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;
15mod 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;
39pub mod tasks;
40
41#[cfg(test)]
42mod code_completion_tests;
43#[cfg(test)]
44mod edit_prediction_tests;
45#[cfg(test)]
46mod editor_tests;
47mod signature_help;
48#[cfg(any(test, feature = "test-support"))]
49pub mod test;
50
51pub(crate) use actions::*;
52pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
53pub use edit_prediction::Direction;
54pub use editor_settings::{
55 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
56 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
57};
58pub use element::{
59 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
60};
61pub use git::blame::BlameRenderer;
62pub use hover_popover::hover_markdown_style;
63pub use inlays::Inlay;
64pub use items::MAX_TAB_TITLE_LEN;
65pub use lsp::CompletionContext;
66pub use lsp_ext::lsp_tasks;
67pub use multi_buffer::{
68 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
69 RowInfo, ToOffset, ToPoint,
70};
71pub use text::Bias;
72
73use ::git::{
74 Restore,
75 blame::{BlameEntry, ParsedCommitMessage},
76 status::FileStatus,
77};
78use aho_corasick::AhoCorasick;
79use anyhow::{Context as _, Result, anyhow};
80use blink_manager::BlinkManager;
81use buffer_diff::DiffHunkStatus;
82use client::{Collaborator, ParticipantIndex, parse_zed_link};
83use clock::ReplicaId;
84use code_context_menus::{
85 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
86 CompletionsMenu, ContextMenuOrigin,
87};
88use collections::{BTreeMap, HashMap, HashSet, VecDeque};
89use convert_case::{Case, Casing};
90use dap::TelemetrySpawnLocation;
91use display_map::*;
92use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
93use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
94use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
95use futures::{
96 FutureExt, StreamExt as _,
97 future::{self, Shared, join},
98 stream::FuturesUnordered,
99};
100use fuzzy::{StringMatch, StringMatchCandidate};
101use git::blame::{GitBlame, GlobalBlameRenderer};
102use gpui::{
103 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
104 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
105 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
106 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
107 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
108 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
109 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
110 div, point, prelude::*, pulsating_between, px, relative, size,
111};
112use hover_links::{HoverLink, HoveredLinkState, find_file};
113use hover_popover::{HoverState, hide_hover};
114use indent_guides::ActiveIndentGuidesState;
115use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
116use itertools::{Either, Itertools};
117use language::{
118 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
119 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
120 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
121 IndentSize, Language, LanguageName, OffsetRangeExt, Point, Runnable, RunnableRange, Selection,
122 SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
123 language_settings::{
124 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
125 all_language_settings, language_settings,
126 },
127 point_from_lsp, point_to_lsp, text_diff_with_options,
128};
129use linked_editing_ranges::refresh_linked_ranges;
130use lsp::{
131 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
132 LanguageServerId,
133};
134use lsp_colors::LspColorData;
135use markdown::Markdown;
136use mouse_context_menu::MouseContextMenu;
137use movement::TextLayoutDetails;
138use multi_buffer::{
139 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
140};
141use parking_lot::Mutex;
142use persistence::DB;
143use project::{
144 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
145 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
146 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
147 ProjectPath, ProjectTransaction, TaskSourceKind,
148 debugger::{
149 breakpoint_store::{
150 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
151 BreakpointStore, BreakpointStoreEvent,
152 },
153 session::{Session, SessionEvent},
154 },
155 git_store::GitStoreEvent,
156 lsp_store::{
157 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
158 OpenLspBufferHandle,
159 },
160 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
161};
162use rand::seq::SliceRandom;
163use rpc::{ErrorCode, ErrorExt, proto::PeerId};
164use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
165use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
166use serde::{Deserialize, Serialize};
167use settings::{
168 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
169 update_settings_file,
170};
171use smallvec::{SmallVec, smallvec};
172use snippet::Snippet;
173use std::{
174 any::{Any, TypeId},
175 borrow::Cow,
176 cell::{OnceCell, RefCell},
177 cmp::{self, Ordering, Reverse},
178 collections::hash_map,
179 iter::{self, Peekable},
180 mem,
181 num::NonZeroU32,
182 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
183 path::{Path, PathBuf},
184 rc::Rc,
185 sync::Arc,
186 time::{Duration, Instant},
187};
188use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
189use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
190use theme::{
191 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
192 observe_buffer_font_size_adjustment,
193};
194use ui::{
195 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
196 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
197};
198use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
199use workspace::{
200 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
201 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
202 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
203 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
204 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
205 searchable::SearchEvent,
206};
207
208use crate::{
209 code_context_menus::CompletionsMenuSource,
210 editor_settings::MultiCursorModifier,
211 hover_links::{find_url, find_url_from_range},
212 inlays::{
213 InlineValueCache,
214 inlay_hints::{LspInlayHintData, inlay_hint_settings},
215 },
216 scroll::{ScrollOffset, ScrollPixelOffset},
217 selections_collection::resolve_selections_wrapping_blocks,
218 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
219};
220
221pub const FILE_HEADER_HEIGHT: u32 = 2;
222pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
223const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
224const MAX_LINE_LEN: usize = 1024;
225const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
226const MAX_SELECTION_HISTORY_LEN: usize = 1024;
227pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
228#[doc(hidden)]
229pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
230pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
231
232pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
233pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
234pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
235pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
236
237pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
238pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
239pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
240
241pub type RenderDiffHunkControlsFn = Arc<
242 dyn Fn(
243 u32,
244 &DiffHunkStatus,
245 Range<Anchor>,
246 bool,
247 Pixels,
248 &Entity<Editor>,
249 &mut Window,
250 &mut App,
251 ) -> AnyElement,
252>;
253
254enum ReportEditorEvent {
255 Saved { auto_saved: bool },
256 EditorOpened,
257 Closed,
258}
259
260impl ReportEditorEvent {
261 pub fn event_type(&self) -> &'static str {
262 match self {
263 Self::Saved { .. } => "Editor Saved",
264 Self::EditorOpened => "Editor Opened",
265 Self::Closed => "Editor Closed",
266 }
267 }
268}
269
270pub enum ActiveDebugLine {}
271pub enum DebugStackFrameLine {}
272enum DocumentHighlightRead {}
273enum DocumentHighlightWrite {}
274enum InputComposition {}
275pub enum PendingInput {}
276enum SelectedTextHighlight {}
277
278pub enum ConflictsOuter {}
279pub enum ConflictsOurs {}
280pub enum ConflictsTheirs {}
281pub enum ConflictsOursMarker {}
282pub enum ConflictsTheirsMarker {}
283
284#[derive(Debug, Copy, Clone, PartialEq, Eq)]
285pub enum Navigated {
286 Yes,
287 No,
288}
289
290impl Navigated {
291 pub fn from_bool(yes: bool) -> Navigated {
292 if yes { Navigated::Yes } else { Navigated::No }
293 }
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
297enum DisplayDiffHunk {
298 Folded {
299 display_row: DisplayRow,
300 },
301 Unfolded {
302 is_created_file: bool,
303 diff_base_byte_range: Range<usize>,
304 display_row_range: Range<DisplayRow>,
305 multi_buffer_range: Range<Anchor>,
306 status: DiffHunkStatus,
307 },
308}
309
310pub enum HideMouseCursorOrigin {
311 TypingAction,
312 MovementAction,
313}
314
315pub fn init(cx: &mut App) {
316 cx.set_global(GlobalBlameRenderer(Arc::new(())));
317
318 workspace::register_project_item::<Editor>(cx);
319 workspace::FollowableViewRegistry::register::<Editor>(cx);
320 workspace::register_serializable_item::<Editor>(cx);
321
322 cx.observe_new(
323 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
324 workspace.register_action(Editor::new_file);
325 workspace.register_action(Editor::new_file_split);
326 workspace.register_action(Editor::new_file_vertical);
327 workspace.register_action(Editor::new_file_horizontal);
328 workspace.register_action(Editor::cancel_language_server_work);
329 workspace.register_action(Editor::toggle_focus);
330 },
331 )
332 .detach();
333
334 cx.on_action(move |_: &workspace::NewFile, cx| {
335 let app_state = workspace::AppState::global(cx);
336 if let Some(app_state) = app_state.upgrade() {
337 workspace::open_new(
338 Default::default(),
339 app_state,
340 cx,
341 |workspace, window, cx| {
342 Editor::new_file(workspace, &Default::default(), window, cx)
343 },
344 )
345 .detach();
346 }
347 });
348 cx.on_action(move |_: &workspace::NewWindow, cx| {
349 let app_state = workspace::AppState::global(cx);
350 if let Some(app_state) = app_state.upgrade() {
351 workspace::open_new(
352 Default::default(),
353 app_state,
354 cx,
355 |workspace, window, cx| {
356 cx.activate(true);
357 Editor::new_file(workspace, &Default::default(), window, cx)
358 },
359 )
360 .detach();
361 }
362 });
363}
364
365pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
366 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
367}
368
369pub trait DiagnosticRenderer {
370 fn render_group(
371 &self,
372 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
373 buffer_id: BufferId,
374 snapshot: EditorSnapshot,
375 editor: WeakEntity<Editor>,
376 cx: &mut App,
377 ) -> Vec<BlockProperties<Anchor>>;
378
379 fn render_hover(
380 &self,
381 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
382 range: Range<Point>,
383 buffer_id: BufferId,
384 cx: &mut App,
385 ) -> Option<Entity<markdown::Markdown>>;
386
387 fn open_link(
388 &self,
389 editor: &mut Editor,
390 link: SharedString,
391 window: &mut Window,
392 cx: &mut Context<Editor>,
393 );
394}
395
396pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
397
398impl GlobalDiagnosticRenderer {
399 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
400 cx.try_global::<Self>().map(|g| g.0.clone())
401 }
402}
403
404impl gpui::Global for GlobalDiagnosticRenderer {}
405pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
406 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
407}
408
409pub struct SearchWithinRange;
410
411trait InvalidationRegion {
412 fn ranges(&self) -> &[Range<Anchor>];
413}
414
415#[derive(Clone, Debug, PartialEq)]
416pub enum SelectPhase {
417 Begin {
418 position: DisplayPoint,
419 add: bool,
420 click_count: usize,
421 },
422 BeginColumnar {
423 position: DisplayPoint,
424 reset: bool,
425 mode: ColumnarMode,
426 goal_column: u32,
427 },
428 Extend {
429 position: DisplayPoint,
430 click_count: usize,
431 },
432 Update {
433 position: DisplayPoint,
434 goal_column: u32,
435 scroll_delta: gpui::Point<f32>,
436 },
437 End,
438}
439
440#[derive(Clone, Debug, PartialEq)]
441pub enum ColumnarMode {
442 FromMouse,
443 FromSelection,
444}
445
446#[derive(Clone, Debug)]
447pub enum SelectMode {
448 Character,
449 Word(Range<Anchor>),
450 Line(Range<Anchor>),
451 All,
452}
453
454#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
455pub enum SizingBehavior {
456 /// The editor will layout itself using `size_full` and will include the vertical
457 /// scroll margin as requested by user settings.
458 #[default]
459 Default,
460 /// The editor will layout itself using `size_full`, but will not have any
461 /// vertical overscroll.
462 ExcludeOverscrollMargin,
463 /// The editor will request a vertical size according to its content and will be
464 /// layouted without a vertical scroll margin.
465 SizeByContent,
466}
467
468#[derive(Clone, PartialEq, Eq, Debug)]
469pub enum EditorMode {
470 SingleLine,
471 AutoHeight {
472 min_lines: usize,
473 max_lines: Option<usize>,
474 },
475 Full {
476 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
477 scale_ui_elements_with_buffer_font_size: bool,
478 /// When set to `true`, the editor will render a background for the active line.
479 show_active_line_background: bool,
480 /// Determines the sizing behavior for this editor
481 sizing_behavior: SizingBehavior,
482 },
483 Minimap {
484 parent: WeakEntity<Editor>,
485 },
486}
487
488impl EditorMode {
489 pub fn full() -> Self {
490 Self::Full {
491 scale_ui_elements_with_buffer_font_size: true,
492 show_active_line_background: true,
493 sizing_behavior: SizingBehavior::Default,
494 }
495 }
496
497 #[inline]
498 pub fn is_full(&self) -> bool {
499 matches!(self, Self::Full { .. })
500 }
501
502 #[inline]
503 pub fn is_single_line(&self) -> bool {
504 matches!(self, Self::SingleLine { .. })
505 }
506
507 #[inline]
508 fn is_minimap(&self) -> bool {
509 matches!(self, Self::Minimap { .. })
510 }
511}
512
513#[derive(Copy, Clone, Debug)]
514pub enum SoftWrap {
515 /// Prefer not to wrap at all.
516 ///
517 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
518 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
519 GitDiff,
520 /// Prefer a single line generally, unless an overly long line is encountered.
521 None,
522 /// Soft wrap lines that exceed the editor width.
523 EditorWidth,
524 /// Soft wrap lines at the preferred line length.
525 Column(u32),
526 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
527 Bounded(u32),
528}
529
530#[derive(Clone)]
531pub struct EditorStyle {
532 pub background: Hsla,
533 pub border: Hsla,
534 pub local_player: PlayerColor,
535 pub text: TextStyle,
536 pub scrollbar_width: Pixels,
537 pub syntax: Arc<SyntaxTheme>,
538 pub status: StatusColors,
539 pub inlay_hints_style: HighlightStyle,
540 pub edit_prediction_styles: EditPredictionStyles,
541 pub unnecessary_code_fade: f32,
542 pub show_underlines: bool,
543}
544
545impl Default for EditorStyle {
546 fn default() -> Self {
547 Self {
548 background: Hsla::default(),
549 border: Hsla::default(),
550 local_player: PlayerColor::default(),
551 text: TextStyle::default(),
552 scrollbar_width: Pixels::default(),
553 syntax: Default::default(),
554 // HACK: Status colors don't have a real default.
555 // We should look into removing the status colors from the editor
556 // style and retrieve them directly from the theme.
557 status: StatusColors::dark(),
558 inlay_hints_style: HighlightStyle::default(),
559 edit_prediction_styles: EditPredictionStyles {
560 insertion: HighlightStyle::default(),
561 whitespace: HighlightStyle::default(),
562 },
563 unnecessary_code_fade: Default::default(),
564 show_underlines: true,
565 }
566 }
567}
568
569pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
570 let show_background = language_settings::language_settings(None, None, cx)
571 .inlay_hints
572 .show_background;
573
574 let mut style = cx.theme().syntax().get("hint");
575
576 if style.color.is_none() {
577 style.color = Some(cx.theme().status().hint);
578 }
579
580 if !show_background {
581 style.background_color = None;
582 return style;
583 }
584
585 if style.background_color.is_none() {
586 style.background_color = Some(cx.theme().status().hint_background);
587 }
588
589 style
590}
591
592pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
593 EditPredictionStyles {
594 insertion: HighlightStyle {
595 color: Some(cx.theme().status().predictive),
596 ..HighlightStyle::default()
597 },
598 whitespace: HighlightStyle {
599 background_color: Some(cx.theme().status().created_background),
600 ..HighlightStyle::default()
601 },
602 }
603}
604
605type CompletionId = usize;
606
607pub(crate) enum EditDisplayMode {
608 TabAccept,
609 DiffPopover,
610 Inline,
611}
612
613enum EditPrediction {
614 Edit {
615 edits: Vec<(Range<Anchor>, Arc<str>)>,
616 edit_preview: Option<EditPreview>,
617 display_mode: EditDisplayMode,
618 snapshot: BufferSnapshot,
619 },
620 /// Move to a specific location in the active editor
621 MoveWithin {
622 target: Anchor,
623 snapshot: BufferSnapshot,
624 },
625 /// Move to a specific location in a different editor (not the active one)
626 MoveOutside {
627 target: language::Anchor,
628 snapshot: BufferSnapshot,
629 },
630}
631
632struct EditPredictionState {
633 inlay_ids: Vec<InlayId>,
634 completion: EditPrediction,
635 completion_id: Option<SharedString>,
636 invalidation_range: Option<Range<Anchor>>,
637}
638
639enum EditPredictionSettings {
640 Disabled,
641 Enabled {
642 show_in_menu: bool,
643 preview_requires_modifier: bool,
644 },
645}
646
647enum EditPredictionHighlight {}
648
649#[derive(Debug, Clone)]
650struct InlineDiagnostic {
651 message: SharedString,
652 group_id: usize,
653 is_primary: bool,
654 start: Point,
655 severity: lsp::DiagnosticSeverity,
656}
657
658pub enum MenuEditPredictionsPolicy {
659 Never,
660 ByProvider,
661}
662
663pub enum EditPredictionPreview {
664 /// Modifier is not pressed
665 Inactive { released_too_fast: bool },
666 /// Modifier pressed
667 Active {
668 since: Instant,
669 previous_scroll_position: Option<ScrollAnchor>,
670 },
671}
672
673impl EditPredictionPreview {
674 pub fn released_too_fast(&self) -> bool {
675 match self {
676 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
677 EditPredictionPreview::Active { .. } => false,
678 }
679 }
680
681 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
682 if let EditPredictionPreview::Active {
683 previous_scroll_position,
684 ..
685 } = self
686 {
687 *previous_scroll_position = scroll_position;
688 }
689 }
690}
691
692pub struct ContextMenuOptions {
693 pub min_entries_visible: usize,
694 pub max_entries_visible: usize,
695 pub placement: Option<ContextMenuPlacement>,
696}
697
698#[derive(Debug, Clone, PartialEq, Eq)]
699pub enum ContextMenuPlacement {
700 Above,
701 Below,
702}
703
704#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
705struct EditorActionId(usize);
706
707impl EditorActionId {
708 pub fn post_inc(&mut self) -> Self {
709 let answer = self.0;
710
711 *self = Self(answer + 1);
712
713 Self(answer)
714 }
715}
716
717// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
718// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
719
720type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
721type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
722
723#[derive(Default)]
724struct ScrollbarMarkerState {
725 scrollbar_size: Size<Pixels>,
726 dirty: bool,
727 markers: Arc<[PaintQuad]>,
728 pending_refresh: Option<Task<Result<()>>>,
729}
730
731impl ScrollbarMarkerState {
732 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
733 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
734 }
735}
736
737#[derive(Clone, Copy, PartialEq, Eq)]
738pub enum MinimapVisibility {
739 Disabled,
740 Enabled {
741 /// The configuration currently present in the users settings.
742 setting_configuration: bool,
743 /// Whether to override the currently set visibility from the users setting.
744 toggle_override: bool,
745 },
746}
747
748impl MinimapVisibility {
749 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
750 if mode.is_full() {
751 Self::Enabled {
752 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
753 toggle_override: false,
754 }
755 } else {
756 Self::Disabled
757 }
758 }
759
760 fn hidden(&self) -> Self {
761 match *self {
762 Self::Enabled {
763 setting_configuration,
764 ..
765 } => Self::Enabled {
766 setting_configuration,
767 toggle_override: setting_configuration,
768 },
769 Self::Disabled => Self::Disabled,
770 }
771 }
772
773 fn disabled(&self) -> bool {
774 matches!(*self, Self::Disabled)
775 }
776
777 fn settings_visibility(&self) -> bool {
778 match *self {
779 Self::Enabled {
780 setting_configuration,
781 ..
782 } => setting_configuration,
783 _ => false,
784 }
785 }
786
787 fn visible(&self) -> bool {
788 match *self {
789 Self::Enabled {
790 setting_configuration,
791 toggle_override,
792 } => setting_configuration ^ toggle_override,
793 _ => false,
794 }
795 }
796
797 fn toggle_visibility(&self) -> Self {
798 match *self {
799 Self::Enabled {
800 toggle_override,
801 setting_configuration,
802 } => Self::Enabled {
803 setting_configuration,
804 toggle_override: !toggle_override,
805 },
806 Self::Disabled => Self::Disabled,
807 }
808 }
809}
810
811#[derive(Debug, Clone, Copy, PartialEq, Eq)]
812pub enum BufferSerialization {
813 All,
814 NonDirtyBuffers,
815}
816
817impl BufferSerialization {
818 fn new(restore_unsaved_buffers: bool) -> Self {
819 if restore_unsaved_buffers {
820 Self::All
821 } else {
822 Self::NonDirtyBuffers
823 }
824 }
825}
826
827#[derive(Clone, Debug)]
828struct RunnableTasks {
829 templates: Vec<(TaskSourceKind, TaskTemplate)>,
830 offset: multi_buffer::Anchor,
831 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
832 column: u32,
833 // Values of all named captures, including those starting with '_'
834 extra_variables: HashMap<String, String>,
835 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
836 context_range: Range<BufferOffset>,
837}
838
839impl RunnableTasks {
840 fn resolve<'a>(
841 &'a self,
842 cx: &'a task::TaskContext,
843 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
844 self.templates.iter().filter_map(|(kind, template)| {
845 template
846 .resolve_task(&kind.to_id_base(), cx)
847 .map(|task| (kind.clone(), task))
848 })
849 }
850}
851
852#[derive(Clone)]
853pub struct ResolvedTasks {
854 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
855 position: Anchor,
856}
857
858#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
859struct BufferOffset(usize);
860
861/// Addons allow storing per-editor state in other crates (e.g. Vim)
862pub trait Addon: 'static {
863 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
864
865 fn render_buffer_header_controls(
866 &self,
867 _: &ExcerptInfo,
868 _: &Window,
869 _: &App,
870 ) -> Option<AnyElement> {
871 None
872 }
873
874 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
875 None
876 }
877
878 fn to_any(&self) -> &dyn std::any::Any;
879
880 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
881 None
882 }
883}
884
885struct ChangeLocation {
886 current: Option<Vec<Anchor>>,
887 original: Vec<Anchor>,
888}
889impl ChangeLocation {
890 fn locations(&self) -> &[Anchor] {
891 self.current.as_ref().unwrap_or(&self.original)
892 }
893}
894
895/// A set of caret positions, registered when the editor was edited.
896pub struct ChangeList {
897 changes: Vec<ChangeLocation>,
898 /// Currently "selected" change.
899 position: Option<usize>,
900}
901
902impl ChangeList {
903 pub fn new() -> Self {
904 Self {
905 changes: Vec::new(),
906 position: None,
907 }
908 }
909
910 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
911 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
912 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
913 if self.changes.is_empty() {
914 return None;
915 }
916
917 let prev = self.position.unwrap_or(self.changes.len());
918 let next = if direction == Direction::Prev {
919 prev.saturating_sub(count)
920 } else {
921 (prev + count).min(self.changes.len() - 1)
922 };
923 self.position = Some(next);
924 self.changes.get(next).map(|change| change.locations())
925 }
926
927 /// Adds a new change to the list, resetting the change list position.
928 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
929 self.position.take();
930 if let Some(last) = self.changes.last_mut()
931 && group
932 {
933 last.current = Some(new_positions)
934 } else {
935 self.changes.push(ChangeLocation {
936 original: new_positions,
937 current: None,
938 });
939 }
940 }
941
942 pub fn last(&self) -> Option<&[Anchor]> {
943 self.changes.last().map(|change| change.locations())
944 }
945
946 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
947 self.changes.last().map(|change| change.original.as_slice())
948 }
949
950 pub fn invert_last_group(&mut self) {
951 if let Some(last) = self.changes.last_mut()
952 && let Some(current) = last.current.as_mut()
953 {
954 mem::swap(&mut last.original, current);
955 }
956 }
957}
958
959#[derive(Clone)]
960struct InlineBlamePopoverState {
961 scroll_handle: ScrollHandle,
962 commit_message: Option<ParsedCommitMessage>,
963 markdown: Entity<Markdown>,
964}
965
966struct InlineBlamePopover {
967 position: gpui::Point<Pixels>,
968 hide_task: Option<Task<()>>,
969 popover_bounds: Option<Bounds<Pixels>>,
970 popover_state: InlineBlamePopoverState,
971 keyboard_grace: bool,
972}
973
974enum SelectionDragState {
975 /// State when no drag related activity is detected.
976 None,
977 /// State when the mouse is down on a selection that is about to be dragged.
978 ReadyToDrag {
979 selection: Selection<Anchor>,
980 click_position: gpui::Point<Pixels>,
981 mouse_down_time: Instant,
982 },
983 /// State when the mouse is dragging the selection in the editor.
984 Dragging {
985 selection: Selection<Anchor>,
986 drop_cursor: Selection<Anchor>,
987 hide_drop_cursor: bool,
988 },
989}
990
991enum ColumnarSelectionState {
992 FromMouse {
993 selection_tail: Anchor,
994 display_point: Option<DisplayPoint>,
995 },
996 FromSelection {
997 selection_tail: Anchor,
998 },
999}
1000
1001/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1002/// a breakpoint on them.
1003#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1004struct PhantomBreakpointIndicator {
1005 display_row: DisplayRow,
1006 /// There's a small debounce between hovering over the line and showing the indicator.
1007 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1008 is_active: bool,
1009 collides_with_existing_breakpoint: bool,
1010}
1011
1012/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1013///
1014/// See the [module level documentation](self) for more information.
1015pub struct Editor {
1016 focus_handle: FocusHandle,
1017 last_focused_descendant: Option<WeakFocusHandle>,
1018 /// The text buffer being edited
1019 buffer: Entity<MultiBuffer>,
1020 /// Map of how text in the buffer should be displayed.
1021 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1022 pub display_map: Entity<DisplayMap>,
1023 placeholder_display_map: Option<Entity<DisplayMap>>,
1024 pub selections: SelectionsCollection,
1025 pub scroll_manager: ScrollManager,
1026 /// When inline assist editors are linked, they all render cursors because
1027 /// typing enters text into each of them, even the ones that aren't focused.
1028 pub(crate) show_cursor_when_unfocused: bool,
1029 columnar_selection_state: Option<ColumnarSelectionState>,
1030 add_selections_state: Option<AddSelectionsState>,
1031 select_next_state: Option<SelectNextState>,
1032 select_prev_state: Option<SelectNextState>,
1033 selection_history: SelectionHistory,
1034 defer_selection_effects: bool,
1035 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1036 autoclose_regions: Vec<AutocloseRegion>,
1037 snippet_stack: InvalidationStack<SnippetState>,
1038 select_syntax_node_history: SelectSyntaxNodeHistory,
1039 ime_transaction: Option<TransactionId>,
1040 pub diagnostics_max_severity: DiagnosticSeverity,
1041 active_diagnostics: ActiveDiagnostic,
1042 show_inline_diagnostics: bool,
1043 inline_diagnostics_update: Task<()>,
1044 inline_diagnostics_enabled: bool,
1045 diagnostics_enabled: bool,
1046 word_completions_enabled: bool,
1047 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1048 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1049 hard_wrap: Option<usize>,
1050 project: Option<Entity<Project>>,
1051 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1052 completion_provider: Option<Rc<dyn CompletionProvider>>,
1053 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1054 blink_manager: Entity<BlinkManager>,
1055 show_cursor_names: bool,
1056 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1057 pub show_local_selections: bool,
1058 mode: EditorMode,
1059 show_breadcrumbs: bool,
1060 show_gutter: bool,
1061 show_scrollbars: ScrollbarAxes,
1062 minimap_visibility: MinimapVisibility,
1063 offset_content: bool,
1064 disable_expand_excerpt_buttons: bool,
1065 show_line_numbers: Option<bool>,
1066 use_relative_line_numbers: Option<bool>,
1067 show_git_diff_gutter: Option<bool>,
1068 show_code_actions: Option<bool>,
1069 show_runnables: Option<bool>,
1070 show_breakpoints: Option<bool>,
1071 show_wrap_guides: Option<bool>,
1072 show_indent_guides: Option<bool>,
1073 highlight_order: usize,
1074 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1075 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1076 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1077 scrollbar_marker_state: ScrollbarMarkerState,
1078 active_indent_guides_state: ActiveIndentGuidesState,
1079 nav_history: Option<ItemNavHistory>,
1080 context_menu: RefCell<Option<CodeContextMenu>>,
1081 context_menu_options: Option<ContextMenuOptions>,
1082 mouse_context_menu: Option<MouseContextMenu>,
1083 completion_tasks: Vec<(CompletionId, Task<()>)>,
1084 inline_blame_popover: Option<InlineBlamePopover>,
1085 inline_blame_popover_show_task: Option<Task<()>>,
1086 signature_help_state: SignatureHelpState,
1087 auto_signature_help: Option<bool>,
1088 find_all_references_task_sources: Vec<Anchor>,
1089 next_completion_id: CompletionId,
1090 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1091 code_actions_task: Option<Task<Result<()>>>,
1092 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1093 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1094 document_highlights_task: Option<Task<()>>,
1095 linked_editing_range_task: Option<Task<Option<()>>>,
1096 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1097 pending_rename: Option<RenameState>,
1098 searchable: bool,
1099 cursor_shape: CursorShape,
1100 current_line_highlight: Option<CurrentLineHighlight>,
1101 autoindent_mode: Option<AutoindentMode>,
1102 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1103 input_enabled: bool,
1104 use_modal_editing: bool,
1105 read_only: bool,
1106 leader_id: Option<CollaboratorId>,
1107 remote_id: Option<ViewId>,
1108 pub hover_state: HoverState,
1109 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1110 gutter_hovered: bool,
1111 hovered_link_state: Option<HoveredLinkState>,
1112 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1113 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1114 active_edit_prediction: Option<EditPredictionState>,
1115 /// Used to prevent flickering as the user types while the menu is open
1116 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1117 edit_prediction_settings: EditPredictionSettings,
1118 edit_predictions_hidden_for_vim_mode: bool,
1119 show_edit_predictions_override: Option<bool>,
1120 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1121 edit_prediction_preview: EditPredictionPreview,
1122 edit_prediction_indent_conflict: bool,
1123 edit_prediction_requires_modifier_in_indent_conflict: bool,
1124 next_inlay_id: usize,
1125 next_color_inlay_id: usize,
1126 _subscriptions: Vec<Subscription>,
1127 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1128 gutter_dimensions: GutterDimensions,
1129 style: Option<EditorStyle>,
1130 text_style_refinement: Option<TextStyleRefinement>,
1131 next_editor_action_id: EditorActionId,
1132 editor_actions: Rc<
1133 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1134 >,
1135 use_autoclose: bool,
1136 use_auto_surround: bool,
1137 auto_replace_emoji_shortcode: bool,
1138 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1139 show_git_blame_gutter: bool,
1140 show_git_blame_inline: bool,
1141 show_git_blame_inline_delay_task: Option<Task<()>>,
1142 git_blame_inline_enabled: bool,
1143 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1144 buffer_serialization: Option<BufferSerialization>,
1145 show_selection_menu: Option<bool>,
1146 blame: Option<Entity<GitBlame>>,
1147 blame_subscription: Option<Subscription>,
1148 custom_context_menu: Option<
1149 Box<
1150 dyn 'static
1151 + Fn(
1152 &mut Self,
1153 DisplayPoint,
1154 &mut Window,
1155 &mut Context<Self>,
1156 ) -> Option<Entity<ui::ContextMenu>>,
1157 >,
1158 >,
1159 last_bounds: Option<Bounds<Pixels>>,
1160 last_position_map: Option<Rc<PositionMap>>,
1161 expect_bounds_change: Option<Bounds<Pixels>>,
1162 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1163 tasks_update_task: Option<Task<()>>,
1164 breakpoint_store: Option<Entity<BreakpointStore>>,
1165 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1166 hovered_diff_hunk_row: Option<DisplayRow>,
1167 pull_diagnostics_task: Task<()>,
1168 in_project_search: bool,
1169 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1170 breadcrumb_header: Option<String>,
1171 focused_block: Option<FocusedBlock>,
1172 next_scroll_position: NextScrollCursorCenterTopBottom,
1173 addons: HashMap<TypeId, Box<dyn Addon>>,
1174 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1175 load_diff_task: Option<Shared<Task<()>>>,
1176 /// Whether we are temporarily displaying a diff other than git's
1177 temporary_diff_override: bool,
1178 selection_mark_mode: bool,
1179 toggle_fold_multiple_buffers: Task<()>,
1180 _scroll_cursor_center_top_bottom_task: Task<()>,
1181 serialize_selections: Task<()>,
1182 serialize_folds: Task<()>,
1183 mouse_cursor_hidden: bool,
1184 minimap: Option<Entity<Self>>,
1185 hide_mouse_mode: HideMouseMode,
1186 pub change_list: ChangeList,
1187 inline_value_cache: InlineValueCache,
1188 selection_drag_state: SelectionDragState,
1189 colors: Option<LspColorData>,
1190 post_scroll_update: Task<()>,
1191 refresh_colors_task: Task<()>,
1192 inlay_hints: Option<LspInlayHintData>,
1193 folding_newlines: Task<()>,
1194 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1195 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1196 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1197}
1198
1199fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1200 if debounce_ms > 0 {
1201 Some(Duration::from_millis(debounce_ms))
1202 } else {
1203 None
1204 }
1205}
1206
1207#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1208enum NextScrollCursorCenterTopBottom {
1209 #[default]
1210 Center,
1211 Top,
1212 Bottom,
1213}
1214
1215impl NextScrollCursorCenterTopBottom {
1216 fn next(&self) -> Self {
1217 match self {
1218 Self::Center => Self::Top,
1219 Self::Top => Self::Bottom,
1220 Self::Bottom => Self::Center,
1221 }
1222 }
1223}
1224
1225#[derive(Clone)]
1226pub struct EditorSnapshot {
1227 pub mode: EditorMode,
1228 show_gutter: bool,
1229 show_line_numbers: Option<bool>,
1230 show_git_diff_gutter: Option<bool>,
1231 show_code_actions: Option<bool>,
1232 show_runnables: Option<bool>,
1233 show_breakpoints: Option<bool>,
1234 git_blame_gutter_max_author_length: Option<usize>,
1235 pub display_snapshot: DisplaySnapshot,
1236 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1237 is_focused: bool,
1238 scroll_anchor: ScrollAnchor,
1239 ongoing_scroll: OngoingScroll,
1240 current_line_highlight: CurrentLineHighlight,
1241 gutter_hovered: bool,
1242}
1243
1244#[derive(Default, Debug, Clone, Copy)]
1245pub struct GutterDimensions {
1246 pub left_padding: Pixels,
1247 pub right_padding: Pixels,
1248 pub width: Pixels,
1249 pub margin: Pixels,
1250 pub git_blame_entries_width: Option<Pixels>,
1251}
1252
1253impl GutterDimensions {
1254 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1255 Self {
1256 margin: Self::default_gutter_margin(font_id, font_size, cx),
1257 ..Default::default()
1258 }
1259 }
1260
1261 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1262 -cx.text_system().descent(font_id, font_size)
1263 }
1264 /// The full width of the space taken up by the gutter.
1265 pub fn full_width(&self) -> Pixels {
1266 self.margin + self.width
1267 }
1268
1269 /// The width of the space reserved for the fold indicators,
1270 /// use alongside 'justify_end' and `gutter_width` to
1271 /// right align content with the line numbers
1272 pub fn fold_area_width(&self) -> Pixels {
1273 self.margin + self.right_padding
1274 }
1275}
1276
1277struct CharacterDimensions {
1278 em_width: Pixels,
1279 em_advance: Pixels,
1280 line_height: Pixels,
1281}
1282
1283#[derive(Debug)]
1284pub struct RemoteSelection {
1285 pub replica_id: ReplicaId,
1286 pub selection: Selection<Anchor>,
1287 pub cursor_shape: CursorShape,
1288 pub collaborator_id: CollaboratorId,
1289 pub line_mode: bool,
1290 pub user_name: Option<SharedString>,
1291 pub color: PlayerColor,
1292}
1293
1294#[derive(Clone, Debug)]
1295struct SelectionHistoryEntry {
1296 selections: Arc<[Selection<Anchor>]>,
1297 select_next_state: Option<SelectNextState>,
1298 select_prev_state: Option<SelectNextState>,
1299 add_selections_state: Option<AddSelectionsState>,
1300}
1301
1302#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1303enum SelectionHistoryMode {
1304 Normal,
1305 Undoing,
1306 Redoing,
1307 Skipping,
1308}
1309
1310#[derive(Clone, PartialEq, Eq, Hash)]
1311struct HoveredCursor {
1312 replica_id: ReplicaId,
1313 selection_id: usize,
1314}
1315
1316impl Default for SelectionHistoryMode {
1317 fn default() -> Self {
1318 Self::Normal
1319 }
1320}
1321
1322#[derive(Debug)]
1323/// SelectionEffects controls the side-effects of updating the selection.
1324///
1325/// The default behaviour does "what you mostly want":
1326/// - it pushes to the nav history if the cursor moved by >10 lines
1327/// - it re-triggers completion requests
1328/// - it scrolls to fit
1329///
1330/// You might want to modify these behaviours. For example when doing a "jump"
1331/// like go to definition, we always want to add to nav history; but when scrolling
1332/// in vim mode we never do.
1333///
1334/// Similarly, you might want to disable scrolling if you don't want the viewport to
1335/// move.
1336#[derive(Clone)]
1337pub struct SelectionEffects {
1338 nav_history: Option<bool>,
1339 completions: bool,
1340 scroll: Option<Autoscroll>,
1341}
1342
1343impl Default for SelectionEffects {
1344 fn default() -> Self {
1345 Self {
1346 nav_history: None,
1347 completions: true,
1348 scroll: Some(Autoscroll::fit()),
1349 }
1350 }
1351}
1352impl SelectionEffects {
1353 pub fn scroll(scroll: Autoscroll) -> Self {
1354 Self {
1355 scroll: Some(scroll),
1356 ..Default::default()
1357 }
1358 }
1359
1360 pub fn no_scroll() -> Self {
1361 Self {
1362 scroll: None,
1363 ..Default::default()
1364 }
1365 }
1366
1367 pub fn completions(self, completions: bool) -> Self {
1368 Self {
1369 completions,
1370 ..self
1371 }
1372 }
1373
1374 pub fn nav_history(self, nav_history: bool) -> Self {
1375 Self {
1376 nav_history: Some(nav_history),
1377 ..self
1378 }
1379 }
1380}
1381
1382struct DeferredSelectionEffectsState {
1383 changed: bool,
1384 effects: SelectionEffects,
1385 old_cursor_position: Anchor,
1386 history_entry: SelectionHistoryEntry,
1387}
1388
1389#[derive(Default)]
1390struct SelectionHistory {
1391 #[allow(clippy::type_complexity)]
1392 selections_by_transaction:
1393 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1394 mode: SelectionHistoryMode,
1395 undo_stack: VecDeque<SelectionHistoryEntry>,
1396 redo_stack: VecDeque<SelectionHistoryEntry>,
1397}
1398
1399impl SelectionHistory {
1400 #[track_caller]
1401 fn insert_transaction(
1402 &mut self,
1403 transaction_id: TransactionId,
1404 selections: Arc<[Selection<Anchor>]>,
1405 ) {
1406 if selections.is_empty() {
1407 log::error!(
1408 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1409 std::panic::Location::caller()
1410 );
1411 return;
1412 }
1413 self.selections_by_transaction
1414 .insert(transaction_id, (selections, None));
1415 }
1416
1417 #[allow(clippy::type_complexity)]
1418 fn transaction(
1419 &self,
1420 transaction_id: TransactionId,
1421 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1422 self.selections_by_transaction.get(&transaction_id)
1423 }
1424
1425 #[allow(clippy::type_complexity)]
1426 fn transaction_mut(
1427 &mut self,
1428 transaction_id: TransactionId,
1429 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1430 self.selections_by_transaction.get_mut(&transaction_id)
1431 }
1432
1433 fn push(&mut self, entry: SelectionHistoryEntry) {
1434 if !entry.selections.is_empty() {
1435 match self.mode {
1436 SelectionHistoryMode::Normal => {
1437 self.push_undo(entry);
1438 self.redo_stack.clear();
1439 }
1440 SelectionHistoryMode::Undoing => self.push_redo(entry),
1441 SelectionHistoryMode::Redoing => self.push_undo(entry),
1442 SelectionHistoryMode::Skipping => {}
1443 }
1444 }
1445 }
1446
1447 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1448 if self
1449 .undo_stack
1450 .back()
1451 .is_none_or(|e| e.selections != entry.selections)
1452 {
1453 self.undo_stack.push_back(entry);
1454 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1455 self.undo_stack.pop_front();
1456 }
1457 }
1458 }
1459
1460 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1461 if self
1462 .redo_stack
1463 .back()
1464 .is_none_or(|e| e.selections != entry.selections)
1465 {
1466 self.redo_stack.push_back(entry);
1467 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1468 self.redo_stack.pop_front();
1469 }
1470 }
1471 }
1472}
1473
1474#[derive(Clone, Copy)]
1475pub struct RowHighlightOptions {
1476 pub autoscroll: bool,
1477 pub include_gutter: bool,
1478}
1479
1480impl Default for RowHighlightOptions {
1481 fn default() -> Self {
1482 Self {
1483 autoscroll: Default::default(),
1484 include_gutter: true,
1485 }
1486 }
1487}
1488
1489struct RowHighlight {
1490 index: usize,
1491 range: Range<Anchor>,
1492 color: Hsla,
1493 options: RowHighlightOptions,
1494 type_id: TypeId,
1495}
1496
1497#[derive(Clone, Debug)]
1498struct AddSelectionsState {
1499 groups: Vec<AddSelectionsGroup>,
1500}
1501
1502#[derive(Clone, Debug)]
1503struct AddSelectionsGroup {
1504 above: bool,
1505 stack: Vec<usize>,
1506}
1507
1508#[derive(Clone)]
1509struct SelectNextState {
1510 query: AhoCorasick,
1511 wordwise: bool,
1512 done: bool,
1513}
1514
1515impl std::fmt::Debug for SelectNextState {
1516 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1517 f.debug_struct(std::any::type_name::<Self>())
1518 .field("wordwise", &self.wordwise)
1519 .field("done", &self.done)
1520 .finish()
1521 }
1522}
1523
1524#[derive(Debug)]
1525struct AutocloseRegion {
1526 selection_id: usize,
1527 range: Range<Anchor>,
1528 pair: BracketPair,
1529}
1530
1531#[derive(Debug)]
1532struct SnippetState {
1533 ranges: Vec<Vec<Range<Anchor>>>,
1534 active_index: usize,
1535 choices: Vec<Option<Vec<String>>>,
1536}
1537
1538#[doc(hidden)]
1539pub struct RenameState {
1540 pub range: Range<Anchor>,
1541 pub old_name: Arc<str>,
1542 pub editor: Entity<Editor>,
1543 block_id: CustomBlockId,
1544}
1545
1546struct InvalidationStack<T>(Vec<T>);
1547
1548struct RegisteredEditPredictionProvider {
1549 provider: Arc<dyn EditPredictionProviderHandle>,
1550 _subscription: Subscription,
1551}
1552
1553#[derive(Debug, PartialEq, Eq)]
1554pub struct ActiveDiagnosticGroup {
1555 pub active_range: Range<Anchor>,
1556 pub active_message: String,
1557 pub group_id: usize,
1558 pub blocks: HashSet<CustomBlockId>,
1559}
1560
1561#[derive(Debug, PartialEq, Eq)]
1562
1563pub(crate) enum ActiveDiagnostic {
1564 None,
1565 All,
1566 Group(ActiveDiagnosticGroup),
1567}
1568
1569#[derive(Serialize, Deserialize, Clone, Debug)]
1570pub struct ClipboardSelection {
1571 /// The number of bytes in this selection.
1572 pub len: usize,
1573 /// Whether this was a full-line selection.
1574 pub is_entire_line: bool,
1575 /// The indentation of the first line when this content was originally copied.
1576 pub first_line_indent: u32,
1577}
1578
1579// selections, scroll behavior, was newest selection reversed
1580type SelectSyntaxNodeHistoryState = (
1581 Box<[Selection<usize>]>,
1582 SelectSyntaxNodeScrollBehavior,
1583 bool,
1584);
1585
1586#[derive(Default)]
1587struct SelectSyntaxNodeHistory {
1588 stack: Vec<SelectSyntaxNodeHistoryState>,
1589 // disable temporarily to allow changing selections without losing the stack
1590 pub disable_clearing: bool,
1591}
1592
1593impl SelectSyntaxNodeHistory {
1594 pub fn try_clear(&mut self) {
1595 if !self.disable_clearing {
1596 self.stack.clear();
1597 }
1598 }
1599
1600 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1601 self.stack.push(selection);
1602 }
1603
1604 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1605 self.stack.pop()
1606 }
1607}
1608
1609enum SelectSyntaxNodeScrollBehavior {
1610 CursorTop,
1611 FitSelection,
1612 CursorBottom,
1613}
1614
1615#[derive(Debug)]
1616pub(crate) struct NavigationData {
1617 cursor_anchor: Anchor,
1618 cursor_position: Point,
1619 scroll_anchor: ScrollAnchor,
1620 scroll_top_row: u32,
1621}
1622
1623#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1624pub enum GotoDefinitionKind {
1625 Symbol,
1626 Declaration,
1627 Type,
1628 Implementation,
1629}
1630
1631pub enum FormatTarget {
1632 Buffers(HashSet<Entity<Buffer>>),
1633 Ranges(Vec<Range<MultiBufferPoint>>),
1634}
1635
1636pub(crate) struct FocusedBlock {
1637 id: BlockId,
1638 focus_handle: WeakFocusHandle,
1639}
1640
1641#[derive(Clone)]
1642enum JumpData {
1643 MultiBufferRow {
1644 row: MultiBufferRow,
1645 line_offset_from_top: u32,
1646 },
1647 MultiBufferPoint {
1648 excerpt_id: ExcerptId,
1649 position: Point,
1650 anchor: text::Anchor,
1651 line_offset_from_top: u32,
1652 },
1653}
1654
1655pub enum MultibufferSelectionMode {
1656 First,
1657 All,
1658}
1659
1660#[derive(Clone, Copy, Debug, Default)]
1661pub struct RewrapOptions {
1662 pub override_language_settings: bool,
1663 pub preserve_existing_whitespace: bool,
1664}
1665
1666impl Editor {
1667 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1668 let buffer = cx.new(|cx| Buffer::local("", cx));
1669 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1670 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1671 }
1672
1673 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::full(), buffer, None, window, cx)
1677 }
1678
1679 pub fn auto_height(
1680 min_lines: usize,
1681 max_lines: usize,
1682 window: &mut Window,
1683 cx: &mut Context<Self>,
1684 ) -> Self {
1685 let buffer = cx.new(|cx| Buffer::local("", cx));
1686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1687 Self::new(
1688 EditorMode::AutoHeight {
1689 min_lines,
1690 max_lines: Some(max_lines),
1691 },
1692 buffer,
1693 None,
1694 window,
1695 cx,
1696 )
1697 }
1698
1699 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1700 /// The editor grows as tall as needed to fit its content.
1701 pub fn auto_height_unbounded(
1702 min_lines: usize,
1703 window: &mut Window,
1704 cx: &mut Context<Self>,
1705 ) -> Self {
1706 let buffer = cx.new(|cx| Buffer::local("", cx));
1707 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1708 Self::new(
1709 EditorMode::AutoHeight {
1710 min_lines,
1711 max_lines: None,
1712 },
1713 buffer,
1714 None,
1715 window,
1716 cx,
1717 )
1718 }
1719
1720 pub fn for_buffer(
1721 buffer: Entity<Buffer>,
1722 project: Option<Entity<Project>>,
1723 window: &mut Window,
1724 cx: &mut Context<Self>,
1725 ) -> Self {
1726 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1727 Self::new(EditorMode::full(), buffer, project, window, cx)
1728 }
1729
1730 pub fn for_multibuffer(
1731 buffer: Entity<MultiBuffer>,
1732 project: Option<Entity<Project>>,
1733 window: &mut Window,
1734 cx: &mut Context<Self>,
1735 ) -> Self {
1736 Self::new(EditorMode::full(), buffer, project, window, cx)
1737 }
1738
1739 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1740 let mut clone = Self::new(
1741 self.mode.clone(),
1742 self.buffer.clone(),
1743 self.project.clone(),
1744 window,
1745 cx,
1746 );
1747 self.display_map.update(cx, |display_map, cx| {
1748 let snapshot = display_map.snapshot(cx);
1749 clone.display_map.update(cx, |display_map, cx| {
1750 display_map.set_state(&snapshot, cx);
1751 });
1752 });
1753 clone.folds_did_change(cx);
1754 clone.selections.clone_state(&self.selections);
1755 clone.scroll_manager.clone_state(&self.scroll_manager);
1756 clone.searchable = self.searchable;
1757 clone.read_only = self.read_only;
1758 clone
1759 }
1760
1761 pub fn new(
1762 mode: EditorMode,
1763 buffer: Entity<MultiBuffer>,
1764 project: Option<Entity<Project>>,
1765 window: &mut Window,
1766 cx: &mut Context<Self>,
1767 ) -> Self {
1768 Editor::new_internal(mode, buffer, project, None, window, cx)
1769 }
1770
1771 fn new_internal(
1772 mode: EditorMode,
1773 multi_buffer: Entity<MultiBuffer>,
1774 project: Option<Entity<Project>>,
1775 display_map: Option<Entity<DisplayMap>>,
1776 window: &mut Window,
1777 cx: &mut Context<Self>,
1778 ) -> Self {
1779 debug_assert!(
1780 display_map.is_none() || mode.is_minimap(),
1781 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1782 );
1783
1784 let full_mode = mode.is_full();
1785 let is_minimap = mode.is_minimap();
1786 let diagnostics_max_severity = if full_mode {
1787 EditorSettings::get_global(cx)
1788 .diagnostics_max_severity
1789 .unwrap_or(DiagnosticSeverity::Hint)
1790 } else {
1791 DiagnosticSeverity::Off
1792 };
1793 let style = window.text_style();
1794 let font_size = style.font_size.to_pixels(window.rem_size());
1795 let editor = cx.entity().downgrade();
1796 let fold_placeholder = FoldPlaceholder {
1797 constrain_width: false,
1798 render: Arc::new(move |fold_id, fold_range, cx| {
1799 let editor = editor.clone();
1800 div()
1801 .id(fold_id)
1802 .bg(cx.theme().colors().ghost_element_background)
1803 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1804 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1805 .rounded_xs()
1806 .size_full()
1807 .cursor_pointer()
1808 .child("⋯")
1809 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1810 .on_click(move |_, _window, cx| {
1811 editor
1812 .update(cx, |editor, cx| {
1813 editor.unfold_ranges(
1814 &[fold_range.start..fold_range.end],
1815 true,
1816 false,
1817 cx,
1818 );
1819 cx.stop_propagation();
1820 })
1821 .ok();
1822 })
1823 .into_any()
1824 }),
1825 merge_adjacent: true,
1826 ..FoldPlaceholder::default()
1827 };
1828 let display_map = display_map.unwrap_or_else(|| {
1829 cx.new(|cx| {
1830 DisplayMap::new(
1831 multi_buffer.clone(),
1832 style.font(),
1833 font_size,
1834 None,
1835 FILE_HEADER_HEIGHT,
1836 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1837 fold_placeholder,
1838 diagnostics_max_severity,
1839 cx,
1840 )
1841 })
1842 });
1843
1844 let selections = SelectionsCollection::new();
1845
1846 let blink_manager = cx.new(|cx| {
1847 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1848 if is_minimap {
1849 blink_manager.disable(cx);
1850 }
1851 blink_manager
1852 });
1853
1854 let soft_wrap_mode_override =
1855 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1856
1857 let mut project_subscriptions = Vec::new();
1858 if full_mode && let Some(project) = project.as_ref() {
1859 project_subscriptions.push(cx.subscribe_in(
1860 project,
1861 window,
1862 |editor, _, event, window, cx| match event {
1863 project::Event::RefreshCodeLens => {
1864 // we always query lens with actions, without storing them, always refreshing them
1865 }
1866 project::Event::RefreshInlayHints {
1867 server_id,
1868 request_id,
1869 } => {
1870 editor.refresh_inlay_hints(
1871 InlayHintRefreshReason::RefreshRequested {
1872 server_id: *server_id,
1873 request_id: *request_id,
1874 },
1875 cx,
1876 );
1877 }
1878 project::Event::LanguageServerRemoved(..) => {
1879 if editor.tasks_update_task.is_none() {
1880 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1881 }
1882 editor.registered_buffers.clear();
1883 editor.register_visible_buffers(cx);
1884 }
1885 project::Event::LanguageServerAdded(..) => {
1886 if editor.tasks_update_task.is_none() {
1887 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1888 }
1889 }
1890 project::Event::SnippetEdit(id, snippet_edits) => {
1891 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1892 let focus_handle = editor.focus_handle(cx);
1893 if focus_handle.is_focused(window) {
1894 let snapshot = buffer.read(cx).snapshot();
1895 for (range, snippet) in snippet_edits {
1896 let editor_range =
1897 language::range_from_lsp(*range).to_offset(&snapshot);
1898 editor
1899 .insert_snippet(
1900 &[editor_range],
1901 snippet.clone(),
1902 window,
1903 cx,
1904 )
1905 .ok();
1906 }
1907 }
1908 }
1909 }
1910 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1911 let buffer_id = *buffer_id;
1912 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1913 editor.register_buffer(buffer_id, cx);
1914 editor.update_lsp_data(Some(buffer_id), window, cx);
1915 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1916 refresh_linked_ranges(editor, window, cx);
1917 editor.refresh_code_actions(window, cx);
1918 editor.refresh_document_highlights(cx);
1919 }
1920 }
1921
1922 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1923 let Some(workspace) = editor.workspace() else {
1924 return;
1925 };
1926 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1927 else {
1928 return;
1929 };
1930
1931 if active_editor.entity_id() == cx.entity_id() {
1932 let entity_id = cx.entity_id();
1933 workspace.update(cx, |this, cx| {
1934 this.panes_mut()
1935 .iter_mut()
1936 .filter(|pane| pane.entity_id() != entity_id)
1937 .for_each(|p| {
1938 p.update(cx, |pane, _| {
1939 pane.nav_history_mut().rename_item(
1940 entity_id,
1941 project_path.clone(),
1942 abs_path.clone().into(),
1943 );
1944 })
1945 });
1946 });
1947 let edited_buffers_already_open = {
1948 let other_editors: Vec<Entity<Editor>> = workspace
1949 .read(cx)
1950 .panes()
1951 .iter()
1952 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1953 .filter(|editor| editor.entity_id() != cx.entity_id())
1954 .collect();
1955
1956 transaction.0.keys().all(|buffer| {
1957 other_editors.iter().any(|editor| {
1958 let multi_buffer = editor.read(cx).buffer();
1959 multi_buffer.read(cx).is_singleton()
1960 && multi_buffer.read(cx).as_singleton().map_or(
1961 false,
1962 |singleton| {
1963 singleton.entity_id() == buffer.entity_id()
1964 },
1965 )
1966 })
1967 })
1968 };
1969 if !edited_buffers_already_open {
1970 let workspace = workspace.downgrade();
1971 let transaction = transaction.clone();
1972 cx.defer_in(window, move |_, window, cx| {
1973 cx.spawn_in(window, async move |editor, cx| {
1974 Self::open_project_transaction(
1975 &editor,
1976 workspace,
1977 transaction,
1978 "Rename".to_string(),
1979 cx,
1980 )
1981 .await
1982 .ok()
1983 })
1984 .detach();
1985 });
1986 }
1987 }
1988 }
1989
1990 _ => {}
1991 },
1992 ));
1993 if let Some(task_inventory) = project
1994 .read(cx)
1995 .task_store()
1996 .read(cx)
1997 .task_inventory()
1998 .cloned()
1999 {
2000 project_subscriptions.push(cx.observe_in(
2001 &task_inventory,
2002 window,
2003 |editor, _, window, cx| {
2004 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2005 },
2006 ));
2007 };
2008
2009 project_subscriptions.push(cx.subscribe_in(
2010 &project.read(cx).breakpoint_store(),
2011 window,
2012 |editor, _, event, window, cx| match event {
2013 BreakpointStoreEvent::ClearDebugLines => {
2014 editor.clear_row_highlights::<ActiveDebugLine>();
2015 editor.refresh_inline_values(cx);
2016 }
2017 BreakpointStoreEvent::SetDebugLine => {
2018 if editor.go_to_active_debug_line(window, cx) {
2019 cx.stop_propagation();
2020 }
2021
2022 editor.refresh_inline_values(cx);
2023 }
2024 _ => {}
2025 },
2026 ));
2027 let git_store = project.read(cx).git_store().clone();
2028 let project = project.clone();
2029 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2030 if let GitStoreEvent::RepositoryAdded = event {
2031 this.load_diff_task = Some(
2032 update_uncommitted_diff_for_buffer(
2033 cx.entity(),
2034 &project,
2035 this.buffer.read(cx).all_buffers(),
2036 this.buffer.clone(),
2037 cx,
2038 )
2039 .shared(),
2040 );
2041 }
2042 }));
2043 }
2044
2045 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2046
2047 let inlay_hint_settings =
2048 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2049 let focus_handle = cx.focus_handle();
2050 if !is_minimap {
2051 cx.on_focus(&focus_handle, window, Self::handle_focus)
2052 .detach();
2053 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2054 .detach();
2055 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2056 .detach();
2057 cx.on_blur(&focus_handle, window, Self::handle_blur)
2058 .detach();
2059 cx.observe_pending_input(window, Self::observe_pending_input)
2060 .detach();
2061 }
2062
2063 let show_indent_guides =
2064 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2065 Some(false)
2066 } else {
2067 None
2068 };
2069
2070 let breakpoint_store = match (&mode, project.as_ref()) {
2071 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2072 _ => None,
2073 };
2074
2075 let mut code_action_providers = Vec::new();
2076 let mut load_uncommitted_diff = None;
2077 if let Some(project) = project.clone() {
2078 load_uncommitted_diff = Some(
2079 update_uncommitted_diff_for_buffer(
2080 cx.entity(),
2081 &project,
2082 multi_buffer.read(cx).all_buffers(),
2083 multi_buffer.clone(),
2084 cx,
2085 )
2086 .shared(),
2087 );
2088 code_action_providers.push(Rc::new(project) as Rc<_>);
2089 }
2090
2091 let mut editor = Self {
2092 focus_handle,
2093 show_cursor_when_unfocused: false,
2094 last_focused_descendant: None,
2095 buffer: multi_buffer.clone(),
2096 display_map: display_map.clone(),
2097 placeholder_display_map: None,
2098 selections,
2099 scroll_manager: ScrollManager::new(cx),
2100 columnar_selection_state: None,
2101 add_selections_state: None,
2102 select_next_state: None,
2103 select_prev_state: None,
2104 selection_history: SelectionHistory::default(),
2105 defer_selection_effects: false,
2106 deferred_selection_effects_state: None,
2107 autoclose_regions: Vec::new(),
2108 snippet_stack: InvalidationStack::default(),
2109 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2110 ime_transaction: None,
2111 active_diagnostics: ActiveDiagnostic::None,
2112 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2113 inline_diagnostics_update: Task::ready(()),
2114 inline_diagnostics: Vec::new(),
2115 soft_wrap_mode_override,
2116 diagnostics_max_severity,
2117 hard_wrap: None,
2118 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2119 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2120 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2121 project,
2122 blink_manager: blink_manager.clone(),
2123 show_local_selections: true,
2124 show_scrollbars: ScrollbarAxes {
2125 horizontal: full_mode,
2126 vertical: full_mode,
2127 },
2128 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2129 offset_content: !matches!(mode, EditorMode::SingleLine),
2130 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2131 show_gutter: full_mode,
2132 show_line_numbers: (!full_mode).then_some(false),
2133 use_relative_line_numbers: None,
2134 disable_expand_excerpt_buttons: !full_mode,
2135 show_git_diff_gutter: None,
2136 show_code_actions: None,
2137 show_runnables: None,
2138 show_breakpoints: None,
2139 show_wrap_guides: None,
2140 show_indent_guides,
2141 highlight_order: 0,
2142 highlighted_rows: HashMap::default(),
2143 background_highlights: HashMap::default(),
2144 gutter_highlights: HashMap::default(),
2145 scrollbar_marker_state: ScrollbarMarkerState::default(),
2146 active_indent_guides_state: ActiveIndentGuidesState::default(),
2147 nav_history: None,
2148 context_menu: RefCell::new(None),
2149 context_menu_options: None,
2150 mouse_context_menu: None,
2151 completion_tasks: Vec::new(),
2152 inline_blame_popover: None,
2153 inline_blame_popover_show_task: None,
2154 signature_help_state: SignatureHelpState::default(),
2155 auto_signature_help: None,
2156 find_all_references_task_sources: Vec::new(),
2157 next_completion_id: 0,
2158 next_inlay_id: 0,
2159 code_action_providers,
2160 available_code_actions: None,
2161 code_actions_task: None,
2162 quick_selection_highlight_task: None,
2163 debounced_selection_highlight_task: None,
2164 document_highlights_task: None,
2165 linked_editing_range_task: None,
2166 pending_rename: None,
2167 searchable: !is_minimap,
2168 cursor_shape: EditorSettings::get_global(cx)
2169 .cursor_shape
2170 .unwrap_or_default(),
2171 current_line_highlight: None,
2172 autoindent_mode: Some(AutoindentMode::EachLine),
2173
2174 workspace: None,
2175 input_enabled: !is_minimap,
2176 use_modal_editing: full_mode,
2177 read_only: is_minimap,
2178 use_autoclose: true,
2179 use_auto_surround: true,
2180 auto_replace_emoji_shortcode: false,
2181 jsx_tag_auto_close_enabled_in_any_buffer: false,
2182 leader_id: None,
2183 remote_id: None,
2184 hover_state: HoverState::default(),
2185 pending_mouse_down: None,
2186 hovered_link_state: None,
2187 edit_prediction_provider: None,
2188 active_edit_prediction: None,
2189 stale_edit_prediction_in_menu: None,
2190 edit_prediction_preview: EditPredictionPreview::Inactive {
2191 released_too_fast: false,
2192 },
2193 inline_diagnostics_enabled: full_mode,
2194 diagnostics_enabled: full_mode,
2195 word_completions_enabled: full_mode,
2196 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2197 gutter_hovered: false,
2198 pixel_position_of_newest_cursor: None,
2199 last_bounds: None,
2200 last_position_map: None,
2201 expect_bounds_change: None,
2202 gutter_dimensions: GutterDimensions::default(),
2203 style: None,
2204 show_cursor_names: false,
2205 hovered_cursors: HashMap::default(),
2206 next_editor_action_id: EditorActionId::default(),
2207 editor_actions: Rc::default(),
2208 edit_predictions_hidden_for_vim_mode: false,
2209 show_edit_predictions_override: None,
2210 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2211 edit_prediction_settings: EditPredictionSettings::Disabled,
2212 edit_prediction_indent_conflict: false,
2213 edit_prediction_requires_modifier_in_indent_conflict: true,
2214 custom_context_menu: None,
2215 show_git_blame_gutter: false,
2216 show_git_blame_inline: false,
2217 show_selection_menu: None,
2218 show_git_blame_inline_delay_task: None,
2219 git_blame_inline_enabled: full_mode
2220 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2221 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2222 buffer_serialization: is_minimap.not().then(|| {
2223 BufferSerialization::new(
2224 ProjectSettings::get_global(cx)
2225 .session
2226 .restore_unsaved_buffers,
2227 )
2228 }),
2229 blame: None,
2230 blame_subscription: None,
2231 tasks: BTreeMap::default(),
2232
2233 breakpoint_store,
2234 gutter_breakpoint_indicator: (None, None),
2235 hovered_diff_hunk_row: None,
2236 _subscriptions: (!is_minimap)
2237 .then(|| {
2238 vec![
2239 cx.observe(&multi_buffer, Self::on_buffer_changed),
2240 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2241 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2242 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2243 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2244 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2245 cx.observe_window_activation(window, |editor, window, cx| {
2246 let active = window.is_window_active();
2247 editor.blink_manager.update(cx, |blink_manager, cx| {
2248 if active {
2249 blink_manager.enable(cx);
2250 } else {
2251 blink_manager.disable(cx);
2252 }
2253 });
2254 if active {
2255 editor.show_mouse_cursor(cx);
2256 }
2257 }),
2258 ]
2259 })
2260 .unwrap_or_default(),
2261 tasks_update_task: None,
2262 pull_diagnostics_task: Task::ready(()),
2263 colors: None,
2264 refresh_colors_task: Task::ready(()),
2265 inlay_hints: None,
2266 next_color_inlay_id: 0,
2267 post_scroll_update: Task::ready(()),
2268 linked_edit_ranges: Default::default(),
2269 in_project_search: false,
2270 previous_search_ranges: None,
2271 breadcrumb_header: None,
2272 focused_block: None,
2273 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2274 addons: HashMap::default(),
2275 registered_buffers: HashMap::default(),
2276 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2277 selection_mark_mode: false,
2278 toggle_fold_multiple_buffers: Task::ready(()),
2279 serialize_selections: Task::ready(()),
2280 serialize_folds: Task::ready(()),
2281 text_style_refinement: None,
2282 load_diff_task: load_uncommitted_diff,
2283 temporary_diff_override: false,
2284 mouse_cursor_hidden: false,
2285 minimap: None,
2286 hide_mouse_mode: EditorSettings::get_global(cx)
2287 .hide_mouse
2288 .unwrap_or_default(),
2289 change_list: ChangeList::new(),
2290 mode,
2291 selection_drag_state: SelectionDragState::None,
2292 folding_newlines: Task::ready(()),
2293 lookup_key: None,
2294 applicable_language_settings: HashMap::default(),
2295 fetched_tree_sitter_chunks: HashMap::default(),
2296 };
2297
2298 if is_minimap {
2299 return editor;
2300 }
2301
2302 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2303
2304 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2305 editor
2306 ._subscriptions
2307 .push(cx.observe(breakpoints, |_, _, cx| {
2308 cx.notify();
2309 }));
2310 }
2311 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2312 editor._subscriptions.extend(project_subscriptions);
2313
2314 editor._subscriptions.push(cx.subscribe_in(
2315 &cx.entity(),
2316 window,
2317 |editor, _, e: &EditorEvent, window, cx| match e {
2318 EditorEvent::ScrollPositionChanged { local, .. } => {
2319 if *local {
2320 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2321 editor.inline_blame_popover.take();
2322 let new_anchor = editor.scroll_manager.anchor();
2323 let snapshot = editor.snapshot(window, cx);
2324 editor.update_restoration_data(cx, move |data| {
2325 data.scroll_position = (
2326 new_anchor.top_row(snapshot.buffer_snapshot()),
2327 new_anchor.offset,
2328 );
2329 });
2330
2331 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2332 cx.background_executor()
2333 .timer(Duration::from_millis(50))
2334 .await;
2335 editor
2336 .update_in(cx, |editor, window, cx| {
2337 editor.register_visible_buffers(cx);
2338 editor.refresh_colors_for_visible_range(None, window, cx);
2339 editor.refresh_inlay_hints(
2340 InlayHintRefreshReason::NewLinesShown,
2341 cx,
2342 );
2343 })
2344 .ok();
2345 });
2346 }
2347 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2348 cx.background_executor()
2349 .timer(Duration::from_millis(50))
2350 .await;
2351 editor
2352 .update_in(cx, |editor, window, cx| {
2353 editor.register_visible_buffers(cx);
2354 editor.refresh_colors_for_visible_range(None, window, cx);
2355 editor
2356 .refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2357 editor.colorize_brackets(false, cx);
2358 })
2359 .ok();
2360 });
2361 }
2362 EditorEvent::Edited { .. } => {
2363 if vim_flavor(cx).is_none() {
2364 let display_map = editor.display_snapshot(cx);
2365 let selections = editor.selections.all_adjusted_display(&display_map);
2366 let pop_state = editor
2367 .change_list
2368 .last()
2369 .map(|previous| {
2370 previous.len() == selections.len()
2371 && previous.iter().enumerate().all(|(ix, p)| {
2372 p.to_display_point(&display_map).row()
2373 == selections[ix].head().row()
2374 })
2375 })
2376 .unwrap_or(false);
2377 let new_positions = selections
2378 .into_iter()
2379 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2380 .collect();
2381 editor
2382 .change_list
2383 .push_to_change_list(pop_state, new_positions);
2384 }
2385 }
2386 _ => (),
2387 },
2388 ));
2389
2390 if let Some(dap_store) = editor
2391 .project
2392 .as_ref()
2393 .map(|project| project.read(cx).dap_store())
2394 {
2395 let weak_editor = cx.weak_entity();
2396
2397 editor
2398 ._subscriptions
2399 .push(
2400 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2401 let session_entity = cx.entity();
2402 weak_editor
2403 .update(cx, |editor, cx| {
2404 editor._subscriptions.push(
2405 cx.subscribe(&session_entity, Self::on_debug_session_event),
2406 );
2407 })
2408 .ok();
2409 }),
2410 );
2411
2412 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2413 editor
2414 ._subscriptions
2415 .push(cx.subscribe(&session, Self::on_debug_session_event));
2416 }
2417 }
2418
2419 // skip adding the initial selection to selection history
2420 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2421 editor.end_selection(window, cx);
2422 editor.selection_history.mode = SelectionHistoryMode::Normal;
2423
2424 editor.scroll_manager.show_scrollbars(window, cx);
2425 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2426
2427 if full_mode {
2428 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2429 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2430
2431 if editor.git_blame_inline_enabled {
2432 editor.start_git_blame_inline(false, window, cx);
2433 }
2434
2435 editor.go_to_active_debug_line(window, cx);
2436
2437 editor.minimap =
2438 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2439 editor.colors = Some(LspColorData::new(cx));
2440 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2441
2442 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2443 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2444 }
2445 editor.update_lsp_data(None, window, cx);
2446 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2447 }
2448
2449 editor
2450 }
2451
2452 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2453 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2454 }
2455
2456 pub fn deploy_mouse_context_menu(
2457 &mut self,
2458 position: gpui::Point<Pixels>,
2459 context_menu: Entity<ContextMenu>,
2460 window: &mut Window,
2461 cx: &mut Context<Self>,
2462 ) {
2463 self.mouse_context_menu = Some(MouseContextMenu::new(
2464 self,
2465 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2466 context_menu,
2467 window,
2468 cx,
2469 ));
2470 }
2471
2472 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2473 self.mouse_context_menu
2474 .as_ref()
2475 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2476 }
2477
2478 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2479 if self
2480 .selections
2481 .pending_anchor()
2482 .is_some_and(|pending_selection| {
2483 let snapshot = self.buffer().read(cx).snapshot(cx);
2484 pending_selection.range().includes(range, &snapshot)
2485 })
2486 {
2487 return true;
2488 }
2489
2490 self.selections
2491 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2492 .into_iter()
2493 .any(|selection| {
2494 // This is needed to cover a corner case, if we just check for an existing
2495 // selection in the fold range, having a cursor at the start of the fold
2496 // marks it as selected. Non-empty selections don't cause this.
2497 let length = selection.end - selection.start;
2498 length > 0
2499 })
2500 }
2501
2502 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2503 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2504 }
2505
2506 fn key_context_internal(
2507 &self,
2508 has_active_edit_prediction: bool,
2509 window: &mut Window,
2510 cx: &mut App,
2511 ) -> KeyContext {
2512 let mut key_context = KeyContext::new_with_defaults();
2513 key_context.add("Editor");
2514 let mode = match self.mode {
2515 EditorMode::SingleLine => "single_line",
2516 EditorMode::AutoHeight { .. } => "auto_height",
2517 EditorMode::Minimap { .. } => "minimap",
2518 EditorMode::Full { .. } => "full",
2519 };
2520
2521 if EditorSettings::jupyter_enabled(cx) {
2522 key_context.add("jupyter");
2523 }
2524
2525 key_context.set("mode", mode);
2526 if self.pending_rename.is_some() {
2527 key_context.add("renaming");
2528 }
2529
2530 if let Some(snippet_stack) = self.snippet_stack.last() {
2531 key_context.add("in_snippet");
2532
2533 if snippet_stack.active_index > 0 {
2534 key_context.add("has_previous_tabstop");
2535 }
2536
2537 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2538 key_context.add("has_next_tabstop");
2539 }
2540 }
2541
2542 match self.context_menu.borrow().as_ref() {
2543 Some(CodeContextMenu::Completions(menu)) => {
2544 if menu.visible() {
2545 key_context.add("menu");
2546 key_context.add("showing_completions");
2547 }
2548 }
2549 Some(CodeContextMenu::CodeActions(menu)) => {
2550 if menu.visible() {
2551 key_context.add("menu");
2552 key_context.add("showing_code_actions")
2553 }
2554 }
2555 None => {}
2556 }
2557
2558 if self.signature_help_state.has_multiple_signatures() {
2559 key_context.add("showing_signature_help");
2560 }
2561
2562 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2563 if !self.focus_handle(cx).contains_focused(window, cx)
2564 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2565 {
2566 for addon in self.addons.values() {
2567 addon.extend_key_context(&mut key_context, cx)
2568 }
2569 }
2570
2571 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2572 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2573 Some(
2574 file.full_path(cx)
2575 .extension()?
2576 .to_string_lossy()
2577 .into_owned(),
2578 )
2579 }) {
2580 key_context.set("extension", extension);
2581 }
2582 } else {
2583 key_context.add("multibuffer");
2584 }
2585
2586 if has_active_edit_prediction {
2587 if self.edit_prediction_in_conflict() {
2588 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2589 } else {
2590 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2591 key_context.add("copilot_suggestion");
2592 }
2593 }
2594
2595 if self.selection_mark_mode {
2596 key_context.add("selection_mode");
2597 }
2598
2599 let disjoint = self.selections.disjoint_anchors();
2600 let snapshot = self.snapshot(window, cx);
2601 let snapshot = snapshot.buffer_snapshot();
2602 if self.mode == EditorMode::SingleLine
2603 && let [selection] = disjoint
2604 && selection.start == selection.end
2605 && selection.end.to_offset(snapshot) == snapshot.len()
2606 {
2607 key_context.add("end_of_input");
2608 }
2609
2610 key_context
2611 }
2612
2613 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2614 self.last_bounds.as_ref()
2615 }
2616
2617 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2618 if self.mouse_cursor_hidden {
2619 self.mouse_cursor_hidden = false;
2620 cx.notify();
2621 }
2622 }
2623
2624 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2625 let hide_mouse_cursor = match origin {
2626 HideMouseCursorOrigin::TypingAction => {
2627 matches!(
2628 self.hide_mouse_mode,
2629 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2630 )
2631 }
2632 HideMouseCursorOrigin::MovementAction => {
2633 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2634 }
2635 };
2636 if self.mouse_cursor_hidden != hide_mouse_cursor {
2637 self.mouse_cursor_hidden = hide_mouse_cursor;
2638 cx.notify();
2639 }
2640 }
2641
2642 pub fn edit_prediction_in_conflict(&self) -> bool {
2643 if !self.show_edit_predictions_in_menu() {
2644 return false;
2645 }
2646
2647 let showing_completions = self
2648 .context_menu
2649 .borrow()
2650 .as_ref()
2651 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2652
2653 showing_completions
2654 || self.edit_prediction_requires_modifier()
2655 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2656 // bindings to insert tab characters.
2657 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2658 }
2659
2660 pub fn accept_edit_prediction_keybind(
2661 &self,
2662 accept_partial: bool,
2663 window: &mut Window,
2664 cx: &mut App,
2665 ) -> AcceptEditPredictionBinding {
2666 let key_context = self.key_context_internal(true, window, cx);
2667 let in_conflict = self.edit_prediction_in_conflict();
2668
2669 let bindings = if accept_partial {
2670 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2671 } else {
2672 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2673 };
2674
2675 // TODO: if the binding contains multiple keystrokes, display all of them, not
2676 // just the first one.
2677 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2678 !in_conflict
2679 || binding
2680 .keystrokes()
2681 .first()
2682 .is_some_and(|keystroke| keystroke.modifiers().modified())
2683 }))
2684 }
2685
2686 pub fn new_file(
2687 workspace: &mut Workspace,
2688 _: &workspace::NewFile,
2689 window: &mut Window,
2690 cx: &mut Context<Workspace>,
2691 ) {
2692 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2693 "Failed to create buffer",
2694 window,
2695 cx,
2696 |e, _, _| match e.error_code() {
2697 ErrorCode::RemoteUpgradeRequired => Some(format!(
2698 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2699 e.error_tag("required").unwrap_or("the latest version")
2700 )),
2701 _ => None,
2702 },
2703 );
2704 }
2705
2706 pub fn new_in_workspace(
2707 workspace: &mut Workspace,
2708 window: &mut Window,
2709 cx: &mut Context<Workspace>,
2710 ) -> Task<Result<Entity<Editor>>> {
2711 let project = workspace.project().clone();
2712 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2713
2714 cx.spawn_in(window, async move |workspace, cx| {
2715 let buffer = create.await?;
2716 workspace.update_in(cx, |workspace, window, cx| {
2717 let editor =
2718 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2719 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2720 editor
2721 })
2722 })
2723 }
2724
2725 fn new_file_vertical(
2726 workspace: &mut Workspace,
2727 _: &workspace::NewFileSplitVertical,
2728 window: &mut Window,
2729 cx: &mut Context<Workspace>,
2730 ) {
2731 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2732 }
2733
2734 fn new_file_horizontal(
2735 workspace: &mut Workspace,
2736 _: &workspace::NewFileSplitHorizontal,
2737 window: &mut Window,
2738 cx: &mut Context<Workspace>,
2739 ) {
2740 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2741 }
2742
2743 fn new_file_split(
2744 workspace: &mut Workspace,
2745 action: &workspace::NewFileSplit,
2746 window: &mut Window,
2747 cx: &mut Context<Workspace>,
2748 ) {
2749 Self::new_file_in_direction(workspace, action.0, window, cx)
2750 }
2751
2752 fn new_file_in_direction(
2753 workspace: &mut Workspace,
2754 direction: SplitDirection,
2755 window: &mut Window,
2756 cx: &mut Context<Workspace>,
2757 ) {
2758 let project = workspace.project().clone();
2759 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2760
2761 cx.spawn_in(window, async move |workspace, cx| {
2762 let buffer = create.await?;
2763 workspace.update_in(cx, move |workspace, window, cx| {
2764 workspace.split_item(
2765 direction,
2766 Box::new(
2767 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2768 ),
2769 window,
2770 cx,
2771 )
2772 })?;
2773 anyhow::Ok(())
2774 })
2775 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2776 match e.error_code() {
2777 ErrorCode::RemoteUpgradeRequired => Some(format!(
2778 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2779 e.error_tag("required").unwrap_or("the latest version")
2780 )),
2781 _ => None,
2782 }
2783 });
2784 }
2785
2786 pub fn leader_id(&self) -> Option<CollaboratorId> {
2787 self.leader_id
2788 }
2789
2790 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2791 &self.buffer
2792 }
2793
2794 pub fn project(&self) -> Option<&Entity<Project>> {
2795 self.project.as_ref()
2796 }
2797
2798 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2799 self.workspace.as_ref()?.0.upgrade()
2800 }
2801
2802 /// Returns the workspace serialization ID if this editor should be serialized.
2803 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2804 self.workspace
2805 .as_ref()
2806 .filter(|_| self.should_serialize_buffer())
2807 .and_then(|workspace| workspace.1)
2808 }
2809
2810 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2811 self.buffer().read(cx).title(cx)
2812 }
2813
2814 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2815 let git_blame_gutter_max_author_length = self
2816 .render_git_blame_gutter(cx)
2817 .then(|| {
2818 if let Some(blame) = self.blame.as_ref() {
2819 let max_author_length =
2820 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2821 Some(max_author_length)
2822 } else {
2823 None
2824 }
2825 })
2826 .flatten();
2827
2828 EditorSnapshot {
2829 mode: self.mode.clone(),
2830 show_gutter: self.show_gutter,
2831 show_line_numbers: self.show_line_numbers,
2832 show_git_diff_gutter: self.show_git_diff_gutter,
2833 show_code_actions: self.show_code_actions,
2834 show_runnables: self.show_runnables,
2835 show_breakpoints: self.show_breakpoints,
2836 git_blame_gutter_max_author_length,
2837 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2838 placeholder_display_snapshot: self
2839 .placeholder_display_map
2840 .as_ref()
2841 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2842 scroll_anchor: self.scroll_manager.anchor(),
2843 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2844 is_focused: self.focus_handle.is_focused(window),
2845 current_line_highlight: self
2846 .current_line_highlight
2847 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2848 gutter_hovered: self.gutter_hovered,
2849 }
2850 }
2851
2852 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2853 self.buffer.read(cx).language_at(point, cx)
2854 }
2855
2856 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2857 self.buffer.read(cx).read(cx).file_at(point).cloned()
2858 }
2859
2860 pub fn active_excerpt(
2861 &self,
2862 cx: &App,
2863 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2864 self.buffer
2865 .read(cx)
2866 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2867 }
2868
2869 pub fn mode(&self) -> &EditorMode {
2870 &self.mode
2871 }
2872
2873 pub fn set_mode(&mut self, mode: EditorMode) {
2874 self.mode = mode;
2875 }
2876
2877 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2878 self.collaboration_hub.as_deref()
2879 }
2880
2881 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2882 self.collaboration_hub = Some(hub);
2883 }
2884
2885 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2886 self.in_project_search = in_project_search;
2887 }
2888
2889 pub fn set_custom_context_menu(
2890 &mut self,
2891 f: impl 'static
2892 + Fn(
2893 &mut Self,
2894 DisplayPoint,
2895 &mut Window,
2896 &mut Context<Self>,
2897 ) -> Option<Entity<ui::ContextMenu>>,
2898 ) {
2899 self.custom_context_menu = Some(Box::new(f))
2900 }
2901
2902 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2903 self.completion_provider = provider;
2904 }
2905
2906 #[cfg(any(test, feature = "test-support"))]
2907 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2908 self.completion_provider.clone()
2909 }
2910
2911 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2912 self.semantics_provider.clone()
2913 }
2914
2915 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2916 self.semantics_provider = provider;
2917 }
2918
2919 pub fn set_edit_prediction_provider<T>(
2920 &mut self,
2921 provider: Option<Entity<T>>,
2922 window: &mut Window,
2923 cx: &mut Context<Self>,
2924 ) where
2925 T: EditPredictionProvider,
2926 {
2927 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2928 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2929 if this.focus_handle.is_focused(window) {
2930 this.update_visible_edit_prediction(window, cx);
2931 }
2932 }),
2933 provider: Arc::new(provider),
2934 });
2935 self.update_edit_prediction_settings(cx);
2936 self.refresh_edit_prediction(false, false, window, cx);
2937 }
2938
2939 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2940 self.placeholder_display_map
2941 .as_ref()
2942 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2943 }
2944
2945 pub fn set_placeholder_text(
2946 &mut self,
2947 placeholder_text: &str,
2948 window: &mut Window,
2949 cx: &mut Context<Self>,
2950 ) {
2951 let multibuffer = cx
2952 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2953
2954 let style = window.text_style();
2955
2956 self.placeholder_display_map = Some(cx.new(|cx| {
2957 DisplayMap::new(
2958 multibuffer,
2959 style.font(),
2960 style.font_size.to_pixels(window.rem_size()),
2961 None,
2962 FILE_HEADER_HEIGHT,
2963 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2964 Default::default(),
2965 DiagnosticSeverity::Off,
2966 cx,
2967 )
2968 }));
2969 cx.notify();
2970 }
2971
2972 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2973 self.cursor_shape = cursor_shape;
2974
2975 // Disrupt blink for immediate user feedback that the cursor shape has changed
2976 self.blink_manager.update(cx, BlinkManager::show_cursor);
2977
2978 cx.notify();
2979 }
2980
2981 pub fn set_current_line_highlight(
2982 &mut self,
2983 current_line_highlight: Option<CurrentLineHighlight>,
2984 ) {
2985 self.current_line_highlight = current_line_highlight;
2986 }
2987
2988 pub fn range_for_match<T: std::marker::Copy>(
2989 &self,
2990 range: &Range<T>,
2991 collapse: bool,
2992 ) -> Range<T> {
2993 if collapse {
2994 return range.start..range.start;
2995 }
2996 range.clone()
2997 }
2998
2999 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3000 if self.display_map.read(cx).clip_at_line_ends != clip {
3001 self.display_map
3002 .update(cx, |map, _| map.clip_at_line_ends = clip);
3003 }
3004 }
3005
3006 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3007 self.input_enabled = input_enabled;
3008 }
3009
3010 pub fn set_edit_predictions_hidden_for_vim_mode(
3011 &mut self,
3012 hidden: bool,
3013 window: &mut Window,
3014 cx: &mut Context<Self>,
3015 ) {
3016 if hidden != self.edit_predictions_hidden_for_vim_mode {
3017 self.edit_predictions_hidden_for_vim_mode = hidden;
3018 if hidden {
3019 self.update_visible_edit_prediction(window, cx);
3020 } else {
3021 self.refresh_edit_prediction(true, false, window, cx);
3022 }
3023 }
3024 }
3025
3026 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3027 self.menu_edit_predictions_policy = value;
3028 }
3029
3030 pub fn set_autoindent(&mut self, autoindent: bool) {
3031 if autoindent {
3032 self.autoindent_mode = Some(AutoindentMode::EachLine);
3033 } else {
3034 self.autoindent_mode = None;
3035 }
3036 }
3037
3038 pub fn read_only(&self, cx: &App) -> bool {
3039 self.read_only || self.buffer.read(cx).read_only()
3040 }
3041
3042 pub fn set_read_only(&mut self, read_only: bool) {
3043 self.read_only = read_only;
3044 }
3045
3046 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3047 self.use_autoclose = autoclose;
3048 }
3049
3050 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3051 self.use_auto_surround = auto_surround;
3052 }
3053
3054 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3055 self.auto_replace_emoji_shortcode = auto_replace;
3056 }
3057
3058 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3059 self.buffer_serialization = should_serialize.then(|| {
3060 BufferSerialization::new(
3061 ProjectSettings::get_global(cx)
3062 .session
3063 .restore_unsaved_buffers,
3064 )
3065 })
3066 }
3067
3068 fn should_serialize_buffer(&self) -> bool {
3069 self.buffer_serialization.is_some()
3070 }
3071
3072 pub fn toggle_edit_predictions(
3073 &mut self,
3074 _: &ToggleEditPrediction,
3075 window: &mut Window,
3076 cx: &mut Context<Self>,
3077 ) {
3078 if self.show_edit_predictions_override.is_some() {
3079 self.set_show_edit_predictions(None, window, cx);
3080 } else {
3081 let show_edit_predictions = !self.edit_predictions_enabled();
3082 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3083 }
3084 }
3085
3086 pub fn set_show_edit_predictions(
3087 &mut self,
3088 show_edit_predictions: Option<bool>,
3089 window: &mut Window,
3090 cx: &mut Context<Self>,
3091 ) {
3092 self.show_edit_predictions_override = show_edit_predictions;
3093 self.update_edit_prediction_settings(cx);
3094
3095 if let Some(false) = show_edit_predictions {
3096 self.discard_edit_prediction(false, cx);
3097 } else {
3098 self.refresh_edit_prediction(false, true, window, cx);
3099 }
3100 }
3101
3102 fn edit_predictions_disabled_in_scope(
3103 &self,
3104 buffer: &Entity<Buffer>,
3105 buffer_position: language::Anchor,
3106 cx: &App,
3107 ) -> bool {
3108 let snapshot = buffer.read(cx).snapshot();
3109 let settings = snapshot.settings_at(buffer_position, cx);
3110
3111 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3112 return false;
3113 };
3114
3115 scope.override_name().is_some_and(|scope_name| {
3116 settings
3117 .edit_predictions_disabled_in
3118 .iter()
3119 .any(|s| s == scope_name)
3120 })
3121 }
3122
3123 pub fn set_use_modal_editing(&mut self, to: bool) {
3124 self.use_modal_editing = to;
3125 }
3126
3127 pub fn use_modal_editing(&self) -> bool {
3128 self.use_modal_editing
3129 }
3130
3131 fn selections_did_change(
3132 &mut self,
3133 local: bool,
3134 old_cursor_position: &Anchor,
3135 effects: SelectionEffects,
3136 window: &mut Window,
3137 cx: &mut Context<Self>,
3138 ) {
3139 window.invalidate_character_coordinates();
3140
3141 // Copy selections to primary selection buffer
3142 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3143 if local {
3144 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3145 let buffer_handle = self.buffer.read(cx).read(cx);
3146
3147 let mut text = String::new();
3148 for (index, selection) in selections.iter().enumerate() {
3149 let text_for_selection = buffer_handle
3150 .text_for_range(selection.start..selection.end)
3151 .collect::<String>();
3152
3153 text.push_str(&text_for_selection);
3154 if index != selections.len() - 1 {
3155 text.push('\n');
3156 }
3157 }
3158
3159 if !text.is_empty() {
3160 cx.write_to_primary(ClipboardItem::new_string(text));
3161 }
3162 }
3163
3164 let selection_anchors = self.selections.disjoint_anchors_arc();
3165
3166 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3167 self.buffer.update(cx, |buffer, cx| {
3168 buffer.set_active_selections(
3169 &selection_anchors,
3170 self.selections.line_mode(),
3171 self.cursor_shape,
3172 cx,
3173 )
3174 });
3175 }
3176 let display_map = self
3177 .display_map
3178 .update(cx, |display_map, cx| display_map.snapshot(cx));
3179 let buffer = display_map.buffer_snapshot();
3180 if self.selections.count() == 1 {
3181 self.add_selections_state = None;
3182 }
3183 self.select_next_state = None;
3184 self.select_prev_state = None;
3185 self.select_syntax_node_history.try_clear();
3186 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3187 self.snippet_stack.invalidate(&selection_anchors, buffer);
3188 self.take_rename(false, window, cx);
3189
3190 let newest_selection = self.selections.newest_anchor();
3191 let new_cursor_position = newest_selection.head();
3192 let selection_start = newest_selection.start;
3193
3194 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3195 self.push_to_nav_history(
3196 *old_cursor_position,
3197 Some(new_cursor_position.to_point(buffer)),
3198 false,
3199 effects.nav_history == Some(true),
3200 cx,
3201 );
3202 }
3203
3204 if local {
3205 if let Some(buffer_id) = new_cursor_position.buffer_id {
3206 self.register_buffer(buffer_id, cx);
3207 }
3208
3209 let mut context_menu = self.context_menu.borrow_mut();
3210 let completion_menu = match context_menu.as_ref() {
3211 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3212 Some(CodeContextMenu::CodeActions(_)) => {
3213 *context_menu = None;
3214 None
3215 }
3216 None => None,
3217 };
3218 let completion_position = completion_menu.map(|menu| menu.initial_position);
3219 drop(context_menu);
3220
3221 if effects.completions
3222 && let Some(completion_position) = completion_position
3223 {
3224 let start_offset = selection_start.to_offset(buffer);
3225 let position_matches = start_offset == completion_position.to_offset(buffer);
3226 let continue_showing = if position_matches {
3227 if self.snippet_stack.is_empty() {
3228 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3229 == Some(CharKind::Word)
3230 } else {
3231 // Snippet choices can be shown even when the cursor is in whitespace.
3232 // Dismissing the menu with actions like backspace is handled by
3233 // invalidation regions.
3234 true
3235 }
3236 } else {
3237 false
3238 };
3239
3240 if continue_showing {
3241 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
3242 } else {
3243 self.hide_context_menu(window, cx);
3244 }
3245 }
3246
3247 hide_hover(self, cx);
3248
3249 if old_cursor_position.to_display_point(&display_map).row()
3250 != new_cursor_position.to_display_point(&display_map).row()
3251 {
3252 self.available_code_actions.take();
3253 }
3254 self.refresh_code_actions(window, cx);
3255 self.refresh_document_highlights(cx);
3256 refresh_linked_ranges(self, window, cx);
3257
3258 self.refresh_selected_text_highlights(false, window, cx);
3259 self.refresh_matching_bracket_highlights(window, cx);
3260 self.update_visible_edit_prediction(window, cx);
3261 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3262 self.inline_blame_popover.take();
3263 if self.git_blame_inline_enabled {
3264 self.start_inline_blame_timer(window, cx);
3265 }
3266 }
3267
3268 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3269 cx.emit(EditorEvent::SelectionsChanged { local });
3270
3271 let selections = &self.selections.disjoint_anchors_arc();
3272 if selections.len() == 1 {
3273 cx.emit(SearchEvent::ActiveMatchChanged)
3274 }
3275 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3276 let inmemory_selections = selections
3277 .iter()
3278 .map(|s| {
3279 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3280 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3281 })
3282 .collect();
3283 self.update_restoration_data(cx, |data| {
3284 data.selections = inmemory_selections;
3285 });
3286
3287 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3288 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3289 {
3290 let snapshot = self.buffer().read(cx).snapshot(cx);
3291 let selections = selections.clone();
3292 let background_executor = cx.background_executor().clone();
3293 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3294 self.serialize_selections = cx.background_spawn(async move {
3295 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3296 let db_selections = selections
3297 .iter()
3298 .map(|selection| {
3299 (
3300 selection.start.to_offset(&snapshot),
3301 selection.end.to_offset(&snapshot),
3302 )
3303 })
3304 .collect();
3305
3306 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3307 .await
3308 .with_context(|| {
3309 format!(
3310 "persisting editor selections for editor {editor_id}, \
3311 workspace {workspace_id:?}"
3312 )
3313 })
3314 .log_err();
3315 });
3316 }
3317 }
3318
3319 cx.notify();
3320 }
3321
3322 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3323 use text::ToOffset as _;
3324 use text::ToPoint as _;
3325
3326 if self.mode.is_minimap()
3327 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3328 {
3329 return;
3330 }
3331
3332 if !self.buffer().read(cx).is_singleton() {
3333 return;
3334 }
3335
3336 let display_snapshot = self
3337 .display_map
3338 .update(cx, |display_map, cx| display_map.snapshot(cx));
3339 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3340 return;
3341 };
3342 let inmemory_folds = display_snapshot
3343 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3344 .map(|fold| {
3345 fold.range.start.text_anchor.to_point(&snapshot)
3346 ..fold.range.end.text_anchor.to_point(&snapshot)
3347 })
3348 .collect();
3349 self.update_restoration_data(cx, |data| {
3350 data.folds = inmemory_folds;
3351 });
3352
3353 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3354 return;
3355 };
3356 let background_executor = cx.background_executor().clone();
3357 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3358 let db_folds = display_snapshot
3359 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3360 .map(|fold| {
3361 (
3362 fold.range.start.text_anchor.to_offset(&snapshot),
3363 fold.range.end.text_anchor.to_offset(&snapshot),
3364 )
3365 })
3366 .collect();
3367 self.serialize_folds = cx.background_spawn(async move {
3368 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3369 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3370 .await
3371 .with_context(|| {
3372 format!(
3373 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3374 )
3375 })
3376 .log_err();
3377 });
3378 }
3379
3380 pub fn sync_selections(
3381 &mut self,
3382 other: Entity<Editor>,
3383 cx: &mut Context<Self>,
3384 ) -> gpui::Subscription {
3385 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3386 if !other_selections.is_empty() {
3387 self.selections
3388 .change_with(&self.display_snapshot(cx), |selections| {
3389 selections.select_anchors(other_selections);
3390 });
3391 }
3392
3393 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3394 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3395 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3396 if other_selections.is_empty() {
3397 return;
3398 }
3399 let snapshot = this.display_snapshot(cx);
3400 this.selections.change_with(&snapshot, |selections| {
3401 selections.select_anchors(other_selections);
3402 });
3403 }
3404 });
3405
3406 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3407 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3408 let these_selections = this.selections.disjoint_anchors().to_vec();
3409 if these_selections.is_empty() {
3410 return;
3411 }
3412 other.update(cx, |other_editor, cx| {
3413 let snapshot = other_editor.display_snapshot(cx);
3414 other_editor
3415 .selections
3416 .change_with(&snapshot, |selections| {
3417 selections.select_anchors(these_selections);
3418 })
3419 });
3420 }
3421 });
3422
3423 Subscription::join(other_subscription, this_subscription)
3424 }
3425
3426 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3427 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3428 /// effects of selection change occur at the end of the transaction.
3429 pub fn change_selections<R>(
3430 &mut self,
3431 effects: SelectionEffects,
3432 window: &mut Window,
3433 cx: &mut Context<Self>,
3434 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3435 ) -> R {
3436 let snapshot = self.display_snapshot(cx);
3437 if let Some(state) = &mut self.deferred_selection_effects_state {
3438 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3439 state.effects.completions = effects.completions;
3440 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3441 let (changed, result) = self.selections.change_with(&snapshot, change);
3442 state.changed |= changed;
3443 return result;
3444 }
3445 let mut state = DeferredSelectionEffectsState {
3446 changed: false,
3447 effects,
3448 old_cursor_position: self.selections.newest_anchor().head(),
3449 history_entry: SelectionHistoryEntry {
3450 selections: self.selections.disjoint_anchors_arc(),
3451 select_next_state: self.select_next_state.clone(),
3452 select_prev_state: self.select_prev_state.clone(),
3453 add_selections_state: self.add_selections_state.clone(),
3454 },
3455 };
3456 let (changed, result) = self.selections.change_with(&snapshot, change);
3457 state.changed = state.changed || changed;
3458 if self.defer_selection_effects {
3459 self.deferred_selection_effects_state = Some(state);
3460 } else {
3461 self.apply_selection_effects(state, window, cx);
3462 }
3463 result
3464 }
3465
3466 /// Defers the effects of selection change, so that the effects of multiple calls to
3467 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3468 /// to selection history and the state of popovers based on selection position aren't
3469 /// erroneously updated.
3470 pub fn with_selection_effects_deferred<R>(
3471 &mut self,
3472 window: &mut Window,
3473 cx: &mut Context<Self>,
3474 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3475 ) -> R {
3476 let already_deferred = self.defer_selection_effects;
3477 self.defer_selection_effects = true;
3478 let result = update(self, window, cx);
3479 if !already_deferred {
3480 self.defer_selection_effects = false;
3481 if let Some(state) = self.deferred_selection_effects_state.take() {
3482 self.apply_selection_effects(state, window, cx);
3483 }
3484 }
3485 result
3486 }
3487
3488 fn apply_selection_effects(
3489 &mut self,
3490 state: DeferredSelectionEffectsState,
3491 window: &mut Window,
3492 cx: &mut Context<Self>,
3493 ) {
3494 if state.changed {
3495 self.selection_history.push(state.history_entry);
3496
3497 if let Some(autoscroll) = state.effects.scroll {
3498 self.request_autoscroll(autoscroll, cx);
3499 }
3500
3501 let old_cursor_position = &state.old_cursor_position;
3502
3503 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3504
3505 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3506 self.show_signature_help(&ShowSignatureHelp, window, cx);
3507 }
3508 }
3509 }
3510
3511 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3512 where
3513 I: IntoIterator<Item = (Range<S>, T)>,
3514 S: ToOffset,
3515 T: Into<Arc<str>>,
3516 {
3517 if self.read_only(cx) {
3518 return;
3519 }
3520
3521 self.buffer
3522 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3523 }
3524
3525 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3526 where
3527 I: IntoIterator<Item = (Range<S>, T)>,
3528 S: ToOffset,
3529 T: Into<Arc<str>>,
3530 {
3531 if self.read_only(cx) {
3532 return;
3533 }
3534
3535 self.buffer.update(cx, |buffer, cx| {
3536 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3537 });
3538 }
3539
3540 pub fn edit_with_block_indent<I, S, T>(
3541 &mut self,
3542 edits: I,
3543 original_indent_columns: Vec<Option<u32>>,
3544 cx: &mut Context<Self>,
3545 ) where
3546 I: IntoIterator<Item = (Range<S>, T)>,
3547 S: ToOffset,
3548 T: Into<Arc<str>>,
3549 {
3550 if self.read_only(cx) {
3551 return;
3552 }
3553
3554 self.buffer.update(cx, |buffer, cx| {
3555 buffer.edit(
3556 edits,
3557 Some(AutoindentMode::Block {
3558 original_indent_columns,
3559 }),
3560 cx,
3561 )
3562 });
3563 }
3564
3565 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3566 self.hide_context_menu(window, cx);
3567
3568 match phase {
3569 SelectPhase::Begin {
3570 position,
3571 add,
3572 click_count,
3573 } => self.begin_selection(position, add, click_count, window, cx),
3574 SelectPhase::BeginColumnar {
3575 position,
3576 goal_column,
3577 reset,
3578 mode,
3579 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3580 SelectPhase::Extend {
3581 position,
3582 click_count,
3583 } => self.extend_selection(position, click_count, window, cx),
3584 SelectPhase::Update {
3585 position,
3586 goal_column,
3587 scroll_delta,
3588 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3589 SelectPhase::End => self.end_selection(window, cx),
3590 }
3591 }
3592
3593 fn extend_selection(
3594 &mut self,
3595 position: DisplayPoint,
3596 click_count: usize,
3597 window: &mut Window,
3598 cx: &mut Context<Self>,
3599 ) {
3600 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3601 let tail = self.selections.newest::<usize>(&display_map).tail();
3602 let click_count = click_count.max(match self.selections.select_mode() {
3603 SelectMode::Character => 1,
3604 SelectMode::Word(_) => 2,
3605 SelectMode::Line(_) => 3,
3606 SelectMode::All => 4,
3607 });
3608 self.begin_selection(position, false, click_count, window, cx);
3609
3610 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3611
3612 let current_selection = match self.selections.select_mode() {
3613 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3614 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3615 };
3616
3617 let mut pending_selection = self
3618 .selections
3619 .pending_anchor()
3620 .cloned()
3621 .expect("extend_selection not called with pending selection");
3622
3623 if pending_selection
3624 .start
3625 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3626 == Ordering::Greater
3627 {
3628 pending_selection.start = current_selection.start;
3629 }
3630 if pending_selection
3631 .end
3632 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3633 == Ordering::Less
3634 {
3635 pending_selection.end = current_selection.end;
3636 pending_selection.reversed = true;
3637 }
3638
3639 let mut pending_mode = self.selections.pending_mode().unwrap();
3640 match &mut pending_mode {
3641 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3642 _ => {}
3643 }
3644
3645 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3646 SelectionEffects::scroll(Autoscroll::fit())
3647 } else {
3648 SelectionEffects::no_scroll()
3649 };
3650
3651 self.change_selections(effects, window, cx, |s| {
3652 s.set_pending(pending_selection.clone(), pending_mode);
3653 s.set_is_extending(true);
3654 });
3655 }
3656
3657 fn begin_selection(
3658 &mut self,
3659 position: DisplayPoint,
3660 add: bool,
3661 click_count: usize,
3662 window: &mut Window,
3663 cx: &mut Context<Self>,
3664 ) {
3665 if !self.focus_handle.is_focused(window) {
3666 self.last_focused_descendant = None;
3667 window.focus(&self.focus_handle);
3668 }
3669
3670 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3671 let buffer = display_map.buffer_snapshot();
3672 let position = display_map.clip_point(position, Bias::Left);
3673
3674 let start;
3675 let end;
3676 let mode;
3677 let mut auto_scroll;
3678 match click_count {
3679 1 => {
3680 start = buffer.anchor_before(position.to_point(&display_map));
3681 end = start;
3682 mode = SelectMode::Character;
3683 auto_scroll = true;
3684 }
3685 2 => {
3686 let position = display_map
3687 .clip_point(position, Bias::Left)
3688 .to_offset(&display_map, Bias::Left);
3689 let (range, _) = buffer.surrounding_word(position, None);
3690 start = buffer.anchor_before(range.start);
3691 end = buffer.anchor_before(range.end);
3692 mode = SelectMode::Word(start..end);
3693 auto_scroll = true;
3694 }
3695 3 => {
3696 let position = display_map
3697 .clip_point(position, Bias::Left)
3698 .to_point(&display_map);
3699 let line_start = display_map.prev_line_boundary(position).0;
3700 let next_line_start = buffer.clip_point(
3701 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3702 Bias::Left,
3703 );
3704 start = buffer.anchor_before(line_start);
3705 end = buffer.anchor_before(next_line_start);
3706 mode = SelectMode::Line(start..end);
3707 auto_scroll = true;
3708 }
3709 _ => {
3710 start = buffer.anchor_before(0);
3711 end = buffer.anchor_before(buffer.len());
3712 mode = SelectMode::All;
3713 auto_scroll = false;
3714 }
3715 }
3716 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3717
3718 let point_to_delete: Option<usize> = {
3719 let selected_points: Vec<Selection<Point>> =
3720 self.selections.disjoint_in_range(start..end, &display_map);
3721
3722 if !add || click_count > 1 {
3723 None
3724 } else if !selected_points.is_empty() {
3725 Some(selected_points[0].id)
3726 } else {
3727 let clicked_point_already_selected =
3728 self.selections.disjoint_anchors().iter().find(|selection| {
3729 selection.start.to_point(buffer) == start.to_point(buffer)
3730 || selection.end.to_point(buffer) == end.to_point(buffer)
3731 });
3732
3733 clicked_point_already_selected.map(|selection| selection.id)
3734 }
3735 };
3736
3737 let selections_count = self.selections.count();
3738 let effects = if auto_scroll {
3739 SelectionEffects::default()
3740 } else {
3741 SelectionEffects::no_scroll()
3742 };
3743
3744 self.change_selections(effects, window, cx, |s| {
3745 if let Some(point_to_delete) = point_to_delete {
3746 s.delete(point_to_delete);
3747
3748 if selections_count == 1 {
3749 s.set_pending_anchor_range(start..end, mode);
3750 }
3751 } else {
3752 if !add {
3753 s.clear_disjoint();
3754 }
3755
3756 s.set_pending_anchor_range(start..end, mode);
3757 }
3758 });
3759 }
3760
3761 fn begin_columnar_selection(
3762 &mut self,
3763 position: DisplayPoint,
3764 goal_column: u32,
3765 reset: bool,
3766 mode: ColumnarMode,
3767 window: &mut Window,
3768 cx: &mut Context<Self>,
3769 ) {
3770 if !self.focus_handle.is_focused(window) {
3771 self.last_focused_descendant = None;
3772 window.focus(&self.focus_handle);
3773 }
3774
3775 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3776
3777 if reset {
3778 let pointer_position = display_map
3779 .buffer_snapshot()
3780 .anchor_before(position.to_point(&display_map));
3781
3782 self.change_selections(
3783 SelectionEffects::scroll(Autoscroll::newest()),
3784 window,
3785 cx,
3786 |s| {
3787 s.clear_disjoint();
3788 s.set_pending_anchor_range(
3789 pointer_position..pointer_position,
3790 SelectMode::Character,
3791 );
3792 },
3793 );
3794 };
3795
3796 let tail = self.selections.newest::<Point>(&display_map).tail();
3797 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3798 self.columnar_selection_state = match mode {
3799 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3800 selection_tail: selection_anchor,
3801 display_point: if reset {
3802 if position.column() != goal_column {
3803 Some(DisplayPoint::new(position.row(), goal_column))
3804 } else {
3805 None
3806 }
3807 } else {
3808 None
3809 },
3810 }),
3811 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3812 selection_tail: selection_anchor,
3813 }),
3814 };
3815
3816 if !reset {
3817 self.select_columns(position, goal_column, &display_map, window, cx);
3818 }
3819 }
3820
3821 fn update_selection(
3822 &mut self,
3823 position: DisplayPoint,
3824 goal_column: u32,
3825 scroll_delta: gpui::Point<f32>,
3826 window: &mut Window,
3827 cx: &mut Context<Self>,
3828 ) {
3829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3830
3831 if self.columnar_selection_state.is_some() {
3832 self.select_columns(position, goal_column, &display_map, window, cx);
3833 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3834 let buffer = display_map.buffer_snapshot();
3835 let head;
3836 let tail;
3837 let mode = self.selections.pending_mode().unwrap();
3838 match &mode {
3839 SelectMode::Character => {
3840 head = position.to_point(&display_map);
3841 tail = pending.tail().to_point(buffer);
3842 }
3843 SelectMode::Word(original_range) => {
3844 let offset = display_map
3845 .clip_point(position, Bias::Left)
3846 .to_offset(&display_map, Bias::Left);
3847 let original_range = original_range.to_offset(buffer);
3848
3849 let head_offset = if buffer.is_inside_word(offset, None)
3850 || original_range.contains(&offset)
3851 {
3852 let (word_range, _) = buffer.surrounding_word(offset, None);
3853 if word_range.start < original_range.start {
3854 word_range.start
3855 } else {
3856 word_range.end
3857 }
3858 } else {
3859 offset
3860 };
3861
3862 head = head_offset.to_point(buffer);
3863 if head_offset <= original_range.start {
3864 tail = original_range.end.to_point(buffer);
3865 } else {
3866 tail = original_range.start.to_point(buffer);
3867 }
3868 }
3869 SelectMode::Line(original_range) => {
3870 let original_range = original_range.to_point(display_map.buffer_snapshot());
3871
3872 let position = display_map
3873 .clip_point(position, Bias::Left)
3874 .to_point(&display_map);
3875 let line_start = display_map.prev_line_boundary(position).0;
3876 let next_line_start = buffer.clip_point(
3877 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3878 Bias::Left,
3879 );
3880
3881 if line_start < original_range.start {
3882 head = line_start
3883 } else {
3884 head = next_line_start
3885 }
3886
3887 if head <= original_range.start {
3888 tail = original_range.end;
3889 } else {
3890 tail = original_range.start;
3891 }
3892 }
3893 SelectMode::All => {
3894 return;
3895 }
3896 };
3897
3898 if head < tail {
3899 pending.start = buffer.anchor_before(head);
3900 pending.end = buffer.anchor_before(tail);
3901 pending.reversed = true;
3902 } else {
3903 pending.start = buffer.anchor_before(tail);
3904 pending.end = buffer.anchor_before(head);
3905 pending.reversed = false;
3906 }
3907
3908 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3909 s.set_pending(pending.clone(), mode);
3910 });
3911 } else {
3912 log::error!("update_selection dispatched with no pending selection");
3913 return;
3914 }
3915
3916 self.apply_scroll_delta(scroll_delta, window, cx);
3917 cx.notify();
3918 }
3919
3920 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3921 self.columnar_selection_state.take();
3922 if let Some(pending_mode) = self.selections.pending_mode() {
3923 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3924 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3925 s.select(selections);
3926 s.clear_pending();
3927 if s.is_extending() {
3928 s.set_is_extending(false);
3929 } else {
3930 s.set_select_mode(pending_mode);
3931 }
3932 });
3933 }
3934 }
3935
3936 fn select_columns(
3937 &mut self,
3938 head: DisplayPoint,
3939 goal_column: u32,
3940 display_map: &DisplaySnapshot,
3941 window: &mut Window,
3942 cx: &mut Context<Self>,
3943 ) {
3944 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3945 return;
3946 };
3947
3948 let tail = match columnar_state {
3949 ColumnarSelectionState::FromMouse {
3950 selection_tail,
3951 display_point,
3952 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3953 ColumnarSelectionState::FromSelection { selection_tail } => {
3954 selection_tail.to_display_point(display_map)
3955 }
3956 };
3957
3958 let start_row = cmp::min(tail.row(), head.row());
3959 let end_row = cmp::max(tail.row(), head.row());
3960 let start_column = cmp::min(tail.column(), goal_column);
3961 let end_column = cmp::max(tail.column(), goal_column);
3962 let reversed = start_column < tail.column();
3963
3964 let selection_ranges = (start_row.0..=end_row.0)
3965 .map(DisplayRow)
3966 .filter_map(|row| {
3967 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3968 || start_column <= display_map.line_len(row))
3969 && !display_map.is_block_line(row)
3970 {
3971 let start = display_map
3972 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3973 .to_point(display_map);
3974 let end = display_map
3975 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
3976 .to_point(display_map);
3977 if reversed {
3978 Some(end..start)
3979 } else {
3980 Some(start..end)
3981 }
3982 } else {
3983 None
3984 }
3985 })
3986 .collect::<Vec<_>>();
3987 if selection_ranges.is_empty() {
3988 return;
3989 }
3990
3991 let ranges = match columnar_state {
3992 ColumnarSelectionState::FromMouse { .. } => {
3993 let mut non_empty_ranges = selection_ranges
3994 .iter()
3995 .filter(|selection_range| selection_range.start != selection_range.end)
3996 .peekable();
3997 if non_empty_ranges.peek().is_some() {
3998 non_empty_ranges.cloned().collect()
3999 } else {
4000 selection_ranges
4001 }
4002 }
4003 _ => selection_ranges,
4004 };
4005
4006 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4007 s.select_ranges(ranges);
4008 });
4009 cx.notify();
4010 }
4011
4012 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4013 self.selections
4014 .all_adjusted(snapshot)
4015 .iter()
4016 .any(|selection| !selection.is_empty())
4017 }
4018
4019 pub fn has_pending_nonempty_selection(&self) -> bool {
4020 let pending_nonempty_selection = match self.selections.pending_anchor() {
4021 Some(Selection { start, end, .. }) => start != end,
4022 None => false,
4023 };
4024
4025 pending_nonempty_selection
4026 || (self.columnar_selection_state.is_some()
4027 && self.selections.disjoint_anchors().len() > 1)
4028 }
4029
4030 pub fn has_pending_selection(&self) -> bool {
4031 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4032 }
4033
4034 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4035 self.selection_mark_mode = false;
4036 self.selection_drag_state = SelectionDragState::None;
4037
4038 if self.clear_expanded_diff_hunks(cx) {
4039 cx.notify();
4040 return;
4041 }
4042 if self.dismiss_menus_and_popups(true, window, cx) {
4043 return;
4044 }
4045
4046 if self.mode.is_full()
4047 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4048 {
4049 return;
4050 }
4051
4052 cx.propagate();
4053 }
4054
4055 pub fn dismiss_menus_and_popups(
4056 &mut self,
4057 is_user_requested: bool,
4058 window: &mut Window,
4059 cx: &mut Context<Self>,
4060 ) -> bool {
4061 if self.take_rename(false, window, cx).is_some() {
4062 return true;
4063 }
4064
4065 if self.hide_blame_popover(true, cx) {
4066 return true;
4067 }
4068
4069 if hide_hover(self, cx) {
4070 return true;
4071 }
4072
4073 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4074 return true;
4075 }
4076
4077 if self.hide_context_menu(window, cx).is_some() {
4078 return true;
4079 }
4080
4081 if self.mouse_context_menu.take().is_some() {
4082 return true;
4083 }
4084
4085 if is_user_requested && self.discard_edit_prediction(true, cx) {
4086 return true;
4087 }
4088
4089 if self.snippet_stack.pop().is_some() {
4090 return true;
4091 }
4092
4093 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4094 self.dismiss_diagnostics(cx);
4095 return true;
4096 }
4097
4098 false
4099 }
4100
4101 fn linked_editing_ranges_for(
4102 &self,
4103 selection: Range<text::Anchor>,
4104 cx: &App,
4105 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4106 if self.linked_edit_ranges.is_empty() {
4107 return None;
4108 }
4109 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4110 selection.end.buffer_id.and_then(|end_buffer_id| {
4111 if selection.start.buffer_id != Some(end_buffer_id) {
4112 return None;
4113 }
4114 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4115 let snapshot = buffer.read(cx).snapshot();
4116 self.linked_edit_ranges
4117 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4118 .map(|ranges| (ranges, snapshot, buffer))
4119 })?;
4120 use text::ToOffset as TO;
4121 // find offset from the start of current range to current cursor position
4122 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4123
4124 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4125 let start_difference = start_offset - start_byte_offset;
4126 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4127 let end_difference = end_offset - start_byte_offset;
4128 // Current range has associated linked ranges.
4129 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4130 for range in linked_ranges.iter() {
4131 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4132 let end_offset = start_offset + end_difference;
4133 let start_offset = start_offset + start_difference;
4134 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4135 continue;
4136 }
4137 if self.selections.disjoint_anchor_ranges().any(|s| {
4138 if s.start.buffer_id != selection.start.buffer_id
4139 || s.end.buffer_id != selection.end.buffer_id
4140 {
4141 return false;
4142 }
4143 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4144 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4145 }) {
4146 continue;
4147 }
4148 let start = buffer_snapshot.anchor_after(start_offset);
4149 let end = buffer_snapshot.anchor_after(end_offset);
4150 linked_edits
4151 .entry(buffer.clone())
4152 .or_default()
4153 .push(start..end);
4154 }
4155 Some(linked_edits)
4156 }
4157
4158 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4159 let text: Arc<str> = text.into();
4160
4161 if self.read_only(cx) {
4162 return;
4163 }
4164
4165 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4166
4167 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4168 let mut bracket_inserted = false;
4169 let mut edits = Vec::new();
4170 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4171 let mut new_selections = Vec::with_capacity(selections.len());
4172 let mut new_autoclose_regions = Vec::new();
4173 let snapshot = self.buffer.read(cx).read(cx);
4174 let mut clear_linked_edit_ranges = false;
4175
4176 for (selection, autoclose_region) in
4177 self.selections_with_autoclose_regions(selections, &snapshot)
4178 {
4179 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4180 // Determine if the inserted text matches the opening or closing
4181 // bracket of any of this language's bracket pairs.
4182 let mut bracket_pair = None;
4183 let mut is_bracket_pair_start = false;
4184 let mut is_bracket_pair_end = false;
4185 if !text.is_empty() {
4186 let mut bracket_pair_matching_end = None;
4187 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4188 // and they are removing the character that triggered IME popup.
4189 for (pair, enabled) in scope.brackets() {
4190 if !pair.close && !pair.surround {
4191 continue;
4192 }
4193
4194 if enabled && pair.start.ends_with(text.as_ref()) {
4195 let prefix_len = pair.start.len() - text.len();
4196 let preceding_text_matches_prefix = prefix_len == 0
4197 || (selection.start.column >= (prefix_len as u32)
4198 && snapshot.contains_str_at(
4199 Point::new(
4200 selection.start.row,
4201 selection.start.column - (prefix_len as u32),
4202 ),
4203 &pair.start[..prefix_len],
4204 ));
4205 if preceding_text_matches_prefix {
4206 bracket_pair = Some(pair.clone());
4207 is_bracket_pair_start = true;
4208 break;
4209 }
4210 }
4211 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4212 {
4213 // take first bracket pair matching end, but don't break in case a later bracket
4214 // pair matches start
4215 bracket_pair_matching_end = Some(pair.clone());
4216 }
4217 }
4218 if let Some(end) = bracket_pair_matching_end
4219 && bracket_pair.is_none()
4220 {
4221 bracket_pair = Some(end);
4222 is_bracket_pair_end = true;
4223 }
4224 }
4225
4226 if let Some(bracket_pair) = bracket_pair {
4227 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4228 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4229 let auto_surround =
4230 self.use_auto_surround && snapshot_settings.use_auto_surround;
4231 if selection.is_empty() {
4232 if is_bracket_pair_start {
4233 // If the inserted text is a suffix of an opening bracket and the
4234 // selection is preceded by the rest of the opening bracket, then
4235 // insert the closing bracket.
4236 let following_text_allows_autoclose = snapshot
4237 .chars_at(selection.start)
4238 .next()
4239 .is_none_or(|c| scope.should_autoclose_before(c));
4240
4241 let preceding_text_allows_autoclose = selection.start.column == 0
4242 || snapshot
4243 .reversed_chars_at(selection.start)
4244 .next()
4245 .is_none_or(|c| {
4246 bracket_pair.start != bracket_pair.end
4247 || !snapshot
4248 .char_classifier_at(selection.start)
4249 .is_word(c)
4250 });
4251
4252 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4253 && bracket_pair.start.len() == 1
4254 {
4255 let target = bracket_pair.start.chars().next().unwrap();
4256 let current_line_count = snapshot
4257 .reversed_chars_at(selection.start)
4258 .take_while(|&c| c != '\n')
4259 .filter(|&c| c == target)
4260 .count();
4261 current_line_count % 2 == 1
4262 } else {
4263 false
4264 };
4265
4266 if autoclose
4267 && bracket_pair.close
4268 && following_text_allows_autoclose
4269 && preceding_text_allows_autoclose
4270 && !is_closing_quote
4271 {
4272 let anchor = snapshot.anchor_before(selection.end);
4273 new_selections.push((selection.map(|_| anchor), text.len()));
4274 new_autoclose_regions.push((
4275 anchor,
4276 text.len(),
4277 selection.id,
4278 bracket_pair.clone(),
4279 ));
4280 edits.push((
4281 selection.range(),
4282 format!("{}{}", text, bracket_pair.end).into(),
4283 ));
4284 bracket_inserted = true;
4285 continue;
4286 }
4287 }
4288
4289 if let Some(region) = autoclose_region {
4290 // If the selection is followed by an auto-inserted closing bracket,
4291 // then don't insert that closing bracket again; just move the selection
4292 // past the closing bracket.
4293 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4294 && text.as_ref() == region.pair.end.as_str()
4295 && snapshot.contains_str_at(region.range.end, text.as_ref());
4296 if should_skip {
4297 let anchor = snapshot.anchor_after(selection.end);
4298 new_selections
4299 .push((selection.map(|_| anchor), region.pair.end.len()));
4300 continue;
4301 }
4302 }
4303
4304 let always_treat_brackets_as_autoclosed = snapshot
4305 .language_settings_at(selection.start, cx)
4306 .always_treat_brackets_as_autoclosed;
4307 if always_treat_brackets_as_autoclosed
4308 && is_bracket_pair_end
4309 && snapshot.contains_str_at(selection.end, text.as_ref())
4310 {
4311 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4312 // and the inserted text is a closing bracket and the selection is followed
4313 // by the closing bracket then move the selection past the closing bracket.
4314 let anchor = snapshot.anchor_after(selection.end);
4315 new_selections.push((selection.map(|_| anchor), text.len()));
4316 continue;
4317 }
4318 }
4319 // If an opening bracket is 1 character long and is typed while
4320 // text is selected, then surround that text with the bracket pair.
4321 else if auto_surround
4322 && bracket_pair.surround
4323 && is_bracket_pair_start
4324 && bracket_pair.start.chars().count() == 1
4325 {
4326 edits.push((selection.start..selection.start, text.clone()));
4327 edits.push((
4328 selection.end..selection.end,
4329 bracket_pair.end.as_str().into(),
4330 ));
4331 bracket_inserted = true;
4332 new_selections.push((
4333 Selection {
4334 id: selection.id,
4335 start: snapshot.anchor_after(selection.start),
4336 end: snapshot.anchor_before(selection.end),
4337 reversed: selection.reversed,
4338 goal: selection.goal,
4339 },
4340 0,
4341 ));
4342 continue;
4343 }
4344 }
4345 }
4346
4347 if self.auto_replace_emoji_shortcode
4348 && selection.is_empty()
4349 && text.as_ref().ends_with(':')
4350 && let Some(possible_emoji_short_code) =
4351 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4352 && !possible_emoji_short_code.is_empty()
4353 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4354 {
4355 let emoji_shortcode_start = Point::new(
4356 selection.start.row,
4357 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4358 );
4359
4360 // Remove shortcode from buffer
4361 edits.push((
4362 emoji_shortcode_start..selection.start,
4363 "".to_string().into(),
4364 ));
4365 new_selections.push((
4366 Selection {
4367 id: selection.id,
4368 start: snapshot.anchor_after(emoji_shortcode_start),
4369 end: snapshot.anchor_before(selection.start),
4370 reversed: selection.reversed,
4371 goal: selection.goal,
4372 },
4373 0,
4374 ));
4375
4376 // Insert emoji
4377 let selection_start_anchor = snapshot.anchor_after(selection.start);
4378 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4379 edits.push((selection.start..selection.end, emoji.to_string().into()));
4380
4381 continue;
4382 }
4383
4384 // If not handling any auto-close operation, then just replace the selected
4385 // text with the given input and move the selection to the end of the
4386 // newly inserted text.
4387 let anchor = snapshot.anchor_after(selection.end);
4388 if !self.linked_edit_ranges.is_empty() {
4389 let start_anchor = snapshot.anchor_before(selection.start);
4390
4391 let is_word_char = text.chars().next().is_none_or(|char| {
4392 let classifier = snapshot
4393 .char_classifier_at(start_anchor.to_offset(&snapshot))
4394 .scope_context(Some(CharScopeContext::LinkedEdit));
4395 classifier.is_word(char)
4396 });
4397
4398 if is_word_char {
4399 if let Some(ranges) = self
4400 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4401 {
4402 for (buffer, edits) in ranges {
4403 linked_edits
4404 .entry(buffer.clone())
4405 .or_default()
4406 .extend(edits.into_iter().map(|range| (range, text.clone())));
4407 }
4408 }
4409 } else {
4410 clear_linked_edit_ranges = true;
4411 }
4412 }
4413
4414 new_selections.push((selection.map(|_| anchor), 0));
4415 edits.push((selection.start..selection.end, text.clone()));
4416 }
4417
4418 drop(snapshot);
4419
4420 self.transact(window, cx, |this, window, cx| {
4421 if clear_linked_edit_ranges {
4422 this.linked_edit_ranges.clear();
4423 }
4424 let initial_buffer_versions =
4425 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4426
4427 this.buffer.update(cx, |buffer, cx| {
4428 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4429 });
4430 for (buffer, edits) in linked_edits {
4431 buffer.update(cx, |buffer, cx| {
4432 let snapshot = buffer.snapshot();
4433 let edits = edits
4434 .into_iter()
4435 .map(|(range, text)| {
4436 use text::ToPoint as TP;
4437 let end_point = TP::to_point(&range.end, &snapshot);
4438 let start_point = TP::to_point(&range.start, &snapshot);
4439 (start_point..end_point, text)
4440 })
4441 .sorted_by_key(|(range, _)| range.start);
4442 buffer.edit(edits, None, cx);
4443 })
4444 }
4445 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4446 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4447 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4448 let new_selections =
4449 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4450 .zip(new_selection_deltas)
4451 .map(|(selection, delta)| Selection {
4452 id: selection.id,
4453 start: selection.start + delta,
4454 end: selection.end + delta,
4455 reversed: selection.reversed,
4456 goal: SelectionGoal::None,
4457 })
4458 .collect::<Vec<_>>();
4459
4460 let mut i = 0;
4461 for (position, delta, selection_id, pair) in new_autoclose_regions {
4462 let position = position.to_offset(map.buffer_snapshot()) + delta;
4463 let start = map.buffer_snapshot().anchor_before(position);
4464 let end = map.buffer_snapshot().anchor_after(position);
4465 while let Some(existing_state) = this.autoclose_regions.get(i) {
4466 match existing_state
4467 .range
4468 .start
4469 .cmp(&start, map.buffer_snapshot())
4470 {
4471 Ordering::Less => i += 1,
4472 Ordering::Greater => break,
4473 Ordering::Equal => {
4474 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4475 Ordering::Less => i += 1,
4476 Ordering::Equal => break,
4477 Ordering::Greater => break,
4478 }
4479 }
4480 }
4481 }
4482 this.autoclose_regions.insert(
4483 i,
4484 AutocloseRegion {
4485 selection_id,
4486 range: start..end,
4487 pair,
4488 },
4489 );
4490 }
4491
4492 let had_active_edit_prediction = this.has_active_edit_prediction();
4493 this.change_selections(
4494 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4495 window,
4496 cx,
4497 |s| s.select(new_selections),
4498 );
4499
4500 if !bracket_inserted
4501 && let Some(on_type_format_task) =
4502 this.trigger_on_type_formatting(text.to_string(), window, cx)
4503 {
4504 on_type_format_task.detach_and_log_err(cx);
4505 }
4506
4507 let editor_settings = EditorSettings::get_global(cx);
4508 if bracket_inserted
4509 && (editor_settings.auto_signature_help
4510 || editor_settings.show_signature_help_after_edits)
4511 {
4512 this.show_signature_help(&ShowSignatureHelp, window, cx);
4513 }
4514
4515 let trigger_in_words =
4516 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4517 if this.hard_wrap.is_some() {
4518 let latest: Range<Point> = this.selections.newest(&map).range();
4519 if latest.is_empty()
4520 && this
4521 .buffer()
4522 .read(cx)
4523 .snapshot(cx)
4524 .line_len(MultiBufferRow(latest.start.row))
4525 == latest.start.column
4526 {
4527 this.rewrap_impl(
4528 RewrapOptions {
4529 override_language_settings: true,
4530 preserve_existing_whitespace: true,
4531 },
4532 cx,
4533 )
4534 }
4535 }
4536 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4537 refresh_linked_ranges(this, window, cx);
4538 this.refresh_edit_prediction(true, false, window, cx);
4539 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4540 });
4541 }
4542
4543 fn find_possible_emoji_shortcode_at_position(
4544 snapshot: &MultiBufferSnapshot,
4545 position: Point,
4546 ) -> Option<String> {
4547 let mut chars = Vec::new();
4548 let mut found_colon = false;
4549 for char in snapshot.reversed_chars_at(position).take(100) {
4550 // Found a possible emoji shortcode in the middle of the buffer
4551 if found_colon {
4552 if char.is_whitespace() {
4553 chars.reverse();
4554 return Some(chars.iter().collect());
4555 }
4556 // If the previous character is not a whitespace, we are in the middle of a word
4557 // and we only want to complete the shortcode if the word is made up of other emojis
4558 let mut containing_word = String::new();
4559 for ch in snapshot
4560 .reversed_chars_at(position)
4561 .skip(chars.len() + 1)
4562 .take(100)
4563 {
4564 if ch.is_whitespace() {
4565 break;
4566 }
4567 containing_word.push(ch);
4568 }
4569 let containing_word = containing_word.chars().rev().collect::<String>();
4570 if util::word_consists_of_emojis(containing_word.as_str()) {
4571 chars.reverse();
4572 return Some(chars.iter().collect());
4573 }
4574 }
4575
4576 if char.is_whitespace() || !char.is_ascii() {
4577 return None;
4578 }
4579 if char == ':' {
4580 found_colon = true;
4581 } else {
4582 chars.push(char);
4583 }
4584 }
4585 // Found a possible emoji shortcode at the beginning of the buffer
4586 chars.reverse();
4587 Some(chars.iter().collect())
4588 }
4589
4590 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4592 self.transact(window, cx, |this, window, cx| {
4593 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4594 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4595 let multi_buffer = this.buffer.read(cx);
4596 let buffer = multi_buffer.snapshot(cx);
4597 selections
4598 .iter()
4599 .map(|selection| {
4600 let start_point = selection.start.to_point(&buffer);
4601 let mut existing_indent =
4602 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4603 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4604 let start = selection.start;
4605 let end = selection.end;
4606 let selection_is_empty = start == end;
4607 let language_scope = buffer.language_scope_at(start);
4608 let (
4609 comment_delimiter,
4610 doc_delimiter,
4611 insert_extra_newline,
4612 indent_on_newline,
4613 indent_on_extra_newline,
4614 ) = if let Some(language) = &language_scope {
4615 let mut insert_extra_newline =
4616 insert_extra_newline_brackets(&buffer, start..end, language)
4617 || insert_extra_newline_tree_sitter(&buffer, start..end);
4618
4619 // Comment extension on newline is allowed only for cursor selections
4620 let comment_delimiter = maybe!({
4621 if !selection_is_empty {
4622 return None;
4623 }
4624
4625 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4626 return None;
4627 }
4628
4629 let delimiters = language.line_comment_prefixes();
4630 let max_len_of_delimiter =
4631 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4632 let (snapshot, range) =
4633 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4634
4635 let num_of_whitespaces = snapshot
4636 .chars_for_range(range.clone())
4637 .take_while(|c| c.is_whitespace())
4638 .count();
4639 let comment_candidate = snapshot
4640 .chars_for_range(range.clone())
4641 .skip(num_of_whitespaces)
4642 .take(max_len_of_delimiter)
4643 .collect::<String>();
4644 let (delimiter, trimmed_len) = delimiters
4645 .iter()
4646 .filter_map(|delimiter| {
4647 let prefix = delimiter.trim_end();
4648 if comment_candidate.starts_with(prefix) {
4649 Some((delimiter, prefix.len()))
4650 } else {
4651 None
4652 }
4653 })
4654 .max_by_key(|(_, len)| *len)?;
4655
4656 if let Some(BlockCommentConfig {
4657 start: block_start, ..
4658 }) = language.block_comment()
4659 {
4660 let block_start_trimmed = block_start.trim_end();
4661 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4662 let line_content = snapshot
4663 .chars_for_range(range)
4664 .skip(num_of_whitespaces)
4665 .take(block_start_trimmed.len())
4666 .collect::<String>();
4667
4668 if line_content.starts_with(block_start_trimmed) {
4669 return None;
4670 }
4671 }
4672 }
4673
4674 let cursor_is_placed_after_comment_marker =
4675 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4676 if cursor_is_placed_after_comment_marker {
4677 Some(delimiter.clone())
4678 } else {
4679 None
4680 }
4681 });
4682
4683 let mut indent_on_newline = IndentSize::spaces(0);
4684 let mut indent_on_extra_newline = IndentSize::spaces(0);
4685
4686 let doc_delimiter = maybe!({
4687 if !selection_is_empty {
4688 return None;
4689 }
4690
4691 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4692 return None;
4693 }
4694
4695 let BlockCommentConfig {
4696 start: start_tag,
4697 end: end_tag,
4698 prefix: delimiter,
4699 tab_size: len,
4700 } = language.documentation_comment()?;
4701 let is_within_block_comment = buffer
4702 .language_scope_at(start_point)
4703 .is_some_and(|scope| scope.override_name() == Some("comment"));
4704 if !is_within_block_comment {
4705 return None;
4706 }
4707
4708 let (snapshot, range) =
4709 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4710
4711 let num_of_whitespaces = snapshot
4712 .chars_for_range(range.clone())
4713 .take_while(|c| c.is_whitespace())
4714 .count();
4715
4716 // 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.
4717 let column = start_point.column;
4718 let cursor_is_after_start_tag = {
4719 let start_tag_len = start_tag.len();
4720 let start_tag_line = snapshot
4721 .chars_for_range(range.clone())
4722 .skip(num_of_whitespaces)
4723 .take(start_tag_len)
4724 .collect::<String>();
4725 if start_tag_line.starts_with(start_tag.as_ref()) {
4726 num_of_whitespaces + start_tag_len <= column as usize
4727 } else {
4728 false
4729 }
4730 };
4731
4732 let cursor_is_after_delimiter = {
4733 let delimiter_trim = delimiter.trim_end();
4734 let delimiter_line = snapshot
4735 .chars_for_range(range.clone())
4736 .skip(num_of_whitespaces)
4737 .take(delimiter_trim.len())
4738 .collect::<String>();
4739 if delimiter_line.starts_with(delimiter_trim) {
4740 num_of_whitespaces + delimiter_trim.len() <= column as usize
4741 } else {
4742 false
4743 }
4744 };
4745
4746 let cursor_is_before_end_tag_if_exists = {
4747 let mut char_position = 0u32;
4748 let mut end_tag_offset = None;
4749
4750 'outer: for chunk in snapshot.text_for_range(range) {
4751 if let Some(byte_pos) = chunk.find(&**end_tag) {
4752 let chars_before_match =
4753 chunk[..byte_pos].chars().count() as u32;
4754 end_tag_offset =
4755 Some(char_position + chars_before_match);
4756 break 'outer;
4757 }
4758 char_position += chunk.chars().count() as u32;
4759 }
4760
4761 if let Some(end_tag_offset) = end_tag_offset {
4762 let cursor_is_before_end_tag = column <= end_tag_offset;
4763 if cursor_is_after_start_tag {
4764 if cursor_is_before_end_tag {
4765 insert_extra_newline = true;
4766 }
4767 let cursor_is_at_start_of_end_tag =
4768 column == end_tag_offset;
4769 if cursor_is_at_start_of_end_tag {
4770 indent_on_extra_newline.len = *len;
4771 }
4772 }
4773 cursor_is_before_end_tag
4774 } else {
4775 true
4776 }
4777 };
4778
4779 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4780 && cursor_is_before_end_tag_if_exists
4781 {
4782 if cursor_is_after_start_tag {
4783 indent_on_newline.len = *len;
4784 }
4785 Some(delimiter.clone())
4786 } else {
4787 None
4788 }
4789 });
4790
4791 (
4792 comment_delimiter,
4793 doc_delimiter,
4794 insert_extra_newline,
4795 indent_on_newline,
4796 indent_on_extra_newline,
4797 )
4798 } else {
4799 (
4800 None,
4801 None,
4802 false,
4803 IndentSize::default(),
4804 IndentSize::default(),
4805 )
4806 };
4807
4808 let prevent_auto_indent = doc_delimiter.is_some();
4809 let delimiter = comment_delimiter.or(doc_delimiter);
4810
4811 let capacity_for_delimiter =
4812 delimiter.as_deref().map(str::len).unwrap_or_default();
4813 let mut new_text = String::with_capacity(
4814 1 + capacity_for_delimiter
4815 + existing_indent.len as usize
4816 + indent_on_newline.len as usize
4817 + indent_on_extra_newline.len as usize,
4818 );
4819 new_text.push('\n');
4820 new_text.extend(existing_indent.chars());
4821 new_text.extend(indent_on_newline.chars());
4822
4823 if let Some(delimiter) = &delimiter {
4824 new_text.push_str(delimiter);
4825 }
4826
4827 if insert_extra_newline {
4828 new_text.push('\n');
4829 new_text.extend(existing_indent.chars());
4830 new_text.extend(indent_on_extra_newline.chars());
4831 }
4832
4833 let anchor = buffer.anchor_after(end);
4834 let new_selection = selection.map(|_| anchor);
4835 (
4836 ((start..end, new_text), prevent_auto_indent),
4837 (insert_extra_newline, new_selection),
4838 )
4839 })
4840 .unzip()
4841 };
4842
4843 let mut auto_indent_edits = Vec::new();
4844 let mut edits = Vec::new();
4845 for (edit, prevent_auto_indent) in edits_with_flags {
4846 if prevent_auto_indent {
4847 edits.push(edit);
4848 } else {
4849 auto_indent_edits.push(edit);
4850 }
4851 }
4852 if !edits.is_empty() {
4853 this.edit(edits, cx);
4854 }
4855 if !auto_indent_edits.is_empty() {
4856 this.edit_with_autoindent(auto_indent_edits, cx);
4857 }
4858
4859 let buffer = this.buffer.read(cx).snapshot(cx);
4860 let new_selections = selection_info
4861 .into_iter()
4862 .map(|(extra_newline_inserted, new_selection)| {
4863 let mut cursor = new_selection.end.to_point(&buffer);
4864 if extra_newline_inserted {
4865 cursor.row -= 1;
4866 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4867 }
4868 new_selection.map(|_| cursor)
4869 })
4870 .collect();
4871
4872 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4873 this.refresh_edit_prediction(true, false, window, cx);
4874 });
4875 }
4876
4877 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4879
4880 let buffer = self.buffer.read(cx);
4881 let snapshot = buffer.snapshot(cx);
4882
4883 let mut edits = Vec::new();
4884 let mut rows = Vec::new();
4885
4886 for (rows_inserted, selection) in self
4887 .selections
4888 .all_adjusted(&self.display_snapshot(cx))
4889 .into_iter()
4890 .enumerate()
4891 {
4892 let cursor = selection.head();
4893 let row = cursor.row;
4894
4895 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4896
4897 let newline = "\n".to_string();
4898 edits.push((start_of_line..start_of_line, newline));
4899
4900 rows.push(row + rows_inserted as u32);
4901 }
4902
4903 self.transact(window, cx, |editor, window, cx| {
4904 editor.edit(edits, cx);
4905
4906 editor.change_selections(Default::default(), window, cx, |s| {
4907 let mut index = 0;
4908 s.move_cursors_with(|map, _, _| {
4909 let row = rows[index];
4910 index += 1;
4911
4912 let point = Point::new(row, 0);
4913 let boundary = map.next_line_boundary(point).1;
4914 let clipped = map.clip_point(boundary, Bias::Left);
4915
4916 (clipped, SelectionGoal::None)
4917 });
4918 });
4919
4920 let mut indent_edits = Vec::new();
4921 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4922 for row in rows {
4923 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4924 for (row, indent) in indents {
4925 if indent.len == 0 {
4926 continue;
4927 }
4928
4929 let text = match indent.kind {
4930 IndentKind::Space => " ".repeat(indent.len as usize),
4931 IndentKind::Tab => "\t".repeat(indent.len as usize),
4932 };
4933 let point = Point::new(row.0, 0);
4934 indent_edits.push((point..point, text));
4935 }
4936 }
4937 editor.edit(indent_edits, cx);
4938 });
4939 }
4940
4941 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4942 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4943
4944 let buffer = self.buffer.read(cx);
4945 let snapshot = buffer.snapshot(cx);
4946
4947 let mut edits = Vec::new();
4948 let mut rows = Vec::new();
4949 let mut rows_inserted = 0;
4950
4951 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4952 let cursor = selection.head();
4953 let row = cursor.row;
4954
4955 let point = Point::new(row + 1, 0);
4956 let start_of_line = snapshot.clip_point(point, Bias::Left);
4957
4958 let newline = "\n".to_string();
4959 edits.push((start_of_line..start_of_line, newline));
4960
4961 rows_inserted += 1;
4962 rows.push(row + rows_inserted);
4963 }
4964
4965 self.transact(window, cx, |editor, window, cx| {
4966 editor.edit(edits, cx);
4967
4968 editor.change_selections(Default::default(), window, cx, |s| {
4969 let mut index = 0;
4970 s.move_cursors_with(|map, _, _| {
4971 let row = rows[index];
4972 index += 1;
4973
4974 let point = Point::new(row, 0);
4975 let boundary = map.next_line_boundary(point).1;
4976 let clipped = map.clip_point(boundary, Bias::Left);
4977
4978 (clipped, SelectionGoal::None)
4979 });
4980 });
4981
4982 let mut indent_edits = Vec::new();
4983 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4984 for row in rows {
4985 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4986 for (row, indent) in indents {
4987 if indent.len == 0 {
4988 continue;
4989 }
4990
4991 let text = match indent.kind {
4992 IndentKind::Space => " ".repeat(indent.len as usize),
4993 IndentKind::Tab => "\t".repeat(indent.len as usize),
4994 };
4995 let point = Point::new(row.0, 0);
4996 indent_edits.push((point..point, text));
4997 }
4998 }
4999 editor.edit(indent_edits, cx);
5000 });
5001 }
5002
5003 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5004 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5005 original_indent_columns: Vec::new(),
5006 });
5007 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5008 }
5009
5010 fn insert_with_autoindent_mode(
5011 &mut self,
5012 text: &str,
5013 autoindent_mode: Option<AutoindentMode>,
5014 window: &mut Window,
5015 cx: &mut Context<Self>,
5016 ) {
5017 if self.read_only(cx) {
5018 return;
5019 }
5020
5021 let text: Arc<str> = text.into();
5022 self.transact(window, cx, |this, window, cx| {
5023 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5024 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5025 let anchors = {
5026 let snapshot = buffer.read(cx);
5027 old_selections
5028 .iter()
5029 .map(|s| {
5030 let anchor = snapshot.anchor_after(s.head());
5031 s.map(|_| anchor)
5032 })
5033 .collect::<Vec<_>>()
5034 };
5035 buffer.edit(
5036 old_selections
5037 .iter()
5038 .map(|s| (s.start..s.end, text.clone())),
5039 autoindent_mode,
5040 cx,
5041 );
5042 anchors
5043 });
5044
5045 this.change_selections(Default::default(), window, cx, |s| {
5046 s.select_anchors(selection_anchors);
5047 });
5048
5049 cx.notify();
5050 });
5051 }
5052
5053 fn trigger_completion_on_input(
5054 &mut self,
5055 text: &str,
5056 trigger_in_words: bool,
5057 window: &mut Window,
5058 cx: &mut Context<Self>,
5059 ) {
5060 let completions_source = self
5061 .context_menu
5062 .borrow()
5063 .as_ref()
5064 .and_then(|menu| match menu {
5065 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5066 CodeContextMenu::CodeActions(_) => None,
5067 });
5068
5069 match completions_source {
5070 Some(CompletionsMenuSource::Words { .. }) => {
5071 self.open_or_update_completions_menu(
5072 Some(CompletionsMenuSource::Words {
5073 ignore_threshold: false,
5074 }),
5075 None,
5076 window,
5077 cx,
5078 );
5079 }
5080 Some(CompletionsMenuSource::Normal)
5081 | Some(CompletionsMenuSource::SnippetChoices)
5082 | None
5083 if self.is_completion_trigger(
5084 text,
5085 trigger_in_words,
5086 completions_source.is_some(),
5087 cx,
5088 ) =>
5089 {
5090 self.show_completions(
5091 &ShowCompletions {
5092 trigger: Some(text.to_owned()).filter(|x| !x.is_empty()),
5093 },
5094 window,
5095 cx,
5096 )
5097 }
5098 _ => {
5099 self.hide_context_menu(window, cx);
5100 }
5101 }
5102 }
5103
5104 fn is_completion_trigger(
5105 &self,
5106 text: &str,
5107 trigger_in_words: bool,
5108 menu_is_open: bool,
5109 cx: &mut Context<Self>,
5110 ) -> bool {
5111 let position = self.selections.newest_anchor().head();
5112 let Some(buffer) = self.buffer.read(cx).buffer_for_anchor(position, cx) else {
5113 return false;
5114 };
5115
5116 if let Some(completion_provider) = &self.completion_provider {
5117 completion_provider.is_completion_trigger(
5118 &buffer,
5119 position.text_anchor,
5120 text,
5121 trigger_in_words,
5122 menu_is_open,
5123 cx,
5124 )
5125 } else {
5126 false
5127 }
5128 }
5129
5130 /// If any empty selections is touching the start of its innermost containing autoclose
5131 /// region, expand it to select the brackets.
5132 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5133 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5134 let buffer = self.buffer.read(cx).read(cx);
5135 let new_selections = self
5136 .selections_with_autoclose_regions(selections, &buffer)
5137 .map(|(mut selection, region)| {
5138 if !selection.is_empty() {
5139 return selection;
5140 }
5141
5142 if let Some(region) = region {
5143 let mut range = region.range.to_offset(&buffer);
5144 if selection.start == range.start && range.start >= region.pair.start.len() {
5145 range.start -= region.pair.start.len();
5146 if buffer.contains_str_at(range.start, ®ion.pair.start)
5147 && buffer.contains_str_at(range.end, ®ion.pair.end)
5148 {
5149 range.end += region.pair.end.len();
5150 selection.start = range.start;
5151 selection.end = range.end;
5152
5153 return selection;
5154 }
5155 }
5156 }
5157
5158 let always_treat_brackets_as_autoclosed = buffer
5159 .language_settings_at(selection.start, cx)
5160 .always_treat_brackets_as_autoclosed;
5161
5162 if !always_treat_brackets_as_autoclosed {
5163 return selection;
5164 }
5165
5166 if let Some(scope) = buffer.language_scope_at(selection.start) {
5167 for (pair, enabled) in scope.brackets() {
5168 if !enabled || !pair.close {
5169 continue;
5170 }
5171
5172 if buffer.contains_str_at(selection.start, &pair.end) {
5173 let pair_start_len = pair.start.len();
5174 if buffer.contains_str_at(
5175 selection.start.saturating_sub(pair_start_len),
5176 &pair.start,
5177 ) {
5178 selection.start -= pair_start_len;
5179 selection.end += pair.end.len();
5180
5181 return selection;
5182 }
5183 }
5184 }
5185 }
5186
5187 selection
5188 })
5189 .collect();
5190
5191 drop(buffer);
5192 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5193 selections.select(new_selections)
5194 });
5195 }
5196
5197 /// Iterate the given selections, and for each one, find the smallest surrounding
5198 /// autoclose region. This uses the ordering of the selections and the autoclose
5199 /// regions to avoid repeated comparisons.
5200 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5201 &'a self,
5202 selections: impl IntoIterator<Item = Selection<D>>,
5203 buffer: &'a MultiBufferSnapshot,
5204 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5205 let mut i = 0;
5206 let mut regions = self.autoclose_regions.as_slice();
5207 selections.into_iter().map(move |selection| {
5208 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5209
5210 let mut enclosing = None;
5211 while let Some(pair_state) = regions.get(i) {
5212 if pair_state.range.end.to_offset(buffer) < range.start {
5213 regions = ®ions[i + 1..];
5214 i = 0;
5215 } else if pair_state.range.start.to_offset(buffer) > range.end {
5216 break;
5217 } else {
5218 if pair_state.selection_id == selection.id {
5219 enclosing = Some(pair_state);
5220 }
5221 i += 1;
5222 }
5223 }
5224
5225 (selection, enclosing)
5226 })
5227 }
5228
5229 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5230 fn invalidate_autoclose_regions(
5231 &mut self,
5232 mut selections: &[Selection<Anchor>],
5233 buffer: &MultiBufferSnapshot,
5234 ) {
5235 self.autoclose_regions.retain(|state| {
5236 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5237 return false;
5238 }
5239
5240 let mut i = 0;
5241 while let Some(selection) = selections.get(i) {
5242 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5243 selections = &selections[1..];
5244 continue;
5245 }
5246 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5247 break;
5248 }
5249 if selection.id == state.selection_id {
5250 return true;
5251 } else {
5252 i += 1;
5253 }
5254 }
5255 false
5256 });
5257 }
5258
5259 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5260 let offset = position.to_offset(buffer);
5261 let (word_range, kind) =
5262 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5263 if offset > word_range.start && kind == Some(CharKind::Word) {
5264 Some(
5265 buffer
5266 .text_for_range(word_range.start..offset)
5267 .collect::<String>(),
5268 )
5269 } else {
5270 None
5271 }
5272 }
5273
5274 pub fn visible_excerpts(
5275 &self,
5276 cx: &mut Context<Editor>,
5277 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5278 let Some(project) = self.project() else {
5279 return HashMap::default();
5280 };
5281 let project = project.read(cx);
5282 let multi_buffer = self.buffer().read(cx);
5283 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5284 let multi_buffer_visible_start = self
5285 .scroll_manager
5286 .anchor()
5287 .anchor
5288 .to_point(&multi_buffer_snapshot);
5289 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5290 multi_buffer_visible_start
5291 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5292 Bias::Left,
5293 );
5294 multi_buffer_snapshot
5295 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5296 .into_iter()
5297 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5298 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5299 let buffer_file = project::File::from_dyn(buffer.file())?;
5300 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5301 let worktree_entry = buffer_worktree
5302 .read(cx)
5303 .entry_for_id(buffer_file.project_entry_id()?)?;
5304 if worktree_entry.is_ignored {
5305 None
5306 } else {
5307 Some((
5308 excerpt_id,
5309 (
5310 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5311 buffer.version().clone(),
5312 excerpt_visible_range,
5313 ),
5314 ))
5315 }
5316 })
5317 .collect()
5318 }
5319
5320 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5321 TextLayoutDetails {
5322 text_system: window.text_system().clone(),
5323 editor_style: self.style.clone().unwrap(),
5324 rem_size: window.rem_size(),
5325 scroll_anchor: self.scroll_manager.anchor(),
5326 visible_rows: self.visible_line_count(),
5327 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5328 }
5329 }
5330
5331 fn trigger_on_type_formatting(
5332 &self,
5333 input: String,
5334 window: &mut Window,
5335 cx: &mut Context<Self>,
5336 ) -> Option<Task<Result<()>>> {
5337 if input.len() != 1 {
5338 return None;
5339 }
5340
5341 let project = self.project()?;
5342 let position = self.selections.newest_anchor().head();
5343 let (buffer, buffer_position) = self
5344 .buffer
5345 .read(cx)
5346 .text_anchor_for_position(position, cx)?;
5347
5348 let settings = language_settings::language_settings(
5349 buffer
5350 .read(cx)
5351 .language_at(buffer_position)
5352 .map(|l| l.name()),
5353 buffer.read(cx).file(),
5354 cx,
5355 );
5356 if !settings.use_on_type_format {
5357 return None;
5358 }
5359
5360 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5361 // hence we do LSP request & edit on host side only — add formats to host's history.
5362 let push_to_lsp_host_history = true;
5363 // If this is not the host, append its history with new edits.
5364 let push_to_client_history = project.read(cx).is_via_collab();
5365
5366 let on_type_formatting = project.update(cx, |project, cx| {
5367 project.on_type_format(
5368 buffer.clone(),
5369 buffer_position,
5370 input,
5371 push_to_lsp_host_history,
5372 cx,
5373 )
5374 });
5375 Some(cx.spawn_in(window, async move |editor, cx| {
5376 if let Some(transaction) = on_type_formatting.await? {
5377 if push_to_client_history {
5378 buffer
5379 .update(cx, |buffer, _| {
5380 buffer.push_transaction(transaction, Instant::now());
5381 buffer.finalize_last_transaction();
5382 })
5383 .ok();
5384 }
5385 editor.update(cx, |editor, cx| {
5386 editor.refresh_document_highlights(cx);
5387 })?;
5388 }
5389 Ok(())
5390 }))
5391 }
5392
5393 pub fn show_word_completions(
5394 &mut self,
5395 _: &ShowWordCompletions,
5396 window: &mut Window,
5397 cx: &mut Context<Self>,
5398 ) {
5399 self.open_or_update_completions_menu(
5400 Some(CompletionsMenuSource::Words {
5401 ignore_threshold: true,
5402 }),
5403 None,
5404 window,
5405 cx,
5406 );
5407 }
5408
5409 pub fn show_completions(
5410 &mut self,
5411 options: &ShowCompletions,
5412 window: &mut Window,
5413 cx: &mut Context<Self>,
5414 ) {
5415 self.open_or_update_completions_menu(None, options.trigger.as_deref(), window, cx);
5416 }
5417
5418 fn open_or_update_completions_menu(
5419 &mut self,
5420 requested_source: Option<CompletionsMenuSource>,
5421 trigger: Option<&str>,
5422 window: &mut Window,
5423 cx: &mut Context<Self>,
5424 ) {
5425 if self.pending_rename.is_some() {
5426 return;
5427 }
5428
5429 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5430
5431 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5432 // inserted and selected. To handle that case, the start of the selection is used so that
5433 // the menu starts with all choices.
5434 let position = self
5435 .selections
5436 .newest_anchor()
5437 .start
5438 .bias_right(&multibuffer_snapshot);
5439 if position.diff_base_anchor.is_some() {
5440 return;
5441 }
5442 let buffer_position = multibuffer_snapshot.anchor_before(position);
5443 let Some(buffer) = buffer_position
5444 .buffer_id
5445 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5446 else {
5447 return;
5448 };
5449 let buffer_snapshot = buffer.read(cx).snapshot();
5450
5451 let query: Option<Arc<String>> =
5452 Self::completion_query(&multibuffer_snapshot, buffer_position)
5453 .map(|query| query.into());
5454
5455 drop(multibuffer_snapshot);
5456
5457 // Hide the current completions menu when query is empty. Without this, cached
5458 // completions from before the trigger char may be reused (#32774).
5459 if query.is_none() {
5460 let menu_is_open = matches!(
5461 self.context_menu.borrow().as_ref(),
5462 Some(CodeContextMenu::Completions(_))
5463 );
5464 if menu_is_open {
5465 self.hide_context_menu(window, cx);
5466 }
5467 }
5468
5469 let mut ignore_word_threshold = false;
5470 let provider = match requested_source {
5471 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5472 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5473 ignore_word_threshold = ignore_threshold;
5474 None
5475 }
5476 Some(CompletionsMenuSource::SnippetChoices) => {
5477 log::error!("bug: SnippetChoices requested_source is not handled");
5478 None
5479 }
5480 };
5481
5482 let sort_completions = provider
5483 .as_ref()
5484 .is_some_and(|provider| provider.sort_completions());
5485
5486 let filter_completions = provider
5487 .as_ref()
5488 .is_none_or(|provider| provider.filter_completions());
5489
5490 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5491 if filter_completions {
5492 menu.filter(query.clone(), provider.clone(), window, cx);
5493 }
5494 // When `is_incomplete` is false, no need to re-query completions when the current query
5495 // is a suffix of the initial query.
5496 if !menu.is_incomplete {
5497 // If the new query is a suffix of the old query (typing more characters) and
5498 // the previous result was complete, the existing completions can be filtered.
5499 //
5500 // Note that this is always true for snippet completions.
5501 let query_matches = match (&menu.initial_query, &query) {
5502 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5503 (None, _) => true,
5504 _ => false,
5505 };
5506 if query_matches {
5507 let position_matches = if menu.initial_position == position {
5508 true
5509 } else {
5510 let snapshot = self.buffer.read(cx).read(cx);
5511 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5512 };
5513 if position_matches {
5514 return;
5515 }
5516 }
5517 }
5518 };
5519
5520 let trigger_kind = match trigger {
5521 Some(trigger) if buffer.read(cx).completion_triggers().contains(trigger) => {
5522 CompletionTriggerKind::TRIGGER_CHARACTER
5523 }
5524 _ => CompletionTriggerKind::INVOKED,
5525 };
5526 let completion_context = CompletionContext {
5527 trigger_character: trigger.and_then(|trigger| {
5528 if trigger_kind == CompletionTriggerKind::TRIGGER_CHARACTER {
5529 Some(String::from(trigger))
5530 } else {
5531 None
5532 }
5533 }),
5534 trigger_kind,
5535 };
5536
5537 let Anchor {
5538 excerpt_id: buffer_excerpt_id,
5539 text_anchor: buffer_position,
5540 ..
5541 } = buffer_position;
5542
5543 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5544 buffer_snapshot.surrounding_word(buffer_position, None)
5545 {
5546 let word_to_exclude = buffer_snapshot
5547 .text_for_range(word_range.clone())
5548 .collect::<String>();
5549 (
5550 buffer_snapshot.anchor_before(word_range.start)
5551 ..buffer_snapshot.anchor_after(buffer_position),
5552 Some(word_to_exclude),
5553 )
5554 } else {
5555 (buffer_position..buffer_position, None)
5556 };
5557
5558 let language = buffer_snapshot
5559 .language_at(buffer_position)
5560 .map(|language| language.name());
5561
5562 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5563 .completions
5564 .clone();
5565
5566 let show_completion_documentation = buffer_snapshot
5567 .settings_at(buffer_position, cx)
5568 .show_completion_documentation;
5569
5570 // The document can be large, so stay in reasonable bounds when searching for words,
5571 // otherwise completion pop-up might be slow to appear.
5572 const WORD_LOOKUP_ROWS: u32 = 5_000;
5573 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5574 let min_word_search = buffer_snapshot.clip_point(
5575 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5576 Bias::Left,
5577 );
5578 let max_word_search = buffer_snapshot.clip_point(
5579 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5580 Bias::Right,
5581 );
5582 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5583 ..buffer_snapshot.point_to_offset(max_word_search);
5584
5585 let skip_digits = query
5586 .as_ref()
5587 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5588
5589 let omit_word_completions = !self.word_completions_enabled
5590 || (!ignore_word_threshold
5591 && match &query {
5592 Some(query) => query.chars().count() < completion_settings.words_min_length,
5593 None => completion_settings.words_min_length != 0,
5594 });
5595
5596 let (mut words, provider_responses) = match &provider {
5597 Some(provider) => {
5598 let provider_responses = provider.completions(
5599 buffer_excerpt_id,
5600 &buffer,
5601 buffer_position,
5602 completion_context,
5603 window,
5604 cx,
5605 );
5606
5607 let words = match (omit_word_completions, completion_settings.words) {
5608 (true, _) | (_, WordsCompletionMode::Disabled) => {
5609 Task::ready(BTreeMap::default())
5610 }
5611 (false, WordsCompletionMode::Enabled | WordsCompletionMode::Fallback) => cx
5612 .background_spawn(async move {
5613 buffer_snapshot.words_in_range(WordsQuery {
5614 fuzzy_contents: None,
5615 range: word_search_range,
5616 skip_digits,
5617 })
5618 }),
5619 };
5620
5621 (words, provider_responses)
5622 }
5623 None => {
5624 let words = if omit_word_completions {
5625 Task::ready(BTreeMap::default())
5626 } else {
5627 cx.background_spawn(async move {
5628 buffer_snapshot.words_in_range(WordsQuery {
5629 fuzzy_contents: None,
5630 range: word_search_range,
5631 skip_digits,
5632 })
5633 })
5634 };
5635 (words, Task::ready(Ok(Vec::new())))
5636 }
5637 };
5638
5639 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5640
5641 let id = post_inc(&mut self.next_completion_id);
5642 let task = cx.spawn_in(window, async move |editor, cx| {
5643 let Ok(()) = editor.update(cx, |this, _| {
5644 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5645 }) else {
5646 return;
5647 };
5648
5649 // TODO: Ideally completions from different sources would be selectively re-queried, so
5650 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5651 let mut completions = Vec::new();
5652 let mut is_incomplete = false;
5653 let mut display_options: Option<CompletionDisplayOptions> = None;
5654 if let Some(provider_responses) = provider_responses.await.log_err()
5655 && !provider_responses.is_empty()
5656 {
5657 for response in provider_responses {
5658 completions.extend(response.completions);
5659 is_incomplete = is_incomplete || response.is_incomplete;
5660 match display_options.as_mut() {
5661 None => {
5662 display_options = Some(response.display_options);
5663 }
5664 Some(options) => options.merge(&response.display_options),
5665 }
5666 }
5667 if completion_settings.words == WordsCompletionMode::Fallback {
5668 words = Task::ready(BTreeMap::default());
5669 }
5670 }
5671 let display_options = display_options.unwrap_or_default();
5672
5673 let mut words = words.await;
5674 if let Some(word_to_exclude) = &word_to_exclude {
5675 words.remove(word_to_exclude);
5676 }
5677 for lsp_completion in &completions {
5678 words.remove(&lsp_completion.new_text);
5679 }
5680 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5681 replace_range: word_replace_range.clone(),
5682 new_text: word.clone(),
5683 label: CodeLabel::plain(word, None),
5684 icon_path: None,
5685 documentation: None,
5686 source: CompletionSource::BufferWord {
5687 word_range,
5688 resolved: false,
5689 },
5690 insert_text_mode: Some(InsertTextMode::AS_IS),
5691 confirm: None,
5692 }));
5693
5694 let menu = if completions.is_empty() {
5695 None
5696 } else {
5697 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5698 let languages = editor
5699 .workspace
5700 .as_ref()
5701 .and_then(|(workspace, _)| workspace.upgrade())
5702 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5703 let menu = CompletionsMenu::new(
5704 id,
5705 requested_source.unwrap_or(CompletionsMenuSource::Normal),
5706 sort_completions,
5707 show_completion_documentation,
5708 position,
5709 query.clone(),
5710 is_incomplete,
5711 buffer.clone(),
5712 completions.into(),
5713 display_options,
5714 snippet_sort_order,
5715 languages,
5716 language,
5717 cx,
5718 );
5719
5720 let query = if filter_completions { query } else { None };
5721 let matches_task = if let Some(query) = query {
5722 menu.do_async_filtering(query, cx)
5723 } else {
5724 Task::ready(menu.unfiltered_matches())
5725 };
5726 (menu, matches_task)
5727 }) else {
5728 return;
5729 };
5730
5731 let matches = matches_task.await;
5732
5733 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5734 // Newer menu already set, so exit.
5735 if let Some(CodeContextMenu::Completions(prev_menu)) =
5736 editor.context_menu.borrow().as_ref()
5737 && prev_menu.id > id
5738 {
5739 return;
5740 };
5741
5742 // Only valid to take prev_menu because it the new menu is immediately set
5743 // below, or the menu is hidden.
5744 if let Some(CodeContextMenu::Completions(prev_menu)) =
5745 editor.context_menu.borrow_mut().take()
5746 {
5747 let position_matches =
5748 if prev_menu.initial_position == menu.initial_position {
5749 true
5750 } else {
5751 let snapshot = editor.buffer.read(cx).read(cx);
5752 prev_menu.initial_position.to_offset(&snapshot)
5753 == menu.initial_position.to_offset(&snapshot)
5754 };
5755 if position_matches {
5756 // Preserve markdown cache before `set_filter_results` because it will
5757 // try to populate the documentation cache.
5758 menu.preserve_markdown_cache(prev_menu);
5759 }
5760 };
5761
5762 menu.set_filter_results(matches, provider, window, cx);
5763 }) else {
5764 return;
5765 };
5766
5767 menu.visible().then_some(menu)
5768 };
5769
5770 editor
5771 .update_in(cx, |editor, window, cx| {
5772 if editor.focus_handle.is_focused(window)
5773 && let Some(menu) = menu
5774 {
5775 *editor.context_menu.borrow_mut() =
5776 Some(CodeContextMenu::Completions(menu));
5777
5778 crate::hover_popover::hide_hover(editor, cx);
5779 if editor.show_edit_predictions_in_menu() {
5780 editor.update_visible_edit_prediction(window, cx);
5781 } else {
5782 editor.discard_edit_prediction(false, cx);
5783 }
5784
5785 cx.notify();
5786 return;
5787 }
5788
5789 if editor.completion_tasks.len() <= 1 {
5790 // If there are no more completion tasks and the last menu was empty, we should hide it.
5791 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5792 // If it was already hidden and we don't show edit predictions in the menu,
5793 // we should also show the edit prediction when available.
5794 if was_hidden && editor.show_edit_predictions_in_menu() {
5795 editor.update_visible_edit_prediction(window, cx);
5796 }
5797 }
5798 })
5799 .ok();
5800 });
5801
5802 self.completion_tasks.push((id, task));
5803 }
5804
5805 #[cfg(feature = "test-support")]
5806 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5807 let menu = self.context_menu.borrow();
5808 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5809 let completions = menu.completions.borrow();
5810 Some(completions.to_vec())
5811 } else {
5812 None
5813 }
5814 }
5815
5816 pub fn with_completions_menu_matching_id<R>(
5817 &self,
5818 id: CompletionId,
5819 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5820 ) -> R {
5821 let mut context_menu = self.context_menu.borrow_mut();
5822 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5823 return f(None);
5824 };
5825 if completions_menu.id != id {
5826 return f(None);
5827 }
5828 f(Some(completions_menu))
5829 }
5830
5831 pub fn confirm_completion(
5832 &mut self,
5833 action: &ConfirmCompletion,
5834 window: &mut Window,
5835 cx: &mut Context<Self>,
5836 ) -> Option<Task<Result<()>>> {
5837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5838 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5839 }
5840
5841 pub fn confirm_completion_insert(
5842 &mut self,
5843 _: &ConfirmCompletionInsert,
5844 window: &mut Window,
5845 cx: &mut Context<Self>,
5846 ) -> Option<Task<Result<()>>> {
5847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5848 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5849 }
5850
5851 pub fn confirm_completion_replace(
5852 &mut self,
5853 _: &ConfirmCompletionReplace,
5854 window: &mut Window,
5855 cx: &mut Context<Self>,
5856 ) -> Option<Task<Result<()>>> {
5857 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5858 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5859 }
5860
5861 pub fn compose_completion(
5862 &mut self,
5863 action: &ComposeCompletion,
5864 window: &mut Window,
5865 cx: &mut Context<Self>,
5866 ) -> Option<Task<Result<()>>> {
5867 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5868 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5869 }
5870
5871 fn do_completion(
5872 &mut self,
5873 item_ix: Option<usize>,
5874 intent: CompletionIntent,
5875 window: &mut Window,
5876 cx: &mut Context<Editor>,
5877 ) -> Option<Task<Result<()>>> {
5878 use language::ToOffset as _;
5879
5880 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5881 else {
5882 return None;
5883 };
5884
5885 let candidate_id = {
5886 let entries = completions_menu.entries.borrow();
5887 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5888 if self.show_edit_predictions_in_menu() {
5889 self.discard_edit_prediction(true, cx);
5890 }
5891 mat.candidate_id
5892 };
5893
5894 let completion = completions_menu
5895 .completions
5896 .borrow()
5897 .get(candidate_id)?
5898 .clone();
5899 cx.stop_propagation();
5900
5901 let buffer_handle = completions_menu.buffer.clone();
5902
5903 let CompletionEdit {
5904 new_text,
5905 snippet,
5906 replace_range,
5907 } = process_completion_for_edit(
5908 &completion,
5909 intent,
5910 &buffer_handle,
5911 &completions_menu.initial_position.text_anchor,
5912 cx,
5913 );
5914
5915 let buffer = buffer_handle.read(cx);
5916 let snapshot = self.buffer.read(cx).snapshot(cx);
5917 let newest_anchor = self.selections.newest_anchor();
5918 let replace_range_multibuffer = {
5919 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5920 excerpt.map_range_from_buffer(replace_range.clone())
5921 };
5922 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5923 return None;
5924 }
5925
5926 let old_text = buffer
5927 .text_for_range(replace_range.clone())
5928 .collect::<String>();
5929 let lookbehind = newest_anchor
5930 .start
5931 .text_anchor
5932 .to_offset(buffer)
5933 .saturating_sub(replace_range.start);
5934 let lookahead = replace_range
5935 .end
5936 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5937 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5938 let suffix = &old_text[lookbehind.min(old_text.len())..];
5939
5940 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5941 let mut ranges = Vec::new();
5942 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5943
5944 for selection in &selections {
5945 let range = if selection.id == newest_anchor.id {
5946 replace_range_multibuffer.clone()
5947 } else {
5948 let mut range = selection.range();
5949
5950 // if prefix is present, don't duplicate it
5951 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5952 range.start = range.start.saturating_sub(lookbehind);
5953
5954 // if suffix is also present, mimic the newest cursor and replace it
5955 if selection.id != newest_anchor.id
5956 && snapshot.contains_str_at(range.end, suffix)
5957 {
5958 range.end += lookahead;
5959 }
5960 }
5961 range
5962 };
5963
5964 ranges.push(range.clone());
5965
5966 if !self.linked_edit_ranges.is_empty() {
5967 let start_anchor = snapshot.anchor_before(range.start);
5968 let end_anchor = snapshot.anchor_after(range.end);
5969 if let Some(ranges) = self
5970 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
5971 {
5972 for (buffer, edits) in ranges {
5973 linked_edits
5974 .entry(buffer.clone())
5975 .or_default()
5976 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
5977 }
5978 }
5979 }
5980 }
5981
5982 let common_prefix_len = old_text
5983 .chars()
5984 .zip(new_text.chars())
5985 .take_while(|(a, b)| a == b)
5986 .map(|(a, _)| a.len_utf8())
5987 .sum::<usize>();
5988
5989 cx.emit(EditorEvent::InputHandled {
5990 utf16_range_to_replace: None,
5991 text: new_text[common_prefix_len..].into(),
5992 });
5993
5994 self.transact(window, cx, |editor, window, cx| {
5995 if let Some(mut snippet) = snippet {
5996 snippet.text = new_text.to_string();
5997 editor
5998 .insert_snippet(&ranges, snippet, window, cx)
5999 .log_err();
6000 } else {
6001 editor.buffer.update(cx, |multi_buffer, cx| {
6002 let auto_indent = match completion.insert_text_mode {
6003 Some(InsertTextMode::AS_IS) => None,
6004 _ => editor.autoindent_mode.clone(),
6005 };
6006 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6007 multi_buffer.edit(edits, auto_indent, cx);
6008 });
6009 }
6010 for (buffer, edits) in linked_edits {
6011 buffer.update(cx, |buffer, cx| {
6012 let snapshot = buffer.snapshot();
6013 let edits = edits
6014 .into_iter()
6015 .map(|(range, text)| {
6016 use text::ToPoint as TP;
6017 let end_point = TP::to_point(&range.end, &snapshot);
6018 let start_point = TP::to_point(&range.start, &snapshot);
6019 (start_point..end_point, text)
6020 })
6021 .sorted_by_key(|(range, _)| range.start);
6022 buffer.edit(edits, None, cx);
6023 })
6024 }
6025
6026 editor.refresh_edit_prediction(true, false, window, cx);
6027 });
6028 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6029
6030 let show_new_completions_on_confirm = completion
6031 .confirm
6032 .as_ref()
6033 .is_some_and(|confirm| confirm(intent, window, cx));
6034 if show_new_completions_on_confirm {
6035 self.show_completions(&ShowCompletions { trigger: None }, window, cx);
6036 }
6037
6038 let provider = self.completion_provider.as_ref()?;
6039 drop(completion);
6040 let apply_edits = provider.apply_additional_edits_for_completion(
6041 buffer_handle,
6042 completions_menu.completions.clone(),
6043 candidate_id,
6044 true,
6045 cx,
6046 );
6047
6048 let editor_settings = EditorSettings::get_global(cx);
6049 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6050 // After the code completion is finished, users often want to know what signatures are needed.
6051 // so we should automatically call signature_help
6052 self.show_signature_help(&ShowSignatureHelp, window, cx);
6053 }
6054
6055 Some(cx.foreground_executor().spawn(async move {
6056 apply_edits.await?;
6057 Ok(())
6058 }))
6059 }
6060
6061 pub fn toggle_code_actions(
6062 &mut self,
6063 action: &ToggleCodeActions,
6064 window: &mut Window,
6065 cx: &mut Context<Self>,
6066 ) {
6067 let quick_launch = action.quick_launch;
6068 let mut context_menu = self.context_menu.borrow_mut();
6069 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6070 if code_actions.deployed_from == action.deployed_from {
6071 // Toggle if we're selecting the same one
6072 *context_menu = None;
6073 cx.notify();
6074 return;
6075 } else {
6076 // Otherwise, clear it and start a new one
6077 *context_menu = None;
6078 cx.notify();
6079 }
6080 }
6081 drop(context_menu);
6082 let snapshot = self.snapshot(window, cx);
6083 let deployed_from = action.deployed_from.clone();
6084 let action = action.clone();
6085 self.completion_tasks.clear();
6086 self.discard_edit_prediction(false, cx);
6087
6088 let multibuffer_point = match &action.deployed_from {
6089 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6090 DisplayPoint::new(*row, 0).to_point(&snapshot)
6091 }
6092 _ => self
6093 .selections
6094 .newest::<Point>(&snapshot.display_snapshot)
6095 .head(),
6096 };
6097 let Some((buffer, buffer_row)) = snapshot
6098 .buffer_snapshot()
6099 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6100 .and_then(|(buffer_snapshot, range)| {
6101 self.buffer()
6102 .read(cx)
6103 .buffer(buffer_snapshot.remote_id())
6104 .map(|buffer| (buffer, range.start.row))
6105 })
6106 else {
6107 return;
6108 };
6109 let buffer_id = buffer.read(cx).remote_id();
6110 let tasks = self
6111 .tasks
6112 .get(&(buffer_id, buffer_row))
6113 .map(|t| Arc::new(t.to_owned()));
6114
6115 if !self.focus_handle.is_focused(window) {
6116 return;
6117 }
6118 let project = self.project.clone();
6119
6120 let code_actions_task = match deployed_from {
6121 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6122 _ => self.code_actions(buffer_row, window, cx),
6123 };
6124
6125 let runnable_task = match deployed_from {
6126 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6127 _ => {
6128 let mut task_context_task = Task::ready(None);
6129 if let Some(tasks) = &tasks
6130 && let Some(project) = project
6131 {
6132 task_context_task =
6133 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6134 }
6135
6136 cx.spawn_in(window, {
6137 let buffer = buffer.clone();
6138 async move |editor, cx| {
6139 let task_context = task_context_task.await;
6140
6141 let resolved_tasks =
6142 tasks
6143 .zip(task_context.clone())
6144 .map(|(tasks, task_context)| ResolvedTasks {
6145 templates: tasks.resolve(&task_context).collect(),
6146 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6147 multibuffer_point.row,
6148 tasks.column,
6149 )),
6150 });
6151 let debug_scenarios = editor
6152 .update(cx, |editor, cx| {
6153 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6154 })?
6155 .await;
6156 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6157 }
6158 })
6159 }
6160 };
6161
6162 cx.spawn_in(window, async move |editor, cx| {
6163 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6164 let code_actions = code_actions_task.await;
6165 let spawn_straight_away = quick_launch
6166 && resolved_tasks
6167 .as_ref()
6168 .is_some_and(|tasks| tasks.templates.len() == 1)
6169 && code_actions
6170 .as_ref()
6171 .is_none_or(|actions| actions.is_empty())
6172 && debug_scenarios.is_empty();
6173
6174 editor.update_in(cx, |editor, window, cx| {
6175 crate::hover_popover::hide_hover(editor, cx);
6176 let actions = CodeActionContents::new(
6177 resolved_tasks,
6178 code_actions,
6179 debug_scenarios,
6180 task_context.unwrap_or_default(),
6181 );
6182
6183 // Don't show the menu if there are no actions available
6184 if actions.is_empty() {
6185 cx.notify();
6186 return Task::ready(Ok(()));
6187 }
6188
6189 *editor.context_menu.borrow_mut() =
6190 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6191 buffer,
6192 actions,
6193 selected_item: Default::default(),
6194 scroll_handle: UniformListScrollHandle::default(),
6195 deployed_from,
6196 }));
6197 cx.notify();
6198 if spawn_straight_away
6199 && let Some(task) = editor.confirm_code_action(
6200 &ConfirmCodeAction { item_ix: Some(0) },
6201 window,
6202 cx,
6203 )
6204 {
6205 return task;
6206 }
6207
6208 Task::ready(Ok(()))
6209 })
6210 })
6211 .detach_and_log_err(cx);
6212 }
6213
6214 fn debug_scenarios(
6215 &mut self,
6216 resolved_tasks: &Option<ResolvedTasks>,
6217 buffer: &Entity<Buffer>,
6218 cx: &mut App,
6219 ) -> Task<Vec<task::DebugScenario>> {
6220 maybe!({
6221 let project = self.project()?;
6222 let dap_store = project.read(cx).dap_store();
6223 let mut scenarios = vec![];
6224 let resolved_tasks = resolved_tasks.as_ref()?;
6225 let buffer = buffer.read(cx);
6226 let language = buffer.language()?;
6227 let file = buffer.file();
6228 let debug_adapter = language_settings(language.name().into(), file, cx)
6229 .debuggers
6230 .first()
6231 .map(SharedString::from)
6232 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6233
6234 dap_store.update(cx, |dap_store, cx| {
6235 for (_, task) in &resolved_tasks.templates {
6236 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6237 task.original_task().clone(),
6238 debug_adapter.clone().into(),
6239 task.display_label().to_owned().into(),
6240 cx,
6241 );
6242 scenarios.push(maybe_scenario);
6243 }
6244 });
6245 Some(cx.background_spawn(async move {
6246 futures::future::join_all(scenarios)
6247 .await
6248 .into_iter()
6249 .flatten()
6250 .collect::<Vec<_>>()
6251 }))
6252 })
6253 .unwrap_or_else(|| Task::ready(vec![]))
6254 }
6255
6256 fn code_actions(
6257 &mut self,
6258 buffer_row: u32,
6259 window: &mut Window,
6260 cx: &mut Context<Self>,
6261 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6262 let mut task = self.code_actions_task.take();
6263 cx.spawn_in(window, async move |editor, cx| {
6264 while let Some(prev_task) = task {
6265 prev_task.await.log_err();
6266 task = editor
6267 .update(cx, |this, _| this.code_actions_task.take())
6268 .ok()?;
6269 }
6270
6271 editor
6272 .update(cx, |editor, cx| {
6273 editor
6274 .available_code_actions
6275 .clone()
6276 .and_then(|(location, code_actions)| {
6277 let snapshot = location.buffer.read(cx).snapshot();
6278 let point_range = location.range.to_point(&snapshot);
6279 let point_range = point_range.start.row..=point_range.end.row;
6280 if point_range.contains(&buffer_row) {
6281 Some(code_actions)
6282 } else {
6283 None
6284 }
6285 })
6286 })
6287 .ok()
6288 .flatten()
6289 })
6290 }
6291
6292 pub fn confirm_code_action(
6293 &mut self,
6294 action: &ConfirmCodeAction,
6295 window: &mut Window,
6296 cx: &mut Context<Self>,
6297 ) -> Option<Task<Result<()>>> {
6298 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6299
6300 let actions_menu =
6301 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6302 menu
6303 } else {
6304 return None;
6305 };
6306
6307 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6308 let action = actions_menu.actions.get(action_ix)?;
6309 let title = action.label();
6310 let buffer = actions_menu.buffer;
6311 let workspace = self.workspace()?;
6312
6313 match action {
6314 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6315 workspace.update(cx, |workspace, cx| {
6316 workspace.schedule_resolved_task(
6317 task_source_kind,
6318 resolved_task,
6319 false,
6320 window,
6321 cx,
6322 );
6323
6324 Some(Task::ready(Ok(())))
6325 })
6326 }
6327 CodeActionsItem::CodeAction {
6328 excerpt_id,
6329 action,
6330 provider,
6331 } => {
6332 let apply_code_action =
6333 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6334 let workspace = workspace.downgrade();
6335 Some(cx.spawn_in(window, async move |editor, cx| {
6336 let project_transaction = apply_code_action.await?;
6337 Self::open_project_transaction(
6338 &editor,
6339 workspace,
6340 project_transaction,
6341 title,
6342 cx,
6343 )
6344 .await
6345 }))
6346 }
6347 CodeActionsItem::DebugScenario(scenario) => {
6348 let context = actions_menu.actions.context;
6349
6350 workspace.update(cx, |workspace, cx| {
6351 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6352 workspace.start_debug_session(
6353 scenario,
6354 context,
6355 Some(buffer),
6356 None,
6357 window,
6358 cx,
6359 );
6360 });
6361 Some(Task::ready(Ok(())))
6362 }
6363 }
6364 }
6365
6366 pub async fn open_project_transaction(
6367 editor: &WeakEntity<Editor>,
6368 workspace: WeakEntity<Workspace>,
6369 transaction: ProjectTransaction,
6370 title: String,
6371 cx: &mut AsyncWindowContext,
6372 ) -> Result<()> {
6373 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6374 cx.update(|_, cx| {
6375 entries.sort_unstable_by_key(|(buffer, _)| {
6376 buffer.read(cx).file().map(|f| f.path().clone())
6377 });
6378 })?;
6379 if entries.is_empty() {
6380 return Ok(());
6381 }
6382
6383 // If the project transaction's edits are all contained within this editor, then
6384 // avoid opening a new editor to display them.
6385
6386 if let [(buffer, transaction)] = &*entries {
6387 let excerpt = editor.update(cx, |editor, cx| {
6388 editor
6389 .buffer()
6390 .read(cx)
6391 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6392 })?;
6393 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6394 && excerpted_buffer == *buffer
6395 {
6396 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6397 let excerpt_range = excerpt_range.to_offset(buffer);
6398 buffer
6399 .edited_ranges_for_transaction::<usize>(transaction)
6400 .all(|range| {
6401 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6402 })
6403 })?;
6404
6405 if all_edits_within_excerpt {
6406 return Ok(());
6407 }
6408 }
6409 }
6410
6411 let mut ranges_to_highlight = Vec::new();
6412 let excerpt_buffer = cx.new(|cx| {
6413 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6414 for (buffer_handle, transaction) in &entries {
6415 let edited_ranges = buffer_handle
6416 .read(cx)
6417 .edited_ranges_for_transaction::<Point>(transaction)
6418 .collect::<Vec<_>>();
6419 let (ranges, _) = multibuffer.set_excerpts_for_path(
6420 PathKey::for_buffer(buffer_handle, cx),
6421 buffer_handle.clone(),
6422 edited_ranges,
6423 multibuffer_context_lines(cx),
6424 cx,
6425 );
6426
6427 ranges_to_highlight.extend(ranges);
6428 }
6429 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6430 multibuffer
6431 })?;
6432
6433 workspace.update_in(cx, |workspace, window, cx| {
6434 let project = workspace.project().clone();
6435 let editor =
6436 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6437 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6438 editor.update(cx, |editor, cx| {
6439 editor.highlight_background::<Self>(
6440 &ranges_to_highlight,
6441 |theme| theme.colors().editor_highlighted_line_background,
6442 cx,
6443 );
6444 });
6445 })?;
6446
6447 Ok(())
6448 }
6449
6450 pub fn clear_code_action_providers(&mut self) {
6451 self.code_action_providers.clear();
6452 self.available_code_actions.take();
6453 }
6454
6455 pub fn add_code_action_provider(
6456 &mut self,
6457 provider: Rc<dyn CodeActionProvider>,
6458 window: &mut Window,
6459 cx: &mut Context<Self>,
6460 ) {
6461 if self
6462 .code_action_providers
6463 .iter()
6464 .any(|existing_provider| existing_provider.id() == provider.id())
6465 {
6466 return;
6467 }
6468
6469 self.code_action_providers.push(provider);
6470 self.refresh_code_actions(window, cx);
6471 }
6472
6473 pub fn remove_code_action_provider(
6474 &mut self,
6475 id: Arc<str>,
6476 window: &mut Window,
6477 cx: &mut Context<Self>,
6478 ) {
6479 self.code_action_providers
6480 .retain(|provider| provider.id() != id);
6481 self.refresh_code_actions(window, cx);
6482 }
6483
6484 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6485 !self.code_action_providers.is_empty()
6486 && EditorSettings::get_global(cx).toolbar.code_actions
6487 }
6488
6489 pub fn has_available_code_actions(&self) -> bool {
6490 self.available_code_actions
6491 .as_ref()
6492 .is_some_and(|(_, actions)| !actions.is_empty())
6493 }
6494
6495 fn render_inline_code_actions(
6496 &self,
6497 icon_size: ui::IconSize,
6498 display_row: DisplayRow,
6499 is_active: bool,
6500 cx: &mut Context<Self>,
6501 ) -> AnyElement {
6502 let show_tooltip = !self.context_menu_visible();
6503 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6504 .icon_size(icon_size)
6505 .shape(ui::IconButtonShape::Square)
6506 .icon_color(ui::Color::Hidden)
6507 .toggle_state(is_active)
6508 .when(show_tooltip, |this| {
6509 this.tooltip({
6510 let focus_handle = self.focus_handle.clone();
6511 move |_window, cx| {
6512 Tooltip::for_action_in(
6513 "Toggle Code Actions",
6514 &ToggleCodeActions {
6515 deployed_from: None,
6516 quick_launch: false,
6517 },
6518 &focus_handle,
6519 cx,
6520 )
6521 }
6522 })
6523 })
6524 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6525 window.focus(&editor.focus_handle(cx));
6526 editor.toggle_code_actions(
6527 &crate::actions::ToggleCodeActions {
6528 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6529 display_row,
6530 )),
6531 quick_launch: false,
6532 },
6533 window,
6534 cx,
6535 );
6536 }))
6537 .into_any_element()
6538 }
6539
6540 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6541 &self.context_menu
6542 }
6543
6544 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6545 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6546 cx.background_executor()
6547 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6548 .await;
6549
6550 let (start_buffer, start, _, end, newest_selection) = this
6551 .update(cx, |this, cx| {
6552 let newest_selection = this.selections.newest_anchor().clone();
6553 if newest_selection.head().diff_base_anchor.is_some() {
6554 return None;
6555 }
6556 let display_snapshot = this.display_snapshot(cx);
6557 let newest_selection_adjusted =
6558 this.selections.newest_adjusted(&display_snapshot);
6559 let buffer = this.buffer.read(cx);
6560
6561 let (start_buffer, start) =
6562 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6563 let (end_buffer, end) =
6564 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6565
6566 Some((start_buffer, start, end_buffer, end, newest_selection))
6567 })?
6568 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6569 .context(
6570 "Expected selection to lie in a single buffer when refreshing code actions",
6571 )?;
6572 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6573 let providers = this.code_action_providers.clone();
6574 let tasks = this
6575 .code_action_providers
6576 .iter()
6577 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6578 .collect::<Vec<_>>();
6579 (providers, tasks)
6580 })?;
6581
6582 let mut actions = Vec::new();
6583 for (provider, provider_actions) in
6584 providers.into_iter().zip(future::join_all(tasks).await)
6585 {
6586 if let Some(provider_actions) = provider_actions.log_err() {
6587 actions.extend(provider_actions.into_iter().map(|action| {
6588 AvailableCodeAction {
6589 excerpt_id: newest_selection.start.excerpt_id,
6590 action,
6591 provider: provider.clone(),
6592 }
6593 }));
6594 }
6595 }
6596
6597 this.update(cx, |this, cx| {
6598 this.available_code_actions = if actions.is_empty() {
6599 None
6600 } else {
6601 Some((
6602 Location {
6603 buffer: start_buffer,
6604 range: start..end,
6605 },
6606 actions.into(),
6607 ))
6608 };
6609 cx.notify();
6610 })
6611 }));
6612 }
6613
6614 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6615 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6616 self.show_git_blame_inline = false;
6617
6618 self.show_git_blame_inline_delay_task =
6619 Some(cx.spawn_in(window, async move |this, cx| {
6620 cx.background_executor().timer(delay).await;
6621
6622 this.update(cx, |this, cx| {
6623 this.show_git_blame_inline = true;
6624 cx.notify();
6625 })
6626 .log_err();
6627 }));
6628 }
6629 }
6630
6631 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6632 let snapshot = self.snapshot(window, cx);
6633 let cursor = self
6634 .selections
6635 .newest::<Point>(&snapshot.display_snapshot)
6636 .head();
6637 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6638 else {
6639 return;
6640 };
6641
6642 let Some(blame) = self.blame.as_ref() else {
6643 return;
6644 };
6645
6646 let row_info = RowInfo {
6647 buffer_id: Some(buffer.remote_id()),
6648 buffer_row: Some(point.row),
6649 ..Default::default()
6650 };
6651 let Some((buffer, blame_entry)) = blame
6652 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6653 .flatten()
6654 else {
6655 return;
6656 };
6657
6658 let anchor = self.selections.newest_anchor().head();
6659 let position = self.to_pixel_point(anchor, &snapshot, window);
6660 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6661 self.show_blame_popover(
6662 buffer,
6663 &blame_entry,
6664 position + last_bounds.origin,
6665 true,
6666 cx,
6667 );
6668 };
6669 }
6670
6671 fn show_blame_popover(
6672 &mut self,
6673 buffer: BufferId,
6674 blame_entry: &BlameEntry,
6675 position: gpui::Point<Pixels>,
6676 ignore_timeout: bool,
6677 cx: &mut Context<Self>,
6678 ) {
6679 if let Some(state) = &mut self.inline_blame_popover {
6680 state.hide_task.take();
6681 } else {
6682 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6683 let blame_entry = blame_entry.clone();
6684 let show_task = cx.spawn(async move |editor, cx| {
6685 if !ignore_timeout {
6686 cx.background_executor()
6687 .timer(std::time::Duration::from_millis(blame_popover_delay))
6688 .await;
6689 }
6690 editor
6691 .update(cx, |editor, cx| {
6692 editor.inline_blame_popover_show_task.take();
6693 let Some(blame) = editor.blame.as_ref() else {
6694 return;
6695 };
6696 let blame = blame.read(cx);
6697 let details = blame.details_for_entry(buffer, &blame_entry);
6698 let markdown = cx.new(|cx| {
6699 Markdown::new(
6700 details
6701 .as_ref()
6702 .map(|message| message.message.clone())
6703 .unwrap_or_default(),
6704 None,
6705 None,
6706 cx,
6707 )
6708 });
6709 editor.inline_blame_popover = Some(InlineBlamePopover {
6710 position,
6711 hide_task: None,
6712 popover_bounds: None,
6713 popover_state: InlineBlamePopoverState {
6714 scroll_handle: ScrollHandle::new(),
6715 commit_message: details,
6716 markdown,
6717 },
6718 keyboard_grace: ignore_timeout,
6719 });
6720 cx.notify();
6721 })
6722 .ok();
6723 });
6724 self.inline_blame_popover_show_task = Some(show_task);
6725 }
6726 }
6727
6728 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6729 self.inline_blame_popover_show_task.take();
6730 if let Some(state) = &mut self.inline_blame_popover {
6731 let hide_task = cx.spawn(async move |editor, cx| {
6732 if !ignore_timeout {
6733 cx.background_executor()
6734 .timer(std::time::Duration::from_millis(100))
6735 .await;
6736 }
6737 editor
6738 .update(cx, |editor, cx| {
6739 editor.inline_blame_popover.take();
6740 cx.notify();
6741 })
6742 .ok();
6743 });
6744 state.hide_task = Some(hide_task);
6745 true
6746 } else {
6747 false
6748 }
6749 }
6750
6751 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6752 if self.pending_rename.is_some() {
6753 return None;
6754 }
6755
6756 let provider = self.semantics_provider.clone()?;
6757 let buffer = self.buffer.read(cx);
6758 let newest_selection = self.selections.newest_anchor().clone();
6759 let cursor_position = newest_selection.head();
6760 let (cursor_buffer, cursor_buffer_position) =
6761 buffer.text_anchor_for_position(cursor_position, cx)?;
6762 let (tail_buffer, tail_buffer_position) =
6763 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6764 if cursor_buffer != tail_buffer {
6765 return None;
6766 }
6767
6768 let snapshot = cursor_buffer.read(cx).snapshot();
6769 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6770 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6771 if start_word_range != end_word_range {
6772 self.document_highlights_task.take();
6773 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6774 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6775 return None;
6776 }
6777
6778 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6779 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6780 cx.background_executor()
6781 .timer(Duration::from_millis(debounce))
6782 .await;
6783
6784 let highlights = if let Some(highlights) = cx
6785 .update(|cx| {
6786 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6787 })
6788 .ok()
6789 .flatten()
6790 {
6791 highlights.await.log_err()
6792 } else {
6793 None
6794 };
6795
6796 if let Some(highlights) = highlights {
6797 this.update(cx, |this, cx| {
6798 if this.pending_rename.is_some() {
6799 return;
6800 }
6801
6802 let buffer = this.buffer.read(cx);
6803 if buffer
6804 .text_anchor_for_position(cursor_position, cx)
6805 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6806 {
6807 return;
6808 }
6809
6810 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6811 let mut write_ranges = Vec::new();
6812 let mut read_ranges = Vec::new();
6813 for highlight in highlights {
6814 let buffer_id = cursor_buffer.read(cx).remote_id();
6815 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6816 {
6817 let start = highlight
6818 .range
6819 .start
6820 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6821 let end = highlight
6822 .range
6823 .end
6824 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6825 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6826 continue;
6827 }
6828
6829 let range =
6830 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6831 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6832 write_ranges.push(range);
6833 } else {
6834 read_ranges.push(range);
6835 }
6836 }
6837 }
6838
6839 this.highlight_background::<DocumentHighlightRead>(
6840 &read_ranges,
6841 |theme| theme.colors().editor_document_highlight_read_background,
6842 cx,
6843 );
6844 this.highlight_background::<DocumentHighlightWrite>(
6845 &write_ranges,
6846 |theme| theme.colors().editor_document_highlight_write_background,
6847 cx,
6848 );
6849 cx.notify();
6850 })
6851 .log_err();
6852 }
6853 }));
6854 None
6855 }
6856
6857 fn prepare_highlight_query_from_selection(
6858 &mut self,
6859 window: &Window,
6860 cx: &mut Context<Editor>,
6861 ) -> Option<(String, Range<Anchor>)> {
6862 if matches!(self.mode, EditorMode::SingleLine) {
6863 return None;
6864 }
6865 if !EditorSettings::get_global(cx).selection_highlight {
6866 return None;
6867 }
6868 if self.selections.count() != 1 || self.selections.line_mode() {
6869 return None;
6870 }
6871 let snapshot = self.snapshot(window, cx);
6872 let selection = self.selections.newest::<Point>(&snapshot);
6873 // If the selection spans multiple rows OR it is empty
6874 if selection.start.row != selection.end.row
6875 || selection.start.column == selection.end.column
6876 {
6877 return None;
6878 }
6879 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6880 let query = snapshot
6881 .buffer_snapshot()
6882 .text_for_range(selection_anchor_range.clone())
6883 .collect::<String>();
6884 if query.trim().is_empty() {
6885 return None;
6886 }
6887 Some((query, selection_anchor_range))
6888 }
6889
6890 fn update_selection_occurrence_highlights(
6891 &mut self,
6892 query_text: String,
6893 query_range: Range<Anchor>,
6894 multi_buffer_range_to_query: Range<Point>,
6895 use_debounce: bool,
6896 window: &mut Window,
6897 cx: &mut Context<Editor>,
6898 ) -> Task<()> {
6899 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6900 cx.spawn_in(window, async move |editor, cx| {
6901 if use_debounce {
6902 cx.background_executor()
6903 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6904 .await;
6905 }
6906 let match_task = cx.background_spawn(async move {
6907 let buffer_ranges = multi_buffer_snapshot
6908 .range_to_buffer_ranges(multi_buffer_range_to_query)
6909 .into_iter()
6910 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6911 let mut match_ranges = Vec::new();
6912 let Ok(regex) = project::search::SearchQuery::text(
6913 query_text.clone(),
6914 false,
6915 false,
6916 false,
6917 Default::default(),
6918 Default::default(),
6919 false,
6920 None,
6921 ) else {
6922 return Vec::default();
6923 };
6924 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6925 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6926 match_ranges.extend(
6927 regex
6928 .search(buffer_snapshot, Some(search_range.clone()))
6929 .await
6930 .into_iter()
6931 .filter_map(|match_range| {
6932 let match_start = buffer_snapshot
6933 .anchor_after(search_range.start + match_range.start);
6934 let match_end = buffer_snapshot
6935 .anchor_before(search_range.start + match_range.end);
6936 let match_anchor_range = Anchor::range_in_buffer(
6937 excerpt_id,
6938 buffer_snapshot.remote_id(),
6939 match_start..match_end,
6940 );
6941 (match_anchor_range != query_range).then_some(match_anchor_range)
6942 }),
6943 );
6944 }
6945 match_ranges
6946 });
6947 let match_ranges = match_task.await;
6948 editor
6949 .update_in(cx, |editor, _, cx| {
6950 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6951 if !match_ranges.is_empty() {
6952 editor.highlight_background::<SelectedTextHighlight>(
6953 &match_ranges,
6954 |theme| theme.colors().editor_document_highlight_bracket_background,
6955 cx,
6956 )
6957 }
6958 })
6959 .log_err();
6960 })
6961 }
6962
6963 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6964 struct NewlineFold;
6965 let type_id = std::any::TypeId::of::<NewlineFold>();
6966 if !self.mode.is_single_line() {
6967 return;
6968 }
6969 let snapshot = self.snapshot(window, cx);
6970 if snapshot.buffer_snapshot().max_point().row == 0 {
6971 return;
6972 }
6973 let task = cx.background_spawn(async move {
6974 let new_newlines = snapshot
6975 .buffer_chars_at(0)
6976 .filter_map(|(c, i)| {
6977 if c == '\n' {
6978 Some(
6979 snapshot.buffer_snapshot().anchor_after(i)
6980 ..snapshot.buffer_snapshot().anchor_before(i + 1),
6981 )
6982 } else {
6983 None
6984 }
6985 })
6986 .collect::<Vec<_>>();
6987 let existing_newlines = snapshot
6988 .folds_in_range(0..snapshot.buffer_snapshot().len())
6989 .filter_map(|fold| {
6990 if fold.placeholder.type_tag == Some(type_id) {
6991 Some(fold.range.start..fold.range.end)
6992 } else {
6993 None
6994 }
6995 })
6996 .collect::<Vec<_>>();
6997
6998 (new_newlines, existing_newlines)
6999 });
7000 self.folding_newlines = cx.spawn(async move |this, cx| {
7001 let (new_newlines, existing_newlines) = task.await;
7002 if new_newlines == existing_newlines {
7003 return;
7004 }
7005 let placeholder = FoldPlaceholder {
7006 render: Arc::new(move |_, _, cx| {
7007 div()
7008 .bg(cx.theme().status().hint_background)
7009 .border_b_1()
7010 .size_full()
7011 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7012 .border_color(cx.theme().status().hint)
7013 .child("\\n")
7014 .into_any()
7015 }),
7016 constrain_width: false,
7017 merge_adjacent: false,
7018 type_tag: Some(type_id),
7019 };
7020 let creases = new_newlines
7021 .into_iter()
7022 .map(|range| Crease::simple(range, placeholder.clone()))
7023 .collect();
7024 this.update(cx, |this, cx| {
7025 this.display_map.update(cx, |display_map, cx| {
7026 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7027 display_map.fold(creases, cx);
7028 });
7029 })
7030 .ok();
7031 });
7032 }
7033
7034 fn refresh_selected_text_highlights(
7035 &mut self,
7036 on_buffer_edit: bool,
7037 window: &mut Window,
7038 cx: &mut Context<Editor>,
7039 ) {
7040 let Some((query_text, query_range)) =
7041 self.prepare_highlight_query_from_selection(window, cx)
7042 else {
7043 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7044 self.quick_selection_highlight_task.take();
7045 self.debounced_selection_highlight_task.take();
7046 return;
7047 };
7048 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7049 if on_buffer_edit
7050 || self
7051 .quick_selection_highlight_task
7052 .as_ref()
7053 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7054 {
7055 let multi_buffer_visible_start = self
7056 .scroll_manager
7057 .anchor()
7058 .anchor
7059 .to_point(&multi_buffer_snapshot);
7060 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7061 multi_buffer_visible_start
7062 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7063 Bias::Left,
7064 );
7065 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7066 self.quick_selection_highlight_task = Some((
7067 query_range.clone(),
7068 self.update_selection_occurrence_highlights(
7069 query_text.clone(),
7070 query_range.clone(),
7071 multi_buffer_visible_range,
7072 false,
7073 window,
7074 cx,
7075 ),
7076 ));
7077 }
7078 if on_buffer_edit
7079 || self
7080 .debounced_selection_highlight_task
7081 .as_ref()
7082 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7083 {
7084 let multi_buffer_start = multi_buffer_snapshot
7085 .anchor_before(0)
7086 .to_point(&multi_buffer_snapshot);
7087 let multi_buffer_end = multi_buffer_snapshot
7088 .anchor_after(multi_buffer_snapshot.len())
7089 .to_point(&multi_buffer_snapshot);
7090 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7091 self.debounced_selection_highlight_task = Some((
7092 query_range.clone(),
7093 self.update_selection_occurrence_highlights(
7094 query_text,
7095 query_range,
7096 multi_buffer_full_range,
7097 true,
7098 window,
7099 cx,
7100 ),
7101 ));
7102 }
7103 }
7104
7105 pub fn refresh_edit_prediction(
7106 &mut self,
7107 debounce: bool,
7108 user_requested: bool,
7109 window: &mut Window,
7110 cx: &mut Context<Self>,
7111 ) -> Option<()> {
7112 if DisableAiSettings::get_global(cx).disable_ai {
7113 return None;
7114 }
7115
7116 let provider = self.edit_prediction_provider()?;
7117 let cursor = self.selections.newest_anchor().head();
7118 let (buffer, cursor_buffer_position) =
7119 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7120
7121 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7122 self.discard_edit_prediction(false, cx);
7123 return None;
7124 }
7125
7126 self.update_visible_edit_prediction(window, cx);
7127
7128 if !user_requested
7129 && (!self.should_show_edit_predictions()
7130 || !self.is_focused(window)
7131 || buffer.read(cx).is_empty())
7132 {
7133 self.discard_edit_prediction(false, cx);
7134 return None;
7135 }
7136
7137 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7138 Some(())
7139 }
7140
7141 fn show_edit_predictions_in_menu(&self) -> bool {
7142 match self.edit_prediction_settings {
7143 EditPredictionSettings::Disabled => false,
7144 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7145 }
7146 }
7147
7148 pub fn edit_predictions_enabled(&self) -> bool {
7149 match self.edit_prediction_settings {
7150 EditPredictionSettings::Disabled => false,
7151 EditPredictionSettings::Enabled { .. } => true,
7152 }
7153 }
7154
7155 fn edit_prediction_requires_modifier(&self) -> bool {
7156 match self.edit_prediction_settings {
7157 EditPredictionSettings::Disabled => false,
7158 EditPredictionSettings::Enabled {
7159 preview_requires_modifier,
7160 ..
7161 } => preview_requires_modifier,
7162 }
7163 }
7164
7165 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7166 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7167 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7168 self.discard_edit_prediction(false, cx);
7169 } else {
7170 let selection = self.selections.newest_anchor();
7171 let cursor = selection.head();
7172
7173 if let Some((buffer, cursor_buffer_position)) =
7174 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7175 {
7176 self.edit_prediction_settings =
7177 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7178 }
7179 }
7180 }
7181
7182 fn edit_prediction_settings_at_position(
7183 &self,
7184 buffer: &Entity<Buffer>,
7185 buffer_position: language::Anchor,
7186 cx: &App,
7187 ) -> EditPredictionSettings {
7188 if !self.mode.is_full()
7189 || !self.show_edit_predictions_override.unwrap_or(true)
7190 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7191 {
7192 return EditPredictionSettings::Disabled;
7193 }
7194
7195 let buffer = buffer.read(cx);
7196
7197 let file = buffer.file();
7198
7199 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7200 return EditPredictionSettings::Disabled;
7201 };
7202
7203 let by_provider = matches!(
7204 self.menu_edit_predictions_policy,
7205 MenuEditPredictionsPolicy::ByProvider
7206 );
7207
7208 let show_in_menu = by_provider
7209 && self
7210 .edit_prediction_provider
7211 .as_ref()
7212 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7213
7214 let preview_requires_modifier =
7215 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7216
7217 EditPredictionSettings::Enabled {
7218 show_in_menu,
7219 preview_requires_modifier,
7220 }
7221 }
7222
7223 fn should_show_edit_predictions(&self) -> bool {
7224 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7225 }
7226
7227 pub fn edit_prediction_preview_is_active(&self) -> bool {
7228 matches!(
7229 self.edit_prediction_preview,
7230 EditPredictionPreview::Active { .. }
7231 )
7232 }
7233
7234 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7235 let cursor = self.selections.newest_anchor().head();
7236 if let Some((buffer, cursor_position)) =
7237 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7238 {
7239 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7240 } else {
7241 false
7242 }
7243 }
7244
7245 pub fn supports_minimap(&self, cx: &App) -> bool {
7246 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7247 }
7248
7249 fn edit_predictions_enabled_in_buffer(
7250 &self,
7251 buffer: &Entity<Buffer>,
7252 buffer_position: language::Anchor,
7253 cx: &App,
7254 ) -> bool {
7255 maybe!({
7256 if self.read_only(cx) {
7257 return Some(false);
7258 }
7259 let provider = self.edit_prediction_provider()?;
7260 if !provider.is_enabled(buffer, buffer_position, cx) {
7261 return Some(false);
7262 }
7263 let buffer = buffer.read(cx);
7264 let Some(file) = buffer.file() else {
7265 return Some(true);
7266 };
7267 let settings = all_language_settings(Some(file), cx);
7268 Some(settings.edit_predictions_enabled_for_file(file, cx))
7269 })
7270 .unwrap_or(false)
7271 }
7272
7273 fn cycle_edit_prediction(
7274 &mut self,
7275 direction: Direction,
7276 window: &mut Window,
7277 cx: &mut Context<Self>,
7278 ) -> Option<()> {
7279 let provider = self.edit_prediction_provider()?;
7280 let cursor = self.selections.newest_anchor().head();
7281 let (buffer, cursor_buffer_position) =
7282 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7283 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7284 return None;
7285 }
7286
7287 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7288 self.update_visible_edit_prediction(window, cx);
7289
7290 Some(())
7291 }
7292
7293 pub fn show_edit_prediction(
7294 &mut self,
7295 _: &ShowEditPrediction,
7296 window: &mut Window,
7297 cx: &mut Context<Self>,
7298 ) {
7299 if !self.has_active_edit_prediction() {
7300 self.refresh_edit_prediction(false, true, window, cx);
7301 return;
7302 }
7303
7304 self.update_visible_edit_prediction(window, cx);
7305 }
7306
7307 pub fn display_cursor_names(
7308 &mut self,
7309 _: &DisplayCursorNames,
7310 window: &mut Window,
7311 cx: &mut Context<Self>,
7312 ) {
7313 self.show_cursor_names(window, cx);
7314 }
7315
7316 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7317 self.show_cursor_names = true;
7318 cx.notify();
7319 cx.spawn_in(window, async move |this, cx| {
7320 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7321 this.update(cx, |this, cx| {
7322 this.show_cursor_names = false;
7323 cx.notify()
7324 })
7325 .ok()
7326 })
7327 .detach();
7328 }
7329
7330 pub fn next_edit_prediction(
7331 &mut self,
7332 _: &NextEditPrediction,
7333 window: &mut Window,
7334 cx: &mut Context<Self>,
7335 ) {
7336 if self.has_active_edit_prediction() {
7337 self.cycle_edit_prediction(Direction::Next, window, cx);
7338 } else {
7339 let is_copilot_disabled = self
7340 .refresh_edit_prediction(false, true, window, cx)
7341 .is_none();
7342 if is_copilot_disabled {
7343 cx.propagate();
7344 }
7345 }
7346 }
7347
7348 pub fn previous_edit_prediction(
7349 &mut self,
7350 _: &PreviousEditPrediction,
7351 window: &mut Window,
7352 cx: &mut Context<Self>,
7353 ) {
7354 if self.has_active_edit_prediction() {
7355 self.cycle_edit_prediction(Direction::Prev, window, cx);
7356 } else {
7357 let is_copilot_disabled = self
7358 .refresh_edit_prediction(false, true, window, cx)
7359 .is_none();
7360 if is_copilot_disabled {
7361 cx.propagate();
7362 }
7363 }
7364 }
7365
7366 pub fn accept_edit_prediction(
7367 &mut self,
7368 _: &AcceptEditPrediction,
7369 window: &mut Window,
7370 cx: &mut Context<Self>,
7371 ) {
7372 if self.show_edit_predictions_in_menu() {
7373 self.hide_context_menu(window, cx);
7374 }
7375
7376 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7377 return;
7378 };
7379
7380 match &active_edit_prediction.completion {
7381 EditPrediction::MoveWithin { target, .. } => {
7382 let target = *target;
7383
7384 if let Some(position_map) = &self.last_position_map {
7385 if position_map
7386 .visible_row_range
7387 .contains(&target.to_display_point(&position_map.snapshot).row())
7388 || !self.edit_prediction_requires_modifier()
7389 {
7390 self.unfold_ranges(&[target..target], true, false, cx);
7391 // Note that this is also done in vim's handler of the Tab action.
7392 self.change_selections(
7393 SelectionEffects::scroll(Autoscroll::newest()),
7394 window,
7395 cx,
7396 |selections| {
7397 selections.select_anchor_ranges([target..target]);
7398 },
7399 );
7400 self.clear_row_highlights::<EditPredictionPreview>();
7401
7402 self.edit_prediction_preview
7403 .set_previous_scroll_position(None);
7404 } else {
7405 self.edit_prediction_preview
7406 .set_previous_scroll_position(Some(
7407 position_map.snapshot.scroll_anchor,
7408 ));
7409
7410 self.highlight_rows::<EditPredictionPreview>(
7411 target..target,
7412 cx.theme().colors().editor_highlighted_line_background,
7413 RowHighlightOptions {
7414 autoscroll: true,
7415 ..Default::default()
7416 },
7417 cx,
7418 );
7419 self.request_autoscroll(Autoscroll::fit(), cx);
7420 }
7421 }
7422 }
7423 EditPrediction::MoveOutside { snapshot, target } => {
7424 if let Some(workspace) = self.workspace() {
7425 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7426 .detach_and_log_err(cx);
7427 }
7428 }
7429 EditPrediction::Edit { edits, .. } => {
7430 self.report_edit_prediction_event(
7431 active_edit_prediction.completion_id.clone(),
7432 true,
7433 cx,
7434 );
7435
7436 if let Some(provider) = self.edit_prediction_provider() {
7437 provider.accept(cx);
7438 }
7439
7440 // Store the transaction ID and selections before applying the edit
7441 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7442
7443 let snapshot = self.buffer.read(cx).snapshot(cx);
7444 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7445
7446 self.buffer.update(cx, |buffer, cx| {
7447 buffer.edit(edits.iter().cloned(), None, cx)
7448 });
7449
7450 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7451 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7452 });
7453
7454 let selections = self.selections.disjoint_anchors_arc();
7455 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7456 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7457 if has_new_transaction {
7458 self.selection_history
7459 .insert_transaction(transaction_id_now, selections);
7460 }
7461 }
7462
7463 self.update_visible_edit_prediction(window, cx);
7464 if self.active_edit_prediction.is_none() {
7465 self.refresh_edit_prediction(true, true, window, cx);
7466 }
7467
7468 cx.notify();
7469 }
7470 }
7471
7472 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7473 }
7474
7475 pub fn accept_partial_edit_prediction(
7476 &mut self,
7477 _: &AcceptPartialEditPrediction,
7478 window: &mut Window,
7479 cx: &mut Context<Self>,
7480 ) {
7481 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7482 return;
7483 };
7484 if self.selections.count() != 1 {
7485 return;
7486 }
7487
7488 match &active_edit_prediction.completion {
7489 EditPrediction::MoveWithin { target, .. } => {
7490 let target = *target;
7491 self.change_selections(
7492 SelectionEffects::scroll(Autoscroll::newest()),
7493 window,
7494 cx,
7495 |selections| {
7496 selections.select_anchor_ranges([target..target]);
7497 },
7498 );
7499 }
7500 EditPrediction::MoveOutside { snapshot, target } => {
7501 if let Some(workspace) = self.workspace() {
7502 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7503 .detach_and_log_err(cx);
7504 }
7505 }
7506 EditPrediction::Edit { edits, .. } => {
7507 self.report_edit_prediction_event(
7508 active_edit_prediction.completion_id.clone(),
7509 true,
7510 cx,
7511 );
7512
7513 // Find an insertion that starts at the cursor position.
7514 let snapshot = self.buffer.read(cx).snapshot(cx);
7515 let cursor_offset = self
7516 .selections
7517 .newest::<usize>(&self.display_snapshot(cx))
7518 .head();
7519 let insertion = edits.iter().find_map(|(range, text)| {
7520 let range = range.to_offset(&snapshot);
7521 if range.is_empty() && range.start == cursor_offset {
7522 Some(text)
7523 } else {
7524 None
7525 }
7526 });
7527
7528 if let Some(text) = insertion {
7529 let mut partial_completion = text
7530 .chars()
7531 .by_ref()
7532 .take_while(|c| c.is_alphabetic())
7533 .collect::<String>();
7534 if partial_completion.is_empty() {
7535 partial_completion = text
7536 .chars()
7537 .by_ref()
7538 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7539 .collect::<String>();
7540 }
7541
7542 cx.emit(EditorEvent::InputHandled {
7543 utf16_range_to_replace: None,
7544 text: partial_completion.clone().into(),
7545 });
7546
7547 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7548
7549 self.refresh_edit_prediction(true, true, window, cx);
7550 cx.notify();
7551 } else {
7552 self.accept_edit_prediction(&Default::default(), window, cx);
7553 }
7554 }
7555 }
7556 }
7557
7558 fn discard_edit_prediction(
7559 &mut self,
7560 should_report_edit_prediction_event: bool,
7561 cx: &mut Context<Self>,
7562 ) -> bool {
7563 if should_report_edit_prediction_event {
7564 let completion_id = self
7565 .active_edit_prediction
7566 .as_ref()
7567 .and_then(|active_completion| active_completion.completion_id.clone());
7568
7569 self.report_edit_prediction_event(completion_id, false, cx);
7570 }
7571
7572 if let Some(provider) = self.edit_prediction_provider() {
7573 provider.discard(cx);
7574 }
7575
7576 self.take_active_edit_prediction(cx)
7577 }
7578
7579 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7580 let Some(provider) = self.edit_prediction_provider() else {
7581 return;
7582 };
7583
7584 let Some((_, buffer, _)) = self
7585 .buffer
7586 .read(cx)
7587 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7588 else {
7589 return;
7590 };
7591
7592 let extension = buffer
7593 .read(cx)
7594 .file()
7595 .and_then(|file| Some(file.path().extension()?.to_string()));
7596
7597 let event_type = match accepted {
7598 true => "Edit Prediction Accepted",
7599 false => "Edit Prediction Discarded",
7600 };
7601 telemetry::event!(
7602 event_type,
7603 provider = provider.name(),
7604 prediction_id = id,
7605 suggestion_accepted = accepted,
7606 file_extension = extension,
7607 );
7608 }
7609
7610 fn open_editor_at_anchor(
7611 snapshot: &language::BufferSnapshot,
7612 target: language::Anchor,
7613 workspace: &Entity<Workspace>,
7614 window: &mut Window,
7615 cx: &mut App,
7616 ) -> Task<Result<()>> {
7617 workspace.update(cx, |workspace, cx| {
7618 let path = snapshot.file().map(|file| file.full_path(cx));
7619 let Some(path) =
7620 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7621 else {
7622 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7623 };
7624 let target = text::ToPoint::to_point(&target, snapshot);
7625 let item = workspace.open_path(path, None, true, window, cx);
7626 window.spawn(cx, async move |cx| {
7627 let Some(editor) = item.await?.downcast::<Editor>() else {
7628 return Ok(());
7629 };
7630 editor
7631 .update_in(cx, |editor, window, cx| {
7632 editor.go_to_singleton_buffer_point(target, window, cx);
7633 })
7634 .ok();
7635 anyhow::Ok(())
7636 })
7637 })
7638 }
7639
7640 pub fn has_active_edit_prediction(&self) -> bool {
7641 self.active_edit_prediction.is_some()
7642 }
7643
7644 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7645 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7646 return false;
7647 };
7648
7649 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7650 self.clear_highlights::<EditPredictionHighlight>(cx);
7651 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7652 true
7653 }
7654
7655 /// Returns true when we're displaying the edit prediction popover below the cursor
7656 /// like we are not previewing and the LSP autocomplete menu is visible
7657 /// or we are in `when_holding_modifier` mode.
7658 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7659 if self.edit_prediction_preview_is_active()
7660 || !self.show_edit_predictions_in_menu()
7661 || !self.edit_predictions_enabled()
7662 {
7663 return false;
7664 }
7665
7666 if self.has_visible_completions_menu() {
7667 return true;
7668 }
7669
7670 has_completion && self.edit_prediction_requires_modifier()
7671 }
7672
7673 fn handle_modifiers_changed(
7674 &mut self,
7675 modifiers: Modifiers,
7676 position_map: &PositionMap,
7677 window: &mut Window,
7678 cx: &mut Context<Self>,
7679 ) {
7680 // Ensure that the edit prediction preview is updated, even when not
7681 // enabled, if there's an active edit prediction preview.
7682 if self.show_edit_predictions_in_menu()
7683 || matches!(
7684 self.edit_prediction_preview,
7685 EditPredictionPreview::Active { .. }
7686 )
7687 {
7688 self.update_edit_prediction_preview(&modifiers, window, cx);
7689 }
7690
7691 self.update_selection_mode(&modifiers, position_map, window, cx);
7692
7693 let mouse_position = window.mouse_position();
7694 if !position_map.text_hitbox.is_hovered(window) {
7695 return;
7696 }
7697
7698 self.update_hovered_link(
7699 position_map.point_for_position(mouse_position),
7700 &position_map.snapshot,
7701 modifiers,
7702 window,
7703 cx,
7704 )
7705 }
7706
7707 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7708 match EditorSettings::get_global(cx).multi_cursor_modifier {
7709 MultiCursorModifier::Alt => modifiers.secondary(),
7710 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7711 }
7712 }
7713
7714 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7715 match EditorSettings::get_global(cx).multi_cursor_modifier {
7716 MultiCursorModifier::Alt => modifiers.alt,
7717 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7718 }
7719 }
7720
7721 fn columnar_selection_mode(
7722 modifiers: &Modifiers,
7723 cx: &mut Context<Self>,
7724 ) -> Option<ColumnarMode> {
7725 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7726 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7727 Some(ColumnarMode::FromMouse)
7728 } else if Self::is_alt_pressed(modifiers, cx) {
7729 Some(ColumnarMode::FromSelection)
7730 } else {
7731 None
7732 }
7733 } else {
7734 None
7735 }
7736 }
7737
7738 fn update_selection_mode(
7739 &mut self,
7740 modifiers: &Modifiers,
7741 position_map: &PositionMap,
7742 window: &mut Window,
7743 cx: &mut Context<Self>,
7744 ) {
7745 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7746 return;
7747 };
7748 if self.selections.pending_anchor().is_none() {
7749 return;
7750 }
7751
7752 let mouse_position = window.mouse_position();
7753 let point_for_position = position_map.point_for_position(mouse_position);
7754 let position = point_for_position.previous_valid;
7755
7756 self.select(
7757 SelectPhase::BeginColumnar {
7758 position,
7759 reset: false,
7760 mode,
7761 goal_column: point_for_position.exact_unclipped.column(),
7762 },
7763 window,
7764 cx,
7765 );
7766 }
7767
7768 fn update_edit_prediction_preview(
7769 &mut self,
7770 modifiers: &Modifiers,
7771 window: &mut Window,
7772 cx: &mut Context<Self>,
7773 ) {
7774 let mut modifiers_held = false;
7775 if let Some(accept_keystroke) = self
7776 .accept_edit_prediction_keybind(false, window, cx)
7777 .keystroke()
7778 {
7779 modifiers_held = modifiers_held
7780 || (accept_keystroke.modifiers() == modifiers
7781 && accept_keystroke.modifiers().modified());
7782 };
7783 if let Some(accept_partial_keystroke) = self
7784 .accept_edit_prediction_keybind(true, window, cx)
7785 .keystroke()
7786 {
7787 modifiers_held = modifiers_held
7788 || (accept_partial_keystroke.modifiers() == modifiers
7789 && accept_partial_keystroke.modifiers().modified());
7790 }
7791
7792 if modifiers_held {
7793 if matches!(
7794 self.edit_prediction_preview,
7795 EditPredictionPreview::Inactive { .. }
7796 ) {
7797 self.edit_prediction_preview = EditPredictionPreview::Active {
7798 previous_scroll_position: None,
7799 since: Instant::now(),
7800 };
7801
7802 self.update_visible_edit_prediction(window, cx);
7803 cx.notify();
7804 }
7805 } else if let EditPredictionPreview::Active {
7806 previous_scroll_position,
7807 since,
7808 } = self.edit_prediction_preview
7809 {
7810 if let (Some(previous_scroll_position), Some(position_map)) =
7811 (previous_scroll_position, self.last_position_map.as_ref())
7812 {
7813 self.set_scroll_position(
7814 previous_scroll_position
7815 .scroll_position(&position_map.snapshot.display_snapshot),
7816 window,
7817 cx,
7818 );
7819 }
7820
7821 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7822 released_too_fast: since.elapsed() < Duration::from_millis(200),
7823 };
7824 self.clear_row_highlights::<EditPredictionPreview>();
7825 self.update_visible_edit_prediction(window, cx);
7826 cx.notify();
7827 }
7828 }
7829
7830 fn update_visible_edit_prediction(
7831 &mut self,
7832 _window: &mut Window,
7833 cx: &mut Context<Self>,
7834 ) -> Option<()> {
7835 if DisableAiSettings::get_global(cx).disable_ai {
7836 return None;
7837 }
7838
7839 if self.ime_transaction.is_some() {
7840 self.discard_edit_prediction(false, cx);
7841 return None;
7842 }
7843
7844 let selection = self.selections.newest_anchor();
7845 let cursor = selection.head();
7846 let multibuffer = self.buffer.read(cx).snapshot(cx);
7847 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7848 let excerpt_id = cursor.excerpt_id;
7849
7850 let show_in_menu = self.show_edit_predictions_in_menu();
7851 let completions_menu_has_precedence = !show_in_menu
7852 && (self.context_menu.borrow().is_some()
7853 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7854
7855 if completions_menu_has_precedence
7856 || !offset_selection.is_empty()
7857 || self
7858 .active_edit_prediction
7859 .as_ref()
7860 .is_some_and(|completion| {
7861 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7862 return false;
7863 };
7864 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7865 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7866 !invalidation_range.contains(&offset_selection.head())
7867 })
7868 {
7869 self.discard_edit_prediction(false, cx);
7870 return None;
7871 }
7872
7873 self.take_active_edit_prediction(cx);
7874 let Some(provider) = self.edit_prediction_provider() else {
7875 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7876 return None;
7877 };
7878
7879 let (buffer, cursor_buffer_position) =
7880 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7881
7882 self.edit_prediction_settings =
7883 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7884
7885 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7886
7887 if self.edit_prediction_indent_conflict {
7888 let cursor_point = cursor.to_point(&multibuffer);
7889
7890 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7891
7892 if let Some((_, indent)) = indents.iter().next()
7893 && indent.len == cursor_point.column
7894 {
7895 self.edit_prediction_indent_conflict = false;
7896 }
7897 }
7898
7899 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7900
7901 let (completion_id, edits, edit_preview) = match edit_prediction {
7902 edit_prediction::EditPrediction::Local {
7903 id,
7904 edits,
7905 edit_preview,
7906 } => (id, edits, edit_preview),
7907 edit_prediction::EditPrediction::Jump {
7908 id,
7909 snapshot,
7910 target,
7911 } => {
7912 self.stale_edit_prediction_in_menu = None;
7913 self.active_edit_prediction = Some(EditPredictionState {
7914 inlay_ids: vec![],
7915 completion: EditPrediction::MoveOutside { snapshot, target },
7916 completion_id: id,
7917 invalidation_range: None,
7918 });
7919 cx.notify();
7920 return Some(());
7921 }
7922 };
7923
7924 let edits = edits
7925 .into_iter()
7926 .flat_map(|(range, new_text)| {
7927 Some((
7928 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7929 new_text,
7930 ))
7931 })
7932 .collect::<Vec<_>>();
7933 if edits.is_empty() {
7934 return None;
7935 }
7936
7937 let first_edit_start = edits.first().unwrap().0.start;
7938 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7939 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7940
7941 let last_edit_end = edits.last().unwrap().0.end;
7942 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7943 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7944
7945 let cursor_row = cursor.to_point(&multibuffer).row;
7946
7947 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7948
7949 let mut inlay_ids = Vec::new();
7950 let invalidation_row_range;
7951 let move_invalidation_row_range = if cursor_row < edit_start_row {
7952 Some(cursor_row..edit_end_row)
7953 } else if cursor_row > edit_end_row {
7954 Some(edit_start_row..cursor_row)
7955 } else {
7956 None
7957 };
7958 let supports_jump = self
7959 .edit_prediction_provider
7960 .as_ref()
7961 .map(|provider| provider.provider.supports_jump_to_edit())
7962 .unwrap_or(true);
7963
7964 let is_move = supports_jump
7965 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7966 let completion = if is_move {
7967 invalidation_row_range =
7968 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
7969 let target = first_edit_start;
7970 EditPrediction::MoveWithin { target, snapshot }
7971 } else {
7972 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
7973 && !self.edit_predictions_hidden_for_vim_mode;
7974
7975 if show_completions_in_buffer {
7976 if edits
7977 .iter()
7978 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
7979 {
7980 let mut inlays = Vec::new();
7981 for (range, new_text) in &edits {
7982 let inlay = Inlay::edit_prediction(
7983 post_inc(&mut self.next_inlay_id),
7984 range.start,
7985 new_text.as_ref(),
7986 );
7987 inlay_ids.push(inlay.id);
7988 inlays.push(inlay);
7989 }
7990
7991 self.splice_inlays(&[], inlays, cx);
7992 } else {
7993 let background_color = cx.theme().status().deleted_background;
7994 self.highlight_text::<EditPredictionHighlight>(
7995 edits.iter().map(|(range, _)| range.clone()).collect(),
7996 HighlightStyle {
7997 background_color: Some(background_color),
7998 ..Default::default()
7999 },
8000 cx,
8001 );
8002 }
8003 }
8004
8005 invalidation_row_range = edit_start_row..edit_end_row;
8006
8007 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8008 if provider.show_tab_accept_marker() {
8009 EditDisplayMode::TabAccept
8010 } else {
8011 EditDisplayMode::Inline
8012 }
8013 } else {
8014 EditDisplayMode::DiffPopover
8015 };
8016
8017 EditPrediction::Edit {
8018 edits,
8019 edit_preview,
8020 display_mode,
8021 snapshot,
8022 }
8023 };
8024
8025 let invalidation_range = multibuffer
8026 .anchor_before(Point::new(invalidation_row_range.start, 0))
8027 ..multibuffer.anchor_after(Point::new(
8028 invalidation_row_range.end,
8029 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8030 ));
8031
8032 self.stale_edit_prediction_in_menu = None;
8033 self.active_edit_prediction = Some(EditPredictionState {
8034 inlay_ids,
8035 completion,
8036 completion_id,
8037 invalidation_range: Some(invalidation_range),
8038 });
8039
8040 cx.notify();
8041
8042 Some(())
8043 }
8044
8045 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8046 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8047 }
8048
8049 fn clear_tasks(&mut self) {
8050 self.tasks.clear()
8051 }
8052
8053 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8054 if self.tasks.insert(key, value).is_some() {
8055 // This case should hopefully be rare, but just in case...
8056 log::error!(
8057 "multiple different run targets found on a single line, only the last target will be rendered"
8058 )
8059 }
8060 }
8061
8062 /// Get all display points of breakpoints that will be rendered within editor
8063 ///
8064 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8065 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8066 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8067 fn active_breakpoints(
8068 &self,
8069 range: Range<DisplayRow>,
8070 window: &mut Window,
8071 cx: &mut Context<Self>,
8072 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8073 let mut breakpoint_display_points = HashMap::default();
8074
8075 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8076 return breakpoint_display_points;
8077 };
8078
8079 let snapshot = self.snapshot(window, cx);
8080
8081 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8082 let Some(project) = self.project() else {
8083 return breakpoint_display_points;
8084 };
8085
8086 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8087 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8088
8089 for (buffer_snapshot, range, excerpt_id) in
8090 multi_buffer_snapshot.range_to_buffer_ranges(range)
8091 {
8092 let Some(buffer) = project
8093 .read(cx)
8094 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8095 else {
8096 continue;
8097 };
8098 let breakpoints = breakpoint_store.read(cx).breakpoints(
8099 &buffer,
8100 Some(
8101 buffer_snapshot.anchor_before(range.start)
8102 ..buffer_snapshot.anchor_after(range.end),
8103 ),
8104 buffer_snapshot,
8105 cx,
8106 );
8107 for (breakpoint, state) in breakpoints {
8108 let multi_buffer_anchor =
8109 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8110 let position = multi_buffer_anchor
8111 .to_point(&multi_buffer_snapshot)
8112 .to_display_point(&snapshot);
8113
8114 breakpoint_display_points.insert(
8115 position.row(),
8116 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8117 );
8118 }
8119 }
8120
8121 breakpoint_display_points
8122 }
8123
8124 fn breakpoint_context_menu(
8125 &self,
8126 anchor: Anchor,
8127 window: &mut Window,
8128 cx: &mut Context<Self>,
8129 ) -> Entity<ui::ContextMenu> {
8130 let weak_editor = cx.weak_entity();
8131 let focus_handle = self.focus_handle(cx);
8132
8133 let row = self
8134 .buffer
8135 .read(cx)
8136 .snapshot(cx)
8137 .summary_for_anchor::<Point>(&anchor)
8138 .row;
8139
8140 let breakpoint = self
8141 .breakpoint_at_row(row, window, cx)
8142 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8143
8144 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8145 "Edit Log Breakpoint"
8146 } else {
8147 "Set Log Breakpoint"
8148 };
8149
8150 let condition_breakpoint_msg = if breakpoint
8151 .as_ref()
8152 .is_some_and(|bp| bp.1.condition.is_some())
8153 {
8154 "Edit Condition Breakpoint"
8155 } else {
8156 "Set Condition Breakpoint"
8157 };
8158
8159 let hit_condition_breakpoint_msg = if breakpoint
8160 .as_ref()
8161 .is_some_and(|bp| bp.1.hit_condition.is_some())
8162 {
8163 "Edit Hit Condition Breakpoint"
8164 } else {
8165 "Set Hit Condition Breakpoint"
8166 };
8167
8168 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8169 "Unset Breakpoint"
8170 } else {
8171 "Set Breakpoint"
8172 };
8173
8174 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8175
8176 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8177 BreakpointState::Enabled => Some("Disable"),
8178 BreakpointState::Disabled => Some("Enable"),
8179 });
8180
8181 let (anchor, breakpoint) =
8182 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8183
8184 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8185 menu.on_blur_subscription(Subscription::new(|| {}))
8186 .context(focus_handle)
8187 .when(run_to_cursor, |this| {
8188 let weak_editor = weak_editor.clone();
8189 this.entry("Run to cursor", None, move |window, cx| {
8190 weak_editor
8191 .update(cx, |editor, cx| {
8192 editor.change_selections(
8193 SelectionEffects::no_scroll(),
8194 window,
8195 cx,
8196 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8197 );
8198 })
8199 .ok();
8200
8201 window.dispatch_action(Box::new(RunToCursor), cx);
8202 })
8203 .separator()
8204 })
8205 .when_some(toggle_state_msg, |this, msg| {
8206 this.entry(msg, None, {
8207 let weak_editor = weak_editor.clone();
8208 let breakpoint = breakpoint.clone();
8209 move |_window, cx| {
8210 weak_editor
8211 .update(cx, |this, cx| {
8212 this.edit_breakpoint_at_anchor(
8213 anchor,
8214 breakpoint.as_ref().clone(),
8215 BreakpointEditAction::InvertState,
8216 cx,
8217 );
8218 })
8219 .log_err();
8220 }
8221 })
8222 })
8223 .entry(set_breakpoint_msg, None, {
8224 let weak_editor = weak_editor.clone();
8225 let breakpoint = breakpoint.clone();
8226 move |_window, cx| {
8227 weak_editor
8228 .update(cx, |this, cx| {
8229 this.edit_breakpoint_at_anchor(
8230 anchor,
8231 breakpoint.as_ref().clone(),
8232 BreakpointEditAction::Toggle,
8233 cx,
8234 );
8235 })
8236 .log_err();
8237 }
8238 })
8239 .entry(log_breakpoint_msg, None, {
8240 let breakpoint = breakpoint.clone();
8241 let weak_editor = weak_editor.clone();
8242 move |window, cx| {
8243 weak_editor
8244 .update(cx, |this, cx| {
8245 this.add_edit_breakpoint_block(
8246 anchor,
8247 breakpoint.as_ref(),
8248 BreakpointPromptEditAction::Log,
8249 window,
8250 cx,
8251 );
8252 })
8253 .log_err();
8254 }
8255 })
8256 .entry(condition_breakpoint_msg, None, {
8257 let breakpoint = breakpoint.clone();
8258 let weak_editor = weak_editor.clone();
8259 move |window, cx| {
8260 weak_editor
8261 .update(cx, |this, cx| {
8262 this.add_edit_breakpoint_block(
8263 anchor,
8264 breakpoint.as_ref(),
8265 BreakpointPromptEditAction::Condition,
8266 window,
8267 cx,
8268 );
8269 })
8270 .log_err();
8271 }
8272 })
8273 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8274 weak_editor
8275 .update(cx, |this, cx| {
8276 this.add_edit_breakpoint_block(
8277 anchor,
8278 breakpoint.as_ref(),
8279 BreakpointPromptEditAction::HitCondition,
8280 window,
8281 cx,
8282 );
8283 })
8284 .log_err();
8285 })
8286 })
8287 }
8288
8289 fn render_breakpoint(
8290 &self,
8291 position: Anchor,
8292 row: DisplayRow,
8293 breakpoint: &Breakpoint,
8294 state: Option<BreakpointSessionState>,
8295 cx: &mut Context<Self>,
8296 ) -> IconButton {
8297 let is_rejected = state.is_some_and(|s| !s.verified);
8298 // Is it a breakpoint that shows up when hovering over gutter?
8299 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8300 (false, false),
8301 |PhantomBreakpointIndicator {
8302 is_active,
8303 display_row,
8304 collides_with_existing_breakpoint,
8305 }| {
8306 (
8307 is_active && display_row == row,
8308 collides_with_existing_breakpoint,
8309 )
8310 },
8311 );
8312
8313 let (color, icon) = {
8314 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8315 (false, false) => ui::IconName::DebugBreakpoint,
8316 (true, false) => ui::IconName::DebugLogBreakpoint,
8317 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8318 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8319 };
8320
8321 let color = if is_phantom {
8322 Color::Hint
8323 } else if is_rejected {
8324 Color::Disabled
8325 } else {
8326 Color::Debugger
8327 };
8328
8329 (color, icon)
8330 };
8331
8332 let breakpoint = Arc::from(breakpoint.clone());
8333
8334 let alt_as_text = gpui::Keystroke {
8335 modifiers: Modifiers::secondary_key(),
8336 ..Default::default()
8337 };
8338 let primary_action_text = if breakpoint.is_disabled() {
8339 "Enable breakpoint"
8340 } else if is_phantom && !collides_with_existing {
8341 "Set breakpoint"
8342 } else {
8343 "Unset breakpoint"
8344 };
8345 let focus_handle = self.focus_handle.clone();
8346
8347 let meta = if is_rejected {
8348 SharedString::from("No executable code is associated with this line.")
8349 } else if collides_with_existing && !breakpoint.is_disabled() {
8350 SharedString::from(format!(
8351 "{alt_as_text}-click to disable,\nright-click for more options."
8352 ))
8353 } else {
8354 SharedString::from("Right-click for more options.")
8355 };
8356 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8357 .icon_size(IconSize::XSmall)
8358 .size(ui::ButtonSize::None)
8359 .when(is_rejected, |this| {
8360 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8361 })
8362 .icon_color(color)
8363 .style(ButtonStyle::Transparent)
8364 .on_click(cx.listener({
8365 move |editor, event: &ClickEvent, window, cx| {
8366 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8367 BreakpointEditAction::InvertState
8368 } else {
8369 BreakpointEditAction::Toggle
8370 };
8371
8372 window.focus(&editor.focus_handle(cx));
8373 editor.edit_breakpoint_at_anchor(
8374 position,
8375 breakpoint.as_ref().clone(),
8376 edit_action,
8377 cx,
8378 );
8379 }
8380 }))
8381 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8382 editor.set_breakpoint_context_menu(
8383 row,
8384 Some(position),
8385 event.position(),
8386 window,
8387 cx,
8388 );
8389 }))
8390 .tooltip(move |_window, cx| {
8391 Tooltip::with_meta_in(
8392 primary_action_text,
8393 Some(&ToggleBreakpoint),
8394 meta.clone(),
8395 &focus_handle,
8396 cx,
8397 )
8398 })
8399 }
8400
8401 fn build_tasks_context(
8402 project: &Entity<Project>,
8403 buffer: &Entity<Buffer>,
8404 buffer_row: u32,
8405 tasks: &Arc<RunnableTasks>,
8406 cx: &mut Context<Self>,
8407 ) -> Task<Option<task::TaskContext>> {
8408 let position = Point::new(buffer_row, tasks.column);
8409 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8410 let location = Location {
8411 buffer: buffer.clone(),
8412 range: range_start..range_start,
8413 };
8414 // Fill in the environmental variables from the tree-sitter captures
8415 let mut captured_task_variables = TaskVariables::default();
8416 for (capture_name, value) in tasks.extra_variables.clone() {
8417 captured_task_variables.insert(
8418 task::VariableName::Custom(capture_name.into()),
8419 value.clone(),
8420 );
8421 }
8422 project.update(cx, |project, cx| {
8423 project.task_store().update(cx, |task_store, cx| {
8424 task_store.task_context_for_location(captured_task_variables, location, cx)
8425 })
8426 })
8427 }
8428
8429 pub fn spawn_nearest_task(
8430 &mut self,
8431 action: &SpawnNearestTask,
8432 window: &mut Window,
8433 cx: &mut Context<Self>,
8434 ) {
8435 let Some((workspace, _)) = self.workspace.clone() else {
8436 return;
8437 };
8438 let Some(project) = self.project.clone() else {
8439 return;
8440 };
8441
8442 // Try to find a closest, enclosing node using tree-sitter that has a task
8443 let Some((buffer, buffer_row, tasks)) = self
8444 .find_enclosing_node_task(cx)
8445 // Or find the task that's closest in row-distance.
8446 .or_else(|| self.find_closest_task(cx))
8447 else {
8448 return;
8449 };
8450
8451 let reveal_strategy = action.reveal;
8452 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8453 cx.spawn_in(window, async move |_, cx| {
8454 let context = task_context.await?;
8455 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8456
8457 let resolved = &mut resolved_task.resolved;
8458 resolved.reveal = reveal_strategy;
8459
8460 workspace
8461 .update_in(cx, |workspace, window, cx| {
8462 workspace.schedule_resolved_task(
8463 task_source_kind,
8464 resolved_task,
8465 false,
8466 window,
8467 cx,
8468 );
8469 })
8470 .ok()
8471 })
8472 .detach();
8473 }
8474
8475 fn find_closest_task(
8476 &mut self,
8477 cx: &mut Context<Self>,
8478 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8479 let cursor_row = self
8480 .selections
8481 .newest_adjusted(&self.display_snapshot(cx))
8482 .head()
8483 .row;
8484
8485 let ((buffer_id, row), tasks) = self
8486 .tasks
8487 .iter()
8488 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8489
8490 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8491 let tasks = Arc::new(tasks.to_owned());
8492 Some((buffer, *row, tasks))
8493 }
8494
8495 fn find_enclosing_node_task(
8496 &mut self,
8497 cx: &mut Context<Self>,
8498 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8499 let snapshot = self.buffer.read(cx).snapshot(cx);
8500 let offset = self
8501 .selections
8502 .newest::<usize>(&self.display_snapshot(cx))
8503 .head();
8504 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8505 let buffer_id = excerpt.buffer().remote_id();
8506
8507 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8508 let mut cursor = layer.node().walk();
8509
8510 while cursor.goto_first_child_for_byte(offset).is_some() {
8511 if cursor.node().end_byte() == offset {
8512 cursor.goto_next_sibling();
8513 }
8514 }
8515
8516 // Ascend to the smallest ancestor that contains the range and has a task.
8517 loop {
8518 let node = cursor.node();
8519 let node_range = node.byte_range();
8520 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8521
8522 // Check if this node contains our offset
8523 if node_range.start <= offset && node_range.end >= offset {
8524 // If it contains offset, check for task
8525 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8526 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8527 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8528 }
8529 }
8530
8531 if !cursor.goto_parent() {
8532 break;
8533 }
8534 }
8535 None
8536 }
8537
8538 fn render_run_indicator(
8539 &self,
8540 _style: &EditorStyle,
8541 is_active: bool,
8542 row: DisplayRow,
8543 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8544 cx: &mut Context<Self>,
8545 ) -> IconButton {
8546 let color = Color::Muted;
8547 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8548
8549 IconButton::new(
8550 ("run_indicator", row.0 as usize),
8551 ui::IconName::PlayOutlined,
8552 )
8553 .shape(ui::IconButtonShape::Square)
8554 .icon_size(IconSize::XSmall)
8555 .icon_color(color)
8556 .toggle_state(is_active)
8557 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8558 let quick_launch = match e {
8559 ClickEvent::Keyboard(_) => true,
8560 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8561 };
8562
8563 window.focus(&editor.focus_handle(cx));
8564 editor.toggle_code_actions(
8565 &ToggleCodeActions {
8566 deployed_from: Some(CodeActionSource::RunMenu(row)),
8567 quick_launch,
8568 },
8569 window,
8570 cx,
8571 );
8572 }))
8573 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8574 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8575 }))
8576 }
8577
8578 pub fn context_menu_visible(&self) -> bool {
8579 !self.edit_prediction_preview_is_active()
8580 && self
8581 .context_menu
8582 .borrow()
8583 .as_ref()
8584 .is_some_and(|menu| menu.visible())
8585 }
8586
8587 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8588 self.context_menu
8589 .borrow()
8590 .as_ref()
8591 .map(|menu| menu.origin())
8592 }
8593
8594 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8595 self.context_menu_options = Some(options);
8596 }
8597
8598 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8599 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8600
8601 fn render_edit_prediction_popover(
8602 &mut self,
8603 text_bounds: &Bounds<Pixels>,
8604 content_origin: gpui::Point<Pixels>,
8605 right_margin: Pixels,
8606 editor_snapshot: &EditorSnapshot,
8607 visible_row_range: Range<DisplayRow>,
8608 scroll_top: ScrollOffset,
8609 scroll_bottom: ScrollOffset,
8610 line_layouts: &[LineWithInvisibles],
8611 line_height: Pixels,
8612 scroll_position: gpui::Point<ScrollOffset>,
8613 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8614 newest_selection_head: Option<DisplayPoint>,
8615 editor_width: Pixels,
8616 style: &EditorStyle,
8617 window: &mut Window,
8618 cx: &mut App,
8619 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8620 if self.mode().is_minimap() {
8621 return None;
8622 }
8623 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8624
8625 if self.edit_prediction_visible_in_cursor_popover(true) {
8626 return None;
8627 }
8628
8629 match &active_edit_prediction.completion {
8630 EditPrediction::MoveWithin { target, .. } => {
8631 let target_display_point = target.to_display_point(editor_snapshot);
8632
8633 if self.edit_prediction_requires_modifier() {
8634 if !self.edit_prediction_preview_is_active() {
8635 return None;
8636 }
8637
8638 self.render_edit_prediction_modifier_jump_popover(
8639 text_bounds,
8640 content_origin,
8641 visible_row_range,
8642 line_layouts,
8643 line_height,
8644 scroll_pixel_position,
8645 newest_selection_head,
8646 target_display_point,
8647 window,
8648 cx,
8649 )
8650 } else {
8651 self.render_edit_prediction_eager_jump_popover(
8652 text_bounds,
8653 content_origin,
8654 editor_snapshot,
8655 visible_row_range,
8656 scroll_top,
8657 scroll_bottom,
8658 line_height,
8659 scroll_pixel_position,
8660 target_display_point,
8661 editor_width,
8662 window,
8663 cx,
8664 )
8665 }
8666 }
8667 EditPrediction::Edit {
8668 display_mode: EditDisplayMode::Inline,
8669 ..
8670 } => None,
8671 EditPrediction::Edit {
8672 display_mode: EditDisplayMode::TabAccept,
8673 edits,
8674 ..
8675 } => {
8676 let range = &edits.first()?.0;
8677 let target_display_point = range.end.to_display_point(editor_snapshot);
8678
8679 self.render_edit_prediction_end_of_line_popover(
8680 "Accept",
8681 editor_snapshot,
8682 visible_row_range,
8683 target_display_point,
8684 line_height,
8685 scroll_pixel_position,
8686 content_origin,
8687 editor_width,
8688 window,
8689 cx,
8690 )
8691 }
8692 EditPrediction::Edit {
8693 edits,
8694 edit_preview,
8695 display_mode: EditDisplayMode::DiffPopover,
8696 snapshot,
8697 } => self.render_edit_prediction_diff_popover(
8698 text_bounds,
8699 content_origin,
8700 right_margin,
8701 editor_snapshot,
8702 visible_row_range,
8703 line_layouts,
8704 line_height,
8705 scroll_position,
8706 scroll_pixel_position,
8707 newest_selection_head,
8708 editor_width,
8709 style,
8710 edits,
8711 edit_preview,
8712 snapshot,
8713 window,
8714 cx,
8715 ),
8716 EditPrediction::MoveOutside { snapshot, .. } => {
8717 let file_name = snapshot
8718 .file()
8719 .map(|file| file.file_name(cx))
8720 .unwrap_or("untitled");
8721 let mut element = self
8722 .render_edit_prediction_line_popover(
8723 format!("Jump to {file_name}"),
8724 Some(IconName::ZedPredict),
8725 window,
8726 cx,
8727 )
8728 .into_any();
8729
8730 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8731 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8732 let origin_y = text_bounds.size.height - size.height - px(30.);
8733 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8734 element.prepaint_at(origin, window, cx);
8735
8736 Some((element, origin))
8737 }
8738 }
8739 }
8740
8741 fn render_edit_prediction_modifier_jump_popover(
8742 &mut self,
8743 text_bounds: &Bounds<Pixels>,
8744 content_origin: gpui::Point<Pixels>,
8745 visible_row_range: Range<DisplayRow>,
8746 line_layouts: &[LineWithInvisibles],
8747 line_height: Pixels,
8748 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8749 newest_selection_head: Option<DisplayPoint>,
8750 target_display_point: DisplayPoint,
8751 window: &mut Window,
8752 cx: &mut App,
8753 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8754 let scrolled_content_origin =
8755 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8756
8757 const SCROLL_PADDING_Y: Pixels = px(12.);
8758
8759 if target_display_point.row() < visible_row_range.start {
8760 return self.render_edit_prediction_scroll_popover(
8761 |_| SCROLL_PADDING_Y,
8762 IconName::ArrowUp,
8763 visible_row_range,
8764 line_layouts,
8765 newest_selection_head,
8766 scrolled_content_origin,
8767 window,
8768 cx,
8769 );
8770 } else if target_display_point.row() >= visible_row_range.end {
8771 return self.render_edit_prediction_scroll_popover(
8772 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8773 IconName::ArrowDown,
8774 visible_row_range,
8775 line_layouts,
8776 newest_selection_head,
8777 scrolled_content_origin,
8778 window,
8779 cx,
8780 );
8781 }
8782
8783 const POLE_WIDTH: Pixels = px(2.);
8784
8785 let line_layout =
8786 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8787 let target_column = target_display_point.column() as usize;
8788
8789 let target_x = line_layout.x_for_index(target_column);
8790 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8791 - scroll_pixel_position.y;
8792
8793 let flag_on_right = target_x < text_bounds.size.width / 2.;
8794
8795 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8796 border_color.l += 0.001;
8797
8798 let mut element = v_flex()
8799 .items_end()
8800 .when(flag_on_right, |el| el.items_start())
8801 .child(if flag_on_right {
8802 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8803 .rounded_bl(px(0.))
8804 .rounded_tl(px(0.))
8805 .border_l_2()
8806 .border_color(border_color)
8807 } else {
8808 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8809 .rounded_br(px(0.))
8810 .rounded_tr(px(0.))
8811 .border_r_2()
8812 .border_color(border_color)
8813 })
8814 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8815 .into_any();
8816
8817 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8818
8819 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8820 - point(
8821 if flag_on_right {
8822 POLE_WIDTH
8823 } else {
8824 size.width - POLE_WIDTH
8825 },
8826 size.height - line_height,
8827 );
8828
8829 origin.x = origin.x.max(content_origin.x);
8830
8831 element.prepaint_at(origin, window, cx);
8832
8833 Some((element, origin))
8834 }
8835
8836 fn render_edit_prediction_scroll_popover(
8837 &mut self,
8838 to_y: impl Fn(Size<Pixels>) -> Pixels,
8839 scroll_icon: IconName,
8840 visible_row_range: Range<DisplayRow>,
8841 line_layouts: &[LineWithInvisibles],
8842 newest_selection_head: Option<DisplayPoint>,
8843 scrolled_content_origin: gpui::Point<Pixels>,
8844 window: &mut Window,
8845 cx: &mut App,
8846 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8847 let mut element = self
8848 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8849 .into_any();
8850
8851 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8852
8853 let cursor = newest_selection_head?;
8854 let cursor_row_layout =
8855 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8856 let cursor_column = cursor.column() as usize;
8857
8858 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8859
8860 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8861
8862 element.prepaint_at(origin, window, cx);
8863 Some((element, origin))
8864 }
8865
8866 fn render_edit_prediction_eager_jump_popover(
8867 &mut self,
8868 text_bounds: &Bounds<Pixels>,
8869 content_origin: gpui::Point<Pixels>,
8870 editor_snapshot: &EditorSnapshot,
8871 visible_row_range: Range<DisplayRow>,
8872 scroll_top: ScrollOffset,
8873 scroll_bottom: ScrollOffset,
8874 line_height: Pixels,
8875 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8876 target_display_point: DisplayPoint,
8877 editor_width: Pixels,
8878 window: &mut Window,
8879 cx: &mut App,
8880 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8881 if target_display_point.row().as_f64() < scroll_top {
8882 let mut element = self
8883 .render_edit_prediction_line_popover(
8884 "Jump to Edit",
8885 Some(IconName::ArrowUp),
8886 window,
8887 cx,
8888 )
8889 .into_any();
8890
8891 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8892 let offset = point(
8893 (text_bounds.size.width - size.width) / 2.,
8894 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8895 );
8896
8897 let origin = text_bounds.origin + offset;
8898 element.prepaint_at(origin, window, cx);
8899 Some((element, origin))
8900 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8901 let mut element = self
8902 .render_edit_prediction_line_popover(
8903 "Jump to Edit",
8904 Some(IconName::ArrowDown),
8905 window,
8906 cx,
8907 )
8908 .into_any();
8909
8910 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8911 let offset = point(
8912 (text_bounds.size.width - size.width) / 2.,
8913 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8914 );
8915
8916 let origin = text_bounds.origin + offset;
8917 element.prepaint_at(origin, window, cx);
8918 Some((element, origin))
8919 } else {
8920 self.render_edit_prediction_end_of_line_popover(
8921 "Jump to Edit",
8922 editor_snapshot,
8923 visible_row_range,
8924 target_display_point,
8925 line_height,
8926 scroll_pixel_position,
8927 content_origin,
8928 editor_width,
8929 window,
8930 cx,
8931 )
8932 }
8933 }
8934
8935 fn render_edit_prediction_end_of_line_popover(
8936 self: &mut Editor,
8937 label: &'static str,
8938 editor_snapshot: &EditorSnapshot,
8939 visible_row_range: Range<DisplayRow>,
8940 target_display_point: DisplayPoint,
8941 line_height: Pixels,
8942 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8943 content_origin: gpui::Point<Pixels>,
8944 editor_width: Pixels,
8945 window: &mut Window,
8946 cx: &mut App,
8947 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8948 let target_line_end = DisplayPoint::new(
8949 target_display_point.row(),
8950 editor_snapshot.line_len(target_display_point.row()),
8951 );
8952
8953 let mut element = self
8954 .render_edit_prediction_line_popover(label, None, window, cx)
8955 .into_any();
8956
8957 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8958
8959 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8960
8961 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8962 let mut origin = start_point
8963 + line_origin
8964 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8965 origin.x = origin.x.max(content_origin.x);
8966
8967 let max_x = content_origin.x + editor_width - size.width;
8968
8969 if origin.x > max_x {
8970 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
8971
8972 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
8973 origin.y += offset;
8974 IconName::ArrowUp
8975 } else {
8976 origin.y -= offset;
8977 IconName::ArrowDown
8978 };
8979
8980 element = self
8981 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
8982 .into_any();
8983
8984 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8985
8986 origin.x = content_origin.x + editor_width - size.width - px(2.);
8987 }
8988
8989 element.prepaint_at(origin, window, cx);
8990 Some((element, origin))
8991 }
8992
8993 fn render_edit_prediction_diff_popover(
8994 self: &Editor,
8995 text_bounds: &Bounds<Pixels>,
8996 content_origin: gpui::Point<Pixels>,
8997 right_margin: Pixels,
8998 editor_snapshot: &EditorSnapshot,
8999 visible_row_range: Range<DisplayRow>,
9000 line_layouts: &[LineWithInvisibles],
9001 line_height: Pixels,
9002 scroll_position: gpui::Point<ScrollOffset>,
9003 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9004 newest_selection_head: Option<DisplayPoint>,
9005 editor_width: Pixels,
9006 style: &EditorStyle,
9007 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9008 edit_preview: &Option<language::EditPreview>,
9009 snapshot: &language::BufferSnapshot,
9010 window: &mut Window,
9011 cx: &mut App,
9012 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9013 let edit_start = edits
9014 .first()
9015 .unwrap()
9016 .0
9017 .start
9018 .to_display_point(editor_snapshot);
9019 let edit_end = edits
9020 .last()
9021 .unwrap()
9022 .0
9023 .end
9024 .to_display_point(editor_snapshot);
9025
9026 let is_visible = visible_row_range.contains(&edit_start.row())
9027 || visible_row_range.contains(&edit_end.row());
9028 if !is_visible {
9029 return None;
9030 }
9031
9032 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9033 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9034 } else {
9035 // Fallback for providers without edit_preview
9036 crate::edit_prediction_fallback_text(edits, cx)
9037 };
9038
9039 let styled_text = highlighted_edits.to_styled_text(&style.text);
9040 let line_count = highlighted_edits.text.lines().count();
9041
9042 const BORDER_WIDTH: Pixels = px(1.);
9043
9044 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9045 let has_keybind = keybind.is_some();
9046
9047 let mut element = h_flex()
9048 .items_start()
9049 .child(
9050 h_flex()
9051 .bg(cx.theme().colors().editor_background)
9052 .border(BORDER_WIDTH)
9053 .shadow_xs()
9054 .border_color(cx.theme().colors().border)
9055 .rounded_l_lg()
9056 .when(line_count > 1, |el| el.rounded_br_lg())
9057 .pr_1()
9058 .child(styled_text),
9059 )
9060 .child(
9061 h_flex()
9062 .h(line_height + BORDER_WIDTH * 2.)
9063 .px_1p5()
9064 .gap_1()
9065 // Workaround: For some reason, there's a gap if we don't do this
9066 .ml(-BORDER_WIDTH)
9067 .shadow(vec![gpui::BoxShadow {
9068 color: gpui::black().opacity(0.05),
9069 offset: point(px(1.), px(1.)),
9070 blur_radius: px(2.),
9071 spread_radius: px(0.),
9072 }])
9073 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9074 .border(BORDER_WIDTH)
9075 .border_color(cx.theme().colors().border)
9076 .rounded_r_lg()
9077 .id("edit_prediction_diff_popover_keybind")
9078 .when(!has_keybind, |el| {
9079 let status_colors = cx.theme().status();
9080
9081 el.bg(status_colors.error_background)
9082 .border_color(status_colors.error.opacity(0.6))
9083 .child(Icon::new(IconName::Info).color(Color::Error))
9084 .cursor_default()
9085 .hoverable_tooltip(move |_window, cx| {
9086 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9087 })
9088 })
9089 .children(keybind),
9090 )
9091 .into_any();
9092
9093 let longest_row =
9094 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9095 let longest_line_width = if visible_row_range.contains(&longest_row) {
9096 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9097 } else {
9098 layout_line(
9099 longest_row,
9100 editor_snapshot,
9101 style,
9102 editor_width,
9103 |_| false,
9104 window,
9105 cx,
9106 )
9107 .width
9108 };
9109
9110 let viewport_bounds =
9111 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9112 right: -right_margin,
9113 ..Default::default()
9114 });
9115
9116 let x_after_longest = Pixels::from(
9117 ScrollPixelOffset::from(
9118 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9119 ) - scroll_pixel_position.x,
9120 );
9121
9122 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9123
9124 // Fully visible if it can be displayed within the window (allow overlapping other
9125 // panes). However, this is only allowed if the popover starts within text_bounds.
9126 let can_position_to_the_right = x_after_longest < text_bounds.right()
9127 && x_after_longest + element_bounds.width < viewport_bounds.right();
9128
9129 let mut origin = if can_position_to_the_right {
9130 point(
9131 x_after_longest,
9132 text_bounds.origin.y
9133 + Pixels::from(
9134 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9135 - scroll_pixel_position.y,
9136 ),
9137 )
9138 } else {
9139 let cursor_row = newest_selection_head.map(|head| head.row());
9140 let above_edit = edit_start
9141 .row()
9142 .0
9143 .checked_sub(line_count as u32)
9144 .map(DisplayRow);
9145 let below_edit = Some(edit_end.row() + 1);
9146 let above_cursor =
9147 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9148 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9149
9150 // Place the edit popover adjacent to the edit if there is a location
9151 // available that is onscreen and does not obscure the cursor. Otherwise,
9152 // place it adjacent to the cursor.
9153 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9154 .into_iter()
9155 .flatten()
9156 .find(|&start_row| {
9157 let end_row = start_row + line_count as u32;
9158 visible_row_range.contains(&start_row)
9159 && visible_row_range.contains(&end_row)
9160 && cursor_row
9161 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9162 })?;
9163
9164 content_origin
9165 + point(
9166 Pixels::from(-scroll_pixel_position.x),
9167 Pixels::from(
9168 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9169 ),
9170 )
9171 };
9172
9173 origin.x -= BORDER_WIDTH;
9174
9175 window.defer_draw(element, origin, 1);
9176
9177 // Do not return an element, since it will already be drawn due to defer_draw.
9178 None
9179 }
9180
9181 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9182 px(30.)
9183 }
9184
9185 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9186 if self.read_only(cx) {
9187 cx.theme().players().read_only()
9188 } else {
9189 self.style.as_ref().unwrap().local_player
9190 }
9191 }
9192
9193 fn render_edit_prediction_accept_keybind(
9194 &self,
9195 window: &mut Window,
9196 cx: &mut App,
9197 ) -> Option<AnyElement> {
9198 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9199 let accept_keystroke = accept_binding.keystroke()?;
9200
9201 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9202
9203 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9204 Color::Accent
9205 } else {
9206 Color::Muted
9207 };
9208
9209 h_flex()
9210 .px_0p5()
9211 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9212 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9213 .text_size(TextSize::XSmall.rems(cx))
9214 .child(h_flex().children(ui::render_modifiers(
9215 accept_keystroke.modifiers(),
9216 PlatformStyle::platform(),
9217 Some(modifiers_color),
9218 Some(IconSize::XSmall.rems().into()),
9219 true,
9220 )))
9221 .when(is_platform_style_mac, |parent| {
9222 parent.child(accept_keystroke.key().to_string())
9223 })
9224 .when(!is_platform_style_mac, |parent| {
9225 parent.child(
9226 Key::new(
9227 util::capitalize(accept_keystroke.key()),
9228 Some(Color::Default),
9229 )
9230 .size(Some(IconSize::XSmall.rems().into())),
9231 )
9232 })
9233 .into_any()
9234 .into()
9235 }
9236
9237 fn render_edit_prediction_line_popover(
9238 &self,
9239 label: impl Into<SharedString>,
9240 icon: Option<IconName>,
9241 window: &mut Window,
9242 cx: &mut App,
9243 ) -> Stateful<Div> {
9244 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9245
9246 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9247 let has_keybind = keybind.is_some();
9248
9249 h_flex()
9250 .id("ep-line-popover")
9251 .py_0p5()
9252 .pl_1()
9253 .pr(padding_right)
9254 .gap_1()
9255 .rounded_md()
9256 .border_1()
9257 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9258 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9259 .shadow_xs()
9260 .when(!has_keybind, |el| {
9261 let status_colors = cx.theme().status();
9262
9263 el.bg(status_colors.error_background)
9264 .border_color(status_colors.error.opacity(0.6))
9265 .pl_2()
9266 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9267 .cursor_default()
9268 .hoverable_tooltip(move |_window, cx| {
9269 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9270 })
9271 })
9272 .children(keybind)
9273 .child(
9274 Label::new(label)
9275 .size(LabelSize::Small)
9276 .when(!has_keybind, |el| {
9277 el.color(cx.theme().status().error.into()).strikethrough()
9278 }),
9279 )
9280 .when(!has_keybind, |el| {
9281 el.child(
9282 h_flex().ml_1().child(
9283 Icon::new(IconName::Info)
9284 .size(IconSize::Small)
9285 .color(cx.theme().status().error.into()),
9286 ),
9287 )
9288 })
9289 .when_some(icon, |element, icon| {
9290 element.child(
9291 div()
9292 .mt(px(1.5))
9293 .child(Icon::new(icon).size(IconSize::Small)),
9294 )
9295 })
9296 }
9297
9298 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9299 let accent_color = cx.theme().colors().text_accent;
9300 let editor_bg_color = cx.theme().colors().editor_background;
9301 editor_bg_color.blend(accent_color.opacity(0.1))
9302 }
9303
9304 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9305 let accent_color = cx.theme().colors().text_accent;
9306 let editor_bg_color = cx.theme().colors().editor_background;
9307 editor_bg_color.blend(accent_color.opacity(0.6))
9308 }
9309 fn get_prediction_provider_icon_name(
9310 provider: &Option<RegisteredEditPredictionProvider>,
9311 ) -> IconName {
9312 match provider {
9313 Some(provider) => match provider.provider.name() {
9314 "copilot" => IconName::Copilot,
9315 "supermaven" => IconName::Supermaven,
9316 _ => IconName::ZedPredict,
9317 },
9318 None => IconName::ZedPredict,
9319 }
9320 }
9321
9322 fn render_edit_prediction_cursor_popover(
9323 &self,
9324 min_width: Pixels,
9325 max_width: Pixels,
9326 cursor_point: Point,
9327 style: &EditorStyle,
9328 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9329 _window: &Window,
9330 cx: &mut Context<Editor>,
9331 ) -> Option<AnyElement> {
9332 let provider = self.edit_prediction_provider.as_ref()?;
9333 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9334
9335 let is_refreshing = provider.provider.is_refreshing(cx);
9336
9337 fn pending_completion_container(icon: IconName) -> Div {
9338 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9339 }
9340
9341 let completion = match &self.active_edit_prediction {
9342 Some(prediction) => {
9343 if !self.has_visible_completions_menu() {
9344 const RADIUS: Pixels = px(6.);
9345 const BORDER_WIDTH: Pixels = px(1.);
9346
9347 return Some(
9348 h_flex()
9349 .elevation_2(cx)
9350 .border(BORDER_WIDTH)
9351 .border_color(cx.theme().colors().border)
9352 .when(accept_keystroke.is_none(), |el| {
9353 el.border_color(cx.theme().status().error)
9354 })
9355 .rounded(RADIUS)
9356 .rounded_tl(px(0.))
9357 .overflow_hidden()
9358 .child(div().px_1p5().child(match &prediction.completion {
9359 EditPrediction::MoveWithin { target, snapshot } => {
9360 use text::ToPoint as _;
9361 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9362 {
9363 Icon::new(IconName::ZedPredictDown)
9364 } else {
9365 Icon::new(IconName::ZedPredictUp)
9366 }
9367 }
9368 EditPrediction::MoveOutside { .. } => {
9369 // TODO [zeta2] custom icon for external jump?
9370 Icon::new(provider_icon)
9371 }
9372 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9373 }))
9374 .child(
9375 h_flex()
9376 .gap_1()
9377 .py_1()
9378 .px_2()
9379 .rounded_r(RADIUS - BORDER_WIDTH)
9380 .border_l_1()
9381 .border_color(cx.theme().colors().border)
9382 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9383 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9384 el.child(
9385 Label::new("Hold")
9386 .size(LabelSize::Small)
9387 .when(accept_keystroke.is_none(), |el| {
9388 el.strikethrough()
9389 })
9390 .line_height_style(LineHeightStyle::UiLabel),
9391 )
9392 })
9393 .id("edit_prediction_cursor_popover_keybind")
9394 .when(accept_keystroke.is_none(), |el| {
9395 let status_colors = cx.theme().status();
9396
9397 el.bg(status_colors.error_background)
9398 .border_color(status_colors.error.opacity(0.6))
9399 .child(Icon::new(IconName::Info).color(Color::Error))
9400 .cursor_default()
9401 .hoverable_tooltip(move |_window, cx| {
9402 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9403 .into()
9404 })
9405 })
9406 .when_some(
9407 accept_keystroke.as_ref(),
9408 |el, accept_keystroke| {
9409 el.child(h_flex().children(ui::render_modifiers(
9410 accept_keystroke.modifiers(),
9411 PlatformStyle::platform(),
9412 Some(Color::Default),
9413 Some(IconSize::XSmall.rems().into()),
9414 false,
9415 )))
9416 },
9417 ),
9418 )
9419 .into_any(),
9420 );
9421 }
9422
9423 self.render_edit_prediction_cursor_popover_preview(
9424 prediction,
9425 cursor_point,
9426 style,
9427 cx,
9428 )?
9429 }
9430
9431 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9432 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9433 stale_completion,
9434 cursor_point,
9435 style,
9436 cx,
9437 )?,
9438
9439 None => pending_completion_container(provider_icon)
9440 .child(Label::new("...").size(LabelSize::Small)),
9441 },
9442
9443 None => pending_completion_container(provider_icon)
9444 .child(Label::new("...").size(LabelSize::Small)),
9445 };
9446
9447 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9448 completion
9449 .with_animation(
9450 "loading-completion",
9451 Animation::new(Duration::from_secs(2))
9452 .repeat()
9453 .with_easing(pulsating_between(0.4, 0.8)),
9454 |label, delta| label.opacity(delta),
9455 )
9456 .into_any_element()
9457 } else {
9458 completion.into_any_element()
9459 };
9460
9461 let has_completion = self.active_edit_prediction.is_some();
9462
9463 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9464 Some(
9465 h_flex()
9466 .min_w(min_width)
9467 .max_w(max_width)
9468 .flex_1()
9469 .elevation_2(cx)
9470 .border_color(cx.theme().colors().border)
9471 .child(
9472 div()
9473 .flex_1()
9474 .py_1()
9475 .px_2()
9476 .overflow_hidden()
9477 .child(completion),
9478 )
9479 .when_some(accept_keystroke, |el, accept_keystroke| {
9480 if !accept_keystroke.modifiers().modified() {
9481 return el;
9482 }
9483
9484 el.child(
9485 h_flex()
9486 .h_full()
9487 .border_l_1()
9488 .rounded_r_lg()
9489 .border_color(cx.theme().colors().border)
9490 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9491 .gap_1()
9492 .py_1()
9493 .px_2()
9494 .child(
9495 h_flex()
9496 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9497 .when(is_platform_style_mac, |parent| parent.gap_1())
9498 .child(h_flex().children(ui::render_modifiers(
9499 accept_keystroke.modifiers(),
9500 PlatformStyle::platform(),
9501 Some(if !has_completion {
9502 Color::Muted
9503 } else {
9504 Color::Default
9505 }),
9506 None,
9507 false,
9508 ))),
9509 )
9510 .child(Label::new("Preview").into_any_element())
9511 .opacity(if has_completion { 1.0 } else { 0.4 }),
9512 )
9513 })
9514 .into_any(),
9515 )
9516 }
9517
9518 fn render_edit_prediction_cursor_popover_preview(
9519 &self,
9520 completion: &EditPredictionState,
9521 cursor_point: Point,
9522 style: &EditorStyle,
9523 cx: &mut Context<Editor>,
9524 ) -> Option<Div> {
9525 use text::ToPoint as _;
9526
9527 fn render_relative_row_jump(
9528 prefix: impl Into<String>,
9529 current_row: u32,
9530 target_row: u32,
9531 ) -> Div {
9532 let (row_diff, arrow) = if target_row < current_row {
9533 (current_row - target_row, IconName::ArrowUp)
9534 } else {
9535 (target_row - current_row, IconName::ArrowDown)
9536 };
9537
9538 h_flex()
9539 .child(
9540 Label::new(format!("{}{}", prefix.into(), row_diff))
9541 .color(Color::Muted)
9542 .size(LabelSize::Small),
9543 )
9544 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9545 }
9546
9547 let supports_jump = self
9548 .edit_prediction_provider
9549 .as_ref()
9550 .map(|provider| provider.provider.supports_jump_to_edit())
9551 .unwrap_or(true);
9552
9553 match &completion.completion {
9554 EditPrediction::MoveWithin {
9555 target, snapshot, ..
9556 } => {
9557 if !supports_jump {
9558 return None;
9559 }
9560
9561 Some(
9562 h_flex()
9563 .px_2()
9564 .gap_2()
9565 .flex_1()
9566 .child(
9567 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9568 Icon::new(IconName::ZedPredictDown)
9569 } else {
9570 Icon::new(IconName::ZedPredictUp)
9571 },
9572 )
9573 .child(Label::new("Jump to Edit")),
9574 )
9575 }
9576 EditPrediction::MoveOutside { snapshot, .. } => {
9577 let file_name = snapshot
9578 .file()
9579 .map(|file| file.file_name(cx))
9580 .unwrap_or("untitled");
9581 Some(
9582 h_flex()
9583 .px_2()
9584 .gap_2()
9585 .flex_1()
9586 .child(Icon::new(IconName::ZedPredict))
9587 .child(Label::new(format!("Jump to {file_name}"))),
9588 )
9589 }
9590 EditPrediction::Edit {
9591 edits,
9592 edit_preview,
9593 snapshot,
9594 display_mode: _,
9595 } => {
9596 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9597
9598 let (highlighted_edits, has_more_lines) =
9599 if let Some(edit_preview) = edit_preview.as_ref() {
9600 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9601 .first_line_preview()
9602 } else {
9603 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9604 };
9605
9606 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9607 .with_default_highlights(&style.text, highlighted_edits.highlights);
9608
9609 let preview = h_flex()
9610 .gap_1()
9611 .min_w_16()
9612 .child(styled_text)
9613 .when(has_more_lines, |parent| parent.child("…"));
9614
9615 let left = if supports_jump && first_edit_row != cursor_point.row {
9616 render_relative_row_jump("", cursor_point.row, first_edit_row)
9617 .into_any_element()
9618 } else {
9619 let icon_name =
9620 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9621 Icon::new(icon_name).into_any_element()
9622 };
9623
9624 Some(
9625 h_flex()
9626 .h_full()
9627 .flex_1()
9628 .gap_2()
9629 .pr_1()
9630 .overflow_x_hidden()
9631 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9632 .child(left)
9633 .child(preview),
9634 )
9635 }
9636 }
9637 }
9638
9639 pub fn render_context_menu(
9640 &self,
9641 style: &EditorStyle,
9642 max_height_in_lines: u32,
9643 window: &mut Window,
9644 cx: &mut Context<Editor>,
9645 ) -> Option<AnyElement> {
9646 let menu = self.context_menu.borrow();
9647 let menu = menu.as_ref()?;
9648 if !menu.visible() {
9649 return None;
9650 };
9651 Some(menu.render(style, max_height_in_lines, window, cx))
9652 }
9653
9654 fn render_context_menu_aside(
9655 &mut self,
9656 max_size: Size<Pixels>,
9657 window: &mut Window,
9658 cx: &mut Context<Editor>,
9659 ) -> Option<AnyElement> {
9660 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9661 if menu.visible() {
9662 menu.render_aside(max_size, window, cx)
9663 } else {
9664 None
9665 }
9666 })
9667 }
9668
9669 fn hide_context_menu(
9670 &mut self,
9671 window: &mut Window,
9672 cx: &mut Context<Self>,
9673 ) -> Option<CodeContextMenu> {
9674 cx.notify();
9675 self.completion_tasks.clear();
9676 let context_menu = self.context_menu.borrow_mut().take();
9677 self.stale_edit_prediction_in_menu.take();
9678 self.update_visible_edit_prediction(window, cx);
9679 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9680 && let Some(completion_provider) = &self.completion_provider
9681 {
9682 completion_provider.selection_changed(None, window, cx);
9683 }
9684 context_menu
9685 }
9686
9687 fn show_snippet_choices(
9688 &mut self,
9689 choices: &Vec<String>,
9690 selection: Range<Anchor>,
9691 cx: &mut Context<Self>,
9692 ) {
9693 let Some((_, buffer, _)) = self
9694 .buffer()
9695 .read(cx)
9696 .excerpt_containing(selection.start, cx)
9697 else {
9698 return;
9699 };
9700 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9701 else {
9702 return;
9703 };
9704 if buffer != end_buffer {
9705 log::error!("expected anchor range to have matching buffer IDs");
9706 return;
9707 }
9708
9709 let id = post_inc(&mut self.next_completion_id);
9710 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9711 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9712 CompletionsMenu::new_snippet_choices(
9713 id,
9714 true,
9715 choices,
9716 selection,
9717 buffer,
9718 snippet_sort_order,
9719 ),
9720 ));
9721 }
9722
9723 pub fn insert_snippet(
9724 &mut self,
9725 insertion_ranges: &[Range<usize>],
9726 snippet: Snippet,
9727 window: &mut Window,
9728 cx: &mut Context<Self>,
9729 ) -> Result<()> {
9730 struct Tabstop<T> {
9731 is_end_tabstop: bool,
9732 ranges: Vec<Range<T>>,
9733 choices: Option<Vec<String>>,
9734 }
9735
9736 let tabstops = self.buffer.update(cx, |buffer, cx| {
9737 let snippet_text: Arc<str> = snippet.text.clone().into();
9738 let edits = insertion_ranges
9739 .iter()
9740 .cloned()
9741 .map(|range| (range, snippet_text.clone()));
9742 let autoindent_mode = AutoindentMode::Block {
9743 original_indent_columns: Vec::new(),
9744 };
9745 buffer.edit(edits, Some(autoindent_mode), cx);
9746
9747 let snapshot = &*buffer.read(cx);
9748 let snippet = &snippet;
9749 snippet
9750 .tabstops
9751 .iter()
9752 .map(|tabstop| {
9753 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9754 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9755 });
9756 let mut tabstop_ranges = tabstop
9757 .ranges
9758 .iter()
9759 .flat_map(|tabstop_range| {
9760 let mut delta = 0_isize;
9761 insertion_ranges.iter().map(move |insertion_range| {
9762 let insertion_start = insertion_range.start as isize + delta;
9763 delta +=
9764 snippet.text.len() as isize - insertion_range.len() as isize;
9765
9766 let start = ((insertion_start + tabstop_range.start) as usize)
9767 .min(snapshot.len());
9768 let end = ((insertion_start + tabstop_range.end) as usize)
9769 .min(snapshot.len());
9770 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9771 })
9772 })
9773 .collect::<Vec<_>>();
9774 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9775
9776 Tabstop {
9777 is_end_tabstop,
9778 ranges: tabstop_ranges,
9779 choices: tabstop.choices.clone(),
9780 }
9781 })
9782 .collect::<Vec<_>>()
9783 });
9784 if let Some(tabstop) = tabstops.first() {
9785 self.change_selections(Default::default(), window, cx, |s| {
9786 // Reverse order so that the first range is the newest created selection.
9787 // Completions will use it and autoscroll will prioritize it.
9788 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9789 });
9790
9791 if let Some(choices) = &tabstop.choices
9792 && let Some(selection) = tabstop.ranges.first()
9793 {
9794 self.show_snippet_choices(choices, selection.clone(), cx)
9795 }
9796
9797 // If we're already at the last tabstop and it's at the end of the snippet,
9798 // we're done, we don't need to keep the state around.
9799 if !tabstop.is_end_tabstop {
9800 let choices = tabstops
9801 .iter()
9802 .map(|tabstop| tabstop.choices.clone())
9803 .collect();
9804
9805 let ranges = tabstops
9806 .into_iter()
9807 .map(|tabstop| tabstop.ranges)
9808 .collect::<Vec<_>>();
9809
9810 self.snippet_stack.push(SnippetState {
9811 active_index: 0,
9812 ranges,
9813 choices,
9814 });
9815 }
9816
9817 // Check whether the just-entered snippet ends with an auto-closable bracket.
9818 if self.autoclose_regions.is_empty() {
9819 let snapshot = self.buffer.read(cx).snapshot(cx);
9820 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9821 let selection_head = selection.head();
9822 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9823 continue;
9824 };
9825
9826 let mut bracket_pair = None;
9827 let max_lookup_length = scope
9828 .brackets()
9829 .map(|(pair, _)| {
9830 pair.start
9831 .as_str()
9832 .chars()
9833 .count()
9834 .max(pair.end.as_str().chars().count())
9835 })
9836 .max();
9837 if let Some(max_lookup_length) = max_lookup_length {
9838 let next_text = snapshot
9839 .chars_at(selection_head)
9840 .take(max_lookup_length)
9841 .collect::<String>();
9842 let prev_text = snapshot
9843 .reversed_chars_at(selection_head)
9844 .take(max_lookup_length)
9845 .collect::<String>();
9846
9847 for (pair, enabled) in scope.brackets() {
9848 if enabled
9849 && pair.close
9850 && prev_text.starts_with(pair.start.as_str())
9851 && next_text.starts_with(pair.end.as_str())
9852 {
9853 bracket_pair = Some(pair.clone());
9854 break;
9855 }
9856 }
9857 }
9858
9859 if let Some(pair) = bracket_pair {
9860 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9861 let autoclose_enabled =
9862 self.use_autoclose && snapshot_settings.use_autoclose;
9863 if autoclose_enabled {
9864 let start = snapshot.anchor_after(selection_head);
9865 let end = snapshot.anchor_after(selection_head);
9866 self.autoclose_regions.push(AutocloseRegion {
9867 selection_id: selection.id,
9868 range: start..end,
9869 pair,
9870 });
9871 }
9872 }
9873 }
9874 }
9875 }
9876 Ok(())
9877 }
9878
9879 pub fn move_to_next_snippet_tabstop(
9880 &mut self,
9881 window: &mut Window,
9882 cx: &mut Context<Self>,
9883 ) -> bool {
9884 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9885 }
9886
9887 pub fn move_to_prev_snippet_tabstop(
9888 &mut self,
9889 window: &mut Window,
9890 cx: &mut Context<Self>,
9891 ) -> bool {
9892 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9893 }
9894
9895 pub fn move_to_snippet_tabstop(
9896 &mut self,
9897 bias: Bias,
9898 window: &mut Window,
9899 cx: &mut Context<Self>,
9900 ) -> bool {
9901 if let Some(mut snippet) = self.snippet_stack.pop() {
9902 match bias {
9903 Bias::Left => {
9904 if snippet.active_index > 0 {
9905 snippet.active_index -= 1;
9906 } else {
9907 self.snippet_stack.push(snippet);
9908 return false;
9909 }
9910 }
9911 Bias::Right => {
9912 if snippet.active_index + 1 < snippet.ranges.len() {
9913 snippet.active_index += 1;
9914 } else {
9915 self.snippet_stack.push(snippet);
9916 return false;
9917 }
9918 }
9919 }
9920 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9921 self.change_selections(Default::default(), window, cx, |s| {
9922 // Reverse order so that the first range is the newest created selection.
9923 // Completions will use it and autoscroll will prioritize it.
9924 s.select_ranges(current_ranges.iter().rev().cloned())
9925 });
9926
9927 if let Some(choices) = &snippet.choices[snippet.active_index]
9928 && let Some(selection) = current_ranges.first()
9929 {
9930 self.show_snippet_choices(choices, selection.clone(), cx);
9931 }
9932
9933 // If snippet state is not at the last tabstop, push it back on the stack
9934 if snippet.active_index + 1 < snippet.ranges.len() {
9935 self.snippet_stack.push(snippet);
9936 }
9937 return true;
9938 }
9939 }
9940
9941 false
9942 }
9943
9944 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9945 self.transact(window, cx, |this, window, cx| {
9946 this.select_all(&SelectAll, window, cx);
9947 this.insert("", window, cx);
9948 });
9949 }
9950
9951 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9952 if self.read_only(cx) {
9953 return;
9954 }
9955 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9956 self.transact(window, cx, |this, window, cx| {
9957 this.select_autoclose_pair(window, cx);
9958
9959 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9960
9961 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9962 if !this.linked_edit_ranges.is_empty() {
9963 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9964 let snapshot = this.buffer.read(cx).snapshot(cx);
9965
9966 for selection in selections.iter() {
9967 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9968 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
9969 if selection_start.buffer_id != selection_end.buffer_id {
9970 continue;
9971 }
9972 if let Some(ranges) =
9973 this.linked_editing_ranges_for(selection_start..selection_end, cx)
9974 {
9975 for (buffer, entries) in ranges {
9976 linked_ranges.entry(buffer).or_default().extend(entries);
9977 }
9978 }
9979 }
9980 }
9981
9982 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
9983 for selection in &mut selections {
9984 if selection.is_empty() {
9985 let old_head = selection.head();
9986 let mut new_head =
9987 movement::left(&display_map, old_head.to_display_point(&display_map))
9988 .to_point(&display_map);
9989 if let Some((buffer, line_buffer_range)) = display_map
9990 .buffer_snapshot()
9991 .buffer_line_for_row(MultiBufferRow(old_head.row))
9992 {
9993 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
9994 let indent_len = match indent_size.kind {
9995 IndentKind::Space => {
9996 buffer.settings_at(line_buffer_range.start, cx).tab_size
9997 }
9998 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
9999 };
10000 if old_head.column <= indent_size.len && old_head.column > 0 {
10001 let indent_len = indent_len.get();
10002 new_head = cmp::min(
10003 new_head,
10004 MultiBufferPoint::new(
10005 old_head.row,
10006 ((old_head.column - 1) / indent_len) * indent_len,
10007 ),
10008 );
10009 }
10010 }
10011
10012 selection.set_head(new_head, SelectionGoal::None);
10013 }
10014 }
10015
10016 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10017 this.insert("", window, cx);
10018 let empty_str: Arc<str> = Arc::from("");
10019 for (buffer, edits) in linked_ranges {
10020 let snapshot = buffer.read(cx).snapshot();
10021 use text::ToPoint as TP;
10022
10023 let edits = edits
10024 .into_iter()
10025 .map(|range| {
10026 let end_point = TP::to_point(&range.end, &snapshot);
10027 let mut start_point = TP::to_point(&range.start, &snapshot);
10028
10029 if end_point == start_point {
10030 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10031 .saturating_sub(1);
10032 start_point =
10033 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10034 };
10035
10036 (start_point..end_point, empty_str.clone())
10037 })
10038 .sorted_by_key(|(range, _)| range.start)
10039 .collect::<Vec<_>>();
10040 buffer.update(cx, |this, cx| {
10041 this.edit(edits, None, cx);
10042 })
10043 }
10044 this.refresh_edit_prediction(true, false, window, cx);
10045 refresh_linked_ranges(this, window, cx);
10046 });
10047 }
10048
10049 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10050 if self.read_only(cx) {
10051 return;
10052 }
10053 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10054 self.transact(window, cx, |this, window, cx| {
10055 this.change_selections(Default::default(), window, cx, |s| {
10056 s.move_with(|map, selection| {
10057 if selection.is_empty() {
10058 let cursor = movement::right(map, selection.head());
10059 selection.end = cursor;
10060 selection.reversed = true;
10061 selection.goal = SelectionGoal::None;
10062 }
10063 })
10064 });
10065 this.insert("", window, cx);
10066 this.refresh_edit_prediction(true, false, window, cx);
10067 });
10068 }
10069
10070 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10071 if self.mode.is_single_line() {
10072 cx.propagate();
10073 return;
10074 }
10075
10076 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10077 if self.move_to_prev_snippet_tabstop(window, cx) {
10078 return;
10079 }
10080 self.outdent(&Outdent, window, cx);
10081 }
10082
10083 pub fn next_snippet_tabstop(
10084 &mut self,
10085 _: &NextSnippetTabstop,
10086 window: &mut Window,
10087 cx: &mut Context<Self>,
10088 ) {
10089 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10090 cx.propagate();
10091 return;
10092 }
10093
10094 if self.move_to_next_snippet_tabstop(window, cx) {
10095 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10096 return;
10097 }
10098 cx.propagate();
10099 }
10100
10101 pub fn previous_snippet_tabstop(
10102 &mut self,
10103 _: &PreviousSnippetTabstop,
10104 window: &mut Window,
10105 cx: &mut Context<Self>,
10106 ) {
10107 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10108 cx.propagate();
10109 return;
10110 }
10111
10112 if self.move_to_prev_snippet_tabstop(window, cx) {
10113 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10114 return;
10115 }
10116 cx.propagate();
10117 }
10118
10119 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10120 if self.mode.is_single_line() {
10121 cx.propagate();
10122 return;
10123 }
10124
10125 if self.move_to_next_snippet_tabstop(window, cx) {
10126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10127 return;
10128 }
10129 if self.read_only(cx) {
10130 return;
10131 }
10132 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10133 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10134 let buffer = self.buffer.read(cx);
10135 let snapshot = buffer.snapshot(cx);
10136 let rows_iter = selections.iter().map(|s| s.head().row);
10137 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10138
10139 let has_some_cursor_in_whitespace = selections
10140 .iter()
10141 .filter(|selection| selection.is_empty())
10142 .any(|selection| {
10143 let cursor = selection.head();
10144 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10145 cursor.column < current_indent.len
10146 });
10147
10148 let mut edits = Vec::new();
10149 let mut prev_edited_row = 0;
10150 let mut row_delta = 0;
10151 for selection in &mut selections {
10152 if selection.start.row != prev_edited_row {
10153 row_delta = 0;
10154 }
10155 prev_edited_row = selection.end.row;
10156
10157 // If the selection is non-empty, then increase the indentation of the selected lines.
10158 if !selection.is_empty() {
10159 row_delta =
10160 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10161 continue;
10162 }
10163
10164 let cursor = selection.head();
10165 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10166 if let Some(suggested_indent) =
10167 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10168 {
10169 // Don't do anything if already at suggested indent
10170 // and there is any other cursor which is not
10171 if has_some_cursor_in_whitespace
10172 && cursor.column == current_indent.len
10173 && current_indent.len == suggested_indent.len
10174 {
10175 continue;
10176 }
10177
10178 // Adjust line and move cursor to suggested indent
10179 // if cursor is not at suggested indent
10180 if cursor.column < suggested_indent.len
10181 && cursor.column <= current_indent.len
10182 && current_indent.len <= suggested_indent.len
10183 {
10184 selection.start = Point::new(cursor.row, suggested_indent.len);
10185 selection.end = selection.start;
10186 if row_delta == 0 {
10187 edits.extend(Buffer::edit_for_indent_size_adjustment(
10188 cursor.row,
10189 current_indent,
10190 suggested_indent,
10191 ));
10192 row_delta = suggested_indent.len - current_indent.len;
10193 }
10194 continue;
10195 }
10196
10197 // If current indent is more than suggested indent
10198 // only move cursor to current indent and skip indent
10199 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10200 selection.start = Point::new(cursor.row, current_indent.len);
10201 selection.end = selection.start;
10202 continue;
10203 }
10204 }
10205
10206 // Otherwise, insert a hard or soft tab.
10207 let settings = buffer.language_settings_at(cursor, cx);
10208 let tab_size = if settings.hard_tabs {
10209 IndentSize::tab()
10210 } else {
10211 let tab_size = settings.tab_size.get();
10212 let indent_remainder = snapshot
10213 .text_for_range(Point::new(cursor.row, 0)..cursor)
10214 .flat_map(str::chars)
10215 .fold(row_delta % tab_size, |counter: u32, c| {
10216 if c == '\t' {
10217 0
10218 } else {
10219 (counter + 1) % tab_size
10220 }
10221 });
10222
10223 let chars_to_next_tab_stop = tab_size - indent_remainder;
10224 IndentSize::spaces(chars_to_next_tab_stop)
10225 };
10226 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10227 selection.end = selection.start;
10228 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10229 row_delta += tab_size.len;
10230 }
10231
10232 self.transact(window, cx, |this, window, cx| {
10233 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10234 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10235 this.refresh_edit_prediction(true, false, window, cx);
10236 });
10237 }
10238
10239 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10240 if self.read_only(cx) {
10241 return;
10242 }
10243 if self.mode.is_single_line() {
10244 cx.propagate();
10245 return;
10246 }
10247
10248 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10249 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10250 let mut prev_edited_row = 0;
10251 let mut row_delta = 0;
10252 let mut edits = Vec::new();
10253 let buffer = self.buffer.read(cx);
10254 let snapshot = buffer.snapshot(cx);
10255 for selection in &mut selections {
10256 if selection.start.row != prev_edited_row {
10257 row_delta = 0;
10258 }
10259 prev_edited_row = selection.end.row;
10260
10261 row_delta =
10262 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10263 }
10264
10265 self.transact(window, cx, |this, window, cx| {
10266 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10267 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10268 });
10269 }
10270
10271 fn indent_selection(
10272 buffer: &MultiBuffer,
10273 snapshot: &MultiBufferSnapshot,
10274 selection: &mut Selection<Point>,
10275 edits: &mut Vec<(Range<Point>, String)>,
10276 delta_for_start_row: u32,
10277 cx: &App,
10278 ) -> u32 {
10279 let settings = buffer.language_settings_at(selection.start, cx);
10280 let tab_size = settings.tab_size.get();
10281 let indent_kind = if settings.hard_tabs {
10282 IndentKind::Tab
10283 } else {
10284 IndentKind::Space
10285 };
10286 let mut start_row = selection.start.row;
10287 let mut end_row = selection.end.row + 1;
10288
10289 // If a selection ends at the beginning of a line, don't indent
10290 // that last line.
10291 if selection.end.column == 0 && selection.end.row > selection.start.row {
10292 end_row -= 1;
10293 }
10294
10295 // Avoid re-indenting a row that has already been indented by a
10296 // previous selection, but still update this selection's column
10297 // to reflect that indentation.
10298 if delta_for_start_row > 0 {
10299 start_row += 1;
10300 selection.start.column += delta_for_start_row;
10301 if selection.end.row == selection.start.row {
10302 selection.end.column += delta_for_start_row;
10303 }
10304 }
10305
10306 let mut delta_for_end_row = 0;
10307 let has_multiple_rows = start_row + 1 != end_row;
10308 for row in start_row..end_row {
10309 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10310 let indent_delta = match (current_indent.kind, indent_kind) {
10311 (IndentKind::Space, IndentKind::Space) => {
10312 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10313 IndentSize::spaces(columns_to_next_tab_stop)
10314 }
10315 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10316 (_, IndentKind::Tab) => IndentSize::tab(),
10317 };
10318
10319 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10320 0
10321 } else {
10322 selection.start.column
10323 };
10324 let row_start = Point::new(row, start);
10325 edits.push((
10326 row_start..row_start,
10327 indent_delta.chars().collect::<String>(),
10328 ));
10329
10330 // Update this selection's endpoints to reflect the indentation.
10331 if row == selection.start.row {
10332 selection.start.column += indent_delta.len;
10333 }
10334 if row == selection.end.row {
10335 selection.end.column += indent_delta.len;
10336 delta_for_end_row = indent_delta.len;
10337 }
10338 }
10339
10340 if selection.start.row == selection.end.row {
10341 delta_for_start_row + delta_for_end_row
10342 } else {
10343 delta_for_end_row
10344 }
10345 }
10346
10347 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10348 if self.read_only(cx) {
10349 return;
10350 }
10351 if self.mode.is_single_line() {
10352 cx.propagate();
10353 return;
10354 }
10355
10356 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10357 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10358 let selections = self.selections.all::<Point>(&display_map);
10359 let mut deletion_ranges = Vec::new();
10360 let mut last_outdent = None;
10361 {
10362 let buffer = self.buffer.read(cx);
10363 let snapshot = buffer.snapshot(cx);
10364 for selection in &selections {
10365 let settings = buffer.language_settings_at(selection.start, cx);
10366 let tab_size = settings.tab_size.get();
10367 let mut rows = selection.spanned_rows(false, &display_map);
10368
10369 // Avoid re-outdenting a row that has already been outdented by a
10370 // previous selection.
10371 if let Some(last_row) = last_outdent
10372 && last_row == rows.start
10373 {
10374 rows.start = rows.start.next_row();
10375 }
10376 let has_multiple_rows = rows.len() > 1;
10377 for row in rows.iter_rows() {
10378 let indent_size = snapshot.indent_size_for_line(row);
10379 if indent_size.len > 0 {
10380 let deletion_len = match indent_size.kind {
10381 IndentKind::Space => {
10382 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10383 if columns_to_prev_tab_stop == 0 {
10384 tab_size
10385 } else {
10386 columns_to_prev_tab_stop
10387 }
10388 }
10389 IndentKind::Tab => 1,
10390 };
10391 let start = if has_multiple_rows
10392 || deletion_len > selection.start.column
10393 || indent_size.len < selection.start.column
10394 {
10395 0
10396 } else {
10397 selection.start.column - deletion_len
10398 };
10399 deletion_ranges.push(
10400 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10401 );
10402 last_outdent = Some(row);
10403 }
10404 }
10405 }
10406 }
10407
10408 self.transact(window, cx, |this, window, cx| {
10409 this.buffer.update(cx, |buffer, cx| {
10410 let empty_str: Arc<str> = Arc::default();
10411 buffer.edit(
10412 deletion_ranges
10413 .into_iter()
10414 .map(|range| (range, empty_str.clone())),
10415 None,
10416 cx,
10417 );
10418 });
10419 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10420 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10421 });
10422 }
10423
10424 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10425 if self.read_only(cx) {
10426 return;
10427 }
10428 if self.mode.is_single_line() {
10429 cx.propagate();
10430 return;
10431 }
10432
10433 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10434 let selections = self
10435 .selections
10436 .all::<usize>(&self.display_snapshot(cx))
10437 .into_iter()
10438 .map(|s| s.range());
10439
10440 self.transact(window, cx, |this, window, cx| {
10441 this.buffer.update(cx, |buffer, cx| {
10442 buffer.autoindent_ranges(selections, cx);
10443 });
10444 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10445 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10446 });
10447 }
10448
10449 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10450 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10451 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10452 let selections = self.selections.all::<Point>(&display_map);
10453
10454 let mut new_cursors = Vec::new();
10455 let mut edit_ranges = Vec::new();
10456 let mut selections = selections.iter().peekable();
10457 while let Some(selection) = selections.next() {
10458 let mut rows = selection.spanned_rows(false, &display_map);
10459
10460 // Accumulate contiguous regions of rows that we want to delete.
10461 while let Some(next_selection) = selections.peek() {
10462 let next_rows = next_selection.spanned_rows(false, &display_map);
10463 if next_rows.start <= rows.end {
10464 rows.end = next_rows.end;
10465 selections.next().unwrap();
10466 } else {
10467 break;
10468 }
10469 }
10470
10471 let buffer = display_map.buffer_snapshot();
10472 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10473 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10474 // If there's a line after the range, delete the \n from the end of the row range
10475 (
10476 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10477 rows.end,
10478 )
10479 } else {
10480 // If there isn't a line after the range, delete the \n from the line before the
10481 // start of the row range
10482 edit_start = edit_start.saturating_sub(1);
10483 (buffer.len(), rows.start.previous_row())
10484 };
10485
10486 let text_layout_details = self.text_layout_details(window);
10487 let x = display_map.x_for_display_point(
10488 selection.head().to_display_point(&display_map),
10489 &text_layout_details,
10490 );
10491 let row = Point::new(target_row.0, 0)
10492 .to_display_point(&display_map)
10493 .row();
10494 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10495
10496 new_cursors.push((
10497 selection.id,
10498 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10499 SelectionGoal::None,
10500 ));
10501 edit_ranges.push(edit_start..edit_end);
10502 }
10503
10504 self.transact(window, cx, |this, window, cx| {
10505 let buffer = this.buffer.update(cx, |buffer, cx| {
10506 let empty_str: Arc<str> = Arc::default();
10507 buffer.edit(
10508 edit_ranges
10509 .into_iter()
10510 .map(|range| (range, empty_str.clone())),
10511 None,
10512 cx,
10513 );
10514 buffer.snapshot(cx)
10515 });
10516 let new_selections = new_cursors
10517 .into_iter()
10518 .map(|(id, cursor, goal)| {
10519 let cursor = cursor.to_point(&buffer);
10520 Selection {
10521 id,
10522 start: cursor,
10523 end: cursor,
10524 reversed: false,
10525 goal,
10526 }
10527 })
10528 .collect();
10529
10530 this.change_selections(Default::default(), window, cx, |s| {
10531 s.select(new_selections);
10532 });
10533 });
10534 }
10535
10536 pub fn join_lines_impl(
10537 &mut self,
10538 insert_whitespace: bool,
10539 window: &mut Window,
10540 cx: &mut Context<Self>,
10541 ) {
10542 if self.read_only(cx) {
10543 return;
10544 }
10545 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10546 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10547 let start = MultiBufferRow(selection.start.row);
10548 // Treat single line selections as if they include the next line. Otherwise this action
10549 // would do nothing for single line selections individual cursors.
10550 let end = if selection.start.row == selection.end.row {
10551 MultiBufferRow(selection.start.row + 1)
10552 } else {
10553 MultiBufferRow(selection.end.row)
10554 };
10555
10556 if let Some(last_row_range) = row_ranges.last_mut()
10557 && start <= last_row_range.end
10558 {
10559 last_row_range.end = end;
10560 continue;
10561 }
10562 row_ranges.push(start..end);
10563 }
10564
10565 let snapshot = self.buffer.read(cx).snapshot(cx);
10566 let mut cursor_positions = Vec::new();
10567 for row_range in &row_ranges {
10568 let anchor = snapshot.anchor_before(Point::new(
10569 row_range.end.previous_row().0,
10570 snapshot.line_len(row_range.end.previous_row()),
10571 ));
10572 cursor_positions.push(anchor..anchor);
10573 }
10574
10575 self.transact(window, cx, |this, window, cx| {
10576 for row_range in row_ranges.into_iter().rev() {
10577 for row in row_range.iter_rows().rev() {
10578 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10579 let next_line_row = row.next_row();
10580 let indent = snapshot.indent_size_for_line(next_line_row);
10581 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10582
10583 let replace =
10584 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10585 " "
10586 } else {
10587 ""
10588 };
10589
10590 this.buffer.update(cx, |buffer, cx| {
10591 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10592 });
10593 }
10594 }
10595
10596 this.change_selections(Default::default(), window, cx, |s| {
10597 s.select_anchor_ranges(cursor_positions)
10598 });
10599 });
10600 }
10601
10602 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10603 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10604 self.join_lines_impl(true, window, cx);
10605 }
10606
10607 pub fn sort_lines_case_sensitive(
10608 &mut self,
10609 _: &SortLinesCaseSensitive,
10610 window: &mut Window,
10611 cx: &mut Context<Self>,
10612 ) {
10613 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10614 }
10615
10616 pub fn sort_lines_by_length(
10617 &mut self,
10618 _: &SortLinesByLength,
10619 window: &mut Window,
10620 cx: &mut Context<Self>,
10621 ) {
10622 self.manipulate_immutable_lines(window, cx, |lines| {
10623 lines.sort_by_key(|&line| line.chars().count())
10624 })
10625 }
10626
10627 pub fn sort_lines_case_insensitive(
10628 &mut self,
10629 _: &SortLinesCaseInsensitive,
10630 window: &mut Window,
10631 cx: &mut Context<Self>,
10632 ) {
10633 self.manipulate_immutable_lines(window, cx, |lines| {
10634 lines.sort_by_key(|line| line.to_lowercase())
10635 })
10636 }
10637
10638 pub fn unique_lines_case_insensitive(
10639 &mut self,
10640 _: &UniqueLinesCaseInsensitive,
10641 window: &mut Window,
10642 cx: &mut Context<Self>,
10643 ) {
10644 self.manipulate_immutable_lines(window, cx, |lines| {
10645 let mut seen = HashSet::default();
10646 lines.retain(|line| seen.insert(line.to_lowercase()));
10647 })
10648 }
10649
10650 pub fn unique_lines_case_sensitive(
10651 &mut self,
10652 _: &UniqueLinesCaseSensitive,
10653 window: &mut Window,
10654 cx: &mut Context<Self>,
10655 ) {
10656 self.manipulate_immutable_lines(window, cx, |lines| {
10657 let mut seen = HashSet::default();
10658 lines.retain(|line| seen.insert(*line));
10659 })
10660 }
10661
10662 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10663 let snapshot = self.buffer.read(cx).snapshot(cx);
10664 for selection in self.selections.disjoint_anchors_arc().iter() {
10665 if snapshot
10666 .language_at(selection.start)
10667 .and_then(|lang| lang.config().wrap_characters.as_ref())
10668 .is_some()
10669 {
10670 return true;
10671 }
10672 }
10673 false
10674 }
10675
10676 fn wrap_selections_in_tag(
10677 &mut self,
10678 _: &WrapSelectionsInTag,
10679 window: &mut Window,
10680 cx: &mut Context<Self>,
10681 ) {
10682 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10683
10684 let snapshot = self.buffer.read(cx).snapshot(cx);
10685
10686 let mut edits = Vec::new();
10687 let mut boundaries = Vec::new();
10688
10689 for selection in self
10690 .selections
10691 .all_adjusted(&self.display_snapshot(cx))
10692 .iter()
10693 {
10694 let Some(wrap_config) = snapshot
10695 .language_at(selection.start)
10696 .and_then(|lang| lang.config().wrap_characters.clone())
10697 else {
10698 continue;
10699 };
10700
10701 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10702 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10703
10704 let start_before = snapshot.anchor_before(selection.start);
10705 let end_after = snapshot.anchor_after(selection.end);
10706
10707 edits.push((start_before..start_before, open_tag));
10708 edits.push((end_after..end_after, close_tag));
10709
10710 boundaries.push((
10711 start_before,
10712 end_after,
10713 wrap_config.start_prefix.len(),
10714 wrap_config.end_suffix.len(),
10715 ));
10716 }
10717
10718 if edits.is_empty() {
10719 return;
10720 }
10721
10722 self.transact(window, cx, |this, window, cx| {
10723 let buffer = this.buffer.update(cx, |buffer, cx| {
10724 buffer.edit(edits, None, cx);
10725 buffer.snapshot(cx)
10726 });
10727
10728 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10729 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10730 boundaries.into_iter()
10731 {
10732 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10733 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10734 new_selections.push(open_offset..open_offset);
10735 new_selections.push(close_offset..close_offset);
10736 }
10737
10738 this.change_selections(Default::default(), window, cx, |s| {
10739 s.select_ranges(new_selections);
10740 });
10741
10742 this.request_autoscroll(Autoscroll::fit(), cx);
10743 });
10744 }
10745
10746 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10747 let Some(project) = self.project.clone() else {
10748 return;
10749 };
10750 self.reload(project, window, cx)
10751 .detach_and_notify_err(window, cx);
10752 }
10753
10754 pub fn restore_file(
10755 &mut self,
10756 _: &::git::RestoreFile,
10757 window: &mut Window,
10758 cx: &mut Context<Self>,
10759 ) {
10760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10761 let mut buffer_ids = HashSet::default();
10762 let snapshot = self.buffer().read(cx).snapshot(cx);
10763 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10764 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10765 }
10766
10767 let buffer = self.buffer().read(cx);
10768 let ranges = buffer_ids
10769 .into_iter()
10770 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10771 .collect::<Vec<_>>();
10772
10773 self.restore_hunks_in_ranges(ranges, window, cx);
10774 }
10775
10776 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10777 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10778 let selections = self
10779 .selections
10780 .all(&self.display_snapshot(cx))
10781 .into_iter()
10782 .map(|s| s.range())
10783 .collect();
10784 self.restore_hunks_in_ranges(selections, window, cx);
10785 }
10786
10787 pub fn restore_hunks_in_ranges(
10788 &mut self,
10789 ranges: Vec<Range<Point>>,
10790 window: &mut Window,
10791 cx: &mut Context<Editor>,
10792 ) {
10793 let mut revert_changes = HashMap::default();
10794 let chunk_by = self
10795 .snapshot(window, cx)
10796 .hunks_for_ranges(ranges)
10797 .into_iter()
10798 .chunk_by(|hunk| hunk.buffer_id);
10799 for (buffer_id, hunks) in &chunk_by {
10800 let hunks = hunks.collect::<Vec<_>>();
10801 for hunk in &hunks {
10802 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10803 }
10804 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10805 }
10806 drop(chunk_by);
10807 if !revert_changes.is_empty() {
10808 self.transact(window, cx, |editor, window, cx| {
10809 editor.restore(revert_changes, window, cx);
10810 });
10811 }
10812 }
10813
10814 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10815 if let Some(status) = self
10816 .addons
10817 .iter()
10818 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10819 {
10820 return Some(status);
10821 }
10822 self.project
10823 .as_ref()?
10824 .read(cx)
10825 .status_for_buffer_id(buffer_id, cx)
10826 }
10827
10828 pub fn open_active_item_in_terminal(
10829 &mut self,
10830 _: &OpenInTerminal,
10831 window: &mut Window,
10832 cx: &mut Context<Self>,
10833 ) {
10834 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10835 let project_path = buffer.read(cx).project_path(cx)?;
10836 let project = self.project()?.read(cx);
10837 let entry = project.entry_for_path(&project_path, cx)?;
10838 let parent = match &entry.canonical_path {
10839 Some(canonical_path) => canonical_path.to_path_buf(),
10840 None => project.absolute_path(&project_path, cx)?,
10841 }
10842 .parent()?
10843 .to_path_buf();
10844 Some(parent)
10845 }) {
10846 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10847 }
10848 }
10849
10850 fn set_breakpoint_context_menu(
10851 &mut self,
10852 display_row: DisplayRow,
10853 position: Option<Anchor>,
10854 clicked_point: gpui::Point<Pixels>,
10855 window: &mut Window,
10856 cx: &mut Context<Self>,
10857 ) {
10858 let source = self
10859 .buffer
10860 .read(cx)
10861 .snapshot(cx)
10862 .anchor_before(Point::new(display_row.0, 0u32));
10863
10864 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10865
10866 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10867 self,
10868 source,
10869 clicked_point,
10870 context_menu,
10871 window,
10872 cx,
10873 );
10874 }
10875
10876 fn add_edit_breakpoint_block(
10877 &mut self,
10878 anchor: Anchor,
10879 breakpoint: &Breakpoint,
10880 edit_action: BreakpointPromptEditAction,
10881 window: &mut Window,
10882 cx: &mut Context<Self>,
10883 ) {
10884 let weak_editor = cx.weak_entity();
10885 let bp_prompt = cx.new(|cx| {
10886 BreakpointPromptEditor::new(
10887 weak_editor,
10888 anchor,
10889 breakpoint.clone(),
10890 edit_action,
10891 window,
10892 cx,
10893 )
10894 });
10895
10896 let height = bp_prompt.update(cx, |this, cx| {
10897 this.prompt
10898 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10899 });
10900 let cloned_prompt = bp_prompt.clone();
10901 let blocks = vec![BlockProperties {
10902 style: BlockStyle::Sticky,
10903 placement: BlockPlacement::Above(anchor),
10904 height: Some(height),
10905 render: Arc::new(move |cx| {
10906 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10907 cloned_prompt.clone().into_any_element()
10908 }),
10909 priority: 0,
10910 }];
10911
10912 let focus_handle = bp_prompt.focus_handle(cx);
10913 window.focus(&focus_handle);
10914
10915 let block_ids = self.insert_blocks(blocks, None, cx);
10916 bp_prompt.update(cx, |prompt, _| {
10917 prompt.add_block_ids(block_ids);
10918 });
10919 }
10920
10921 pub(crate) fn breakpoint_at_row(
10922 &self,
10923 row: u32,
10924 window: &mut Window,
10925 cx: &mut Context<Self>,
10926 ) -> Option<(Anchor, Breakpoint)> {
10927 let snapshot = self.snapshot(window, cx);
10928 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10929
10930 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10931 }
10932
10933 pub(crate) fn breakpoint_at_anchor(
10934 &self,
10935 breakpoint_position: Anchor,
10936 snapshot: &EditorSnapshot,
10937 cx: &mut Context<Self>,
10938 ) -> Option<(Anchor, Breakpoint)> {
10939 let buffer = self
10940 .buffer
10941 .read(cx)
10942 .buffer_for_anchor(breakpoint_position, cx)?;
10943
10944 let enclosing_excerpt = breakpoint_position.excerpt_id;
10945 let buffer_snapshot = buffer.read(cx).snapshot();
10946
10947 let row = buffer_snapshot
10948 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10949 .row;
10950
10951 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10952 let anchor_end = snapshot
10953 .buffer_snapshot()
10954 .anchor_after(Point::new(row, line_len));
10955
10956 self.breakpoint_store
10957 .as_ref()?
10958 .read_with(cx, |breakpoint_store, cx| {
10959 breakpoint_store
10960 .breakpoints(
10961 &buffer,
10962 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10963 &buffer_snapshot,
10964 cx,
10965 )
10966 .next()
10967 .and_then(|(bp, _)| {
10968 let breakpoint_row = buffer_snapshot
10969 .summary_for_anchor::<text::PointUtf16>(&bp.position)
10970 .row;
10971
10972 if breakpoint_row == row {
10973 snapshot
10974 .buffer_snapshot()
10975 .anchor_in_excerpt(enclosing_excerpt, bp.position)
10976 .map(|position| (position, bp.bp.clone()))
10977 } else {
10978 None
10979 }
10980 })
10981 })
10982 }
10983
10984 pub fn edit_log_breakpoint(
10985 &mut self,
10986 _: &EditLogBreakpoint,
10987 window: &mut Window,
10988 cx: &mut Context<Self>,
10989 ) {
10990 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
10991 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
10992 message: None,
10993 state: BreakpointState::Enabled,
10994 condition: None,
10995 hit_condition: None,
10996 });
10997
10998 self.add_edit_breakpoint_block(
10999 anchor,
11000 &breakpoint,
11001 BreakpointPromptEditAction::Log,
11002 window,
11003 cx,
11004 );
11005 }
11006 }
11007
11008 fn breakpoints_at_cursors(
11009 &self,
11010 window: &mut Window,
11011 cx: &mut Context<Self>,
11012 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11013 let snapshot = self.snapshot(window, cx);
11014 let cursors = self
11015 .selections
11016 .disjoint_anchors_arc()
11017 .iter()
11018 .map(|selection| {
11019 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11020
11021 let breakpoint_position = self
11022 .breakpoint_at_row(cursor_position.row, window, cx)
11023 .map(|bp| bp.0)
11024 .unwrap_or_else(|| {
11025 snapshot
11026 .display_snapshot
11027 .buffer_snapshot()
11028 .anchor_after(Point::new(cursor_position.row, 0))
11029 });
11030
11031 let breakpoint = self
11032 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11033 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11034
11035 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11036 })
11037 // 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.
11038 .collect::<HashMap<Anchor, _>>();
11039
11040 cursors.into_iter().collect()
11041 }
11042
11043 pub fn enable_breakpoint(
11044 &mut self,
11045 _: &crate::actions::EnableBreakpoint,
11046 window: &mut Window,
11047 cx: &mut Context<Self>,
11048 ) {
11049 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11050 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11051 continue;
11052 };
11053 self.edit_breakpoint_at_anchor(
11054 anchor,
11055 breakpoint,
11056 BreakpointEditAction::InvertState,
11057 cx,
11058 );
11059 }
11060 }
11061
11062 pub fn disable_breakpoint(
11063 &mut self,
11064 _: &crate::actions::DisableBreakpoint,
11065 window: &mut Window,
11066 cx: &mut Context<Self>,
11067 ) {
11068 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11069 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11070 continue;
11071 };
11072 self.edit_breakpoint_at_anchor(
11073 anchor,
11074 breakpoint,
11075 BreakpointEditAction::InvertState,
11076 cx,
11077 );
11078 }
11079 }
11080
11081 pub fn toggle_breakpoint(
11082 &mut self,
11083 _: &crate::actions::ToggleBreakpoint,
11084 window: &mut Window,
11085 cx: &mut Context<Self>,
11086 ) {
11087 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11088 if let Some(breakpoint) = breakpoint {
11089 self.edit_breakpoint_at_anchor(
11090 anchor,
11091 breakpoint,
11092 BreakpointEditAction::Toggle,
11093 cx,
11094 );
11095 } else {
11096 self.edit_breakpoint_at_anchor(
11097 anchor,
11098 Breakpoint::new_standard(),
11099 BreakpointEditAction::Toggle,
11100 cx,
11101 );
11102 }
11103 }
11104 }
11105
11106 pub fn edit_breakpoint_at_anchor(
11107 &mut self,
11108 breakpoint_position: Anchor,
11109 breakpoint: Breakpoint,
11110 edit_action: BreakpointEditAction,
11111 cx: &mut Context<Self>,
11112 ) {
11113 let Some(breakpoint_store) = &self.breakpoint_store else {
11114 return;
11115 };
11116
11117 let Some(buffer) = self
11118 .buffer
11119 .read(cx)
11120 .buffer_for_anchor(breakpoint_position, cx)
11121 else {
11122 return;
11123 };
11124
11125 breakpoint_store.update(cx, |breakpoint_store, cx| {
11126 breakpoint_store.toggle_breakpoint(
11127 buffer,
11128 BreakpointWithPosition {
11129 position: breakpoint_position.text_anchor,
11130 bp: breakpoint,
11131 },
11132 edit_action,
11133 cx,
11134 );
11135 });
11136
11137 cx.notify();
11138 }
11139
11140 #[cfg(any(test, feature = "test-support"))]
11141 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11142 self.breakpoint_store.clone()
11143 }
11144
11145 pub fn prepare_restore_change(
11146 &self,
11147 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11148 hunk: &MultiBufferDiffHunk,
11149 cx: &mut App,
11150 ) -> Option<()> {
11151 if hunk.is_created_file() {
11152 return None;
11153 }
11154 let buffer = self.buffer.read(cx);
11155 let diff = buffer.diff_for(hunk.buffer_id)?;
11156 let buffer = buffer.buffer(hunk.buffer_id)?;
11157 let buffer = buffer.read(cx);
11158 let original_text = diff
11159 .read(cx)
11160 .base_text()
11161 .as_rope()
11162 .slice(hunk.diff_base_byte_range.clone());
11163 let buffer_snapshot = buffer.snapshot();
11164 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11165 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11166 probe
11167 .0
11168 .start
11169 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11170 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11171 }) {
11172 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11173 Some(())
11174 } else {
11175 None
11176 }
11177 }
11178
11179 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11180 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11181 }
11182
11183 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11184 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11185 }
11186
11187 fn manipulate_lines<M>(
11188 &mut self,
11189 window: &mut Window,
11190 cx: &mut Context<Self>,
11191 mut manipulate: M,
11192 ) where
11193 M: FnMut(&str) -> LineManipulationResult,
11194 {
11195 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11196
11197 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11198 let buffer = self.buffer.read(cx).snapshot(cx);
11199
11200 let mut edits = Vec::new();
11201
11202 let selections = self.selections.all::<Point>(&display_map);
11203 let mut selections = selections.iter().peekable();
11204 let mut contiguous_row_selections = Vec::new();
11205 let mut new_selections = Vec::new();
11206 let mut added_lines = 0;
11207 let mut removed_lines = 0;
11208
11209 while let Some(selection) = selections.next() {
11210 let (start_row, end_row) = consume_contiguous_rows(
11211 &mut contiguous_row_selections,
11212 selection,
11213 &display_map,
11214 &mut selections,
11215 );
11216
11217 let start_point = Point::new(start_row.0, 0);
11218 let end_point = Point::new(
11219 end_row.previous_row().0,
11220 buffer.line_len(end_row.previous_row()),
11221 );
11222 let text = buffer
11223 .text_for_range(start_point..end_point)
11224 .collect::<String>();
11225
11226 let LineManipulationResult {
11227 new_text,
11228 line_count_before,
11229 line_count_after,
11230 } = manipulate(&text);
11231
11232 edits.push((start_point..end_point, new_text));
11233
11234 // Selections must change based on added and removed line count
11235 let start_row =
11236 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11237 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11238 new_selections.push(Selection {
11239 id: selection.id,
11240 start: start_row,
11241 end: end_row,
11242 goal: SelectionGoal::None,
11243 reversed: selection.reversed,
11244 });
11245
11246 if line_count_after > line_count_before {
11247 added_lines += line_count_after - line_count_before;
11248 } else if line_count_before > line_count_after {
11249 removed_lines += line_count_before - line_count_after;
11250 }
11251 }
11252
11253 self.transact(window, cx, |this, window, cx| {
11254 let buffer = this.buffer.update(cx, |buffer, cx| {
11255 buffer.edit(edits, None, cx);
11256 buffer.snapshot(cx)
11257 });
11258
11259 // Recalculate offsets on newly edited buffer
11260 let new_selections = new_selections
11261 .iter()
11262 .map(|s| {
11263 let start_point = Point::new(s.start.0, 0);
11264 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11265 Selection {
11266 id: s.id,
11267 start: buffer.point_to_offset(start_point),
11268 end: buffer.point_to_offset(end_point),
11269 goal: s.goal,
11270 reversed: s.reversed,
11271 }
11272 })
11273 .collect();
11274
11275 this.change_selections(Default::default(), window, cx, |s| {
11276 s.select(new_selections);
11277 });
11278
11279 this.request_autoscroll(Autoscroll::fit(), cx);
11280 });
11281 }
11282
11283 fn manipulate_immutable_lines<Fn>(
11284 &mut self,
11285 window: &mut Window,
11286 cx: &mut Context<Self>,
11287 mut callback: Fn,
11288 ) where
11289 Fn: FnMut(&mut Vec<&str>),
11290 {
11291 self.manipulate_lines(window, cx, |text| {
11292 let mut lines: Vec<&str> = text.split('\n').collect();
11293 let line_count_before = lines.len();
11294
11295 callback(&mut lines);
11296
11297 LineManipulationResult {
11298 new_text: lines.join("\n"),
11299 line_count_before,
11300 line_count_after: lines.len(),
11301 }
11302 });
11303 }
11304
11305 fn manipulate_mutable_lines<Fn>(
11306 &mut self,
11307 window: &mut Window,
11308 cx: &mut Context<Self>,
11309 mut callback: Fn,
11310 ) where
11311 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11312 {
11313 self.manipulate_lines(window, cx, |text| {
11314 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11315 let line_count_before = lines.len();
11316
11317 callback(&mut lines);
11318
11319 LineManipulationResult {
11320 new_text: lines.join("\n"),
11321 line_count_before,
11322 line_count_after: lines.len(),
11323 }
11324 });
11325 }
11326
11327 pub fn convert_indentation_to_spaces(
11328 &mut self,
11329 _: &ConvertIndentationToSpaces,
11330 window: &mut Window,
11331 cx: &mut Context<Self>,
11332 ) {
11333 let settings = self.buffer.read(cx).language_settings(cx);
11334 let tab_size = settings.tab_size.get() as usize;
11335
11336 self.manipulate_mutable_lines(window, cx, |lines| {
11337 // Allocates a reasonably sized scratch buffer once for the whole loop
11338 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11339 // Avoids recomputing spaces that could be inserted many times
11340 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11341 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11342 .collect();
11343
11344 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11345 let mut chars = line.as_ref().chars();
11346 let mut col = 0;
11347 let mut changed = false;
11348
11349 for ch in chars.by_ref() {
11350 match ch {
11351 ' ' => {
11352 reindented_line.push(' ');
11353 col += 1;
11354 }
11355 '\t' => {
11356 // \t are converted to spaces depending on the current column
11357 let spaces_len = tab_size - (col % tab_size);
11358 reindented_line.extend(&space_cache[spaces_len - 1]);
11359 col += spaces_len;
11360 changed = true;
11361 }
11362 _ => {
11363 // If we dont append before break, the character is consumed
11364 reindented_line.push(ch);
11365 break;
11366 }
11367 }
11368 }
11369
11370 if !changed {
11371 reindented_line.clear();
11372 continue;
11373 }
11374 // Append the rest of the line and replace old reference with new one
11375 reindented_line.extend(chars);
11376 *line = Cow::Owned(reindented_line.clone());
11377 reindented_line.clear();
11378 }
11379 });
11380 }
11381
11382 pub fn convert_indentation_to_tabs(
11383 &mut self,
11384 _: &ConvertIndentationToTabs,
11385 window: &mut Window,
11386 cx: &mut Context<Self>,
11387 ) {
11388 let settings = self.buffer.read(cx).language_settings(cx);
11389 let tab_size = settings.tab_size.get() as usize;
11390
11391 self.manipulate_mutable_lines(window, cx, |lines| {
11392 // Allocates a reasonably sized buffer once for the whole loop
11393 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11394 // Avoids recomputing spaces that could be inserted many times
11395 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11396 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11397 .collect();
11398
11399 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11400 let mut chars = line.chars();
11401 let mut spaces_count = 0;
11402 let mut first_non_indent_char = None;
11403 let mut changed = false;
11404
11405 for ch in chars.by_ref() {
11406 match ch {
11407 ' ' => {
11408 // Keep track of spaces. Append \t when we reach tab_size
11409 spaces_count += 1;
11410 changed = true;
11411 if spaces_count == tab_size {
11412 reindented_line.push('\t');
11413 spaces_count = 0;
11414 }
11415 }
11416 '\t' => {
11417 reindented_line.push('\t');
11418 spaces_count = 0;
11419 }
11420 _ => {
11421 // Dont append it yet, we might have remaining spaces
11422 first_non_indent_char = Some(ch);
11423 break;
11424 }
11425 }
11426 }
11427
11428 if !changed {
11429 reindented_line.clear();
11430 continue;
11431 }
11432 // Remaining spaces that didn't make a full tab stop
11433 if spaces_count > 0 {
11434 reindented_line.extend(&space_cache[spaces_count - 1]);
11435 }
11436 // If we consume an extra character that was not indentation, add it back
11437 if let Some(extra_char) = first_non_indent_char {
11438 reindented_line.push(extra_char);
11439 }
11440 // Append the rest of the line and replace old reference with new one
11441 reindented_line.extend(chars);
11442 *line = Cow::Owned(reindented_line.clone());
11443 reindented_line.clear();
11444 }
11445 });
11446 }
11447
11448 pub fn convert_to_upper_case(
11449 &mut self,
11450 _: &ConvertToUpperCase,
11451 window: &mut Window,
11452 cx: &mut Context<Self>,
11453 ) {
11454 self.manipulate_text(window, cx, |text| text.to_uppercase())
11455 }
11456
11457 pub fn convert_to_lower_case(
11458 &mut self,
11459 _: &ConvertToLowerCase,
11460 window: &mut Window,
11461 cx: &mut Context<Self>,
11462 ) {
11463 self.manipulate_text(window, cx, |text| text.to_lowercase())
11464 }
11465
11466 pub fn convert_to_title_case(
11467 &mut self,
11468 _: &ConvertToTitleCase,
11469 window: &mut Window,
11470 cx: &mut Context<Self>,
11471 ) {
11472 self.manipulate_text(window, cx, |text| {
11473 text.split('\n')
11474 .map(|line| line.to_case(Case::Title))
11475 .join("\n")
11476 })
11477 }
11478
11479 pub fn convert_to_snake_case(
11480 &mut self,
11481 _: &ConvertToSnakeCase,
11482 window: &mut Window,
11483 cx: &mut Context<Self>,
11484 ) {
11485 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11486 }
11487
11488 pub fn convert_to_kebab_case(
11489 &mut self,
11490 _: &ConvertToKebabCase,
11491 window: &mut Window,
11492 cx: &mut Context<Self>,
11493 ) {
11494 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11495 }
11496
11497 pub fn convert_to_upper_camel_case(
11498 &mut self,
11499 _: &ConvertToUpperCamelCase,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 self.manipulate_text(window, cx, |text| {
11504 text.split('\n')
11505 .map(|line| line.to_case(Case::UpperCamel))
11506 .join("\n")
11507 })
11508 }
11509
11510 pub fn convert_to_lower_camel_case(
11511 &mut self,
11512 _: &ConvertToLowerCamelCase,
11513 window: &mut Window,
11514 cx: &mut Context<Self>,
11515 ) {
11516 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11517 }
11518
11519 pub fn convert_to_opposite_case(
11520 &mut self,
11521 _: &ConvertToOppositeCase,
11522 window: &mut Window,
11523 cx: &mut Context<Self>,
11524 ) {
11525 self.manipulate_text(window, cx, |text| {
11526 text.chars()
11527 .fold(String::with_capacity(text.len()), |mut t, c| {
11528 if c.is_uppercase() {
11529 t.extend(c.to_lowercase());
11530 } else {
11531 t.extend(c.to_uppercase());
11532 }
11533 t
11534 })
11535 })
11536 }
11537
11538 pub fn convert_to_sentence_case(
11539 &mut self,
11540 _: &ConvertToSentenceCase,
11541 window: &mut Window,
11542 cx: &mut Context<Self>,
11543 ) {
11544 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11545 }
11546
11547 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11548 self.manipulate_text(window, cx, |text| {
11549 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11550 if has_upper_case_characters {
11551 text.to_lowercase()
11552 } else {
11553 text.to_uppercase()
11554 }
11555 })
11556 }
11557
11558 pub fn convert_to_rot13(
11559 &mut self,
11560 _: &ConvertToRot13,
11561 window: &mut Window,
11562 cx: &mut Context<Self>,
11563 ) {
11564 self.manipulate_text(window, cx, |text| {
11565 text.chars()
11566 .map(|c| match c {
11567 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11568 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11569 _ => c,
11570 })
11571 .collect()
11572 })
11573 }
11574
11575 pub fn convert_to_rot47(
11576 &mut self,
11577 _: &ConvertToRot47,
11578 window: &mut Window,
11579 cx: &mut Context<Self>,
11580 ) {
11581 self.manipulate_text(window, cx, |text| {
11582 text.chars()
11583 .map(|c| {
11584 let code_point = c as u32;
11585 if code_point >= 33 && code_point <= 126 {
11586 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11587 }
11588 c
11589 })
11590 .collect()
11591 })
11592 }
11593
11594 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11595 where
11596 Fn: FnMut(&str) -> String,
11597 {
11598 let buffer = self.buffer.read(cx).snapshot(cx);
11599
11600 let mut new_selections = Vec::new();
11601 let mut edits = Vec::new();
11602 let mut selection_adjustment = 0i32;
11603
11604 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11605 let selection_is_empty = selection.is_empty();
11606
11607 let (start, end) = if selection_is_empty {
11608 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11609 (word_range.start, word_range.end)
11610 } else {
11611 (
11612 buffer.point_to_offset(selection.start),
11613 buffer.point_to_offset(selection.end),
11614 )
11615 };
11616
11617 let text = buffer.text_for_range(start..end).collect::<String>();
11618 let old_length = text.len() as i32;
11619 let text = callback(&text);
11620
11621 new_selections.push(Selection {
11622 start: (start as i32 - selection_adjustment) as usize,
11623 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11624 goal: SelectionGoal::None,
11625 id: selection.id,
11626 reversed: selection.reversed,
11627 });
11628
11629 selection_adjustment += old_length - text.len() as i32;
11630
11631 edits.push((start..end, text));
11632 }
11633
11634 self.transact(window, cx, |this, window, cx| {
11635 this.buffer.update(cx, |buffer, cx| {
11636 buffer.edit(edits, None, cx);
11637 });
11638
11639 this.change_selections(Default::default(), window, cx, |s| {
11640 s.select(new_selections);
11641 });
11642
11643 this.request_autoscroll(Autoscroll::fit(), cx);
11644 });
11645 }
11646
11647 pub fn move_selection_on_drop(
11648 &mut self,
11649 selection: &Selection<Anchor>,
11650 target: DisplayPoint,
11651 is_cut: bool,
11652 window: &mut Window,
11653 cx: &mut Context<Self>,
11654 ) {
11655 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11656 let buffer = display_map.buffer_snapshot();
11657 let mut edits = Vec::new();
11658 let insert_point = display_map
11659 .clip_point(target, Bias::Left)
11660 .to_point(&display_map);
11661 let text = buffer
11662 .text_for_range(selection.start..selection.end)
11663 .collect::<String>();
11664 if is_cut {
11665 edits.push(((selection.start..selection.end), String::new()));
11666 }
11667 let insert_anchor = buffer.anchor_before(insert_point);
11668 edits.push(((insert_anchor..insert_anchor), text));
11669 let last_edit_start = insert_anchor.bias_left(buffer);
11670 let last_edit_end = insert_anchor.bias_right(buffer);
11671 self.transact(window, cx, |this, window, cx| {
11672 this.buffer.update(cx, |buffer, cx| {
11673 buffer.edit(edits, None, cx);
11674 });
11675 this.change_selections(Default::default(), window, cx, |s| {
11676 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11677 });
11678 });
11679 }
11680
11681 pub fn clear_selection_drag_state(&mut self) {
11682 self.selection_drag_state = SelectionDragState::None;
11683 }
11684
11685 pub fn duplicate(
11686 &mut self,
11687 upwards: bool,
11688 whole_lines: bool,
11689 window: &mut Window,
11690 cx: &mut Context<Self>,
11691 ) {
11692 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11693
11694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11695 let buffer = display_map.buffer_snapshot();
11696 let selections = self.selections.all::<Point>(&display_map);
11697
11698 let mut edits = Vec::new();
11699 let mut selections_iter = selections.iter().peekable();
11700 while let Some(selection) = selections_iter.next() {
11701 let mut rows = selection.spanned_rows(false, &display_map);
11702 // duplicate line-wise
11703 if whole_lines || selection.start == selection.end {
11704 // Avoid duplicating the same lines twice.
11705 while let Some(next_selection) = selections_iter.peek() {
11706 let next_rows = next_selection.spanned_rows(false, &display_map);
11707 if next_rows.start < rows.end {
11708 rows.end = next_rows.end;
11709 selections_iter.next().unwrap();
11710 } else {
11711 break;
11712 }
11713 }
11714
11715 // Copy the text from the selected row region and splice it either at the start
11716 // or end of the region.
11717 let start = Point::new(rows.start.0, 0);
11718 let end = Point::new(
11719 rows.end.previous_row().0,
11720 buffer.line_len(rows.end.previous_row()),
11721 );
11722
11723 let mut text = buffer.text_for_range(start..end).collect::<String>();
11724
11725 let insert_location = if upwards {
11726 // When duplicating upward, we need to insert before the current line.
11727 // If we're on the last line and it doesn't end with a newline,
11728 // we need to add a newline before the duplicated content.
11729 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11730 && buffer.max_point().column > 0
11731 && !text.ends_with('\n');
11732
11733 if needs_leading_newline {
11734 text.insert(0, '\n');
11735 end
11736 } else {
11737 text.push('\n');
11738 Point::new(rows.start.0, 0)
11739 }
11740 } else {
11741 text.push('\n');
11742 start
11743 };
11744 edits.push((insert_location..insert_location, text));
11745 } else {
11746 // duplicate character-wise
11747 let start = selection.start;
11748 let end = selection.end;
11749 let text = buffer.text_for_range(start..end).collect::<String>();
11750 edits.push((selection.end..selection.end, text));
11751 }
11752 }
11753
11754 self.transact(window, cx, |this, window, cx| {
11755 this.buffer.update(cx, |buffer, cx| {
11756 buffer.edit(edits, None, cx);
11757 });
11758
11759 // When duplicating upward with whole lines, move the cursor to the duplicated line
11760 if upwards && whole_lines {
11761 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11762
11763 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11764 let mut new_ranges = Vec::new();
11765 let selections = s.all::<Point>(&display_map);
11766 let mut selections_iter = selections.iter().peekable();
11767
11768 while let Some(first_selection) = selections_iter.next() {
11769 // Group contiguous selections together to find the total row span
11770 let mut group_selections = vec![first_selection];
11771 let mut rows = first_selection.spanned_rows(false, &display_map);
11772
11773 while let Some(next_selection) = selections_iter.peek() {
11774 let next_rows = next_selection.spanned_rows(false, &display_map);
11775 if next_rows.start < rows.end {
11776 rows.end = next_rows.end;
11777 group_selections.push(selections_iter.next().unwrap());
11778 } else {
11779 break;
11780 }
11781 }
11782
11783 let row_count = rows.end.0 - rows.start.0;
11784
11785 // Move all selections in this group up by the total number of duplicated rows
11786 for selection in group_selections {
11787 let new_start = Point::new(
11788 selection.start.row.saturating_sub(row_count),
11789 selection.start.column,
11790 );
11791
11792 let new_end = Point::new(
11793 selection.end.row.saturating_sub(row_count),
11794 selection.end.column,
11795 );
11796
11797 new_ranges.push(new_start..new_end);
11798 }
11799 }
11800
11801 s.select_ranges(new_ranges);
11802 });
11803 }
11804
11805 this.request_autoscroll(Autoscroll::fit(), cx);
11806 });
11807 }
11808
11809 pub fn duplicate_line_up(
11810 &mut self,
11811 _: &DuplicateLineUp,
11812 window: &mut Window,
11813 cx: &mut Context<Self>,
11814 ) {
11815 self.duplicate(true, true, window, cx);
11816 }
11817
11818 pub fn duplicate_line_down(
11819 &mut self,
11820 _: &DuplicateLineDown,
11821 window: &mut Window,
11822 cx: &mut Context<Self>,
11823 ) {
11824 self.duplicate(false, true, window, cx);
11825 }
11826
11827 pub fn duplicate_selection(
11828 &mut self,
11829 _: &DuplicateSelection,
11830 window: &mut Window,
11831 cx: &mut Context<Self>,
11832 ) {
11833 self.duplicate(false, false, window, cx);
11834 }
11835
11836 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11837 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11838 if self.mode.is_single_line() {
11839 cx.propagate();
11840 return;
11841 }
11842
11843 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11844 let buffer = self.buffer.read(cx).snapshot(cx);
11845
11846 let mut edits = Vec::new();
11847 let mut unfold_ranges = Vec::new();
11848 let mut refold_creases = Vec::new();
11849
11850 let selections = self.selections.all::<Point>(&display_map);
11851 let mut selections = selections.iter().peekable();
11852 let mut contiguous_row_selections = Vec::new();
11853 let mut new_selections = Vec::new();
11854
11855 while let Some(selection) = selections.next() {
11856 // Find all the selections that span a contiguous row range
11857 let (start_row, end_row) = consume_contiguous_rows(
11858 &mut contiguous_row_selections,
11859 selection,
11860 &display_map,
11861 &mut selections,
11862 );
11863
11864 // Move the text spanned by the row range to be before the line preceding the row range
11865 if start_row.0 > 0 {
11866 let range_to_move = Point::new(
11867 start_row.previous_row().0,
11868 buffer.line_len(start_row.previous_row()),
11869 )
11870 ..Point::new(
11871 end_row.previous_row().0,
11872 buffer.line_len(end_row.previous_row()),
11873 );
11874 let insertion_point = display_map
11875 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11876 .0;
11877
11878 // Don't move lines across excerpts
11879 if buffer
11880 .excerpt_containing(insertion_point..range_to_move.end)
11881 .is_some()
11882 {
11883 let text = buffer
11884 .text_for_range(range_to_move.clone())
11885 .flat_map(|s| s.chars())
11886 .skip(1)
11887 .chain(['\n'])
11888 .collect::<String>();
11889
11890 edits.push((
11891 buffer.anchor_after(range_to_move.start)
11892 ..buffer.anchor_before(range_to_move.end),
11893 String::new(),
11894 ));
11895 let insertion_anchor = buffer.anchor_after(insertion_point);
11896 edits.push((insertion_anchor..insertion_anchor, text));
11897
11898 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11899
11900 // Move selections up
11901 new_selections.extend(contiguous_row_selections.drain(..).map(
11902 |mut selection| {
11903 selection.start.row -= row_delta;
11904 selection.end.row -= row_delta;
11905 selection
11906 },
11907 ));
11908
11909 // Move folds up
11910 unfold_ranges.push(range_to_move.clone());
11911 for fold in display_map.folds_in_range(
11912 buffer.anchor_before(range_to_move.start)
11913 ..buffer.anchor_after(range_to_move.end),
11914 ) {
11915 let mut start = fold.range.start.to_point(&buffer);
11916 let mut end = fold.range.end.to_point(&buffer);
11917 start.row -= row_delta;
11918 end.row -= row_delta;
11919 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11920 }
11921 }
11922 }
11923
11924 // If we didn't move line(s), preserve the existing selections
11925 new_selections.append(&mut contiguous_row_selections);
11926 }
11927
11928 self.transact(window, cx, |this, window, cx| {
11929 this.unfold_ranges(&unfold_ranges, true, true, cx);
11930 this.buffer.update(cx, |buffer, cx| {
11931 for (range, text) in edits {
11932 buffer.edit([(range, text)], None, cx);
11933 }
11934 });
11935 this.fold_creases(refold_creases, true, window, cx);
11936 this.change_selections(Default::default(), window, cx, |s| {
11937 s.select(new_selections);
11938 })
11939 });
11940 }
11941
11942 pub fn move_line_down(
11943 &mut self,
11944 _: &MoveLineDown,
11945 window: &mut Window,
11946 cx: &mut Context<Self>,
11947 ) {
11948 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11949 if self.mode.is_single_line() {
11950 cx.propagate();
11951 return;
11952 }
11953
11954 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11955 let buffer = self.buffer.read(cx).snapshot(cx);
11956
11957 let mut edits = Vec::new();
11958 let mut unfold_ranges = Vec::new();
11959 let mut refold_creases = Vec::new();
11960
11961 let selections = self.selections.all::<Point>(&display_map);
11962 let mut selections = selections.iter().peekable();
11963 let mut contiguous_row_selections = Vec::new();
11964 let mut new_selections = Vec::new();
11965
11966 while let Some(selection) = selections.next() {
11967 // Find all the selections that span a contiguous row range
11968 let (start_row, end_row) = consume_contiguous_rows(
11969 &mut contiguous_row_selections,
11970 selection,
11971 &display_map,
11972 &mut selections,
11973 );
11974
11975 // Move the text spanned by the row range to be after the last line of the row range
11976 if end_row.0 <= buffer.max_point().row {
11977 let range_to_move =
11978 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
11979 let insertion_point = display_map
11980 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
11981 .0;
11982
11983 // Don't move lines across excerpt boundaries
11984 if buffer
11985 .excerpt_containing(range_to_move.start..insertion_point)
11986 .is_some()
11987 {
11988 let mut text = String::from("\n");
11989 text.extend(buffer.text_for_range(range_to_move.clone()));
11990 text.pop(); // Drop trailing newline
11991 edits.push((
11992 buffer.anchor_after(range_to_move.start)
11993 ..buffer.anchor_before(range_to_move.end),
11994 String::new(),
11995 ));
11996 let insertion_anchor = buffer.anchor_after(insertion_point);
11997 edits.push((insertion_anchor..insertion_anchor, text));
11998
11999 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12000
12001 // Move selections down
12002 new_selections.extend(contiguous_row_selections.drain(..).map(
12003 |mut selection| {
12004 selection.start.row += row_delta;
12005 selection.end.row += row_delta;
12006 selection
12007 },
12008 ));
12009
12010 // Move folds down
12011 unfold_ranges.push(range_to_move.clone());
12012 for fold in display_map.folds_in_range(
12013 buffer.anchor_before(range_to_move.start)
12014 ..buffer.anchor_after(range_to_move.end),
12015 ) {
12016 let mut start = fold.range.start.to_point(&buffer);
12017 let mut end = fold.range.end.to_point(&buffer);
12018 start.row += row_delta;
12019 end.row += row_delta;
12020 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12021 }
12022 }
12023 }
12024
12025 // If we didn't move line(s), preserve the existing selections
12026 new_selections.append(&mut contiguous_row_selections);
12027 }
12028
12029 self.transact(window, cx, |this, window, cx| {
12030 this.unfold_ranges(&unfold_ranges, true, true, cx);
12031 this.buffer.update(cx, |buffer, cx| {
12032 for (range, text) in edits {
12033 buffer.edit([(range, text)], None, cx);
12034 }
12035 });
12036 this.fold_creases(refold_creases, true, window, cx);
12037 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12038 });
12039 }
12040
12041 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12042 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12043 let text_layout_details = &self.text_layout_details(window);
12044 self.transact(window, cx, |this, window, cx| {
12045 let edits = this.change_selections(Default::default(), window, cx, |s| {
12046 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12047 s.move_with(|display_map, selection| {
12048 if !selection.is_empty() {
12049 return;
12050 }
12051
12052 let mut head = selection.head();
12053 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12054 if head.column() == display_map.line_len(head.row()) {
12055 transpose_offset = display_map
12056 .buffer_snapshot()
12057 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12058 }
12059
12060 if transpose_offset == 0 {
12061 return;
12062 }
12063
12064 *head.column_mut() += 1;
12065 head = display_map.clip_point(head, Bias::Right);
12066 let goal = SelectionGoal::HorizontalPosition(
12067 display_map
12068 .x_for_display_point(head, text_layout_details)
12069 .into(),
12070 );
12071 selection.collapse_to(head, goal);
12072
12073 let transpose_start = display_map
12074 .buffer_snapshot()
12075 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12076 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12077 let transpose_end = display_map
12078 .buffer_snapshot()
12079 .clip_offset(transpose_offset + 1, Bias::Right);
12080 if let Some(ch) = display_map
12081 .buffer_snapshot()
12082 .chars_at(transpose_start)
12083 .next()
12084 {
12085 edits.push((transpose_start..transpose_offset, String::new()));
12086 edits.push((transpose_end..transpose_end, ch.to_string()));
12087 }
12088 }
12089 });
12090 edits
12091 });
12092 this.buffer
12093 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12094 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12095 this.change_selections(Default::default(), window, cx, |s| {
12096 s.select(selections);
12097 });
12098 });
12099 }
12100
12101 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12102 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12103 if self.mode.is_single_line() {
12104 cx.propagate();
12105 return;
12106 }
12107
12108 self.rewrap_impl(RewrapOptions::default(), cx)
12109 }
12110
12111 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12112 let buffer = self.buffer.read(cx).snapshot(cx);
12113 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12114
12115 #[derive(Clone, Debug, PartialEq)]
12116 enum CommentFormat {
12117 /// single line comment, with prefix for line
12118 Line(String),
12119 /// single line within a block comment, with prefix for line
12120 BlockLine(String),
12121 /// a single line of a block comment that includes the initial delimiter
12122 BlockCommentWithStart(BlockCommentConfig),
12123 /// a single line of a block comment that includes the ending delimiter
12124 BlockCommentWithEnd(BlockCommentConfig),
12125 }
12126
12127 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12128 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12129 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12130 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12131 .peekable();
12132
12133 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12134 row
12135 } else {
12136 return Vec::new();
12137 };
12138
12139 let language_settings = buffer.language_settings_at(selection.head(), cx);
12140 let language_scope = buffer.language_scope_at(selection.head());
12141
12142 let indent_and_prefix_for_row =
12143 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12144 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12145 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12146 &language_scope
12147 {
12148 let indent_end = Point::new(row, indent.len);
12149 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12150 let line_text_after_indent = buffer
12151 .text_for_range(indent_end..line_end)
12152 .collect::<String>();
12153
12154 let is_within_comment_override = buffer
12155 .language_scope_at(indent_end)
12156 .is_some_and(|scope| scope.override_name() == Some("comment"));
12157 let comment_delimiters = if is_within_comment_override {
12158 // we are within a comment syntax node, but we don't
12159 // yet know what kind of comment: block, doc or line
12160 match (
12161 language_scope.documentation_comment(),
12162 language_scope.block_comment(),
12163 ) {
12164 (Some(config), _) | (_, Some(config))
12165 if buffer.contains_str_at(indent_end, &config.start) =>
12166 {
12167 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12168 }
12169 (Some(config), _) | (_, Some(config))
12170 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12171 {
12172 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12173 }
12174 (Some(config), _) | (_, Some(config))
12175 if buffer.contains_str_at(indent_end, &config.prefix) =>
12176 {
12177 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12178 }
12179 (_, _) => language_scope
12180 .line_comment_prefixes()
12181 .iter()
12182 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12183 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12184 }
12185 } else {
12186 // we not in an overridden comment node, but we may
12187 // be within a non-overridden line comment node
12188 language_scope
12189 .line_comment_prefixes()
12190 .iter()
12191 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12192 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12193 };
12194
12195 let rewrap_prefix = language_scope
12196 .rewrap_prefixes()
12197 .iter()
12198 .find_map(|prefix_regex| {
12199 prefix_regex.find(&line_text_after_indent).map(|mat| {
12200 if mat.start() == 0 {
12201 Some(mat.as_str().to_string())
12202 } else {
12203 None
12204 }
12205 })
12206 })
12207 .flatten();
12208 (comment_delimiters, rewrap_prefix)
12209 } else {
12210 (None, None)
12211 };
12212 (indent, comment_prefix, rewrap_prefix)
12213 };
12214
12215 let mut ranges = Vec::new();
12216 let from_empty_selection = selection.is_empty();
12217
12218 let mut current_range_start = first_row;
12219 let mut prev_row = first_row;
12220 let (
12221 mut current_range_indent,
12222 mut current_range_comment_delimiters,
12223 mut current_range_rewrap_prefix,
12224 ) = indent_and_prefix_for_row(first_row);
12225
12226 for row in non_blank_rows_iter.skip(1) {
12227 let has_paragraph_break = row > prev_row + 1;
12228
12229 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12230 indent_and_prefix_for_row(row);
12231
12232 let has_indent_change = row_indent != current_range_indent;
12233 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12234
12235 let has_boundary_change = has_comment_change
12236 || row_rewrap_prefix.is_some()
12237 || (has_indent_change && current_range_comment_delimiters.is_some());
12238
12239 if has_paragraph_break || has_boundary_change {
12240 ranges.push((
12241 language_settings.clone(),
12242 Point::new(current_range_start, 0)
12243 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12244 current_range_indent,
12245 current_range_comment_delimiters.clone(),
12246 current_range_rewrap_prefix.clone(),
12247 from_empty_selection,
12248 ));
12249 current_range_start = row;
12250 current_range_indent = row_indent;
12251 current_range_comment_delimiters = row_comment_delimiters;
12252 current_range_rewrap_prefix = row_rewrap_prefix;
12253 }
12254 prev_row = row;
12255 }
12256
12257 ranges.push((
12258 language_settings.clone(),
12259 Point::new(current_range_start, 0)
12260 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12261 current_range_indent,
12262 current_range_comment_delimiters,
12263 current_range_rewrap_prefix,
12264 from_empty_selection,
12265 ));
12266
12267 ranges
12268 });
12269
12270 let mut edits = Vec::new();
12271 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12272
12273 for (
12274 language_settings,
12275 wrap_range,
12276 mut indent_size,
12277 comment_prefix,
12278 rewrap_prefix,
12279 from_empty_selection,
12280 ) in wrap_ranges
12281 {
12282 let mut start_row = wrap_range.start.row;
12283 let mut end_row = wrap_range.end.row;
12284
12285 // Skip selections that overlap with a range that has already been rewrapped.
12286 let selection_range = start_row..end_row;
12287 if rewrapped_row_ranges
12288 .iter()
12289 .any(|range| range.overlaps(&selection_range))
12290 {
12291 continue;
12292 }
12293
12294 let tab_size = language_settings.tab_size;
12295
12296 let (line_prefix, inside_comment) = match &comment_prefix {
12297 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12298 (Some(prefix.as_str()), true)
12299 }
12300 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12301 (Some(prefix.as_ref()), true)
12302 }
12303 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12304 start: _,
12305 end: _,
12306 prefix,
12307 tab_size,
12308 })) => {
12309 indent_size.len += tab_size;
12310 (Some(prefix.as_ref()), true)
12311 }
12312 None => (None, false),
12313 };
12314 let indent_prefix = indent_size.chars().collect::<String>();
12315 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12316
12317 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12318 RewrapBehavior::InComments => inside_comment,
12319 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12320 RewrapBehavior::Anywhere => true,
12321 };
12322
12323 let should_rewrap = options.override_language_settings
12324 || allow_rewrap_based_on_language
12325 || self.hard_wrap.is_some();
12326 if !should_rewrap {
12327 continue;
12328 }
12329
12330 if from_empty_selection {
12331 'expand_upwards: while start_row > 0 {
12332 let prev_row = start_row - 1;
12333 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12334 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12335 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12336 {
12337 start_row = prev_row;
12338 } else {
12339 break 'expand_upwards;
12340 }
12341 }
12342
12343 'expand_downwards: while end_row < buffer.max_point().row {
12344 let next_row = end_row + 1;
12345 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12346 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12347 && !buffer.is_line_blank(MultiBufferRow(next_row))
12348 {
12349 end_row = next_row;
12350 } else {
12351 break 'expand_downwards;
12352 }
12353 }
12354 }
12355
12356 let start = Point::new(start_row, 0);
12357 let start_offset = ToOffset::to_offset(&start, &buffer);
12358 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12359 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12360 let mut first_line_delimiter = None;
12361 let mut last_line_delimiter = None;
12362 let Some(lines_without_prefixes) = selection_text
12363 .lines()
12364 .enumerate()
12365 .map(|(ix, line)| {
12366 let line_trimmed = line.trim_start();
12367 if rewrap_prefix.is_some() && ix > 0 {
12368 Ok(line_trimmed)
12369 } else if let Some(
12370 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12371 start,
12372 prefix,
12373 end,
12374 tab_size,
12375 })
12376 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12377 start,
12378 prefix,
12379 end,
12380 tab_size,
12381 }),
12382 ) = &comment_prefix
12383 {
12384 let line_trimmed = line_trimmed
12385 .strip_prefix(start.as_ref())
12386 .map(|s| {
12387 let mut indent_size = indent_size;
12388 indent_size.len -= tab_size;
12389 let indent_prefix: String = indent_size.chars().collect();
12390 first_line_delimiter = Some((indent_prefix, start));
12391 s.trim_start()
12392 })
12393 .unwrap_or(line_trimmed);
12394 let line_trimmed = line_trimmed
12395 .strip_suffix(end.as_ref())
12396 .map(|s| {
12397 last_line_delimiter = Some(end);
12398 s.trim_end()
12399 })
12400 .unwrap_or(line_trimmed);
12401 let line_trimmed = line_trimmed
12402 .strip_prefix(prefix.as_ref())
12403 .unwrap_or(line_trimmed);
12404 Ok(line_trimmed)
12405 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12406 line_trimmed.strip_prefix(prefix).with_context(|| {
12407 format!("line did not start with prefix {prefix:?}: {line:?}")
12408 })
12409 } else {
12410 line_trimmed
12411 .strip_prefix(&line_prefix.trim_start())
12412 .with_context(|| {
12413 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12414 })
12415 }
12416 })
12417 .collect::<Result<Vec<_>, _>>()
12418 .log_err()
12419 else {
12420 continue;
12421 };
12422
12423 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12424 buffer
12425 .language_settings_at(Point::new(start_row, 0), cx)
12426 .preferred_line_length as usize
12427 });
12428
12429 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12430 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12431 } else {
12432 line_prefix.clone()
12433 };
12434
12435 let wrapped_text = {
12436 let mut wrapped_text = wrap_with_prefix(
12437 line_prefix,
12438 subsequent_lines_prefix,
12439 lines_without_prefixes.join("\n"),
12440 wrap_column,
12441 tab_size,
12442 options.preserve_existing_whitespace,
12443 );
12444
12445 if let Some((indent, delimiter)) = first_line_delimiter {
12446 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12447 }
12448 if let Some(last_line) = last_line_delimiter {
12449 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12450 }
12451
12452 wrapped_text
12453 };
12454
12455 // TODO: should always use char-based diff while still supporting cursor behavior that
12456 // matches vim.
12457 let mut diff_options = DiffOptions::default();
12458 if options.override_language_settings {
12459 diff_options.max_word_diff_len = 0;
12460 diff_options.max_word_diff_line_count = 0;
12461 } else {
12462 diff_options.max_word_diff_len = usize::MAX;
12463 diff_options.max_word_diff_line_count = usize::MAX;
12464 }
12465
12466 for (old_range, new_text) in
12467 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12468 {
12469 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12470 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12471 edits.push((edit_start..edit_end, new_text));
12472 }
12473
12474 rewrapped_row_ranges.push(start_row..=end_row);
12475 }
12476
12477 self.buffer
12478 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12479 }
12480
12481 pub fn cut_common(
12482 &mut self,
12483 cut_no_selection_line: bool,
12484 window: &mut Window,
12485 cx: &mut Context<Self>,
12486 ) -> ClipboardItem {
12487 let mut text = String::new();
12488 let buffer = self.buffer.read(cx).snapshot(cx);
12489 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12490 let mut clipboard_selections = Vec::with_capacity(selections.len());
12491 {
12492 let max_point = buffer.max_point();
12493 let mut is_first = true;
12494 for selection in &mut selections {
12495 let is_entire_line =
12496 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12497 if is_entire_line {
12498 selection.start = Point::new(selection.start.row, 0);
12499 if !selection.is_empty() && selection.end.column == 0 {
12500 selection.end = cmp::min(max_point, selection.end);
12501 } else {
12502 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12503 }
12504 selection.goal = SelectionGoal::None;
12505 }
12506 if is_first {
12507 is_first = false;
12508 } else {
12509 text += "\n";
12510 }
12511 let mut len = 0;
12512 for chunk in buffer.text_for_range(selection.start..selection.end) {
12513 text.push_str(chunk);
12514 len += chunk.len();
12515 }
12516 clipboard_selections.push(ClipboardSelection {
12517 len,
12518 is_entire_line,
12519 first_line_indent: buffer
12520 .indent_size_for_line(MultiBufferRow(selection.start.row))
12521 .len,
12522 });
12523 }
12524 }
12525
12526 self.transact(window, cx, |this, window, cx| {
12527 this.change_selections(Default::default(), window, cx, |s| {
12528 s.select(selections);
12529 });
12530 this.insert("", window, cx);
12531 });
12532 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12533 }
12534
12535 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12536 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12537 let item = self.cut_common(true, window, cx);
12538 cx.write_to_clipboard(item);
12539 }
12540
12541 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12542 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12543 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12544 s.move_with(|snapshot, sel| {
12545 if sel.is_empty() {
12546 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12547 }
12548 if sel.is_empty() {
12549 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12550 }
12551 });
12552 });
12553 let item = self.cut_common(false, window, cx);
12554 cx.set_global(KillRing(item))
12555 }
12556
12557 pub fn kill_ring_yank(
12558 &mut self,
12559 _: &KillRingYank,
12560 window: &mut Window,
12561 cx: &mut Context<Self>,
12562 ) {
12563 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12564 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12565 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12566 (kill_ring.text().to_string(), kill_ring.metadata_json())
12567 } else {
12568 return;
12569 }
12570 } else {
12571 return;
12572 };
12573 self.do_paste(&text, metadata, false, window, cx);
12574 }
12575
12576 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12577 self.do_copy(true, cx);
12578 }
12579
12580 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12581 self.do_copy(false, cx);
12582 }
12583
12584 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12585 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12586 let buffer = self.buffer.read(cx).read(cx);
12587 let mut text = String::new();
12588
12589 let mut clipboard_selections = Vec::with_capacity(selections.len());
12590 {
12591 let max_point = buffer.max_point();
12592 let mut is_first = true;
12593 for selection in &selections {
12594 let mut start = selection.start;
12595 let mut end = selection.end;
12596 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12597 let mut add_trailing_newline = false;
12598 if is_entire_line {
12599 start = Point::new(start.row, 0);
12600 let next_line_start = Point::new(end.row + 1, 0);
12601 if next_line_start <= max_point {
12602 end = next_line_start;
12603 } else {
12604 // We're on the last line without a trailing newline.
12605 // Copy to the end of the line and add a newline afterwards.
12606 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12607 add_trailing_newline = true;
12608 }
12609 }
12610
12611 let mut trimmed_selections = Vec::new();
12612 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12613 let row = MultiBufferRow(start.row);
12614 let first_indent = buffer.indent_size_for_line(row);
12615 if first_indent.len == 0 || start.column > first_indent.len {
12616 trimmed_selections.push(start..end);
12617 } else {
12618 trimmed_selections.push(
12619 Point::new(row.0, first_indent.len)
12620 ..Point::new(row.0, buffer.line_len(row)),
12621 );
12622 for row in start.row + 1..=end.row {
12623 let mut line_len = buffer.line_len(MultiBufferRow(row));
12624 if row == end.row {
12625 line_len = end.column;
12626 }
12627 if line_len == 0 {
12628 trimmed_selections
12629 .push(Point::new(row, 0)..Point::new(row, line_len));
12630 continue;
12631 }
12632 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12633 if row_indent_size.len >= first_indent.len {
12634 trimmed_selections.push(
12635 Point::new(row, first_indent.len)..Point::new(row, line_len),
12636 );
12637 } else {
12638 trimmed_selections.clear();
12639 trimmed_selections.push(start..end);
12640 break;
12641 }
12642 }
12643 }
12644 } else {
12645 trimmed_selections.push(start..end);
12646 }
12647
12648 for trimmed_range in trimmed_selections {
12649 if is_first {
12650 is_first = false;
12651 } else {
12652 text += "\n";
12653 }
12654 let mut len = 0;
12655 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12656 text.push_str(chunk);
12657 len += chunk.len();
12658 }
12659 if add_trailing_newline {
12660 text.push('\n');
12661 len += 1;
12662 }
12663 clipboard_selections.push(ClipboardSelection {
12664 len,
12665 is_entire_line,
12666 first_line_indent: buffer
12667 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12668 .len,
12669 });
12670 }
12671 }
12672 }
12673
12674 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12675 text,
12676 clipboard_selections,
12677 ));
12678 }
12679
12680 pub fn do_paste(
12681 &mut self,
12682 text: &String,
12683 clipboard_selections: Option<Vec<ClipboardSelection>>,
12684 handle_entire_lines: bool,
12685 window: &mut Window,
12686 cx: &mut Context<Self>,
12687 ) {
12688 if self.read_only(cx) {
12689 return;
12690 }
12691
12692 let clipboard_text = Cow::Borrowed(text.as_str());
12693
12694 self.transact(window, cx, |this, window, cx| {
12695 let had_active_edit_prediction = this.has_active_edit_prediction();
12696 let display_map = this.display_snapshot(cx);
12697 let old_selections = this.selections.all::<usize>(&display_map);
12698 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12699
12700 if let Some(mut clipboard_selections) = clipboard_selections {
12701 let all_selections_were_entire_line =
12702 clipboard_selections.iter().all(|s| s.is_entire_line);
12703 let first_selection_indent_column =
12704 clipboard_selections.first().map(|s| s.first_line_indent);
12705 if clipboard_selections.len() != old_selections.len() {
12706 clipboard_selections.drain(..);
12707 }
12708 let mut auto_indent_on_paste = true;
12709
12710 this.buffer.update(cx, |buffer, cx| {
12711 let snapshot = buffer.read(cx);
12712 auto_indent_on_paste = snapshot
12713 .language_settings_at(cursor_offset, cx)
12714 .auto_indent_on_paste;
12715
12716 let mut start_offset = 0;
12717 let mut edits = Vec::new();
12718 let mut original_indent_columns = Vec::new();
12719 for (ix, selection) in old_selections.iter().enumerate() {
12720 let to_insert;
12721 let entire_line;
12722 let original_indent_column;
12723 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12724 let end_offset = start_offset + clipboard_selection.len;
12725 to_insert = &clipboard_text[start_offset..end_offset];
12726 entire_line = clipboard_selection.is_entire_line;
12727 start_offset = end_offset + 1;
12728 original_indent_column = Some(clipboard_selection.first_line_indent);
12729 } else {
12730 to_insert = &*clipboard_text;
12731 entire_line = all_selections_were_entire_line;
12732 original_indent_column = first_selection_indent_column
12733 }
12734
12735 let (range, to_insert) =
12736 if selection.is_empty() && handle_entire_lines && entire_line {
12737 // If the corresponding selection was empty when this slice of the
12738 // clipboard text was written, then the entire line containing the
12739 // selection was copied. If this selection is also currently empty,
12740 // then paste the line before the current line of the buffer.
12741 let column = selection.start.to_point(&snapshot).column as usize;
12742 let line_start = selection.start - column;
12743 (line_start..line_start, Cow::Borrowed(to_insert))
12744 } else {
12745 let language = snapshot.language_at(selection.head());
12746 let range = selection.range();
12747 if let Some(language) = language
12748 && language.name() == "Markdown".into()
12749 {
12750 edit_for_markdown_paste(
12751 &snapshot,
12752 range,
12753 to_insert,
12754 url::Url::parse(to_insert).ok(),
12755 )
12756 } else {
12757 (range, Cow::Borrowed(to_insert))
12758 }
12759 };
12760
12761 edits.push((range, to_insert));
12762 original_indent_columns.push(original_indent_column);
12763 }
12764 drop(snapshot);
12765
12766 buffer.edit(
12767 edits,
12768 if auto_indent_on_paste {
12769 Some(AutoindentMode::Block {
12770 original_indent_columns,
12771 })
12772 } else {
12773 None
12774 },
12775 cx,
12776 );
12777 });
12778
12779 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12780 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12781 } else {
12782 let url = url::Url::parse(&clipboard_text).ok();
12783
12784 let auto_indent_mode = if !clipboard_text.is_empty() {
12785 Some(AutoindentMode::Block {
12786 original_indent_columns: Vec::new(),
12787 })
12788 } else {
12789 None
12790 };
12791
12792 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12793 let snapshot = buffer.snapshot(cx);
12794
12795 let anchors = old_selections
12796 .iter()
12797 .map(|s| {
12798 let anchor = snapshot.anchor_after(s.head());
12799 s.map(|_| anchor)
12800 })
12801 .collect::<Vec<_>>();
12802
12803 let mut edits = Vec::new();
12804
12805 for selection in old_selections.iter() {
12806 let language = snapshot.language_at(selection.head());
12807 let range = selection.range();
12808
12809 let (edit_range, edit_text) = if let Some(language) = language
12810 && language.name() == "Markdown".into()
12811 {
12812 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12813 } else {
12814 (range, clipboard_text.clone())
12815 };
12816
12817 edits.push((edit_range, edit_text));
12818 }
12819
12820 drop(snapshot);
12821 buffer.edit(edits, auto_indent_mode, cx);
12822
12823 anchors
12824 });
12825
12826 this.change_selections(Default::default(), window, cx, |s| {
12827 s.select_anchors(selection_anchors);
12828 });
12829 }
12830
12831 let trigger_in_words =
12832 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12833
12834 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12835 });
12836 }
12837
12838 pub fn diff_clipboard_with_selection(
12839 &mut self,
12840 _: &DiffClipboardWithSelection,
12841 window: &mut Window,
12842 cx: &mut Context<Self>,
12843 ) {
12844 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12845
12846 if selections.is_empty() {
12847 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12848 return;
12849 };
12850
12851 let clipboard_text = match cx.read_from_clipboard() {
12852 Some(item) => match item.entries().first() {
12853 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12854 _ => None,
12855 },
12856 None => None,
12857 };
12858
12859 let Some(clipboard_text) = clipboard_text else {
12860 log::warn!("Clipboard doesn't contain text.");
12861 return;
12862 };
12863
12864 window.dispatch_action(
12865 Box::new(DiffClipboardWithSelectionData {
12866 clipboard_text,
12867 editor: cx.entity(),
12868 }),
12869 cx,
12870 );
12871 }
12872
12873 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12875 if let Some(item) = cx.read_from_clipboard() {
12876 let entries = item.entries();
12877
12878 match entries.first() {
12879 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12880 // of all the pasted entries.
12881 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12882 .do_paste(
12883 clipboard_string.text(),
12884 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12885 true,
12886 window,
12887 cx,
12888 ),
12889 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12890 }
12891 }
12892 }
12893
12894 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12895 if self.read_only(cx) {
12896 return;
12897 }
12898
12899 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12900
12901 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12902 if let Some((selections, _)) =
12903 self.selection_history.transaction(transaction_id).cloned()
12904 {
12905 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12906 s.select_anchors(selections.to_vec());
12907 });
12908 } else {
12909 log::error!(
12910 "No entry in selection_history found for undo. \
12911 This may correspond to a bug where undo does not update the selection. \
12912 If this is occurring, please add details to \
12913 https://github.com/zed-industries/zed/issues/22692"
12914 );
12915 }
12916 self.request_autoscroll(Autoscroll::fit(), cx);
12917 self.unmark_text(window, cx);
12918 self.refresh_edit_prediction(true, false, window, cx);
12919 cx.emit(EditorEvent::Edited { transaction_id });
12920 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12921 }
12922 }
12923
12924 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12925 if self.read_only(cx) {
12926 return;
12927 }
12928
12929 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12930
12931 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12932 if let Some((_, Some(selections))) =
12933 self.selection_history.transaction(transaction_id).cloned()
12934 {
12935 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12936 s.select_anchors(selections.to_vec());
12937 });
12938 } else {
12939 log::error!(
12940 "No entry in selection_history found for redo. \
12941 This may correspond to a bug where undo does not update the selection. \
12942 If this is occurring, please add details to \
12943 https://github.com/zed-industries/zed/issues/22692"
12944 );
12945 }
12946 self.request_autoscroll(Autoscroll::fit(), cx);
12947 self.unmark_text(window, cx);
12948 self.refresh_edit_prediction(true, false, window, cx);
12949 cx.emit(EditorEvent::Edited { transaction_id });
12950 }
12951 }
12952
12953 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12954 self.buffer
12955 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12956 }
12957
12958 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12959 self.buffer
12960 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12961 }
12962
12963 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12965 self.change_selections(Default::default(), window, cx, |s| {
12966 s.move_with(|map, selection| {
12967 let cursor = if selection.is_empty() {
12968 movement::left(map, selection.start)
12969 } else {
12970 selection.start
12971 };
12972 selection.collapse_to(cursor, SelectionGoal::None);
12973 });
12974 })
12975 }
12976
12977 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
12978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12979 self.change_selections(Default::default(), window, cx, |s| {
12980 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
12981 })
12982 }
12983
12984 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
12985 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
12986 self.change_selections(Default::default(), window, cx, |s| {
12987 s.move_with(|map, selection| {
12988 let cursor = if selection.is_empty() {
12989 movement::right(map, selection.end)
12990 } else {
12991 selection.end
12992 };
12993 selection.collapse_to(cursor, SelectionGoal::None)
12994 });
12995 })
12996 }
12997
12998 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
12999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13000 self.change_selections(Default::default(), window, cx, |s| {
13001 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13002 });
13003 }
13004
13005 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13006 if self.take_rename(true, window, cx).is_some() {
13007 return;
13008 }
13009
13010 if self.mode.is_single_line() {
13011 cx.propagate();
13012 return;
13013 }
13014
13015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13016
13017 let text_layout_details = &self.text_layout_details(window);
13018 let selection_count = self.selections.count();
13019 let first_selection = self.selections.first_anchor();
13020
13021 self.change_selections(Default::default(), window, cx, |s| {
13022 s.move_with(|map, selection| {
13023 if !selection.is_empty() {
13024 selection.goal = SelectionGoal::None;
13025 }
13026 let (cursor, goal) = movement::up(
13027 map,
13028 selection.start,
13029 selection.goal,
13030 false,
13031 text_layout_details,
13032 );
13033 selection.collapse_to(cursor, goal);
13034 });
13035 });
13036
13037 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13038 {
13039 cx.propagate();
13040 }
13041 }
13042
13043 pub fn move_up_by_lines(
13044 &mut self,
13045 action: &MoveUpByLines,
13046 window: &mut Window,
13047 cx: &mut Context<Self>,
13048 ) {
13049 if self.take_rename(true, window, cx).is_some() {
13050 return;
13051 }
13052
13053 if self.mode.is_single_line() {
13054 cx.propagate();
13055 return;
13056 }
13057
13058 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13059
13060 let text_layout_details = &self.text_layout_details(window);
13061
13062 self.change_selections(Default::default(), window, cx, |s| {
13063 s.move_with(|map, selection| {
13064 if !selection.is_empty() {
13065 selection.goal = SelectionGoal::None;
13066 }
13067 let (cursor, goal) = movement::up_by_rows(
13068 map,
13069 selection.start,
13070 action.lines,
13071 selection.goal,
13072 false,
13073 text_layout_details,
13074 );
13075 selection.collapse_to(cursor, goal);
13076 });
13077 })
13078 }
13079
13080 pub fn move_down_by_lines(
13081 &mut self,
13082 action: &MoveDownByLines,
13083 window: &mut Window,
13084 cx: &mut Context<Self>,
13085 ) {
13086 if self.take_rename(true, window, cx).is_some() {
13087 return;
13088 }
13089
13090 if self.mode.is_single_line() {
13091 cx.propagate();
13092 return;
13093 }
13094
13095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13096
13097 let text_layout_details = &self.text_layout_details(window);
13098
13099 self.change_selections(Default::default(), window, cx, |s| {
13100 s.move_with(|map, selection| {
13101 if !selection.is_empty() {
13102 selection.goal = SelectionGoal::None;
13103 }
13104 let (cursor, goal) = movement::down_by_rows(
13105 map,
13106 selection.start,
13107 action.lines,
13108 selection.goal,
13109 false,
13110 text_layout_details,
13111 );
13112 selection.collapse_to(cursor, goal);
13113 });
13114 })
13115 }
13116
13117 pub fn select_down_by_lines(
13118 &mut self,
13119 action: &SelectDownByLines,
13120 window: &mut Window,
13121 cx: &mut Context<Self>,
13122 ) {
13123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13124 let text_layout_details = &self.text_layout_details(window);
13125 self.change_selections(Default::default(), window, cx, |s| {
13126 s.move_heads_with(|map, head, goal| {
13127 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13128 })
13129 })
13130 }
13131
13132 pub fn select_up_by_lines(
13133 &mut self,
13134 action: &SelectUpByLines,
13135 window: &mut Window,
13136 cx: &mut Context<Self>,
13137 ) {
13138 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13139 let text_layout_details = &self.text_layout_details(window);
13140 self.change_selections(Default::default(), window, cx, |s| {
13141 s.move_heads_with(|map, head, goal| {
13142 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13143 })
13144 })
13145 }
13146
13147 pub fn select_page_up(
13148 &mut self,
13149 _: &SelectPageUp,
13150 window: &mut Window,
13151 cx: &mut Context<Self>,
13152 ) {
13153 let Some(row_count) = self.visible_row_count() else {
13154 return;
13155 };
13156
13157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13158
13159 let text_layout_details = &self.text_layout_details(window);
13160
13161 self.change_selections(Default::default(), window, cx, |s| {
13162 s.move_heads_with(|map, head, goal| {
13163 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13164 })
13165 })
13166 }
13167
13168 pub fn move_page_up(
13169 &mut self,
13170 action: &MovePageUp,
13171 window: &mut Window,
13172 cx: &mut Context<Self>,
13173 ) {
13174 if self.take_rename(true, window, cx).is_some() {
13175 return;
13176 }
13177
13178 if self
13179 .context_menu
13180 .borrow_mut()
13181 .as_mut()
13182 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13183 .unwrap_or(false)
13184 {
13185 return;
13186 }
13187
13188 if matches!(self.mode, EditorMode::SingleLine) {
13189 cx.propagate();
13190 return;
13191 }
13192
13193 let Some(row_count) = self.visible_row_count() else {
13194 return;
13195 };
13196
13197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13198
13199 let effects = if action.center_cursor {
13200 SelectionEffects::scroll(Autoscroll::center())
13201 } else {
13202 SelectionEffects::default()
13203 };
13204
13205 let text_layout_details = &self.text_layout_details(window);
13206
13207 self.change_selections(effects, window, cx, |s| {
13208 s.move_with(|map, selection| {
13209 if !selection.is_empty() {
13210 selection.goal = SelectionGoal::None;
13211 }
13212 let (cursor, goal) = movement::up_by_rows(
13213 map,
13214 selection.end,
13215 row_count,
13216 selection.goal,
13217 false,
13218 text_layout_details,
13219 );
13220 selection.collapse_to(cursor, goal);
13221 });
13222 });
13223 }
13224
13225 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13226 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13227 let text_layout_details = &self.text_layout_details(window);
13228 self.change_selections(Default::default(), window, cx, |s| {
13229 s.move_heads_with(|map, head, goal| {
13230 movement::up(map, head, goal, false, text_layout_details)
13231 })
13232 })
13233 }
13234
13235 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13236 self.take_rename(true, window, cx);
13237
13238 if self.mode.is_single_line() {
13239 cx.propagate();
13240 return;
13241 }
13242
13243 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13244
13245 let text_layout_details = &self.text_layout_details(window);
13246 let selection_count = self.selections.count();
13247 let first_selection = self.selections.first_anchor();
13248
13249 self.change_selections(Default::default(), window, cx, |s| {
13250 s.move_with(|map, selection| {
13251 if !selection.is_empty() {
13252 selection.goal = SelectionGoal::None;
13253 }
13254 let (cursor, goal) = movement::down(
13255 map,
13256 selection.end,
13257 selection.goal,
13258 false,
13259 text_layout_details,
13260 );
13261 selection.collapse_to(cursor, goal);
13262 });
13263 });
13264
13265 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13266 {
13267 cx.propagate();
13268 }
13269 }
13270
13271 pub fn select_page_down(
13272 &mut self,
13273 _: &SelectPageDown,
13274 window: &mut Window,
13275 cx: &mut Context<Self>,
13276 ) {
13277 let Some(row_count) = self.visible_row_count() else {
13278 return;
13279 };
13280
13281 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13282
13283 let text_layout_details = &self.text_layout_details(window);
13284
13285 self.change_selections(Default::default(), window, cx, |s| {
13286 s.move_heads_with(|map, head, goal| {
13287 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13288 })
13289 })
13290 }
13291
13292 pub fn move_page_down(
13293 &mut self,
13294 action: &MovePageDown,
13295 window: &mut Window,
13296 cx: &mut Context<Self>,
13297 ) {
13298 if self.take_rename(true, window, cx).is_some() {
13299 return;
13300 }
13301
13302 if self
13303 .context_menu
13304 .borrow_mut()
13305 .as_mut()
13306 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13307 .unwrap_or(false)
13308 {
13309 return;
13310 }
13311
13312 if matches!(self.mode, EditorMode::SingleLine) {
13313 cx.propagate();
13314 return;
13315 }
13316
13317 let Some(row_count) = self.visible_row_count() else {
13318 return;
13319 };
13320
13321 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13322
13323 let effects = if action.center_cursor {
13324 SelectionEffects::scroll(Autoscroll::center())
13325 } else {
13326 SelectionEffects::default()
13327 };
13328
13329 let text_layout_details = &self.text_layout_details(window);
13330 self.change_selections(effects, window, cx, |s| {
13331 s.move_with(|map, selection| {
13332 if !selection.is_empty() {
13333 selection.goal = SelectionGoal::None;
13334 }
13335 let (cursor, goal) = movement::down_by_rows(
13336 map,
13337 selection.end,
13338 row_count,
13339 selection.goal,
13340 false,
13341 text_layout_details,
13342 );
13343 selection.collapse_to(cursor, goal);
13344 });
13345 });
13346 }
13347
13348 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13349 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13350 let text_layout_details = &self.text_layout_details(window);
13351 self.change_selections(Default::default(), window, cx, |s| {
13352 s.move_heads_with(|map, head, goal| {
13353 movement::down(map, head, goal, false, text_layout_details)
13354 })
13355 });
13356 }
13357
13358 pub fn context_menu_first(
13359 &mut self,
13360 _: &ContextMenuFirst,
13361 window: &mut Window,
13362 cx: &mut Context<Self>,
13363 ) {
13364 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13365 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13366 }
13367 }
13368
13369 pub fn context_menu_prev(
13370 &mut self,
13371 _: &ContextMenuPrevious,
13372 window: &mut Window,
13373 cx: &mut Context<Self>,
13374 ) {
13375 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13376 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13377 }
13378 }
13379
13380 pub fn context_menu_next(
13381 &mut self,
13382 _: &ContextMenuNext,
13383 window: &mut Window,
13384 cx: &mut Context<Self>,
13385 ) {
13386 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13387 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13388 }
13389 }
13390
13391 pub fn context_menu_last(
13392 &mut self,
13393 _: &ContextMenuLast,
13394 window: &mut Window,
13395 cx: &mut Context<Self>,
13396 ) {
13397 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13398 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13399 }
13400 }
13401
13402 pub fn signature_help_prev(
13403 &mut self,
13404 _: &SignatureHelpPrevious,
13405 _: &mut Window,
13406 cx: &mut Context<Self>,
13407 ) {
13408 if let Some(popover) = self.signature_help_state.popover_mut() {
13409 if popover.current_signature == 0 {
13410 popover.current_signature = popover.signatures.len() - 1;
13411 } else {
13412 popover.current_signature -= 1;
13413 }
13414 cx.notify();
13415 }
13416 }
13417
13418 pub fn signature_help_next(
13419 &mut self,
13420 _: &SignatureHelpNext,
13421 _: &mut Window,
13422 cx: &mut Context<Self>,
13423 ) {
13424 if let Some(popover) = self.signature_help_state.popover_mut() {
13425 if popover.current_signature + 1 == popover.signatures.len() {
13426 popover.current_signature = 0;
13427 } else {
13428 popover.current_signature += 1;
13429 }
13430 cx.notify();
13431 }
13432 }
13433
13434 pub fn move_to_previous_word_start(
13435 &mut self,
13436 _: &MoveToPreviousWordStart,
13437 window: &mut Window,
13438 cx: &mut Context<Self>,
13439 ) {
13440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13441 self.change_selections(Default::default(), window, cx, |s| {
13442 s.move_cursors_with(|map, head, _| {
13443 (
13444 movement::previous_word_start(map, head),
13445 SelectionGoal::None,
13446 )
13447 });
13448 })
13449 }
13450
13451 pub fn move_to_previous_subword_start(
13452 &mut self,
13453 _: &MoveToPreviousSubwordStart,
13454 window: &mut Window,
13455 cx: &mut Context<Self>,
13456 ) {
13457 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13458 self.change_selections(Default::default(), window, cx, |s| {
13459 s.move_cursors_with(|map, head, _| {
13460 (
13461 movement::previous_subword_start(map, head),
13462 SelectionGoal::None,
13463 )
13464 });
13465 })
13466 }
13467
13468 pub fn select_to_previous_word_start(
13469 &mut self,
13470 _: &SelectToPreviousWordStart,
13471 window: &mut Window,
13472 cx: &mut Context<Self>,
13473 ) {
13474 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13475 self.change_selections(Default::default(), window, cx, |s| {
13476 s.move_heads_with(|map, head, _| {
13477 (
13478 movement::previous_word_start(map, head),
13479 SelectionGoal::None,
13480 )
13481 });
13482 })
13483 }
13484
13485 pub fn select_to_previous_subword_start(
13486 &mut self,
13487 _: &SelectToPreviousSubwordStart,
13488 window: &mut Window,
13489 cx: &mut Context<Self>,
13490 ) {
13491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13492 self.change_selections(Default::default(), window, cx, |s| {
13493 s.move_heads_with(|map, head, _| {
13494 (
13495 movement::previous_subword_start(map, head),
13496 SelectionGoal::None,
13497 )
13498 });
13499 })
13500 }
13501
13502 pub fn delete_to_previous_word_start(
13503 &mut self,
13504 action: &DeleteToPreviousWordStart,
13505 window: &mut Window,
13506 cx: &mut Context<Self>,
13507 ) {
13508 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13509 self.transact(window, cx, |this, window, cx| {
13510 this.select_autoclose_pair(window, cx);
13511 this.change_selections(Default::default(), window, cx, |s| {
13512 s.move_with(|map, selection| {
13513 if selection.is_empty() {
13514 let mut cursor = if action.ignore_newlines {
13515 movement::previous_word_start(map, selection.head())
13516 } else {
13517 movement::previous_word_start_or_newline(map, selection.head())
13518 };
13519 cursor = movement::adjust_greedy_deletion(
13520 map,
13521 selection.head(),
13522 cursor,
13523 action.ignore_brackets,
13524 );
13525 selection.set_head(cursor, SelectionGoal::None);
13526 }
13527 });
13528 });
13529 this.insert("", window, cx);
13530 });
13531 }
13532
13533 pub fn delete_to_previous_subword_start(
13534 &mut self,
13535 _: &DeleteToPreviousSubwordStart,
13536 window: &mut Window,
13537 cx: &mut Context<Self>,
13538 ) {
13539 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13540 self.transact(window, cx, |this, window, cx| {
13541 this.select_autoclose_pair(window, cx);
13542 this.change_selections(Default::default(), window, cx, |s| {
13543 s.move_with(|map, selection| {
13544 if selection.is_empty() {
13545 let mut cursor = movement::previous_subword_start(map, selection.head());
13546 cursor =
13547 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13548 selection.set_head(cursor, SelectionGoal::None);
13549 }
13550 });
13551 });
13552 this.insert("", window, cx);
13553 });
13554 }
13555
13556 pub fn move_to_next_word_end(
13557 &mut self,
13558 _: &MoveToNextWordEnd,
13559 window: &mut Window,
13560 cx: &mut Context<Self>,
13561 ) {
13562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13563 self.change_selections(Default::default(), window, cx, |s| {
13564 s.move_cursors_with(|map, head, _| {
13565 (movement::next_word_end(map, head), SelectionGoal::None)
13566 });
13567 })
13568 }
13569
13570 pub fn move_to_next_subword_end(
13571 &mut self,
13572 _: &MoveToNextSubwordEnd,
13573 window: &mut Window,
13574 cx: &mut Context<Self>,
13575 ) {
13576 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13577 self.change_selections(Default::default(), window, cx, |s| {
13578 s.move_cursors_with(|map, head, _| {
13579 (movement::next_subword_end(map, head), SelectionGoal::None)
13580 });
13581 })
13582 }
13583
13584 pub fn select_to_next_word_end(
13585 &mut self,
13586 _: &SelectToNextWordEnd,
13587 window: &mut Window,
13588 cx: &mut Context<Self>,
13589 ) {
13590 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13591 self.change_selections(Default::default(), window, cx, |s| {
13592 s.move_heads_with(|map, head, _| {
13593 (movement::next_word_end(map, head), SelectionGoal::None)
13594 });
13595 })
13596 }
13597
13598 pub fn select_to_next_subword_end(
13599 &mut self,
13600 _: &SelectToNextSubwordEnd,
13601 window: &mut Window,
13602 cx: &mut Context<Self>,
13603 ) {
13604 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13605 self.change_selections(Default::default(), window, cx, |s| {
13606 s.move_heads_with(|map, head, _| {
13607 (movement::next_subword_end(map, head), SelectionGoal::None)
13608 });
13609 })
13610 }
13611
13612 pub fn delete_to_next_word_end(
13613 &mut self,
13614 action: &DeleteToNextWordEnd,
13615 window: &mut Window,
13616 cx: &mut Context<Self>,
13617 ) {
13618 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13619 self.transact(window, cx, |this, window, cx| {
13620 this.change_selections(Default::default(), window, cx, |s| {
13621 s.move_with(|map, selection| {
13622 if selection.is_empty() {
13623 let mut cursor = if action.ignore_newlines {
13624 movement::next_word_end(map, selection.head())
13625 } else {
13626 movement::next_word_end_or_newline(map, selection.head())
13627 };
13628 cursor = movement::adjust_greedy_deletion(
13629 map,
13630 selection.head(),
13631 cursor,
13632 action.ignore_brackets,
13633 );
13634 selection.set_head(cursor, SelectionGoal::None);
13635 }
13636 });
13637 });
13638 this.insert("", window, cx);
13639 });
13640 }
13641
13642 pub fn delete_to_next_subword_end(
13643 &mut self,
13644 _: &DeleteToNextSubwordEnd,
13645 window: &mut Window,
13646 cx: &mut Context<Self>,
13647 ) {
13648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13649 self.transact(window, cx, |this, window, cx| {
13650 this.change_selections(Default::default(), window, cx, |s| {
13651 s.move_with(|map, selection| {
13652 if selection.is_empty() {
13653 let mut cursor = movement::next_subword_end(map, selection.head());
13654 cursor =
13655 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13656 selection.set_head(cursor, SelectionGoal::None);
13657 }
13658 });
13659 });
13660 this.insert("", window, cx);
13661 });
13662 }
13663
13664 pub fn move_to_beginning_of_line(
13665 &mut self,
13666 action: &MoveToBeginningOfLine,
13667 window: &mut Window,
13668 cx: &mut Context<Self>,
13669 ) {
13670 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13671 self.change_selections(Default::default(), window, cx, |s| {
13672 s.move_cursors_with(|map, head, _| {
13673 (
13674 movement::indented_line_beginning(
13675 map,
13676 head,
13677 action.stop_at_soft_wraps,
13678 action.stop_at_indent,
13679 ),
13680 SelectionGoal::None,
13681 )
13682 });
13683 })
13684 }
13685
13686 pub fn select_to_beginning_of_line(
13687 &mut self,
13688 action: &SelectToBeginningOfLine,
13689 window: &mut Window,
13690 cx: &mut Context<Self>,
13691 ) {
13692 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13693 self.change_selections(Default::default(), window, cx, |s| {
13694 s.move_heads_with(|map, head, _| {
13695 (
13696 movement::indented_line_beginning(
13697 map,
13698 head,
13699 action.stop_at_soft_wraps,
13700 action.stop_at_indent,
13701 ),
13702 SelectionGoal::None,
13703 )
13704 });
13705 });
13706 }
13707
13708 pub fn delete_to_beginning_of_line(
13709 &mut self,
13710 action: &DeleteToBeginningOfLine,
13711 window: &mut Window,
13712 cx: &mut Context<Self>,
13713 ) {
13714 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13715 self.transact(window, cx, |this, window, cx| {
13716 this.change_selections(Default::default(), window, cx, |s| {
13717 s.move_with(|_, selection| {
13718 selection.reversed = true;
13719 });
13720 });
13721
13722 this.select_to_beginning_of_line(
13723 &SelectToBeginningOfLine {
13724 stop_at_soft_wraps: false,
13725 stop_at_indent: action.stop_at_indent,
13726 },
13727 window,
13728 cx,
13729 );
13730 this.backspace(&Backspace, window, cx);
13731 });
13732 }
13733
13734 pub fn move_to_end_of_line(
13735 &mut self,
13736 action: &MoveToEndOfLine,
13737 window: &mut Window,
13738 cx: &mut Context<Self>,
13739 ) {
13740 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13741 self.change_selections(Default::default(), window, cx, |s| {
13742 s.move_cursors_with(|map, head, _| {
13743 (
13744 movement::line_end(map, head, action.stop_at_soft_wraps),
13745 SelectionGoal::None,
13746 )
13747 });
13748 })
13749 }
13750
13751 pub fn select_to_end_of_line(
13752 &mut self,
13753 action: &SelectToEndOfLine,
13754 window: &mut Window,
13755 cx: &mut Context<Self>,
13756 ) {
13757 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13758 self.change_selections(Default::default(), window, cx, |s| {
13759 s.move_heads_with(|map, head, _| {
13760 (
13761 movement::line_end(map, head, action.stop_at_soft_wraps),
13762 SelectionGoal::None,
13763 )
13764 });
13765 })
13766 }
13767
13768 pub fn delete_to_end_of_line(
13769 &mut self,
13770 _: &DeleteToEndOfLine,
13771 window: &mut Window,
13772 cx: &mut Context<Self>,
13773 ) {
13774 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13775 self.transact(window, cx, |this, window, cx| {
13776 this.select_to_end_of_line(
13777 &SelectToEndOfLine {
13778 stop_at_soft_wraps: false,
13779 },
13780 window,
13781 cx,
13782 );
13783 this.delete(&Delete, window, cx);
13784 });
13785 }
13786
13787 pub fn cut_to_end_of_line(
13788 &mut self,
13789 action: &CutToEndOfLine,
13790 window: &mut Window,
13791 cx: &mut Context<Self>,
13792 ) {
13793 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13794 self.transact(window, cx, |this, window, cx| {
13795 this.select_to_end_of_line(
13796 &SelectToEndOfLine {
13797 stop_at_soft_wraps: false,
13798 },
13799 window,
13800 cx,
13801 );
13802 if !action.stop_at_newlines {
13803 this.change_selections(Default::default(), window, cx, |s| {
13804 s.move_with(|_, sel| {
13805 if sel.is_empty() {
13806 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13807 }
13808 });
13809 });
13810 }
13811 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13812 let item = this.cut_common(false, window, cx);
13813 cx.write_to_clipboard(item);
13814 });
13815 }
13816
13817 pub fn move_to_start_of_paragraph(
13818 &mut self,
13819 _: &MoveToStartOfParagraph,
13820 window: &mut Window,
13821 cx: &mut Context<Self>,
13822 ) {
13823 if matches!(self.mode, EditorMode::SingleLine) {
13824 cx.propagate();
13825 return;
13826 }
13827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13828 self.change_selections(Default::default(), window, cx, |s| {
13829 s.move_with(|map, selection| {
13830 selection.collapse_to(
13831 movement::start_of_paragraph(map, selection.head(), 1),
13832 SelectionGoal::None,
13833 )
13834 });
13835 })
13836 }
13837
13838 pub fn move_to_end_of_paragraph(
13839 &mut self,
13840 _: &MoveToEndOfParagraph,
13841 window: &mut Window,
13842 cx: &mut Context<Self>,
13843 ) {
13844 if matches!(self.mode, EditorMode::SingleLine) {
13845 cx.propagate();
13846 return;
13847 }
13848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13849 self.change_selections(Default::default(), window, cx, |s| {
13850 s.move_with(|map, selection| {
13851 selection.collapse_to(
13852 movement::end_of_paragraph(map, selection.head(), 1),
13853 SelectionGoal::None,
13854 )
13855 });
13856 })
13857 }
13858
13859 pub fn select_to_start_of_paragraph(
13860 &mut self,
13861 _: &SelectToStartOfParagraph,
13862 window: &mut Window,
13863 cx: &mut Context<Self>,
13864 ) {
13865 if matches!(self.mode, EditorMode::SingleLine) {
13866 cx.propagate();
13867 return;
13868 }
13869 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13870 self.change_selections(Default::default(), window, cx, |s| {
13871 s.move_heads_with(|map, head, _| {
13872 (
13873 movement::start_of_paragraph(map, head, 1),
13874 SelectionGoal::None,
13875 )
13876 });
13877 })
13878 }
13879
13880 pub fn select_to_end_of_paragraph(
13881 &mut self,
13882 _: &SelectToEndOfParagraph,
13883 window: &mut Window,
13884 cx: &mut Context<Self>,
13885 ) {
13886 if matches!(self.mode, EditorMode::SingleLine) {
13887 cx.propagate();
13888 return;
13889 }
13890 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13891 self.change_selections(Default::default(), window, cx, |s| {
13892 s.move_heads_with(|map, head, _| {
13893 (
13894 movement::end_of_paragraph(map, head, 1),
13895 SelectionGoal::None,
13896 )
13897 });
13898 })
13899 }
13900
13901 pub fn move_to_start_of_excerpt(
13902 &mut self,
13903 _: &MoveToStartOfExcerpt,
13904 window: &mut Window,
13905 cx: &mut Context<Self>,
13906 ) {
13907 if matches!(self.mode, EditorMode::SingleLine) {
13908 cx.propagate();
13909 return;
13910 }
13911 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13912 self.change_selections(Default::default(), window, cx, |s| {
13913 s.move_with(|map, selection| {
13914 selection.collapse_to(
13915 movement::start_of_excerpt(
13916 map,
13917 selection.head(),
13918 workspace::searchable::Direction::Prev,
13919 ),
13920 SelectionGoal::None,
13921 )
13922 });
13923 })
13924 }
13925
13926 pub fn move_to_start_of_next_excerpt(
13927 &mut self,
13928 _: &MoveToStartOfNextExcerpt,
13929 window: &mut Window,
13930 cx: &mut Context<Self>,
13931 ) {
13932 if matches!(self.mode, EditorMode::SingleLine) {
13933 cx.propagate();
13934 return;
13935 }
13936
13937 self.change_selections(Default::default(), window, cx, |s| {
13938 s.move_with(|map, selection| {
13939 selection.collapse_to(
13940 movement::start_of_excerpt(
13941 map,
13942 selection.head(),
13943 workspace::searchable::Direction::Next,
13944 ),
13945 SelectionGoal::None,
13946 )
13947 });
13948 })
13949 }
13950
13951 pub fn move_to_end_of_excerpt(
13952 &mut self,
13953 _: &MoveToEndOfExcerpt,
13954 window: &mut Window,
13955 cx: &mut Context<Self>,
13956 ) {
13957 if matches!(self.mode, EditorMode::SingleLine) {
13958 cx.propagate();
13959 return;
13960 }
13961 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13962 self.change_selections(Default::default(), window, cx, |s| {
13963 s.move_with(|map, selection| {
13964 selection.collapse_to(
13965 movement::end_of_excerpt(
13966 map,
13967 selection.head(),
13968 workspace::searchable::Direction::Next,
13969 ),
13970 SelectionGoal::None,
13971 )
13972 });
13973 })
13974 }
13975
13976 pub fn move_to_end_of_previous_excerpt(
13977 &mut self,
13978 _: &MoveToEndOfPreviousExcerpt,
13979 window: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 if matches!(self.mode, EditorMode::SingleLine) {
13983 cx.propagate();
13984 return;
13985 }
13986 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13987 self.change_selections(Default::default(), window, cx, |s| {
13988 s.move_with(|map, selection| {
13989 selection.collapse_to(
13990 movement::end_of_excerpt(
13991 map,
13992 selection.head(),
13993 workspace::searchable::Direction::Prev,
13994 ),
13995 SelectionGoal::None,
13996 )
13997 });
13998 })
13999 }
14000
14001 pub fn select_to_start_of_excerpt(
14002 &mut self,
14003 _: &SelectToStartOfExcerpt,
14004 window: &mut Window,
14005 cx: &mut Context<Self>,
14006 ) {
14007 if matches!(self.mode, EditorMode::SingleLine) {
14008 cx.propagate();
14009 return;
14010 }
14011 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14012 self.change_selections(Default::default(), window, cx, |s| {
14013 s.move_heads_with(|map, head, _| {
14014 (
14015 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14016 SelectionGoal::None,
14017 )
14018 });
14019 })
14020 }
14021
14022 pub fn select_to_start_of_next_excerpt(
14023 &mut self,
14024 _: &SelectToStartOfNextExcerpt,
14025 window: &mut Window,
14026 cx: &mut Context<Self>,
14027 ) {
14028 if matches!(self.mode, EditorMode::SingleLine) {
14029 cx.propagate();
14030 return;
14031 }
14032 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14033 self.change_selections(Default::default(), window, cx, |s| {
14034 s.move_heads_with(|map, head, _| {
14035 (
14036 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14037 SelectionGoal::None,
14038 )
14039 });
14040 })
14041 }
14042
14043 pub fn select_to_end_of_excerpt(
14044 &mut self,
14045 _: &SelectToEndOfExcerpt,
14046 window: &mut Window,
14047 cx: &mut Context<Self>,
14048 ) {
14049 if matches!(self.mode, EditorMode::SingleLine) {
14050 cx.propagate();
14051 return;
14052 }
14053 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14054 self.change_selections(Default::default(), window, cx, |s| {
14055 s.move_heads_with(|map, head, _| {
14056 (
14057 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14058 SelectionGoal::None,
14059 )
14060 });
14061 })
14062 }
14063
14064 pub fn select_to_end_of_previous_excerpt(
14065 &mut self,
14066 _: &SelectToEndOfPreviousExcerpt,
14067 window: &mut Window,
14068 cx: &mut Context<Self>,
14069 ) {
14070 if matches!(self.mode, EditorMode::SingleLine) {
14071 cx.propagate();
14072 return;
14073 }
14074 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14075 self.change_selections(Default::default(), window, cx, |s| {
14076 s.move_heads_with(|map, head, _| {
14077 (
14078 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14079 SelectionGoal::None,
14080 )
14081 });
14082 })
14083 }
14084
14085 pub fn move_to_beginning(
14086 &mut self,
14087 _: &MoveToBeginning,
14088 window: &mut Window,
14089 cx: &mut Context<Self>,
14090 ) {
14091 if matches!(self.mode, EditorMode::SingleLine) {
14092 cx.propagate();
14093 return;
14094 }
14095 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14096 self.change_selections(Default::default(), window, cx, |s| {
14097 s.select_ranges(vec![0..0]);
14098 });
14099 }
14100
14101 pub fn select_to_beginning(
14102 &mut self,
14103 _: &SelectToBeginning,
14104 window: &mut Window,
14105 cx: &mut Context<Self>,
14106 ) {
14107 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14108 selection.set_head(Point::zero(), SelectionGoal::None);
14109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14110 self.change_selections(Default::default(), window, cx, |s| {
14111 s.select(vec![selection]);
14112 });
14113 }
14114
14115 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14116 if matches!(self.mode, EditorMode::SingleLine) {
14117 cx.propagate();
14118 return;
14119 }
14120 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14121 let cursor = self.buffer.read(cx).read(cx).len();
14122 self.change_selections(Default::default(), window, cx, |s| {
14123 s.select_ranges(vec![cursor..cursor])
14124 });
14125 }
14126
14127 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14128 self.nav_history = nav_history;
14129 }
14130
14131 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14132 self.nav_history.as_ref()
14133 }
14134
14135 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14136 self.push_to_nav_history(
14137 self.selections.newest_anchor().head(),
14138 None,
14139 false,
14140 true,
14141 cx,
14142 );
14143 }
14144
14145 fn push_to_nav_history(
14146 &mut self,
14147 cursor_anchor: Anchor,
14148 new_position: Option<Point>,
14149 is_deactivate: bool,
14150 always: bool,
14151 cx: &mut Context<Self>,
14152 ) {
14153 if let Some(nav_history) = self.nav_history.as_mut() {
14154 let buffer = self.buffer.read(cx).read(cx);
14155 let cursor_position = cursor_anchor.to_point(&buffer);
14156 let scroll_state = self.scroll_manager.anchor();
14157 let scroll_top_row = scroll_state.top_row(&buffer);
14158 drop(buffer);
14159
14160 if let Some(new_position) = new_position {
14161 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14162 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14163 return;
14164 }
14165 }
14166
14167 nav_history.push(
14168 Some(NavigationData {
14169 cursor_anchor,
14170 cursor_position,
14171 scroll_anchor: scroll_state,
14172 scroll_top_row,
14173 }),
14174 cx,
14175 );
14176 cx.emit(EditorEvent::PushedToNavHistory {
14177 anchor: cursor_anchor,
14178 is_deactivate,
14179 })
14180 }
14181 }
14182
14183 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14185 let buffer = self.buffer.read(cx).snapshot(cx);
14186 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14187 selection.set_head(buffer.len(), SelectionGoal::None);
14188 self.change_selections(Default::default(), window, cx, |s| {
14189 s.select(vec![selection]);
14190 });
14191 }
14192
14193 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14194 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14195 let end = self.buffer.read(cx).read(cx).len();
14196 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14197 s.select_ranges(vec![0..end]);
14198 });
14199 }
14200
14201 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14202 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14203 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14204 let mut selections = self.selections.all::<Point>(&display_map);
14205 let max_point = display_map.buffer_snapshot().max_point();
14206 for selection in &mut selections {
14207 let rows = selection.spanned_rows(true, &display_map);
14208 selection.start = Point::new(rows.start.0, 0);
14209 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14210 selection.reversed = false;
14211 }
14212 self.change_selections(Default::default(), window, cx, |s| {
14213 s.select(selections);
14214 });
14215 }
14216
14217 pub fn split_selection_into_lines(
14218 &mut self,
14219 action: &SplitSelectionIntoLines,
14220 window: &mut Window,
14221 cx: &mut Context<Self>,
14222 ) {
14223 let selections = self
14224 .selections
14225 .all::<Point>(&self.display_snapshot(cx))
14226 .into_iter()
14227 .map(|selection| selection.start..selection.end)
14228 .collect::<Vec<_>>();
14229 self.unfold_ranges(&selections, true, true, cx);
14230
14231 let mut new_selection_ranges = Vec::new();
14232 {
14233 let buffer = self.buffer.read(cx).read(cx);
14234 for selection in selections {
14235 for row in selection.start.row..selection.end.row {
14236 let line_start = Point::new(row, 0);
14237 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14238
14239 if action.keep_selections {
14240 // Keep the selection range for each line
14241 let selection_start = if row == selection.start.row {
14242 selection.start
14243 } else {
14244 line_start
14245 };
14246 new_selection_ranges.push(selection_start..line_end);
14247 } else {
14248 // Collapse to cursor at end of line
14249 new_selection_ranges.push(line_end..line_end);
14250 }
14251 }
14252
14253 let is_multiline_selection = selection.start.row != selection.end.row;
14254 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14255 // so this action feels more ergonomic when paired with other selection operations
14256 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14257 if !should_skip_last {
14258 if action.keep_selections {
14259 if is_multiline_selection {
14260 let line_start = Point::new(selection.end.row, 0);
14261 new_selection_ranges.push(line_start..selection.end);
14262 } else {
14263 new_selection_ranges.push(selection.start..selection.end);
14264 }
14265 } else {
14266 new_selection_ranges.push(selection.end..selection.end);
14267 }
14268 }
14269 }
14270 }
14271 self.change_selections(Default::default(), window, cx, |s| {
14272 s.select_ranges(new_selection_ranges);
14273 });
14274 }
14275
14276 pub fn add_selection_above(
14277 &mut self,
14278 action: &AddSelectionAbove,
14279 window: &mut Window,
14280 cx: &mut Context<Self>,
14281 ) {
14282 self.add_selection(true, action.skip_soft_wrap, window, cx);
14283 }
14284
14285 pub fn add_selection_below(
14286 &mut self,
14287 action: &AddSelectionBelow,
14288 window: &mut Window,
14289 cx: &mut Context<Self>,
14290 ) {
14291 self.add_selection(false, action.skip_soft_wrap, window, cx);
14292 }
14293
14294 fn add_selection(
14295 &mut self,
14296 above: bool,
14297 skip_soft_wrap: bool,
14298 window: &mut Window,
14299 cx: &mut Context<Self>,
14300 ) {
14301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14302
14303 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14304 let all_selections = self.selections.all::<Point>(&display_map);
14305 let text_layout_details = self.text_layout_details(window);
14306
14307 let (mut columnar_selections, new_selections_to_columnarize) = {
14308 if let Some(state) = self.add_selections_state.as_ref() {
14309 let columnar_selection_ids: HashSet<_> = state
14310 .groups
14311 .iter()
14312 .flat_map(|group| group.stack.iter())
14313 .copied()
14314 .collect();
14315
14316 all_selections
14317 .into_iter()
14318 .partition(|s| columnar_selection_ids.contains(&s.id))
14319 } else {
14320 (Vec::new(), all_selections)
14321 }
14322 };
14323
14324 let mut state = self
14325 .add_selections_state
14326 .take()
14327 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14328
14329 for selection in new_selections_to_columnarize {
14330 let range = selection.display_range(&display_map).sorted();
14331 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14332 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14333 let positions = start_x.min(end_x)..start_x.max(end_x);
14334 let mut stack = Vec::new();
14335 for row in range.start.row().0..=range.end.row().0 {
14336 if let Some(selection) = self.selections.build_columnar_selection(
14337 &display_map,
14338 DisplayRow(row),
14339 &positions,
14340 selection.reversed,
14341 &text_layout_details,
14342 ) {
14343 stack.push(selection.id);
14344 columnar_selections.push(selection);
14345 }
14346 }
14347 if !stack.is_empty() {
14348 if above {
14349 stack.reverse();
14350 }
14351 state.groups.push(AddSelectionsGroup { above, stack });
14352 }
14353 }
14354
14355 let mut final_selections = Vec::new();
14356 let end_row = if above {
14357 DisplayRow(0)
14358 } else {
14359 display_map.max_point().row()
14360 };
14361
14362 let mut last_added_item_per_group = HashMap::default();
14363 for group in state.groups.iter_mut() {
14364 if let Some(last_id) = group.stack.last() {
14365 last_added_item_per_group.insert(*last_id, group);
14366 }
14367 }
14368
14369 for selection in columnar_selections {
14370 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14371 if above == group.above {
14372 let range = selection.display_range(&display_map).sorted();
14373 debug_assert_eq!(range.start.row(), range.end.row());
14374 let mut row = range.start.row();
14375 let positions =
14376 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14377 Pixels::from(start)..Pixels::from(end)
14378 } else {
14379 let start_x =
14380 display_map.x_for_display_point(range.start, &text_layout_details);
14381 let end_x =
14382 display_map.x_for_display_point(range.end, &text_layout_details);
14383 start_x.min(end_x)..start_x.max(end_x)
14384 };
14385
14386 let mut maybe_new_selection = None;
14387 let direction = if above { -1 } else { 1 };
14388
14389 while row != end_row {
14390 if skip_soft_wrap {
14391 row = display_map
14392 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14393 .row();
14394 } else if above {
14395 row.0 -= 1;
14396 } else {
14397 row.0 += 1;
14398 }
14399
14400 if let Some(new_selection) = self.selections.build_columnar_selection(
14401 &display_map,
14402 row,
14403 &positions,
14404 selection.reversed,
14405 &text_layout_details,
14406 ) {
14407 maybe_new_selection = Some(new_selection);
14408 break;
14409 }
14410 }
14411
14412 if let Some(new_selection) = maybe_new_selection {
14413 group.stack.push(new_selection.id);
14414 if above {
14415 final_selections.push(new_selection);
14416 final_selections.push(selection);
14417 } else {
14418 final_selections.push(selection);
14419 final_selections.push(new_selection);
14420 }
14421 } else {
14422 final_selections.push(selection);
14423 }
14424 } else {
14425 group.stack.pop();
14426 }
14427 } else {
14428 final_selections.push(selection);
14429 }
14430 }
14431
14432 self.change_selections(Default::default(), window, cx, |s| {
14433 s.select(final_selections);
14434 });
14435
14436 let final_selection_ids: HashSet<_> = self
14437 .selections
14438 .all::<Point>(&display_map)
14439 .iter()
14440 .map(|s| s.id)
14441 .collect();
14442 state.groups.retain_mut(|group| {
14443 // selections might get merged above so we remove invalid items from stacks
14444 group.stack.retain(|id| final_selection_ids.contains(id));
14445
14446 // single selection in stack can be treated as initial state
14447 group.stack.len() > 1
14448 });
14449
14450 if !state.groups.is_empty() {
14451 self.add_selections_state = Some(state);
14452 }
14453 }
14454
14455 fn select_match_ranges(
14456 &mut self,
14457 range: Range<usize>,
14458 reversed: bool,
14459 replace_newest: bool,
14460 auto_scroll: Option<Autoscroll>,
14461 window: &mut Window,
14462 cx: &mut Context<Editor>,
14463 ) {
14464 self.unfold_ranges(
14465 std::slice::from_ref(&range),
14466 false,
14467 auto_scroll.is_some(),
14468 cx,
14469 );
14470 let effects = if let Some(scroll) = auto_scroll {
14471 SelectionEffects::scroll(scroll)
14472 } else {
14473 SelectionEffects::no_scroll()
14474 };
14475 self.change_selections(effects, window, cx, |s| {
14476 if replace_newest {
14477 s.delete(s.newest_anchor().id);
14478 }
14479 if reversed {
14480 s.insert_range(range.end..range.start);
14481 } else {
14482 s.insert_range(range);
14483 }
14484 });
14485 }
14486
14487 pub fn select_next_match_internal(
14488 &mut self,
14489 display_map: &DisplaySnapshot,
14490 replace_newest: bool,
14491 autoscroll: Option<Autoscroll>,
14492 window: &mut Window,
14493 cx: &mut Context<Self>,
14494 ) -> Result<()> {
14495 let buffer = display_map.buffer_snapshot();
14496 let mut selections = self.selections.all::<usize>(&display_map);
14497 if let Some(mut select_next_state) = self.select_next_state.take() {
14498 let query = &select_next_state.query;
14499 if !select_next_state.done {
14500 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14501 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14502 let mut next_selected_range = None;
14503
14504 let bytes_after_last_selection =
14505 buffer.bytes_in_range(last_selection.end..buffer.len());
14506 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14507 let query_matches = query
14508 .stream_find_iter(bytes_after_last_selection)
14509 .map(|result| (last_selection.end, result))
14510 .chain(
14511 query
14512 .stream_find_iter(bytes_before_first_selection)
14513 .map(|result| (0, result)),
14514 );
14515
14516 for (start_offset, query_match) in query_matches {
14517 let query_match = query_match.unwrap(); // can only fail due to I/O
14518 let offset_range =
14519 start_offset + query_match.start()..start_offset + query_match.end();
14520
14521 if !select_next_state.wordwise
14522 || (!buffer.is_inside_word(offset_range.start, None)
14523 && !buffer.is_inside_word(offset_range.end, None))
14524 {
14525 let idx = selections
14526 .partition_point(|selection| selection.end <= offset_range.start);
14527 let overlaps = selections
14528 .get(idx)
14529 .map_or(false, |selection| selection.start < offset_range.end);
14530
14531 if !overlaps {
14532 next_selected_range = Some(offset_range);
14533 break;
14534 }
14535 }
14536 }
14537
14538 if let Some(next_selected_range) = next_selected_range {
14539 self.select_match_ranges(
14540 next_selected_range,
14541 last_selection.reversed,
14542 replace_newest,
14543 autoscroll,
14544 window,
14545 cx,
14546 );
14547 } else {
14548 select_next_state.done = true;
14549 }
14550 }
14551
14552 self.select_next_state = Some(select_next_state);
14553 } else {
14554 let mut only_carets = true;
14555 let mut same_text_selected = true;
14556 let mut selected_text = None;
14557
14558 let mut selections_iter = selections.iter().peekable();
14559 while let Some(selection) = selections_iter.next() {
14560 if selection.start != selection.end {
14561 only_carets = false;
14562 }
14563
14564 if same_text_selected {
14565 if selected_text.is_none() {
14566 selected_text =
14567 Some(buffer.text_for_range(selection.range()).collect::<String>());
14568 }
14569
14570 if let Some(next_selection) = selections_iter.peek() {
14571 if next_selection.range().len() == selection.range().len() {
14572 let next_selected_text = buffer
14573 .text_for_range(next_selection.range())
14574 .collect::<String>();
14575 if Some(next_selected_text) != selected_text {
14576 same_text_selected = false;
14577 selected_text = None;
14578 }
14579 } else {
14580 same_text_selected = false;
14581 selected_text = None;
14582 }
14583 }
14584 }
14585 }
14586
14587 if only_carets {
14588 for selection in &mut selections {
14589 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14590 selection.start = word_range.start;
14591 selection.end = word_range.end;
14592 selection.goal = SelectionGoal::None;
14593 selection.reversed = false;
14594 self.select_match_ranges(
14595 selection.start..selection.end,
14596 selection.reversed,
14597 replace_newest,
14598 autoscroll,
14599 window,
14600 cx,
14601 );
14602 }
14603
14604 if selections.len() == 1 {
14605 let selection = selections
14606 .last()
14607 .expect("ensured that there's only one selection");
14608 let query = buffer
14609 .text_for_range(selection.start..selection.end)
14610 .collect::<String>();
14611 let is_empty = query.is_empty();
14612 let select_state = SelectNextState {
14613 query: AhoCorasick::new(&[query])?,
14614 wordwise: true,
14615 done: is_empty,
14616 };
14617 self.select_next_state = Some(select_state);
14618 } else {
14619 self.select_next_state = None;
14620 }
14621 } else if let Some(selected_text) = selected_text {
14622 self.select_next_state = Some(SelectNextState {
14623 query: AhoCorasick::new(&[selected_text])?,
14624 wordwise: false,
14625 done: false,
14626 });
14627 self.select_next_match_internal(
14628 display_map,
14629 replace_newest,
14630 autoscroll,
14631 window,
14632 cx,
14633 )?;
14634 }
14635 }
14636 Ok(())
14637 }
14638
14639 pub fn select_all_matches(
14640 &mut self,
14641 _action: &SelectAllMatches,
14642 window: &mut Window,
14643 cx: &mut Context<Self>,
14644 ) -> Result<()> {
14645 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14646
14647 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14648
14649 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14650 let Some(select_next_state) = self.select_next_state.as_mut() else {
14651 return Ok(());
14652 };
14653 if select_next_state.done {
14654 return Ok(());
14655 }
14656
14657 let mut new_selections = Vec::new();
14658
14659 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14660 let buffer = display_map.buffer_snapshot();
14661 let query_matches = select_next_state
14662 .query
14663 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14664
14665 for query_match in query_matches.into_iter() {
14666 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14667 let offset_range = if reversed {
14668 query_match.end()..query_match.start()
14669 } else {
14670 query_match.start()..query_match.end()
14671 };
14672
14673 if !select_next_state.wordwise
14674 || (!buffer.is_inside_word(offset_range.start, None)
14675 && !buffer.is_inside_word(offset_range.end, None))
14676 {
14677 new_selections.push(offset_range.start..offset_range.end);
14678 }
14679 }
14680
14681 select_next_state.done = true;
14682
14683 if new_selections.is_empty() {
14684 log::error!("bug: new_selections is empty in select_all_matches");
14685 return Ok(());
14686 }
14687
14688 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14689 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14690 selections.select_ranges(new_selections)
14691 });
14692
14693 Ok(())
14694 }
14695
14696 pub fn select_next(
14697 &mut self,
14698 action: &SelectNext,
14699 window: &mut Window,
14700 cx: &mut Context<Self>,
14701 ) -> Result<()> {
14702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14703 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14704 self.select_next_match_internal(
14705 &display_map,
14706 action.replace_newest,
14707 Some(Autoscroll::newest()),
14708 window,
14709 cx,
14710 )?;
14711 Ok(())
14712 }
14713
14714 pub fn select_previous(
14715 &mut self,
14716 action: &SelectPrevious,
14717 window: &mut Window,
14718 cx: &mut Context<Self>,
14719 ) -> Result<()> {
14720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14721 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14722 let buffer = display_map.buffer_snapshot();
14723 let mut selections = self.selections.all::<usize>(&display_map);
14724 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14725 let query = &select_prev_state.query;
14726 if !select_prev_state.done {
14727 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14728 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14729 let mut next_selected_range = None;
14730 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14731 let bytes_before_last_selection =
14732 buffer.reversed_bytes_in_range(0..last_selection.start);
14733 let bytes_after_first_selection =
14734 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14735 let query_matches = query
14736 .stream_find_iter(bytes_before_last_selection)
14737 .map(|result| (last_selection.start, result))
14738 .chain(
14739 query
14740 .stream_find_iter(bytes_after_first_selection)
14741 .map(|result| (buffer.len(), result)),
14742 );
14743 for (end_offset, query_match) in query_matches {
14744 let query_match = query_match.unwrap(); // can only fail due to I/O
14745 let offset_range =
14746 end_offset - query_match.end()..end_offset - query_match.start();
14747
14748 if !select_prev_state.wordwise
14749 || (!buffer.is_inside_word(offset_range.start, None)
14750 && !buffer.is_inside_word(offset_range.end, None))
14751 {
14752 next_selected_range = Some(offset_range);
14753 break;
14754 }
14755 }
14756
14757 if let Some(next_selected_range) = next_selected_range {
14758 self.select_match_ranges(
14759 next_selected_range,
14760 last_selection.reversed,
14761 action.replace_newest,
14762 Some(Autoscroll::newest()),
14763 window,
14764 cx,
14765 );
14766 } else {
14767 select_prev_state.done = true;
14768 }
14769 }
14770
14771 self.select_prev_state = Some(select_prev_state);
14772 } else {
14773 let mut only_carets = true;
14774 let mut same_text_selected = true;
14775 let mut selected_text = None;
14776
14777 let mut selections_iter = selections.iter().peekable();
14778 while let Some(selection) = selections_iter.next() {
14779 if selection.start != selection.end {
14780 only_carets = false;
14781 }
14782
14783 if same_text_selected {
14784 if selected_text.is_none() {
14785 selected_text =
14786 Some(buffer.text_for_range(selection.range()).collect::<String>());
14787 }
14788
14789 if let Some(next_selection) = selections_iter.peek() {
14790 if next_selection.range().len() == selection.range().len() {
14791 let next_selected_text = buffer
14792 .text_for_range(next_selection.range())
14793 .collect::<String>();
14794 if Some(next_selected_text) != selected_text {
14795 same_text_selected = false;
14796 selected_text = None;
14797 }
14798 } else {
14799 same_text_selected = false;
14800 selected_text = None;
14801 }
14802 }
14803 }
14804 }
14805
14806 if only_carets {
14807 for selection in &mut selections {
14808 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14809 selection.start = word_range.start;
14810 selection.end = word_range.end;
14811 selection.goal = SelectionGoal::None;
14812 selection.reversed = false;
14813 self.select_match_ranges(
14814 selection.start..selection.end,
14815 selection.reversed,
14816 action.replace_newest,
14817 Some(Autoscroll::newest()),
14818 window,
14819 cx,
14820 );
14821 }
14822 if selections.len() == 1 {
14823 let selection = selections
14824 .last()
14825 .expect("ensured that there's only one selection");
14826 let query = buffer
14827 .text_for_range(selection.start..selection.end)
14828 .collect::<String>();
14829 let is_empty = query.is_empty();
14830 let select_state = SelectNextState {
14831 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14832 wordwise: true,
14833 done: is_empty,
14834 };
14835 self.select_prev_state = Some(select_state);
14836 } else {
14837 self.select_prev_state = None;
14838 }
14839 } else if let Some(selected_text) = selected_text {
14840 self.select_prev_state = Some(SelectNextState {
14841 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14842 wordwise: false,
14843 done: false,
14844 });
14845 self.select_previous(action, window, cx)?;
14846 }
14847 }
14848 Ok(())
14849 }
14850
14851 pub fn find_next_match(
14852 &mut self,
14853 _: &FindNextMatch,
14854 window: &mut Window,
14855 cx: &mut Context<Self>,
14856 ) -> Result<()> {
14857 let selections = self.selections.disjoint_anchors_arc();
14858 match selections.first() {
14859 Some(first) if selections.len() >= 2 => {
14860 self.change_selections(Default::default(), window, cx, |s| {
14861 s.select_ranges([first.range()]);
14862 });
14863 }
14864 _ => self.select_next(
14865 &SelectNext {
14866 replace_newest: true,
14867 },
14868 window,
14869 cx,
14870 )?,
14871 }
14872 Ok(())
14873 }
14874
14875 pub fn find_previous_match(
14876 &mut self,
14877 _: &FindPreviousMatch,
14878 window: &mut Window,
14879 cx: &mut Context<Self>,
14880 ) -> Result<()> {
14881 let selections = self.selections.disjoint_anchors_arc();
14882 match selections.last() {
14883 Some(last) if selections.len() >= 2 => {
14884 self.change_selections(Default::default(), window, cx, |s| {
14885 s.select_ranges([last.range()]);
14886 });
14887 }
14888 _ => self.select_previous(
14889 &SelectPrevious {
14890 replace_newest: true,
14891 },
14892 window,
14893 cx,
14894 )?,
14895 }
14896 Ok(())
14897 }
14898
14899 pub fn toggle_comments(
14900 &mut self,
14901 action: &ToggleComments,
14902 window: &mut Window,
14903 cx: &mut Context<Self>,
14904 ) {
14905 if self.read_only(cx) {
14906 return;
14907 }
14908 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14909 let text_layout_details = &self.text_layout_details(window);
14910 self.transact(window, cx, |this, window, cx| {
14911 let mut selections = this
14912 .selections
14913 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14914 let mut edits = Vec::new();
14915 let mut selection_edit_ranges = Vec::new();
14916 let mut last_toggled_row = None;
14917 let snapshot = this.buffer.read(cx).read(cx);
14918 let empty_str: Arc<str> = Arc::default();
14919 let mut suffixes_inserted = Vec::new();
14920 let ignore_indent = action.ignore_indent;
14921
14922 fn comment_prefix_range(
14923 snapshot: &MultiBufferSnapshot,
14924 row: MultiBufferRow,
14925 comment_prefix: &str,
14926 comment_prefix_whitespace: &str,
14927 ignore_indent: bool,
14928 ) -> Range<Point> {
14929 let indent_size = if ignore_indent {
14930 0
14931 } else {
14932 snapshot.indent_size_for_line(row).len
14933 };
14934
14935 let start = Point::new(row.0, indent_size);
14936
14937 let mut line_bytes = snapshot
14938 .bytes_in_range(start..snapshot.max_point())
14939 .flatten()
14940 .copied();
14941
14942 // If this line currently begins with the line comment prefix, then record
14943 // the range containing the prefix.
14944 if line_bytes
14945 .by_ref()
14946 .take(comment_prefix.len())
14947 .eq(comment_prefix.bytes())
14948 {
14949 // Include any whitespace that matches the comment prefix.
14950 let matching_whitespace_len = line_bytes
14951 .zip(comment_prefix_whitespace.bytes())
14952 .take_while(|(a, b)| a == b)
14953 .count() as u32;
14954 let end = Point::new(
14955 start.row,
14956 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14957 );
14958 start..end
14959 } else {
14960 start..start
14961 }
14962 }
14963
14964 fn comment_suffix_range(
14965 snapshot: &MultiBufferSnapshot,
14966 row: MultiBufferRow,
14967 comment_suffix: &str,
14968 comment_suffix_has_leading_space: bool,
14969 ) -> Range<Point> {
14970 let end = Point::new(row.0, snapshot.line_len(row));
14971 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
14972
14973 let mut line_end_bytes = snapshot
14974 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
14975 .flatten()
14976 .copied();
14977
14978 let leading_space_len = if suffix_start_column > 0
14979 && line_end_bytes.next() == Some(b' ')
14980 && comment_suffix_has_leading_space
14981 {
14982 1
14983 } else {
14984 0
14985 };
14986
14987 // If this line currently begins with the line comment prefix, then record
14988 // the range containing the prefix.
14989 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
14990 let start = Point::new(end.row, suffix_start_column - leading_space_len);
14991 start..end
14992 } else {
14993 end..end
14994 }
14995 }
14996
14997 // TODO: Handle selections that cross excerpts
14998 for selection in &mut selections {
14999 let start_column = snapshot
15000 .indent_size_for_line(MultiBufferRow(selection.start.row))
15001 .len;
15002 let language = if let Some(language) =
15003 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15004 {
15005 language
15006 } else {
15007 continue;
15008 };
15009
15010 selection_edit_ranges.clear();
15011
15012 // If multiple selections contain a given row, avoid processing that
15013 // row more than once.
15014 let mut start_row = MultiBufferRow(selection.start.row);
15015 if last_toggled_row == Some(start_row) {
15016 start_row = start_row.next_row();
15017 }
15018 let end_row =
15019 if selection.end.row > selection.start.row && selection.end.column == 0 {
15020 MultiBufferRow(selection.end.row - 1)
15021 } else {
15022 MultiBufferRow(selection.end.row)
15023 };
15024 last_toggled_row = Some(end_row);
15025
15026 if start_row > end_row {
15027 continue;
15028 }
15029
15030 // If the language has line comments, toggle those.
15031 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15032
15033 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15034 if ignore_indent {
15035 full_comment_prefixes = full_comment_prefixes
15036 .into_iter()
15037 .map(|s| Arc::from(s.trim_end()))
15038 .collect();
15039 }
15040
15041 if !full_comment_prefixes.is_empty() {
15042 let first_prefix = full_comment_prefixes
15043 .first()
15044 .expect("prefixes is non-empty");
15045 let prefix_trimmed_lengths = full_comment_prefixes
15046 .iter()
15047 .map(|p| p.trim_end_matches(' ').len())
15048 .collect::<SmallVec<[usize; 4]>>();
15049
15050 let mut all_selection_lines_are_comments = true;
15051
15052 for row in start_row.0..=end_row.0 {
15053 let row = MultiBufferRow(row);
15054 if start_row < end_row && snapshot.is_line_blank(row) {
15055 continue;
15056 }
15057
15058 let prefix_range = full_comment_prefixes
15059 .iter()
15060 .zip(prefix_trimmed_lengths.iter().copied())
15061 .map(|(prefix, trimmed_prefix_len)| {
15062 comment_prefix_range(
15063 snapshot.deref(),
15064 row,
15065 &prefix[..trimmed_prefix_len],
15066 &prefix[trimmed_prefix_len..],
15067 ignore_indent,
15068 )
15069 })
15070 .max_by_key(|range| range.end.column - range.start.column)
15071 .expect("prefixes is non-empty");
15072
15073 if prefix_range.is_empty() {
15074 all_selection_lines_are_comments = false;
15075 }
15076
15077 selection_edit_ranges.push(prefix_range);
15078 }
15079
15080 if all_selection_lines_are_comments {
15081 edits.extend(
15082 selection_edit_ranges
15083 .iter()
15084 .cloned()
15085 .map(|range| (range, empty_str.clone())),
15086 );
15087 } else {
15088 let min_column = selection_edit_ranges
15089 .iter()
15090 .map(|range| range.start.column)
15091 .min()
15092 .unwrap_or(0);
15093 edits.extend(selection_edit_ranges.iter().map(|range| {
15094 let position = Point::new(range.start.row, min_column);
15095 (position..position, first_prefix.clone())
15096 }));
15097 }
15098 } else if let Some(BlockCommentConfig {
15099 start: full_comment_prefix,
15100 end: comment_suffix,
15101 ..
15102 }) = language.block_comment()
15103 {
15104 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15105 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15106 let prefix_range = comment_prefix_range(
15107 snapshot.deref(),
15108 start_row,
15109 comment_prefix,
15110 comment_prefix_whitespace,
15111 ignore_indent,
15112 );
15113 let suffix_range = comment_suffix_range(
15114 snapshot.deref(),
15115 end_row,
15116 comment_suffix.trim_start_matches(' '),
15117 comment_suffix.starts_with(' '),
15118 );
15119
15120 if prefix_range.is_empty() || suffix_range.is_empty() {
15121 edits.push((
15122 prefix_range.start..prefix_range.start,
15123 full_comment_prefix.clone(),
15124 ));
15125 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15126 suffixes_inserted.push((end_row, comment_suffix.len()));
15127 } else {
15128 edits.push((prefix_range, empty_str.clone()));
15129 edits.push((suffix_range, empty_str.clone()));
15130 }
15131 } else {
15132 continue;
15133 }
15134 }
15135
15136 drop(snapshot);
15137 this.buffer.update(cx, |buffer, cx| {
15138 buffer.edit(edits, None, cx);
15139 });
15140
15141 // Adjust selections so that they end before any comment suffixes that
15142 // were inserted.
15143 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15144 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15145 let snapshot = this.buffer.read(cx).read(cx);
15146 for selection in &mut selections {
15147 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15148 match row.cmp(&MultiBufferRow(selection.end.row)) {
15149 Ordering::Less => {
15150 suffixes_inserted.next();
15151 continue;
15152 }
15153 Ordering::Greater => break,
15154 Ordering::Equal => {
15155 if selection.end.column == snapshot.line_len(row) {
15156 if selection.is_empty() {
15157 selection.start.column -= suffix_len as u32;
15158 }
15159 selection.end.column -= suffix_len as u32;
15160 }
15161 break;
15162 }
15163 }
15164 }
15165 }
15166
15167 drop(snapshot);
15168 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15169
15170 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15171 let selections_on_single_row = selections.windows(2).all(|selections| {
15172 selections[0].start.row == selections[1].start.row
15173 && selections[0].end.row == selections[1].end.row
15174 && selections[0].start.row == selections[0].end.row
15175 });
15176 let selections_selecting = selections
15177 .iter()
15178 .any(|selection| selection.start != selection.end);
15179 let advance_downwards = action.advance_downwards
15180 && selections_on_single_row
15181 && !selections_selecting
15182 && !matches!(this.mode, EditorMode::SingleLine);
15183
15184 if advance_downwards {
15185 let snapshot = this.buffer.read(cx).snapshot(cx);
15186
15187 this.change_selections(Default::default(), window, cx, |s| {
15188 s.move_cursors_with(|display_snapshot, display_point, _| {
15189 let mut point = display_point.to_point(display_snapshot);
15190 point.row += 1;
15191 point = snapshot.clip_point(point, Bias::Left);
15192 let display_point = point.to_display_point(display_snapshot);
15193 let goal = SelectionGoal::HorizontalPosition(
15194 display_snapshot
15195 .x_for_display_point(display_point, text_layout_details)
15196 .into(),
15197 );
15198 (display_point, goal)
15199 })
15200 });
15201 }
15202 });
15203 }
15204
15205 pub fn select_enclosing_symbol(
15206 &mut self,
15207 _: &SelectEnclosingSymbol,
15208 window: &mut Window,
15209 cx: &mut Context<Self>,
15210 ) {
15211 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15212
15213 let buffer = self.buffer.read(cx).snapshot(cx);
15214 let old_selections = self
15215 .selections
15216 .all::<usize>(&self.display_snapshot(cx))
15217 .into_boxed_slice();
15218
15219 fn update_selection(
15220 selection: &Selection<usize>,
15221 buffer_snap: &MultiBufferSnapshot,
15222 ) -> Option<Selection<usize>> {
15223 let cursor = selection.head();
15224 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15225 for symbol in symbols.iter().rev() {
15226 let start = symbol.range.start.to_offset(buffer_snap);
15227 let end = symbol.range.end.to_offset(buffer_snap);
15228 let new_range = start..end;
15229 if start < selection.start || end > selection.end {
15230 return Some(Selection {
15231 id: selection.id,
15232 start: new_range.start,
15233 end: new_range.end,
15234 goal: SelectionGoal::None,
15235 reversed: selection.reversed,
15236 });
15237 }
15238 }
15239 None
15240 }
15241
15242 let mut selected_larger_symbol = false;
15243 let new_selections = old_selections
15244 .iter()
15245 .map(|selection| match update_selection(selection, &buffer) {
15246 Some(new_selection) => {
15247 if new_selection.range() != selection.range() {
15248 selected_larger_symbol = true;
15249 }
15250 new_selection
15251 }
15252 None => selection.clone(),
15253 })
15254 .collect::<Vec<_>>();
15255
15256 if selected_larger_symbol {
15257 self.change_selections(Default::default(), window, cx, |s| {
15258 s.select(new_selections);
15259 });
15260 }
15261 }
15262
15263 pub fn select_larger_syntax_node(
15264 &mut self,
15265 _: &SelectLargerSyntaxNode,
15266 window: &mut Window,
15267 cx: &mut Context<Self>,
15268 ) {
15269 let Some(visible_row_count) = self.visible_row_count() else {
15270 return;
15271 };
15272 let old_selections: Box<[_]> = self
15273 .selections
15274 .all::<usize>(&self.display_snapshot(cx))
15275 .into();
15276 if old_selections.is_empty() {
15277 return;
15278 }
15279
15280 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15281
15282 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15283 let buffer = self.buffer.read(cx).snapshot(cx);
15284
15285 let mut selected_larger_node = false;
15286 let mut new_selections = old_selections
15287 .iter()
15288 .map(|selection| {
15289 let old_range = selection.start..selection.end;
15290
15291 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15292 // manually select word at selection
15293 if ["string_content", "inline"].contains(&node.kind()) {
15294 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15295 // ignore if word is already selected
15296 if !word_range.is_empty() && old_range != word_range {
15297 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15298 // only select word if start and end point belongs to same word
15299 if word_range == last_word_range {
15300 selected_larger_node = true;
15301 return Selection {
15302 id: selection.id,
15303 start: word_range.start,
15304 end: word_range.end,
15305 goal: SelectionGoal::None,
15306 reversed: selection.reversed,
15307 };
15308 }
15309 }
15310 }
15311 }
15312
15313 let mut new_range = old_range.clone();
15314 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15315 new_range = range;
15316 if !node.is_named() {
15317 continue;
15318 }
15319 if !display_map.intersects_fold(new_range.start)
15320 && !display_map.intersects_fold(new_range.end)
15321 {
15322 break;
15323 }
15324 }
15325
15326 selected_larger_node |= new_range != old_range;
15327 Selection {
15328 id: selection.id,
15329 start: new_range.start,
15330 end: new_range.end,
15331 goal: SelectionGoal::None,
15332 reversed: selection.reversed,
15333 }
15334 })
15335 .collect::<Vec<_>>();
15336
15337 if !selected_larger_node {
15338 return; // don't put this call in the history
15339 }
15340
15341 // scroll based on transformation done to the last selection created by the user
15342 let (last_old, last_new) = old_selections
15343 .last()
15344 .zip(new_selections.last().cloned())
15345 .expect("old_selections isn't empty");
15346
15347 // revert selection
15348 let is_selection_reversed = {
15349 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15350 new_selections.last_mut().expect("checked above").reversed =
15351 should_newest_selection_be_reversed;
15352 should_newest_selection_be_reversed
15353 };
15354
15355 if selected_larger_node {
15356 self.select_syntax_node_history.disable_clearing = true;
15357 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15358 s.select(new_selections.clone());
15359 });
15360 self.select_syntax_node_history.disable_clearing = false;
15361 }
15362
15363 let start_row = last_new.start.to_display_point(&display_map).row().0;
15364 let end_row = last_new.end.to_display_point(&display_map).row().0;
15365 let selection_height = end_row - start_row + 1;
15366 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15367
15368 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15369 let scroll_behavior = if fits_on_the_screen {
15370 self.request_autoscroll(Autoscroll::fit(), cx);
15371 SelectSyntaxNodeScrollBehavior::FitSelection
15372 } else if is_selection_reversed {
15373 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15374 SelectSyntaxNodeScrollBehavior::CursorTop
15375 } else {
15376 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15377 SelectSyntaxNodeScrollBehavior::CursorBottom
15378 };
15379
15380 self.select_syntax_node_history.push((
15381 old_selections,
15382 scroll_behavior,
15383 is_selection_reversed,
15384 ));
15385 }
15386
15387 pub fn select_smaller_syntax_node(
15388 &mut self,
15389 _: &SelectSmallerSyntaxNode,
15390 window: &mut Window,
15391 cx: &mut Context<Self>,
15392 ) {
15393 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15394
15395 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15396 self.select_syntax_node_history.pop()
15397 {
15398 if let Some(selection) = selections.last_mut() {
15399 selection.reversed = is_selection_reversed;
15400 }
15401
15402 self.select_syntax_node_history.disable_clearing = true;
15403 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15404 s.select(selections.to_vec());
15405 });
15406 self.select_syntax_node_history.disable_clearing = false;
15407
15408 match scroll_behavior {
15409 SelectSyntaxNodeScrollBehavior::CursorTop => {
15410 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15411 }
15412 SelectSyntaxNodeScrollBehavior::FitSelection => {
15413 self.request_autoscroll(Autoscroll::fit(), cx);
15414 }
15415 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15416 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15417 }
15418 }
15419 }
15420 }
15421
15422 pub fn unwrap_syntax_node(
15423 &mut self,
15424 _: &UnwrapSyntaxNode,
15425 window: &mut Window,
15426 cx: &mut Context<Self>,
15427 ) {
15428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15429
15430 let buffer = self.buffer.read(cx).snapshot(cx);
15431 let selections = self
15432 .selections
15433 .all::<usize>(&self.display_snapshot(cx))
15434 .into_iter()
15435 // subtracting the offset requires sorting
15436 .sorted_by_key(|i| i.start);
15437
15438 let full_edits = selections
15439 .into_iter()
15440 .filter_map(|selection| {
15441 let child = if selection.is_empty()
15442 && let Some((_, ancestor_range)) =
15443 buffer.syntax_ancestor(selection.start..selection.end)
15444 {
15445 ancestor_range
15446 } else {
15447 selection.range()
15448 };
15449
15450 let mut parent = child.clone();
15451 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15452 parent = ancestor_range;
15453 if parent.start < child.start || parent.end > child.end {
15454 break;
15455 }
15456 }
15457
15458 if parent == child {
15459 return None;
15460 }
15461 let text = buffer.text_for_range(child).collect::<String>();
15462 Some((selection.id, parent, text))
15463 })
15464 .collect::<Vec<_>>();
15465 if full_edits.is_empty() {
15466 return;
15467 }
15468
15469 self.transact(window, cx, |this, window, cx| {
15470 this.buffer.update(cx, |buffer, cx| {
15471 buffer.edit(
15472 full_edits
15473 .iter()
15474 .map(|(_, p, t)| (p.clone(), t.clone()))
15475 .collect::<Vec<_>>(),
15476 None,
15477 cx,
15478 );
15479 });
15480 this.change_selections(Default::default(), window, cx, |s| {
15481 let mut offset = 0;
15482 let mut selections = vec![];
15483 for (id, parent, text) in full_edits {
15484 let start = parent.start - offset;
15485 offset += parent.len() - text.len();
15486 selections.push(Selection {
15487 id,
15488 start,
15489 end: start + text.len(),
15490 reversed: false,
15491 goal: Default::default(),
15492 });
15493 }
15494 s.select(selections);
15495 });
15496 });
15497 }
15498
15499 pub fn select_next_syntax_node(
15500 &mut self,
15501 _: &SelectNextSyntaxNode,
15502 window: &mut Window,
15503 cx: &mut Context<Self>,
15504 ) {
15505 let old_selections: Box<[_]> = self
15506 .selections
15507 .all::<usize>(&self.display_snapshot(cx))
15508 .into();
15509 if old_selections.is_empty() {
15510 return;
15511 }
15512
15513 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15514
15515 let buffer = self.buffer.read(cx).snapshot(cx);
15516 let mut selected_sibling = false;
15517
15518 let new_selections = old_selections
15519 .iter()
15520 .map(|selection| {
15521 let old_range = selection.start..selection.end;
15522
15523 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15524 let new_range = node.byte_range();
15525 selected_sibling = true;
15526 Selection {
15527 id: selection.id,
15528 start: new_range.start,
15529 end: new_range.end,
15530 goal: SelectionGoal::None,
15531 reversed: selection.reversed,
15532 }
15533 } else {
15534 selection.clone()
15535 }
15536 })
15537 .collect::<Vec<_>>();
15538
15539 if selected_sibling {
15540 self.change_selections(
15541 SelectionEffects::scroll(Autoscroll::fit()),
15542 window,
15543 cx,
15544 |s| {
15545 s.select(new_selections);
15546 },
15547 );
15548 }
15549 }
15550
15551 pub fn select_prev_syntax_node(
15552 &mut self,
15553 _: &SelectPreviousSyntaxNode,
15554 window: &mut Window,
15555 cx: &mut Context<Self>,
15556 ) {
15557 let old_selections: Box<[_]> = self
15558 .selections
15559 .all::<usize>(&self.display_snapshot(cx))
15560 .into();
15561 if old_selections.is_empty() {
15562 return;
15563 }
15564
15565 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15566
15567 let buffer = self.buffer.read(cx).snapshot(cx);
15568 let mut selected_sibling = false;
15569
15570 let new_selections = old_selections
15571 .iter()
15572 .map(|selection| {
15573 let old_range = selection.start..selection.end;
15574
15575 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15576 let new_range = node.byte_range();
15577 selected_sibling = true;
15578 Selection {
15579 id: selection.id,
15580 start: new_range.start,
15581 end: new_range.end,
15582 goal: SelectionGoal::None,
15583 reversed: selection.reversed,
15584 }
15585 } else {
15586 selection.clone()
15587 }
15588 })
15589 .collect::<Vec<_>>();
15590
15591 if selected_sibling {
15592 self.change_selections(
15593 SelectionEffects::scroll(Autoscroll::fit()),
15594 window,
15595 cx,
15596 |s| {
15597 s.select(new_selections);
15598 },
15599 );
15600 }
15601 }
15602
15603 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15604 if !EditorSettings::get_global(cx).gutter.runnables {
15605 self.clear_tasks();
15606 return Task::ready(());
15607 }
15608 let project = self.project().map(Entity::downgrade);
15609 let task_sources = self.lsp_task_sources(cx);
15610 let multi_buffer = self.buffer.downgrade();
15611 cx.spawn_in(window, async move |editor, cx| {
15612 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15613 let Some(project) = project.and_then(|p| p.upgrade()) else {
15614 return;
15615 };
15616 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15617 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15618 }) else {
15619 return;
15620 };
15621
15622 let hide_runnables = project
15623 .update(cx, |project, _| project.is_via_collab())
15624 .unwrap_or(true);
15625 if hide_runnables {
15626 return;
15627 }
15628 let new_rows =
15629 cx.background_spawn({
15630 let snapshot = display_snapshot.clone();
15631 async move {
15632 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15633 }
15634 })
15635 .await;
15636 let Ok(lsp_tasks) =
15637 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15638 else {
15639 return;
15640 };
15641 let lsp_tasks = lsp_tasks.await;
15642
15643 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15644 lsp_tasks
15645 .into_iter()
15646 .flat_map(|(kind, tasks)| {
15647 tasks.into_iter().filter_map(move |(location, task)| {
15648 Some((kind.clone(), location?, task))
15649 })
15650 })
15651 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15652 let buffer = location.target.buffer;
15653 let buffer_snapshot = buffer.read(cx).snapshot();
15654 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15655 |(excerpt_id, snapshot, _)| {
15656 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15657 display_snapshot
15658 .buffer_snapshot()
15659 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15660 } else {
15661 None
15662 }
15663 },
15664 );
15665 if let Some(offset) = offset {
15666 let task_buffer_range =
15667 location.target.range.to_point(&buffer_snapshot);
15668 let context_buffer_range =
15669 task_buffer_range.to_offset(&buffer_snapshot);
15670 let context_range = BufferOffset(context_buffer_range.start)
15671 ..BufferOffset(context_buffer_range.end);
15672
15673 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15674 .or_insert_with(|| RunnableTasks {
15675 templates: Vec::new(),
15676 offset,
15677 column: task_buffer_range.start.column,
15678 extra_variables: HashMap::default(),
15679 context_range,
15680 })
15681 .templates
15682 .push((kind, task.original_task().clone()));
15683 }
15684
15685 acc
15686 })
15687 }) else {
15688 return;
15689 };
15690
15691 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15692 buffer.language_settings(cx).tasks.prefer_lsp
15693 }) else {
15694 return;
15695 };
15696
15697 let rows = Self::runnable_rows(
15698 project,
15699 display_snapshot,
15700 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15701 new_rows,
15702 cx.clone(),
15703 )
15704 .await;
15705 editor
15706 .update(cx, |editor, _| {
15707 editor.clear_tasks();
15708 for (key, mut value) in rows {
15709 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15710 value.templates.extend(lsp_tasks.templates);
15711 }
15712
15713 editor.insert_tasks(key, value);
15714 }
15715 for (key, value) in lsp_tasks_by_rows {
15716 editor.insert_tasks(key, value);
15717 }
15718 })
15719 .ok();
15720 })
15721 }
15722 fn fetch_runnable_ranges(
15723 snapshot: &DisplaySnapshot,
15724 range: Range<Anchor>,
15725 ) -> Vec<language::RunnableRange> {
15726 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15727 }
15728
15729 fn runnable_rows(
15730 project: Entity<Project>,
15731 snapshot: DisplaySnapshot,
15732 prefer_lsp: bool,
15733 runnable_ranges: Vec<RunnableRange>,
15734 cx: AsyncWindowContext,
15735 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15736 cx.spawn(async move |cx| {
15737 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15738 for mut runnable in runnable_ranges {
15739 let Some(tasks) = cx
15740 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15741 .ok()
15742 else {
15743 continue;
15744 };
15745 let mut tasks = tasks.await;
15746
15747 if prefer_lsp {
15748 tasks.retain(|(task_kind, _)| {
15749 !matches!(task_kind, TaskSourceKind::Language { .. })
15750 });
15751 }
15752 if tasks.is_empty() {
15753 continue;
15754 }
15755
15756 let point = runnable
15757 .run_range
15758 .start
15759 .to_point(&snapshot.buffer_snapshot());
15760 let Some(row) = snapshot
15761 .buffer_snapshot()
15762 .buffer_line_for_row(MultiBufferRow(point.row))
15763 .map(|(_, range)| range.start.row)
15764 else {
15765 continue;
15766 };
15767
15768 let context_range =
15769 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15770 runnable_rows.push((
15771 (runnable.buffer_id, row),
15772 RunnableTasks {
15773 templates: tasks,
15774 offset: snapshot
15775 .buffer_snapshot()
15776 .anchor_before(runnable.run_range.start),
15777 context_range,
15778 column: point.column,
15779 extra_variables: runnable.extra_captures,
15780 },
15781 ));
15782 }
15783 runnable_rows
15784 })
15785 }
15786
15787 fn templates_with_tags(
15788 project: &Entity<Project>,
15789 runnable: &mut Runnable,
15790 cx: &mut App,
15791 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15792 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15793 let (worktree_id, file) = project
15794 .buffer_for_id(runnable.buffer, cx)
15795 .and_then(|buffer| buffer.read(cx).file())
15796 .map(|file| (file.worktree_id(cx), file.clone()))
15797 .unzip();
15798
15799 (
15800 project.task_store().read(cx).task_inventory().cloned(),
15801 worktree_id,
15802 file,
15803 )
15804 });
15805
15806 let tags = mem::take(&mut runnable.tags);
15807 let language = runnable.language.clone();
15808 cx.spawn(async move |cx| {
15809 let mut templates_with_tags = Vec::new();
15810 if let Some(inventory) = inventory {
15811 for RunnableTag(tag) in tags {
15812 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15813 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15814 }) else {
15815 return templates_with_tags;
15816 };
15817 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15818 move |(_, template)| {
15819 template.tags.iter().any(|source_tag| source_tag == &tag)
15820 },
15821 ));
15822 }
15823 }
15824 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15825
15826 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15827 // Strongest source wins; if we have worktree tag binding, prefer that to
15828 // global and language bindings;
15829 // if we have a global binding, prefer that to language binding.
15830 let first_mismatch = templates_with_tags
15831 .iter()
15832 .position(|(tag_source, _)| tag_source != leading_tag_source);
15833 if let Some(index) = first_mismatch {
15834 templates_with_tags.truncate(index);
15835 }
15836 }
15837
15838 templates_with_tags
15839 })
15840 }
15841
15842 pub fn move_to_enclosing_bracket(
15843 &mut self,
15844 _: &MoveToEnclosingBracket,
15845 window: &mut Window,
15846 cx: &mut Context<Self>,
15847 ) {
15848 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15849 self.change_selections(Default::default(), window, cx, |s| {
15850 s.move_offsets_with(|snapshot, selection| {
15851 let Some(enclosing_bracket_ranges) =
15852 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15853 else {
15854 return;
15855 };
15856
15857 let mut best_length = usize::MAX;
15858 let mut best_inside = false;
15859 let mut best_in_bracket_range = false;
15860 let mut best_destination = None;
15861 for (open, close) in enclosing_bracket_ranges {
15862 let close = close.to_inclusive();
15863 let length = close.end() - open.start;
15864 let inside = selection.start >= open.end && selection.end <= *close.start();
15865 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15866 || close.contains(&selection.head());
15867
15868 // If best is next to a bracket and current isn't, skip
15869 if !in_bracket_range && best_in_bracket_range {
15870 continue;
15871 }
15872
15873 // Prefer smaller lengths unless best is inside and current isn't
15874 if length > best_length && (best_inside || !inside) {
15875 continue;
15876 }
15877
15878 best_length = length;
15879 best_inside = inside;
15880 best_in_bracket_range = in_bracket_range;
15881 best_destination = Some(
15882 if close.contains(&selection.start) && close.contains(&selection.end) {
15883 if inside { open.end } else { open.start }
15884 } else if inside {
15885 *close.start()
15886 } else {
15887 *close.end()
15888 },
15889 );
15890 }
15891
15892 if let Some(destination) = best_destination {
15893 selection.collapse_to(destination, SelectionGoal::None);
15894 }
15895 })
15896 });
15897 }
15898
15899 pub fn undo_selection(
15900 &mut self,
15901 _: &UndoSelection,
15902 window: &mut Window,
15903 cx: &mut Context<Self>,
15904 ) {
15905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15906 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15907 self.selection_history.mode = SelectionHistoryMode::Undoing;
15908 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15909 this.end_selection(window, cx);
15910 this.change_selections(
15911 SelectionEffects::scroll(Autoscroll::newest()),
15912 window,
15913 cx,
15914 |s| s.select_anchors(entry.selections.to_vec()),
15915 );
15916 });
15917 self.selection_history.mode = SelectionHistoryMode::Normal;
15918
15919 self.select_next_state = entry.select_next_state;
15920 self.select_prev_state = entry.select_prev_state;
15921 self.add_selections_state = entry.add_selections_state;
15922 }
15923 }
15924
15925 pub fn redo_selection(
15926 &mut self,
15927 _: &RedoSelection,
15928 window: &mut Window,
15929 cx: &mut Context<Self>,
15930 ) {
15931 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15932 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15933 self.selection_history.mode = SelectionHistoryMode::Redoing;
15934 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15935 this.end_selection(window, cx);
15936 this.change_selections(
15937 SelectionEffects::scroll(Autoscroll::newest()),
15938 window,
15939 cx,
15940 |s| s.select_anchors(entry.selections.to_vec()),
15941 );
15942 });
15943 self.selection_history.mode = SelectionHistoryMode::Normal;
15944
15945 self.select_next_state = entry.select_next_state;
15946 self.select_prev_state = entry.select_prev_state;
15947 self.add_selections_state = entry.add_selections_state;
15948 }
15949 }
15950
15951 pub fn expand_excerpts(
15952 &mut self,
15953 action: &ExpandExcerpts,
15954 _: &mut Window,
15955 cx: &mut Context<Self>,
15956 ) {
15957 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15958 }
15959
15960 pub fn expand_excerpts_down(
15961 &mut self,
15962 action: &ExpandExcerptsDown,
15963 _: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) {
15966 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
15967 }
15968
15969 pub fn expand_excerpts_up(
15970 &mut self,
15971 action: &ExpandExcerptsUp,
15972 _: &mut Window,
15973 cx: &mut Context<Self>,
15974 ) {
15975 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
15976 }
15977
15978 pub fn expand_excerpts_for_direction(
15979 &mut self,
15980 lines: u32,
15981 direction: ExpandExcerptDirection,
15982
15983 cx: &mut Context<Self>,
15984 ) {
15985 let selections = self.selections.disjoint_anchors_arc();
15986
15987 let lines = if lines == 0 {
15988 EditorSettings::get_global(cx).expand_excerpt_lines
15989 } else {
15990 lines
15991 };
15992
15993 self.buffer.update(cx, |buffer, cx| {
15994 let snapshot = buffer.snapshot(cx);
15995 let mut excerpt_ids = selections
15996 .iter()
15997 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
15998 .collect::<Vec<_>>();
15999 excerpt_ids.sort();
16000 excerpt_ids.dedup();
16001 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16002 })
16003 }
16004
16005 pub fn expand_excerpt(
16006 &mut self,
16007 excerpt: ExcerptId,
16008 direction: ExpandExcerptDirection,
16009 window: &mut Window,
16010 cx: &mut Context<Self>,
16011 ) {
16012 let current_scroll_position = self.scroll_position(cx);
16013 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16014 let mut scroll = None;
16015
16016 if direction == ExpandExcerptDirection::Down {
16017 let multi_buffer = self.buffer.read(cx);
16018 let snapshot = multi_buffer.snapshot(cx);
16019 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16020 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16021 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16022 {
16023 let buffer_snapshot = buffer.read(cx).snapshot();
16024 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16025 let last_row = buffer_snapshot.max_point().row;
16026 let lines_below = last_row.saturating_sub(excerpt_end_row);
16027 if lines_below >= lines_to_expand {
16028 scroll = Some(
16029 current_scroll_position
16030 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16031 );
16032 }
16033 }
16034 }
16035 if direction == ExpandExcerptDirection::Up
16036 && self
16037 .buffer
16038 .read(cx)
16039 .snapshot(cx)
16040 .excerpt_before(excerpt)
16041 .is_none()
16042 {
16043 scroll = Some(current_scroll_position);
16044 }
16045
16046 self.buffer.update(cx, |buffer, cx| {
16047 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16048 });
16049
16050 if let Some(new_scroll_position) = scroll {
16051 self.set_scroll_position(new_scroll_position, window, cx);
16052 }
16053 }
16054
16055 pub fn go_to_singleton_buffer_point(
16056 &mut self,
16057 point: Point,
16058 window: &mut Window,
16059 cx: &mut Context<Self>,
16060 ) {
16061 self.go_to_singleton_buffer_range(point..point, window, cx);
16062 }
16063
16064 pub fn go_to_singleton_buffer_range(
16065 &mut self,
16066 range: Range<Point>,
16067 window: &mut Window,
16068 cx: &mut Context<Self>,
16069 ) {
16070 let multibuffer = self.buffer().read(cx);
16071 let Some(buffer) = multibuffer.as_singleton() else {
16072 return;
16073 };
16074 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16075 return;
16076 };
16077 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16078 return;
16079 };
16080 self.change_selections(
16081 SelectionEffects::default().nav_history(true),
16082 window,
16083 cx,
16084 |s| s.select_anchor_ranges([start..end]),
16085 );
16086 }
16087
16088 pub fn go_to_diagnostic(
16089 &mut self,
16090 action: &GoToDiagnostic,
16091 window: &mut Window,
16092 cx: &mut Context<Self>,
16093 ) {
16094 if !self.diagnostics_enabled() {
16095 return;
16096 }
16097 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16098 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16099 }
16100
16101 pub fn go_to_prev_diagnostic(
16102 &mut self,
16103 action: &GoToPreviousDiagnostic,
16104 window: &mut Window,
16105 cx: &mut Context<Self>,
16106 ) {
16107 if !self.diagnostics_enabled() {
16108 return;
16109 }
16110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16111 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16112 }
16113
16114 pub fn go_to_diagnostic_impl(
16115 &mut self,
16116 direction: Direction,
16117 severity: GoToDiagnosticSeverityFilter,
16118 window: &mut Window,
16119 cx: &mut Context<Self>,
16120 ) {
16121 let buffer = self.buffer.read(cx).snapshot(cx);
16122 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16123
16124 let mut active_group_id = None;
16125 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16126 && active_group.active_range.start.to_offset(&buffer) == selection.start
16127 {
16128 active_group_id = Some(active_group.group_id);
16129 }
16130
16131 fn filtered<'a>(
16132 severity: GoToDiagnosticSeverityFilter,
16133 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16134 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16135 diagnostics
16136 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16137 .filter(|entry| entry.range.start != entry.range.end)
16138 .filter(|entry| !entry.diagnostic.is_unnecessary)
16139 }
16140
16141 let before = filtered(
16142 severity,
16143 buffer
16144 .diagnostics_in_range(0..selection.start)
16145 .filter(|entry| entry.range.start <= selection.start),
16146 );
16147 let after = filtered(
16148 severity,
16149 buffer
16150 .diagnostics_in_range(selection.start..buffer.len())
16151 .filter(|entry| entry.range.start >= selection.start),
16152 );
16153
16154 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16155 if direction == Direction::Prev {
16156 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16157 {
16158 for diagnostic in prev_diagnostics.into_iter().rev() {
16159 if diagnostic.range.start != selection.start
16160 || active_group_id
16161 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16162 {
16163 found = Some(diagnostic);
16164 break 'outer;
16165 }
16166 }
16167 }
16168 } else {
16169 for diagnostic in after.chain(before) {
16170 if diagnostic.range.start != selection.start
16171 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16172 {
16173 found = Some(diagnostic);
16174 break;
16175 }
16176 }
16177 }
16178 let Some(next_diagnostic) = found else {
16179 return;
16180 };
16181
16182 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16183 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16184 return;
16185 };
16186 let snapshot = self.snapshot(window, cx);
16187 if snapshot.intersects_fold(next_diagnostic.range.start) {
16188 self.unfold_ranges(
16189 std::slice::from_ref(&next_diagnostic.range),
16190 true,
16191 false,
16192 cx,
16193 );
16194 }
16195 self.change_selections(Default::default(), window, cx, |s| {
16196 s.select_ranges(vec![
16197 next_diagnostic.range.start..next_diagnostic.range.start,
16198 ])
16199 });
16200 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16201 self.refresh_edit_prediction(false, true, window, cx);
16202 }
16203
16204 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16205 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16206 let snapshot = self.snapshot(window, cx);
16207 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16208 self.go_to_hunk_before_or_after_position(
16209 &snapshot,
16210 selection.head(),
16211 Direction::Next,
16212 window,
16213 cx,
16214 );
16215 }
16216
16217 pub fn go_to_hunk_before_or_after_position(
16218 &mut self,
16219 snapshot: &EditorSnapshot,
16220 position: Point,
16221 direction: Direction,
16222 window: &mut Window,
16223 cx: &mut Context<Editor>,
16224 ) {
16225 let row = if direction == Direction::Next {
16226 self.hunk_after_position(snapshot, position)
16227 .map(|hunk| hunk.row_range.start)
16228 } else {
16229 self.hunk_before_position(snapshot, position)
16230 };
16231
16232 if let Some(row) = row {
16233 let destination = Point::new(row.0, 0);
16234 let autoscroll = Autoscroll::center();
16235
16236 self.unfold_ranges(&[destination..destination], false, false, cx);
16237 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16238 s.select_ranges([destination..destination]);
16239 });
16240 }
16241 }
16242
16243 fn hunk_after_position(
16244 &mut self,
16245 snapshot: &EditorSnapshot,
16246 position: Point,
16247 ) -> Option<MultiBufferDiffHunk> {
16248 snapshot
16249 .buffer_snapshot()
16250 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16251 .find(|hunk| hunk.row_range.start.0 > position.row)
16252 .or_else(|| {
16253 snapshot
16254 .buffer_snapshot()
16255 .diff_hunks_in_range(Point::zero()..position)
16256 .find(|hunk| hunk.row_range.end.0 < position.row)
16257 })
16258 }
16259
16260 fn go_to_prev_hunk(
16261 &mut self,
16262 _: &GoToPreviousHunk,
16263 window: &mut Window,
16264 cx: &mut Context<Self>,
16265 ) {
16266 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16267 let snapshot = self.snapshot(window, cx);
16268 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16269 self.go_to_hunk_before_or_after_position(
16270 &snapshot,
16271 selection.head(),
16272 Direction::Prev,
16273 window,
16274 cx,
16275 );
16276 }
16277
16278 fn hunk_before_position(
16279 &mut self,
16280 snapshot: &EditorSnapshot,
16281 position: Point,
16282 ) -> Option<MultiBufferRow> {
16283 snapshot
16284 .buffer_snapshot()
16285 .diff_hunk_before(position)
16286 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16287 }
16288
16289 fn go_to_next_change(
16290 &mut self,
16291 _: &GoToNextChange,
16292 window: &mut Window,
16293 cx: &mut Context<Self>,
16294 ) {
16295 if let Some(selections) = self
16296 .change_list
16297 .next_change(1, Direction::Next)
16298 .map(|s| s.to_vec())
16299 {
16300 self.change_selections(Default::default(), window, cx, |s| {
16301 let map = s.display_snapshot();
16302 s.select_display_ranges(selections.iter().map(|a| {
16303 let point = a.to_display_point(&map);
16304 point..point
16305 }))
16306 })
16307 }
16308 }
16309
16310 fn go_to_previous_change(
16311 &mut self,
16312 _: &GoToPreviousChange,
16313 window: &mut Window,
16314 cx: &mut Context<Self>,
16315 ) {
16316 if let Some(selections) = self
16317 .change_list
16318 .next_change(1, Direction::Prev)
16319 .map(|s| s.to_vec())
16320 {
16321 self.change_selections(Default::default(), window, cx, |s| {
16322 let map = s.display_snapshot();
16323 s.select_display_ranges(selections.iter().map(|a| {
16324 let point = a.to_display_point(&map);
16325 point..point
16326 }))
16327 })
16328 }
16329 }
16330
16331 pub fn go_to_next_document_highlight(
16332 &mut self,
16333 _: &GoToNextDocumentHighlight,
16334 window: &mut Window,
16335 cx: &mut Context<Self>,
16336 ) {
16337 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16338 }
16339
16340 pub fn go_to_prev_document_highlight(
16341 &mut self,
16342 _: &GoToPreviousDocumentHighlight,
16343 window: &mut Window,
16344 cx: &mut Context<Self>,
16345 ) {
16346 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16347 }
16348
16349 pub fn go_to_document_highlight_before_or_after_position(
16350 &mut self,
16351 direction: Direction,
16352 window: &mut Window,
16353 cx: &mut Context<Editor>,
16354 ) {
16355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16356 let snapshot = self.snapshot(window, cx);
16357 let buffer = &snapshot.buffer_snapshot();
16358 let position = self
16359 .selections
16360 .newest::<Point>(&snapshot.display_snapshot)
16361 .head();
16362 let anchor_position = buffer.anchor_after(position);
16363
16364 // Get all document highlights (both read and write)
16365 let mut all_highlights = Vec::new();
16366
16367 if let Some((_, read_highlights)) = self
16368 .background_highlights
16369 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16370 {
16371 all_highlights.extend(read_highlights.iter());
16372 }
16373
16374 if let Some((_, write_highlights)) = self
16375 .background_highlights
16376 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16377 {
16378 all_highlights.extend(write_highlights.iter());
16379 }
16380
16381 if all_highlights.is_empty() {
16382 return;
16383 }
16384
16385 // Sort highlights by position
16386 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16387
16388 let target_highlight = match direction {
16389 Direction::Next => {
16390 // Find the first highlight after the current position
16391 all_highlights
16392 .iter()
16393 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16394 }
16395 Direction::Prev => {
16396 // Find the last highlight before the current position
16397 all_highlights
16398 .iter()
16399 .rev()
16400 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16401 }
16402 };
16403
16404 if let Some(highlight) = target_highlight {
16405 let destination = highlight.start.to_point(buffer);
16406 let autoscroll = Autoscroll::center();
16407
16408 self.unfold_ranges(&[destination..destination], false, false, cx);
16409 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16410 s.select_ranges([destination..destination]);
16411 });
16412 }
16413 }
16414
16415 fn go_to_line<T: 'static>(
16416 &mut self,
16417 position: Anchor,
16418 highlight_color: Option<Hsla>,
16419 window: &mut Window,
16420 cx: &mut Context<Self>,
16421 ) {
16422 let snapshot = self.snapshot(window, cx).display_snapshot;
16423 let position = position.to_point(&snapshot.buffer_snapshot());
16424 let start = snapshot
16425 .buffer_snapshot()
16426 .clip_point(Point::new(position.row, 0), Bias::Left);
16427 let end = start + Point::new(1, 0);
16428 let start = snapshot.buffer_snapshot().anchor_before(start);
16429 let end = snapshot.buffer_snapshot().anchor_before(end);
16430
16431 self.highlight_rows::<T>(
16432 start..end,
16433 highlight_color
16434 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16435 Default::default(),
16436 cx,
16437 );
16438
16439 if self.buffer.read(cx).is_singleton() {
16440 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16441 }
16442 }
16443
16444 pub fn go_to_definition(
16445 &mut self,
16446 _: &GoToDefinition,
16447 window: &mut Window,
16448 cx: &mut Context<Self>,
16449 ) -> Task<Result<Navigated>> {
16450 let definition =
16451 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16452 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16453 cx.spawn_in(window, async move |editor, cx| {
16454 if definition.await? == Navigated::Yes {
16455 return Ok(Navigated::Yes);
16456 }
16457 match fallback_strategy {
16458 GoToDefinitionFallback::None => Ok(Navigated::No),
16459 GoToDefinitionFallback::FindAllReferences => {
16460 match editor.update_in(cx, |editor, window, cx| {
16461 editor.find_all_references(&FindAllReferences, window, cx)
16462 })? {
16463 Some(references) => references.await,
16464 None => Ok(Navigated::No),
16465 }
16466 }
16467 }
16468 })
16469 }
16470
16471 pub fn go_to_declaration(
16472 &mut self,
16473 _: &GoToDeclaration,
16474 window: &mut Window,
16475 cx: &mut Context<Self>,
16476 ) -> Task<Result<Navigated>> {
16477 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16478 }
16479
16480 pub fn go_to_declaration_split(
16481 &mut self,
16482 _: &GoToDeclaration,
16483 window: &mut Window,
16484 cx: &mut Context<Self>,
16485 ) -> Task<Result<Navigated>> {
16486 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16487 }
16488
16489 pub fn go_to_implementation(
16490 &mut self,
16491 _: &GoToImplementation,
16492 window: &mut Window,
16493 cx: &mut Context<Self>,
16494 ) -> Task<Result<Navigated>> {
16495 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16496 }
16497
16498 pub fn go_to_implementation_split(
16499 &mut self,
16500 _: &GoToImplementationSplit,
16501 window: &mut Window,
16502 cx: &mut Context<Self>,
16503 ) -> Task<Result<Navigated>> {
16504 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16505 }
16506
16507 pub fn go_to_type_definition(
16508 &mut self,
16509 _: &GoToTypeDefinition,
16510 window: &mut Window,
16511 cx: &mut Context<Self>,
16512 ) -> Task<Result<Navigated>> {
16513 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16514 }
16515
16516 pub fn go_to_definition_split(
16517 &mut self,
16518 _: &GoToDefinitionSplit,
16519 window: &mut Window,
16520 cx: &mut Context<Self>,
16521 ) -> Task<Result<Navigated>> {
16522 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16523 }
16524
16525 pub fn go_to_type_definition_split(
16526 &mut self,
16527 _: &GoToTypeDefinitionSplit,
16528 window: &mut Window,
16529 cx: &mut Context<Self>,
16530 ) -> Task<Result<Navigated>> {
16531 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16532 }
16533
16534 fn go_to_definition_of_kind(
16535 &mut self,
16536 kind: GotoDefinitionKind,
16537 split: bool,
16538 window: &mut Window,
16539 cx: &mut Context<Self>,
16540 ) -> Task<Result<Navigated>> {
16541 let Some(provider) = self.semantics_provider.clone() else {
16542 return Task::ready(Ok(Navigated::No));
16543 };
16544 let head = self
16545 .selections
16546 .newest::<usize>(&self.display_snapshot(cx))
16547 .head();
16548 let buffer = self.buffer.read(cx);
16549 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16550 return Task::ready(Ok(Navigated::No));
16551 };
16552 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16553 return Task::ready(Ok(Navigated::No));
16554 };
16555
16556 cx.spawn_in(window, async move |editor, cx| {
16557 let Some(definitions) = definitions.await? else {
16558 return Ok(Navigated::No);
16559 };
16560 let navigated = editor
16561 .update_in(cx, |editor, window, cx| {
16562 editor.navigate_to_hover_links(
16563 Some(kind),
16564 definitions
16565 .into_iter()
16566 .filter(|location| {
16567 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16568 })
16569 .map(HoverLink::Text)
16570 .collect::<Vec<_>>(),
16571 split,
16572 window,
16573 cx,
16574 )
16575 })?
16576 .await?;
16577 anyhow::Ok(navigated)
16578 })
16579 }
16580
16581 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16582 let selection = self.selections.newest_anchor();
16583 let head = selection.head();
16584 let tail = selection.tail();
16585
16586 let Some((buffer, start_position)) =
16587 self.buffer.read(cx).text_anchor_for_position(head, cx)
16588 else {
16589 return;
16590 };
16591
16592 let end_position = if head != tail {
16593 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16594 return;
16595 };
16596 Some(pos)
16597 } else {
16598 None
16599 };
16600
16601 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16602 let url = if let Some(end_pos) = end_position {
16603 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16604 } else {
16605 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16606 };
16607
16608 if let Some(url) = url {
16609 cx.update(|window, cx| {
16610 if parse_zed_link(&url, cx).is_some() {
16611 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16612 } else {
16613 cx.open_url(&url);
16614 }
16615 })?;
16616 }
16617
16618 anyhow::Ok(())
16619 });
16620
16621 url_finder.detach();
16622 }
16623
16624 pub fn open_selected_filename(
16625 &mut self,
16626 _: &OpenSelectedFilename,
16627 window: &mut Window,
16628 cx: &mut Context<Self>,
16629 ) {
16630 let Some(workspace) = self.workspace() else {
16631 return;
16632 };
16633
16634 let position = self.selections.newest_anchor().head();
16635
16636 let Some((buffer, buffer_position)) =
16637 self.buffer.read(cx).text_anchor_for_position(position, cx)
16638 else {
16639 return;
16640 };
16641
16642 let project = self.project.clone();
16643
16644 cx.spawn_in(window, async move |_, cx| {
16645 let result = find_file(&buffer, project, buffer_position, cx).await;
16646
16647 if let Some((_, path)) = result {
16648 workspace
16649 .update_in(cx, |workspace, window, cx| {
16650 workspace.open_resolved_path(path, window, cx)
16651 })?
16652 .await?;
16653 }
16654 anyhow::Ok(())
16655 })
16656 .detach();
16657 }
16658
16659 pub(crate) fn navigate_to_hover_links(
16660 &mut self,
16661 kind: Option<GotoDefinitionKind>,
16662 definitions: Vec<HoverLink>,
16663 split: bool,
16664 window: &mut Window,
16665 cx: &mut Context<Editor>,
16666 ) -> Task<Result<Navigated>> {
16667 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16668 let mut first_url_or_file = None;
16669 let definitions: Vec<_> = definitions
16670 .into_iter()
16671 .filter_map(|def| match def {
16672 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16673 HoverLink::InlayHint(lsp_location, server_id) => {
16674 let computation =
16675 self.compute_target_location(lsp_location, server_id, window, cx);
16676 Some(cx.background_spawn(computation))
16677 }
16678 HoverLink::Url(url) => {
16679 first_url_or_file = Some(Either::Left(url));
16680 None
16681 }
16682 HoverLink::File(path) => {
16683 first_url_or_file = Some(Either::Right(path));
16684 None
16685 }
16686 })
16687 .collect();
16688
16689 let workspace = self.workspace();
16690
16691 cx.spawn_in(window, async move |editor, cx| {
16692 let locations: Vec<Location> = future::join_all(definitions)
16693 .await
16694 .into_iter()
16695 .filter_map(|location| location.transpose())
16696 .collect::<Result<_>>()
16697 .context("location tasks")?;
16698 let mut locations = cx.update(|_, cx| {
16699 locations
16700 .into_iter()
16701 .map(|location| {
16702 let buffer = location.buffer.read(cx);
16703 (location.buffer, location.range.to_point(buffer))
16704 })
16705 .into_group_map()
16706 })?;
16707 let mut num_locations = 0;
16708 for ranges in locations.values_mut() {
16709 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16710 ranges.dedup();
16711 num_locations += ranges.len();
16712 }
16713
16714 if num_locations > 1 {
16715 let Some(workspace) = workspace else {
16716 return Ok(Navigated::No);
16717 };
16718
16719 let tab_kind = match kind {
16720 Some(GotoDefinitionKind::Implementation) => "Implementations",
16721 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16722 Some(GotoDefinitionKind::Declaration) => "Declarations",
16723 Some(GotoDefinitionKind::Type) => "Types",
16724 };
16725 let title = editor
16726 .update_in(cx, |_, _, cx| {
16727 let target = locations
16728 .iter()
16729 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16730 .map(|(buffer, location)| {
16731 buffer
16732 .read(cx)
16733 .text_for_range(location.clone())
16734 .collect::<String>()
16735 })
16736 .filter(|text| !text.contains('\n'))
16737 .unique()
16738 .take(3)
16739 .join(", ");
16740 if target.is_empty() {
16741 tab_kind.to_owned()
16742 } else {
16743 format!("{tab_kind} for {target}")
16744 }
16745 })
16746 .context("buffer title")?;
16747
16748 let opened = workspace
16749 .update_in(cx, |workspace, window, cx| {
16750 Self::open_locations_in_multibuffer(
16751 workspace,
16752 locations,
16753 title,
16754 split,
16755 MultibufferSelectionMode::First,
16756 window,
16757 cx,
16758 )
16759 })
16760 .is_ok();
16761
16762 anyhow::Ok(Navigated::from_bool(opened))
16763 } else if num_locations == 0 {
16764 // If there is one url or file, open it directly
16765 match first_url_or_file {
16766 Some(Either::Left(url)) => {
16767 cx.update(|_, cx| cx.open_url(&url))?;
16768 Ok(Navigated::Yes)
16769 }
16770 Some(Either::Right(path)) => {
16771 let Some(workspace) = workspace else {
16772 return Ok(Navigated::No);
16773 };
16774
16775 workspace
16776 .update_in(cx, |workspace, window, cx| {
16777 workspace.open_resolved_path(path, window, cx)
16778 })?
16779 .await?;
16780 Ok(Navigated::Yes)
16781 }
16782 None => Ok(Navigated::No),
16783 }
16784 } else {
16785 let Some(workspace) = workspace else {
16786 return Ok(Navigated::No);
16787 };
16788
16789 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16790 let target_range = target_ranges.first().unwrap().clone();
16791
16792 editor.update_in(cx, |editor, window, cx| {
16793 let range = target_range.to_point(target_buffer.read(cx));
16794 let range = editor.range_for_match(&range, false);
16795 let range = collapse_multiline_range(range);
16796
16797 if !split
16798 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16799 {
16800 editor.go_to_singleton_buffer_range(range, window, cx);
16801 } else {
16802 let pane = workspace.read(cx).active_pane().clone();
16803 window.defer(cx, move |window, cx| {
16804 let target_editor: Entity<Self> =
16805 workspace.update(cx, |workspace, cx| {
16806 let pane = if split {
16807 workspace.adjacent_pane(window, cx)
16808 } else {
16809 workspace.active_pane().clone()
16810 };
16811
16812 workspace.open_project_item(
16813 pane,
16814 target_buffer.clone(),
16815 true,
16816 true,
16817 window,
16818 cx,
16819 )
16820 });
16821 target_editor.update(cx, |target_editor, cx| {
16822 // When selecting a definition in a different buffer, disable the nav history
16823 // to avoid creating a history entry at the previous cursor location.
16824 pane.update(cx, |pane, _| pane.disable_history());
16825 target_editor.go_to_singleton_buffer_range(range, window, cx);
16826 pane.update(cx, |pane, _| pane.enable_history());
16827 });
16828 });
16829 }
16830 Navigated::Yes
16831 })
16832 }
16833 })
16834 }
16835
16836 fn compute_target_location(
16837 &self,
16838 lsp_location: lsp::Location,
16839 server_id: LanguageServerId,
16840 window: &mut Window,
16841 cx: &mut Context<Self>,
16842 ) -> Task<anyhow::Result<Option<Location>>> {
16843 let Some(project) = self.project.clone() else {
16844 return Task::ready(Ok(None));
16845 };
16846
16847 cx.spawn_in(window, async move |editor, cx| {
16848 let location_task = editor.update(cx, |_, cx| {
16849 project.update(cx, |project, cx| {
16850 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16851 })
16852 })?;
16853 let location = Some({
16854 let target_buffer_handle = location_task.await.context("open local buffer")?;
16855 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16856 let target_start = target_buffer
16857 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16858 let target_end = target_buffer
16859 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16860 target_buffer.anchor_after(target_start)
16861 ..target_buffer.anchor_before(target_end)
16862 })?;
16863 Location {
16864 buffer: target_buffer_handle,
16865 range,
16866 }
16867 });
16868 Ok(location)
16869 })
16870 }
16871
16872 fn go_to_next_reference(
16873 &mut self,
16874 _: &GoToNextReference,
16875 window: &mut Window,
16876 cx: &mut Context<Self>,
16877 ) {
16878 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16879 if let Some(task) = task {
16880 task.detach();
16881 };
16882 }
16883
16884 fn go_to_prev_reference(
16885 &mut self,
16886 _: &GoToPreviousReference,
16887 window: &mut Window,
16888 cx: &mut Context<Self>,
16889 ) {
16890 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16891 if let Some(task) = task {
16892 task.detach();
16893 };
16894 }
16895
16896 pub fn go_to_reference_before_or_after_position(
16897 &mut self,
16898 direction: Direction,
16899 count: usize,
16900 window: &mut Window,
16901 cx: &mut Context<Self>,
16902 ) -> Option<Task<Result<()>>> {
16903 let selection = self.selections.newest_anchor();
16904 let head = selection.head();
16905
16906 let multi_buffer = self.buffer.read(cx);
16907
16908 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16909 let workspace = self.workspace()?;
16910 let project = workspace.read(cx).project().clone();
16911 let references =
16912 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16913 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16914 let Some(locations) = references.await? else {
16915 return Ok(());
16916 };
16917
16918 if locations.is_empty() {
16919 // totally normal - the cursor may be on something which is not
16920 // a symbol (e.g. a keyword)
16921 log::info!("no references found under cursor");
16922 return Ok(());
16923 }
16924
16925 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16926
16927 let multi_buffer_snapshot =
16928 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16929
16930 let (locations, current_location_index) =
16931 multi_buffer.update(cx, |multi_buffer, cx| {
16932 let mut locations = locations
16933 .into_iter()
16934 .filter_map(|loc| {
16935 let start = multi_buffer.buffer_anchor_to_anchor(
16936 &loc.buffer,
16937 loc.range.start,
16938 cx,
16939 )?;
16940 let end = multi_buffer.buffer_anchor_to_anchor(
16941 &loc.buffer,
16942 loc.range.end,
16943 cx,
16944 )?;
16945 Some(start..end)
16946 })
16947 .collect::<Vec<_>>();
16948
16949 // There is an O(n) implementation, but given this list will be
16950 // small (usually <100 items), the extra O(log(n)) factor isn't
16951 // worth the (surprisingly large amount of) extra complexity.
16952 locations
16953 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16954
16955 let head_offset = head.to_offset(&multi_buffer_snapshot);
16956
16957 let current_location_index = locations.iter().position(|loc| {
16958 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16959 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16960 });
16961
16962 (locations, current_location_index)
16963 })?;
16964
16965 let Some(current_location_index) = current_location_index else {
16966 // This indicates something has gone wrong, because we already
16967 // handle the "no references" case above
16968 log::error!(
16969 "failed to find current reference under cursor. Total references: {}",
16970 locations.len()
16971 );
16972 return Ok(());
16973 };
16974
16975 let destination_location_index = match direction {
16976 Direction::Next => (current_location_index + count) % locations.len(),
16977 Direction::Prev => {
16978 (current_location_index + locations.len() - count % locations.len())
16979 % locations.len()
16980 }
16981 };
16982
16983 // TODO(cameron): is this needed?
16984 // the thinking is to avoid "jumping to the current location" (avoid
16985 // polluting "jumplist" in vim terms)
16986 if current_location_index == destination_location_index {
16987 return Ok(());
16988 }
16989
16990 let Range { start, end } = locations[destination_location_index];
16991
16992 editor.update_in(cx, |editor, window, cx| {
16993 let effects = SelectionEffects::default();
16994
16995 editor.unfold_ranges(&[start..end], false, false, cx);
16996 editor.change_selections(effects, window, cx, |s| {
16997 s.select_ranges([start..start]);
16998 });
16999 })?;
17000
17001 Ok(())
17002 }))
17003 }
17004
17005 pub fn find_all_references(
17006 &mut self,
17007 _: &FindAllReferences,
17008 window: &mut Window,
17009 cx: &mut Context<Self>,
17010 ) -> Option<Task<Result<Navigated>>> {
17011 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
17012 let multi_buffer = self.buffer.read(cx);
17013 let head = selection.head();
17014
17015 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17016 let head_anchor = multi_buffer_snapshot.anchor_at(
17017 head,
17018 if head < selection.tail() {
17019 Bias::Right
17020 } else {
17021 Bias::Left
17022 },
17023 );
17024
17025 match self
17026 .find_all_references_task_sources
17027 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17028 {
17029 Ok(_) => {
17030 log::info!(
17031 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17032 );
17033 return None;
17034 }
17035 Err(i) => {
17036 self.find_all_references_task_sources.insert(i, head_anchor);
17037 }
17038 }
17039
17040 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17041 let workspace = self.workspace()?;
17042 let project = workspace.read(cx).project().clone();
17043 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17044 Some(cx.spawn_in(window, async move |editor, cx| {
17045 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17046 if let Ok(i) = editor
17047 .find_all_references_task_sources
17048 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17049 {
17050 editor.find_all_references_task_sources.remove(i);
17051 }
17052 });
17053
17054 let Some(locations) = references.await? else {
17055 return anyhow::Ok(Navigated::No);
17056 };
17057 let mut locations = cx.update(|_, cx| {
17058 locations
17059 .into_iter()
17060 .map(|location| {
17061 let buffer = location.buffer.read(cx);
17062 (location.buffer, location.range.to_point(buffer))
17063 })
17064 .into_group_map()
17065 })?;
17066 if locations.is_empty() {
17067 return anyhow::Ok(Navigated::No);
17068 }
17069 for ranges in locations.values_mut() {
17070 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17071 ranges.dedup();
17072 }
17073
17074 workspace.update_in(cx, |workspace, window, cx| {
17075 let target = locations
17076 .iter()
17077 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17078 .map(|(buffer, location)| {
17079 buffer
17080 .read(cx)
17081 .text_for_range(location.clone())
17082 .collect::<String>()
17083 })
17084 .filter(|text| !text.contains('\n'))
17085 .unique()
17086 .take(3)
17087 .join(", ");
17088 let title = if target.is_empty() {
17089 "References".to_owned()
17090 } else {
17091 format!("References to {target}")
17092 };
17093 Self::open_locations_in_multibuffer(
17094 workspace,
17095 locations,
17096 title,
17097 false,
17098 MultibufferSelectionMode::First,
17099 window,
17100 cx,
17101 );
17102 Navigated::Yes
17103 })
17104 }))
17105 }
17106
17107 /// Opens a multibuffer with the given project locations in it
17108 pub fn open_locations_in_multibuffer(
17109 workspace: &mut Workspace,
17110 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17111 title: String,
17112 split: bool,
17113 multibuffer_selection_mode: MultibufferSelectionMode,
17114 window: &mut Window,
17115 cx: &mut Context<Workspace>,
17116 ) {
17117 if locations.is_empty() {
17118 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17119 return;
17120 }
17121
17122 let capability = workspace.project().read(cx).capability();
17123 let mut ranges = <Vec<Range<Anchor>>>::new();
17124
17125 // a key to find existing multibuffer editors with the same set of locations
17126 // to prevent us from opening more and more multibuffer tabs for searches and the like
17127 let mut key = (title.clone(), vec![]);
17128 let excerpt_buffer = cx.new(|cx| {
17129 let key = &mut key.1;
17130 let mut multibuffer = MultiBuffer::new(capability);
17131 for (buffer, mut ranges_for_buffer) in locations {
17132 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17133 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17134 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17135 PathKey::for_buffer(&buffer, cx),
17136 buffer.clone(),
17137 ranges_for_buffer,
17138 multibuffer_context_lines(cx),
17139 cx,
17140 );
17141 ranges.extend(new_ranges)
17142 }
17143
17144 multibuffer.with_title(title)
17145 });
17146 let existing = workspace.active_pane().update(cx, |pane, cx| {
17147 pane.items()
17148 .filter_map(|item| item.downcast::<Editor>())
17149 .find(|editor| {
17150 editor
17151 .read(cx)
17152 .lookup_key
17153 .as_ref()
17154 .and_then(|it| {
17155 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17156 })
17157 .is_some_and(|it| *it == key)
17158 })
17159 });
17160 let editor = existing.unwrap_or_else(|| {
17161 cx.new(|cx| {
17162 let mut editor = Editor::for_multibuffer(
17163 excerpt_buffer,
17164 Some(workspace.project().clone()),
17165 window,
17166 cx,
17167 );
17168 editor.lookup_key = Some(Box::new(key));
17169 editor
17170 })
17171 });
17172 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17173 MultibufferSelectionMode::First => {
17174 if let Some(first_range) = ranges.first() {
17175 editor.change_selections(
17176 SelectionEffects::no_scroll(),
17177 window,
17178 cx,
17179 |selections| {
17180 selections.clear_disjoint();
17181 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17182 },
17183 );
17184 }
17185 editor.highlight_background::<Self>(
17186 &ranges,
17187 |theme| theme.colors().editor_highlighted_line_background,
17188 cx,
17189 );
17190 }
17191 MultibufferSelectionMode::All => {
17192 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17193 selections.clear_disjoint();
17194 selections.select_anchor_ranges(ranges);
17195 });
17196 }
17197 });
17198
17199 let item = Box::new(editor);
17200 let item_id = item.item_id();
17201
17202 if split {
17203 let pane = workspace.adjacent_pane(window, cx);
17204 workspace.add_item(pane, item, None, true, true, window, cx);
17205 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17206 let (preview_item_id, preview_item_idx) =
17207 workspace.active_pane().read_with(cx, |pane, _| {
17208 (pane.preview_item_id(), pane.preview_item_idx())
17209 });
17210
17211 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17212
17213 if let Some(preview_item_id) = preview_item_id {
17214 workspace.active_pane().update(cx, |pane, cx| {
17215 pane.remove_item(preview_item_id, false, false, window, cx);
17216 });
17217 }
17218 } else {
17219 workspace.add_item_to_active_pane(item, None, true, window, cx);
17220 }
17221 workspace.active_pane().update(cx, |pane, cx| {
17222 pane.set_preview_item_id(Some(item_id), cx);
17223 });
17224 }
17225
17226 pub fn rename(
17227 &mut self,
17228 _: &Rename,
17229 window: &mut Window,
17230 cx: &mut Context<Self>,
17231 ) -> Option<Task<Result<()>>> {
17232 use language::ToOffset as _;
17233
17234 let provider = self.semantics_provider.clone()?;
17235 let selection = self.selections.newest_anchor().clone();
17236 let (cursor_buffer, cursor_buffer_position) = self
17237 .buffer
17238 .read(cx)
17239 .text_anchor_for_position(selection.head(), cx)?;
17240 let (tail_buffer, cursor_buffer_position_end) = self
17241 .buffer
17242 .read(cx)
17243 .text_anchor_for_position(selection.tail(), cx)?;
17244 if tail_buffer != cursor_buffer {
17245 return None;
17246 }
17247
17248 let snapshot = cursor_buffer.read(cx).snapshot();
17249 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17250 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17251 let prepare_rename = provider
17252 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17253 .unwrap_or_else(|| Task::ready(Ok(None)));
17254 drop(snapshot);
17255
17256 Some(cx.spawn_in(window, async move |this, cx| {
17257 let rename_range = if let Some(range) = prepare_rename.await? {
17258 Some(range)
17259 } else {
17260 this.update(cx, |this, cx| {
17261 let buffer = this.buffer.read(cx).snapshot(cx);
17262 let mut buffer_highlights = this
17263 .document_highlights_for_position(selection.head(), &buffer)
17264 .filter(|highlight| {
17265 highlight.start.excerpt_id == selection.head().excerpt_id
17266 && highlight.end.excerpt_id == selection.head().excerpt_id
17267 });
17268 buffer_highlights
17269 .next()
17270 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17271 })?
17272 };
17273 if let Some(rename_range) = rename_range {
17274 this.update_in(cx, |this, window, cx| {
17275 let snapshot = cursor_buffer.read(cx).snapshot();
17276 let rename_buffer_range = rename_range.to_offset(&snapshot);
17277 let cursor_offset_in_rename_range =
17278 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17279 let cursor_offset_in_rename_range_end =
17280 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17281
17282 this.take_rename(false, window, cx);
17283 let buffer = this.buffer.read(cx).read(cx);
17284 let cursor_offset = selection.head().to_offset(&buffer);
17285 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17286 let rename_end = rename_start + rename_buffer_range.len();
17287 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17288 let mut old_highlight_id = None;
17289 let old_name: Arc<str> = buffer
17290 .chunks(rename_start..rename_end, true)
17291 .map(|chunk| {
17292 if old_highlight_id.is_none() {
17293 old_highlight_id = chunk.syntax_highlight_id;
17294 }
17295 chunk.text
17296 })
17297 .collect::<String>()
17298 .into();
17299
17300 drop(buffer);
17301
17302 // Position the selection in the rename editor so that it matches the current selection.
17303 this.show_local_selections = false;
17304 let rename_editor = cx.new(|cx| {
17305 let mut editor = Editor::single_line(window, cx);
17306 editor.buffer.update(cx, |buffer, cx| {
17307 buffer.edit([(0..0, old_name.clone())], None, cx)
17308 });
17309 let rename_selection_range = match cursor_offset_in_rename_range
17310 .cmp(&cursor_offset_in_rename_range_end)
17311 {
17312 Ordering::Equal => {
17313 editor.select_all(&SelectAll, window, cx);
17314 return editor;
17315 }
17316 Ordering::Less => {
17317 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17318 }
17319 Ordering::Greater => {
17320 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17321 }
17322 };
17323 if rename_selection_range.end > old_name.len() {
17324 editor.select_all(&SelectAll, window, cx);
17325 } else {
17326 editor.change_selections(Default::default(), window, cx, |s| {
17327 s.select_ranges([rename_selection_range]);
17328 });
17329 }
17330 editor
17331 });
17332 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17333 if e == &EditorEvent::Focused {
17334 cx.emit(EditorEvent::FocusedIn)
17335 }
17336 })
17337 .detach();
17338
17339 let write_highlights =
17340 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17341 let read_highlights =
17342 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17343 let ranges = write_highlights
17344 .iter()
17345 .flat_map(|(_, ranges)| ranges.iter())
17346 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17347 .cloned()
17348 .collect();
17349
17350 this.highlight_text::<Rename>(
17351 ranges,
17352 HighlightStyle {
17353 fade_out: Some(0.6),
17354 ..Default::default()
17355 },
17356 cx,
17357 );
17358 let rename_focus_handle = rename_editor.focus_handle(cx);
17359 window.focus(&rename_focus_handle);
17360 let block_id = this.insert_blocks(
17361 [BlockProperties {
17362 style: BlockStyle::Flex,
17363 placement: BlockPlacement::Below(range.start),
17364 height: Some(1),
17365 render: Arc::new({
17366 let rename_editor = rename_editor.clone();
17367 move |cx: &mut BlockContext| {
17368 let mut text_style = cx.editor_style.text.clone();
17369 if let Some(highlight_style) = old_highlight_id
17370 .and_then(|h| h.style(&cx.editor_style.syntax))
17371 {
17372 text_style = text_style.highlight(highlight_style);
17373 }
17374 div()
17375 .block_mouse_except_scroll()
17376 .pl(cx.anchor_x)
17377 .child(EditorElement::new(
17378 &rename_editor,
17379 EditorStyle {
17380 background: cx.theme().system().transparent,
17381 local_player: cx.editor_style.local_player,
17382 text: text_style,
17383 scrollbar_width: cx.editor_style.scrollbar_width,
17384 syntax: cx.editor_style.syntax.clone(),
17385 status: cx.editor_style.status.clone(),
17386 inlay_hints_style: HighlightStyle {
17387 font_weight: Some(FontWeight::BOLD),
17388 ..make_inlay_hints_style(cx.app)
17389 },
17390 edit_prediction_styles: make_suggestion_styles(
17391 cx.app,
17392 ),
17393 ..EditorStyle::default()
17394 },
17395 ))
17396 .into_any_element()
17397 }
17398 }),
17399 priority: 0,
17400 }],
17401 Some(Autoscroll::fit()),
17402 cx,
17403 )[0];
17404 this.pending_rename = Some(RenameState {
17405 range,
17406 old_name,
17407 editor: rename_editor,
17408 block_id,
17409 });
17410 })?;
17411 }
17412
17413 Ok(())
17414 }))
17415 }
17416
17417 pub fn confirm_rename(
17418 &mut self,
17419 _: &ConfirmRename,
17420 window: &mut Window,
17421 cx: &mut Context<Self>,
17422 ) -> Option<Task<Result<()>>> {
17423 let rename = self.take_rename(false, window, cx)?;
17424 let workspace = self.workspace()?.downgrade();
17425 let (buffer, start) = self
17426 .buffer
17427 .read(cx)
17428 .text_anchor_for_position(rename.range.start, cx)?;
17429 let (end_buffer, _) = self
17430 .buffer
17431 .read(cx)
17432 .text_anchor_for_position(rename.range.end, cx)?;
17433 if buffer != end_buffer {
17434 return None;
17435 }
17436
17437 let old_name = rename.old_name;
17438 let new_name = rename.editor.read(cx).text(cx);
17439
17440 let rename = self.semantics_provider.as_ref()?.perform_rename(
17441 &buffer,
17442 start,
17443 new_name.clone(),
17444 cx,
17445 )?;
17446
17447 Some(cx.spawn_in(window, async move |editor, cx| {
17448 let project_transaction = rename.await?;
17449 Self::open_project_transaction(
17450 &editor,
17451 workspace,
17452 project_transaction,
17453 format!("Rename: {} → {}", old_name, new_name),
17454 cx,
17455 )
17456 .await?;
17457
17458 editor.update(cx, |editor, cx| {
17459 editor.refresh_document_highlights(cx);
17460 })?;
17461 Ok(())
17462 }))
17463 }
17464
17465 fn take_rename(
17466 &mut self,
17467 moving_cursor: bool,
17468 window: &mut Window,
17469 cx: &mut Context<Self>,
17470 ) -> Option<RenameState> {
17471 let rename = self.pending_rename.take()?;
17472 if rename.editor.focus_handle(cx).is_focused(window) {
17473 window.focus(&self.focus_handle);
17474 }
17475
17476 self.remove_blocks(
17477 [rename.block_id].into_iter().collect(),
17478 Some(Autoscroll::fit()),
17479 cx,
17480 );
17481 self.clear_highlights::<Rename>(cx);
17482 self.show_local_selections = true;
17483
17484 if moving_cursor {
17485 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17486 editor
17487 .selections
17488 .newest::<usize>(&editor.display_snapshot(cx))
17489 .head()
17490 });
17491
17492 // Update the selection to match the position of the selection inside
17493 // the rename editor.
17494 let snapshot = self.buffer.read(cx).read(cx);
17495 let rename_range = rename.range.to_offset(&snapshot);
17496 let cursor_in_editor = snapshot
17497 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17498 .min(rename_range.end);
17499 drop(snapshot);
17500
17501 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17502 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17503 });
17504 } else {
17505 self.refresh_document_highlights(cx);
17506 }
17507
17508 Some(rename)
17509 }
17510
17511 pub fn pending_rename(&self) -> Option<&RenameState> {
17512 self.pending_rename.as_ref()
17513 }
17514
17515 fn format(
17516 &mut self,
17517 _: &Format,
17518 window: &mut Window,
17519 cx: &mut Context<Self>,
17520 ) -> Option<Task<Result<()>>> {
17521 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17522
17523 let project = match &self.project {
17524 Some(project) => project.clone(),
17525 None => return None,
17526 };
17527
17528 Some(self.perform_format(
17529 project,
17530 FormatTrigger::Manual,
17531 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17532 window,
17533 cx,
17534 ))
17535 }
17536
17537 fn format_selections(
17538 &mut self,
17539 _: &FormatSelections,
17540 window: &mut Window,
17541 cx: &mut Context<Self>,
17542 ) -> Option<Task<Result<()>>> {
17543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17544
17545 let project = match &self.project {
17546 Some(project) => project.clone(),
17547 None => return None,
17548 };
17549
17550 let ranges = self
17551 .selections
17552 .all_adjusted(&self.display_snapshot(cx))
17553 .into_iter()
17554 .map(|selection| selection.range())
17555 .collect_vec();
17556
17557 Some(self.perform_format(
17558 project,
17559 FormatTrigger::Manual,
17560 FormatTarget::Ranges(ranges),
17561 window,
17562 cx,
17563 ))
17564 }
17565
17566 fn perform_format(
17567 &mut self,
17568 project: Entity<Project>,
17569 trigger: FormatTrigger,
17570 target: FormatTarget,
17571 window: &mut Window,
17572 cx: &mut Context<Self>,
17573 ) -> Task<Result<()>> {
17574 let buffer = self.buffer.clone();
17575 let (buffers, target) = match target {
17576 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17577 FormatTarget::Ranges(selection_ranges) => {
17578 let multi_buffer = buffer.read(cx);
17579 let snapshot = multi_buffer.read(cx);
17580 let mut buffers = HashSet::default();
17581 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17582 BTreeMap::new();
17583 for selection_range in selection_ranges {
17584 for (buffer, buffer_range, _) in
17585 snapshot.range_to_buffer_ranges(selection_range)
17586 {
17587 let buffer_id = buffer.remote_id();
17588 let start = buffer.anchor_before(buffer_range.start);
17589 let end = buffer.anchor_after(buffer_range.end);
17590 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17591 buffer_id_to_ranges
17592 .entry(buffer_id)
17593 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17594 .or_insert_with(|| vec![start..end]);
17595 }
17596 }
17597 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17598 }
17599 };
17600
17601 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17602 let selections_prev = transaction_id_prev
17603 .and_then(|transaction_id_prev| {
17604 // default to selections as they were after the last edit, if we have them,
17605 // instead of how they are now.
17606 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17607 // will take you back to where you made the last edit, instead of staying where you scrolled
17608 self.selection_history
17609 .transaction(transaction_id_prev)
17610 .map(|t| t.0.clone())
17611 })
17612 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17613
17614 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17615 let format = project.update(cx, |project, cx| {
17616 project.format(buffers, target, true, trigger, cx)
17617 });
17618
17619 cx.spawn_in(window, async move |editor, cx| {
17620 let transaction = futures::select_biased! {
17621 transaction = format.log_err().fuse() => transaction,
17622 () = timeout => {
17623 log::warn!("timed out waiting for formatting");
17624 None
17625 }
17626 };
17627
17628 buffer
17629 .update(cx, |buffer, cx| {
17630 if let Some(transaction) = transaction
17631 && !buffer.is_singleton()
17632 {
17633 buffer.push_transaction(&transaction.0, cx);
17634 }
17635 cx.notify();
17636 })
17637 .ok();
17638
17639 if let Some(transaction_id_now) =
17640 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17641 {
17642 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17643 if has_new_transaction {
17644 _ = editor.update(cx, |editor, _| {
17645 editor
17646 .selection_history
17647 .insert_transaction(transaction_id_now, selections_prev);
17648 });
17649 }
17650 }
17651
17652 Ok(())
17653 })
17654 }
17655
17656 fn organize_imports(
17657 &mut self,
17658 _: &OrganizeImports,
17659 window: &mut Window,
17660 cx: &mut Context<Self>,
17661 ) -> Option<Task<Result<()>>> {
17662 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17663 let project = match &self.project {
17664 Some(project) => project.clone(),
17665 None => return None,
17666 };
17667 Some(self.perform_code_action_kind(
17668 project,
17669 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17670 window,
17671 cx,
17672 ))
17673 }
17674
17675 fn perform_code_action_kind(
17676 &mut self,
17677 project: Entity<Project>,
17678 kind: CodeActionKind,
17679 window: &mut Window,
17680 cx: &mut Context<Self>,
17681 ) -> Task<Result<()>> {
17682 let buffer = self.buffer.clone();
17683 let buffers = buffer.read(cx).all_buffers();
17684 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17685 let apply_action = project.update(cx, |project, cx| {
17686 project.apply_code_action_kind(buffers, kind, true, cx)
17687 });
17688 cx.spawn_in(window, async move |_, cx| {
17689 let transaction = futures::select_biased! {
17690 () = timeout => {
17691 log::warn!("timed out waiting for executing code action");
17692 None
17693 }
17694 transaction = apply_action.log_err().fuse() => transaction,
17695 };
17696 buffer
17697 .update(cx, |buffer, cx| {
17698 // check if we need this
17699 if let Some(transaction) = transaction
17700 && !buffer.is_singleton()
17701 {
17702 buffer.push_transaction(&transaction.0, cx);
17703 }
17704 cx.notify();
17705 })
17706 .ok();
17707 Ok(())
17708 })
17709 }
17710
17711 pub fn restart_language_server(
17712 &mut self,
17713 _: &RestartLanguageServer,
17714 _: &mut Window,
17715 cx: &mut Context<Self>,
17716 ) {
17717 if let Some(project) = self.project.clone() {
17718 self.buffer.update(cx, |multi_buffer, cx| {
17719 project.update(cx, |project, cx| {
17720 project.restart_language_servers_for_buffers(
17721 multi_buffer.all_buffers().into_iter().collect(),
17722 HashSet::default(),
17723 cx,
17724 );
17725 });
17726 })
17727 }
17728 }
17729
17730 pub fn stop_language_server(
17731 &mut self,
17732 _: &StopLanguageServer,
17733 _: &mut Window,
17734 cx: &mut Context<Self>,
17735 ) {
17736 if let Some(project) = self.project.clone() {
17737 self.buffer.update(cx, |multi_buffer, cx| {
17738 project.update(cx, |project, cx| {
17739 project.stop_language_servers_for_buffers(
17740 multi_buffer.all_buffers().into_iter().collect(),
17741 HashSet::default(),
17742 cx,
17743 );
17744 });
17745 });
17746 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17747 }
17748 }
17749
17750 fn cancel_language_server_work(
17751 workspace: &mut Workspace,
17752 _: &actions::CancelLanguageServerWork,
17753 _: &mut Window,
17754 cx: &mut Context<Workspace>,
17755 ) {
17756 let project = workspace.project();
17757 let buffers = workspace
17758 .active_item(cx)
17759 .and_then(|item| item.act_as::<Editor>(cx))
17760 .map_or(HashSet::default(), |editor| {
17761 editor.read(cx).buffer.read(cx).all_buffers()
17762 });
17763 project.update(cx, |project, cx| {
17764 project.cancel_language_server_work_for_buffers(buffers, cx);
17765 });
17766 }
17767
17768 fn show_character_palette(
17769 &mut self,
17770 _: &ShowCharacterPalette,
17771 window: &mut Window,
17772 _: &mut Context<Self>,
17773 ) {
17774 window.show_character_palette();
17775 }
17776
17777 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17778 if !self.diagnostics_enabled() {
17779 return;
17780 }
17781
17782 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17783 let buffer = self.buffer.read(cx).snapshot(cx);
17784 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17785 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17786 let is_valid = buffer
17787 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17788 .any(|entry| {
17789 entry.diagnostic.is_primary
17790 && !entry.range.is_empty()
17791 && entry.range.start == primary_range_start
17792 && entry.diagnostic.message == active_diagnostics.active_message
17793 });
17794
17795 if !is_valid {
17796 self.dismiss_diagnostics(cx);
17797 }
17798 }
17799 }
17800
17801 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17802 match &self.active_diagnostics {
17803 ActiveDiagnostic::Group(group) => Some(group),
17804 _ => None,
17805 }
17806 }
17807
17808 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17809 if !self.diagnostics_enabled() {
17810 return;
17811 }
17812 self.dismiss_diagnostics(cx);
17813 self.active_diagnostics = ActiveDiagnostic::All;
17814 }
17815
17816 fn activate_diagnostics(
17817 &mut self,
17818 buffer_id: BufferId,
17819 diagnostic: DiagnosticEntryRef<'_, usize>,
17820 window: &mut Window,
17821 cx: &mut Context<Self>,
17822 ) {
17823 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17824 return;
17825 }
17826 self.dismiss_diagnostics(cx);
17827 let snapshot = self.snapshot(window, cx);
17828 let buffer = self.buffer.read(cx).snapshot(cx);
17829 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17830 return;
17831 };
17832
17833 let diagnostic_group = buffer
17834 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17835 .collect::<Vec<_>>();
17836
17837 let blocks =
17838 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17839
17840 let blocks = self.display_map.update(cx, |display_map, cx| {
17841 display_map.insert_blocks(blocks, cx).into_iter().collect()
17842 });
17843 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17844 active_range: buffer.anchor_before(diagnostic.range.start)
17845 ..buffer.anchor_after(diagnostic.range.end),
17846 active_message: diagnostic.diagnostic.message.clone(),
17847 group_id: diagnostic.diagnostic.group_id,
17848 blocks,
17849 });
17850 cx.notify();
17851 }
17852
17853 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17854 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17855 return;
17856 };
17857
17858 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17859 if let ActiveDiagnostic::Group(group) = prev {
17860 self.display_map.update(cx, |display_map, cx| {
17861 display_map.remove_blocks(group.blocks, cx);
17862 });
17863 cx.notify();
17864 }
17865 }
17866
17867 /// Disable inline diagnostics rendering for this editor.
17868 pub fn disable_inline_diagnostics(&mut self) {
17869 self.inline_diagnostics_enabled = false;
17870 self.inline_diagnostics_update = Task::ready(());
17871 self.inline_diagnostics.clear();
17872 }
17873
17874 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17875 self.diagnostics_enabled = false;
17876 self.dismiss_diagnostics(cx);
17877 self.inline_diagnostics_update = Task::ready(());
17878 self.inline_diagnostics.clear();
17879 }
17880
17881 pub fn disable_word_completions(&mut self) {
17882 self.word_completions_enabled = false;
17883 }
17884
17885 pub fn diagnostics_enabled(&self) -> bool {
17886 self.diagnostics_enabled && self.mode.is_full()
17887 }
17888
17889 pub fn inline_diagnostics_enabled(&self) -> bool {
17890 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17891 }
17892
17893 pub fn show_inline_diagnostics(&self) -> bool {
17894 self.show_inline_diagnostics
17895 }
17896
17897 pub fn toggle_inline_diagnostics(
17898 &mut self,
17899 _: &ToggleInlineDiagnostics,
17900 window: &mut Window,
17901 cx: &mut Context<Editor>,
17902 ) {
17903 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17904 self.refresh_inline_diagnostics(false, window, cx);
17905 }
17906
17907 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17908 self.diagnostics_max_severity = severity;
17909 self.display_map.update(cx, |display_map, _| {
17910 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17911 });
17912 }
17913
17914 pub fn toggle_diagnostics(
17915 &mut self,
17916 _: &ToggleDiagnostics,
17917 window: &mut Window,
17918 cx: &mut Context<Editor>,
17919 ) {
17920 if !self.diagnostics_enabled() {
17921 return;
17922 }
17923
17924 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17925 EditorSettings::get_global(cx)
17926 .diagnostics_max_severity
17927 .filter(|severity| severity != &DiagnosticSeverity::Off)
17928 .unwrap_or(DiagnosticSeverity::Hint)
17929 } else {
17930 DiagnosticSeverity::Off
17931 };
17932 self.set_max_diagnostics_severity(new_severity, cx);
17933 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17934 self.active_diagnostics = ActiveDiagnostic::None;
17935 self.inline_diagnostics_update = Task::ready(());
17936 self.inline_diagnostics.clear();
17937 } else {
17938 self.refresh_inline_diagnostics(false, window, cx);
17939 }
17940
17941 cx.notify();
17942 }
17943
17944 pub fn toggle_minimap(
17945 &mut self,
17946 _: &ToggleMinimap,
17947 window: &mut Window,
17948 cx: &mut Context<Editor>,
17949 ) {
17950 if self.supports_minimap(cx) {
17951 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17952 }
17953 }
17954
17955 fn refresh_inline_diagnostics(
17956 &mut self,
17957 debounce: bool,
17958 window: &mut Window,
17959 cx: &mut Context<Self>,
17960 ) {
17961 let max_severity = ProjectSettings::get_global(cx)
17962 .diagnostics
17963 .inline
17964 .max_severity
17965 .unwrap_or(self.diagnostics_max_severity);
17966
17967 if !self.inline_diagnostics_enabled()
17968 || !self.diagnostics_enabled()
17969 || !self.show_inline_diagnostics
17970 || max_severity == DiagnosticSeverity::Off
17971 {
17972 self.inline_diagnostics_update = Task::ready(());
17973 self.inline_diagnostics.clear();
17974 return;
17975 }
17976
17977 let debounce_ms = ProjectSettings::get_global(cx)
17978 .diagnostics
17979 .inline
17980 .update_debounce_ms;
17981 let debounce = if debounce && debounce_ms > 0 {
17982 Some(Duration::from_millis(debounce_ms))
17983 } else {
17984 None
17985 };
17986 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
17987 if let Some(debounce) = debounce {
17988 cx.background_executor().timer(debounce).await;
17989 }
17990 let Some(snapshot) = editor.upgrade().and_then(|editor| {
17991 editor
17992 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
17993 .ok()
17994 }) else {
17995 return;
17996 };
17997
17998 let new_inline_diagnostics = cx
17999 .background_spawn(async move {
18000 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18001 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
18002 let message = diagnostic_entry
18003 .diagnostic
18004 .message
18005 .split_once('\n')
18006 .map(|(line, _)| line)
18007 .map(SharedString::new)
18008 .unwrap_or_else(|| {
18009 SharedString::new(&*diagnostic_entry.diagnostic.message)
18010 });
18011 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18012 let (Ok(i) | Err(i)) = inline_diagnostics
18013 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18014 inline_diagnostics.insert(
18015 i,
18016 (
18017 start_anchor,
18018 InlineDiagnostic {
18019 message,
18020 group_id: diagnostic_entry.diagnostic.group_id,
18021 start: diagnostic_entry.range.start.to_point(&snapshot),
18022 is_primary: diagnostic_entry.diagnostic.is_primary,
18023 severity: diagnostic_entry.diagnostic.severity,
18024 },
18025 ),
18026 );
18027 }
18028 inline_diagnostics
18029 })
18030 .await;
18031
18032 editor
18033 .update(cx, |editor, cx| {
18034 editor.inline_diagnostics = new_inline_diagnostics;
18035 cx.notify();
18036 })
18037 .ok();
18038 });
18039 }
18040
18041 fn pull_diagnostics(
18042 &mut self,
18043 buffer_id: Option<BufferId>,
18044 window: &Window,
18045 cx: &mut Context<Self>,
18046 ) -> Option<()> {
18047 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18048 return None;
18049 }
18050 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18051 .diagnostics
18052 .lsp_pull_diagnostics;
18053 if !pull_diagnostics_settings.enabled {
18054 return None;
18055 }
18056 let project = self.project()?.downgrade();
18057 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18058 let mut buffers = self.buffer.read(cx).all_buffers();
18059 buffers.retain(|buffer| {
18060 let buffer_id_to_retain = buffer.read(cx).remote_id();
18061 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18062 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18063 });
18064 if buffers.is_empty() {
18065 self.pull_diagnostics_task = Task::ready(());
18066 return None;
18067 }
18068
18069 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18070 cx.background_executor().timer(debounce).await;
18071
18072 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18073 buffers
18074 .into_iter()
18075 .filter_map(|buffer| {
18076 project
18077 .update(cx, |project, cx| {
18078 project.lsp_store().update(cx, |lsp_store, cx| {
18079 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18080 })
18081 })
18082 .ok()
18083 })
18084 .collect::<FuturesUnordered<_>>()
18085 }) else {
18086 return;
18087 };
18088
18089 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18090 match pull_task {
18091 Ok(()) => {
18092 if editor
18093 .update_in(cx, |editor, window, cx| {
18094 editor.update_diagnostics_state(window, cx);
18095 })
18096 .is_err()
18097 {
18098 return;
18099 }
18100 }
18101 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18102 }
18103 }
18104 });
18105
18106 Some(())
18107 }
18108
18109 pub fn set_selections_from_remote(
18110 &mut self,
18111 selections: Vec<Selection<Anchor>>,
18112 pending_selection: Option<Selection<Anchor>>,
18113 window: &mut Window,
18114 cx: &mut Context<Self>,
18115 ) {
18116 let old_cursor_position = self.selections.newest_anchor().head();
18117 self.selections
18118 .change_with(&self.display_snapshot(cx), |s| {
18119 s.select_anchors(selections);
18120 if let Some(pending_selection) = pending_selection {
18121 s.set_pending(pending_selection, SelectMode::Character);
18122 } else {
18123 s.clear_pending();
18124 }
18125 });
18126 self.selections_did_change(
18127 false,
18128 &old_cursor_position,
18129 SelectionEffects::default(),
18130 window,
18131 cx,
18132 );
18133 }
18134
18135 pub fn transact(
18136 &mut self,
18137 window: &mut Window,
18138 cx: &mut Context<Self>,
18139 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18140 ) -> Option<TransactionId> {
18141 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18142 this.start_transaction_at(Instant::now(), window, cx);
18143 update(this, window, cx);
18144 this.end_transaction_at(Instant::now(), cx)
18145 })
18146 }
18147
18148 pub fn start_transaction_at(
18149 &mut self,
18150 now: Instant,
18151 window: &mut Window,
18152 cx: &mut Context<Self>,
18153 ) -> Option<TransactionId> {
18154 self.end_selection(window, cx);
18155 if let Some(tx_id) = self
18156 .buffer
18157 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18158 {
18159 self.selection_history
18160 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18161 cx.emit(EditorEvent::TransactionBegun {
18162 transaction_id: tx_id,
18163 });
18164 Some(tx_id)
18165 } else {
18166 None
18167 }
18168 }
18169
18170 pub fn end_transaction_at(
18171 &mut self,
18172 now: Instant,
18173 cx: &mut Context<Self>,
18174 ) -> Option<TransactionId> {
18175 if let Some(transaction_id) = self
18176 .buffer
18177 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18178 {
18179 if let Some((_, end_selections)) =
18180 self.selection_history.transaction_mut(transaction_id)
18181 {
18182 *end_selections = Some(self.selections.disjoint_anchors_arc());
18183 } else {
18184 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18185 }
18186
18187 cx.emit(EditorEvent::Edited { transaction_id });
18188 Some(transaction_id)
18189 } else {
18190 None
18191 }
18192 }
18193
18194 pub fn modify_transaction_selection_history(
18195 &mut self,
18196 transaction_id: TransactionId,
18197 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18198 ) -> bool {
18199 self.selection_history
18200 .transaction_mut(transaction_id)
18201 .map(modify)
18202 .is_some()
18203 }
18204
18205 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18206 if self.selection_mark_mode {
18207 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18208 s.move_with(|_, sel| {
18209 sel.collapse_to(sel.head(), SelectionGoal::None);
18210 });
18211 })
18212 }
18213 self.selection_mark_mode = true;
18214 cx.notify();
18215 }
18216
18217 pub fn swap_selection_ends(
18218 &mut self,
18219 _: &actions::SwapSelectionEnds,
18220 window: &mut Window,
18221 cx: &mut Context<Self>,
18222 ) {
18223 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18224 s.move_with(|_, sel| {
18225 if sel.start != sel.end {
18226 sel.reversed = !sel.reversed
18227 }
18228 });
18229 });
18230 self.request_autoscroll(Autoscroll::newest(), cx);
18231 cx.notify();
18232 }
18233
18234 pub fn toggle_focus(
18235 workspace: &mut Workspace,
18236 _: &actions::ToggleFocus,
18237 window: &mut Window,
18238 cx: &mut Context<Workspace>,
18239 ) {
18240 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18241 return;
18242 };
18243 workspace.activate_item(&item, true, true, window, cx);
18244 }
18245
18246 pub fn toggle_fold(
18247 &mut self,
18248 _: &actions::ToggleFold,
18249 window: &mut Window,
18250 cx: &mut Context<Self>,
18251 ) {
18252 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18253 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18254 let selection = self.selections.newest::<Point>(&display_map);
18255
18256 let range = if selection.is_empty() {
18257 let point = selection.head().to_display_point(&display_map);
18258 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18259 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18260 .to_point(&display_map);
18261 start..end
18262 } else {
18263 selection.range()
18264 };
18265 if display_map.folds_in_range(range).next().is_some() {
18266 self.unfold_lines(&Default::default(), window, cx)
18267 } else {
18268 self.fold(&Default::default(), window, cx)
18269 }
18270 } else {
18271 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18272 let buffer_ids: HashSet<_> = self
18273 .selections
18274 .disjoint_anchor_ranges()
18275 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18276 .collect();
18277
18278 let should_unfold = buffer_ids
18279 .iter()
18280 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18281
18282 for buffer_id in buffer_ids {
18283 if should_unfold {
18284 self.unfold_buffer(buffer_id, cx);
18285 } else {
18286 self.fold_buffer(buffer_id, cx);
18287 }
18288 }
18289 }
18290 }
18291
18292 pub fn toggle_fold_recursive(
18293 &mut self,
18294 _: &actions::ToggleFoldRecursive,
18295 window: &mut Window,
18296 cx: &mut Context<Self>,
18297 ) {
18298 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18299
18300 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18301 let range = if selection.is_empty() {
18302 let point = selection.head().to_display_point(&display_map);
18303 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18304 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18305 .to_point(&display_map);
18306 start..end
18307 } else {
18308 selection.range()
18309 };
18310 if display_map.folds_in_range(range).next().is_some() {
18311 self.unfold_recursive(&Default::default(), window, cx)
18312 } else {
18313 self.fold_recursive(&Default::default(), window, cx)
18314 }
18315 }
18316
18317 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18318 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18319 let mut to_fold = Vec::new();
18320 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18321 let selections = self.selections.all_adjusted(&display_map);
18322
18323 for selection in selections {
18324 let range = selection.range().sorted();
18325 let buffer_start_row = range.start.row;
18326
18327 if range.start.row != range.end.row {
18328 let mut found = false;
18329 let mut row = range.start.row;
18330 while row <= range.end.row {
18331 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18332 {
18333 found = true;
18334 row = crease.range().end.row + 1;
18335 to_fold.push(crease);
18336 } else {
18337 row += 1
18338 }
18339 }
18340 if found {
18341 continue;
18342 }
18343 }
18344
18345 for row in (0..=range.start.row).rev() {
18346 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18347 && crease.range().end.row >= buffer_start_row
18348 {
18349 to_fold.push(crease);
18350 if row <= range.start.row {
18351 break;
18352 }
18353 }
18354 }
18355 }
18356
18357 self.fold_creases(to_fold, true, window, cx);
18358 } else {
18359 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18360 let buffer_ids = self
18361 .selections
18362 .disjoint_anchor_ranges()
18363 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18364 .collect::<HashSet<_>>();
18365 for buffer_id in buffer_ids {
18366 self.fold_buffer(buffer_id, cx);
18367 }
18368 }
18369 }
18370
18371 pub fn toggle_fold_all(
18372 &mut self,
18373 _: &actions::ToggleFoldAll,
18374 window: &mut Window,
18375 cx: &mut Context<Self>,
18376 ) {
18377 if self.buffer.read(cx).is_singleton() {
18378 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18379 let has_folds = display_map
18380 .folds_in_range(0..display_map.buffer_snapshot().len())
18381 .next()
18382 .is_some();
18383
18384 if has_folds {
18385 self.unfold_all(&actions::UnfoldAll, window, cx);
18386 } else {
18387 self.fold_all(&actions::FoldAll, window, cx);
18388 }
18389 } else {
18390 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18391 let should_unfold = buffer_ids
18392 .iter()
18393 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18394
18395 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18396 editor
18397 .update_in(cx, |editor, _, cx| {
18398 for buffer_id in buffer_ids {
18399 if should_unfold {
18400 editor.unfold_buffer(buffer_id, cx);
18401 } else {
18402 editor.fold_buffer(buffer_id, cx);
18403 }
18404 }
18405 })
18406 .ok();
18407 });
18408 }
18409 }
18410
18411 fn fold_at_level(
18412 &mut self,
18413 fold_at: &FoldAtLevel,
18414 window: &mut Window,
18415 cx: &mut Context<Self>,
18416 ) {
18417 if !self.buffer.read(cx).is_singleton() {
18418 return;
18419 }
18420
18421 let fold_at_level = fold_at.0;
18422 let snapshot = self.buffer.read(cx).snapshot(cx);
18423 let mut to_fold = Vec::new();
18424 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18425
18426 let row_ranges_to_keep: Vec<Range<u32>> = self
18427 .selections
18428 .all::<Point>(&self.display_snapshot(cx))
18429 .into_iter()
18430 .map(|sel| sel.start.row..sel.end.row)
18431 .collect();
18432
18433 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18434 while start_row < end_row {
18435 match self
18436 .snapshot(window, cx)
18437 .crease_for_buffer_row(MultiBufferRow(start_row))
18438 {
18439 Some(crease) => {
18440 let nested_start_row = crease.range().start.row + 1;
18441 let nested_end_row = crease.range().end.row;
18442
18443 if current_level < fold_at_level {
18444 stack.push((nested_start_row, nested_end_row, current_level + 1));
18445 } else if current_level == fold_at_level {
18446 // Fold iff there is no selection completely contained within the fold region
18447 if !row_ranges_to_keep.iter().any(|selection| {
18448 selection.end >= nested_start_row
18449 && selection.start <= nested_end_row
18450 }) {
18451 to_fold.push(crease);
18452 }
18453 }
18454
18455 start_row = nested_end_row + 1;
18456 }
18457 None => start_row += 1,
18458 }
18459 }
18460 }
18461
18462 self.fold_creases(to_fold, true, window, cx);
18463 }
18464
18465 pub fn fold_at_level_1(
18466 &mut self,
18467 _: &actions::FoldAtLevel1,
18468 window: &mut Window,
18469 cx: &mut Context<Self>,
18470 ) {
18471 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18472 }
18473
18474 pub fn fold_at_level_2(
18475 &mut self,
18476 _: &actions::FoldAtLevel2,
18477 window: &mut Window,
18478 cx: &mut Context<Self>,
18479 ) {
18480 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18481 }
18482
18483 pub fn fold_at_level_3(
18484 &mut self,
18485 _: &actions::FoldAtLevel3,
18486 window: &mut Window,
18487 cx: &mut Context<Self>,
18488 ) {
18489 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18490 }
18491
18492 pub fn fold_at_level_4(
18493 &mut self,
18494 _: &actions::FoldAtLevel4,
18495 window: &mut Window,
18496 cx: &mut Context<Self>,
18497 ) {
18498 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18499 }
18500
18501 pub fn fold_at_level_5(
18502 &mut self,
18503 _: &actions::FoldAtLevel5,
18504 window: &mut Window,
18505 cx: &mut Context<Self>,
18506 ) {
18507 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18508 }
18509
18510 pub fn fold_at_level_6(
18511 &mut self,
18512 _: &actions::FoldAtLevel6,
18513 window: &mut Window,
18514 cx: &mut Context<Self>,
18515 ) {
18516 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18517 }
18518
18519 pub fn fold_at_level_7(
18520 &mut self,
18521 _: &actions::FoldAtLevel7,
18522 window: &mut Window,
18523 cx: &mut Context<Self>,
18524 ) {
18525 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18526 }
18527
18528 pub fn fold_at_level_8(
18529 &mut self,
18530 _: &actions::FoldAtLevel8,
18531 window: &mut Window,
18532 cx: &mut Context<Self>,
18533 ) {
18534 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18535 }
18536
18537 pub fn fold_at_level_9(
18538 &mut self,
18539 _: &actions::FoldAtLevel9,
18540 window: &mut Window,
18541 cx: &mut Context<Self>,
18542 ) {
18543 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18544 }
18545
18546 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18547 if self.buffer.read(cx).is_singleton() {
18548 let mut fold_ranges = Vec::new();
18549 let snapshot = self.buffer.read(cx).snapshot(cx);
18550
18551 for row in 0..snapshot.max_row().0 {
18552 if let Some(foldable_range) = self
18553 .snapshot(window, cx)
18554 .crease_for_buffer_row(MultiBufferRow(row))
18555 {
18556 fold_ranges.push(foldable_range);
18557 }
18558 }
18559
18560 self.fold_creases(fold_ranges, true, window, cx);
18561 } else {
18562 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18563 editor
18564 .update_in(cx, |editor, _, cx| {
18565 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18566 editor.fold_buffer(buffer_id, cx);
18567 }
18568 })
18569 .ok();
18570 });
18571 }
18572 }
18573
18574 pub fn fold_function_bodies(
18575 &mut self,
18576 _: &actions::FoldFunctionBodies,
18577 window: &mut Window,
18578 cx: &mut Context<Self>,
18579 ) {
18580 let snapshot = self.buffer.read(cx).snapshot(cx);
18581
18582 let ranges = snapshot
18583 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18584 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18585 .collect::<Vec<_>>();
18586
18587 let creases = ranges
18588 .into_iter()
18589 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18590 .collect();
18591
18592 self.fold_creases(creases, true, window, cx);
18593 }
18594
18595 pub fn fold_recursive(
18596 &mut self,
18597 _: &actions::FoldRecursive,
18598 window: &mut Window,
18599 cx: &mut Context<Self>,
18600 ) {
18601 let mut to_fold = Vec::new();
18602 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18603 let selections = self.selections.all_adjusted(&display_map);
18604
18605 for selection in selections {
18606 let range = selection.range().sorted();
18607 let buffer_start_row = range.start.row;
18608
18609 if range.start.row != range.end.row {
18610 let mut found = false;
18611 for row in range.start.row..=range.end.row {
18612 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18613 found = true;
18614 to_fold.push(crease);
18615 }
18616 }
18617 if found {
18618 continue;
18619 }
18620 }
18621
18622 for row in (0..=range.start.row).rev() {
18623 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18624 if crease.range().end.row >= buffer_start_row {
18625 to_fold.push(crease);
18626 } else {
18627 break;
18628 }
18629 }
18630 }
18631 }
18632
18633 self.fold_creases(to_fold, true, window, cx);
18634 }
18635
18636 pub fn fold_at(
18637 &mut self,
18638 buffer_row: MultiBufferRow,
18639 window: &mut Window,
18640 cx: &mut Context<Self>,
18641 ) {
18642 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18643
18644 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18645 let autoscroll = self
18646 .selections
18647 .all::<Point>(&display_map)
18648 .iter()
18649 .any(|selection| crease.range().overlaps(&selection.range()));
18650
18651 self.fold_creases(vec![crease], autoscroll, window, cx);
18652 }
18653 }
18654
18655 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18656 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18657 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18658 let buffer = display_map.buffer_snapshot();
18659 let selections = self.selections.all::<Point>(&display_map);
18660 let ranges = selections
18661 .iter()
18662 .map(|s| {
18663 let range = s.display_range(&display_map).sorted();
18664 let mut start = range.start.to_point(&display_map);
18665 let mut end = range.end.to_point(&display_map);
18666 start.column = 0;
18667 end.column = buffer.line_len(MultiBufferRow(end.row));
18668 start..end
18669 })
18670 .collect::<Vec<_>>();
18671
18672 self.unfold_ranges(&ranges, true, true, cx);
18673 } else {
18674 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18675 let buffer_ids = self
18676 .selections
18677 .disjoint_anchor_ranges()
18678 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18679 .collect::<HashSet<_>>();
18680 for buffer_id in buffer_ids {
18681 self.unfold_buffer(buffer_id, cx);
18682 }
18683 }
18684 }
18685
18686 pub fn unfold_recursive(
18687 &mut self,
18688 _: &UnfoldRecursive,
18689 _window: &mut Window,
18690 cx: &mut Context<Self>,
18691 ) {
18692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18693 let selections = self.selections.all::<Point>(&display_map);
18694 let ranges = selections
18695 .iter()
18696 .map(|s| {
18697 let mut range = s.display_range(&display_map).sorted();
18698 *range.start.column_mut() = 0;
18699 *range.end.column_mut() = display_map.line_len(range.end.row());
18700 let start = range.start.to_point(&display_map);
18701 let end = range.end.to_point(&display_map);
18702 start..end
18703 })
18704 .collect::<Vec<_>>();
18705
18706 self.unfold_ranges(&ranges, true, true, cx);
18707 }
18708
18709 pub fn unfold_at(
18710 &mut self,
18711 buffer_row: MultiBufferRow,
18712 _window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) {
18715 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18716
18717 let intersection_range = Point::new(buffer_row.0, 0)
18718 ..Point::new(
18719 buffer_row.0,
18720 display_map.buffer_snapshot().line_len(buffer_row),
18721 );
18722
18723 let autoscroll = self
18724 .selections
18725 .all::<Point>(&display_map)
18726 .iter()
18727 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18728
18729 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18730 }
18731
18732 pub fn unfold_all(
18733 &mut self,
18734 _: &actions::UnfoldAll,
18735 _window: &mut Window,
18736 cx: &mut Context<Self>,
18737 ) {
18738 if self.buffer.read(cx).is_singleton() {
18739 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18740 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18741 } else {
18742 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18743 editor
18744 .update(cx, |editor, cx| {
18745 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18746 editor.unfold_buffer(buffer_id, cx);
18747 }
18748 })
18749 .ok();
18750 });
18751 }
18752 }
18753
18754 pub fn fold_selected_ranges(
18755 &mut self,
18756 _: &FoldSelectedRanges,
18757 window: &mut Window,
18758 cx: &mut Context<Self>,
18759 ) {
18760 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18761 let selections = self.selections.all_adjusted(&display_map);
18762 let ranges = selections
18763 .into_iter()
18764 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18765 .collect::<Vec<_>>();
18766 self.fold_creases(ranges, true, window, cx);
18767 }
18768
18769 pub fn fold_ranges<T: ToOffset + Clone>(
18770 &mut self,
18771 ranges: Vec<Range<T>>,
18772 auto_scroll: bool,
18773 window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) {
18776 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18777 let ranges = ranges
18778 .into_iter()
18779 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18780 .collect::<Vec<_>>();
18781 self.fold_creases(ranges, auto_scroll, window, cx);
18782 }
18783
18784 pub fn fold_creases<T: ToOffset + Clone>(
18785 &mut self,
18786 creases: Vec<Crease<T>>,
18787 auto_scroll: bool,
18788 _window: &mut Window,
18789 cx: &mut Context<Self>,
18790 ) {
18791 if creases.is_empty() {
18792 return;
18793 }
18794
18795 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18796
18797 if auto_scroll {
18798 self.request_autoscroll(Autoscroll::fit(), cx);
18799 }
18800
18801 cx.notify();
18802
18803 self.scrollbar_marker_state.dirty = true;
18804 self.folds_did_change(cx);
18805 }
18806
18807 /// Removes any folds whose ranges intersect any of the given ranges.
18808 pub fn unfold_ranges<T: ToOffset + Clone>(
18809 &mut self,
18810 ranges: &[Range<T>],
18811 inclusive: bool,
18812 auto_scroll: bool,
18813 cx: &mut Context<Self>,
18814 ) {
18815 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18816 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18817 });
18818 self.folds_did_change(cx);
18819 }
18820
18821 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18822 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18823 return;
18824 }
18825 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18826 self.display_map.update(cx, |display_map, cx| {
18827 display_map.fold_buffers([buffer_id], cx)
18828 });
18829 cx.emit(EditorEvent::BufferFoldToggled {
18830 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18831 folded: true,
18832 });
18833 cx.notify();
18834 }
18835
18836 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18837 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18838 return;
18839 }
18840 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18841 self.display_map.update(cx, |display_map, cx| {
18842 display_map.unfold_buffers([buffer_id], cx);
18843 });
18844 cx.emit(EditorEvent::BufferFoldToggled {
18845 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18846 folded: false,
18847 });
18848 cx.notify();
18849 }
18850
18851 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18852 self.display_map.read(cx).is_buffer_folded(buffer)
18853 }
18854
18855 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18856 self.display_map.read(cx).folded_buffers()
18857 }
18858
18859 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18860 self.display_map.update(cx, |display_map, cx| {
18861 display_map.disable_header_for_buffer(buffer_id, cx);
18862 });
18863 cx.notify();
18864 }
18865
18866 /// Removes any folds with the given ranges.
18867 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18868 &mut self,
18869 ranges: &[Range<T>],
18870 type_id: TypeId,
18871 auto_scroll: bool,
18872 cx: &mut Context<Self>,
18873 ) {
18874 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18875 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18876 });
18877 self.folds_did_change(cx);
18878 }
18879
18880 fn remove_folds_with<T: ToOffset + Clone>(
18881 &mut self,
18882 ranges: &[Range<T>],
18883 auto_scroll: bool,
18884 cx: &mut Context<Self>,
18885 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18886 ) {
18887 if ranges.is_empty() {
18888 return;
18889 }
18890
18891 let mut buffers_affected = HashSet::default();
18892 let multi_buffer = self.buffer().read(cx);
18893 for range in ranges {
18894 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18895 buffers_affected.insert(buffer.read(cx).remote_id());
18896 };
18897 }
18898
18899 self.display_map.update(cx, update);
18900
18901 if auto_scroll {
18902 self.request_autoscroll(Autoscroll::fit(), cx);
18903 }
18904
18905 cx.notify();
18906 self.scrollbar_marker_state.dirty = true;
18907 self.active_indent_guides_state.dirty = true;
18908 }
18909
18910 pub fn update_renderer_widths(
18911 &mut self,
18912 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18913 cx: &mut Context<Self>,
18914 ) -> bool {
18915 self.display_map
18916 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18917 }
18918
18919 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18920 self.display_map.read(cx).fold_placeholder.clone()
18921 }
18922
18923 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18924 self.buffer.update(cx, |buffer, cx| {
18925 buffer.set_all_diff_hunks_expanded(cx);
18926 });
18927 }
18928
18929 pub fn expand_all_diff_hunks(
18930 &mut self,
18931 _: &ExpandAllDiffHunks,
18932 _window: &mut Window,
18933 cx: &mut Context<Self>,
18934 ) {
18935 self.buffer.update(cx, |buffer, cx| {
18936 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18937 });
18938 }
18939
18940 pub fn collapse_all_diff_hunks(
18941 &mut self,
18942 _: &CollapseAllDiffHunks,
18943 _window: &mut Window,
18944 cx: &mut Context<Self>,
18945 ) {
18946 self.buffer.update(cx, |buffer, cx| {
18947 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18948 });
18949 }
18950
18951 pub fn toggle_selected_diff_hunks(
18952 &mut self,
18953 _: &ToggleSelectedDiffHunks,
18954 _window: &mut Window,
18955 cx: &mut Context<Self>,
18956 ) {
18957 let ranges: Vec<_> = self
18958 .selections
18959 .disjoint_anchors()
18960 .iter()
18961 .map(|s| s.range())
18962 .collect();
18963 self.toggle_diff_hunks_in_ranges(ranges, cx);
18964 }
18965
18966 pub fn diff_hunks_in_ranges<'a>(
18967 &'a self,
18968 ranges: &'a [Range<Anchor>],
18969 buffer: &'a MultiBufferSnapshot,
18970 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
18971 ranges.iter().flat_map(move |range| {
18972 let end_excerpt_id = range.end.excerpt_id;
18973 let range = range.to_point(buffer);
18974 let mut peek_end = range.end;
18975 if range.end.row < buffer.max_row().0 {
18976 peek_end = Point::new(range.end.row + 1, 0);
18977 }
18978 buffer
18979 .diff_hunks_in_range(range.start..peek_end)
18980 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
18981 })
18982 }
18983
18984 pub fn has_stageable_diff_hunks_in_ranges(
18985 &self,
18986 ranges: &[Range<Anchor>],
18987 snapshot: &MultiBufferSnapshot,
18988 ) -> bool {
18989 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
18990 hunks.any(|hunk| hunk.status().has_secondary_hunk())
18991 }
18992
18993 pub fn toggle_staged_selected_diff_hunks(
18994 &mut self,
18995 _: &::git::ToggleStaged,
18996 _: &mut Window,
18997 cx: &mut Context<Self>,
18998 ) {
18999 let snapshot = self.buffer.read(cx).snapshot(cx);
19000 let ranges: Vec<_> = self
19001 .selections
19002 .disjoint_anchors()
19003 .iter()
19004 .map(|s| s.range())
19005 .collect();
19006 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19007 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19008 }
19009
19010 pub fn set_render_diff_hunk_controls(
19011 &mut self,
19012 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19013 cx: &mut Context<Self>,
19014 ) {
19015 self.render_diff_hunk_controls = render_diff_hunk_controls;
19016 cx.notify();
19017 }
19018
19019 pub fn stage_and_next(
19020 &mut self,
19021 _: &::git::StageAndNext,
19022 window: &mut Window,
19023 cx: &mut Context<Self>,
19024 ) {
19025 self.do_stage_or_unstage_and_next(true, window, cx);
19026 }
19027
19028 pub fn unstage_and_next(
19029 &mut self,
19030 _: &::git::UnstageAndNext,
19031 window: &mut Window,
19032 cx: &mut Context<Self>,
19033 ) {
19034 self.do_stage_or_unstage_and_next(false, window, cx);
19035 }
19036
19037 pub fn stage_or_unstage_diff_hunks(
19038 &mut self,
19039 stage: bool,
19040 ranges: Vec<Range<Anchor>>,
19041 cx: &mut Context<Self>,
19042 ) {
19043 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19044 cx.spawn(async move |this, cx| {
19045 task.await?;
19046 this.update(cx, |this, cx| {
19047 let snapshot = this.buffer.read(cx).snapshot(cx);
19048 let chunk_by = this
19049 .diff_hunks_in_ranges(&ranges, &snapshot)
19050 .chunk_by(|hunk| hunk.buffer_id);
19051 for (buffer_id, hunks) in &chunk_by {
19052 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19053 }
19054 })
19055 })
19056 .detach_and_log_err(cx);
19057 }
19058
19059 fn save_buffers_for_ranges_if_needed(
19060 &mut self,
19061 ranges: &[Range<Anchor>],
19062 cx: &mut Context<Editor>,
19063 ) -> Task<Result<()>> {
19064 let multibuffer = self.buffer.read(cx);
19065 let snapshot = multibuffer.read(cx);
19066 let buffer_ids: HashSet<_> = ranges
19067 .iter()
19068 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19069 .collect();
19070 drop(snapshot);
19071
19072 let mut buffers = HashSet::default();
19073 for buffer_id in buffer_ids {
19074 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19075 let buffer = buffer_entity.read(cx);
19076 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19077 {
19078 buffers.insert(buffer_entity);
19079 }
19080 }
19081 }
19082
19083 if let Some(project) = &self.project {
19084 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19085 } else {
19086 Task::ready(Ok(()))
19087 }
19088 }
19089
19090 fn do_stage_or_unstage_and_next(
19091 &mut self,
19092 stage: bool,
19093 window: &mut Window,
19094 cx: &mut Context<Self>,
19095 ) {
19096 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19097
19098 if ranges.iter().any(|range| range.start != range.end) {
19099 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19100 return;
19101 }
19102
19103 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19104 let snapshot = self.snapshot(window, cx);
19105 let position = self
19106 .selections
19107 .newest::<Point>(&snapshot.display_snapshot)
19108 .head();
19109 let mut row = snapshot
19110 .buffer_snapshot()
19111 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19112 .find(|hunk| hunk.row_range.start.0 > position.row)
19113 .map(|hunk| hunk.row_range.start);
19114
19115 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19116 // Outside of the project diff editor, wrap around to the beginning.
19117 if !all_diff_hunks_expanded {
19118 row = row.or_else(|| {
19119 snapshot
19120 .buffer_snapshot()
19121 .diff_hunks_in_range(Point::zero()..position)
19122 .find(|hunk| hunk.row_range.end.0 < position.row)
19123 .map(|hunk| hunk.row_range.start)
19124 });
19125 }
19126
19127 if let Some(row) = row {
19128 let destination = Point::new(row.0, 0);
19129 let autoscroll = Autoscroll::center();
19130
19131 self.unfold_ranges(&[destination..destination], false, false, cx);
19132 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19133 s.select_ranges([destination..destination]);
19134 });
19135 }
19136 }
19137
19138 fn do_stage_or_unstage(
19139 &self,
19140 stage: bool,
19141 buffer_id: BufferId,
19142 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19143 cx: &mut App,
19144 ) -> Option<()> {
19145 let project = self.project()?;
19146 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19147 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19148 let buffer_snapshot = buffer.read(cx).snapshot();
19149 let file_exists = buffer_snapshot
19150 .file()
19151 .is_some_and(|file| file.disk_state().exists());
19152 diff.update(cx, |diff, cx| {
19153 diff.stage_or_unstage_hunks(
19154 stage,
19155 &hunks
19156 .map(|hunk| buffer_diff::DiffHunk {
19157 buffer_range: hunk.buffer_range,
19158 diff_base_byte_range: hunk.diff_base_byte_range,
19159 secondary_status: hunk.secondary_status,
19160 range: Point::zero()..Point::zero(), // unused
19161 })
19162 .collect::<Vec<_>>(),
19163 &buffer_snapshot,
19164 file_exists,
19165 cx,
19166 )
19167 });
19168 None
19169 }
19170
19171 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19172 let ranges: Vec<_> = self
19173 .selections
19174 .disjoint_anchors()
19175 .iter()
19176 .map(|s| s.range())
19177 .collect();
19178 self.buffer
19179 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19180 }
19181
19182 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19183 self.buffer.update(cx, |buffer, cx| {
19184 let ranges = vec![Anchor::min()..Anchor::max()];
19185 if !buffer.all_diff_hunks_expanded()
19186 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19187 {
19188 buffer.collapse_diff_hunks(ranges, cx);
19189 true
19190 } else {
19191 false
19192 }
19193 })
19194 }
19195
19196 fn toggle_diff_hunks_in_ranges(
19197 &mut self,
19198 ranges: Vec<Range<Anchor>>,
19199 cx: &mut Context<Editor>,
19200 ) {
19201 self.buffer.update(cx, |buffer, cx| {
19202 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19203 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19204 })
19205 }
19206
19207 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19208 self.buffer.update(cx, |buffer, cx| {
19209 let snapshot = buffer.snapshot(cx);
19210 let excerpt_id = range.end.excerpt_id;
19211 let point_range = range.to_point(&snapshot);
19212 let expand = !buffer.single_hunk_is_expanded(range, cx);
19213 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19214 })
19215 }
19216
19217 pub(crate) fn apply_all_diff_hunks(
19218 &mut self,
19219 _: &ApplyAllDiffHunks,
19220 window: &mut Window,
19221 cx: &mut Context<Self>,
19222 ) {
19223 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19224
19225 let buffers = self.buffer.read(cx).all_buffers();
19226 for branch_buffer in buffers {
19227 branch_buffer.update(cx, |branch_buffer, cx| {
19228 branch_buffer.merge_into_base(Vec::new(), cx);
19229 });
19230 }
19231
19232 if let Some(project) = self.project.clone() {
19233 self.save(
19234 SaveOptions {
19235 format: true,
19236 autosave: false,
19237 },
19238 project,
19239 window,
19240 cx,
19241 )
19242 .detach_and_log_err(cx);
19243 }
19244 }
19245
19246 pub(crate) fn apply_selected_diff_hunks(
19247 &mut self,
19248 _: &ApplyDiffHunk,
19249 window: &mut Window,
19250 cx: &mut Context<Self>,
19251 ) {
19252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19253 let snapshot = self.snapshot(window, cx);
19254 let hunks = snapshot.hunks_for_ranges(
19255 self.selections
19256 .all(&snapshot.display_snapshot)
19257 .into_iter()
19258 .map(|selection| selection.range()),
19259 );
19260 let mut ranges_by_buffer = HashMap::default();
19261 self.transact(window, cx, |editor, _window, cx| {
19262 for hunk in hunks {
19263 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19264 ranges_by_buffer
19265 .entry(buffer.clone())
19266 .or_insert_with(Vec::new)
19267 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19268 }
19269 }
19270
19271 for (buffer, ranges) in ranges_by_buffer {
19272 buffer.update(cx, |buffer, cx| {
19273 buffer.merge_into_base(ranges, cx);
19274 });
19275 }
19276 });
19277
19278 if let Some(project) = self.project.clone() {
19279 self.save(
19280 SaveOptions {
19281 format: true,
19282 autosave: false,
19283 },
19284 project,
19285 window,
19286 cx,
19287 )
19288 .detach_and_log_err(cx);
19289 }
19290 }
19291
19292 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19293 if hovered != self.gutter_hovered {
19294 self.gutter_hovered = hovered;
19295 cx.notify();
19296 }
19297 }
19298
19299 pub fn insert_blocks(
19300 &mut self,
19301 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19302 autoscroll: Option<Autoscroll>,
19303 cx: &mut Context<Self>,
19304 ) -> Vec<CustomBlockId> {
19305 let blocks = self
19306 .display_map
19307 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19308 if let Some(autoscroll) = autoscroll {
19309 self.request_autoscroll(autoscroll, cx);
19310 }
19311 cx.notify();
19312 blocks
19313 }
19314
19315 pub fn resize_blocks(
19316 &mut self,
19317 heights: HashMap<CustomBlockId, u32>,
19318 autoscroll: Option<Autoscroll>,
19319 cx: &mut Context<Self>,
19320 ) {
19321 self.display_map
19322 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19323 if let Some(autoscroll) = autoscroll {
19324 self.request_autoscroll(autoscroll, cx);
19325 }
19326 cx.notify();
19327 }
19328
19329 pub fn replace_blocks(
19330 &mut self,
19331 renderers: HashMap<CustomBlockId, RenderBlock>,
19332 autoscroll: Option<Autoscroll>,
19333 cx: &mut Context<Self>,
19334 ) {
19335 self.display_map
19336 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19337 if let Some(autoscroll) = autoscroll {
19338 self.request_autoscroll(autoscroll, cx);
19339 }
19340 cx.notify();
19341 }
19342
19343 pub fn remove_blocks(
19344 &mut self,
19345 block_ids: HashSet<CustomBlockId>,
19346 autoscroll: Option<Autoscroll>,
19347 cx: &mut Context<Self>,
19348 ) {
19349 self.display_map.update(cx, |display_map, cx| {
19350 display_map.remove_blocks(block_ids, cx)
19351 });
19352 if let Some(autoscroll) = autoscroll {
19353 self.request_autoscroll(autoscroll, cx);
19354 }
19355 cx.notify();
19356 }
19357
19358 pub fn row_for_block(
19359 &self,
19360 block_id: CustomBlockId,
19361 cx: &mut Context<Self>,
19362 ) -> Option<DisplayRow> {
19363 self.display_map
19364 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19365 }
19366
19367 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19368 self.focused_block = Some(focused_block);
19369 }
19370
19371 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19372 self.focused_block.take()
19373 }
19374
19375 pub fn insert_creases(
19376 &mut self,
19377 creases: impl IntoIterator<Item = Crease<Anchor>>,
19378 cx: &mut Context<Self>,
19379 ) -> Vec<CreaseId> {
19380 self.display_map
19381 .update(cx, |map, cx| map.insert_creases(creases, cx))
19382 }
19383
19384 pub fn remove_creases(
19385 &mut self,
19386 ids: impl IntoIterator<Item = CreaseId>,
19387 cx: &mut Context<Self>,
19388 ) -> Vec<(CreaseId, Range<Anchor>)> {
19389 self.display_map
19390 .update(cx, |map, cx| map.remove_creases(ids, cx))
19391 }
19392
19393 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19394 self.display_map
19395 .update(cx, |map, cx| map.snapshot(cx))
19396 .longest_row()
19397 }
19398
19399 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19400 self.display_map
19401 .update(cx, |map, cx| map.snapshot(cx))
19402 .max_point()
19403 }
19404
19405 pub fn text(&self, cx: &App) -> String {
19406 self.buffer.read(cx).read(cx).text()
19407 }
19408
19409 pub fn is_empty(&self, cx: &App) -> bool {
19410 self.buffer.read(cx).read(cx).is_empty()
19411 }
19412
19413 pub fn text_option(&self, cx: &App) -> Option<String> {
19414 let text = self.text(cx);
19415 let text = text.trim();
19416
19417 if text.is_empty() {
19418 return None;
19419 }
19420
19421 Some(text.to_string())
19422 }
19423
19424 pub fn set_text(
19425 &mut self,
19426 text: impl Into<Arc<str>>,
19427 window: &mut Window,
19428 cx: &mut Context<Self>,
19429 ) {
19430 self.transact(window, cx, |this, _, cx| {
19431 this.buffer
19432 .read(cx)
19433 .as_singleton()
19434 .expect("you can only call set_text on editors for singleton buffers")
19435 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19436 });
19437 }
19438
19439 pub fn display_text(&self, cx: &mut App) -> String {
19440 self.display_map
19441 .update(cx, |map, cx| map.snapshot(cx))
19442 .text()
19443 }
19444
19445 fn create_minimap(
19446 &self,
19447 minimap_settings: MinimapSettings,
19448 window: &mut Window,
19449 cx: &mut Context<Self>,
19450 ) -> Option<Entity<Self>> {
19451 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19452 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19453 }
19454
19455 fn initialize_new_minimap(
19456 &self,
19457 minimap_settings: MinimapSettings,
19458 window: &mut Window,
19459 cx: &mut Context<Self>,
19460 ) -> Entity<Self> {
19461 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19462
19463 let mut minimap = Editor::new_internal(
19464 EditorMode::Minimap {
19465 parent: cx.weak_entity(),
19466 },
19467 self.buffer.clone(),
19468 None,
19469 Some(self.display_map.clone()),
19470 window,
19471 cx,
19472 );
19473 minimap.scroll_manager.clone_state(&self.scroll_manager);
19474 minimap.set_text_style_refinement(TextStyleRefinement {
19475 font_size: Some(MINIMAP_FONT_SIZE),
19476 font_weight: Some(MINIMAP_FONT_WEIGHT),
19477 ..Default::default()
19478 });
19479 minimap.update_minimap_configuration(minimap_settings, cx);
19480 cx.new(|_| minimap)
19481 }
19482
19483 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19484 let current_line_highlight = minimap_settings
19485 .current_line_highlight
19486 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19487 self.set_current_line_highlight(Some(current_line_highlight));
19488 }
19489
19490 pub fn minimap(&self) -> Option<&Entity<Self>> {
19491 self.minimap
19492 .as_ref()
19493 .filter(|_| self.minimap_visibility.visible())
19494 }
19495
19496 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19497 let mut wrap_guides = smallvec![];
19498
19499 if self.show_wrap_guides == Some(false) {
19500 return wrap_guides;
19501 }
19502
19503 let settings = self.buffer.read(cx).language_settings(cx);
19504 if settings.show_wrap_guides {
19505 match self.soft_wrap_mode(cx) {
19506 SoftWrap::Column(soft_wrap) => {
19507 wrap_guides.push((soft_wrap as usize, true));
19508 }
19509 SoftWrap::Bounded(soft_wrap) => {
19510 wrap_guides.push((soft_wrap as usize, true));
19511 }
19512 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19513 }
19514 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19515 }
19516
19517 wrap_guides
19518 }
19519
19520 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19521 let settings = self.buffer.read(cx).language_settings(cx);
19522 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19523 match mode {
19524 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19525 SoftWrap::None
19526 }
19527 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19528 language_settings::SoftWrap::PreferredLineLength => {
19529 SoftWrap::Column(settings.preferred_line_length)
19530 }
19531 language_settings::SoftWrap::Bounded => {
19532 SoftWrap::Bounded(settings.preferred_line_length)
19533 }
19534 }
19535 }
19536
19537 pub fn set_soft_wrap_mode(
19538 &mut self,
19539 mode: language_settings::SoftWrap,
19540
19541 cx: &mut Context<Self>,
19542 ) {
19543 self.soft_wrap_mode_override = Some(mode);
19544 cx.notify();
19545 }
19546
19547 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19548 self.hard_wrap = hard_wrap;
19549 cx.notify();
19550 }
19551
19552 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19553 self.text_style_refinement = Some(style);
19554 }
19555
19556 /// called by the Element so we know what style we were most recently rendered with.
19557 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19558 // We intentionally do not inform the display map about the minimap style
19559 // so that wrapping is not recalculated and stays consistent for the editor
19560 // and its linked minimap.
19561 if !self.mode.is_minimap() {
19562 let font = style.text.font();
19563 let font_size = style.text.font_size.to_pixels(window.rem_size());
19564 let display_map = self
19565 .placeholder_display_map
19566 .as_ref()
19567 .filter(|_| self.is_empty(cx))
19568 .unwrap_or(&self.display_map);
19569
19570 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19571 }
19572 self.style = Some(style);
19573 }
19574
19575 pub fn style(&self) -> Option<&EditorStyle> {
19576 self.style.as_ref()
19577 }
19578
19579 // Called by the element. This method is not designed to be called outside of the editor
19580 // element's layout code because it does not notify when rewrapping is computed synchronously.
19581 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19582 if self.is_empty(cx) {
19583 self.placeholder_display_map
19584 .as_ref()
19585 .map_or(false, |display_map| {
19586 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19587 })
19588 } else {
19589 self.display_map
19590 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19591 }
19592 }
19593
19594 pub fn set_soft_wrap(&mut self) {
19595 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19596 }
19597
19598 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19599 if self.soft_wrap_mode_override.is_some() {
19600 self.soft_wrap_mode_override.take();
19601 } else {
19602 let soft_wrap = match self.soft_wrap_mode(cx) {
19603 SoftWrap::GitDiff => return,
19604 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19605 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19606 language_settings::SoftWrap::None
19607 }
19608 };
19609 self.soft_wrap_mode_override = Some(soft_wrap);
19610 }
19611 cx.notify();
19612 }
19613
19614 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19615 let Some(workspace) = self.workspace() else {
19616 return;
19617 };
19618 let fs = workspace.read(cx).app_state().fs.clone();
19619 let current_show = TabBarSettings::get_global(cx).show;
19620 update_settings_file(fs, cx, move |setting, _| {
19621 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19622 });
19623 }
19624
19625 pub fn toggle_indent_guides(
19626 &mut self,
19627 _: &ToggleIndentGuides,
19628 _: &mut Window,
19629 cx: &mut Context<Self>,
19630 ) {
19631 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19632 self.buffer
19633 .read(cx)
19634 .language_settings(cx)
19635 .indent_guides
19636 .enabled
19637 });
19638 self.show_indent_guides = Some(!currently_enabled);
19639 cx.notify();
19640 }
19641
19642 fn should_show_indent_guides(&self) -> Option<bool> {
19643 self.show_indent_guides
19644 }
19645
19646 pub fn toggle_line_numbers(
19647 &mut self,
19648 _: &ToggleLineNumbers,
19649 _: &mut Window,
19650 cx: &mut Context<Self>,
19651 ) {
19652 let mut editor_settings = EditorSettings::get_global(cx).clone();
19653 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19654 EditorSettings::override_global(editor_settings, cx);
19655 }
19656
19657 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19658 if let Some(show_line_numbers) = self.show_line_numbers {
19659 return show_line_numbers;
19660 }
19661 EditorSettings::get_global(cx).gutter.line_numbers
19662 }
19663
19664 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19665 match (
19666 self.use_relative_line_numbers,
19667 EditorSettings::get_global(cx).relative_line_numbers,
19668 ) {
19669 (None, setting) => setting,
19670 (Some(false), _) => RelativeLineNumbers::Disabled,
19671 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19672 (Some(true), _) => RelativeLineNumbers::Enabled,
19673 }
19674 }
19675
19676 pub fn toggle_relative_line_numbers(
19677 &mut self,
19678 _: &ToggleRelativeLineNumbers,
19679 _: &mut Window,
19680 cx: &mut Context<Self>,
19681 ) {
19682 let is_relative = self.relative_line_numbers(cx);
19683 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19684 }
19685
19686 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19687 self.use_relative_line_numbers = is_relative;
19688 cx.notify();
19689 }
19690
19691 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19692 self.show_gutter = show_gutter;
19693 cx.notify();
19694 }
19695
19696 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19697 self.show_scrollbars = ScrollbarAxes {
19698 horizontal: show,
19699 vertical: show,
19700 };
19701 cx.notify();
19702 }
19703
19704 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19705 self.show_scrollbars.vertical = show;
19706 cx.notify();
19707 }
19708
19709 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19710 self.show_scrollbars.horizontal = show;
19711 cx.notify();
19712 }
19713
19714 pub fn set_minimap_visibility(
19715 &mut self,
19716 minimap_visibility: MinimapVisibility,
19717 window: &mut Window,
19718 cx: &mut Context<Self>,
19719 ) {
19720 if self.minimap_visibility != minimap_visibility {
19721 if minimap_visibility.visible() && self.minimap.is_none() {
19722 let minimap_settings = EditorSettings::get_global(cx).minimap;
19723 self.minimap =
19724 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19725 }
19726 self.minimap_visibility = minimap_visibility;
19727 cx.notify();
19728 }
19729 }
19730
19731 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19732 self.set_show_scrollbars(false, cx);
19733 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19734 }
19735
19736 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19737 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19738 }
19739
19740 /// Normally the text in full mode and auto height editors is padded on the
19741 /// left side by roughly half a character width for improved hit testing.
19742 ///
19743 /// Use this method to disable this for cases where this is not wanted (e.g.
19744 /// if you want to align the editor text with some other text above or below)
19745 /// or if you want to add this padding to single-line editors.
19746 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19747 self.offset_content = offset_content;
19748 cx.notify();
19749 }
19750
19751 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19752 self.show_line_numbers = Some(show_line_numbers);
19753 cx.notify();
19754 }
19755
19756 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19757 self.disable_expand_excerpt_buttons = true;
19758 cx.notify();
19759 }
19760
19761 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19762 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19763 cx.notify();
19764 }
19765
19766 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19767 self.show_code_actions = Some(show_code_actions);
19768 cx.notify();
19769 }
19770
19771 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19772 self.show_runnables = Some(show_runnables);
19773 cx.notify();
19774 }
19775
19776 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19777 self.show_breakpoints = Some(show_breakpoints);
19778 cx.notify();
19779 }
19780
19781 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19782 if self.display_map.read(cx).masked != masked {
19783 self.display_map.update(cx, |map, _| map.masked = masked);
19784 }
19785 cx.notify()
19786 }
19787
19788 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19789 self.show_wrap_guides = Some(show_wrap_guides);
19790 cx.notify();
19791 }
19792
19793 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19794 self.show_indent_guides = Some(show_indent_guides);
19795 cx.notify();
19796 }
19797
19798 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19799 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19800 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19801 && let Some(dir) = file.abs_path(cx).parent()
19802 {
19803 return Some(dir.to_owned());
19804 }
19805 }
19806
19807 None
19808 }
19809
19810 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19811 self.active_excerpt(cx)?
19812 .1
19813 .read(cx)
19814 .file()
19815 .and_then(|f| f.as_local())
19816 }
19817
19818 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19819 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19820 let buffer = buffer.read(cx);
19821 if let Some(project_path) = buffer.project_path(cx) {
19822 let project = self.project()?.read(cx);
19823 project.absolute_path(&project_path, cx)
19824 } else {
19825 buffer
19826 .file()
19827 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19828 }
19829 })
19830 }
19831
19832 pub fn reveal_in_finder(
19833 &mut self,
19834 _: &RevealInFileManager,
19835 _window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 if let Some(target) = self.target_file(cx) {
19839 cx.reveal_path(&target.abs_path(cx));
19840 }
19841 }
19842
19843 pub fn copy_path(
19844 &mut self,
19845 _: &zed_actions::workspace::CopyPath,
19846 _window: &mut Window,
19847 cx: &mut Context<Self>,
19848 ) {
19849 if let Some(path) = self.target_file_abs_path(cx)
19850 && let Some(path) = path.to_str()
19851 {
19852 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19853 } else {
19854 cx.propagate();
19855 }
19856 }
19857
19858 pub fn copy_relative_path(
19859 &mut self,
19860 _: &zed_actions::workspace::CopyRelativePath,
19861 _window: &mut Window,
19862 cx: &mut Context<Self>,
19863 ) {
19864 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19865 let project = self.project()?.read(cx);
19866 let path = buffer.read(cx).file()?.path();
19867 let path = path.display(project.path_style(cx));
19868 Some(path)
19869 }) {
19870 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19871 } else {
19872 cx.propagate();
19873 }
19874 }
19875
19876 /// Returns the project path for the editor's buffer, if any buffer is
19877 /// opened in the editor.
19878 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19879 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19880 buffer.read(cx).project_path(cx)
19881 } else {
19882 None
19883 }
19884 }
19885
19886 // Returns true if the editor handled a go-to-line request
19887 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19888 maybe!({
19889 let breakpoint_store = self.breakpoint_store.as_ref()?;
19890
19891 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19892 else {
19893 self.clear_row_highlights::<ActiveDebugLine>();
19894 return None;
19895 };
19896
19897 let position = active_stack_frame.position;
19898 let buffer_id = position.buffer_id?;
19899 let snapshot = self
19900 .project
19901 .as_ref()?
19902 .read(cx)
19903 .buffer_for_id(buffer_id, cx)?
19904 .read(cx)
19905 .snapshot();
19906
19907 let mut handled = false;
19908 for (id, ExcerptRange { context, .. }) in
19909 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19910 {
19911 if context.start.cmp(&position, &snapshot).is_ge()
19912 || context.end.cmp(&position, &snapshot).is_lt()
19913 {
19914 continue;
19915 }
19916 let snapshot = self.buffer.read(cx).snapshot(cx);
19917 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19918
19919 handled = true;
19920 self.clear_row_highlights::<ActiveDebugLine>();
19921
19922 self.go_to_line::<ActiveDebugLine>(
19923 multibuffer_anchor,
19924 Some(cx.theme().colors().editor_debugger_active_line_background),
19925 window,
19926 cx,
19927 );
19928
19929 cx.notify();
19930 }
19931
19932 handled.then_some(())
19933 })
19934 .is_some()
19935 }
19936
19937 pub fn copy_file_name_without_extension(
19938 &mut self,
19939 _: &CopyFileNameWithoutExtension,
19940 _: &mut Window,
19941 cx: &mut Context<Self>,
19942 ) {
19943 if let Some(file) = self.target_file(cx)
19944 && let Some(file_stem) = file.path().file_stem()
19945 {
19946 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19947 }
19948 }
19949
19950 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19951 if let Some(file) = self.target_file(cx)
19952 && let Some(name) = file.path().file_name()
19953 {
19954 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19955 }
19956 }
19957
19958 pub fn toggle_git_blame(
19959 &mut self,
19960 _: &::git::Blame,
19961 window: &mut Window,
19962 cx: &mut Context<Self>,
19963 ) {
19964 self.show_git_blame_gutter = !self.show_git_blame_gutter;
19965
19966 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
19967 self.start_git_blame(true, window, cx);
19968 }
19969
19970 cx.notify();
19971 }
19972
19973 pub fn toggle_git_blame_inline(
19974 &mut self,
19975 _: &ToggleGitBlameInline,
19976 window: &mut Window,
19977 cx: &mut Context<Self>,
19978 ) {
19979 self.toggle_git_blame_inline_internal(true, window, cx);
19980 cx.notify();
19981 }
19982
19983 pub fn open_git_blame_commit(
19984 &mut self,
19985 _: &OpenGitBlameCommit,
19986 window: &mut Window,
19987 cx: &mut Context<Self>,
19988 ) {
19989 self.open_git_blame_commit_internal(window, cx);
19990 }
19991
19992 fn open_git_blame_commit_internal(
19993 &mut self,
19994 window: &mut Window,
19995 cx: &mut Context<Self>,
19996 ) -> Option<()> {
19997 let blame = self.blame.as_ref()?;
19998 let snapshot = self.snapshot(window, cx);
19999 let cursor = self
20000 .selections
20001 .newest::<Point>(&snapshot.display_snapshot)
20002 .head();
20003 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20004 let (_, blame_entry) = blame
20005 .update(cx, |blame, cx| {
20006 blame
20007 .blame_for_rows(
20008 &[RowInfo {
20009 buffer_id: Some(buffer.remote_id()),
20010 buffer_row: Some(point.row),
20011 ..Default::default()
20012 }],
20013 cx,
20014 )
20015 .next()
20016 })
20017 .flatten()?;
20018 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20019 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20020 let workspace = self.workspace()?.downgrade();
20021 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20022 None
20023 }
20024
20025 pub fn git_blame_inline_enabled(&self) -> bool {
20026 self.git_blame_inline_enabled
20027 }
20028
20029 pub fn toggle_selection_menu(
20030 &mut self,
20031 _: &ToggleSelectionMenu,
20032 _: &mut Window,
20033 cx: &mut Context<Self>,
20034 ) {
20035 self.show_selection_menu = self
20036 .show_selection_menu
20037 .map(|show_selections_menu| !show_selections_menu)
20038 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20039
20040 cx.notify();
20041 }
20042
20043 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20044 self.show_selection_menu
20045 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20046 }
20047
20048 fn start_git_blame(
20049 &mut self,
20050 user_triggered: bool,
20051 window: &mut Window,
20052 cx: &mut Context<Self>,
20053 ) {
20054 if let Some(project) = self.project() {
20055 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20056 && buffer.read(cx).file().is_none()
20057 {
20058 return;
20059 }
20060
20061 let focused = self.focus_handle(cx).contains_focused(window, cx);
20062
20063 let project = project.clone();
20064 let blame = cx
20065 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20066 self.blame_subscription =
20067 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20068 self.blame = Some(blame);
20069 }
20070 }
20071
20072 fn toggle_git_blame_inline_internal(
20073 &mut self,
20074 user_triggered: bool,
20075 window: &mut Window,
20076 cx: &mut Context<Self>,
20077 ) {
20078 if self.git_blame_inline_enabled {
20079 self.git_blame_inline_enabled = false;
20080 self.show_git_blame_inline = false;
20081 self.show_git_blame_inline_delay_task.take();
20082 } else {
20083 self.git_blame_inline_enabled = true;
20084 self.start_git_blame_inline(user_triggered, window, cx);
20085 }
20086
20087 cx.notify();
20088 }
20089
20090 fn start_git_blame_inline(
20091 &mut self,
20092 user_triggered: bool,
20093 window: &mut Window,
20094 cx: &mut Context<Self>,
20095 ) {
20096 self.start_git_blame(user_triggered, window, cx);
20097
20098 if ProjectSettings::get_global(cx)
20099 .git
20100 .inline_blame_delay()
20101 .is_some()
20102 {
20103 self.start_inline_blame_timer(window, cx);
20104 } else {
20105 self.show_git_blame_inline = true
20106 }
20107 }
20108
20109 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20110 self.blame.as_ref()
20111 }
20112
20113 pub fn show_git_blame_gutter(&self) -> bool {
20114 self.show_git_blame_gutter
20115 }
20116
20117 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20118 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20119 }
20120
20121 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20122 self.show_git_blame_inline
20123 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20124 && !self.newest_selection_head_on_empty_line(cx)
20125 && self.has_blame_entries(cx)
20126 }
20127
20128 fn has_blame_entries(&self, cx: &App) -> bool {
20129 self.blame()
20130 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20131 }
20132
20133 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20134 let cursor_anchor = self.selections.newest_anchor().head();
20135
20136 let snapshot = self.buffer.read(cx).snapshot(cx);
20137 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20138
20139 snapshot.line_len(buffer_row) == 0
20140 }
20141
20142 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20143 let buffer_and_selection = maybe!({
20144 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20145 let selection_range = selection.range();
20146
20147 let multi_buffer = self.buffer().read(cx);
20148 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20149 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20150
20151 let (buffer, range, _) = if selection.reversed {
20152 buffer_ranges.first()
20153 } else {
20154 buffer_ranges.last()
20155 }?;
20156
20157 let selection = text::ToPoint::to_point(&range.start, buffer).row
20158 ..text::ToPoint::to_point(&range.end, buffer).row;
20159 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20160 });
20161
20162 let Some((buffer, selection)) = buffer_and_selection else {
20163 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20164 };
20165
20166 let Some(project) = self.project() else {
20167 return Task::ready(Err(anyhow!("editor does not have project")));
20168 };
20169
20170 project.update(cx, |project, cx| {
20171 project.get_permalink_to_line(&buffer, selection, cx)
20172 })
20173 }
20174
20175 pub fn copy_permalink_to_line(
20176 &mut self,
20177 _: &CopyPermalinkToLine,
20178 window: &mut Window,
20179 cx: &mut Context<Self>,
20180 ) {
20181 let permalink_task = self.get_permalink_to_line(cx);
20182 let workspace = self.workspace();
20183
20184 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20185 Ok(permalink) => {
20186 cx.update(|_, cx| {
20187 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20188 })
20189 .ok();
20190 }
20191 Err(err) => {
20192 let message = format!("Failed to copy permalink: {err}");
20193
20194 anyhow::Result::<()>::Err(err).log_err();
20195
20196 if let Some(workspace) = workspace {
20197 workspace
20198 .update_in(cx, |workspace, _, cx| {
20199 struct CopyPermalinkToLine;
20200
20201 workspace.show_toast(
20202 Toast::new(
20203 NotificationId::unique::<CopyPermalinkToLine>(),
20204 message,
20205 ),
20206 cx,
20207 )
20208 })
20209 .ok();
20210 }
20211 }
20212 })
20213 .detach();
20214 }
20215
20216 pub fn copy_file_location(
20217 &mut self,
20218 _: &CopyFileLocation,
20219 _: &mut Window,
20220 cx: &mut Context<Self>,
20221 ) {
20222 let selection = self
20223 .selections
20224 .newest::<Point>(&self.display_snapshot(cx))
20225 .start
20226 .row
20227 + 1;
20228 if let Some(file) = self.target_file(cx) {
20229 let path = file.path().display(file.path_style(cx));
20230 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20231 }
20232 }
20233
20234 pub fn open_permalink_to_line(
20235 &mut self,
20236 _: &OpenPermalinkToLine,
20237 window: &mut Window,
20238 cx: &mut Context<Self>,
20239 ) {
20240 let permalink_task = self.get_permalink_to_line(cx);
20241 let workspace = self.workspace();
20242
20243 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20244 Ok(permalink) => {
20245 cx.update(|_, cx| {
20246 cx.open_url(permalink.as_ref());
20247 })
20248 .ok();
20249 }
20250 Err(err) => {
20251 let message = format!("Failed to open permalink: {err}");
20252
20253 anyhow::Result::<()>::Err(err).log_err();
20254
20255 if let Some(workspace) = workspace {
20256 workspace
20257 .update(cx, |workspace, cx| {
20258 struct OpenPermalinkToLine;
20259
20260 workspace.show_toast(
20261 Toast::new(
20262 NotificationId::unique::<OpenPermalinkToLine>(),
20263 message,
20264 ),
20265 cx,
20266 )
20267 })
20268 .ok();
20269 }
20270 }
20271 })
20272 .detach();
20273 }
20274
20275 pub fn insert_uuid_v4(
20276 &mut self,
20277 _: &InsertUuidV4,
20278 window: &mut Window,
20279 cx: &mut Context<Self>,
20280 ) {
20281 self.insert_uuid(UuidVersion::V4, window, cx);
20282 }
20283
20284 pub fn insert_uuid_v7(
20285 &mut self,
20286 _: &InsertUuidV7,
20287 window: &mut Window,
20288 cx: &mut Context<Self>,
20289 ) {
20290 self.insert_uuid(UuidVersion::V7, window, cx);
20291 }
20292
20293 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20294 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20295 self.transact(window, cx, |this, window, cx| {
20296 let edits = this
20297 .selections
20298 .all::<Point>(&this.display_snapshot(cx))
20299 .into_iter()
20300 .map(|selection| {
20301 let uuid = match version {
20302 UuidVersion::V4 => uuid::Uuid::new_v4(),
20303 UuidVersion::V7 => uuid::Uuid::now_v7(),
20304 };
20305
20306 (selection.range(), uuid.to_string())
20307 });
20308 this.edit(edits, cx);
20309 this.refresh_edit_prediction(true, false, window, cx);
20310 });
20311 }
20312
20313 pub fn open_selections_in_multibuffer(
20314 &mut self,
20315 _: &OpenSelectionsInMultibuffer,
20316 window: &mut Window,
20317 cx: &mut Context<Self>,
20318 ) {
20319 let multibuffer = self.buffer.read(cx);
20320
20321 let Some(buffer) = multibuffer.as_singleton() else {
20322 return;
20323 };
20324
20325 let Some(workspace) = self.workspace() else {
20326 return;
20327 };
20328
20329 let title = multibuffer.title(cx).to_string();
20330
20331 let locations = self
20332 .selections
20333 .all_anchors(&self.display_snapshot(cx))
20334 .iter()
20335 .map(|selection| {
20336 (
20337 buffer.clone(),
20338 (selection.start.text_anchor..selection.end.text_anchor)
20339 .to_point(buffer.read(cx)),
20340 )
20341 })
20342 .into_group_map();
20343
20344 cx.spawn_in(window, async move |_, cx| {
20345 workspace.update_in(cx, |workspace, window, cx| {
20346 Self::open_locations_in_multibuffer(
20347 workspace,
20348 locations,
20349 format!("Selections for '{title}'"),
20350 false,
20351 MultibufferSelectionMode::All,
20352 window,
20353 cx,
20354 );
20355 })
20356 })
20357 .detach();
20358 }
20359
20360 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20361 /// last highlight added will be used.
20362 ///
20363 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20364 pub fn highlight_rows<T: 'static>(
20365 &mut self,
20366 range: Range<Anchor>,
20367 color: Hsla,
20368 options: RowHighlightOptions,
20369 cx: &mut Context<Self>,
20370 ) {
20371 let snapshot = self.buffer().read(cx).snapshot(cx);
20372 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20373 let ix = row_highlights.binary_search_by(|highlight| {
20374 Ordering::Equal
20375 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20376 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20377 });
20378
20379 if let Err(mut ix) = ix {
20380 let index = post_inc(&mut self.highlight_order);
20381
20382 // If this range intersects with the preceding highlight, then merge it with
20383 // the preceding highlight. Otherwise insert a new highlight.
20384 let mut merged = false;
20385 if ix > 0 {
20386 let prev_highlight = &mut row_highlights[ix - 1];
20387 if prev_highlight
20388 .range
20389 .end
20390 .cmp(&range.start, &snapshot)
20391 .is_ge()
20392 {
20393 ix -= 1;
20394 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20395 prev_highlight.range.end = range.end;
20396 }
20397 merged = true;
20398 prev_highlight.index = index;
20399 prev_highlight.color = color;
20400 prev_highlight.options = options;
20401 }
20402 }
20403
20404 if !merged {
20405 row_highlights.insert(
20406 ix,
20407 RowHighlight {
20408 range,
20409 index,
20410 color,
20411 options,
20412 type_id: TypeId::of::<T>(),
20413 },
20414 );
20415 }
20416
20417 // If any of the following highlights intersect with this one, merge them.
20418 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20419 let highlight = &row_highlights[ix];
20420 if next_highlight
20421 .range
20422 .start
20423 .cmp(&highlight.range.end, &snapshot)
20424 .is_le()
20425 {
20426 if next_highlight
20427 .range
20428 .end
20429 .cmp(&highlight.range.end, &snapshot)
20430 .is_gt()
20431 {
20432 row_highlights[ix].range.end = next_highlight.range.end;
20433 }
20434 row_highlights.remove(ix + 1);
20435 } else {
20436 break;
20437 }
20438 }
20439 }
20440 }
20441
20442 /// Remove any highlighted row ranges of the given type that intersect the
20443 /// given ranges.
20444 pub fn remove_highlighted_rows<T: 'static>(
20445 &mut self,
20446 ranges_to_remove: Vec<Range<Anchor>>,
20447 cx: &mut Context<Self>,
20448 ) {
20449 let snapshot = self.buffer().read(cx).snapshot(cx);
20450 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20451 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20452 row_highlights.retain(|highlight| {
20453 while let Some(range_to_remove) = ranges_to_remove.peek() {
20454 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20455 Ordering::Less | Ordering::Equal => {
20456 ranges_to_remove.next();
20457 }
20458 Ordering::Greater => {
20459 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20460 Ordering::Less | Ordering::Equal => {
20461 return false;
20462 }
20463 Ordering::Greater => break,
20464 }
20465 }
20466 }
20467 }
20468
20469 true
20470 })
20471 }
20472
20473 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20474 pub fn clear_row_highlights<T: 'static>(&mut self) {
20475 self.highlighted_rows.remove(&TypeId::of::<T>());
20476 }
20477
20478 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20479 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20480 self.highlighted_rows
20481 .get(&TypeId::of::<T>())
20482 .map_or(&[] as &[_], |vec| vec.as_slice())
20483 .iter()
20484 .map(|highlight| (highlight.range.clone(), highlight.color))
20485 }
20486
20487 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20488 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20489 /// Allows to ignore certain kinds of highlights.
20490 pub fn highlighted_display_rows(
20491 &self,
20492 window: &mut Window,
20493 cx: &mut App,
20494 ) -> BTreeMap<DisplayRow, LineHighlight> {
20495 let snapshot = self.snapshot(window, cx);
20496 let mut used_highlight_orders = HashMap::default();
20497 self.highlighted_rows
20498 .iter()
20499 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20500 .fold(
20501 BTreeMap::<DisplayRow, LineHighlight>::new(),
20502 |mut unique_rows, highlight| {
20503 let start = highlight.range.start.to_display_point(&snapshot);
20504 let end = highlight.range.end.to_display_point(&snapshot);
20505 let start_row = start.row().0;
20506 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20507 && end.column() == 0
20508 {
20509 end.row().0.saturating_sub(1)
20510 } else {
20511 end.row().0
20512 };
20513 for row in start_row..=end_row {
20514 let used_index =
20515 used_highlight_orders.entry(row).or_insert(highlight.index);
20516 if highlight.index >= *used_index {
20517 *used_index = highlight.index;
20518 unique_rows.insert(
20519 DisplayRow(row),
20520 LineHighlight {
20521 include_gutter: highlight.options.include_gutter,
20522 border: None,
20523 background: highlight.color.into(),
20524 type_id: Some(highlight.type_id),
20525 },
20526 );
20527 }
20528 }
20529 unique_rows
20530 },
20531 )
20532 }
20533
20534 pub fn highlighted_display_row_for_autoscroll(
20535 &self,
20536 snapshot: &DisplaySnapshot,
20537 ) -> Option<DisplayRow> {
20538 self.highlighted_rows
20539 .values()
20540 .flat_map(|highlighted_rows| highlighted_rows.iter())
20541 .filter_map(|highlight| {
20542 if highlight.options.autoscroll {
20543 Some(highlight.range.start.to_display_point(snapshot).row())
20544 } else {
20545 None
20546 }
20547 })
20548 .min()
20549 }
20550
20551 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20552 self.highlight_background::<SearchWithinRange>(
20553 ranges,
20554 |colors| colors.colors().editor_document_highlight_read_background,
20555 cx,
20556 )
20557 }
20558
20559 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20560 self.breadcrumb_header = Some(new_header);
20561 }
20562
20563 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20564 self.clear_background_highlights::<SearchWithinRange>(cx);
20565 }
20566
20567 pub fn highlight_background<T: 'static>(
20568 &mut self,
20569 ranges: &[Range<Anchor>],
20570 color_fetcher: fn(&Theme) -> Hsla,
20571 cx: &mut Context<Self>,
20572 ) {
20573 self.background_highlights.insert(
20574 HighlightKey::Type(TypeId::of::<T>()),
20575 (color_fetcher, Arc::from(ranges)),
20576 );
20577 self.scrollbar_marker_state.dirty = true;
20578 cx.notify();
20579 }
20580
20581 pub fn highlight_background_key<T: 'static>(
20582 &mut self,
20583 key: usize,
20584 ranges: &[Range<Anchor>],
20585 color_fetcher: fn(&Theme) -> Hsla,
20586 cx: &mut Context<Self>,
20587 ) {
20588 self.background_highlights.insert(
20589 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20590 (color_fetcher, Arc::from(ranges)),
20591 );
20592 self.scrollbar_marker_state.dirty = true;
20593 cx.notify();
20594 }
20595
20596 pub fn clear_background_highlights<T: 'static>(
20597 &mut self,
20598 cx: &mut Context<Self>,
20599 ) -> Option<BackgroundHighlight> {
20600 let text_highlights = self
20601 .background_highlights
20602 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20603 if !text_highlights.1.is_empty() {
20604 self.scrollbar_marker_state.dirty = true;
20605 cx.notify();
20606 }
20607 Some(text_highlights)
20608 }
20609
20610 pub fn highlight_gutter<T: 'static>(
20611 &mut self,
20612 ranges: impl Into<Vec<Range<Anchor>>>,
20613 color_fetcher: fn(&App) -> Hsla,
20614 cx: &mut Context<Self>,
20615 ) {
20616 self.gutter_highlights
20617 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20618 cx.notify();
20619 }
20620
20621 pub fn clear_gutter_highlights<T: 'static>(
20622 &mut self,
20623 cx: &mut Context<Self>,
20624 ) -> Option<GutterHighlight> {
20625 cx.notify();
20626 self.gutter_highlights.remove(&TypeId::of::<T>())
20627 }
20628
20629 pub fn insert_gutter_highlight<T: 'static>(
20630 &mut self,
20631 range: Range<Anchor>,
20632 color_fetcher: fn(&App) -> Hsla,
20633 cx: &mut Context<Self>,
20634 ) {
20635 let snapshot = self.buffer().read(cx).snapshot(cx);
20636 let mut highlights = self
20637 .gutter_highlights
20638 .remove(&TypeId::of::<T>())
20639 .map(|(_, highlights)| highlights)
20640 .unwrap_or_default();
20641 let ix = highlights.binary_search_by(|highlight| {
20642 Ordering::Equal
20643 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20644 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20645 });
20646 if let Err(ix) = ix {
20647 highlights.insert(ix, range);
20648 }
20649 self.gutter_highlights
20650 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20651 }
20652
20653 pub fn remove_gutter_highlights<T: 'static>(
20654 &mut self,
20655 ranges_to_remove: Vec<Range<Anchor>>,
20656 cx: &mut Context<Self>,
20657 ) {
20658 let snapshot = self.buffer().read(cx).snapshot(cx);
20659 let Some((color_fetcher, mut gutter_highlights)) =
20660 self.gutter_highlights.remove(&TypeId::of::<T>())
20661 else {
20662 return;
20663 };
20664 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20665 gutter_highlights.retain(|highlight| {
20666 while let Some(range_to_remove) = ranges_to_remove.peek() {
20667 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20668 Ordering::Less | Ordering::Equal => {
20669 ranges_to_remove.next();
20670 }
20671 Ordering::Greater => {
20672 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20673 Ordering::Less | Ordering::Equal => {
20674 return false;
20675 }
20676 Ordering::Greater => break,
20677 }
20678 }
20679 }
20680 }
20681
20682 true
20683 });
20684 self.gutter_highlights
20685 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20686 }
20687
20688 #[cfg(feature = "test-support")]
20689 pub fn all_text_highlights(
20690 &self,
20691 window: &mut Window,
20692 cx: &mut Context<Self>,
20693 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20694 let snapshot = self.snapshot(window, cx);
20695 self.display_map.update(cx, |display_map, _| {
20696 display_map
20697 .all_text_highlights()
20698 .map(|highlight| {
20699 let (style, ranges) = highlight.as_ref();
20700 (
20701 *style,
20702 ranges
20703 .iter()
20704 .map(|range| range.clone().to_display_points(&snapshot))
20705 .collect(),
20706 )
20707 })
20708 .collect()
20709 })
20710 }
20711
20712 #[cfg(feature = "test-support")]
20713 pub fn all_text_background_highlights(
20714 &self,
20715 window: &mut Window,
20716 cx: &mut Context<Self>,
20717 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20718 let snapshot = self.snapshot(window, cx);
20719 let buffer = &snapshot.buffer_snapshot();
20720 let start = buffer.anchor_before(0);
20721 let end = buffer.anchor_after(buffer.len());
20722 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20723 }
20724
20725 #[cfg(any(test, feature = "test-support"))]
20726 pub fn sorted_background_highlights_in_range(
20727 &self,
20728 search_range: Range<Anchor>,
20729 display_snapshot: &DisplaySnapshot,
20730 theme: &Theme,
20731 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20732 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20733 res.sort_by(|a, b| {
20734 a.0.start
20735 .cmp(&b.0.start)
20736 .then_with(|| a.0.end.cmp(&b.0.end))
20737 .then_with(|| a.1.cmp(&b.1))
20738 });
20739 res
20740 }
20741
20742 #[cfg(feature = "test-support")]
20743 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20744 let snapshot = self.buffer().read(cx).snapshot(cx);
20745
20746 let highlights = self
20747 .background_highlights
20748 .get(&HighlightKey::Type(TypeId::of::<
20749 items::BufferSearchHighlights,
20750 >()));
20751
20752 if let Some((_color, ranges)) = highlights {
20753 ranges
20754 .iter()
20755 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20756 .collect_vec()
20757 } else {
20758 vec![]
20759 }
20760 }
20761
20762 fn document_highlights_for_position<'a>(
20763 &'a self,
20764 position: Anchor,
20765 buffer: &'a MultiBufferSnapshot,
20766 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20767 let read_highlights = self
20768 .background_highlights
20769 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20770 .map(|h| &h.1);
20771 let write_highlights = self
20772 .background_highlights
20773 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20774 .map(|h| &h.1);
20775 let left_position = position.bias_left(buffer);
20776 let right_position = position.bias_right(buffer);
20777 read_highlights
20778 .into_iter()
20779 .chain(write_highlights)
20780 .flat_map(move |ranges| {
20781 let start_ix = match ranges.binary_search_by(|probe| {
20782 let cmp = probe.end.cmp(&left_position, buffer);
20783 if cmp.is_ge() {
20784 Ordering::Greater
20785 } else {
20786 Ordering::Less
20787 }
20788 }) {
20789 Ok(i) | Err(i) => i,
20790 };
20791
20792 ranges[start_ix..]
20793 .iter()
20794 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20795 })
20796 }
20797
20798 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20799 self.background_highlights
20800 .get(&HighlightKey::Type(TypeId::of::<T>()))
20801 .is_some_and(|(_, highlights)| !highlights.is_empty())
20802 }
20803
20804 /// Returns all background highlights for a given range.
20805 ///
20806 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20807 pub fn background_highlights_in_range(
20808 &self,
20809 search_range: Range<Anchor>,
20810 display_snapshot: &DisplaySnapshot,
20811 theme: &Theme,
20812 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20813 let mut results = Vec::new();
20814 for (color_fetcher, ranges) in self.background_highlights.values() {
20815 let color = color_fetcher(theme);
20816 let start_ix = match ranges.binary_search_by(|probe| {
20817 let cmp = probe
20818 .end
20819 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20820 if cmp.is_gt() {
20821 Ordering::Greater
20822 } else {
20823 Ordering::Less
20824 }
20825 }) {
20826 Ok(i) | Err(i) => i,
20827 };
20828 for range in &ranges[start_ix..] {
20829 if range
20830 .start
20831 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20832 .is_ge()
20833 {
20834 break;
20835 }
20836
20837 let start = range.start.to_display_point(display_snapshot);
20838 let end = range.end.to_display_point(display_snapshot);
20839 results.push((start..end, color))
20840 }
20841 }
20842 results
20843 }
20844
20845 pub fn gutter_highlights_in_range(
20846 &self,
20847 search_range: Range<Anchor>,
20848 display_snapshot: &DisplaySnapshot,
20849 cx: &App,
20850 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20851 let mut results = Vec::new();
20852 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20853 let color = color_fetcher(cx);
20854 let start_ix = match ranges.binary_search_by(|probe| {
20855 let cmp = probe
20856 .end
20857 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20858 if cmp.is_gt() {
20859 Ordering::Greater
20860 } else {
20861 Ordering::Less
20862 }
20863 }) {
20864 Ok(i) | Err(i) => i,
20865 };
20866 for range in &ranges[start_ix..] {
20867 if range
20868 .start
20869 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20870 .is_ge()
20871 {
20872 break;
20873 }
20874
20875 let start = range.start.to_display_point(display_snapshot);
20876 let end = range.end.to_display_point(display_snapshot);
20877 results.push((start..end, color))
20878 }
20879 }
20880 results
20881 }
20882
20883 /// Get the text ranges corresponding to the redaction query
20884 pub fn redacted_ranges(
20885 &self,
20886 search_range: Range<Anchor>,
20887 display_snapshot: &DisplaySnapshot,
20888 cx: &App,
20889 ) -> Vec<Range<DisplayPoint>> {
20890 display_snapshot
20891 .buffer_snapshot()
20892 .redacted_ranges(search_range, |file| {
20893 if let Some(file) = file {
20894 file.is_private()
20895 && EditorSettings::get(
20896 Some(SettingsLocation {
20897 worktree_id: file.worktree_id(cx),
20898 path: file.path().as_ref(),
20899 }),
20900 cx,
20901 )
20902 .redact_private_values
20903 } else {
20904 false
20905 }
20906 })
20907 .map(|range| {
20908 range.start.to_display_point(display_snapshot)
20909 ..range.end.to_display_point(display_snapshot)
20910 })
20911 .collect()
20912 }
20913
20914 pub fn highlight_text_key<T: 'static>(
20915 &mut self,
20916 key: usize,
20917 ranges: Vec<Range<Anchor>>,
20918 style: HighlightStyle,
20919 cx: &mut Context<Self>,
20920 ) {
20921 self.display_map.update(cx, |map, _| {
20922 map.highlight_text(
20923 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20924 ranges,
20925 style,
20926 );
20927 });
20928 cx.notify();
20929 }
20930
20931 pub fn highlight_text<T: 'static>(
20932 &mut self,
20933 ranges: Vec<Range<Anchor>>,
20934 style: HighlightStyle,
20935 cx: &mut Context<Self>,
20936 ) {
20937 self.display_map.update(cx, |map, _| {
20938 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style, false)
20939 });
20940 cx.notify();
20941 }
20942
20943 pub fn text_highlights<'a, T: 'static>(
20944 &'a self,
20945 cx: &'a App,
20946 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20947 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20948 }
20949
20950 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20951 let cleared = self
20952 .display_map
20953 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20954 if cleared {
20955 cx.notify();
20956 }
20957 }
20958
20959 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20960 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20961 && self.focus_handle.is_focused(window)
20962 }
20963
20964 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
20965 self.show_cursor_when_unfocused = is_enabled;
20966 cx.notify();
20967 }
20968
20969 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
20970 cx.notify();
20971 }
20972
20973 fn on_debug_session_event(
20974 &mut self,
20975 _session: Entity<Session>,
20976 event: &SessionEvent,
20977 cx: &mut Context<Self>,
20978 ) {
20979 if let SessionEvent::InvalidateInlineValue = event {
20980 self.refresh_inline_values(cx);
20981 }
20982 }
20983
20984 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
20985 let Some(project) = self.project.clone() else {
20986 return;
20987 };
20988
20989 if !self.inline_value_cache.enabled {
20990 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
20991 self.splice_inlays(&inlays, Vec::new(), cx);
20992 return;
20993 }
20994
20995 let current_execution_position = self
20996 .highlighted_rows
20997 .get(&TypeId::of::<ActiveDebugLine>())
20998 .and_then(|lines| lines.last().map(|line| line.range.end));
20999
21000 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21001 let inline_values = editor
21002 .update(cx, |editor, cx| {
21003 let Some(current_execution_position) = current_execution_position else {
21004 return Some(Task::ready(Ok(Vec::new())));
21005 };
21006
21007 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21008 let snapshot = buffer.snapshot(cx);
21009
21010 let excerpt = snapshot.excerpt_containing(
21011 current_execution_position..current_execution_position,
21012 )?;
21013
21014 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21015 })?;
21016
21017 let range =
21018 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21019
21020 project.inline_values(buffer, range, cx)
21021 })
21022 .ok()
21023 .flatten()?
21024 .await
21025 .context("refreshing debugger inlays")
21026 .log_err()?;
21027
21028 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21029
21030 for (buffer_id, inline_value) in inline_values
21031 .into_iter()
21032 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21033 {
21034 buffer_inline_values
21035 .entry(buffer_id)
21036 .or_default()
21037 .push(inline_value);
21038 }
21039
21040 editor
21041 .update(cx, |editor, cx| {
21042 let snapshot = editor.buffer.read(cx).snapshot(cx);
21043 let mut new_inlays = Vec::default();
21044
21045 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21046 let buffer_id = buffer_snapshot.remote_id();
21047 buffer_inline_values
21048 .get(&buffer_id)
21049 .into_iter()
21050 .flatten()
21051 .for_each(|hint| {
21052 let inlay = Inlay::debugger(
21053 post_inc(&mut editor.next_inlay_id),
21054 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21055 hint.text(),
21056 );
21057 if !inlay.text().chars().contains(&'\n') {
21058 new_inlays.push(inlay);
21059 }
21060 });
21061 }
21062
21063 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21064 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21065
21066 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21067 })
21068 .ok()?;
21069 Some(())
21070 });
21071 }
21072
21073 fn on_buffer_event(
21074 &mut self,
21075 multibuffer: &Entity<MultiBuffer>,
21076 event: &multi_buffer::Event,
21077 window: &mut Window,
21078 cx: &mut Context<Self>,
21079 ) {
21080 match event {
21081 multi_buffer::Event::Edited { edited_buffer } => {
21082 self.scrollbar_marker_state.dirty = true;
21083 self.active_indent_guides_state.dirty = true;
21084 self.refresh_active_diagnostics(cx);
21085 self.refresh_code_actions(window, cx);
21086 self.refresh_selected_text_highlights(true, window, cx);
21087 self.refresh_single_line_folds(window, cx);
21088 self.colorize_brackets(true, cx);
21089 self.refresh_matching_bracket_highlights(window, cx);
21090 if self.has_active_edit_prediction() {
21091 self.update_visible_edit_prediction(window, cx);
21092 }
21093
21094 if let Some(buffer) = edited_buffer {
21095 if buffer.read(cx).file().is_none() {
21096 cx.emit(EditorEvent::TitleChanged);
21097 }
21098
21099 if self.project.is_some() {
21100 let buffer_id = buffer.read(cx).remote_id();
21101 self.register_buffer(buffer_id, cx);
21102 self.update_lsp_data(Some(buffer_id), window, cx);
21103 self.refresh_inlay_hints(
21104 InlayHintRefreshReason::BufferEdited(buffer_id),
21105 cx,
21106 );
21107 }
21108 }
21109
21110 cx.emit(EditorEvent::BufferEdited);
21111 cx.emit(SearchEvent::MatchesInvalidated);
21112
21113 let Some(project) = &self.project else { return };
21114 let (telemetry, is_via_ssh) = {
21115 let project = project.read(cx);
21116 let telemetry = project.client().telemetry().clone();
21117 let is_via_ssh = project.is_via_remote_server();
21118 (telemetry, is_via_ssh)
21119 };
21120 telemetry.log_edit_event("editor", is_via_ssh);
21121 }
21122 multi_buffer::Event::ExcerptsAdded {
21123 buffer,
21124 predecessor,
21125 excerpts,
21126 } => {
21127 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21128 let buffer_id = buffer.read(cx).remote_id();
21129 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21130 && let Some(project) = &self.project
21131 {
21132 update_uncommitted_diff_for_buffer(
21133 cx.entity(),
21134 project,
21135 [buffer.clone()],
21136 self.buffer.clone(),
21137 cx,
21138 )
21139 .detach();
21140 }
21141 self.update_lsp_data(Some(buffer_id), window, cx);
21142 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21143 self.colorize_brackets(false, cx);
21144 cx.emit(EditorEvent::ExcerptsAdded {
21145 buffer: buffer.clone(),
21146 predecessor: *predecessor,
21147 excerpts: excerpts.clone(),
21148 });
21149 }
21150 multi_buffer::Event::ExcerptsRemoved {
21151 ids,
21152 removed_buffer_ids,
21153 } => {
21154 if let Some(inlay_hints) = &mut self.inlay_hints {
21155 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21156 }
21157 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21158 for buffer_id in removed_buffer_ids {
21159 self.registered_buffers.remove(buffer_id);
21160 }
21161 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21162 cx.emit(EditorEvent::ExcerptsRemoved {
21163 ids: ids.clone(),
21164 removed_buffer_ids: removed_buffer_ids.clone(),
21165 });
21166 }
21167 multi_buffer::Event::ExcerptsEdited {
21168 excerpt_ids,
21169 buffer_ids,
21170 } => {
21171 self.display_map.update(cx, |map, cx| {
21172 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21173 });
21174 cx.emit(EditorEvent::ExcerptsEdited {
21175 ids: excerpt_ids.clone(),
21176 });
21177 }
21178 multi_buffer::Event::ExcerptsExpanded { ids } => {
21179 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21180 self.refresh_document_highlights(cx);
21181 for id in ids {
21182 self.fetched_tree_sitter_chunks.remove(id);
21183 }
21184 self.colorize_brackets(false, cx);
21185 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21186 }
21187 multi_buffer::Event::Reparsed(buffer_id) => {
21188 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21189 self.colorize_brackets(true, cx);
21190 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21191
21192 cx.emit(EditorEvent::Reparsed(*buffer_id));
21193 }
21194 multi_buffer::Event::DiffHunksToggled => {
21195 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21196 }
21197 multi_buffer::Event::LanguageChanged(buffer_id) => {
21198 self.registered_buffers.remove(&buffer_id);
21199 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21200 cx.emit(EditorEvent::Reparsed(*buffer_id));
21201 cx.notify();
21202 }
21203 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21204 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21205 multi_buffer::Event::FileHandleChanged
21206 | multi_buffer::Event::Reloaded
21207 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21208 multi_buffer::Event::DiagnosticsUpdated => {
21209 self.update_diagnostics_state(window, cx);
21210 }
21211 _ => {}
21212 };
21213 }
21214
21215 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21216 if !self.diagnostics_enabled() {
21217 return;
21218 }
21219 self.refresh_active_diagnostics(cx);
21220 self.refresh_inline_diagnostics(true, window, cx);
21221 self.scrollbar_marker_state.dirty = true;
21222 cx.notify();
21223 }
21224
21225 pub fn start_temporary_diff_override(&mut self) {
21226 self.load_diff_task.take();
21227 self.temporary_diff_override = true;
21228 }
21229
21230 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21231 self.temporary_diff_override = false;
21232 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21233 self.buffer.update(cx, |buffer, cx| {
21234 buffer.set_all_diff_hunks_collapsed(cx);
21235 });
21236
21237 if let Some(project) = self.project.clone() {
21238 self.load_diff_task = Some(
21239 update_uncommitted_diff_for_buffer(
21240 cx.entity(),
21241 &project,
21242 self.buffer.read(cx).all_buffers(),
21243 self.buffer.clone(),
21244 cx,
21245 )
21246 .shared(),
21247 );
21248 }
21249 }
21250
21251 fn on_display_map_changed(
21252 &mut self,
21253 _: Entity<DisplayMap>,
21254 _: &mut Window,
21255 cx: &mut Context<Self>,
21256 ) {
21257 cx.notify();
21258 }
21259
21260 fn fetch_applicable_language_settings(
21261 &self,
21262 cx: &App,
21263 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21264 if !self.mode.is_full() {
21265 return HashMap::default();
21266 }
21267
21268 self.buffer().read(cx).all_buffers().into_iter().fold(
21269 HashMap::default(),
21270 |mut acc, buffer| {
21271 let buffer = buffer.read(cx);
21272 let language = buffer.language().map(|language| language.name());
21273 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21274 let file = buffer.file();
21275 v.insert(language_settings(language, file, cx).into_owned());
21276 }
21277 acc
21278 },
21279 )
21280 }
21281
21282 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21283 let new_language_settings = self.fetch_applicable_language_settings(cx);
21284 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21285 if language_settings_changed {
21286 self.applicable_language_settings = new_language_settings;
21287 }
21288
21289 if self.diagnostics_enabled() {
21290 let new_severity = EditorSettings::get_global(cx)
21291 .diagnostics_max_severity
21292 .unwrap_or(DiagnosticSeverity::Hint);
21293 self.set_max_diagnostics_severity(new_severity, cx);
21294 }
21295 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21296 self.update_edit_prediction_settings(cx);
21297 self.refresh_edit_prediction(true, false, window, cx);
21298 self.refresh_inline_values(cx);
21299 self.refresh_inlay_hints(
21300 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21301 self.selections.newest_anchor().head(),
21302 &self.buffer.read(cx).snapshot(cx),
21303 cx,
21304 )),
21305 cx,
21306 );
21307
21308 let old_cursor_shape = self.cursor_shape;
21309 let old_show_breadcrumbs = self.show_breadcrumbs;
21310
21311 {
21312 let editor_settings = EditorSettings::get_global(cx);
21313 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21314 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21315 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21316 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21317 }
21318
21319 if old_cursor_shape != self.cursor_shape {
21320 cx.emit(EditorEvent::CursorShapeChanged);
21321 }
21322
21323 if old_show_breadcrumbs != self.show_breadcrumbs {
21324 cx.emit(EditorEvent::BreadcrumbsChanged);
21325 }
21326
21327 let project_settings = ProjectSettings::get_global(cx);
21328 self.buffer_serialization = self
21329 .should_serialize_buffer()
21330 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21331
21332 if self.mode.is_full() {
21333 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21334 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21335 if self.show_inline_diagnostics != show_inline_diagnostics {
21336 self.show_inline_diagnostics = show_inline_diagnostics;
21337 self.refresh_inline_diagnostics(false, window, cx);
21338 }
21339
21340 if self.git_blame_inline_enabled != inline_blame_enabled {
21341 self.toggle_git_blame_inline_internal(false, window, cx);
21342 }
21343
21344 let minimap_settings = EditorSettings::get_global(cx).minimap;
21345 if self.minimap_visibility != MinimapVisibility::Disabled {
21346 if self.minimap_visibility.settings_visibility()
21347 != minimap_settings.minimap_enabled()
21348 {
21349 self.set_minimap_visibility(
21350 MinimapVisibility::for_mode(self.mode(), cx),
21351 window,
21352 cx,
21353 );
21354 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21355 minimap_entity.update(cx, |minimap_editor, cx| {
21356 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21357 })
21358 }
21359 }
21360
21361 if language_settings_changed {
21362 self.colorize_brackets(true, cx);
21363 }
21364
21365 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21366 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21367 }) {
21368 if !inlay_splice.is_empty() {
21369 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21370 }
21371 self.refresh_colors_for_visible_range(None, window, cx);
21372 }
21373 }
21374
21375 cx.notify();
21376 }
21377
21378 pub fn set_searchable(&mut self, searchable: bool) {
21379 self.searchable = searchable;
21380 }
21381
21382 pub fn searchable(&self) -> bool {
21383 self.searchable
21384 }
21385
21386 pub fn open_excerpts_in_split(
21387 &mut self,
21388 _: &OpenExcerptsSplit,
21389 window: &mut Window,
21390 cx: &mut Context<Self>,
21391 ) {
21392 self.open_excerpts_common(None, true, window, cx)
21393 }
21394
21395 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21396 self.open_excerpts_common(None, false, window, cx)
21397 }
21398
21399 fn open_excerpts_common(
21400 &mut self,
21401 jump_data: Option<JumpData>,
21402 split: bool,
21403 window: &mut Window,
21404 cx: &mut Context<Self>,
21405 ) {
21406 let Some(workspace) = self.workspace() else {
21407 cx.propagate();
21408 return;
21409 };
21410
21411 if self.buffer.read(cx).is_singleton() {
21412 cx.propagate();
21413 return;
21414 }
21415
21416 let mut new_selections_by_buffer = HashMap::default();
21417 match &jump_data {
21418 Some(JumpData::MultiBufferPoint {
21419 excerpt_id,
21420 position,
21421 anchor,
21422 line_offset_from_top,
21423 }) => {
21424 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21425 if let Some(buffer) = multi_buffer_snapshot
21426 .buffer_id_for_excerpt(*excerpt_id)
21427 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21428 {
21429 let buffer_snapshot = buffer.read(cx).snapshot();
21430 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21431 language::ToPoint::to_point(anchor, &buffer_snapshot)
21432 } else {
21433 buffer_snapshot.clip_point(*position, Bias::Left)
21434 };
21435 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21436 new_selections_by_buffer.insert(
21437 buffer,
21438 (
21439 vec![jump_to_offset..jump_to_offset],
21440 Some(*line_offset_from_top),
21441 ),
21442 );
21443 }
21444 }
21445 Some(JumpData::MultiBufferRow {
21446 row,
21447 line_offset_from_top,
21448 }) => {
21449 let point = MultiBufferPoint::new(row.0, 0);
21450 if let Some((buffer, buffer_point, _)) =
21451 self.buffer.read(cx).point_to_buffer_point(point, cx)
21452 {
21453 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21454 new_selections_by_buffer
21455 .entry(buffer)
21456 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21457 .0
21458 .push(buffer_offset..buffer_offset)
21459 }
21460 }
21461 None => {
21462 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21463 let multi_buffer = self.buffer.read(cx);
21464 for selection in selections {
21465 for (snapshot, range, _, anchor) in multi_buffer
21466 .snapshot(cx)
21467 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21468 {
21469 if let Some(anchor) = anchor {
21470 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21471 else {
21472 continue;
21473 };
21474 let offset = text::ToOffset::to_offset(
21475 &anchor.text_anchor,
21476 &buffer_handle.read(cx).snapshot(),
21477 );
21478 let range = offset..offset;
21479 new_selections_by_buffer
21480 .entry(buffer_handle)
21481 .or_insert((Vec::new(), None))
21482 .0
21483 .push(range)
21484 } else {
21485 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21486 else {
21487 continue;
21488 };
21489 new_selections_by_buffer
21490 .entry(buffer_handle)
21491 .or_insert((Vec::new(), None))
21492 .0
21493 .push(range)
21494 }
21495 }
21496 }
21497 }
21498 }
21499
21500 new_selections_by_buffer
21501 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21502
21503 if new_selections_by_buffer.is_empty() {
21504 return;
21505 }
21506
21507 // We defer the pane interaction because we ourselves are a workspace item
21508 // and activating a new item causes the pane to call a method on us reentrantly,
21509 // which panics if we're on the stack.
21510 window.defer(cx, move |window, cx| {
21511 workspace.update(cx, |workspace, cx| {
21512 let pane = if split {
21513 workspace.adjacent_pane(window, cx)
21514 } else {
21515 workspace.active_pane().clone()
21516 };
21517
21518 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21519 let editor = buffer
21520 .read(cx)
21521 .file()
21522 .is_none()
21523 .then(|| {
21524 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21525 // so `workspace.open_project_item` will never find them, always opening a new editor.
21526 // Instead, we try to activate the existing editor in the pane first.
21527 let (editor, pane_item_index) =
21528 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21529 let editor = item.downcast::<Editor>()?;
21530 let singleton_buffer =
21531 editor.read(cx).buffer().read(cx).as_singleton()?;
21532 if singleton_buffer == buffer {
21533 Some((editor, i))
21534 } else {
21535 None
21536 }
21537 })?;
21538 pane.update(cx, |pane, cx| {
21539 pane.activate_item(pane_item_index, true, true, window, cx)
21540 });
21541 Some(editor)
21542 })
21543 .flatten()
21544 .unwrap_or_else(|| {
21545 workspace.open_project_item::<Self>(
21546 pane.clone(),
21547 buffer,
21548 true,
21549 true,
21550 window,
21551 cx,
21552 )
21553 });
21554
21555 editor.update(cx, |editor, cx| {
21556 let autoscroll = match scroll_offset {
21557 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21558 None => Autoscroll::newest(),
21559 };
21560 let nav_history = editor.nav_history.take();
21561 editor.change_selections(
21562 SelectionEffects::scroll(autoscroll),
21563 window,
21564 cx,
21565 |s| {
21566 s.select_ranges(ranges);
21567 },
21568 );
21569 editor.nav_history = nav_history;
21570 });
21571 }
21572 })
21573 });
21574 }
21575
21576 // For now, don't allow opening excerpts in buffers that aren't backed by
21577 // regular project files.
21578 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21579 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21580 }
21581
21582 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21583 let snapshot = self.buffer.read(cx).read(cx);
21584 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21585 Some(
21586 ranges
21587 .iter()
21588 .map(move |range| {
21589 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21590 })
21591 .collect(),
21592 )
21593 }
21594
21595 fn selection_replacement_ranges(
21596 &self,
21597 range: Range<OffsetUtf16>,
21598 cx: &mut App,
21599 ) -> Vec<Range<OffsetUtf16>> {
21600 let selections = self
21601 .selections
21602 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21603 let newest_selection = selections
21604 .iter()
21605 .max_by_key(|selection| selection.id)
21606 .unwrap();
21607 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21608 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21609 let snapshot = self.buffer.read(cx).read(cx);
21610 selections
21611 .into_iter()
21612 .map(|mut selection| {
21613 selection.start.0 =
21614 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21615 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21616 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21617 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21618 })
21619 .collect()
21620 }
21621
21622 fn report_editor_event(
21623 &self,
21624 reported_event: ReportEditorEvent,
21625 file_extension: Option<String>,
21626 cx: &App,
21627 ) {
21628 if cfg!(any(test, feature = "test-support")) {
21629 return;
21630 }
21631
21632 let Some(project) = &self.project else { return };
21633
21634 // If None, we are in a file without an extension
21635 let file = self
21636 .buffer
21637 .read(cx)
21638 .as_singleton()
21639 .and_then(|b| b.read(cx).file());
21640 let file_extension = file_extension.or(file
21641 .as_ref()
21642 .and_then(|file| Path::new(file.file_name(cx)).extension())
21643 .and_then(|e| e.to_str())
21644 .map(|a| a.to_string()));
21645
21646 let vim_mode = vim_flavor(cx).is_some();
21647
21648 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21649 let copilot_enabled = edit_predictions_provider
21650 == language::language_settings::EditPredictionProvider::Copilot;
21651 let copilot_enabled_for_language = self
21652 .buffer
21653 .read(cx)
21654 .language_settings(cx)
21655 .show_edit_predictions;
21656
21657 let project = project.read(cx);
21658 let event_type = reported_event.event_type();
21659
21660 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21661 telemetry::event!(
21662 event_type,
21663 type = if auto_saved {"autosave"} else {"manual"},
21664 file_extension,
21665 vim_mode,
21666 copilot_enabled,
21667 copilot_enabled_for_language,
21668 edit_predictions_provider,
21669 is_via_ssh = project.is_via_remote_server(),
21670 );
21671 } else {
21672 telemetry::event!(
21673 event_type,
21674 file_extension,
21675 vim_mode,
21676 copilot_enabled,
21677 copilot_enabled_for_language,
21678 edit_predictions_provider,
21679 is_via_ssh = project.is_via_remote_server(),
21680 );
21681 };
21682 }
21683
21684 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21685 /// with each line being an array of {text, highlight} objects.
21686 fn copy_highlight_json(
21687 &mut self,
21688 _: &CopyHighlightJson,
21689 window: &mut Window,
21690 cx: &mut Context<Self>,
21691 ) {
21692 #[derive(Serialize)]
21693 struct Chunk<'a> {
21694 text: String,
21695 highlight: Option<&'a str>,
21696 }
21697
21698 let snapshot = self.buffer.read(cx).snapshot(cx);
21699 let range = self
21700 .selected_text_range(false, window, cx)
21701 .and_then(|selection| {
21702 if selection.range.is_empty() {
21703 None
21704 } else {
21705 Some(
21706 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21707 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21708 )
21709 }
21710 })
21711 .unwrap_or_else(|| 0..snapshot.len());
21712
21713 let chunks = snapshot.chunks(range, true);
21714 let mut lines = Vec::new();
21715 let mut line: VecDeque<Chunk> = VecDeque::new();
21716
21717 let Some(style) = self.style.as_ref() else {
21718 return;
21719 };
21720
21721 for chunk in chunks {
21722 let highlight = chunk
21723 .syntax_highlight_id
21724 .and_then(|id| id.name(&style.syntax));
21725 let mut chunk_lines = chunk.text.split('\n').peekable();
21726 while let Some(text) = chunk_lines.next() {
21727 let mut merged_with_last_token = false;
21728 if let Some(last_token) = line.back_mut()
21729 && last_token.highlight == highlight
21730 {
21731 last_token.text.push_str(text);
21732 merged_with_last_token = true;
21733 }
21734
21735 if !merged_with_last_token {
21736 line.push_back(Chunk {
21737 text: text.into(),
21738 highlight,
21739 });
21740 }
21741
21742 if chunk_lines.peek().is_some() {
21743 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21744 line.pop_front();
21745 }
21746 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21747 line.pop_back();
21748 }
21749
21750 lines.push(mem::take(&mut line));
21751 }
21752 }
21753 }
21754
21755 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21756 return;
21757 };
21758 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21759 }
21760
21761 pub fn open_context_menu(
21762 &mut self,
21763 _: &OpenContextMenu,
21764 window: &mut Window,
21765 cx: &mut Context<Self>,
21766 ) {
21767 self.request_autoscroll(Autoscroll::newest(), cx);
21768 let position = self
21769 .selections
21770 .newest_display(&self.display_snapshot(cx))
21771 .start;
21772 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21773 }
21774
21775 pub fn replay_insert_event(
21776 &mut self,
21777 text: &str,
21778 relative_utf16_range: Option<Range<isize>>,
21779 window: &mut Window,
21780 cx: &mut Context<Self>,
21781 ) {
21782 if !self.input_enabled {
21783 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21784 return;
21785 }
21786 if let Some(relative_utf16_range) = relative_utf16_range {
21787 let selections = self
21788 .selections
21789 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21790 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21791 let new_ranges = selections.into_iter().map(|range| {
21792 let start = OffsetUtf16(
21793 range
21794 .head()
21795 .0
21796 .saturating_add_signed(relative_utf16_range.start),
21797 );
21798 let end = OffsetUtf16(
21799 range
21800 .head()
21801 .0
21802 .saturating_add_signed(relative_utf16_range.end),
21803 );
21804 start..end
21805 });
21806 s.select_ranges(new_ranges);
21807 });
21808 }
21809
21810 self.handle_input(text, window, cx);
21811 }
21812
21813 pub fn is_focused(&self, window: &Window) -> bool {
21814 self.focus_handle.is_focused(window)
21815 }
21816
21817 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21818 cx.emit(EditorEvent::Focused);
21819
21820 if let Some(descendant) = self
21821 .last_focused_descendant
21822 .take()
21823 .and_then(|descendant| descendant.upgrade())
21824 {
21825 window.focus(&descendant);
21826 } else {
21827 if let Some(blame) = self.blame.as_ref() {
21828 blame.update(cx, GitBlame::focus)
21829 }
21830
21831 self.blink_manager.update(cx, BlinkManager::enable);
21832 self.show_cursor_names(window, cx);
21833 self.buffer.update(cx, |buffer, cx| {
21834 buffer.finalize_last_transaction(cx);
21835 if self.leader_id.is_none() {
21836 buffer.set_active_selections(
21837 &self.selections.disjoint_anchors_arc(),
21838 self.selections.line_mode(),
21839 self.cursor_shape,
21840 cx,
21841 );
21842 }
21843 });
21844 }
21845 }
21846
21847 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21848 cx.emit(EditorEvent::FocusedIn)
21849 }
21850
21851 fn handle_focus_out(
21852 &mut self,
21853 event: FocusOutEvent,
21854 _window: &mut Window,
21855 cx: &mut Context<Self>,
21856 ) {
21857 if event.blurred != self.focus_handle {
21858 self.last_focused_descendant = Some(event.blurred);
21859 }
21860 self.selection_drag_state = SelectionDragState::None;
21861 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21862 }
21863
21864 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21865 self.blink_manager.update(cx, BlinkManager::disable);
21866 self.buffer
21867 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21868
21869 if let Some(blame) = self.blame.as_ref() {
21870 blame.update(cx, GitBlame::blur)
21871 }
21872 if !self.hover_state.focused(window, cx) {
21873 hide_hover(self, cx);
21874 }
21875 if !self
21876 .context_menu
21877 .borrow()
21878 .as_ref()
21879 .is_some_and(|context_menu| context_menu.focused(window, cx))
21880 {
21881 self.hide_context_menu(window, cx);
21882 }
21883 self.take_active_edit_prediction(cx);
21884 cx.emit(EditorEvent::Blurred);
21885 cx.notify();
21886 }
21887
21888 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21889 let mut pending: String = window
21890 .pending_input_keystrokes()
21891 .into_iter()
21892 .flatten()
21893 .filter_map(|keystroke| {
21894 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21895 keystroke.key_char.clone()
21896 } else {
21897 None
21898 }
21899 })
21900 .collect();
21901
21902 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21903 pending = "".to_string();
21904 }
21905
21906 let existing_pending = self
21907 .text_highlights::<PendingInput>(cx)
21908 .map(|(_, ranges)| ranges.to_vec());
21909 if existing_pending.is_none() && pending.is_empty() {
21910 return;
21911 }
21912 let transaction =
21913 self.transact(window, cx, |this, window, cx| {
21914 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21915 let edits = selections
21916 .iter()
21917 .map(|selection| (selection.end..selection.end, pending.clone()));
21918 this.edit(edits, cx);
21919 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21920 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21921 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21922 }));
21923 });
21924 if let Some(existing_ranges) = existing_pending {
21925 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21926 this.edit(edits, cx);
21927 }
21928 });
21929
21930 let snapshot = self.snapshot(window, cx);
21931 let ranges = self
21932 .selections
21933 .all::<usize>(&snapshot.display_snapshot)
21934 .into_iter()
21935 .map(|selection| {
21936 snapshot.buffer_snapshot().anchor_after(selection.end)
21937 ..snapshot
21938 .buffer_snapshot()
21939 .anchor_before(selection.end + pending.len())
21940 })
21941 .collect();
21942
21943 if pending.is_empty() {
21944 self.clear_highlights::<PendingInput>(cx);
21945 } else {
21946 self.highlight_text::<PendingInput>(
21947 ranges,
21948 HighlightStyle {
21949 underline: Some(UnderlineStyle {
21950 thickness: px(1.),
21951 color: None,
21952 wavy: false,
21953 }),
21954 ..Default::default()
21955 },
21956 cx,
21957 );
21958 }
21959
21960 self.ime_transaction = self.ime_transaction.or(transaction);
21961 if let Some(transaction) = self.ime_transaction {
21962 self.buffer.update(cx, |buffer, cx| {
21963 buffer.group_until_transaction(transaction, cx);
21964 });
21965 }
21966
21967 if self.text_highlights::<PendingInput>(cx).is_none() {
21968 self.ime_transaction.take();
21969 }
21970 }
21971
21972 pub fn register_action_renderer(
21973 &mut self,
21974 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21975 ) -> Subscription {
21976 let id = self.next_editor_action_id.post_inc();
21977 self.editor_actions
21978 .borrow_mut()
21979 .insert(id, Box::new(listener));
21980
21981 let editor_actions = self.editor_actions.clone();
21982 Subscription::new(move || {
21983 editor_actions.borrow_mut().remove(&id);
21984 })
21985 }
21986
21987 pub fn register_action<A: Action>(
21988 &mut self,
21989 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21990 ) -> Subscription {
21991 let id = self.next_editor_action_id.post_inc();
21992 let listener = Arc::new(listener);
21993 self.editor_actions.borrow_mut().insert(
21994 id,
21995 Box::new(move |_, window, _| {
21996 let listener = listener.clone();
21997 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21998 let action = action.downcast_ref().unwrap();
21999 if phase == DispatchPhase::Bubble {
22000 listener(action, window, cx)
22001 }
22002 })
22003 }),
22004 );
22005
22006 let editor_actions = self.editor_actions.clone();
22007 Subscription::new(move || {
22008 editor_actions.borrow_mut().remove(&id);
22009 })
22010 }
22011
22012 pub fn file_header_size(&self) -> u32 {
22013 FILE_HEADER_HEIGHT
22014 }
22015
22016 pub fn restore(
22017 &mut self,
22018 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22019 window: &mut Window,
22020 cx: &mut Context<Self>,
22021 ) {
22022 let workspace = self.workspace();
22023 let project = self.project();
22024 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22025 let mut tasks = Vec::new();
22026 for (buffer_id, changes) in revert_changes {
22027 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22028 buffer.update(cx, |buffer, cx| {
22029 buffer.edit(
22030 changes
22031 .into_iter()
22032 .map(|(range, text)| (range, text.to_string())),
22033 None,
22034 cx,
22035 );
22036 });
22037
22038 if let Some(project) =
22039 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22040 {
22041 project.update(cx, |project, cx| {
22042 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22043 })
22044 }
22045 }
22046 }
22047 tasks
22048 });
22049 cx.spawn_in(window, async move |_, cx| {
22050 for (buffer, task) in save_tasks {
22051 let result = task.await;
22052 if result.is_err() {
22053 let Some(path) = buffer
22054 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22055 .ok()
22056 else {
22057 continue;
22058 };
22059 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22060 let Some(task) = cx
22061 .update_window_entity(workspace, |workspace, window, cx| {
22062 workspace
22063 .open_path_preview(path, None, false, false, false, window, cx)
22064 })
22065 .ok()
22066 else {
22067 continue;
22068 };
22069 task.await.log_err();
22070 }
22071 }
22072 }
22073 })
22074 .detach();
22075 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22076 selections.refresh()
22077 });
22078 }
22079
22080 pub fn to_pixel_point(
22081 &self,
22082 source: multi_buffer::Anchor,
22083 editor_snapshot: &EditorSnapshot,
22084 window: &mut Window,
22085 ) -> Option<gpui::Point<Pixels>> {
22086 let source_point = source.to_display_point(editor_snapshot);
22087 self.display_to_pixel_point(source_point, editor_snapshot, window)
22088 }
22089
22090 pub fn display_to_pixel_point(
22091 &self,
22092 source: DisplayPoint,
22093 editor_snapshot: &EditorSnapshot,
22094 window: &mut Window,
22095 ) -> Option<gpui::Point<Pixels>> {
22096 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22097 let text_layout_details = self.text_layout_details(window);
22098 let scroll_top = text_layout_details
22099 .scroll_anchor
22100 .scroll_position(editor_snapshot)
22101 .y;
22102
22103 if source.row().as_f64() < scroll_top.floor() {
22104 return None;
22105 }
22106 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22107 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22108 Some(gpui::Point::new(source_x, source_y))
22109 }
22110
22111 pub fn has_visible_completions_menu(&self) -> bool {
22112 !self.edit_prediction_preview_is_active()
22113 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22114 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22115 })
22116 }
22117
22118 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22119 if self.mode.is_minimap() {
22120 return;
22121 }
22122 self.addons
22123 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22124 }
22125
22126 pub fn unregister_addon<T: Addon>(&mut self) {
22127 self.addons.remove(&std::any::TypeId::of::<T>());
22128 }
22129
22130 pub fn addon<T: Addon>(&self) -> Option<&T> {
22131 let type_id = std::any::TypeId::of::<T>();
22132 self.addons
22133 .get(&type_id)
22134 .and_then(|item| item.to_any().downcast_ref::<T>())
22135 }
22136
22137 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22138 let type_id = std::any::TypeId::of::<T>();
22139 self.addons
22140 .get_mut(&type_id)
22141 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22142 }
22143
22144 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22145 let text_layout_details = self.text_layout_details(window);
22146 let style = &text_layout_details.editor_style;
22147 let font_id = window.text_system().resolve_font(&style.text.font());
22148 let font_size = style.text.font_size.to_pixels(window.rem_size());
22149 let line_height = style.text.line_height_in_pixels(window.rem_size());
22150 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22151 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22152
22153 CharacterDimensions {
22154 em_width,
22155 em_advance,
22156 line_height,
22157 }
22158 }
22159
22160 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22161 self.load_diff_task.clone()
22162 }
22163
22164 fn read_metadata_from_db(
22165 &mut self,
22166 item_id: u64,
22167 workspace_id: WorkspaceId,
22168 window: &mut Window,
22169 cx: &mut Context<Editor>,
22170 ) {
22171 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22172 && !self.mode.is_minimap()
22173 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22174 {
22175 let buffer_snapshot = OnceCell::new();
22176
22177 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22178 && !folds.is_empty()
22179 {
22180 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22181 self.fold_ranges(
22182 folds
22183 .into_iter()
22184 .map(|(start, end)| {
22185 snapshot.clip_offset(start, Bias::Left)
22186 ..snapshot.clip_offset(end, Bias::Right)
22187 })
22188 .collect(),
22189 false,
22190 window,
22191 cx,
22192 );
22193 }
22194
22195 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22196 && !selections.is_empty()
22197 {
22198 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22199 // skip adding the initial selection to selection history
22200 self.selection_history.mode = SelectionHistoryMode::Skipping;
22201 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22202 s.select_ranges(selections.into_iter().map(|(start, end)| {
22203 snapshot.clip_offset(start, Bias::Left)
22204 ..snapshot.clip_offset(end, Bias::Right)
22205 }));
22206 });
22207 self.selection_history.mode = SelectionHistoryMode::Normal;
22208 };
22209 }
22210
22211 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22212 }
22213
22214 fn update_lsp_data(
22215 &mut self,
22216 for_buffer: Option<BufferId>,
22217 window: &mut Window,
22218 cx: &mut Context<'_, Self>,
22219 ) {
22220 self.pull_diagnostics(for_buffer, window, cx);
22221 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22222 }
22223
22224 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22225 if self.ignore_lsp_data() {
22226 return;
22227 }
22228 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22229 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22230 }
22231 }
22232
22233 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22234 if self.ignore_lsp_data() {
22235 return;
22236 }
22237
22238 if !self.registered_buffers.contains_key(&buffer_id)
22239 && let Some(project) = self.project.as_ref()
22240 {
22241 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22242 project.update(cx, |project, cx| {
22243 self.registered_buffers.insert(
22244 buffer_id,
22245 project.register_buffer_with_language_servers(&buffer, cx),
22246 );
22247 });
22248 } else {
22249 self.registered_buffers.remove(&buffer_id);
22250 }
22251 }
22252 }
22253
22254 fn ignore_lsp_data(&self) -> bool {
22255 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22256 // skip any LSP updates for it.
22257 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22258 }
22259}
22260
22261fn edit_for_markdown_paste<'a>(
22262 buffer: &MultiBufferSnapshot,
22263 range: Range<usize>,
22264 to_insert: &'a str,
22265 url: Option<url::Url>,
22266) -> (Range<usize>, Cow<'a, str>) {
22267 if url.is_none() {
22268 return (range, Cow::Borrowed(to_insert));
22269 };
22270
22271 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22272
22273 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22274 Cow::Borrowed(to_insert)
22275 } else {
22276 Cow::Owned(format!("[{old_text}]({to_insert})"))
22277 };
22278 (range, new_text)
22279}
22280
22281#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22282pub enum VimFlavor {
22283 Vim,
22284 Helix,
22285}
22286
22287pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22288 if vim_mode_setting::HelixModeSetting::try_get(cx)
22289 .map(|helix_mode| helix_mode.0)
22290 .unwrap_or(false)
22291 {
22292 Some(VimFlavor::Helix)
22293 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22294 .map(|vim_mode| vim_mode.0)
22295 .unwrap_or(false)
22296 {
22297 Some(VimFlavor::Vim)
22298 } else {
22299 None // neither vim nor helix mode
22300 }
22301}
22302
22303fn process_completion_for_edit(
22304 completion: &Completion,
22305 intent: CompletionIntent,
22306 buffer: &Entity<Buffer>,
22307 cursor_position: &text::Anchor,
22308 cx: &mut Context<Editor>,
22309) -> CompletionEdit {
22310 let buffer = buffer.read(cx);
22311 let buffer_snapshot = buffer.snapshot();
22312 let (snippet, new_text) = if completion.is_snippet() {
22313 let mut snippet_source = completion.new_text.clone();
22314 // Workaround for typescript language server issues so that methods don't expand within
22315 // strings and functions with type expressions. The previous point is used because the query
22316 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22317 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22318 let previous_point = if previous_point.column > 0 {
22319 cursor_position.to_previous_offset(&buffer_snapshot)
22320 } else {
22321 cursor_position.to_offset(&buffer_snapshot)
22322 };
22323 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22324 && scope.prefers_label_for_snippet_in_completion()
22325 && let Some(label) = completion.label()
22326 && matches!(
22327 completion.kind(),
22328 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22329 )
22330 {
22331 snippet_source = label;
22332 }
22333 match Snippet::parse(&snippet_source).log_err() {
22334 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22335 None => (None, completion.new_text.clone()),
22336 }
22337 } else {
22338 (None, completion.new_text.clone())
22339 };
22340
22341 let mut range_to_replace = {
22342 let replace_range = &completion.replace_range;
22343 if let CompletionSource::Lsp {
22344 insert_range: Some(insert_range),
22345 ..
22346 } = &completion.source
22347 {
22348 debug_assert_eq!(
22349 insert_range.start, replace_range.start,
22350 "insert_range and replace_range should start at the same position"
22351 );
22352 debug_assert!(
22353 insert_range
22354 .start
22355 .cmp(cursor_position, &buffer_snapshot)
22356 .is_le(),
22357 "insert_range should start before or at cursor position"
22358 );
22359 debug_assert!(
22360 replace_range
22361 .start
22362 .cmp(cursor_position, &buffer_snapshot)
22363 .is_le(),
22364 "replace_range should start before or at cursor position"
22365 );
22366
22367 let should_replace = match intent {
22368 CompletionIntent::CompleteWithInsert => false,
22369 CompletionIntent::CompleteWithReplace => true,
22370 CompletionIntent::Complete | CompletionIntent::Compose => {
22371 let insert_mode =
22372 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22373 .completions
22374 .lsp_insert_mode;
22375 match insert_mode {
22376 LspInsertMode::Insert => false,
22377 LspInsertMode::Replace => true,
22378 LspInsertMode::ReplaceSubsequence => {
22379 let mut text_to_replace = buffer.chars_for_range(
22380 buffer.anchor_before(replace_range.start)
22381 ..buffer.anchor_after(replace_range.end),
22382 );
22383 let mut current_needle = text_to_replace.next();
22384 for haystack_ch in completion.label.text.chars() {
22385 if let Some(needle_ch) = current_needle
22386 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22387 {
22388 current_needle = text_to_replace.next();
22389 }
22390 }
22391 current_needle.is_none()
22392 }
22393 LspInsertMode::ReplaceSuffix => {
22394 if replace_range
22395 .end
22396 .cmp(cursor_position, &buffer_snapshot)
22397 .is_gt()
22398 {
22399 let range_after_cursor = *cursor_position..replace_range.end;
22400 let text_after_cursor = buffer
22401 .text_for_range(
22402 buffer.anchor_before(range_after_cursor.start)
22403 ..buffer.anchor_after(range_after_cursor.end),
22404 )
22405 .collect::<String>()
22406 .to_ascii_lowercase();
22407 completion
22408 .label
22409 .text
22410 .to_ascii_lowercase()
22411 .ends_with(&text_after_cursor)
22412 } else {
22413 true
22414 }
22415 }
22416 }
22417 }
22418 };
22419
22420 if should_replace {
22421 replace_range.clone()
22422 } else {
22423 insert_range.clone()
22424 }
22425 } else {
22426 replace_range.clone()
22427 }
22428 };
22429
22430 if range_to_replace
22431 .end
22432 .cmp(cursor_position, &buffer_snapshot)
22433 .is_lt()
22434 {
22435 range_to_replace.end = *cursor_position;
22436 }
22437
22438 CompletionEdit {
22439 new_text,
22440 replace_range: range_to_replace.to_offset(buffer),
22441 snippet,
22442 }
22443}
22444
22445struct CompletionEdit {
22446 new_text: String,
22447 replace_range: Range<usize>,
22448 snippet: Option<Snippet>,
22449}
22450
22451fn insert_extra_newline_brackets(
22452 buffer: &MultiBufferSnapshot,
22453 range: Range<usize>,
22454 language: &language::LanguageScope,
22455) -> bool {
22456 let leading_whitespace_len = buffer
22457 .reversed_chars_at(range.start)
22458 .take_while(|c| c.is_whitespace() && *c != '\n')
22459 .map(|c| c.len_utf8())
22460 .sum::<usize>();
22461 let trailing_whitespace_len = buffer
22462 .chars_at(range.end)
22463 .take_while(|c| c.is_whitespace() && *c != '\n')
22464 .map(|c| c.len_utf8())
22465 .sum::<usize>();
22466 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22467
22468 language.brackets().any(|(pair, enabled)| {
22469 let pair_start = pair.start.trim_end();
22470 let pair_end = pair.end.trim_start();
22471
22472 enabled
22473 && pair.newline
22474 && buffer.contains_str_at(range.end, pair_end)
22475 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22476 })
22477}
22478
22479fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22480 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22481 [(buffer, range, _)] => (*buffer, range.clone()),
22482 _ => return false,
22483 };
22484 let pair = {
22485 let mut result: Option<BracketMatch> = None;
22486
22487 for pair in buffer
22488 .all_bracket_ranges(range.clone())
22489 .filter(move |pair| {
22490 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22491 })
22492 {
22493 let len = pair.close_range.end - pair.open_range.start;
22494
22495 if let Some(existing) = &result {
22496 let existing_len = existing.close_range.end - existing.open_range.start;
22497 if len > existing_len {
22498 continue;
22499 }
22500 }
22501
22502 result = Some(pair);
22503 }
22504
22505 result
22506 };
22507 let Some(pair) = pair else {
22508 return false;
22509 };
22510 pair.newline_only
22511 && buffer
22512 .chars_for_range(pair.open_range.end..range.start)
22513 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22514 .all(|c| c.is_whitespace() && c != '\n')
22515}
22516
22517fn update_uncommitted_diff_for_buffer(
22518 editor: Entity<Editor>,
22519 project: &Entity<Project>,
22520 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22521 buffer: Entity<MultiBuffer>,
22522 cx: &mut App,
22523) -> Task<()> {
22524 let mut tasks = Vec::new();
22525 project.update(cx, |project, cx| {
22526 for buffer in buffers {
22527 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22528 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22529 }
22530 }
22531 });
22532 cx.spawn(async move |cx| {
22533 let diffs = future::join_all(tasks).await;
22534 if editor
22535 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22536 .unwrap_or(false)
22537 {
22538 return;
22539 }
22540
22541 buffer
22542 .update(cx, |buffer, cx| {
22543 for diff in diffs.into_iter().flatten() {
22544 buffer.add_diff(diff, cx);
22545 }
22546 })
22547 .ok();
22548 })
22549}
22550
22551fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22552 let tab_size = tab_size.get() as usize;
22553 let mut width = offset;
22554
22555 for ch in text.chars() {
22556 width += if ch == '\t' {
22557 tab_size - (width % tab_size)
22558 } else {
22559 1
22560 };
22561 }
22562
22563 width - offset
22564}
22565
22566#[cfg(test)]
22567mod tests {
22568 use super::*;
22569
22570 #[test]
22571 fn test_string_size_with_expanded_tabs() {
22572 let nz = |val| NonZeroU32::new(val).unwrap();
22573 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22574 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22575 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22576 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22577 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22578 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22579 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22580 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22581 }
22582}
22583
22584/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22585struct WordBreakingTokenizer<'a> {
22586 input: &'a str,
22587}
22588
22589impl<'a> WordBreakingTokenizer<'a> {
22590 fn new(input: &'a str) -> Self {
22591 Self { input }
22592 }
22593}
22594
22595fn is_char_ideographic(ch: char) -> bool {
22596 use unicode_script::Script::*;
22597 use unicode_script::UnicodeScript;
22598 matches!(ch.script(), Han | Tangut | Yi)
22599}
22600
22601fn is_grapheme_ideographic(text: &str) -> bool {
22602 text.chars().any(is_char_ideographic)
22603}
22604
22605fn is_grapheme_whitespace(text: &str) -> bool {
22606 text.chars().any(|x| x.is_whitespace())
22607}
22608
22609fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22610 text.chars()
22611 .next()
22612 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22613}
22614
22615#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22616enum WordBreakToken<'a> {
22617 Word { token: &'a str, grapheme_len: usize },
22618 InlineWhitespace { token: &'a str, grapheme_len: usize },
22619 Newline,
22620}
22621
22622impl<'a> Iterator for WordBreakingTokenizer<'a> {
22623 /// Yields a span, the count of graphemes in the token, and whether it was
22624 /// whitespace. Note that it also breaks at word boundaries.
22625 type Item = WordBreakToken<'a>;
22626
22627 fn next(&mut self) -> Option<Self::Item> {
22628 use unicode_segmentation::UnicodeSegmentation;
22629 if self.input.is_empty() {
22630 return None;
22631 }
22632
22633 let mut iter = self.input.graphemes(true).peekable();
22634 let mut offset = 0;
22635 let mut grapheme_len = 0;
22636 if let Some(first_grapheme) = iter.next() {
22637 let is_newline = first_grapheme == "\n";
22638 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22639 offset += first_grapheme.len();
22640 grapheme_len += 1;
22641 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22642 if let Some(grapheme) = iter.peek().copied()
22643 && should_stay_with_preceding_ideograph(grapheme)
22644 {
22645 offset += grapheme.len();
22646 grapheme_len += 1;
22647 }
22648 } else {
22649 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22650 let mut next_word_bound = words.peek().copied();
22651 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22652 next_word_bound = words.next();
22653 }
22654 while let Some(grapheme) = iter.peek().copied() {
22655 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22656 break;
22657 };
22658 if is_grapheme_whitespace(grapheme) != is_whitespace
22659 || (grapheme == "\n") != is_newline
22660 {
22661 break;
22662 };
22663 offset += grapheme.len();
22664 grapheme_len += 1;
22665 iter.next();
22666 }
22667 }
22668 let token = &self.input[..offset];
22669 self.input = &self.input[offset..];
22670 if token == "\n" {
22671 Some(WordBreakToken::Newline)
22672 } else if is_whitespace {
22673 Some(WordBreakToken::InlineWhitespace {
22674 token,
22675 grapheme_len,
22676 })
22677 } else {
22678 Some(WordBreakToken::Word {
22679 token,
22680 grapheme_len,
22681 })
22682 }
22683 } else {
22684 None
22685 }
22686 }
22687}
22688
22689#[test]
22690fn test_word_breaking_tokenizer() {
22691 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22692 ("", &[]),
22693 (" ", &[whitespace(" ", 2)]),
22694 ("Ʒ", &[word("Ʒ", 1)]),
22695 ("Ǽ", &[word("Ǽ", 1)]),
22696 ("⋑", &[word("⋑", 1)]),
22697 ("⋑⋑", &[word("⋑⋑", 2)]),
22698 (
22699 "原理,进而",
22700 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22701 ),
22702 (
22703 "hello world",
22704 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22705 ),
22706 (
22707 "hello, world",
22708 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22709 ),
22710 (
22711 " hello world",
22712 &[
22713 whitespace(" ", 2),
22714 word("hello", 5),
22715 whitespace(" ", 1),
22716 word("world", 5),
22717 ],
22718 ),
22719 (
22720 "这是什么 \n 钢笔",
22721 &[
22722 word("这", 1),
22723 word("是", 1),
22724 word("什", 1),
22725 word("么", 1),
22726 whitespace(" ", 1),
22727 newline(),
22728 whitespace(" ", 1),
22729 word("钢", 1),
22730 word("笔", 1),
22731 ],
22732 ),
22733 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22734 ];
22735
22736 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22737 WordBreakToken::Word {
22738 token,
22739 grapheme_len,
22740 }
22741 }
22742
22743 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22744 WordBreakToken::InlineWhitespace {
22745 token,
22746 grapheme_len,
22747 }
22748 }
22749
22750 fn newline() -> WordBreakToken<'static> {
22751 WordBreakToken::Newline
22752 }
22753
22754 for (input, result) in tests {
22755 assert_eq!(
22756 WordBreakingTokenizer::new(input)
22757 .collect::<Vec<_>>()
22758 .as_slice(),
22759 *result,
22760 );
22761 }
22762}
22763
22764fn wrap_with_prefix(
22765 first_line_prefix: String,
22766 subsequent_lines_prefix: String,
22767 unwrapped_text: String,
22768 wrap_column: usize,
22769 tab_size: NonZeroU32,
22770 preserve_existing_whitespace: bool,
22771) -> String {
22772 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22773 let subsequent_lines_prefix_len =
22774 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22775 let mut wrapped_text = String::new();
22776 let mut current_line = first_line_prefix;
22777 let mut is_first_line = true;
22778
22779 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22780 let mut current_line_len = first_line_prefix_len;
22781 let mut in_whitespace = false;
22782 for token in tokenizer {
22783 let have_preceding_whitespace = in_whitespace;
22784 match token {
22785 WordBreakToken::Word {
22786 token,
22787 grapheme_len,
22788 } => {
22789 in_whitespace = false;
22790 let current_prefix_len = if is_first_line {
22791 first_line_prefix_len
22792 } else {
22793 subsequent_lines_prefix_len
22794 };
22795 if current_line_len + grapheme_len > wrap_column
22796 && current_line_len != current_prefix_len
22797 {
22798 wrapped_text.push_str(current_line.trim_end());
22799 wrapped_text.push('\n');
22800 is_first_line = false;
22801 current_line = subsequent_lines_prefix.clone();
22802 current_line_len = subsequent_lines_prefix_len;
22803 }
22804 current_line.push_str(token);
22805 current_line_len += grapheme_len;
22806 }
22807 WordBreakToken::InlineWhitespace {
22808 mut token,
22809 mut grapheme_len,
22810 } => {
22811 in_whitespace = true;
22812 if have_preceding_whitespace && !preserve_existing_whitespace {
22813 continue;
22814 }
22815 if !preserve_existing_whitespace {
22816 // Keep a single whitespace grapheme as-is
22817 if let Some(first) =
22818 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22819 {
22820 token = first;
22821 } else {
22822 token = " ";
22823 }
22824 grapheme_len = 1;
22825 }
22826 let current_prefix_len = if is_first_line {
22827 first_line_prefix_len
22828 } else {
22829 subsequent_lines_prefix_len
22830 };
22831 if current_line_len + grapheme_len > wrap_column {
22832 wrapped_text.push_str(current_line.trim_end());
22833 wrapped_text.push('\n');
22834 is_first_line = false;
22835 current_line = subsequent_lines_prefix.clone();
22836 current_line_len = subsequent_lines_prefix_len;
22837 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22838 current_line.push_str(token);
22839 current_line_len += grapheme_len;
22840 }
22841 }
22842 WordBreakToken::Newline => {
22843 in_whitespace = true;
22844 let current_prefix_len = if is_first_line {
22845 first_line_prefix_len
22846 } else {
22847 subsequent_lines_prefix_len
22848 };
22849 if preserve_existing_whitespace {
22850 wrapped_text.push_str(current_line.trim_end());
22851 wrapped_text.push('\n');
22852 is_first_line = false;
22853 current_line = subsequent_lines_prefix.clone();
22854 current_line_len = subsequent_lines_prefix_len;
22855 } else if have_preceding_whitespace {
22856 continue;
22857 } else if current_line_len + 1 > wrap_column
22858 && current_line_len != current_prefix_len
22859 {
22860 wrapped_text.push_str(current_line.trim_end());
22861 wrapped_text.push('\n');
22862 is_first_line = false;
22863 current_line = subsequent_lines_prefix.clone();
22864 current_line_len = subsequent_lines_prefix_len;
22865 } else if current_line_len != current_prefix_len {
22866 current_line.push(' ');
22867 current_line_len += 1;
22868 }
22869 }
22870 }
22871 }
22872
22873 if !current_line.is_empty() {
22874 wrapped_text.push_str(¤t_line);
22875 }
22876 wrapped_text
22877}
22878
22879#[test]
22880fn test_wrap_with_prefix() {
22881 assert_eq!(
22882 wrap_with_prefix(
22883 "# ".to_string(),
22884 "# ".to_string(),
22885 "abcdefg".to_string(),
22886 4,
22887 NonZeroU32::new(4).unwrap(),
22888 false,
22889 ),
22890 "# abcdefg"
22891 );
22892 assert_eq!(
22893 wrap_with_prefix(
22894 "".to_string(),
22895 "".to_string(),
22896 "\thello world".to_string(),
22897 8,
22898 NonZeroU32::new(4).unwrap(),
22899 false,
22900 ),
22901 "hello\nworld"
22902 );
22903 assert_eq!(
22904 wrap_with_prefix(
22905 "// ".to_string(),
22906 "// ".to_string(),
22907 "xx \nyy zz aa bb cc".to_string(),
22908 12,
22909 NonZeroU32::new(4).unwrap(),
22910 false,
22911 ),
22912 "// xx yy zz\n// aa bb cc"
22913 );
22914 assert_eq!(
22915 wrap_with_prefix(
22916 String::new(),
22917 String::new(),
22918 "这是什么 \n 钢笔".to_string(),
22919 3,
22920 NonZeroU32::new(4).unwrap(),
22921 false,
22922 ),
22923 "这是什\n么 钢\n笔"
22924 );
22925 assert_eq!(
22926 wrap_with_prefix(
22927 String::new(),
22928 String::new(),
22929 format!("foo{}bar", '\u{2009}'), // thin space
22930 80,
22931 NonZeroU32::new(4).unwrap(),
22932 false,
22933 ),
22934 format!("foo{}bar", '\u{2009}')
22935 );
22936}
22937
22938pub trait CollaborationHub {
22939 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22940 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22941 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22942}
22943
22944impl CollaborationHub for Entity<Project> {
22945 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22946 self.read(cx).collaborators()
22947 }
22948
22949 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22950 self.read(cx).user_store().read(cx).participant_indices()
22951 }
22952
22953 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22954 let this = self.read(cx);
22955 let user_ids = this.collaborators().values().map(|c| c.user_id);
22956 this.user_store().read(cx).participant_names(user_ids, cx)
22957 }
22958}
22959
22960pub trait SemanticsProvider {
22961 fn hover(
22962 &self,
22963 buffer: &Entity<Buffer>,
22964 position: text::Anchor,
22965 cx: &mut App,
22966 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22967
22968 fn inline_values(
22969 &self,
22970 buffer_handle: Entity<Buffer>,
22971 range: Range<text::Anchor>,
22972 cx: &mut App,
22973 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22974
22975 fn applicable_inlay_chunks(
22976 &self,
22977 buffer: &Entity<Buffer>,
22978 ranges: &[Range<text::Anchor>],
22979 cx: &mut App,
22980 ) -> Vec<Range<BufferRow>>;
22981
22982 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22983
22984 fn inlay_hints(
22985 &self,
22986 invalidate: InvalidationStrategy,
22987 buffer: Entity<Buffer>,
22988 ranges: Vec<Range<text::Anchor>>,
22989 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22990 cx: &mut App,
22991 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22992
22993 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22994
22995 fn document_highlights(
22996 &self,
22997 buffer: &Entity<Buffer>,
22998 position: text::Anchor,
22999 cx: &mut App,
23000 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23001
23002 fn definitions(
23003 &self,
23004 buffer: &Entity<Buffer>,
23005 position: text::Anchor,
23006 kind: GotoDefinitionKind,
23007 cx: &mut App,
23008 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23009
23010 fn range_for_rename(
23011 &self,
23012 buffer: &Entity<Buffer>,
23013 position: text::Anchor,
23014 cx: &mut App,
23015 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23016
23017 fn perform_rename(
23018 &self,
23019 buffer: &Entity<Buffer>,
23020 position: text::Anchor,
23021 new_name: String,
23022 cx: &mut App,
23023 ) -> Option<Task<Result<ProjectTransaction>>>;
23024}
23025
23026pub trait CompletionProvider {
23027 fn completions(
23028 &self,
23029 excerpt_id: ExcerptId,
23030 buffer: &Entity<Buffer>,
23031 buffer_position: text::Anchor,
23032 trigger: CompletionContext,
23033 window: &mut Window,
23034 cx: &mut Context<Editor>,
23035 ) -> Task<Result<Vec<CompletionResponse>>>;
23036
23037 fn resolve_completions(
23038 &self,
23039 _buffer: Entity<Buffer>,
23040 _completion_indices: Vec<usize>,
23041 _completions: Rc<RefCell<Box<[Completion]>>>,
23042 _cx: &mut Context<Editor>,
23043 ) -> Task<Result<bool>> {
23044 Task::ready(Ok(false))
23045 }
23046
23047 fn apply_additional_edits_for_completion(
23048 &self,
23049 _buffer: Entity<Buffer>,
23050 _completions: Rc<RefCell<Box<[Completion]>>>,
23051 _completion_index: usize,
23052 _push_to_history: bool,
23053 _cx: &mut Context<Editor>,
23054 ) -> Task<Result<Option<language::Transaction>>> {
23055 Task::ready(Ok(None))
23056 }
23057
23058 fn is_completion_trigger(
23059 &self,
23060 buffer: &Entity<Buffer>,
23061 position: language::Anchor,
23062 text: &str,
23063 trigger_in_words: bool,
23064 menu_is_open: bool,
23065 cx: &mut Context<Editor>,
23066 ) -> bool;
23067
23068 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23069
23070 fn sort_completions(&self) -> bool {
23071 true
23072 }
23073
23074 fn filter_completions(&self) -> bool {
23075 true
23076 }
23077}
23078
23079pub trait CodeActionProvider {
23080 fn id(&self) -> Arc<str>;
23081
23082 fn code_actions(
23083 &self,
23084 buffer: &Entity<Buffer>,
23085 range: Range<text::Anchor>,
23086 window: &mut Window,
23087 cx: &mut App,
23088 ) -> Task<Result<Vec<CodeAction>>>;
23089
23090 fn apply_code_action(
23091 &self,
23092 buffer_handle: Entity<Buffer>,
23093 action: CodeAction,
23094 excerpt_id: ExcerptId,
23095 push_to_history: bool,
23096 window: &mut Window,
23097 cx: &mut App,
23098 ) -> Task<Result<ProjectTransaction>>;
23099}
23100
23101impl CodeActionProvider for Entity<Project> {
23102 fn id(&self) -> Arc<str> {
23103 "project".into()
23104 }
23105
23106 fn code_actions(
23107 &self,
23108 buffer: &Entity<Buffer>,
23109 range: Range<text::Anchor>,
23110 _window: &mut Window,
23111 cx: &mut App,
23112 ) -> Task<Result<Vec<CodeAction>>> {
23113 self.update(cx, |project, cx| {
23114 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23115 let code_actions = project.code_actions(buffer, range, None, cx);
23116 cx.background_spawn(async move {
23117 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23118 Ok(code_lens_actions
23119 .context("code lens fetch")?
23120 .into_iter()
23121 .flatten()
23122 .chain(
23123 code_actions
23124 .context("code action fetch")?
23125 .into_iter()
23126 .flatten(),
23127 )
23128 .collect())
23129 })
23130 })
23131 }
23132
23133 fn apply_code_action(
23134 &self,
23135 buffer_handle: Entity<Buffer>,
23136 action: CodeAction,
23137 _excerpt_id: ExcerptId,
23138 push_to_history: bool,
23139 _window: &mut Window,
23140 cx: &mut App,
23141 ) -> Task<Result<ProjectTransaction>> {
23142 self.update(cx, |project, cx| {
23143 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23144 })
23145 }
23146}
23147
23148fn snippet_completions(
23149 project: &Project,
23150 buffer: &Entity<Buffer>,
23151 buffer_position: text::Anchor,
23152 cx: &mut App,
23153) -> Task<Result<CompletionResponse>> {
23154 let languages = buffer.read(cx).languages_at(buffer_position);
23155 let snippet_store = project.snippets().read(cx);
23156
23157 let scopes: Vec<_> = languages
23158 .iter()
23159 .filter_map(|language| {
23160 let language_name = language.lsp_id();
23161 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23162
23163 if snippets.is_empty() {
23164 None
23165 } else {
23166 Some((language.default_scope(), snippets))
23167 }
23168 })
23169 .collect();
23170
23171 if scopes.is_empty() {
23172 return Task::ready(Ok(CompletionResponse {
23173 completions: vec![],
23174 display_options: CompletionDisplayOptions::default(),
23175 is_incomplete: false,
23176 }));
23177 }
23178
23179 let snapshot = buffer.read(cx).text_snapshot();
23180 let executor = cx.background_executor().clone();
23181
23182 cx.background_spawn(async move {
23183 let mut is_incomplete = false;
23184 let mut completions: Vec<Completion> = Vec::new();
23185 for (scope, snippets) in scopes.into_iter() {
23186 let classifier =
23187 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23188
23189 const MAX_WORD_PREFIX_LEN: usize = 128;
23190 let last_word: String = snapshot
23191 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23192 .take(MAX_WORD_PREFIX_LEN)
23193 .take_while(|c| classifier.is_word(*c))
23194 .collect::<String>()
23195 .chars()
23196 .rev()
23197 .collect();
23198
23199 if last_word.is_empty() {
23200 return Ok(CompletionResponse {
23201 completions: vec![],
23202 display_options: CompletionDisplayOptions::default(),
23203 is_incomplete: true,
23204 });
23205 }
23206
23207 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23208 let to_lsp = |point: &text::Anchor| {
23209 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23210 point_to_lsp(end)
23211 };
23212 let lsp_end = to_lsp(&buffer_position);
23213
23214 let candidates = snippets
23215 .iter()
23216 .enumerate()
23217 .flat_map(|(ix, snippet)| {
23218 snippet
23219 .prefix
23220 .iter()
23221 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23222 })
23223 .collect::<Vec<StringMatchCandidate>>();
23224
23225 const MAX_RESULTS: usize = 100;
23226 let mut matches = fuzzy::match_strings(
23227 &candidates,
23228 &last_word,
23229 last_word.chars().any(|c| c.is_uppercase()),
23230 true,
23231 MAX_RESULTS,
23232 &Default::default(),
23233 executor.clone(),
23234 )
23235 .await;
23236
23237 if matches.len() >= MAX_RESULTS {
23238 is_incomplete = true;
23239 }
23240
23241 // Remove all candidates where the query's start does not match the start of any word in the candidate
23242 if let Some(query_start) = last_word.chars().next() {
23243 matches.retain(|string_match| {
23244 split_words(&string_match.string).any(|word| {
23245 // Check that the first codepoint of the word as lowercase matches the first
23246 // codepoint of the query as lowercase
23247 word.chars()
23248 .flat_map(|codepoint| codepoint.to_lowercase())
23249 .zip(query_start.to_lowercase())
23250 .all(|(word_cp, query_cp)| word_cp == query_cp)
23251 })
23252 });
23253 }
23254
23255 let matched_strings = matches
23256 .into_iter()
23257 .map(|m| m.string)
23258 .collect::<HashSet<_>>();
23259
23260 completions.extend(snippets.iter().filter_map(|snippet| {
23261 let matching_prefix = snippet
23262 .prefix
23263 .iter()
23264 .find(|prefix| matched_strings.contains(*prefix))?;
23265 let start = as_offset - last_word.len();
23266 let start = snapshot.anchor_before(start);
23267 let range = start..buffer_position;
23268 let lsp_start = to_lsp(&start);
23269 let lsp_range = lsp::Range {
23270 start: lsp_start,
23271 end: lsp_end,
23272 };
23273 Some(Completion {
23274 replace_range: range,
23275 new_text: snippet.body.clone(),
23276 source: CompletionSource::Lsp {
23277 insert_range: None,
23278 server_id: LanguageServerId(usize::MAX),
23279 resolved: true,
23280 lsp_completion: Box::new(lsp::CompletionItem {
23281 label: snippet.prefix.first().unwrap().clone(),
23282 kind: Some(CompletionItemKind::SNIPPET),
23283 label_details: snippet.description.as_ref().map(|description| {
23284 lsp::CompletionItemLabelDetails {
23285 detail: Some(description.clone()),
23286 description: None,
23287 }
23288 }),
23289 insert_text_format: Some(InsertTextFormat::SNIPPET),
23290 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23291 lsp::InsertReplaceEdit {
23292 new_text: snippet.body.clone(),
23293 insert: lsp_range,
23294 replace: lsp_range,
23295 },
23296 )),
23297 filter_text: Some(snippet.body.clone()),
23298 sort_text: Some(char::MAX.to_string()),
23299 ..lsp::CompletionItem::default()
23300 }),
23301 lsp_defaults: None,
23302 },
23303 label: CodeLabel::plain(matching_prefix.clone(), None),
23304 icon_path: None,
23305 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23306 single_line: snippet.name.clone().into(),
23307 plain_text: snippet
23308 .description
23309 .clone()
23310 .map(|description| description.into()),
23311 }),
23312 insert_text_mode: None,
23313 confirm: None,
23314 })
23315 }))
23316 }
23317
23318 Ok(CompletionResponse {
23319 completions,
23320 display_options: CompletionDisplayOptions::default(),
23321 is_incomplete,
23322 })
23323 })
23324}
23325
23326impl CompletionProvider for Entity<Project> {
23327 fn completions(
23328 &self,
23329 _excerpt_id: ExcerptId,
23330 buffer: &Entity<Buffer>,
23331 buffer_position: text::Anchor,
23332 options: CompletionContext,
23333 _window: &mut Window,
23334 cx: &mut Context<Editor>,
23335 ) -> Task<Result<Vec<CompletionResponse>>> {
23336 self.update(cx, |project, cx| {
23337 let snippets = snippet_completions(project, buffer, buffer_position, cx);
23338 let project_completions = project.completions(buffer, buffer_position, options, cx);
23339 cx.background_spawn(async move {
23340 let mut responses = project_completions.await?;
23341 let snippets = snippets.await?;
23342 if !snippets.completions.is_empty() {
23343 responses.push(snippets);
23344 }
23345 Ok(responses)
23346 })
23347 })
23348 }
23349
23350 fn resolve_completions(
23351 &self,
23352 buffer: Entity<Buffer>,
23353 completion_indices: Vec<usize>,
23354 completions: Rc<RefCell<Box<[Completion]>>>,
23355 cx: &mut Context<Editor>,
23356 ) -> Task<Result<bool>> {
23357 self.update(cx, |project, cx| {
23358 project.lsp_store().update(cx, |lsp_store, cx| {
23359 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23360 })
23361 })
23362 }
23363
23364 fn apply_additional_edits_for_completion(
23365 &self,
23366 buffer: Entity<Buffer>,
23367 completions: Rc<RefCell<Box<[Completion]>>>,
23368 completion_index: usize,
23369 push_to_history: bool,
23370 cx: &mut Context<Editor>,
23371 ) -> Task<Result<Option<language::Transaction>>> {
23372 self.update(cx, |project, cx| {
23373 project.lsp_store().update(cx, |lsp_store, cx| {
23374 lsp_store.apply_additional_edits_for_completion(
23375 buffer,
23376 completions,
23377 completion_index,
23378 push_to_history,
23379 cx,
23380 )
23381 })
23382 })
23383 }
23384
23385 fn is_completion_trigger(
23386 &self,
23387 buffer: &Entity<Buffer>,
23388 position: language::Anchor,
23389 text: &str,
23390 trigger_in_words: bool,
23391 menu_is_open: bool,
23392 cx: &mut Context<Editor>,
23393 ) -> bool {
23394 let mut chars = text.chars();
23395 let char = if let Some(char) = chars.next() {
23396 char
23397 } else {
23398 return false;
23399 };
23400 if chars.next().is_some() {
23401 return false;
23402 }
23403
23404 let buffer = buffer.read(cx);
23405 let snapshot = buffer.snapshot();
23406 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23407 return false;
23408 }
23409 let classifier = snapshot
23410 .char_classifier_at(position)
23411 .scope_context(Some(CharScopeContext::Completion));
23412 if trigger_in_words && classifier.is_word(char) {
23413 return true;
23414 }
23415
23416 buffer.completion_triggers().contains(text)
23417 }
23418}
23419
23420impl SemanticsProvider for Entity<Project> {
23421 fn hover(
23422 &self,
23423 buffer: &Entity<Buffer>,
23424 position: text::Anchor,
23425 cx: &mut App,
23426 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23427 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23428 }
23429
23430 fn document_highlights(
23431 &self,
23432 buffer: &Entity<Buffer>,
23433 position: text::Anchor,
23434 cx: &mut App,
23435 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23436 Some(self.update(cx, |project, cx| {
23437 project.document_highlights(buffer, position, cx)
23438 }))
23439 }
23440
23441 fn definitions(
23442 &self,
23443 buffer: &Entity<Buffer>,
23444 position: text::Anchor,
23445 kind: GotoDefinitionKind,
23446 cx: &mut App,
23447 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23448 Some(self.update(cx, |project, cx| match kind {
23449 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23450 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23451 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23452 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23453 }))
23454 }
23455
23456 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23457 self.update(cx, |project, cx| {
23458 if project
23459 .active_debug_session(cx)
23460 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23461 {
23462 return true;
23463 }
23464
23465 buffer.update(cx, |buffer, cx| {
23466 project.any_language_server_supports_inlay_hints(buffer, cx)
23467 })
23468 })
23469 }
23470
23471 fn inline_values(
23472 &self,
23473 buffer_handle: Entity<Buffer>,
23474 range: Range<text::Anchor>,
23475 cx: &mut App,
23476 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23477 self.update(cx, |project, cx| {
23478 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23479
23480 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23481 })
23482 }
23483
23484 fn applicable_inlay_chunks(
23485 &self,
23486 buffer: &Entity<Buffer>,
23487 ranges: &[Range<text::Anchor>],
23488 cx: &mut App,
23489 ) -> Vec<Range<BufferRow>> {
23490 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23491 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23492 })
23493 }
23494
23495 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23496 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23497 lsp_store.invalidate_inlay_hints(for_buffers)
23498 });
23499 }
23500
23501 fn inlay_hints(
23502 &self,
23503 invalidate: InvalidationStrategy,
23504 buffer: Entity<Buffer>,
23505 ranges: Vec<Range<text::Anchor>>,
23506 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23507 cx: &mut App,
23508 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23509 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23510 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23511 }))
23512 }
23513
23514 fn range_for_rename(
23515 &self,
23516 buffer: &Entity<Buffer>,
23517 position: text::Anchor,
23518 cx: &mut App,
23519 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23520 Some(self.update(cx, |project, cx| {
23521 let buffer = buffer.clone();
23522 let task = project.prepare_rename(buffer.clone(), position, cx);
23523 cx.spawn(async move |_, cx| {
23524 Ok(match task.await? {
23525 PrepareRenameResponse::Success(range) => Some(range),
23526 PrepareRenameResponse::InvalidPosition => None,
23527 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23528 // Fallback on using TreeSitter info to determine identifier range
23529 buffer.read_with(cx, |buffer, _| {
23530 let snapshot = buffer.snapshot();
23531 let (range, kind) = snapshot.surrounding_word(position, None);
23532 if kind != Some(CharKind::Word) {
23533 return None;
23534 }
23535 Some(
23536 snapshot.anchor_before(range.start)
23537 ..snapshot.anchor_after(range.end),
23538 )
23539 })?
23540 }
23541 })
23542 })
23543 }))
23544 }
23545
23546 fn perform_rename(
23547 &self,
23548 buffer: &Entity<Buffer>,
23549 position: text::Anchor,
23550 new_name: String,
23551 cx: &mut App,
23552 ) -> Option<Task<Result<ProjectTransaction>>> {
23553 Some(self.update(cx, |project, cx| {
23554 project.perform_rename(buffer.clone(), position, new_name, cx)
23555 }))
23556 }
23557}
23558
23559fn consume_contiguous_rows(
23560 contiguous_row_selections: &mut Vec<Selection<Point>>,
23561 selection: &Selection<Point>,
23562 display_map: &DisplaySnapshot,
23563 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23564) -> (MultiBufferRow, MultiBufferRow) {
23565 contiguous_row_selections.push(selection.clone());
23566 let start_row = starting_row(selection, display_map);
23567 let mut end_row = ending_row(selection, display_map);
23568
23569 while let Some(next_selection) = selections.peek() {
23570 if next_selection.start.row <= end_row.0 {
23571 end_row = ending_row(next_selection, display_map);
23572 contiguous_row_selections.push(selections.next().unwrap().clone());
23573 } else {
23574 break;
23575 }
23576 }
23577 (start_row, end_row)
23578}
23579
23580fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23581 if selection.start.column > 0 {
23582 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23583 } else {
23584 MultiBufferRow(selection.start.row)
23585 }
23586}
23587
23588fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23589 if next_selection.end.column > 0 || next_selection.is_empty() {
23590 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23591 } else {
23592 MultiBufferRow(next_selection.end.row)
23593 }
23594}
23595
23596impl EditorSnapshot {
23597 pub fn remote_selections_in_range<'a>(
23598 &'a self,
23599 range: &'a Range<Anchor>,
23600 collaboration_hub: &dyn CollaborationHub,
23601 cx: &'a App,
23602 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23603 let participant_names = collaboration_hub.user_names(cx);
23604 let participant_indices = collaboration_hub.user_participant_indices(cx);
23605 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23606 let collaborators_by_replica_id = collaborators_by_peer_id
23607 .values()
23608 .map(|collaborator| (collaborator.replica_id, collaborator))
23609 .collect::<HashMap<_, _>>();
23610 self.buffer_snapshot()
23611 .selections_in_range(range, false)
23612 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23613 if replica_id == ReplicaId::AGENT {
23614 Some(RemoteSelection {
23615 replica_id,
23616 selection,
23617 cursor_shape,
23618 line_mode,
23619 collaborator_id: CollaboratorId::Agent,
23620 user_name: Some("Agent".into()),
23621 color: cx.theme().players().agent(),
23622 })
23623 } else {
23624 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23625 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23626 let user_name = participant_names.get(&collaborator.user_id).cloned();
23627 Some(RemoteSelection {
23628 replica_id,
23629 selection,
23630 cursor_shape,
23631 line_mode,
23632 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23633 user_name,
23634 color: if let Some(index) = participant_index {
23635 cx.theme().players().color_for_participant(index.0)
23636 } else {
23637 cx.theme().players().absent()
23638 },
23639 })
23640 }
23641 })
23642 }
23643
23644 pub fn hunks_for_ranges(
23645 &self,
23646 ranges: impl IntoIterator<Item = Range<Point>>,
23647 ) -> Vec<MultiBufferDiffHunk> {
23648 let mut hunks = Vec::new();
23649 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23650 HashMap::default();
23651 for query_range in ranges {
23652 let query_rows =
23653 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23654 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23655 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23656 ) {
23657 // Include deleted hunks that are adjacent to the query range, because
23658 // otherwise they would be missed.
23659 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23660 if hunk.status().is_deleted() {
23661 intersects_range |= hunk.row_range.start == query_rows.end;
23662 intersects_range |= hunk.row_range.end == query_rows.start;
23663 }
23664 if intersects_range {
23665 if !processed_buffer_rows
23666 .entry(hunk.buffer_id)
23667 .or_default()
23668 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23669 {
23670 continue;
23671 }
23672 hunks.push(hunk);
23673 }
23674 }
23675 }
23676
23677 hunks
23678 }
23679
23680 fn display_diff_hunks_for_rows<'a>(
23681 &'a self,
23682 display_rows: Range<DisplayRow>,
23683 folded_buffers: &'a HashSet<BufferId>,
23684 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23685 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23686 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23687
23688 self.buffer_snapshot()
23689 .diff_hunks_in_range(buffer_start..buffer_end)
23690 .filter_map(|hunk| {
23691 if folded_buffers.contains(&hunk.buffer_id) {
23692 return None;
23693 }
23694
23695 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23696 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23697
23698 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23699 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23700
23701 let display_hunk = if hunk_display_start.column() != 0 {
23702 DisplayDiffHunk::Folded {
23703 display_row: hunk_display_start.row(),
23704 }
23705 } else {
23706 let mut end_row = hunk_display_end.row();
23707 if hunk_display_end.column() > 0 {
23708 end_row.0 += 1;
23709 }
23710 let is_created_file = hunk.is_created_file();
23711 DisplayDiffHunk::Unfolded {
23712 status: hunk.status(),
23713 diff_base_byte_range: hunk.diff_base_byte_range,
23714 display_row_range: hunk_display_start.row()..end_row,
23715 multi_buffer_range: Anchor::range_in_buffer(
23716 hunk.excerpt_id,
23717 hunk.buffer_id,
23718 hunk.buffer_range,
23719 ),
23720 is_created_file,
23721 }
23722 };
23723
23724 Some(display_hunk)
23725 })
23726 }
23727
23728 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23729 self.display_snapshot
23730 .buffer_snapshot()
23731 .language_at(position)
23732 }
23733
23734 pub fn is_focused(&self) -> bool {
23735 self.is_focused
23736 }
23737
23738 pub fn placeholder_text(&self) -> Option<String> {
23739 self.placeholder_display_snapshot
23740 .as_ref()
23741 .map(|display_map| display_map.text())
23742 }
23743
23744 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23745 self.scroll_anchor.scroll_position(&self.display_snapshot)
23746 }
23747
23748 fn gutter_dimensions(
23749 &self,
23750 font_id: FontId,
23751 font_size: Pixels,
23752 max_line_number_width: Pixels,
23753 cx: &App,
23754 ) -> Option<GutterDimensions> {
23755 if !self.show_gutter {
23756 return None;
23757 }
23758
23759 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23760 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23761
23762 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23763 matches!(
23764 ProjectSettings::get_global(cx).git.git_gutter,
23765 GitGutterSetting::TrackedFiles
23766 )
23767 });
23768 let gutter_settings = EditorSettings::get_global(cx).gutter;
23769 let show_line_numbers = self
23770 .show_line_numbers
23771 .unwrap_or(gutter_settings.line_numbers);
23772 let line_gutter_width = if show_line_numbers {
23773 // Avoid flicker-like gutter resizes when the line number gains another digit by
23774 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23775 let min_width_for_number_on_gutter =
23776 ch_advance * gutter_settings.min_line_number_digits as f32;
23777 max_line_number_width.max(min_width_for_number_on_gutter)
23778 } else {
23779 0.0.into()
23780 };
23781
23782 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23783 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23784
23785 let git_blame_entries_width =
23786 self.git_blame_gutter_max_author_length
23787 .map(|max_author_length| {
23788 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23789 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23790
23791 /// The number of characters to dedicate to gaps and margins.
23792 const SPACING_WIDTH: usize = 4;
23793
23794 let max_char_count = max_author_length.min(renderer.max_author_length())
23795 + ::git::SHORT_SHA_LENGTH
23796 + MAX_RELATIVE_TIMESTAMP.len()
23797 + SPACING_WIDTH;
23798
23799 ch_advance * max_char_count
23800 });
23801
23802 let is_singleton = self.buffer_snapshot().is_singleton();
23803
23804 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23805 left_padding += if !is_singleton {
23806 ch_width * 4.0
23807 } else if show_runnables || show_breakpoints {
23808 ch_width * 3.0
23809 } else if show_git_gutter && show_line_numbers {
23810 ch_width * 2.0
23811 } else if show_git_gutter || show_line_numbers {
23812 ch_width
23813 } else {
23814 px(0.)
23815 };
23816
23817 let shows_folds = is_singleton && gutter_settings.folds;
23818
23819 let right_padding = if shows_folds && show_line_numbers {
23820 ch_width * 4.0
23821 } else if shows_folds || (!is_singleton && show_line_numbers) {
23822 ch_width * 3.0
23823 } else if show_line_numbers {
23824 ch_width
23825 } else {
23826 px(0.)
23827 };
23828
23829 Some(GutterDimensions {
23830 left_padding,
23831 right_padding,
23832 width: line_gutter_width + left_padding + right_padding,
23833 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23834 git_blame_entries_width,
23835 })
23836 }
23837
23838 pub fn render_crease_toggle(
23839 &self,
23840 buffer_row: MultiBufferRow,
23841 row_contains_cursor: bool,
23842 editor: Entity<Editor>,
23843 window: &mut Window,
23844 cx: &mut App,
23845 ) -> Option<AnyElement> {
23846 let folded = self.is_line_folded(buffer_row);
23847 let mut is_foldable = false;
23848
23849 if let Some(crease) = self
23850 .crease_snapshot
23851 .query_row(buffer_row, self.buffer_snapshot())
23852 {
23853 is_foldable = true;
23854 match crease {
23855 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23856 if let Some(render_toggle) = render_toggle {
23857 let toggle_callback =
23858 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23859 if folded {
23860 editor.update(cx, |editor, cx| {
23861 editor.fold_at(buffer_row, window, cx)
23862 });
23863 } else {
23864 editor.update(cx, |editor, cx| {
23865 editor.unfold_at(buffer_row, window, cx)
23866 });
23867 }
23868 });
23869 return Some((render_toggle)(
23870 buffer_row,
23871 folded,
23872 toggle_callback,
23873 window,
23874 cx,
23875 ));
23876 }
23877 }
23878 }
23879 }
23880
23881 is_foldable |= self.starts_indent(buffer_row);
23882
23883 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23884 Some(
23885 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23886 .toggle_state(folded)
23887 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23888 if folded {
23889 this.unfold_at(buffer_row, window, cx);
23890 } else {
23891 this.fold_at(buffer_row, window, cx);
23892 }
23893 }))
23894 .into_any_element(),
23895 )
23896 } else {
23897 None
23898 }
23899 }
23900
23901 pub fn render_crease_trailer(
23902 &self,
23903 buffer_row: MultiBufferRow,
23904 window: &mut Window,
23905 cx: &mut App,
23906 ) -> Option<AnyElement> {
23907 let folded = self.is_line_folded(buffer_row);
23908 if let Crease::Inline { render_trailer, .. } = self
23909 .crease_snapshot
23910 .query_row(buffer_row, self.buffer_snapshot())?
23911 {
23912 let render_trailer = render_trailer.as_ref()?;
23913 Some(render_trailer(buffer_row, folded, window, cx))
23914 } else {
23915 None
23916 }
23917 }
23918}
23919
23920impl Deref for EditorSnapshot {
23921 type Target = DisplaySnapshot;
23922
23923 fn deref(&self) -> &Self::Target {
23924 &self.display_snapshot
23925 }
23926}
23927
23928#[derive(Clone, Debug, PartialEq, Eq)]
23929pub enum EditorEvent {
23930 InputIgnored {
23931 text: Arc<str>,
23932 },
23933 InputHandled {
23934 utf16_range_to_replace: Option<Range<isize>>,
23935 text: Arc<str>,
23936 },
23937 ExcerptsAdded {
23938 buffer: Entity<Buffer>,
23939 predecessor: ExcerptId,
23940 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23941 },
23942 ExcerptsRemoved {
23943 ids: Vec<ExcerptId>,
23944 removed_buffer_ids: Vec<BufferId>,
23945 },
23946 BufferFoldToggled {
23947 ids: Vec<ExcerptId>,
23948 folded: bool,
23949 },
23950 ExcerptsEdited {
23951 ids: Vec<ExcerptId>,
23952 },
23953 ExcerptsExpanded {
23954 ids: Vec<ExcerptId>,
23955 },
23956 BufferEdited,
23957 Edited {
23958 transaction_id: clock::Lamport,
23959 },
23960 Reparsed(BufferId),
23961 Focused,
23962 FocusedIn,
23963 Blurred,
23964 DirtyChanged,
23965 Saved,
23966 TitleChanged,
23967 SelectionsChanged {
23968 local: bool,
23969 },
23970 ScrollPositionChanged {
23971 local: bool,
23972 autoscroll: bool,
23973 },
23974 TransactionUndone {
23975 transaction_id: clock::Lamport,
23976 },
23977 TransactionBegun {
23978 transaction_id: clock::Lamport,
23979 },
23980 CursorShapeChanged,
23981 BreadcrumbsChanged,
23982 PushedToNavHistory {
23983 anchor: Anchor,
23984 is_deactivate: bool,
23985 },
23986}
23987
23988impl EventEmitter<EditorEvent> for Editor {}
23989
23990impl Focusable for Editor {
23991 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23992 self.focus_handle.clone()
23993 }
23994}
23995
23996impl Render for Editor {
23997 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23998 let settings = ThemeSettings::get_global(cx);
23999
24000 let mut text_style = match self.mode {
24001 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24002 color: cx.theme().colors().editor_foreground,
24003 font_family: settings.ui_font.family.clone(),
24004 font_features: settings.ui_font.features.clone(),
24005 font_fallbacks: settings.ui_font.fallbacks.clone(),
24006 font_size: rems(0.875).into(),
24007 font_weight: settings.ui_font.weight,
24008 line_height: relative(settings.buffer_line_height.value()),
24009 ..Default::default()
24010 },
24011 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24012 color: cx.theme().colors().editor_foreground,
24013 font_family: settings.buffer_font.family.clone(),
24014 font_features: settings.buffer_font.features.clone(),
24015 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24016 font_size: settings.buffer_font_size(cx).into(),
24017 font_weight: settings.buffer_font.weight,
24018 line_height: relative(settings.buffer_line_height.value()),
24019 ..Default::default()
24020 },
24021 };
24022 if let Some(text_style_refinement) = &self.text_style_refinement {
24023 text_style.refine(text_style_refinement)
24024 }
24025
24026 let background = match self.mode {
24027 EditorMode::SingleLine => cx.theme().system().transparent,
24028 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24029 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24030 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24031 };
24032
24033 EditorElement::new(
24034 &cx.entity(),
24035 EditorStyle {
24036 background,
24037 border: cx.theme().colors().border,
24038 local_player: cx.theme().players().local(),
24039 text: text_style,
24040 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24041 syntax: cx.theme().syntax().clone(),
24042 status: cx.theme().status().clone(),
24043 inlay_hints_style: make_inlay_hints_style(cx),
24044 edit_prediction_styles: make_suggestion_styles(cx),
24045 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24046 show_underlines: self.diagnostics_enabled(),
24047 },
24048 )
24049 }
24050}
24051
24052impl EntityInputHandler for Editor {
24053 fn text_for_range(
24054 &mut self,
24055 range_utf16: Range<usize>,
24056 adjusted_range: &mut Option<Range<usize>>,
24057 _: &mut Window,
24058 cx: &mut Context<Self>,
24059 ) -> Option<String> {
24060 let snapshot = self.buffer.read(cx).read(cx);
24061 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24062 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24063 if (start.0..end.0) != range_utf16 {
24064 adjusted_range.replace(start.0..end.0);
24065 }
24066 Some(snapshot.text_for_range(start..end).collect())
24067 }
24068
24069 fn selected_text_range(
24070 &mut self,
24071 ignore_disabled_input: bool,
24072 _: &mut Window,
24073 cx: &mut Context<Self>,
24074 ) -> Option<UTF16Selection> {
24075 // Prevent the IME menu from appearing when holding down an alphabetic key
24076 // while input is disabled.
24077 if !ignore_disabled_input && !self.input_enabled {
24078 return None;
24079 }
24080
24081 let selection = self
24082 .selections
24083 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24084 let range = selection.range();
24085
24086 Some(UTF16Selection {
24087 range: range.start.0..range.end.0,
24088 reversed: selection.reversed,
24089 })
24090 }
24091
24092 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24093 let snapshot = self.buffer.read(cx).read(cx);
24094 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24095 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24096 }
24097
24098 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24099 self.clear_highlights::<InputComposition>(cx);
24100 self.ime_transaction.take();
24101 }
24102
24103 fn replace_text_in_range(
24104 &mut self,
24105 range_utf16: Option<Range<usize>>,
24106 text: &str,
24107 window: &mut Window,
24108 cx: &mut Context<Self>,
24109 ) {
24110 if !self.input_enabled {
24111 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24112 return;
24113 }
24114
24115 self.transact(window, cx, |this, window, cx| {
24116 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24117 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24118 Some(this.selection_replacement_ranges(range_utf16, cx))
24119 } else {
24120 this.marked_text_ranges(cx)
24121 };
24122
24123 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24124 let newest_selection_id = this.selections.newest_anchor().id;
24125 this.selections
24126 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24127 .iter()
24128 .zip(ranges_to_replace.iter())
24129 .find_map(|(selection, range)| {
24130 if selection.id == newest_selection_id {
24131 Some(
24132 (range.start.0 as isize - selection.head().0 as isize)
24133 ..(range.end.0 as isize - selection.head().0 as isize),
24134 )
24135 } else {
24136 None
24137 }
24138 })
24139 });
24140
24141 cx.emit(EditorEvent::InputHandled {
24142 utf16_range_to_replace: range_to_replace,
24143 text: text.into(),
24144 });
24145
24146 if let Some(new_selected_ranges) = new_selected_ranges {
24147 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24148 selections.select_ranges(new_selected_ranges)
24149 });
24150 this.backspace(&Default::default(), window, cx);
24151 }
24152
24153 this.handle_input(text, window, cx);
24154 });
24155
24156 if let Some(transaction) = self.ime_transaction {
24157 self.buffer.update(cx, |buffer, cx| {
24158 buffer.group_until_transaction(transaction, cx);
24159 });
24160 }
24161
24162 self.unmark_text(window, cx);
24163 }
24164
24165 fn replace_and_mark_text_in_range(
24166 &mut self,
24167 range_utf16: Option<Range<usize>>,
24168 text: &str,
24169 new_selected_range_utf16: Option<Range<usize>>,
24170 window: &mut Window,
24171 cx: &mut Context<Self>,
24172 ) {
24173 if !self.input_enabled {
24174 return;
24175 }
24176
24177 let transaction = self.transact(window, cx, |this, window, cx| {
24178 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24179 let snapshot = this.buffer.read(cx).read(cx);
24180 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24181 for marked_range in &mut marked_ranges {
24182 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24183 marked_range.start.0 += relative_range_utf16.start;
24184 marked_range.start =
24185 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24186 marked_range.end =
24187 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24188 }
24189 }
24190 Some(marked_ranges)
24191 } else if let Some(range_utf16) = range_utf16 {
24192 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24193 Some(this.selection_replacement_ranges(range_utf16, cx))
24194 } else {
24195 None
24196 };
24197
24198 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24199 let newest_selection_id = this.selections.newest_anchor().id;
24200 this.selections
24201 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24202 .iter()
24203 .zip(ranges_to_replace.iter())
24204 .find_map(|(selection, range)| {
24205 if selection.id == newest_selection_id {
24206 Some(
24207 (range.start.0 as isize - selection.head().0 as isize)
24208 ..(range.end.0 as isize - selection.head().0 as isize),
24209 )
24210 } else {
24211 None
24212 }
24213 })
24214 });
24215
24216 cx.emit(EditorEvent::InputHandled {
24217 utf16_range_to_replace: range_to_replace,
24218 text: text.into(),
24219 });
24220
24221 if let Some(ranges) = ranges_to_replace {
24222 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24223 s.select_ranges(ranges)
24224 });
24225 }
24226
24227 let marked_ranges = {
24228 let snapshot = this.buffer.read(cx).read(cx);
24229 this.selections
24230 .disjoint_anchors_arc()
24231 .iter()
24232 .map(|selection| {
24233 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24234 })
24235 .collect::<Vec<_>>()
24236 };
24237
24238 if text.is_empty() {
24239 this.unmark_text(window, cx);
24240 } else {
24241 this.highlight_text::<InputComposition>(
24242 marked_ranges.clone(),
24243 HighlightStyle {
24244 underline: Some(UnderlineStyle {
24245 thickness: px(1.),
24246 color: None,
24247 wavy: false,
24248 }),
24249 ..Default::default()
24250 },
24251 cx,
24252 );
24253 }
24254
24255 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24256 let use_autoclose = this.use_autoclose;
24257 let use_auto_surround = this.use_auto_surround;
24258 this.set_use_autoclose(false);
24259 this.set_use_auto_surround(false);
24260 this.handle_input(text, window, cx);
24261 this.set_use_autoclose(use_autoclose);
24262 this.set_use_auto_surround(use_auto_surround);
24263
24264 if let Some(new_selected_range) = new_selected_range_utf16 {
24265 let snapshot = this.buffer.read(cx).read(cx);
24266 let new_selected_ranges = marked_ranges
24267 .into_iter()
24268 .map(|marked_range| {
24269 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24270 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24271 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24272 snapshot.clip_offset_utf16(new_start, Bias::Left)
24273 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24274 })
24275 .collect::<Vec<_>>();
24276
24277 drop(snapshot);
24278 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24279 selections.select_ranges(new_selected_ranges)
24280 });
24281 }
24282 });
24283
24284 self.ime_transaction = self.ime_transaction.or(transaction);
24285 if let Some(transaction) = self.ime_transaction {
24286 self.buffer.update(cx, |buffer, cx| {
24287 buffer.group_until_transaction(transaction, cx);
24288 });
24289 }
24290
24291 if self.text_highlights::<InputComposition>(cx).is_none() {
24292 self.ime_transaction.take();
24293 }
24294 }
24295
24296 fn bounds_for_range(
24297 &mut self,
24298 range_utf16: Range<usize>,
24299 element_bounds: gpui::Bounds<Pixels>,
24300 window: &mut Window,
24301 cx: &mut Context<Self>,
24302 ) -> Option<gpui::Bounds<Pixels>> {
24303 let text_layout_details = self.text_layout_details(window);
24304 let CharacterDimensions {
24305 em_width,
24306 em_advance,
24307 line_height,
24308 } = self.character_dimensions(window);
24309
24310 let snapshot = self.snapshot(window, cx);
24311 let scroll_position = snapshot.scroll_position();
24312 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24313
24314 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24315 let x = Pixels::from(
24316 ScrollOffset::from(
24317 snapshot.x_for_display_point(start, &text_layout_details)
24318 + self.gutter_dimensions.full_width(),
24319 ) - scroll_left,
24320 );
24321 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24322
24323 Some(Bounds {
24324 origin: element_bounds.origin + point(x, y),
24325 size: size(em_width, line_height),
24326 })
24327 }
24328
24329 fn character_index_for_point(
24330 &mut self,
24331 point: gpui::Point<Pixels>,
24332 _window: &mut Window,
24333 _cx: &mut Context<Self>,
24334 ) -> Option<usize> {
24335 let position_map = self.last_position_map.as_ref()?;
24336 if !position_map.text_hitbox.contains(&point) {
24337 return None;
24338 }
24339 let display_point = position_map.point_for_position(point).previous_valid;
24340 let anchor = position_map
24341 .snapshot
24342 .display_point_to_anchor(display_point, Bias::Left);
24343 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24344 Some(utf16_offset.0)
24345 }
24346
24347 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24348 self.input_enabled
24349 }
24350}
24351
24352trait SelectionExt {
24353 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24354 fn spanned_rows(
24355 &self,
24356 include_end_if_at_line_start: bool,
24357 map: &DisplaySnapshot,
24358 ) -> Range<MultiBufferRow>;
24359}
24360
24361impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24362 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24363 let start = self
24364 .start
24365 .to_point(map.buffer_snapshot())
24366 .to_display_point(map);
24367 let end = self
24368 .end
24369 .to_point(map.buffer_snapshot())
24370 .to_display_point(map);
24371 if self.reversed {
24372 end..start
24373 } else {
24374 start..end
24375 }
24376 }
24377
24378 fn spanned_rows(
24379 &self,
24380 include_end_if_at_line_start: bool,
24381 map: &DisplaySnapshot,
24382 ) -> Range<MultiBufferRow> {
24383 let start = self.start.to_point(map.buffer_snapshot());
24384 let mut end = self.end.to_point(map.buffer_snapshot());
24385 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24386 end.row -= 1;
24387 }
24388
24389 let buffer_start = map.prev_line_boundary(start).0;
24390 let buffer_end = map.next_line_boundary(end).0;
24391 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24392 }
24393}
24394
24395impl<T: InvalidationRegion> InvalidationStack<T> {
24396 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24397 where
24398 S: Clone + ToOffset,
24399 {
24400 while let Some(region) = self.last() {
24401 let all_selections_inside_invalidation_ranges =
24402 if selections.len() == region.ranges().len() {
24403 selections
24404 .iter()
24405 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24406 .all(|(selection, invalidation_range)| {
24407 let head = selection.head().to_offset(buffer);
24408 invalidation_range.start <= head && invalidation_range.end >= head
24409 })
24410 } else {
24411 false
24412 };
24413
24414 if all_selections_inside_invalidation_ranges {
24415 break;
24416 } else {
24417 self.pop();
24418 }
24419 }
24420 }
24421}
24422
24423impl<T> Default for InvalidationStack<T> {
24424 fn default() -> Self {
24425 Self(Default::default())
24426 }
24427}
24428
24429impl<T> Deref for InvalidationStack<T> {
24430 type Target = Vec<T>;
24431
24432 fn deref(&self) -> &Self::Target {
24433 &self.0
24434 }
24435}
24436
24437impl<T> DerefMut for InvalidationStack<T> {
24438 fn deref_mut(&mut self) -> &mut Self::Target {
24439 &mut self.0
24440 }
24441}
24442
24443impl InvalidationRegion for SnippetState {
24444 fn ranges(&self) -> &[Range<Anchor>] {
24445 &self.ranges[self.active_index]
24446 }
24447}
24448
24449fn edit_prediction_edit_text(
24450 current_snapshot: &BufferSnapshot,
24451 edits: &[(Range<Anchor>, impl AsRef<str>)],
24452 edit_preview: &EditPreview,
24453 include_deletions: bool,
24454 cx: &App,
24455) -> HighlightedText {
24456 let edits = edits
24457 .iter()
24458 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24459 .collect::<Vec<_>>();
24460
24461 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24462}
24463
24464fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24465 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24466 // Just show the raw edit text with basic styling
24467 let mut text = String::new();
24468 let mut highlights = Vec::new();
24469
24470 let insertion_highlight_style = HighlightStyle {
24471 color: Some(cx.theme().colors().text),
24472 ..Default::default()
24473 };
24474
24475 for (_, edit_text) in edits {
24476 let start_offset = text.len();
24477 text.push_str(edit_text);
24478 let end_offset = text.len();
24479
24480 if start_offset < end_offset {
24481 highlights.push((start_offset..end_offset, insertion_highlight_style));
24482 }
24483 }
24484
24485 HighlightedText {
24486 text: text.into(),
24487 highlights,
24488 }
24489}
24490
24491pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24492 match severity {
24493 lsp::DiagnosticSeverity::ERROR => colors.error,
24494 lsp::DiagnosticSeverity::WARNING => colors.warning,
24495 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24496 lsp::DiagnosticSeverity::HINT => colors.info,
24497 _ => colors.ignored,
24498 }
24499}
24500
24501pub fn styled_runs_for_code_label<'a>(
24502 label: &'a CodeLabel,
24503 syntax_theme: &'a theme::SyntaxTheme,
24504) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24505 let fade_out = HighlightStyle {
24506 fade_out: Some(0.35),
24507 ..Default::default()
24508 };
24509
24510 let mut prev_end = label.filter_range.end;
24511 label
24512 .runs
24513 .iter()
24514 .enumerate()
24515 .flat_map(move |(ix, (range, highlight_id))| {
24516 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24517 style
24518 } else {
24519 return Default::default();
24520 };
24521 let muted_style = style.highlight(fade_out);
24522
24523 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24524 if range.start >= label.filter_range.end {
24525 if range.start > prev_end {
24526 runs.push((prev_end..range.start, fade_out));
24527 }
24528 runs.push((range.clone(), muted_style));
24529 } else if range.end <= label.filter_range.end {
24530 runs.push((range.clone(), style));
24531 } else {
24532 runs.push((range.start..label.filter_range.end, style));
24533 runs.push((label.filter_range.end..range.end, muted_style));
24534 }
24535 prev_end = cmp::max(prev_end, range.end);
24536
24537 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24538 runs.push((prev_end..label.text.len(), fade_out));
24539 }
24540
24541 runs
24542 })
24543}
24544
24545pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24546 let mut prev_index = 0;
24547 let mut prev_codepoint: Option<char> = None;
24548 text.char_indices()
24549 .chain([(text.len(), '\0')])
24550 .filter_map(move |(index, codepoint)| {
24551 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24552 let is_boundary = index == text.len()
24553 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24554 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24555 if is_boundary {
24556 let chunk = &text[prev_index..index];
24557 prev_index = index;
24558 Some(chunk)
24559 } else {
24560 None
24561 }
24562 })
24563}
24564
24565pub trait RangeToAnchorExt: Sized {
24566 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24567
24568 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24569 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24570 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24571 }
24572}
24573
24574impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24575 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24576 let start_offset = self.start.to_offset(snapshot);
24577 let end_offset = self.end.to_offset(snapshot);
24578 if start_offset == end_offset {
24579 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24580 } else {
24581 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24582 }
24583 }
24584}
24585
24586pub trait RowExt {
24587 fn as_f64(&self) -> f64;
24588
24589 fn next_row(&self) -> Self;
24590
24591 fn previous_row(&self) -> Self;
24592
24593 fn minus(&self, other: Self) -> u32;
24594}
24595
24596impl RowExt for DisplayRow {
24597 fn as_f64(&self) -> f64 {
24598 self.0 as _
24599 }
24600
24601 fn next_row(&self) -> Self {
24602 Self(self.0 + 1)
24603 }
24604
24605 fn previous_row(&self) -> Self {
24606 Self(self.0.saturating_sub(1))
24607 }
24608
24609 fn minus(&self, other: Self) -> u32 {
24610 self.0 - other.0
24611 }
24612}
24613
24614impl RowExt for MultiBufferRow {
24615 fn as_f64(&self) -> f64 {
24616 self.0 as _
24617 }
24618
24619 fn next_row(&self) -> Self {
24620 Self(self.0 + 1)
24621 }
24622
24623 fn previous_row(&self) -> Self {
24624 Self(self.0.saturating_sub(1))
24625 }
24626
24627 fn minus(&self, other: Self) -> u32 {
24628 self.0 - other.0
24629 }
24630}
24631
24632trait RowRangeExt {
24633 type Row;
24634
24635 fn len(&self) -> usize;
24636
24637 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24638}
24639
24640impl RowRangeExt for Range<MultiBufferRow> {
24641 type Row = MultiBufferRow;
24642
24643 fn len(&self) -> usize {
24644 (self.end.0 - self.start.0) as usize
24645 }
24646
24647 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24648 (self.start.0..self.end.0).map(MultiBufferRow)
24649 }
24650}
24651
24652impl RowRangeExt for Range<DisplayRow> {
24653 type Row = DisplayRow;
24654
24655 fn len(&self) -> usize {
24656 (self.end.0 - self.start.0) as usize
24657 }
24658
24659 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24660 (self.start.0..self.end.0).map(DisplayRow)
24661 }
24662}
24663
24664/// If select range has more than one line, we
24665/// just point the cursor to range.start.
24666fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24667 if range.start.row == range.end.row {
24668 range
24669 } else {
24670 range.start..range.start
24671 }
24672}
24673pub struct KillRing(ClipboardItem);
24674impl Global for KillRing {}
24675
24676const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24677
24678enum BreakpointPromptEditAction {
24679 Log,
24680 Condition,
24681 HitCondition,
24682}
24683
24684struct BreakpointPromptEditor {
24685 pub(crate) prompt: Entity<Editor>,
24686 editor: WeakEntity<Editor>,
24687 breakpoint_anchor: Anchor,
24688 breakpoint: Breakpoint,
24689 edit_action: BreakpointPromptEditAction,
24690 block_ids: HashSet<CustomBlockId>,
24691 editor_margins: Arc<Mutex<EditorMargins>>,
24692 _subscriptions: Vec<Subscription>,
24693}
24694
24695impl BreakpointPromptEditor {
24696 const MAX_LINES: u8 = 4;
24697
24698 fn new(
24699 editor: WeakEntity<Editor>,
24700 breakpoint_anchor: Anchor,
24701 breakpoint: Breakpoint,
24702 edit_action: BreakpointPromptEditAction,
24703 window: &mut Window,
24704 cx: &mut Context<Self>,
24705 ) -> Self {
24706 let base_text = match edit_action {
24707 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24708 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24709 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24710 }
24711 .map(|msg| msg.to_string())
24712 .unwrap_or_default();
24713
24714 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24715 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24716
24717 let prompt = cx.new(|cx| {
24718 let mut prompt = Editor::new(
24719 EditorMode::AutoHeight {
24720 min_lines: 1,
24721 max_lines: Some(Self::MAX_LINES as usize),
24722 },
24723 buffer,
24724 None,
24725 window,
24726 cx,
24727 );
24728 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24729 prompt.set_show_cursor_when_unfocused(false, cx);
24730 prompt.set_placeholder_text(
24731 match edit_action {
24732 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24733 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24734 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24735 },
24736 window,
24737 cx,
24738 );
24739
24740 prompt
24741 });
24742
24743 Self {
24744 prompt,
24745 editor,
24746 breakpoint_anchor,
24747 breakpoint,
24748 edit_action,
24749 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24750 block_ids: Default::default(),
24751 _subscriptions: vec![],
24752 }
24753 }
24754
24755 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24756 self.block_ids.extend(block_ids)
24757 }
24758
24759 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24760 if let Some(editor) = self.editor.upgrade() {
24761 let message = self
24762 .prompt
24763 .read(cx)
24764 .buffer
24765 .read(cx)
24766 .as_singleton()
24767 .expect("A multi buffer in breakpoint prompt isn't possible")
24768 .read(cx)
24769 .as_rope()
24770 .to_string();
24771
24772 editor.update(cx, |editor, cx| {
24773 editor.edit_breakpoint_at_anchor(
24774 self.breakpoint_anchor,
24775 self.breakpoint.clone(),
24776 match self.edit_action {
24777 BreakpointPromptEditAction::Log => {
24778 BreakpointEditAction::EditLogMessage(message.into())
24779 }
24780 BreakpointPromptEditAction::Condition => {
24781 BreakpointEditAction::EditCondition(message.into())
24782 }
24783 BreakpointPromptEditAction::HitCondition => {
24784 BreakpointEditAction::EditHitCondition(message.into())
24785 }
24786 },
24787 cx,
24788 );
24789
24790 editor.remove_blocks(self.block_ids.clone(), None, cx);
24791 cx.focus_self(window);
24792 });
24793 }
24794 }
24795
24796 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24797 self.editor
24798 .update(cx, |editor, cx| {
24799 editor.remove_blocks(self.block_ids.clone(), None, cx);
24800 window.focus(&editor.focus_handle);
24801 })
24802 .log_err();
24803 }
24804
24805 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24806 let settings = ThemeSettings::get_global(cx);
24807 let text_style = TextStyle {
24808 color: if self.prompt.read(cx).read_only(cx) {
24809 cx.theme().colors().text_disabled
24810 } else {
24811 cx.theme().colors().text
24812 },
24813 font_family: settings.buffer_font.family.clone(),
24814 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24815 font_size: settings.buffer_font_size(cx).into(),
24816 font_weight: settings.buffer_font.weight,
24817 line_height: relative(settings.buffer_line_height.value()),
24818 ..Default::default()
24819 };
24820 EditorElement::new(
24821 &self.prompt,
24822 EditorStyle {
24823 background: cx.theme().colors().editor_background,
24824 local_player: cx.theme().players().local(),
24825 text: text_style,
24826 ..Default::default()
24827 },
24828 )
24829 }
24830}
24831
24832impl Render for BreakpointPromptEditor {
24833 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24834 let editor_margins = *self.editor_margins.lock();
24835 let gutter_dimensions = editor_margins.gutter;
24836 h_flex()
24837 .key_context("Editor")
24838 .bg(cx.theme().colors().editor_background)
24839 .border_y_1()
24840 .border_color(cx.theme().status().info_border)
24841 .size_full()
24842 .py(window.line_height() / 2.5)
24843 .on_action(cx.listener(Self::confirm))
24844 .on_action(cx.listener(Self::cancel))
24845 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24846 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24847 }
24848}
24849
24850impl Focusable for BreakpointPromptEditor {
24851 fn focus_handle(&self, cx: &App) -> FocusHandle {
24852 self.prompt.focus_handle(cx)
24853 }
24854}
24855
24856fn all_edits_insertions_or_deletions(
24857 edits: &Vec<(Range<Anchor>, Arc<str>)>,
24858 snapshot: &MultiBufferSnapshot,
24859) -> bool {
24860 let mut all_insertions = true;
24861 let mut all_deletions = true;
24862
24863 for (range, new_text) in edits.iter() {
24864 let range_is_empty = range.to_offset(snapshot).is_empty();
24865 let text_is_empty = new_text.is_empty();
24866
24867 if range_is_empty != text_is_empty {
24868 if range_is_empty {
24869 all_deletions = false;
24870 } else {
24871 all_insertions = false;
24872 }
24873 } else {
24874 return false;
24875 }
24876
24877 if !all_insertions && !all_deletions {
24878 return false;
24879 }
24880 }
24881 all_insertions || all_deletions
24882}
24883
24884struct MissingEditPredictionKeybindingTooltip;
24885
24886impl Render for MissingEditPredictionKeybindingTooltip {
24887 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24888 ui::tooltip_container(cx, |container, cx| {
24889 container
24890 .flex_shrink_0()
24891 .max_w_80()
24892 .min_h(rems_from_px(124.))
24893 .justify_between()
24894 .child(
24895 v_flex()
24896 .flex_1()
24897 .text_ui_sm(cx)
24898 .child(Label::new("Conflict with Accept Keybinding"))
24899 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24900 )
24901 .child(
24902 h_flex()
24903 .pb_1()
24904 .gap_1()
24905 .items_end()
24906 .w_full()
24907 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24908 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24909 }))
24910 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24911 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24912 })),
24913 )
24914 })
24915 }
24916}
24917
24918#[derive(Debug, Clone, Copy, PartialEq)]
24919pub struct LineHighlight {
24920 pub background: Background,
24921 pub border: Option<gpui::Hsla>,
24922 pub include_gutter: bool,
24923 pub type_id: Option<TypeId>,
24924}
24925
24926struct LineManipulationResult {
24927 pub new_text: String,
24928 pub line_count_before: usize,
24929 pub line_count_after: usize,
24930}
24931
24932fn render_diff_hunk_controls(
24933 row: u32,
24934 status: &DiffHunkStatus,
24935 hunk_range: Range<Anchor>,
24936 is_created_file: bool,
24937 line_height: Pixels,
24938 editor: &Entity<Editor>,
24939 _window: &mut Window,
24940 cx: &mut App,
24941) -> AnyElement {
24942 h_flex()
24943 .h(line_height)
24944 .mr_1()
24945 .gap_1()
24946 .px_0p5()
24947 .pb_1()
24948 .border_x_1()
24949 .border_b_1()
24950 .border_color(cx.theme().colors().border_variant)
24951 .rounded_b_lg()
24952 .bg(cx.theme().colors().editor_background)
24953 .gap_1()
24954 .block_mouse_except_scroll()
24955 .shadow_md()
24956 .child(if status.has_secondary_hunk() {
24957 Button::new(("stage", row as u64), "Stage")
24958 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24959 .tooltip({
24960 let focus_handle = editor.focus_handle(cx);
24961 move |_window, cx| {
24962 Tooltip::for_action_in(
24963 "Stage Hunk",
24964 &::git::ToggleStaged,
24965 &focus_handle,
24966 cx,
24967 )
24968 }
24969 })
24970 .on_click({
24971 let editor = editor.clone();
24972 move |_event, _window, cx| {
24973 editor.update(cx, |editor, cx| {
24974 editor.stage_or_unstage_diff_hunks(
24975 true,
24976 vec![hunk_range.start..hunk_range.start],
24977 cx,
24978 );
24979 });
24980 }
24981 })
24982 } else {
24983 Button::new(("unstage", row as u64), "Unstage")
24984 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24985 .tooltip({
24986 let focus_handle = editor.focus_handle(cx);
24987 move |_window, cx| {
24988 Tooltip::for_action_in(
24989 "Unstage Hunk",
24990 &::git::ToggleStaged,
24991 &focus_handle,
24992 cx,
24993 )
24994 }
24995 })
24996 .on_click({
24997 let editor = editor.clone();
24998 move |_event, _window, cx| {
24999 editor.update(cx, |editor, cx| {
25000 editor.stage_or_unstage_diff_hunks(
25001 false,
25002 vec![hunk_range.start..hunk_range.start],
25003 cx,
25004 );
25005 });
25006 }
25007 })
25008 })
25009 .child(
25010 Button::new(("restore", row as u64), "Restore")
25011 .tooltip({
25012 let focus_handle = editor.focus_handle(cx);
25013 move |_window, cx| {
25014 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25015 }
25016 })
25017 .on_click({
25018 let editor = editor.clone();
25019 move |_event, window, cx| {
25020 editor.update(cx, |editor, cx| {
25021 let snapshot = editor.snapshot(window, cx);
25022 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25023 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25024 });
25025 }
25026 })
25027 .disabled(is_created_file),
25028 )
25029 .when(
25030 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25031 |el| {
25032 el.child(
25033 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25034 .shape(IconButtonShape::Square)
25035 .icon_size(IconSize::Small)
25036 // .disabled(!has_multiple_hunks)
25037 .tooltip({
25038 let focus_handle = editor.focus_handle(cx);
25039 move |_window, cx| {
25040 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25041 }
25042 })
25043 .on_click({
25044 let editor = editor.clone();
25045 move |_event, window, cx| {
25046 editor.update(cx, |editor, cx| {
25047 let snapshot = editor.snapshot(window, cx);
25048 let position =
25049 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25050 editor.go_to_hunk_before_or_after_position(
25051 &snapshot,
25052 position,
25053 Direction::Next,
25054 window,
25055 cx,
25056 );
25057 editor.expand_selected_diff_hunks(cx);
25058 });
25059 }
25060 }),
25061 )
25062 .child(
25063 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25064 .shape(IconButtonShape::Square)
25065 .icon_size(IconSize::Small)
25066 // .disabled(!has_multiple_hunks)
25067 .tooltip({
25068 let focus_handle = editor.focus_handle(cx);
25069 move |_window, cx| {
25070 Tooltip::for_action_in(
25071 "Previous Hunk",
25072 &GoToPreviousHunk,
25073 &focus_handle,
25074 cx,
25075 )
25076 }
25077 })
25078 .on_click({
25079 let editor = editor.clone();
25080 move |_event, window, cx| {
25081 editor.update(cx, |editor, cx| {
25082 let snapshot = editor.snapshot(window, cx);
25083 let point =
25084 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25085 editor.go_to_hunk_before_or_after_position(
25086 &snapshot,
25087 point,
25088 Direction::Prev,
25089 window,
25090 cx,
25091 );
25092 editor.expand_selected_diff_hunks(cx);
25093 });
25094 }
25095 }),
25096 )
25097 },
25098 )
25099 .into_any_element()
25100}
25101
25102pub fn multibuffer_context_lines(cx: &App) -> u32 {
25103 EditorSettings::try_get(cx)
25104 .map(|settings| settings.excerpt_context_lines)
25105 .unwrap_or(2)
25106 .min(32)
25107}