1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction_types::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79 status::FileStatus,
80};
81use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
82use anyhow::{Context as _, Result, anyhow, bail};
83use blink_manager::BlinkManager;
84use buffer_diff::DiffHunkStatus;
85use client::{Collaborator, ParticipantIndex, parse_zed_link};
86use clock::ReplicaId;
87use code_context_menus::{
88 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
89 CompletionsMenu, ContextMenuOrigin,
90};
91use collections::{BTreeMap, HashMap, HashSet, VecDeque};
92use convert_case::{Case, Casing};
93use dap::TelemetrySpawnLocation;
94use display_map::*;
95use edit_prediction_types::{
96 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
97};
98use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
99use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
100use futures::{
101 FutureExt, StreamExt as _,
102 future::{self, Shared, join},
103 stream::FuturesUnordered,
104};
105use fuzzy::{StringMatch, StringMatchCandidate};
106use git::blame::{GitBlame, GlobalBlameRenderer};
107use gpui::{
108 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
109 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
110 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
111 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
112 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
113 Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun,
114 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
115 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
116 size,
117};
118use hover_links::{HoverLink, HoveredLinkState, find_file};
119use hover_popover::{HoverState, hide_hover};
120use indent_guides::ActiveIndentGuidesState;
121use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
122use itertools::{Either, Itertools};
123use language::{
124 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
125 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
126 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
127 IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point,
128 Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
129 language_settings::{
130 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
131 all_language_settings, language_settings,
132 },
133 point_from_lsp, point_to_lsp, text_diff_with_options,
134};
135use linked_editing_ranges::refresh_linked_ranges;
136use lsp::{
137 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
138 LanguageServerId,
139};
140use lsp_colors::LspColorData;
141use markdown::Markdown;
142use mouse_context_menu::MouseContextMenu;
143use movement::TextLayoutDetails;
144use multi_buffer::{
145 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
152 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
153 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::GitStoreEvent,
162 lsp_store::{
163 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
164 OpenLspBufferHandle,
165 },
166 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
167};
168use rand::seq::SliceRandom;
169use rpc::{ErrorCode, ErrorExt, proto::PeerId};
170use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
171use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
172use serde::{Deserialize, Serialize};
173use settings::{
174 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
175 update_settings_file,
176};
177use smallvec::{SmallVec, smallvec};
178use snippet::Snippet;
179use std::{
180 any::{Any, TypeId},
181 borrow::Cow,
182 cell::{OnceCell, RefCell},
183 cmp::{self, Ordering, Reverse},
184 collections::hash_map,
185 iter::{self, Peekable},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 sync::Arc,
192 time::{Duration, Instant},
193};
194use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
196use theme::{
197 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use workspace::{
206 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
207 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
208 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
209 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
210 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
211 searchable::SearchEvent,
212};
213
214use crate::{
215 code_context_menus::CompletionsMenuSource,
216 editor_settings::MultiCursorModifier,
217 hover_links::{find_url, find_url_from_range},
218 inlays::{
219 InlineValueCache,
220 inlay_hints::{LspInlayHintData, inlay_hint_settings},
221 },
222 scroll::{ScrollOffset, ScrollPixelOffset},
223 selections_collection::resolve_selections_wrapping_blocks,
224 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
225};
226
227pub const FILE_HEADER_HEIGHT: u32 = 2;
228pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
229const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
230const MAX_LINE_LEN: usize = 1024;
231const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
232const MAX_SELECTION_HISTORY_LEN: usize = 1024;
233pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
234#[doc(hidden)]
235pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
236pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
237
238pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
239pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
241pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
242
243pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
244pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
245pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
246
247pub type RenderDiffHunkControlsFn = Arc<
248 dyn Fn(
249 u32,
250 &DiffHunkStatus,
251 Range<Anchor>,
252 bool,
253 Pixels,
254 &Entity<Editor>,
255 &mut Window,
256 &mut App,
257 ) -> AnyElement,
258>;
259
260enum ReportEditorEvent {
261 Saved { auto_saved: bool },
262 EditorOpened,
263 Closed,
264}
265
266impl ReportEditorEvent {
267 pub fn event_type(&self) -> &'static str {
268 match self {
269 Self::Saved { .. } => "Editor Saved",
270 Self::EditorOpened => "Editor Opened",
271 Self::Closed => "Editor Closed",
272 }
273 }
274}
275
276pub enum ActiveDebugLine {}
277pub enum DebugStackFrameLine {}
278enum DocumentHighlightRead {}
279enum DocumentHighlightWrite {}
280enum InputComposition {}
281pub enum PendingInput {}
282enum SelectedTextHighlight {}
283
284pub enum ConflictsOuter {}
285pub enum ConflictsOurs {}
286pub enum ConflictsTheirs {}
287pub enum ConflictsOursMarker {}
288pub enum ConflictsTheirsMarker {}
289
290pub struct HunkAddedColor;
291pub struct HunkRemovedColor;
292
293#[derive(Debug, Copy, Clone, PartialEq, Eq)]
294pub enum Navigated {
295 Yes,
296 No,
297}
298
299impl Navigated {
300 pub fn from_bool(yes: bool) -> Navigated {
301 if yes { Navigated::Yes } else { Navigated::No }
302 }
303}
304
305#[derive(Debug, Clone, PartialEq, Eq)]
306enum DisplayDiffHunk {
307 Folded {
308 display_row: DisplayRow,
309 },
310 Unfolded {
311 is_created_file: bool,
312 diff_base_byte_range: Range<usize>,
313 display_row_range: Range<DisplayRow>,
314 multi_buffer_range: Range<Anchor>,
315 status: DiffHunkStatus,
316 word_diffs: Vec<Range<MultiBufferOffset>>,
317 },
318}
319
320pub enum HideMouseCursorOrigin {
321 TypingAction,
322 MovementAction,
323}
324
325pub fn init(cx: &mut App) {
326 cx.set_global(GlobalBlameRenderer(Arc::new(())));
327
328 workspace::register_project_item::<Editor>(cx);
329 workspace::FollowableViewRegistry::register::<Editor>(cx);
330 workspace::register_serializable_item::<Editor>(cx);
331
332 cx.observe_new(
333 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
334 workspace.register_action(Editor::new_file);
335 workspace.register_action(Editor::new_file_split);
336 workspace.register_action(Editor::new_file_vertical);
337 workspace.register_action(Editor::new_file_horizontal);
338 workspace.register_action(Editor::cancel_language_server_work);
339 workspace.register_action(Editor::toggle_focus);
340 },
341 )
342 .detach();
343
344 cx.on_action(move |_: &workspace::NewFile, cx| {
345 let app_state = workspace::AppState::global(cx);
346 if let Some(app_state) = app_state.upgrade() {
347 workspace::open_new(
348 Default::default(),
349 app_state,
350 cx,
351 |workspace, window, cx| {
352 Editor::new_file(workspace, &Default::default(), window, cx)
353 },
354 )
355 .detach();
356 }
357 })
358 .on_action(move |_: &workspace::NewWindow, cx| {
359 let app_state = workspace::AppState::global(cx);
360 if let Some(app_state) = app_state.upgrade() {
361 workspace::open_new(
362 Default::default(),
363 app_state,
364 cx,
365 |workspace, window, cx| {
366 cx.activate(true);
367 Editor::new_file(workspace, &Default::default(), window, cx)
368 },
369 )
370 .detach();
371 }
372 });
373}
374
375pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
376 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
377}
378
379pub trait DiagnosticRenderer {
380 fn render_group(
381 &self,
382 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
383 buffer_id: BufferId,
384 snapshot: EditorSnapshot,
385 editor: WeakEntity<Editor>,
386 language_registry: Option<Arc<LanguageRegistry>>,
387 cx: &mut App,
388 ) -> Vec<BlockProperties<Anchor>>;
389
390 fn render_hover(
391 &self,
392 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
393 range: Range<Point>,
394 buffer_id: BufferId,
395 language_registry: Option<Arc<LanguageRegistry>>,
396 cx: &mut App,
397 ) -> Option<Entity<markdown::Markdown>>;
398
399 fn open_link(
400 &self,
401 editor: &mut Editor,
402 link: SharedString,
403 window: &mut Window,
404 cx: &mut Context<Editor>,
405 );
406}
407
408pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
409
410impl GlobalDiagnosticRenderer {
411 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
412 cx.try_global::<Self>().map(|g| g.0.clone())
413 }
414}
415
416impl gpui::Global for GlobalDiagnosticRenderer {}
417pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
418 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
419}
420
421pub struct SearchWithinRange;
422
423trait InvalidationRegion {
424 fn ranges(&self) -> &[Range<Anchor>];
425}
426
427#[derive(Clone, Debug, PartialEq)]
428pub enum SelectPhase {
429 Begin {
430 position: DisplayPoint,
431 add: bool,
432 click_count: usize,
433 },
434 BeginColumnar {
435 position: DisplayPoint,
436 reset: bool,
437 mode: ColumnarMode,
438 goal_column: u32,
439 },
440 Extend {
441 position: DisplayPoint,
442 click_count: usize,
443 },
444 Update {
445 position: DisplayPoint,
446 goal_column: u32,
447 scroll_delta: gpui::Point<f32>,
448 },
449 End,
450}
451
452#[derive(Clone, Debug, PartialEq)]
453pub enum ColumnarMode {
454 FromMouse,
455 FromSelection,
456}
457
458#[derive(Clone, Debug)]
459pub enum SelectMode {
460 Character,
461 Word(Range<Anchor>),
462 Line(Range<Anchor>),
463 All,
464}
465
466#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
467pub enum SizingBehavior {
468 /// The editor will layout itself using `size_full` and will include the vertical
469 /// scroll margin as requested by user settings.
470 #[default]
471 Default,
472 /// The editor will layout itself using `size_full`, but will not have any
473 /// vertical overscroll.
474 ExcludeOverscrollMargin,
475 /// The editor will request a vertical size according to its content and will be
476 /// layouted without a vertical scroll margin.
477 SizeByContent,
478}
479
480#[derive(Clone, PartialEq, Eq, Debug)]
481pub enum EditorMode {
482 SingleLine,
483 AutoHeight {
484 min_lines: usize,
485 max_lines: Option<usize>,
486 },
487 Full {
488 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
489 scale_ui_elements_with_buffer_font_size: bool,
490 /// When set to `true`, the editor will render a background for the active line.
491 show_active_line_background: bool,
492 /// Determines the sizing behavior for this editor
493 sizing_behavior: SizingBehavior,
494 },
495 Minimap {
496 parent: WeakEntity<Editor>,
497 },
498}
499
500impl EditorMode {
501 pub fn full() -> Self {
502 Self::Full {
503 scale_ui_elements_with_buffer_font_size: true,
504 show_active_line_background: true,
505 sizing_behavior: SizingBehavior::Default,
506 }
507 }
508
509 #[inline]
510 pub fn is_full(&self) -> bool {
511 matches!(self, Self::Full { .. })
512 }
513
514 #[inline]
515 pub fn is_single_line(&self) -> bool {
516 matches!(self, Self::SingleLine { .. })
517 }
518
519 #[inline]
520 fn is_minimap(&self) -> bool {
521 matches!(self, Self::Minimap { .. })
522 }
523}
524
525#[derive(Copy, Clone, Debug)]
526pub enum SoftWrap {
527 /// Prefer not to wrap at all.
528 ///
529 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
530 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
531 GitDiff,
532 /// Prefer a single line generally, unless an overly long line is encountered.
533 None,
534 /// Soft wrap lines that exceed the editor width.
535 EditorWidth,
536 /// Soft wrap lines at the preferred line length.
537 Column(u32),
538 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
539 Bounded(u32),
540}
541
542#[derive(Clone)]
543pub struct EditorStyle {
544 pub background: Hsla,
545 pub border: Hsla,
546 pub local_player: PlayerColor,
547 pub text: TextStyle,
548 pub scrollbar_width: Pixels,
549 pub syntax: Arc<SyntaxTheme>,
550 pub status: StatusColors,
551 pub inlay_hints_style: HighlightStyle,
552 pub edit_prediction_styles: EditPredictionStyles,
553 pub unnecessary_code_fade: f32,
554 pub show_underlines: bool,
555}
556
557impl Default for EditorStyle {
558 fn default() -> Self {
559 Self {
560 background: Hsla::default(),
561 border: Hsla::default(),
562 local_player: PlayerColor::default(),
563 text: TextStyle::default(),
564 scrollbar_width: Pixels::default(),
565 syntax: Default::default(),
566 // HACK: Status colors don't have a real default.
567 // We should look into removing the status colors from the editor
568 // style and retrieve them directly from the theme.
569 status: StatusColors::dark(),
570 inlay_hints_style: HighlightStyle::default(),
571 edit_prediction_styles: EditPredictionStyles {
572 insertion: HighlightStyle::default(),
573 whitespace: HighlightStyle::default(),
574 },
575 unnecessary_code_fade: Default::default(),
576 show_underlines: true,
577 }
578 }
579}
580
581pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
582 let show_background = language_settings::language_settings(None, None, cx)
583 .inlay_hints
584 .show_background;
585
586 let mut style = cx.theme().syntax().get("hint");
587
588 if style.color.is_none() {
589 style.color = Some(cx.theme().status().hint);
590 }
591
592 if !show_background {
593 style.background_color = None;
594 return style;
595 }
596
597 if style.background_color.is_none() {
598 style.background_color = Some(cx.theme().status().hint_background);
599 }
600
601 style
602}
603
604pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
605 EditPredictionStyles {
606 insertion: HighlightStyle {
607 color: Some(cx.theme().status().predictive),
608 ..HighlightStyle::default()
609 },
610 whitespace: HighlightStyle {
611 background_color: Some(cx.theme().status().created_background),
612 ..HighlightStyle::default()
613 },
614 }
615}
616
617type CompletionId = usize;
618
619pub(crate) enum EditDisplayMode {
620 TabAccept,
621 DiffPopover,
622 Inline,
623}
624
625enum EditPrediction {
626 Edit {
627 edits: Vec<(Range<Anchor>, Arc<str>)>,
628 edit_preview: Option<EditPreview>,
629 display_mode: EditDisplayMode,
630 snapshot: BufferSnapshot,
631 },
632 /// Move to a specific location in the active editor
633 MoveWithin {
634 target: Anchor,
635 snapshot: BufferSnapshot,
636 },
637 /// Move to a specific location in a different editor (not the active one)
638 MoveOutside {
639 target: language::Anchor,
640 snapshot: BufferSnapshot,
641 },
642}
643
644struct EditPredictionState {
645 inlay_ids: Vec<InlayId>,
646 completion: EditPrediction,
647 completion_id: Option<SharedString>,
648 invalidation_range: Option<Range<Anchor>>,
649}
650
651enum EditPredictionSettings {
652 Disabled,
653 Enabled {
654 show_in_menu: bool,
655 preview_requires_modifier: bool,
656 },
657}
658
659enum EditPredictionHighlight {}
660
661#[derive(Debug, Clone)]
662struct InlineDiagnostic {
663 message: SharedString,
664 group_id: usize,
665 is_primary: bool,
666 start: Point,
667 severity: lsp::DiagnosticSeverity,
668}
669
670pub enum MenuEditPredictionsPolicy {
671 Never,
672 ByProvider,
673}
674
675pub enum EditPredictionPreview {
676 /// Modifier is not pressed
677 Inactive { released_too_fast: bool },
678 /// Modifier pressed
679 Active {
680 since: Instant,
681 previous_scroll_position: Option<ScrollAnchor>,
682 },
683}
684
685impl EditPredictionPreview {
686 pub fn released_too_fast(&self) -> bool {
687 match self {
688 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
689 EditPredictionPreview::Active { .. } => false,
690 }
691 }
692
693 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
694 if let EditPredictionPreview::Active {
695 previous_scroll_position,
696 ..
697 } = self
698 {
699 *previous_scroll_position = scroll_position;
700 }
701 }
702}
703
704pub struct ContextMenuOptions {
705 pub min_entries_visible: usize,
706 pub max_entries_visible: usize,
707 pub placement: Option<ContextMenuPlacement>,
708}
709
710#[derive(Debug, Clone, PartialEq, Eq)]
711pub enum ContextMenuPlacement {
712 Above,
713 Below,
714}
715
716#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
717struct EditorActionId(usize);
718
719impl EditorActionId {
720 pub fn post_inc(&mut self) -> Self {
721 let answer = self.0;
722
723 *self = Self(answer + 1);
724
725 Self(answer)
726 }
727}
728
729// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
730// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
731
732type BackgroundHighlight = (
733 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
734 Arc<[Range<Anchor>]>,
735);
736type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
737
738#[derive(Default)]
739struct ScrollbarMarkerState {
740 scrollbar_size: Size<Pixels>,
741 dirty: bool,
742 markers: Arc<[PaintQuad]>,
743 pending_refresh: Option<Task<Result<()>>>,
744}
745
746impl ScrollbarMarkerState {
747 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
748 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
749 }
750}
751
752#[derive(Clone, Copy, PartialEq, Eq)]
753pub enum MinimapVisibility {
754 Disabled,
755 Enabled {
756 /// The configuration currently present in the users settings.
757 setting_configuration: bool,
758 /// Whether to override the currently set visibility from the users setting.
759 toggle_override: bool,
760 },
761}
762
763impl MinimapVisibility {
764 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
765 if mode.is_full() {
766 Self::Enabled {
767 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
768 toggle_override: false,
769 }
770 } else {
771 Self::Disabled
772 }
773 }
774
775 fn hidden(&self) -> Self {
776 match *self {
777 Self::Enabled {
778 setting_configuration,
779 ..
780 } => Self::Enabled {
781 setting_configuration,
782 toggle_override: setting_configuration,
783 },
784 Self::Disabled => Self::Disabled,
785 }
786 }
787
788 fn disabled(&self) -> bool {
789 matches!(*self, Self::Disabled)
790 }
791
792 fn settings_visibility(&self) -> bool {
793 match *self {
794 Self::Enabled {
795 setting_configuration,
796 ..
797 } => setting_configuration,
798 _ => false,
799 }
800 }
801
802 fn visible(&self) -> bool {
803 match *self {
804 Self::Enabled {
805 setting_configuration,
806 toggle_override,
807 } => setting_configuration ^ toggle_override,
808 _ => false,
809 }
810 }
811
812 fn toggle_visibility(&self) -> Self {
813 match *self {
814 Self::Enabled {
815 toggle_override,
816 setting_configuration,
817 } => Self::Enabled {
818 setting_configuration,
819 toggle_override: !toggle_override,
820 },
821 Self::Disabled => Self::Disabled,
822 }
823 }
824}
825
826#[derive(Debug, Clone, Copy, PartialEq, Eq)]
827pub enum BufferSerialization {
828 All,
829 NonDirtyBuffers,
830}
831
832impl BufferSerialization {
833 fn new(restore_unsaved_buffers: bool) -> Self {
834 if restore_unsaved_buffers {
835 Self::All
836 } else {
837 Self::NonDirtyBuffers
838 }
839 }
840}
841
842#[derive(Clone, Debug)]
843struct RunnableTasks {
844 templates: Vec<(TaskSourceKind, TaskTemplate)>,
845 offset: multi_buffer::Anchor,
846 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
847 column: u32,
848 // Values of all named captures, including those starting with '_'
849 extra_variables: HashMap<String, String>,
850 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
851 context_range: Range<BufferOffset>,
852}
853
854impl RunnableTasks {
855 fn resolve<'a>(
856 &'a self,
857 cx: &'a task::TaskContext,
858 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
859 self.templates.iter().filter_map(|(kind, template)| {
860 template
861 .resolve_task(&kind.to_id_base(), cx)
862 .map(|task| (kind.clone(), task))
863 })
864 }
865}
866
867#[derive(Clone)]
868pub struct ResolvedTasks {
869 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
870 position: Anchor,
871}
872
873/// Addons allow storing per-editor state in other crates (e.g. Vim)
874pub trait Addon: 'static {
875 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
876
877 fn render_buffer_header_controls(
878 &self,
879 _: &ExcerptInfo,
880 _: &Window,
881 _: &App,
882 ) -> Option<AnyElement> {
883 None
884 }
885
886 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
887 None
888 }
889
890 fn to_any(&self) -> &dyn std::any::Any;
891
892 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
893 None
894 }
895}
896
897struct ChangeLocation {
898 current: Option<Vec<Anchor>>,
899 original: Vec<Anchor>,
900}
901impl ChangeLocation {
902 fn locations(&self) -> &[Anchor] {
903 self.current.as_ref().unwrap_or(&self.original)
904 }
905}
906
907/// A set of caret positions, registered when the editor was edited.
908pub struct ChangeList {
909 changes: Vec<ChangeLocation>,
910 /// Currently "selected" change.
911 position: Option<usize>,
912}
913
914impl ChangeList {
915 pub fn new() -> Self {
916 Self {
917 changes: Vec::new(),
918 position: None,
919 }
920 }
921
922 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
923 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
924 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
925 if self.changes.is_empty() {
926 return None;
927 }
928
929 let prev = self.position.unwrap_or(self.changes.len());
930 let next = if direction == Direction::Prev {
931 prev.saturating_sub(count)
932 } else {
933 (prev + count).min(self.changes.len() - 1)
934 };
935 self.position = Some(next);
936 self.changes.get(next).map(|change| change.locations())
937 }
938
939 /// Adds a new change to the list, resetting the change list position.
940 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
941 self.position.take();
942 if let Some(last) = self.changes.last_mut()
943 && group
944 {
945 last.current = Some(new_positions)
946 } else {
947 self.changes.push(ChangeLocation {
948 original: new_positions,
949 current: None,
950 });
951 }
952 }
953
954 pub fn last(&self) -> Option<&[Anchor]> {
955 self.changes.last().map(|change| change.locations())
956 }
957
958 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
959 self.changes.last().map(|change| change.original.as_slice())
960 }
961
962 pub fn invert_last_group(&mut self) {
963 if let Some(last) = self.changes.last_mut()
964 && let Some(current) = last.current.as_mut()
965 {
966 mem::swap(&mut last.original, current);
967 }
968 }
969}
970
971#[derive(Clone)]
972struct InlineBlamePopoverState {
973 scroll_handle: ScrollHandle,
974 commit_message: Option<ParsedCommitMessage>,
975 markdown: Entity<Markdown>,
976}
977
978struct InlineBlamePopover {
979 position: gpui::Point<Pixels>,
980 hide_task: Option<Task<()>>,
981 popover_bounds: Option<Bounds<Pixels>>,
982 popover_state: InlineBlamePopoverState,
983 keyboard_grace: bool,
984}
985
986enum SelectionDragState {
987 /// State when no drag related activity is detected.
988 None,
989 /// State when the mouse is down on a selection that is about to be dragged.
990 ReadyToDrag {
991 selection: Selection<Anchor>,
992 click_position: gpui::Point<Pixels>,
993 mouse_down_time: Instant,
994 },
995 /// State when the mouse is dragging the selection in the editor.
996 Dragging {
997 selection: Selection<Anchor>,
998 drop_cursor: Selection<Anchor>,
999 hide_drop_cursor: bool,
1000 },
1001}
1002
1003enum ColumnarSelectionState {
1004 FromMouse {
1005 selection_tail: Anchor,
1006 display_point: Option<DisplayPoint>,
1007 },
1008 FromSelection {
1009 selection_tail: Anchor,
1010 },
1011}
1012
1013/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1014/// a breakpoint on them.
1015#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1016struct PhantomBreakpointIndicator {
1017 display_row: DisplayRow,
1018 /// There's a small debounce between hovering over the line and showing the indicator.
1019 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1020 is_active: bool,
1021 collides_with_existing_breakpoint: bool,
1022}
1023
1024/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1025///
1026/// See the [module level documentation](self) for more information.
1027pub struct Editor {
1028 focus_handle: FocusHandle,
1029 last_focused_descendant: Option<WeakFocusHandle>,
1030 /// The text buffer being edited
1031 buffer: Entity<MultiBuffer>,
1032 /// Map of how text in the buffer should be displayed.
1033 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1034 pub display_map: Entity<DisplayMap>,
1035 placeholder_display_map: Option<Entity<DisplayMap>>,
1036 pub selections: SelectionsCollection,
1037 pub scroll_manager: ScrollManager,
1038 /// When inline assist editors are linked, they all render cursors because
1039 /// typing enters text into each of them, even the ones that aren't focused.
1040 pub(crate) show_cursor_when_unfocused: bool,
1041 columnar_selection_state: Option<ColumnarSelectionState>,
1042 add_selections_state: Option<AddSelectionsState>,
1043 select_next_state: Option<SelectNextState>,
1044 select_prev_state: Option<SelectNextState>,
1045 selection_history: SelectionHistory,
1046 defer_selection_effects: bool,
1047 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1048 autoclose_regions: Vec<AutocloseRegion>,
1049 snippet_stack: InvalidationStack<SnippetState>,
1050 select_syntax_node_history: SelectSyntaxNodeHistory,
1051 ime_transaction: Option<TransactionId>,
1052 pub diagnostics_max_severity: DiagnosticSeverity,
1053 active_diagnostics: ActiveDiagnostic,
1054 show_inline_diagnostics: bool,
1055 inline_diagnostics_update: Task<()>,
1056 inline_diagnostics_enabled: bool,
1057 diagnostics_enabled: bool,
1058 word_completions_enabled: bool,
1059 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1060 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1061 hard_wrap: Option<usize>,
1062 project: Option<Entity<Project>>,
1063 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1064 completion_provider: Option<Rc<dyn CompletionProvider>>,
1065 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1066 blink_manager: Entity<BlinkManager>,
1067 show_cursor_names: bool,
1068 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1069 pub show_local_selections: bool,
1070 mode: EditorMode,
1071 show_breadcrumbs: bool,
1072 show_gutter: bool,
1073 show_scrollbars: ScrollbarAxes,
1074 minimap_visibility: MinimapVisibility,
1075 offset_content: bool,
1076 disable_expand_excerpt_buttons: bool,
1077 show_line_numbers: Option<bool>,
1078 use_relative_line_numbers: Option<bool>,
1079 show_git_diff_gutter: Option<bool>,
1080 show_code_actions: Option<bool>,
1081 show_runnables: Option<bool>,
1082 show_breakpoints: Option<bool>,
1083 show_wrap_guides: Option<bool>,
1084 show_indent_guides: Option<bool>,
1085 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1086 highlight_order: usize,
1087 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1088 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1089 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1090 scrollbar_marker_state: ScrollbarMarkerState,
1091 active_indent_guides_state: ActiveIndentGuidesState,
1092 nav_history: Option<ItemNavHistory>,
1093 context_menu: RefCell<Option<CodeContextMenu>>,
1094 context_menu_options: Option<ContextMenuOptions>,
1095 mouse_context_menu: Option<MouseContextMenu>,
1096 completion_tasks: Vec<(CompletionId, Task<()>)>,
1097 inline_blame_popover: Option<InlineBlamePopover>,
1098 inline_blame_popover_show_task: Option<Task<()>>,
1099 signature_help_state: SignatureHelpState,
1100 auto_signature_help: Option<bool>,
1101 find_all_references_task_sources: Vec<Anchor>,
1102 next_completion_id: CompletionId,
1103 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1104 code_actions_task: Option<Task<Result<()>>>,
1105 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1106 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1107 document_highlights_task: Option<Task<()>>,
1108 linked_editing_range_task: Option<Task<Option<()>>>,
1109 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1110 pending_rename: Option<RenameState>,
1111 searchable: bool,
1112 cursor_shape: CursorShape,
1113 /// Whether the cursor is offset one character to the left when something is
1114 /// selected (needed for vim visual mode)
1115 cursor_offset_on_selection: bool,
1116 current_line_highlight: Option<CurrentLineHighlight>,
1117 pub collapse_matches: bool,
1118 autoindent_mode: Option<AutoindentMode>,
1119 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1120 input_enabled: bool,
1121 use_modal_editing: bool,
1122 read_only: bool,
1123 leader_id: Option<CollaboratorId>,
1124 remote_id: Option<ViewId>,
1125 pub hover_state: HoverState,
1126 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1127 prev_pressure_stage: Option<PressureStage>,
1128 gutter_hovered: bool,
1129 hovered_link_state: Option<HoveredLinkState>,
1130 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1131 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1132 active_edit_prediction: Option<EditPredictionState>,
1133 /// Used to prevent flickering as the user types while the menu is open
1134 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1135 edit_prediction_settings: EditPredictionSettings,
1136 edit_predictions_hidden_for_vim_mode: bool,
1137 show_edit_predictions_override: Option<bool>,
1138 show_completions_on_input_override: Option<bool>,
1139 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1140 edit_prediction_preview: EditPredictionPreview,
1141 edit_prediction_indent_conflict: bool,
1142 edit_prediction_requires_modifier_in_indent_conflict: bool,
1143 next_inlay_id: usize,
1144 next_color_inlay_id: usize,
1145 _subscriptions: Vec<Subscription>,
1146 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1147 gutter_dimensions: GutterDimensions,
1148 style: Option<EditorStyle>,
1149 text_style_refinement: Option<TextStyleRefinement>,
1150 next_editor_action_id: EditorActionId,
1151 editor_actions: Rc<
1152 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1153 >,
1154 use_autoclose: bool,
1155 use_auto_surround: bool,
1156 auto_replace_emoji_shortcode: bool,
1157 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1158 show_git_blame_gutter: bool,
1159 show_git_blame_inline: bool,
1160 show_git_blame_inline_delay_task: Option<Task<()>>,
1161 git_blame_inline_enabled: bool,
1162 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1163 buffer_serialization: Option<BufferSerialization>,
1164 show_selection_menu: Option<bool>,
1165 blame: Option<Entity<GitBlame>>,
1166 blame_subscription: Option<Subscription>,
1167 custom_context_menu: Option<
1168 Box<
1169 dyn 'static
1170 + Fn(
1171 &mut Self,
1172 DisplayPoint,
1173 &mut Window,
1174 &mut Context<Self>,
1175 ) -> Option<Entity<ui::ContextMenu>>,
1176 >,
1177 >,
1178 last_bounds: Option<Bounds<Pixels>>,
1179 last_position_map: Option<Rc<PositionMap>>,
1180 expect_bounds_change: Option<Bounds<Pixels>>,
1181 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1182 tasks_update_task: Option<Task<()>>,
1183 breakpoint_store: Option<Entity<BreakpointStore>>,
1184 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1185 hovered_diff_hunk_row: Option<DisplayRow>,
1186 pull_diagnostics_task: Task<()>,
1187 pull_diagnostics_background_task: Task<()>,
1188 in_project_search: bool,
1189 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1190 breadcrumb_header: Option<String>,
1191 focused_block: Option<FocusedBlock>,
1192 next_scroll_position: NextScrollCursorCenterTopBottom,
1193 addons: HashMap<TypeId, Box<dyn Addon>>,
1194 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1195 load_diff_task: Option<Shared<Task<()>>>,
1196 /// Whether we are temporarily displaying a diff other than git's
1197 temporary_diff_override: bool,
1198 selection_mark_mode: bool,
1199 toggle_fold_multiple_buffers: Task<()>,
1200 _scroll_cursor_center_top_bottom_task: Task<()>,
1201 serialize_selections: Task<()>,
1202 serialize_folds: Task<()>,
1203 mouse_cursor_hidden: bool,
1204 minimap: Option<Entity<Self>>,
1205 hide_mouse_mode: HideMouseMode,
1206 pub change_list: ChangeList,
1207 inline_value_cache: InlineValueCache,
1208
1209 selection_drag_state: SelectionDragState,
1210 colors: Option<LspColorData>,
1211 post_scroll_update: Task<()>,
1212 refresh_colors_task: Task<()>,
1213 inlay_hints: Option<LspInlayHintData>,
1214 folding_newlines: Task<()>,
1215 select_next_is_case_sensitive: Option<bool>,
1216 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1217 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1218 accent_data: Option<AccentData>,
1219 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1220 use_base_text_line_numbers: bool,
1221}
1222
1223#[derive(Debug, PartialEq)]
1224struct AccentData {
1225 colors: AccentColors,
1226 overrides: Vec<SharedString>,
1227}
1228
1229fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1230 if debounce_ms > 0 {
1231 Some(Duration::from_millis(debounce_ms))
1232 } else {
1233 None
1234 }
1235}
1236
1237#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1238enum NextScrollCursorCenterTopBottom {
1239 #[default]
1240 Center,
1241 Top,
1242 Bottom,
1243}
1244
1245impl NextScrollCursorCenterTopBottom {
1246 fn next(&self) -> Self {
1247 match self {
1248 Self::Center => Self::Top,
1249 Self::Top => Self::Bottom,
1250 Self::Bottom => Self::Center,
1251 }
1252 }
1253}
1254
1255#[derive(Clone)]
1256pub struct EditorSnapshot {
1257 pub mode: EditorMode,
1258 show_gutter: bool,
1259 offset_content: bool,
1260 show_line_numbers: Option<bool>,
1261 show_git_diff_gutter: Option<bool>,
1262 show_code_actions: Option<bool>,
1263 show_runnables: Option<bool>,
1264 show_breakpoints: Option<bool>,
1265 git_blame_gutter_max_author_length: Option<usize>,
1266 pub display_snapshot: DisplaySnapshot,
1267 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1268 is_focused: bool,
1269 scroll_anchor: ScrollAnchor,
1270 ongoing_scroll: OngoingScroll,
1271 current_line_highlight: CurrentLineHighlight,
1272 gutter_hovered: bool,
1273}
1274
1275#[derive(Default, Debug, Clone, Copy)]
1276pub struct GutterDimensions {
1277 pub left_padding: Pixels,
1278 pub right_padding: Pixels,
1279 pub width: Pixels,
1280 pub margin: Pixels,
1281 pub git_blame_entries_width: Option<Pixels>,
1282}
1283
1284impl GutterDimensions {
1285 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1286 Self {
1287 margin: Self::default_gutter_margin(font_id, font_size, cx),
1288 ..Default::default()
1289 }
1290 }
1291
1292 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1293 -cx.text_system().descent(font_id, font_size)
1294 }
1295 /// The full width of the space taken up by the gutter.
1296 pub fn full_width(&self) -> Pixels {
1297 self.margin + self.width
1298 }
1299
1300 /// The width of the space reserved for the fold indicators,
1301 /// use alongside 'justify_end' and `gutter_width` to
1302 /// right align content with the line numbers
1303 pub fn fold_area_width(&self) -> Pixels {
1304 self.margin + self.right_padding
1305 }
1306}
1307
1308struct CharacterDimensions {
1309 em_width: Pixels,
1310 em_advance: Pixels,
1311 line_height: Pixels,
1312}
1313
1314#[derive(Debug)]
1315pub struct RemoteSelection {
1316 pub replica_id: ReplicaId,
1317 pub selection: Selection<Anchor>,
1318 pub cursor_shape: CursorShape,
1319 pub collaborator_id: CollaboratorId,
1320 pub line_mode: bool,
1321 pub user_name: Option<SharedString>,
1322 pub color: PlayerColor,
1323}
1324
1325#[derive(Clone, Debug)]
1326struct SelectionHistoryEntry {
1327 selections: Arc<[Selection<Anchor>]>,
1328 select_next_state: Option<SelectNextState>,
1329 select_prev_state: Option<SelectNextState>,
1330 add_selections_state: Option<AddSelectionsState>,
1331}
1332
1333#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1334enum SelectionHistoryMode {
1335 #[default]
1336 Normal,
1337 Undoing,
1338 Redoing,
1339 Skipping,
1340}
1341
1342#[derive(Clone, PartialEq, Eq, Hash)]
1343struct HoveredCursor {
1344 replica_id: ReplicaId,
1345 selection_id: usize,
1346}
1347
1348#[derive(Debug)]
1349/// SelectionEffects controls the side-effects of updating the selection.
1350///
1351/// The default behaviour does "what you mostly want":
1352/// - it pushes to the nav history if the cursor moved by >10 lines
1353/// - it re-triggers completion requests
1354/// - it scrolls to fit
1355///
1356/// You might want to modify these behaviours. For example when doing a "jump"
1357/// like go to definition, we always want to add to nav history; but when scrolling
1358/// in vim mode we never do.
1359///
1360/// Similarly, you might want to disable scrolling if you don't want the viewport to
1361/// move.
1362#[derive(Clone)]
1363pub struct SelectionEffects {
1364 nav_history: Option<bool>,
1365 completions: bool,
1366 scroll: Option<Autoscroll>,
1367}
1368
1369impl Default for SelectionEffects {
1370 fn default() -> Self {
1371 Self {
1372 nav_history: None,
1373 completions: true,
1374 scroll: Some(Autoscroll::fit()),
1375 }
1376 }
1377}
1378impl SelectionEffects {
1379 pub fn scroll(scroll: Autoscroll) -> Self {
1380 Self {
1381 scroll: Some(scroll),
1382 ..Default::default()
1383 }
1384 }
1385
1386 pub fn no_scroll() -> Self {
1387 Self {
1388 scroll: None,
1389 ..Default::default()
1390 }
1391 }
1392
1393 pub fn completions(self, completions: bool) -> Self {
1394 Self {
1395 completions,
1396 ..self
1397 }
1398 }
1399
1400 pub fn nav_history(self, nav_history: bool) -> Self {
1401 Self {
1402 nav_history: Some(nav_history),
1403 ..self
1404 }
1405 }
1406}
1407
1408struct DeferredSelectionEffectsState {
1409 changed: bool,
1410 effects: SelectionEffects,
1411 old_cursor_position: Anchor,
1412 history_entry: SelectionHistoryEntry,
1413}
1414
1415#[derive(Default)]
1416struct SelectionHistory {
1417 #[allow(clippy::type_complexity)]
1418 selections_by_transaction:
1419 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1420 mode: SelectionHistoryMode,
1421 undo_stack: VecDeque<SelectionHistoryEntry>,
1422 redo_stack: VecDeque<SelectionHistoryEntry>,
1423}
1424
1425impl SelectionHistory {
1426 #[track_caller]
1427 fn insert_transaction(
1428 &mut self,
1429 transaction_id: TransactionId,
1430 selections: Arc<[Selection<Anchor>]>,
1431 ) {
1432 if selections.is_empty() {
1433 log::error!(
1434 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1435 std::panic::Location::caller()
1436 );
1437 return;
1438 }
1439 self.selections_by_transaction
1440 .insert(transaction_id, (selections, None));
1441 }
1442
1443 #[allow(clippy::type_complexity)]
1444 fn transaction(
1445 &self,
1446 transaction_id: TransactionId,
1447 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1448 self.selections_by_transaction.get(&transaction_id)
1449 }
1450
1451 #[allow(clippy::type_complexity)]
1452 fn transaction_mut(
1453 &mut self,
1454 transaction_id: TransactionId,
1455 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1456 self.selections_by_transaction.get_mut(&transaction_id)
1457 }
1458
1459 fn push(&mut self, entry: SelectionHistoryEntry) {
1460 if !entry.selections.is_empty() {
1461 match self.mode {
1462 SelectionHistoryMode::Normal => {
1463 self.push_undo(entry);
1464 self.redo_stack.clear();
1465 }
1466 SelectionHistoryMode::Undoing => self.push_redo(entry),
1467 SelectionHistoryMode::Redoing => self.push_undo(entry),
1468 SelectionHistoryMode::Skipping => {}
1469 }
1470 }
1471 }
1472
1473 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1474 if self
1475 .undo_stack
1476 .back()
1477 .is_none_or(|e| e.selections != entry.selections)
1478 {
1479 self.undo_stack.push_back(entry);
1480 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1481 self.undo_stack.pop_front();
1482 }
1483 }
1484 }
1485
1486 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1487 if self
1488 .redo_stack
1489 .back()
1490 .is_none_or(|e| e.selections != entry.selections)
1491 {
1492 self.redo_stack.push_back(entry);
1493 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1494 self.redo_stack.pop_front();
1495 }
1496 }
1497 }
1498}
1499
1500#[derive(Clone, Copy)]
1501pub struct RowHighlightOptions {
1502 pub autoscroll: bool,
1503 pub include_gutter: bool,
1504}
1505
1506impl Default for RowHighlightOptions {
1507 fn default() -> Self {
1508 Self {
1509 autoscroll: Default::default(),
1510 include_gutter: true,
1511 }
1512 }
1513}
1514
1515struct RowHighlight {
1516 index: usize,
1517 range: Range<Anchor>,
1518 color: Hsla,
1519 options: RowHighlightOptions,
1520 type_id: TypeId,
1521}
1522
1523#[derive(Clone, Debug)]
1524struct AddSelectionsState {
1525 groups: Vec<AddSelectionsGroup>,
1526}
1527
1528#[derive(Clone, Debug)]
1529struct AddSelectionsGroup {
1530 above: bool,
1531 stack: Vec<usize>,
1532}
1533
1534#[derive(Clone)]
1535struct SelectNextState {
1536 query: AhoCorasick,
1537 wordwise: bool,
1538 done: bool,
1539}
1540
1541impl std::fmt::Debug for SelectNextState {
1542 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1543 f.debug_struct(std::any::type_name::<Self>())
1544 .field("wordwise", &self.wordwise)
1545 .field("done", &self.done)
1546 .finish()
1547 }
1548}
1549
1550#[derive(Debug)]
1551struct AutocloseRegion {
1552 selection_id: usize,
1553 range: Range<Anchor>,
1554 pair: BracketPair,
1555}
1556
1557#[derive(Debug)]
1558struct SnippetState {
1559 ranges: Vec<Vec<Range<Anchor>>>,
1560 active_index: usize,
1561 choices: Vec<Option<Vec<String>>>,
1562}
1563
1564#[doc(hidden)]
1565pub struct RenameState {
1566 pub range: Range<Anchor>,
1567 pub old_name: Arc<str>,
1568 pub editor: Entity<Editor>,
1569 block_id: CustomBlockId,
1570}
1571
1572struct InvalidationStack<T>(Vec<T>);
1573
1574struct RegisteredEditPredictionDelegate {
1575 provider: Arc<dyn EditPredictionDelegateHandle>,
1576 _subscription: Subscription,
1577}
1578
1579#[derive(Debug, PartialEq, Eq)]
1580pub struct ActiveDiagnosticGroup {
1581 pub active_range: Range<Anchor>,
1582 pub active_message: String,
1583 pub group_id: usize,
1584 pub blocks: HashSet<CustomBlockId>,
1585}
1586
1587#[derive(Debug, PartialEq, Eq)]
1588
1589pub(crate) enum ActiveDiagnostic {
1590 None,
1591 All,
1592 Group(ActiveDiagnosticGroup),
1593}
1594
1595#[derive(Serialize, Deserialize, Clone, Debug)]
1596pub struct ClipboardSelection {
1597 /// The number of bytes in this selection.
1598 pub len: usize,
1599 /// Whether this was a full-line selection.
1600 pub is_entire_line: bool,
1601 /// The indentation of the first line when this content was originally copied.
1602 pub first_line_indent: u32,
1603 #[serde(default)]
1604 pub file_path: Option<PathBuf>,
1605 #[serde(default)]
1606 pub line_range: Option<RangeInclusive<u32>>,
1607}
1608
1609impl ClipboardSelection {
1610 pub fn for_buffer(
1611 len: usize,
1612 is_entire_line: bool,
1613 range: Range<Point>,
1614 buffer: &MultiBufferSnapshot,
1615 project: Option<&Entity<Project>>,
1616 cx: &App,
1617 ) -> Self {
1618 let first_line_indent = buffer
1619 .indent_size_for_line(MultiBufferRow(range.start.row))
1620 .len;
1621
1622 let file_path = util::maybe!({
1623 let project = project?.read(cx);
1624 let file = buffer.file_at(range.start)?;
1625 let project_path = ProjectPath {
1626 worktree_id: file.worktree_id(cx),
1627 path: file.path().clone(),
1628 };
1629 project.absolute_path(&project_path, cx)
1630 });
1631
1632 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1633
1634 Self {
1635 len,
1636 is_entire_line,
1637 first_line_indent,
1638 file_path,
1639 line_range,
1640 }
1641 }
1642}
1643
1644// selections, scroll behavior, was newest selection reversed
1645type SelectSyntaxNodeHistoryState = (
1646 Box<[Selection<MultiBufferOffset>]>,
1647 SelectSyntaxNodeScrollBehavior,
1648 bool,
1649);
1650
1651#[derive(Default)]
1652struct SelectSyntaxNodeHistory {
1653 stack: Vec<SelectSyntaxNodeHistoryState>,
1654 // disable temporarily to allow changing selections without losing the stack
1655 pub disable_clearing: bool,
1656}
1657
1658impl SelectSyntaxNodeHistory {
1659 pub fn try_clear(&mut self) {
1660 if !self.disable_clearing {
1661 self.stack.clear();
1662 }
1663 }
1664
1665 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1666 self.stack.push(selection);
1667 }
1668
1669 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1670 self.stack.pop()
1671 }
1672}
1673
1674enum SelectSyntaxNodeScrollBehavior {
1675 CursorTop,
1676 FitSelection,
1677 CursorBottom,
1678}
1679
1680#[derive(Debug)]
1681pub(crate) struct NavigationData {
1682 cursor_anchor: Anchor,
1683 cursor_position: Point,
1684 scroll_anchor: ScrollAnchor,
1685 scroll_top_row: u32,
1686}
1687
1688#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1689pub enum GotoDefinitionKind {
1690 Symbol,
1691 Declaration,
1692 Type,
1693 Implementation,
1694}
1695
1696pub enum FormatTarget {
1697 Buffers(HashSet<Entity<Buffer>>),
1698 Ranges(Vec<Range<MultiBufferPoint>>),
1699}
1700
1701pub(crate) struct FocusedBlock {
1702 id: BlockId,
1703 focus_handle: WeakFocusHandle,
1704}
1705
1706#[derive(Clone, Debug)]
1707enum JumpData {
1708 MultiBufferRow {
1709 row: MultiBufferRow,
1710 line_offset_from_top: u32,
1711 },
1712 MultiBufferPoint {
1713 excerpt_id: ExcerptId,
1714 position: Point,
1715 anchor: text::Anchor,
1716 line_offset_from_top: u32,
1717 },
1718}
1719
1720pub enum MultibufferSelectionMode {
1721 First,
1722 All,
1723}
1724
1725#[derive(Clone, Copy, Debug, Default)]
1726pub struct RewrapOptions {
1727 pub override_language_settings: bool,
1728 pub preserve_existing_whitespace: bool,
1729}
1730
1731impl Editor {
1732 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1733 let buffer = cx.new(|cx| Buffer::local("", cx));
1734 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1735 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1736 }
1737
1738 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1739 let buffer = cx.new(|cx| Buffer::local("", cx));
1740 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1741 Self::new(EditorMode::full(), buffer, None, window, cx)
1742 }
1743
1744 pub fn auto_height(
1745 min_lines: usize,
1746 max_lines: usize,
1747 window: &mut Window,
1748 cx: &mut Context<Self>,
1749 ) -> Self {
1750 let buffer = cx.new(|cx| Buffer::local("", cx));
1751 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1752 Self::new(
1753 EditorMode::AutoHeight {
1754 min_lines,
1755 max_lines: Some(max_lines),
1756 },
1757 buffer,
1758 None,
1759 window,
1760 cx,
1761 )
1762 }
1763
1764 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1765 /// The editor grows as tall as needed to fit its content.
1766 pub fn auto_height_unbounded(
1767 min_lines: usize,
1768 window: &mut Window,
1769 cx: &mut Context<Self>,
1770 ) -> Self {
1771 let buffer = cx.new(|cx| Buffer::local("", cx));
1772 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1773 Self::new(
1774 EditorMode::AutoHeight {
1775 min_lines,
1776 max_lines: None,
1777 },
1778 buffer,
1779 None,
1780 window,
1781 cx,
1782 )
1783 }
1784
1785 pub fn for_buffer(
1786 buffer: Entity<Buffer>,
1787 project: Option<Entity<Project>>,
1788 window: &mut Window,
1789 cx: &mut Context<Self>,
1790 ) -> Self {
1791 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1792 Self::new(EditorMode::full(), buffer, project, window, cx)
1793 }
1794
1795 pub fn for_multibuffer(
1796 buffer: Entity<MultiBuffer>,
1797 project: Option<Entity<Project>>,
1798 window: &mut Window,
1799 cx: &mut Context<Self>,
1800 ) -> Self {
1801 Self::new(EditorMode::full(), buffer, project, window, cx)
1802 }
1803
1804 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1805 let mut clone = Self::new(
1806 self.mode.clone(),
1807 self.buffer.clone(),
1808 self.project.clone(),
1809 window,
1810 cx,
1811 );
1812 self.display_map.update(cx, |display_map, cx| {
1813 let snapshot = display_map.snapshot(cx);
1814 clone.display_map.update(cx, |display_map, cx| {
1815 display_map.set_state(&snapshot, cx);
1816 });
1817 });
1818 clone.folds_did_change(cx);
1819 clone.selections.clone_state(&self.selections);
1820 clone.scroll_manager.clone_state(&self.scroll_manager);
1821 clone.searchable = self.searchable;
1822 clone.read_only = self.read_only;
1823 clone
1824 }
1825
1826 pub fn new(
1827 mode: EditorMode,
1828 buffer: Entity<MultiBuffer>,
1829 project: Option<Entity<Project>>,
1830 window: &mut Window,
1831 cx: &mut Context<Self>,
1832 ) -> Self {
1833 Editor::new_internal(mode, buffer, project, None, window, cx)
1834 }
1835
1836 pub fn sticky_headers(
1837 &self,
1838 style: &EditorStyle,
1839 cx: &App,
1840 ) -> Option<Vec<OutlineItem<Anchor>>> {
1841 let multi_buffer = self.buffer().read(cx);
1842 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1843 let multi_buffer_visible_start = self
1844 .scroll_manager
1845 .anchor()
1846 .anchor
1847 .to_point(&multi_buffer_snapshot);
1848 let max_row = multi_buffer_snapshot.max_point().row;
1849
1850 let start_row = (multi_buffer_visible_start.row).min(max_row);
1851 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1852
1853 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1854 let outline_items = buffer
1855 .outline_items_containing(
1856 Point::new(start_row, 0)..Point::new(end_row, 0),
1857 true,
1858 Some(style.syntax.as_ref()),
1859 )
1860 .into_iter()
1861 .map(|outline_item| OutlineItem {
1862 depth: outline_item.depth,
1863 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1864 source_range_for_text: Anchor::range_in_buffer(
1865 *excerpt_id,
1866 outline_item.source_range_for_text,
1867 ),
1868 text: outline_item.text,
1869 highlight_ranges: outline_item.highlight_ranges,
1870 name_ranges: outline_item.name_ranges,
1871 body_range: outline_item
1872 .body_range
1873 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1874 annotation_range: outline_item
1875 .annotation_range
1876 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1877 });
1878 return Some(outline_items.collect());
1879 }
1880
1881 None
1882 }
1883
1884 fn new_internal(
1885 mode: EditorMode,
1886 multi_buffer: Entity<MultiBuffer>,
1887 project: Option<Entity<Project>>,
1888 display_map: Option<Entity<DisplayMap>>,
1889 window: &mut Window,
1890 cx: &mut Context<Self>,
1891 ) -> Self {
1892 debug_assert!(
1893 display_map.is_none() || mode.is_minimap(),
1894 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1895 );
1896
1897 let full_mode = mode.is_full();
1898 let is_minimap = mode.is_minimap();
1899 let diagnostics_max_severity = if full_mode {
1900 EditorSettings::get_global(cx)
1901 .diagnostics_max_severity
1902 .unwrap_or(DiagnosticSeverity::Hint)
1903 } else {
1904 DiagnosticSeverity::Off
1905 };
1906 let style = window.text_style();
1907 let font_size = style.font_size.to_pixels(window.rem_size());
1908 let editor = cx.entity().downgrade();
1909 let fold_placeholder = FoldPlaceholder {
1910 constrain_width: false,
1911 render: Arc::new(move |fold_id, fold_range, cx| {
1912 let editor = editor.clone();
1913 div()
1914 .id(fold_id)
1915 .bg(cx.theme().colors().ghost_element_background)
1916 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1917 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1918 .rounded_xs()
1919 .size_full()
1920 .cursor_pointer()
1921 .child("⋯")
1922 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1923 .on_click(move |_, _window, cx| {
1924 editor
1925 .update(cx, |editor, cx| {
1926 editor.unfold_ranges(
1927 &[fold_range.start..fold_range.end],
1928 true,
1929 false,
1930 cx,
1931 );
1932 cx.stop_propagation();
1933 })
1934 .ok();
1935 })
1936 .into_any()
1937 }),
1938 merge_adjacent: true,
1939 ..FoldPlaceholder::default()
1940 };
1941 let display_map = display_map.unwrap_or_else(|| {
1942 cx.new(|cx| {
1943 DisplayMap::new(
1944 multi_buffer.clone(),
1945 style.font(),
1946 font_size,
1947 None,
1948 FILE_HEADER_HEIGHT,
1949 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1950 fold_placeholder,
1951 diagnostics_max_severity,
1952 cx,
1953 )
1954 })
1955 });
1956
1957 let selections = SelectionsCollection::new();
1958
1959 let blink_manager = cx.new(|cx| {
1960 let mut blink_manager = BlinkManager::new(
1961 CURSOR_BLINK_INTERVAL,
1962 |cx| EditorSettings::get_global(cx).cursor_blink,
1963 cx,
1964 );
1965 if is_minimap {
1966 blink_manager.disable(cx);
1967 }
1968 blink_manager
1969 });
1970
1971 let soft_wrap_mode_override =
1972 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1973
1974 let mut project_subscriptions = Vec::new();
1975 if full_mode && let Some(project) = project.as_ref() {
1976 project_subscriptions.push(cx.subscribe_in(
1977 project,
1978 window,
1979 |editor, _, event, window, cx| match event {
1980 project::Event::RefreshCodeLens => {
1981 // we always query lens with actions, without storing them, always refreshing them
1982 }
1983 project::Event::RefreshInlayHints {
1984 server_id,
1985 request_id,
1986 } => {
1987 editor.refresh_inlay_hints(
1988 InlayHintRefreshReason::RefreshRequested {
1989 server_id: *server_id,
1990 request_id: *request_id,
1991 },
1992 cx,
1993 );
1994 }
1995 project::Event::LanguageServerRemoved(..) => {
1996 if editor.tasks_update_task.is_none() {
1997 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1998 }
1999 editor.registered_buffers.clear();
2000 editor.register_visible_buffers(cx);
2001 }
2002 project::Event::LanguageServerAdded(..) => {
2003 if editor.tasks_update_task.is_none() {
2004 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2005 }
2006 }
2007 project::Event::SnippetEdit(id, snippet_edits) => {
2008 // todo(lw): Non singletons
2009 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2010 let snapshot = buffer.read(cx).snapshot();
2011 let focus_handle = editor.focus_handle(cx);
2012 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2013 for (range, snippet) in snippet_edits {
2014 let buffer_range =
2015 language::range_from_lsp(*range).to_offset(&snapshot);
2016 editor
2017 .insert_snippet(
2018 &[MultiBufferOffset(buffer_range.start)
2019 ..MultiBufferOffset(buffer_range.end)],
2020 snippet.clone(),
2021 window,
2022 cx,
2023 )
2024 .ok();
2025 }
2026 }
2027 }
2028 }
2029 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2030 let buffer_id = *buffer_id;
2031 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2032 editor.register_buffer(buffer_id, cx);
2033 editor.update_lsp_data(Some(buffer_id), window, cx);
2034 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2035 refresh_linked_ranges(editor, window, cx);
2036 editor.refresh_code_actions(window, cx);
2037 editor.refresh_document_highlights(cx);
2038 }
2039 }
2040
2041 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2042 let Some(workspace) = editor.workspace() else {
2043 return;
2044 };
2045 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2046 else {
2047 return;
2048 };
2049
2050 if active_editor.entity_id() == cx.entity_id() {
2051 let entity_id = cx.entity_id();
2052 workspace.update(cx, |this, cx| {
2053 this.panes_mut()
2054 .iter_mut()
2055 .filter(|pane| pane.entity_id() != entity_id)
2056 .for_each(|p| {
2057 p.update(cx, |pane, _| {
2058 pane.nav_history_mut().rename_item(
2059 entity_id,
2060 project_path.clone(),
2061 abs_path.clone().into(),
2062 );
2063 })
2064 });
2065 });
2066 let edited_buffers_already_open = {
2067 let other_editors: Vec<Entity<Editor>> = workspace
2068 .read(cx)
2069 .panes()
2070 .iter()
2071 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2072 .filter(|editor| editor.entity_id() != cx.entity_id())
2073 .collect();
2074
2075 transaction.0.keys().all(|buffer| {
2076 other_editors.iter().any(|editor| {
2077 let multi_buffer = editor.read(cx).buffer();
2078 multi_buffer.read(cx).is_singleton()
2079 && multi_buffer.read(cx).as_singleton().map_or(
2080 false,
2081 |singleton| {
2082 singleton.entity_id() == buffer.entity_id()
2083 },
2084 )
2085 })
2086 })
2087 };
2088 if !edited_buffers_already_open {
2089 let workspace = workspace.downgrade();
2090 let transaction = transaction.clone();
2091 cx.defer_in(window, move |_, window, cx| {
2092 cx.spawn_in(window, async move |editor, cx| {
2093 Self::open_project_transaction(
2094 &editor,
2095 workspace,
2096 transaction,
2097 "Rename".to_string(),
2098 cx,
2099 )
2100 .await
2101 .ok()
2102 })
2103 .detach();
2104 });
2105 }
2106 }
2107 }
2108
2109 _ => {}
2110 },
2111 ));
2112 if let Some(task_inventory) = project
2113 .read(cx)
2114 .task_store()
2115 .read(cx)
2116 .task_inventory()
2117 .cloned()
2118 {
2119 project_subscriptions.push(cx.observe_in(
2120 &task_inventory,
2121 window,
2122 |editor, _, window, cx| {
2123 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2124 },
2125 ));
2126 };
2127
2128 project_subscriptions.push(cx.subscribe_in(
2129 &project.read(cx).breakpoint_store(),
2130 window,
2131 |editor, _, event, window, cx| match event {
2132 BreakpointStoreEvent::ClearDebugLines => {
2133 editor.clear_row_highlights::<ActiveDebugLine>();
2134 editor.refresh_inline_values(cx);
2135 }
2136 BreakpointStoreEvent::SetDebugLine => {
2137 if editor.go_to_active_debug_line(window, cx) {
2138 cx.stop_propagation();
2139 }
2140
2141 editor.refresh_inline_values(cx);
2142 }
2143 _ => {}
2144 },
2145 ));
2146 let git_store = project.read(cx).git_store().clone();
2147 let project = project.clone();
2148 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2149 if let GitStoreEvent::RepositoryAdded = event {
2150 this.load_diff_task = Some(
2151 update_uncommitted_diff_for_buffer(
2152 cx.entity(),
2153 &project,
2154 this.buffer.read(cx).all_buffers(),
2155 this.buffer.clone(),
2156 cx,
2157 )
2158 .shared(),
2159 );
2160 }
2161 }));
2162 }
2163
2164 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2165
2166 let inlay_hint_settings =
2167 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2168 let focus_handle = cx.focus_handle();
2169 if !is_minimap {
2170 cx.on_focus(&focus_handle, window, Self::handle_focus)
2171 .detach();
2172 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2173 .detach();
2174 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2175 .detach();
2176 cx.on_blur(&focus_handle, window, Self::handle_blur)
2177 .detach();
2178 cx.observe_pending_input(window, Self::observe_pending_input)
2179 .detach();
2180 }
2181
2182 let show_indent_guides =
2183 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2184 Some(false)
2185 } else {
2186 None
2187 };
2188
2189 let breakpoint_store = match (&mode, project.as_ref()) {
2190 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2191 _ => None,
2192 };
2193
2194 let mut code_action_providers = Vec::new();
2195 let mut load_uncommitted_diff = None;
2196 if let Some(project) = project.clone() {
2197 load_uncommitted_diff = Some(
2198 update_uncommitted_diff_for_buffer(
2199 cx.entity(),
2200 &project,
2201 multi_buffer.read(cx).all_buffers(),
2202 multi_buffer.clone(),
2203 cx,
2204 )
2205 .shared(),
2206 );
2207 code_action_providers.push(Rc::new(project) as Rc<_>);
2208 }
2209
2210 let mut editor = Self {
2211 focus_handle,
2212 show_cursor_when_unfocused: false,
2213 last_focused_descendant: None,
2214 buffer: multi_buffer.clone(),
2215 display_map: display_map.clone(),
2216 placeholder_display_map: None,
2217 selections,
2218 scroll_manager: ScrollManager::new(cx),
2219 columnar_selection_state: None,
2220 add_selections_state: None,
2221 select_next_state: None,
2222 select_prev_state: None,
2223 selection_history: SelectionHistory::default(),
2224 defer_selection_effects: false,
2225 deferred_selection_effects_state: None,
2226 autoclose_regions: Vec::new(),
2227 snippet_stack: InvalidationStack::default(),
2228 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2229 ime_transaction: None,
2230 active_diagnostics: ActiveDiagnostic::None,
2231 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2232 inline_diagnostics_update: Task::ready(()),
2233 inline_diagnostics: Vec::new(),
2234 soft_wrap_mode_override,
2235 diagnostics_max_severity,
2236 hard_wrap: None,
2237 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2238 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2239 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2240 project,
2241 blink_manager: blink_manager.clone(),
2242 show_local_selections: true,
2243 show_scrollbars: ScrollbarAxes {
2244 horizontal: full_mode,
2245 vertical: full_mode,
2246 },
2247 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2248 offset_content: !matches!(mode, EditorMode::SingleLine),
2249 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2250 show_gutter: full_mode,
2251 show_line_numbers: (!full_mode).then_some(false),
2252 use_relative_line_numbers: None,
2253 disable_expand_excerpt_buttons: !full_mode,
2254 show_git_diff_gutter: None,
2255 show_code_actions: None,
2256 show_runnables: None,
2257 show_breakpoints: None,
2258 show_wrap_guides: None,
2259 show_indent_guides,
2260 buffers_with_disabled_indent_guides: HashSet::default(),
2261 highlight_order: 0,
2262 highlighted_rows: HashMap::default(),
2263 background_highlights: HashMap::default(),
2264 gutter_highlights: HashMap::default(),
2265 scrollbar_marker_state: ScrollbarMarkerState::default(),
2266 active_indent_guides_state: ActiveIndentGuidesState::default(),
2267 nav_history: None,
2268 context_menu: RefCell::new(None),
2269 context_menu_options: None,
2270 mouse_context_menu: None,
2271 completion_tasks: Vec::new(),
2272 inline_blame_popover: None,
2273 inline_blame_popover_show_task: None,
2274 signature_help_state: SignatureHelpState::default(),
2275 auto_signature_help: None,
2276 find_all_references_task_sources: Vec::new(),
2277 next_completion_id: 0,
2278 next_inlay_id: 0,
2279 code_action_providers,
2280 available_code_actions: None,
2281 code_actions_task: None,
2282 quick_selection_highlight_task: None,
2283 debounced_selection_highlight_task: None,
2284 document_highlights_task: None,
2285 linked_editing_range_task: None,
2286 pending_rename: None,
2287 searchable: !is_minimap,
2288 cursor_shape: EditorSettings::get_global(cx)
2289 .cursor_shape
2290 .unwrap_or_default(),
2291 cursor_offset_on_selection: false,
2292 current_line_highlight: None,
2293 autoindent_mode: Some(AutoindentMode::EachLine),
2294 collapse_matches: false,
2295 workspace: None,
2296 input_enabled: !is_minimap,
2297 use_modal_editing: full_mode,
2298 read_only: is_minimap,
2299 use_autoclose: true,
2300 use_auto_surround: true,
2301 auto_replace_emoji_shortcode: false,
2302 jsx_tag_auto_close_enabled_in_any_buffer: false,
2303 leader_id: None,
2304 remote_id: None,
2305 hover_state: HoverState::default(),
2306 pending_mouse_down: None,
2307 prev_pressure_stage: None,
2308 hovered_link_state: None,
2309 edit_prediction_provider: None,
2310 active_edit_prediction: None,
2311 stale_edit_prediction_in_menu: None,
2312 edit_prediction_preview: EditPredictionPreview::Inactive {
2313 released_too_fast: false,
2314 },
2315 inline_diagnostics_enabled: full_mode,
2316 diagnostics_enabled: full_mode,
2317 word_completions_enabled: full_mode,
2318 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2319 gutter_hovered: false,
2320 pixel_position_of_newest_cursor: None,
2321 last_bounds: None,
2322 last_position_map: None,
2323 expect_bounds_change: None,
2324 gutter_dimensions: GutterDimensions::default(),
2325 style: None,
2326 show_cursor_names: false,
2327 hovered_cursors: HashMap::default(),
2328 next_editor_action_id: EditorActionId::default(),
2329 editor_actions: Rc::default(),
2330 edit_predictions_hidden_for_vim_mode: false,
2331 show_edit_predictions_override: None,
2332 show_completions_on_input_override: None,
2333 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2334 edit_prediction_settings: EditPredictionSettings::Disabled,
2335 edit_prediction_indent_conflict: false,
2336 edit_prediction_requires_modifier_in_indent_conflict: true,
2337 custom_context_menu: None,
2338 show_git_blame_gutter: false,
2339 show_git_blame_inline: false,
2340 show_selection_menu: None,
2341 show_git_blame_inline_delay_task: None,
2342 git_blame_inline_enabled: full_mode
2343 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2344 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2345 buffer_serialization: is_minimap.not().then(|| {
2346 BufferSerialization::new(
2347 ProjectSettings::get_global(cx)
2348 .session
2349 .restore_unsaved_buffers,
2350 )
2351 }),
2352 blame: None,
2353 blame_subscription: None,
2354 tasks: BTreeMap::default(),
2355
2356 breakpoint_store,
2357 gutter_breakpoint_indicator: (None, None),
2358 hovered_diff_hunk_row: None,
2359 _subscriptions: (!is_minimap)
2360 .then(|| {
2361 vec![
2362 cx.observe(&multi_buffer, Self::on_buffer_changed),
2363 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2364 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2365 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2366 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2367 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2368 cx.observe_window_activation(window, |editor, window, cx| {
2369 let active = window.is_window_active();
2370 editor.blink_manager.update(cx, |blink_manager, cx| {
2371 if active {
2372 blink_manager.enable(cx);
2373 } else {
2374 blink_manager.disable(cx);
2375 }
2376 });
2377 if active {
2378 editor.show_mouse_cursor(cx);
2379 }
2380 }),
2381 ]
2382 })
2383 .unwrap_or_default(),
2384 tasks_update_task: None,
2385 pull_diagnostics_task: Task::ready(()),
2386 pull_diagnostics_background_task: Task::ready(()),
2387 colors: None,
2388 refresh_colors_task: Task::ready(()),
2389 inlay_hints: None,
2390 next_color_inlay_id: 0,
2391 post_scroll_update: Task::ready(()),
2392 linked_edit_ranges: Default::default(),
2393 in_project_search: false,
2394 previous_search_ranges: None,
2395 breadcrumb_header: None,
2396 focused_block: None,
2397 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2398 addons: HashMap::default(),
2399 registered_buffers: HashMap::default(),
2400 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2401 selection_mark_mode: false,
2402 toggle_fold_multiple_buffers: Task::ready(()),
2403 serialize_selections: Task::ready(()),
2404 serialize_folds: Task::ready(()),
2405 text_style_refinement: None,
2406 load_diff_task: load_uncommitted_diff,
2407 temporary_diff_override: false,
2408 mouse_cursor_hidden: false,
2409 minimap: None,
2410 hide_mouse_mode: EditorSettings::get_global(cx)
2411 .hide_mouse
2412 .unwrap_or_default(),
2413 change_list: ChangeList::new(),
2414 mode,
2415 selection_drag_state: SelectionDragState::None,
2416 folding_newlines: Task::ready(()),
2417 lookup_key: None,
2418 select_next_is_case_sensitive: None,
2419 applicable_language_settings: HashMap::default(),
2420 accent_data: None,
2421 fetched_tree_sitter_chunks: HashMap::default(),
2422 use_base_text_line_numbers: false,
2423 };
2424
2425 if is_minimap {
2426 return editor;
2427 }
2428
2429 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2430 editor.accent_data = editor.fetch_accent_data(cx);
2431
2432 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2433 editor
2434 ._subscriptions
2435 .push(cx.observe(breakpoints, |_, _, cx| {
2436 cx.notify();
2437 }));
2438 }
2439 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2440 editor._subscriptions.extend(project_subscriptions);
2441
2442 editor._subscriptions.push(cx.subscribe_in(
2443 &cx.entity(),
2444 window,
2445 |editor, _, e: &EditorEvent, window, cx| match e {
2446 EditorEvent::ScrollPositionChanged { local, .. } => {
2447 if *local {
2448 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2449 editor.inline_blame_popover.take();
2450 let new_anchor = editor.scroll_manager.anchor();
2451 let snapshot = editor.snapshot(window, cx);
2452 editor.update_restoration_data(cx, move |data| {
2453 data.scroll_position = (
2454 new_anchor.top_row(snapshot.buffer_snapshot()),
2455 new_anchor.offset,
2456 );
2457 });
2458
2459 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2460 cx.background_executor()
2461 .timer(Duration::from_millis(50))
2462 .await;
2463 editor
2464 .update_in(cx, |editor, window, cx| {
2465 editor.register_visible_buffers(cx);
2466 editor.refresh_colors_for_visible_range(None, window, cx);
2467 editor.refresh_inlay_hints(
2468 InlayHintRefreshReason::NewLinesShown,
2469 cx,
2470 );
2471 editor.colorize_brackets(false, cx);
2472 })
2473 .ok();
2474 });
2475 }
2476 }
2477 EditorEvent::Edited { .. } => {
2478 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2479 .map(|vim_mode| vim_mode.0)
2480 .unwrap_or(false);
2481 if !vim_mode {
2482 let display_map = editor.display_snapshot(cx);
2483 let selections = editor.selections.all_adjusted_display(&display_map);
2484 let pop_state = editor
2485 .change_list
2486 .last()
2487 .map(|previous| {
2488 previous.len() == selections.len()
2489 && previous.iter().enumerate().all(|(ix, p)| {
2490 p.to_display_point(&display_map).row()
2491 == selections[ix].head().row()
2492 })
2493 })
2494 .unwrap_or(false);
2495 let new_positions = selections
2496 .into_iter()
2497 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2498 .collect();
2499 editor
2500 .change_list
2501 .push_to_change_list(pop_state, new_positions);
2502 }
2503 }
2504 _ => (),
2505 },
2506 ));
2507
2508 if let Some(dap_store) = editor
2509 .project
2510 .as_ref()
2511 .map(|project| project.read(cx).dap_store())
2512 {
2513 let weak_editor = cx.weak_entity();
2514
2515 editor
2516 ._subscriptions
2517 .push(
2518 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2519 let session_entity = cx.entity();
2520 weak_editor
2521 .update(cx, |editor, cx| {
2522 editor._subscriptions.push(
2523 cx.subscribe(&session_entity, Self::on_debug_session_event),
2524 );
2525 })
2526 .ok();
2527 }),
2528 );
2529
2530 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2531 editor
2532 ._subscriptions
2533 .push(cx.subscribe(&session, Self::on_debug_session_event));
2534 }
2535 }
2536
2537 // skip adding the initial selection to selection history
2538 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2539 editor.end_selection(window, cx);
2540 editor.selection_history.mode = SelectionHistoryMode::Normal;
2541
2542 editor.scroll_manager.show_scrollbars(window, cx);
2543 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2544
2545 if full_mode {
2546 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2547 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2548
2549 if editor.git_blame_inline_enabled {
2550 editor.start_git_blame_inline(false, window, cx);
2551 }
2552
2553 editor.go_to_active_debug_line(window, cx);
2554
2555 editor.minimap =
2556 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2557 editor.colors = Some(LspColorData::new(cx));
2558 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2559
2560 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2561 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2562 }
2563 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2564 }
2565
2566 editor
2567 }
2568
2569 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2570 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2571 }
2572
2573 pub fn deploy_mouse_context_menu(
2574 &mut self,
2575 position: gpui::Point<Pixels>,
2576 context_menu: Entity<ContextMenu>,
2577 window: &mut Window,
2578 cx: &mut Context<Self>,
2579 ) {
2580 self.mouse_context_menu = Some(MouseContextMenu::new(
2581 self,
2582 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2583 context_menu,
2584 window,
2585 cx,
2586 ));
2587 }
2588
2589 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2590 self.mouse_context_menu
2591 .as_ref()
2592 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2593 }
2594
2595 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2596 if self
2597 .selections
2598 .pending_anchor()
2599 .is_some_and(|pending_selection| {
2600 let snapshot = self.buffer().read(cx).snapshot(cx);
2601 pending_selection.range().includes(range, &snapshot)
2602 })
2603 {
2604 return true;
2605 }
2606
2607 self.selections
2608 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2609 .into_iter()
2610 .any(|selection| {
2611 // This is needed to cover a corner case, if we just check for an existing
2612 // selection in the fold range, having a cursor at the start of the fold
2613 // marks it as selected. Non-empty selections don't cause this.
2614 let length = selection.end - selection.start;
2615 length > 0
2616 })
2617 }
2618
2619 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2620 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2621 }
2622
2623 fn key_context_internal(
2624 &self,
2625 has_active_edit_prediction: bool,
2626 window: &mut Window,
2627 cx: &mut App,
2628 ) -> KeyContext {
2629 let mut key_context = KeyContext::new_with_defaults();
2630 key_context.add("Editor");
2631 let mode = match self.mode {
2632 EditorMode::SingleLine => "single_line",
2633 EditorMode::AutoHeight { .. } => "auto_height",
2634 EditorMode::Minimap { .. } => "minimap",
2635 EditorMode::Full { .. } => "full",
2636 };
2637
2638 if EditorSettings::jupyter_enabled(cx) {
2639 key_context.add("jupyter");
2640 }
2641
2642 key_context.set("mode", mode);
2643 if self.pending_rename.is_some() {
2644 key_context.add("renaming");
2645 }
2646
2647 if let Some(snippet_stack) = self.snippet_stack.last() {
2648 key_context.add("in_snippet");
2649
2650 if snippet_stack.active_index > 0 {
2651 key_context.add("has_previous_tabstop");
2652 }
2653
2654 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2655 key_context.add("has_next_tabstop");
2656 }
2657 }
2658
2659 match self.context_menu.borrow().as_ref() {
2660 Some(CodeContextMenu::Completions(menu)) => {
2661 if menu.visible() {
2662 key_context.add("menu");
2663 key_context.add("showing_completions");
2664 }
2665 }
2666 Some(CodeContextMenu::CodeActions(menu)) => {
2667 if menu.visible() {
2668 key_context.add("menu");
2669 key_context.add("showing_code_actions")
2670 }
2671 }
2672 None => {}
2673 }
2674
2675 if self.signature_help_state.has_multiple_signatures() {
2676 key_context.add("showing_signature_help");
2677 }
2678
2679 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2680 if !self.focus_handle(cx).contains_focused(window, cx)
2681 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2682 {
2683 for addon in self.addons.values() {
2684 addon.extend_key_context(&mut key_context, cx)
2685 }
2686 }
2687
2688 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2689 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2690 Some(
2691 file.full_path(cx)
2692 .extension()?
2693 .to_string_lossy()
2694 .into_owned(),
2695 )
2696 }) {
2697 key_context.set("extension", extension);
2698 }
2699 } else {
2700 key_context.add("multibuffer");
2701 }
2702
2703 if has_active_edit_prediction {
2704 if self.edit_prediction_in_conflict() {
2705 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2706 } else {
2707 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2708 key_context.add("copilot_suggestion");
2709 }
2710 }
2711
2712 if self.selection_mark_mode {
2713 key_context.add("selection_mode");
2714 }
2715
2716 let disjoint = self.selections.disjoint_anchors();
2717 let snapshot = self.snapshot(window, cx);
2718 let snapshot = snapshot.buffer_snapshot();
2719 if self.mode == EditorMode::SingleLine
2720 && let [selection] = disjoint
2721 && selection.start == selection.end
2722 && selection.end.to_offset(snapshot) == snapshot.len()
2723 {
2724 key_context.add("end_of_input");
2725 }
2726
2727 if self.has_any_expanded_diff_hunks(cx) {
2728 key_context.add("diffs_expanded");
2729 }
2730
2731 key_context
2732 }
2733
2734 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2735 self.last_bounds.as_ref()
2736 }
2737
2738 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2739 if self.mouse_cursor_hidden {
2740 self.mouse_cursor_hidden = false;
2741 cx.notify();
2742 }
2743 }
2744
2745 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2746 let hide_mouse_cursor = match origin {
2747 HideMouseCursorOrigin::TypingAction => {
2748 matches!(
2749 self.hide_mouse_mode,
2750 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2751 )
2752 }
2753 HideMouseCursorOrigin::MovementAction => {
2754 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2755 }
2756 };
2757 if self.mouse_cursor_hidden != hide_mouse_cursor {
2758 self.mouse_cursor_hidden = hide_mouse_cursor;
2759 cx.notify();
2760 }
2761 }
2762
2763 pub fn edit_prediction_in_conflict(&self) -> bool {
2764 if !self.show_edit_predictions_in_menu() {
2765 return false;
2766 }
2767
2768 let showing_completions = self
2769 .context_menu
2770 .borrow()
2771 .as_ref()
2772 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2773
2774 showing_completions
2775 || self.edit_prediction_requires_modifier()
2776 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2777 // bindings to insert tab characters.
2778 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2779 }
2780
2781 pub fn accept_edit_prediction_keybind(
2782 &self,
2783 granularity: EditPredictionGranularity,
2784 window: &mut Window,
2785 cx: &mut App,
2786 ) -> AcceptEditPredictionBinding {
2787 let key_context = self.key_context_internal(true, window, cx);
2788 let in_conflict = self.edit_prediction_in_conflict();
2789
2790 let bindings =
2791 match granularity {
2792 EditPredictionGranularity::Word => window
2793 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2794 EditPredictionGranularity::Line => window
2795 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2796 EditPredictionGranularity::Full => {
2797 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2798 }
2799 };
2800
2801 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2802 !in_conflict
2803 || binding
2804 .keystrokes()
2805 .first()
2806 .is_some_and(|keystroke| keystroke.modifiers().modified())
2807 }))
2808 }
2809
2810 pub fn new_file(
2811 workspace: &mut Workspace,
2812 _: &workspace::NewFile,
2813 window: &mut Window,
2814 cx: &mut Context<Workspace>,
2815 ) {
2816 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2817 "Failed to create buffer",
2818 window,
2819 cx,
2820 |e, _, _| match e.error_code() {
2821 ErrorCode::RemoteUpgradeRequired => Some(format!(
2822 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2823 e.error_tag("required").unwrap_or("the latest version")
2824 )),
2825 _ => None,
2826 },
2827 );
2828 }
2829
2830 pub fn new_in_workspace(
2831 workspace: &mut Workspace,
2832 window: &mut Window,
2833 cx: &mut Context<Workspace>,
2834 ) -> Task<Result<Entity<Editor>>> {
2835 let project = workspace.project().clone();
2836 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2837
2838 cx.spawn_in(window, async move |workspace, cx| {
2839 let buffer = create.await?;
2840 workspace.update_in(cx, |workspace, window, cx| {
2841 let editor =
2842 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2843 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2844 editor
2845 })
2846 })
2847 }
2848
2849 fn new_file_vertical(
2850 workspace: &mut Workspace,
2851 _: &workspace::NewFileSplitVertical,
2852 window: &mut Window,
2853 cx: &mut Context<Workspace>,
2854 ) {
2855 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2856 }
2857
2858 fn new_file_horizontal(
2859 workspace: &mut Workspace,
2860 _: &workspace::NewFileSplitHorizontal,
2861 window: &mut Window,
2862 cx: &mut Context<Workspace>,
2863 ) {
2864 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2865 }
2866
2867 fn new_file_split(
2868 workspace: &mut Workspace,
2869 action: &workspace::NewFileSplit,
2870 window: &mut Window,
2871 cx: &mut Context<Workspace>,
2872 ) {
2873 Self::new_file_in_direction(workspace, action.0, window, cx)
2874 }
2875
2876 fn new_file_in_direction(
2877 workspace: &mut Workspace,
2878 direction: SplitDirection,
2879 window: &mut Window,
2880 cx: &mut Context<Workspace>,
2881 ) {
2882 let project = workspace.project().clone();
2883 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2884
2885 cx.spawn_in(window, async move |workspace, cx| {
2886 let buffer = create.await?;
2887 workspace.update_in(cx, move |workspace, window, cx| {
2888 workspace.split_item(
2889 direction,
2890 Box::new(
2891 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2892 ),
2893 window,
2894 cx,
2895 )
2896 })?;
2897 anyhow::Ok(())
2898 })
2899 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2900 match e.error_code() {
2901 ErrorCode::RemoteUpgradeRequired => Some(format!(
2902 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2903 e.error_tag("required").unwrap_or("the latest version")
2904 )),
2905 _ => None,
2906 }
2907 });
2908 }
2909
2910 pub fn leader_id(&self) -> Option<CollaboratorId> {
2911 self.leader_id
2912 }
2913
2914 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2915 &self.buffer
2916 }
2917
2918 pub fn project(&self) -> Option<&Entity<Project>> {
2919 self.project.as_ref()
2920 }
2921
2922 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2923 self.workspace.as_ref()?.0.upgrade()
2924 }
2925
2926 /// Returns the workspace serialization ID if this editor should be serialized.
2927 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2928 self.workspace
2929 .as_ref()
2930 .filter(|_| self.should_serialize_buffer())
2931 .and_then(|workspace| workspace.1)
2932 }
2933
2934 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2935 self.buffer().read(cx).title(cx)
2936 }
2937
2938 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2939 let git_blame_gutter_max_author_length = self
2940 .render_git_blame_gutter(cx)
2941 .then(|| {
2942 if let Some(blame) = self.blame.as_ref() {
2943 let max_author_length =
2944 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2945 Some(max_author_length)
2946 } else {
2947 None
2948 }
2949 })
2950 .flatten();
2951
2952 EditorSnapshot {
2953 mode: self.mode.clone(),
2954 show_gutter: self.show_gutter,
2955 offset_content: self.offset_content,
2956 show_line_numbers: self.show_line_numbers,
2957 show_git_diff_gutter: self.show_git_diff_gutter,
2958 show_code_actions: self.show_code_actions,
2959 show_runnables: self.show_runnables,
2960 show_breakpoints: self.show_breakpoints,
2961 git_blame_gutter_max_author_length,
2962 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2963 placeholder_display_snapshot: self
2964 .placeholder_display_map
2965 .as_ref()
2966 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2967 scroll_anchor: self.scroll_manager.anchor(),
2968 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2969 is_focused: self.focus_handle.is_focused(window),
2970 current_line_highlight: self
2971 .current_line_highlight
2972 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2973 gutter_hovered: self.gutter_hovered,
2974 }
2975 }
2976
2977 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2978 self.buffer.read(cx).language_at(point, cx)
2979 }
2980
2981 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2982 self.buffer.read(cx).read(cx).file_at(point).cloned()
2983 }
2984
2985 pub fn active_excerpt(
2986 &self,
2987 cx: &App,
2988 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2989 self.buffer
2990 .read(cx)
2991 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2992 }
2993
2994 pub fn mode(&self) -> &EditorMode {
2995 &self.mode
2996 }
2997
2998 pub fn set_mode(&mut self, mode: EditorMode) {
2999 self.mode = mode;
3000 }
3001
3002 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
3003 self.collaboration_hub.as_deref()
3004 }
3005
3006 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3007 self.collaboration_hub = Some(hub);
3008 }
3009
3010 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3011 self.in_project_search = in_project_search;
3012 }
3013
3014 pub fn set_custom_context_menu(
3015 &mut self,
3016 f: impl 'static
3017 + Fn(
3018 &mut Self,
3019 DisplayPoint,
3020 &mut Window,
3021 &mut Context<Self>,
3022 ) -> Option<Entity<ui::ContextMenu>>,
3023 ) {
3024 self.custom_context_menu = Some(Box::new(f))
3025 }
3026
3027 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3028 self.completion_provider = provider;
3029 }
3030
3031 #[cfg(any(test, feature = "test-support"))]
3032 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3033 self.completion_provider.clone()
3034 }
3035
3036 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3037 self.semantics_provider.clone()
3038 }
3039
3040 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3041 self.semantics_provider = provider;
3042 }
3043
3044 pub fn set_edit_prediction_provider<T>(
3045 &mut self,
3046 provider: Option<Entity<T>>,
3047 window: &mut Window,
3048 cx: &mut Context<Self>,
3049 ) where
3050 T: EditPredictionDelegate,
3051 {
3052 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3053 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3054 if this.focus_handle.is_focused(window) {
3055 this.update_visible_edit_prediction(window, cx);
3056 }
3057 }),
3058 provider: Arc::new(provider),
3059 });
3060 self.update_edit_prediction_settings(cx);
3061 self.refresh_edit_prediction(false, false, window, cx);
3062 }
3063
3064 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3065 self.placeholder_display_map
3066 .as_ref()
3067 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3068 }
3069
3070 pub fn set_placeholder_text(
3071 &mut self,
3072 placeholder_text: &str,
3073 window: &mut Window,
3074 cx: &mut Context<Self>,
3075 ) {
3076 let multibuffer = cx
3077 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3078
3079 let style = window.text_style();
3080
3081 self.placeholder_display_map = Some(cx.new(|cx| {
3082 DisplayMap::new(
3083 multibuffer,
3084 style.font(),
3085 style.font_size.to_pixels(window.rem_size()),
3086 None,
3087 FILE_HEADER_HEIGHT,
3088 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3089 Default::default(),
3090 DiagnosticSeverity::Off,
3091 cx,
3092 )
3093 }));
3094 cx.notify();
3095 }
3096
3097 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3098 self.cursor_shape = cursor_shape;
3099
3100 // Disrupt blink for immediate user feedback that the cursor shape has changed
3101 self.blink_manager.update(cx, BlinkManager::show_cursor);
3102
3103 cx.notify();
3104 }
3105
3106 pub fn cursor_shape(&self) -> CursorShape {
3107 self.cursor_shape
3108 }
3109
3110 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3111 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3112 }
3113
3114 pub fn set_current_line_highlight(
3115 &mut self,
3116 current_line_highlight: Option<CurrentLineHighlight>,
3117 ) {
3118 self.current_line_highlight = current_line_highlight;
3119 }
3120
3121 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3122 self.collapse_matches = collapse_matches;
3123 }
3124
3125 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3126 if self.collapse_matches {
3127 return range.start..range.start;
3128 }
3129 range.clone()
3130 }
3131
3132 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3133 self.display_map.read(cx).clip_at_line_ends
3134 }
3135
3136 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3137 if self.display_map.read(cx).clip_at_line_ends != clip {
3138 self.display_map
3139 .update(cx, |map, _| map.clip_at_line_ends = clip);
3140 }
3141 }
3142
3143 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3144 self.input_enabled = input_enabled;
3145 }
3146
3147 pub fn set_edit_predictions_hidden_for_vim_mode(
3148 &mut self,
3149 hidden: bool,
3150 window: &mut Window,
3151 cx: &mut Context<Self>,
3152 ) {
3153 if hidden != self.edit_predictions_hidden_for_vim_mode {
3154 self.edit_predictions_hidden_for_vim_mode = hidden;
3155 if hidden {
3156 self.update_visible_edit_prediction(window, cx);
3157 } else {
3158 self.refresh_edit_prediction(true, false, window, cx);
3159 }
3160 }
3161 }
3162
3163 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3164 self.menu_edit_predictions_policy = value;
3165 }
3166
3167 pub fn set_autoindent(&mut self, autoindent: bool) {
3168 if autoindent {
3169 self.autoindent_mode = Some(AutoindentMode::EachLine);
3170 } else {
3171 self.autoindent_mode = None;
3172 }
3173 }
3174
3175 pub fn read_only(&self, cx: &App) -> bool {
3176 self.read_only || self.buffer.read(cx).read_only()
3177 }
3178
3179 pub fn set_read_only(&mut self, read_only: bool) {
3180 self.read_only = read_only;
3181 }
3182
3183 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3184 self.use_autoclose = autoclose;
3185 }
3186
3187 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3188 self.use_auto_surround = auto_surround;
3189 }
3190
3191 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3192 self.auto_replace_emoji_shortcode = auto_replace;
3193 }
3194
3195 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3196 self.buffer_serialization = should_serialize.then(|| {
3197 BufferSerialization::new(
3198 ProjectSettings::get_global(cx)
3199 .session
3200 .restore_unsaved_buffers,
3201 )
3202 })
3203 }
3204
3205 fn should_serialize_buffer(&self) -> bool {
3206 self.buffer_serialization.is_some()
3207 }
3208
3209 pub fn toggle_edit_predictions(
3210 &mut self,
3211 _: &ToggleEditPrediction,
3212 window: &mut Window,
3213 cx: &mut Context<Self>,
3214 ) {
3215 if self.show_edit_predictions_override.is_some() {
3216 self.set_show_edit_predictions(None, window, cx);
3217 } else {
3218 let show_edit_predictions = !self.edit_predictions_enabled();
3219 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3220 }
3221 }
3222
3223 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3224 self.show_completions_on_input_override = show_completions_on_input;
3225 }
3226
3227 pub fn set_show_edit_predictions(
3228 &mut self,
3229 show_edit_predictions: Option<bool>,
3230 window: &mut Window,
3231 cx: &mut Context<Self>,
3232 ) {
3233 self.show_edit_predictions_override = show_edit_predictions;
3234 self.update_edit_prediction_settings(cx);
3235
3236 if let Some(false) = show_edit_predictions {
3237 self.discard_edit_prediction(false, cx);
3238 } else {
3239 self.refresh_edit_prediction(false, true, window, cx);
3240 }
3241 }
3242
3243 fn edit_predictions_disabled_in_scope(
3244 &self,
3245 buffer: &Entity<Buffer>,
3246 buffer_position: language::Anchor,
3247 cx: &App,
3248 ) -> bool {
3249 let snapshot = buffer.read(cx).snapshot();
3250 let settings = snapshot.settings_at(buffer_position, cx);
3251
3252 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3253 return false;
3254 };
3255
3256 scope.override_name().is_some_and(|scope_name| {
3257 settings
3258 .edit_predictions_disabled_in
3259 .iter()
3260 .any(|s| s == scope_name)
3261 })
3262 }
3263
3264 pub fn set_use_modal_editing(&mut self, to: bool) {
3265 self.use_modal_editing = to;
3266 }
3267
3268 pub fn use_modal_editing(&self) -> bool {
3269 self.use_modal_editing
3270 }
3271
3272 fn selections_did_change(
3273 &mut self,
3274 local: bool,
3275 old_cursor_position: &Anchor,
3276 effects: SelectionEffects,
3277 window: &mut Window,
3278 cx: &mut Context<Self>,
3279 ) {
3280 window.invalidate_character_coordinates();
3281
3282 // Copy selections to primary selection buffer
3283 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3284 if local {
3285 let selections = self
3286 .selections
3287 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3288 let buffer_handle = self.buffer.read(cx).read(cx);
3289
3290 let mut text = String::new();
3291 for (index, selection) in selections.iter().enumerate() {
3292 let text_for_selection = buffer_handle
3293 .text_for_range(selection.start..selection.end)
3294 .collect::<String>();
3295
3296 text.push_str(&text_for_selection);
3297 if index != selections.len() - 1 {
3298 text.push('\n');
3299 }
3300 }
3301
3302 if !text.is_empty() {
3303 cx.write_to_primary(ClipboardItem::new_string(text));
3304 }
3305 }
3306
3307 let selection_anchors = self.selections.disjoint_anchors_arc();
3308
3309 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3310 self.buffer.update(cx, |buffer, cx| {
3311 buffer.set_active_selections(
3312 &selection_anchors,
3313 self.selections.line_mode(),
3314 self.cursor_shape,
3315 cx,
3316 )
3317 });
3318 }
3319 let display_map = self
3320 .display_map
3321 .update(cx, |display_map, cx| display_map.snapshot(cx));
3322 let buffer = display_map.buffer_snapshot();
3323 if self.selections.count() == 1 {
3324 self.add_selections_state = None;
3325 }
3326 self.select_next_state = None;
3327 self.select_prev_state = None;
3328 self.select_syntax_node_history.try_clear();
3329 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3330 self.snippet_stack.invalidate(&selection_anchors, buffer);
3331 self.take_rename(false, window, cx);
3332
3333 let newest_selection = self.selections.newest_anchor();
3334 let new_cursor_position = newest_selection.head();
3335 let selection_start = newest_selection.start;
3336
3337 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3338 self.push_to_nav_history(
3339 *old_cursor_position,
3340 Some(new_cursor_position.to_point(buffer)),
3341 false,
3342 effects.nav_history == Some(true),
3343 cx,
3344 );
3345 }
3346
3347 if local {
3348 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3349 self.register_buffer(buffer_id, cx);
3350 }
3351
3352 let mut context_menu = self.context_menu.borrow_mut();
3353 let completion_menu = match context_menu.as_ref() {
3354 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3355 Some(CodeContextMenu::CodeActions(_)) => {
3356 *context_menu = None;
3357 None
3358 }
3359 None => None,
3360 };
3361 let completion_position = completion_menu.map(|menu| menu.initial_position);
3362 drop(context_menu);
3363
3364 if effects.completions
3365 && let Some(completion_position) = completion_position
3366 {
3367 let start_offset = selection_start.to_offset(buffer);
3368 let position_matches = start_offset == completion_position.to_offset(buffer);
3369 let continue_showing = if position_matches {
3370 if self.snippet_stack.is_empty() {
3371 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3372 == Some(CharKind::Word)
3373 } else {
3374 // Snippet choices can be shown even when the cursor is in whitespace.
3375 // Dismissing the menu with actions like backspace is handled by
3376 // invalidation regions.
3377 true
3378 }
3379 } else {
3380 false
3381 };
3382
3383 if continue_showing {
3384 self.open_or_update_completions_menu(None, None, false, window, cx);
3385 } else {
3386 self.hide_context_menu(window, cx);
3387 }
3388 }
3389
3390 hide_hover(self, cx);
3391
3392 if old_cursor_position.to_display_point(&display_map).row()
3393 != new_cursor_position.to_display_point(&display_map).row()
3394 {
3395 self.available_code_actions.take();
3396 }
3397 self.refresh_code_actions(window, cx);
3398 self.refresh_document_highlights(cx);
3399 refresh_linked_ranges(self, window, cx);
3400
3401 self.refresh_selected_text_highlights(false, window, cx);
3402 self.refresh_matching_bracket_highlights(window, cx);
3403 self.update_visible_edit_prediction(window, cx);
3404 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3405 self.inline_blame_popover.take();
3406 if self.git_blame_inline_enabled {
3407 self.start_inline_blame_timer(window, cx);
3408 }
3409 }
3410
3411 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3412 cx.emit(EditorEvent::SelectionsChanged { local });
3413
3414 let selections = &self.selections.disjoint_anchors_arc();
3415 if selections.len() == 1 {
3416 cx.emit(SearchEvent::ActiveMatchChanged)
3417 }
3418 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3419 let inmemory_selections = selections
3420 .iter()
3421 .map(|s| {
3422 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3423 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3424 })
3425 .collect();
3426 self.update_restoration_data(cx, |data| {
3427 data.selections = inmemory_selections;
3428 });
3429
3430 if WorkspaceSettings::get(None, cx).restore_on_startup
3431 != RestoreOnStartupBehavior::EmptyTab
3432 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3433 {
3434 let snapshot = self.buffer().read(cx).snapshot(cx);
3435 let selections = selections.clone();
3436 let background_executor = cx.background_executor().clone();
3437 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3438 self.serialize_selections = cx.background_spawn(async move {
3439 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3440 let db_selections = selections
3441 .iter()
3442 .map(|selection| {
3443 (
3444 selection.start.to_offset(&snapshot).0,
3445 selection.end.to_offset(&snapshot).0,
3446 )
3447 })
3448 .collect();
3449
3450 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3451 .await
3452 .with_context(|| {
3453 format!(
3454 "persisting editor selections for editor {editor_id}, \
3455 workspace {workspace_id:?}"
3456 )
3457 })
3458 .log_err();
3459 });
3460 }
3461 }
3462
3463 cx.notify();
3464 }
3465
3466 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3467 use text::ToOffset as _;
3468 use text::ToPoint as _;
3469
3470 if self.mode.is_minimap()
3471 || WorkspaceSettings::get(None, cx).restore_on_startup
3472 == RestoreOnStartupBehavior::EmptyTab
3473 {
3474 return;
3475 }
3476
3477 if !self.buffer().read(cx).is_singleton() {
3478 return;
3479 }
3480
3481 let display_snapshot = self
3482 .display_map
3483 .update(cx, |display_map, cx| display_map.snapshot(cx));
3484 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3485 return;
3486 };
3487 let inmemory_folds = display_snapshot
3488 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3489 .map(|fold| {
3490 fold.range.start.text_anchor.to_point(&snapshot)
3491 ..fold.range.end.text_anchor.to_point(&snapshot)
3492 })
3493 .collect();
3494 self.update_restoration_data(cx, |data| {
3495 data.folds = inmemory_folds;
3496 });
3497
3498 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3499 return;
3500 };
3501 let background_executor = cx.background_executor().clone();
3502 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3503 let db_folds = display_snapshot
3504 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3505 .map(|fold| {
3506 (
3507 fold.range.start.text_anchor.to_offset(&snapshot),
3508 fold.range.end.text_anchor.to_offset(&snapshot),
3509 )
3510 })
3511 .collect();
3512 self.serialize_folds = cx.background_spawn(async move {
3513 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3514 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3515 .await
3516 .with_context(|| {
3517 format!(
3518 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3519 )
3520 })
3521 .log_err();
3522 });
3523 }
3524
3525 pub fn sync_selections(
3526 &mut self,
3527 other: Entity<Editor>,
3528 cx: &mut Context<Self>,
3529 ) -> gpui::Subscription {
3530 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3531 if !other_selections.is_empty() {
3532 self.selections
3533 .change_with(&self.display_snapshot(cx), |selections| {
3534 selections.select_anchors(other_selections);
3535 });
3536 }
3537
3538 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3539 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3540 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3541 if other_selections.is_empty() {
3542 return;
3543 }
3544 let snapshot = this.display_snapshot(cx);
3545 this.selections.change_with(&snapshot, |selections| {
3546 selections.select_anchors(other_selections);
3547 });
3548 }
3549 });
3550
3551 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3552 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3553 let these_selections = this.selections.disjoint_anchors().to_vec();
3554 if these_selections.is_empty() {
3555 return;
3556 }
3557 other.update(cx, |other_editor, cx| {
3558 let snapshot = other_editor.display_snapshot(cx);
3559 other_editor
3560 .selections
3561 .change_with(&snapshot, |selections| {
3562 selections.select_anchors(these_selections);
3563 })
3564 });
3565 }
3566 });
3567
3568 Subscription::join(other_subscription, this_subscription)
3569 }
3570
3571 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3572 if self.buffer().read(cx).is_singleton() {
3573 return;
3574 }
3575 let snapshot = self.buffer.read(cx).snapshot(cx);
3576 let buffer_ids: HashSet<BufferId> = self
3577 .selections
3578 .disjoint_anchor_ranges()
3579 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3580 .collect();
3581 for buffer_id in buffer_ids {
3582 self.unfold_buffer(buffer_id, cx);
3583 }
3584 }
3585
3586 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3587 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3588 /// effects of selection change occur at the end of the transaction.
3589 pub fn change_selections<R>(
3590 &mut self,
3591 effects: SelectionEffects,
3592 window: &mut Window,
3593 cx: &mut Context<Self>,
3594 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3595 ) -> R {
3596 let snapshot = self.display_snapshot(cx);
3597 if let Some(state) = &mut self.deferred_selection_effects_state {
3598 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3599 state.effects.completions = effects.completions;
3600 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3601 let (changed, result) = self.selections.change_with(&snapshot, change);
3602 state.changed |= changed;
3603 return result;
3604 }
3605 let mut state = DeferredSelectionEffectsState {
3606 changed: false,
3607 effects,
3608 old_cursor_position: self.selections.newest_anchor().head(),
3609 history_entry: SelectionHistoryEntry {
3610 selections: self.selections.disjoint_anchors_arc(),
3611 select_next_state: self.select_next_state.clone(),
3612 select_prev_state: self.select_prev_state.clone(),
3613 add_selections_state: self.add_selections_state.clone(),
3614 },
3615 };
3616 let (changed, result) = self.selections.change_with(&snapshot, change);
3617 state.changed = state.changed || changed;
3618 if self.defer_selection_effects {
3619 self.deferred_selection_effects_state = Some(state);
3620 } else {
3621 self.apply_selection_effects(state, window, cx);
3622 }
3623 result
3624 }
3625
3626 /// Defers the effects of selection change, so that the effects of multiple calls to
3627 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3628 /// to selection history and the state of popovers based on selection position aren't
3629 /// erroneously updated.
3630 pub fn with_selection_effects_deferred<R>(
3631 &mut self,
3632 window: &mut Window,
3633 cx: &mut Context<Self>,
3634 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3635 ) -> R {
3636 let already_deferred = self.defer_selection_effects;
3637 self.defer_selection_effects = true;
3638 let result = update(self, window, cx);
3639 if !already_deferred {
3640 self.defer_selection_effects = false;
3641 if let Some(state) = self.deferred_selection_effects_state.take() {
3642 self.apply_selection_effects(state, window, cx);
3643 }
3644 }
3645 result
3646 }
3647
3648 fn apply_selection_effects(
3649 &mut self,
3650 state: DeferredSelectionEffectsState,
3651 window: &mut Window,
3652 cx: &mut Context<Self>,
3653 ) {
3654 if state.changed {
3655 self.selection_history.push(state.history_entry);
3656
3657 if let Some(autoscroll) = state.effects.scroll {
3658 self.request_autoscroll(autoscroll, cx);
3659 }
3660
3661 let old_cursor_position = &state.old_cursor_position;
3662
3663 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3664
3665 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3666 self.show_signature_help(&ShowSignatureHelp, window, cx);
3667 }
3668 }
3669 }
3670
3671 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3672 where
3673 I: IntoIterator<Item = (Range<S>, T)>,
3674 S: ToOffset,
3675 T: Into<Arc<str>>,
3676 {
3677 if self.read_only(cx) {
3678 return;
3679 }
3680
3681 self.buffer
3682 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3683 }
3684
3685 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3686 where
3687 I: IntoIterator<Item = (Range<S>, T)>,
3688 S: ToOffset,
3689 T: Into<Arc<str>>,
3690 {
3691 if self.read_only(cx) {
3692 return;
3693 }
3694
3695 self.buffer.update(cx, |buffer, cx| {
3696 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3697 });
3698 }
3699
3700 pub fn edit_with_block_indent<I, S, T>(
3701 &mut self,
3702 edits: I,
3703 original_indent_columns: Vec<Option<u32>>,
3704 cx: &mut Context<Self>,
3705 ) where
3706 I: IntoIterator<Item = (Range<S>, T)>,
3707 S: ToOffset,
3708 T: Into<Arc<str>>,
3709 {
3710 if self.read_only(cx) {
3711 return;
3712 }
3713
3714 self.buffer.update(cx, |buffer, cx| {
3715 buffer.edit(
3716 edits,
3717 Some(AutoindentMode::Block {
3718 original_indent_columns,
3719 }),
3720 cx,
3721 )
3722 });
3723 }
3724
3725 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3726 self.hide_context_menu(window, cx);
3727
3728 match phase {
3729 SelectPhase::Begin {
3730 position,
3731 add,
3732 click_count,
3733 } => self.begin_selection(position, add, click_count, window, cx),
3734 SelectPhase::BeginColumnar {
3735 position,
3736 goal_column,
3737 reset,
3738 mode,
3739 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3740 SelectPhase::Extend {
3741 position,
3742 click_count,
3743 } => self.extend_selection(position, click_count, window, cx),
3744 SelectPhase::Update {
3745 position,
3746 goal_column,
3747 scroll_delta,
3748 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3749 SelectPhase::End => self.end_selection(window, cx),
3750 }
3751 }
3752
3753 fn extend_selection(
3754 &mut self,
3755 position: DisplayPoint,
3756 click_count: usize,
3757 window: &mut Window,
3758 cx: &mut Context<Self>,
3759 ) {
3760 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3761 let tail = self
3762 .selections
3763 .newest::<MultiBufferOffset>(&display_map)
3764 .tail();
3765 let click_count = click_count.max(match self.selections.select_mode() {
3766 SelectMode::Character => 1,
3767 SelectMode::Word(_) => 2,
3768 SelectMode::Line(_) => 3,
3769 SelectMode::All => 4,
3770 });
3771 self.begin_selection(position, false, click_count, window, cx);
3772
3773 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3774
3775 let current_selection = match self.selections.select_mode() {
3776 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3777 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3778 };
3779
3780 let mut pending_selection = self
3781 .selections
3782 .pending_anchor()
3783 .cloned()
3784 .expect("extend_selection not called with pending selection");
3785
3786 if pending_selection
3787 .start
3788 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3789 == Ordering::Greater
3790 {
3791 pending_selection.start = current_selection.start;
3792 }
3793 if pending_selection
3794 .end
3795 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3796 == Ordering::Less
3797 {
3798 pending_selection.end = current_selection.end;
3799 pending_selection.reversed = true;
3800 }
3801
3802 let mut pending_mode = self.selections.pending_mode().unwrap();
3803 match &mut pending_mode {
3804 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3805 _ => {}
3806 }
3807
3808 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3809 SelectionEffects::scroll(Autoscroll::fit())
3810 } else {
3811 SelectionEffects::no_scroll()
3812 };
3813
3814 self.change_selections(effects, window, cx, |s| {
3815 s.set_pending(pending_selection.clone(), pending_mode);
3816 s.set_is_extending(true);
3817 });
3818 }
3819
3820 fn begin_selection(
3821 &mut self,
3822 position: DisplayPoint,
3823 add: bool,
3824 click_count: usize,
3825 window: &mut Window,
3826 cx: &mut Context<Self>,
3827 ) {
3828 if !self.focus_handle.is_focused(window) {
3829 self.last_focused_descendant = None;
3830 window.focus(&self.focus_handle);
3831 }
3832
3833 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3834 let buffer = display_map.buffer_snapshot();
3835 let position = display_map.clip_point(position, Bias::Left);
3836
3837 let start;
3838 let end;
3839 let mode;
3840 let mut auto_scroll;
3841 match click_count {
3842 1 => {
3843 start = buffer.anchor_before(position.to_point(&display_map));
3844 end = start;
3845 mode = SelectMode::Character;
3846 auto_scroll = true;
3847 }
3848 2 => {
3849 let position = display_map
3850 .clip_point(position, Bias::Left)
3851 .to_offset(&display_map, Bias::Left);
3852 let (range, _) = buffer.surrounding_word(position, None);
3853 start = buffer.anchor_before(range.start);
3854 end = buffer.anchor_before(range.end);
3855 mode = SelectMode::Word(start..end);
3856 auto_scroll = true;
3857 }
3858 3 => {
3859 let position = display_map
3860 .clip_point(position, Bias::Left)
3861 .to_point(&display_map);
3862 let line_start = display_map.prev_line_boundary(position).0;
3863 let next_line_start = buffer.clip_point(
3864 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3865 Bias::Left,
3866 );
3867 start = buffer.anchor_before(line_start);
3868 end = buffer.anchor_before(next_line_start);
3869 mode = SelectMode::Line(start..end);
3870 auto_scroll = true;
3871 }
3872 _ => {
3873 start = buffer.anchor_before(MultiBufferOffset(0));
3874 end = buffer.anchor_before(buffer.len());
3875 mode = SelectMode::All;
3876 auto_scroll = false;
3877 }
3878 }
3879 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3880
3881 let point_to_delete: Option<usize> = {
3882 let selected_points: Vec<Selection<Point>> =
3883 self.selections.disjoint_in_range(start..end, &display_map);
3884
3885 if !add || click_count > 1 {
3886 None
3887 } else if !selected_points.is_empty() {
3888 Some(selected_points[0].id)
3889 } else {
3890 let clicked_point_already_selected =
3891 self.selections.disjoint_anchors().iter().find(|selection| {
3892 selection.start.to_point(buffer) == start.to_point(buffer)
3893 || selection.end.to_point(buffer) == end.to_point(buffer)
3894 });
3895
3896 clicked_point_already_selected.map(|selection| selection.id)
3897 }
3898 };
3899
3900 let selections_count = self.selections.count();
3901 let effects = if auto_scroll {
3902 SelectionEffects::default()
3903 } else {
3904 SelectionEffects::no_scroll()
3905 };
3906
3907 self.change_selections(effects, window, cx, |s| {
3908 if let Some(point_to_delete) = point_to_delete {
3909 s.delete(point_to_delete);
3910
3911 if selections_count == 1 {
3912 s.set_pending_anchor_range(start..end, mode);
3913 }
3914 } else {
3915 if !add {
3916 s.clear_disjoint();
3917 }
3918
3919 s.set_pending_anchor_range(start..end, mode);
3920 }
3921 });
3922 }
3923
3924 fn begin_columnar_selection(
3925 &mut self,
3926 position: DisplayPoint,
3927 goal_column: u32,
3928 reset: bool,
3929 mode: ColumnarMode,
3930 window: &mut Window,
3931 cx: &mut Context<Self>,
3932 ) {
3933 if !self.focus_handle.is_focused(window) {
3934 self.last_focused_descendant = None;
3935 window.focus(&self.focus_handle);
3936 }
3937
3938 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3939
3940 if reset {
3941 let pointer_position = display_map
3942 .buffer_snapshot()
3943 .anchor_before(position.to_point(&display_map));
3944
3945 self.change_selections(
3946 SelectionEffects::scroll(Autoscroll::newest()),
3947 window,
3948 cx,
3949 |s| {
3950 s.clear_disjoint();
3951 s.set_pending_anchor_range(
3952 pointer_position..pointer_position,
3953 SelectMode::Character,
3954 );
3955 },
3956 );
3957 };
3958
3959 let tail = self.selections.newest::<Point>(&display_map).tail();
3960 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3961 self.columnar_selection_state = match mode {
3962 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3963 selection_tail: selection_anchor,
3964 display_point: if reset {
3965 if position.column() != goal_column {
3966 Some(DisplayPoint::new(position.row(), goal_column))
3967 } else {
3968 None
3969 }
3970 } else {
3971 None
3972 },
3973 }),
3974 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3975 selection_tail: selection_anchor,
3976 }),
3977 };
3978
3979 if !reset {
3980 self.select_columns(position, goal_column, &display_map, window, cx);
3981 }
3982 }
3983
3984 fn update_selection(
3985 &mut self,
3986 position: DisplayPoint,
3987 goal_column: u32,
3988 scroll_delta: gpui::Point<f32>,
3989 window: &mut Window,
3990 cx: &mut Context<Self>,
3991 ) {
3992 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3993
3994 if self.columnar_selection_state.is_some() {
3995 self.select_columns(position, goal_column, &display_map, window, cx);
3996 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3997 let buffer = display_map.buffer_snapshot();
3998 let head;
3999 let tail;
4000 let mode = self.selections.pending_mode().unwrap();
4001 match &mode {
4002 SelectMode::Character => {
4003 head = position.to_point(&display_map);
4004 tail = pending.tail().to_point(buffer);
4005 }
4006 SelectMode::Word(original_range) => {
4007 let offset = display_map
4008 .clip_point(position, Bias::Left)
4009 .to_offset(&display_map, Bias::Left);
4010 let original_range = original_range.to_offset(buffer);
4011
4012 let head_offset = if buffer.is_inside_word(offset, None)
4013 || original_range.contains(&offset)
4014 {
4015 let (word_range, _) = buffer.surrounding_word(offset, None);
4016 if word_range.start < original_range.start {
4017 word_range.start
4018 } else {
4019 word_range.end
4020 }
4021 } else {
4022 offset
4023 };
4024
4025 head = head_offset.to_point(buffer);
4026 if head_offset <= original_range.start {
4027 tail = original_range.end.to_point(buffer);
4028 } else {
4029 tail = original_range.start.to_point(buffer);
4030 }
4031 }
4032 SelectMode::Line(original_range) => {
4033 let original_range = original_range.to_point(display_map.buffer_snapshot());
4034
4035 let position = display_map
4036 .clip_point(position, Bias::Left)
4037 .to_point(&display_map);
4038 let line_start = display_map.prev_line_boundary(position).0;
4039 let next_line_start = buffer.clip_point(
4040 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4041 Bias::Left,
4042 );
4043
4044 if line_start < original_range.start {
4045 head = line_start
4046 } else {
4047 head = next_line_start
4048 }
4049
4050 if head <= original_range.start {
4051 tail = original_range.end;
4052 } else {
4053 tail = original_range.start;
4054 }
4055 }
4056 SelectMode::All => {
4057 return;
4058 }
4059 };
4060
4061 if head < tail {
4062 pending.start = buffer.anchor_before(head);
4063 pending.end = buffer.anchor_before(tail);
4064 pending.reversed = true;
4065 } else {
4066 pending.start = buffer.anchor_before(tail);
4067 pending.end = buffer.anchor_before(head);
4068 pending.reversed = false;
4069 }
4070
4071 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4072 s.set_pending(pending.clone(), mode);
4073 });
4074 } else {
4075 log::error!("update_selection dispatched with no pending selection");
4076 return;
4077 }
4078
4079 self.apply_scroll_delta(scroll_delta, window, cx);
4080 cx.notify();
4081 }
4082
4083 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4084 self.columnar_selection_state.take();
4085 if let Some(pending_mode) = self.selections.pending_mode() {
4086 let selections = self
4087 .selections
4088 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4089 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4090 s.select(selections);
4091 s.clear_pending();
4092 if s.is_extending() {
4093 s.set_is_extending(false);
4094 } else {
4095 s.set_select_mode(pending_mode);
4096 }
4097 });
4098 }
4099 }
4100
4101 fn select_columns(
4102 &mut self,
4103 head: DisplayPoint,
4104 goal_column: u32,
4105 display_map: &DisplaySnapshot,
4106 window: &mut Window,
4107 cx: &mut Context<Self>,
4108 ) {
4109 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4110 return;
4111 };
4112
4113 let tail = match columnar_state {
4114 ColumnarSelectionState::FromMouse {
4115 selection_tail,
4116 display_point,
4117 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4118 ColumnarSelectionState::FromSelection { selection_tail } => {
4119 selection_tail.to_display_point(display_map)
4120 }
4121 };
4122
4123 let start_row = cmp::min(tail.row(), head.row());
4124 let end_row = cmp::max(tail.row(), head.row());
4125 let start_column = cmp::min(tail.column(), goal_column);
4126 let end_column = cmp::max(tail.column(), goal_column);
4127 let reversed = start_column < tail.column();
4128
4129 let selection_ranges = (start_row.0..=end_row.0)
4130 .map(DisplayRow)
4131 .filter_map(|row| {
4132 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4133 || start_column <= display_map.line_len(row))
4134 && !display_map.is_block_line(row)
4135 {
4136 let start = display_map
4137 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4138 .to_point(display_map);
4139 let end = display_map
4140 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4141 .to_point(display_map);
4142 if reversed {
4143 Some(end..start)
4144 } else {
4145 Some(start..end)
4146 }
4147 } else {
4148 None
4149 }
4150 })
4151 .collect::<Vec<_>>();
4152 if selection_ranges.is_empty() {
4153 return;
4154 }
4155
4156 let ranges = match columnar_state {
4157 ColumnarSelectionState::FromMouse { .. } => {
4158 let mut non_empty_ranges = selection_ranges
4159 .iter()
4160 .filter(|selection_range| selection_range.start != selection_range.end)
4161 .peekable();
4162 if non_empty_ranges.peek().is_some() {
4163 non_empty_ranges.cloned().collect()
4164 } else {
4165 selection_ranges
4166 }
4167 }
4168 _ => selection_ranges,
4169 };
4170
4171 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4172 s.select_ranges(ranges);
4173 });
4174 cx.notify();
4175 }
4176
4177 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4178 self.selections
4179 .all_adjusted(snapshot)
4180 .iter()
4181 .any(|selection| !selection.is_empty())
4182 }
4183
4184 pub fn has_pending_nonempty_selection(&self) -> bool {
4185 let pending_nonempty_selection = match self.selections.pending_anchor() {
4186 Some(Selection { start, end, .. }) => start != end,
4187 None => false,
4188 };
4189
4190 pending_nonempty_selection
4191 || (self.columnar_selection_state.is_some()
4192 && self.selections.disjoint_anchors().len() > 1)
4193 }
4194
4195 pub fn has_pending_selection(&self) -> bool {
4196 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4197 }
4198
4199 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4200 self.selection_mark_mode = false;
4201 self.selection_drag_state = SelectionDragState::None;
4202
4203 if self.dismiss_menus_and_popups(true, window, cx) {
4204 cx.notify();
4205 return;
4206 }
4207 if self.clear_expanded_diff_hunks(cx) {
4208 cx.notify();
4209 return;
4210 }
4211 if self.show_git_blame_gutter {
4212 self.show_git_blame_gutter = false;
4213 cx.notify();
4214 return;
4215 }
4216
4217 if self.mode.is_full()
4218 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4219 {
4220 cx.notify();
4221 return;
4222 }
4223
4224 cx.propagate();
4225 }
4226
4227 pub fn dismiss_menus_and_popups(
4228 &mut self,
4229 is_user_requested: bool,
4230 window: &mut Window,
4231 cx: &mut Context<Self>,
4232 ) -> bool {
4233 let mut dismissed = false;
4234
4235 dismissed |= self.take_rename(false, window, cx).is_some();
4236 dismissed |= self.hide_blame_popover(true, cx);
4237 dismissed |= hide_hover(self, cx);
4238 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4239 dismissed |= self.hide_context_menu(window, cx).is_some();
4240 dismissed |= self.mouse_context_menu.take().is_some();
4241 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4242 dismissed |= self.snippet_stack.pop().is_some();
4243
4244 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4245 self.dismiss_diagnostics(cx);
4246 dismissed = true;
4247 }
4248
4249 dismissed
4250 }
4251
4252 fn linked_editing_ranges_for(
4253 &self,
4254 selection: Range<text::Anchor>,
4255 cx: &App,
4256 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4257 if self.linked_edit_ranges.is_empty() {
4258 return None;
4259 }
4260 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4261 selection.end.buffer_id.and_then(|end_buffer_id| {
4262 if selection.start.buffer_id != Some(end_buffer_id) {
4263 return None;
4264 }
4265 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4266 let snapshot = buffer.read(cx).snapshot();
4267 self.linked_edit_ranges
4268 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4269 .map(|ranges| (ranges, snapshot, buffer))
4270 })?;
4271 use text::ToOffset as TO;
4272 // find offset from the start of current range to current cursor position
4273 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4274
4275 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4276 let start_difference = start_offset - start_byte_offset;
4277 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4278 let end_difference = end_offset - start_byte_offset;
4279 // Current range has associated linked ranges.
4280 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4281 for range in linked_ranges.iter() {
4282 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4283 let end_offset = start_offset + end_difference;
4284 let start_offset = start_offset + start_difference;
4285 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4286 continue;
4287 }
4288 if self.selections.disjoint_anchor_ranges().any(|s| {
4289 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4290 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4291 {
4292 return false;
4293 }
4294 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4295 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4296 }) {
4297 continue;
4298 }
4299 let start = buffer_snapshot.anchor_after(start_offset);
4300 let end = buffer_snapshot.anchor_after(end_offset);
4301 linked_edits
4302 .entry(buffer.clone())
4303 .or_default()
4304 .push(start..end);
4305 }
4306 Some(linked_edits)
4307 }
4308
4309 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4310 let text: Arc<str> = text.into();
4311
4312 if self.read_only(cx) {
4313 return;
4314 }
4315
4316 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4317
4318 self.unfold_buffers_with_selections(cx);
4319
4320 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4321 let mut bracket_inserted = false;
4322 let mut edits = Vec::new();
4323 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4324 let mut new_selections = Vec::with_capacity(selections.len());
4325 let mut new_autoclose_regions = Vec::new();
4326 let snapshot = self.buffer.read(cx).read(cx);
4327 let mut clear_linked_edit_ranges = false;
4328
4329 for (selection, autoclose_region) in
4330 self.selections_with_autoclose_regions(selections, &snapshot)
4331 {
4332 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4333 // Determine if the inserted text matches the opening or closing
4334 // bracket of any of this language's bracket pairs.
4335 let mut bracket_pair = None;
4336 let mut is_bracket_pair_start = false;
4337 let mut is_bracket_pair_end = false;
4338 if !text.is_empty() {
4339 let mut bracket_pair_matching_end = None;
4340 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4341 // and they are removing the character that triggered IME popup.
4342 for (pair, enabled) in scope.brackets() {
4343 if !pair.close && !pair.surround {
4344 continue;
4345 }
4346
4347 if enabled && pair.start.ends_with(text.as_ref()) {
4348 let prefix_len = pair.start.len() - text.len();
4349 let preceding_text_matches_prefix = prefix_len == 0
4350 || (selection.start.column >= (prefix_len as u32)
4351 && snapshot.contains_str_at(
4352 Point::new(
4353 selection.start.row,
4354 selection.start.column - (prefix_len as u32),
4355 ),
4356 &pair.start[..prefix_len],
4357 ));
4358 if preceding_text_matches_prefix {
4359 bracket_pair = Some(pair.clone());
4360 is_bracket_pair_start = true;
4361 break;
4362 }
4363 }
4364 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4365 {
4366 // take first bracket pair matching end, but don't break in case a later bracket
4367 // pair matches start
4368 bracket_pair_matching_end = Some(pair.clone());
4369 }
4370 }
4371 if let Some(end) = bracket_pair_matching_end
4372 && bracket_pair.is_none()
4373 {
4374 bracket_pair = Some(end);
4375 is_bracket_pair_end = true;
4376 }
4377 }
4378
4379 if let Some(bracket_pair) = bracket_pair {
4380 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4381 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4382 let auto_surround =
4383 self.use_auto_surround && snapshot_settings.use_auto_surround;
4384 if selection.is_empty() {
4385 if is_bracket_pair_start {
4386 // If the inserted text is a suffix of an opening bracket and the
4387 // selection is preceded by the rest of the opening bracket, then
4388 // insert the closing bracket.
4389 let following_text_allows_autoclose = snapshot
4390 .chars_at(selection.start)
4391 .next()
4392 .is_none_or(|c| scope.should_autoclose_before(c));
4393
4394 let preceding_text_allows_autoclose = selection.start.column == 0
4395 || snapshot
4396 .reversed_chars_at(selection.start)
4397 .next()
4398 .is_none_or(|c| {
4399 bracket_pair.start != bracket_pair.end
4400 || !snapshot
4401 .char_classifier_at(selection.start)
4402 .is_word(c)
4403 });
4404
4405 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4406 && bracket_pair.start.len() == 1
4407 {
4408 let target = bracket_pair.start.chars().next().unwrap();
4409 let mut byte_offset = 0u32;
4410 let current_line_count = snapshot
4411 .reversed_chars_at(selection.start)
4412 .take_while(|&c| c != '\n')
4413 .filter(|c| {
4414 byte_offset += c.len_utf8() as u32;
4415 if *c != target {
4416 return false;
4417 }
4418
4419 let point = Point::new(
4420 selection.start.row,
4421 selection.start.column.saturating_sub(byte_offset),
4422 );
4423
4424 let is_enabled = snapshot
4425 .language_scope_at(point)
4426 .and_then(|scope| {
4427 scope
4428 .brackets()
4429 .find(|(pair, _)| {
4430 pair.start == bracket_pair.start
4431 })
4432 .map(|(_, enabled)| enabled)
4433 })
4434 .unwrap_or(true);
4435
4436 let is_delimiter = snapshot
4437 .language_scope_at(Point::new(
4438 point.row,
4439 point.column + 1,
4440 ))
4441 .and_then(|scope| {
4442 scope
4443 .brackets()
4444 .find(|(pair, _)| {
4445 pair.start == bracket_pair.start
4446 })
4447 .map(|(_, enabled)| !enabled)
4448 })
4449 .unwrap_or(false);
4450
4451 is_enabled && !is_delimiter
4452 })
4453 .count();
4454 current_line_count % 2 == 1
4455 } else {
4456 false
4457 };
4458
4459 if autoclose
4460 && bracket_pair.close
4461 && following_text_allows_autoclose
4462 && preceding_text_allows_autoclose
4463 && !is_closing_quote
4464 {
4465 let anchor = snapshot.anchor_before(selection.end);
4466 new_selections.push((selection.map(|_| anchor), text.len()));
4467 new_autoclose_regions.push((
4468 anchor,
4469 text.len(),
4470 selection.id,
4471 bracket_pair.clone(),
4472 ));
4473 edits.push((
4474 selection.range(),
4475 format!("{}{}", text, bracket_pair.end).into(),
4476 ));
4477 bracket_inserted = true;
4478 continue;
4479 }
4480 }
4481
4482 if let Some(region) = autoclose_region {
4483 // If the selection is followed by an auto-inserted closing bracket,
4484 // then don't insert that closing bracket again; just move the selection
4485 // past the closing bracket.
4486 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4487 && text.as_ref() == region.pair.end.as_str()
4488 && snapshot.contains_str_at(region.range.end, text.as_ref());
4489 if should_skip {
4490 let anchor = snapshot.anchor_after(selection.end);
4491 new_selections
4492 .push((selection.map(|_| anchor), region.pair.end.len()));
4493 continue;
4494 }
4495 }
4496
4497 let always_treat_brackets_as_autoclosed = snapshot
4498 .language_settings_at(selection.start, cx)
4499 .always_treat_brackets_as_autoclosed;
4500 if always_treat_brackets_as_autoclosed
4501 && is_bracket_pair_end
4502 && snapshot.contains_str_at(selection.end, text.as_ref())
4503 {
4504 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4505 // and the inserted text is a closing bracket and the selection is followed
4506 // by the closing bracket then move the selection past the closing bracket.
4507 let anchor = snapshot.anchor_after(selection.end);
4508 new_selections.push((selection.map(|_| anchor), text.len()));
4509 continue;
4510 }
4511 }
4512 // If an opening bracket is 1 character long and is typed while
4513 // text is selected, then surround that text with the bracket pair.
4514 else if auto_surround
4515 && bracket_pair.surround
4516 && is_bracket_pair_start
4517 && bracket_pair.start.chars().count() == 1
4518 {
4519 edits.push((selection.start..selection.start, text.clone()));
4520 edits.push((
4521 selection.end..selection.end,
4522 bracket_pair.end.as_str().into(),
4523 ));
4524 bracket_inserted = true;
4525 new_selections.push((
4526 Selection {
4527 id: selection.id,
4528 start: snapshot.anchor_after(selection.start),
4529 end: snapshot.anchor_before(selection.end),
4530 reversed: selection.reversed,
4531 goal: selection.goal,
4532 },
4533 0,
4534 ));
4535 continue;
4536 }
4537 }
4538 }
4539
4540 if self.auto_replace_emoji_shortcode
4541 && selection.is_empty()
4542 && text.as_ref().ends_with(':')
4543 && let Some(possible_emoji_short_code) =
4544 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4545 && !possible_emoji_short_code.is_empty()
4546 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4547 {
4548 let emoji_shortcode_start = Point::new(
4549 selection.start.row,
4550 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4551 );
4552
4553 // Remove shortcode from buffer
4554 edits.push((
4555 emoji_shortcode_start..selection.start,
4556 "".to_string().into(),
4557 ));
4558 new_selections.push((
4559 Selection {
4560 id: selection.id,
4561 start: snapshot.anchor_after(emoji_shortcode_start),
4562 end: snapshot.anchor_before(selection.start),
4563 reversed: selection.reversed,
4564 goal: selection.goal,
4565 },
4566 0,
4567 ));
4568
4569 // Insert emoji
4570 let selection_start_anchor = snapshot.anchor_after(selection.start);
4571 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4572 edits.push((selection.start..selection.end, emoji.to_string().into()));
4573
4574 continue;
4575 }
4576
4577 // If not handling any auto-close operation, then just replace the selected
4578 // text with the given input and move the selection to the end of the
4579 // newly inserted text.
4580 let anchor = snapshot.anchor_after(selection.end);
4581 if !self.linked_edit_ranges.is_empty() {
4582 let start_anchor = snapshot.anchor_before(selection.start);
4583
4584 let is_word_char = text.chars().next().is_none_or(|char| {
4585 let classifier = snapshot
4586 .char_classifier_at(start_anchor.to_offset(&snapshot))
4587 .scope_context(Some(CharScopeContext::LinkedEdit));
4588 classifier.is_word(char)
4589 });
4590
4591 if is_word_char {
4592 if let Some(ranges) = self
4593 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4594 {
4595 for (buffer, edits) in ranges {
4596 linked_edits
4597 .entry(buffer.clone())
4598 .or_default()
4599 .extend(edits.into_iter().map(|range| (range, text.clone())));
4600 }
4601 }
4602 } else {
4603 clear_linked_edit_ranges = true;
4604 }
4605 }
4606
4607 new_selections.push((selection.map(|_| anchor), 0));
4608 edits.push((selection.start..selection.end, text.clone()));
4609 }
4610
4611 drop(snapshot);
4612
4613 self.transact(window, cx, |this, window, cx| {
4614 if clear_linked_edit_ranges {
4615 this.linked_edit_ranges.clear();
4616 }
4617 let initial_buffer_versions =
4618 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4619
4620 this.buffer.update(cx, |buffer, cx| {
4621 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4622 });
4623 for (buffer, edits) in linked_edits {
4624 buffer.update(cx, |buffer, cx| {
4625 let snapshot = buffer.snapshot();
4626 let edits = edits
4627 .into_iter()
4628 .map(|(range, text)| {
4629 use text::ToPoint as TP;
4630 let end_point = TP::to_point(&range.end, &snapshot);
4631 let start_point = TP::to_point(&range.start, &snapshot);
4632 (start_point..end_point, text)
4633 })
4634 .sorted_by_key(|(range, _)| range.start);
4635 buffer.edit(edits, None, cx);
4636 })
4637 }
4638 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4639 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4640 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4641 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4642 new_anchor_selections,
4643 &map,
4644 )
4645 .zip(new_selection_deltas)
4646 .map(|(selection, delta)| Selection {
4647 id: selection.id,
4648 start: selection.start + delta,
4649 end: selection.end + delta,
4650 reversed: selection.reversed,
4651 goal: SelectionGoal::None,
4652 })
4653 .collect::<Vec<_>>();
4654
4655 let mut i = 0;
4656 for (position, delta, selection_id, pair) in new_autoclose_regions {
4657 let position = position.to_offset(map.buffer_snapshot()) + delta;
4658 let start = map.buffer_snapshot().anchor_before(position);
4659 let end = map.buffer_snapshot().anchor_after(position);
4660 while let Some(existing_state) = this.autoclose_regions.get(i) {
4661 match existing_state
4662 .range
4663 .start
4664 .cmp(&start, map.buffer_snapshot())
4665 {
4666 Ordering::Less => i += 1,
4667 Ordering::Greater => break,
4668 Ordering::Equal => {
4669 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4670 Ordering::Less => i += 1,
4671 Ordering::Equal => break,
4672 Ordering::Greater => break,
4673 }
4674 }
4675 }
4676 }
4677 this.autoclose_regions.insert(
4678 i,
4679 AutocloseRegion {
4680 selection_id,
4681 range: start..end,
4682 pair,
4683 },
4684 );
4685 }
4686
4687 let had_active_edit_prediction = this.has_active_edit_prediction();
4688 this.change_selections(
4689 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4690 window,
4691 cx,
4692 |s| s.select(new_selections),
4693 );
4694
4695 if !bracket_inserted
4696 && let Some(on_type_format_task) =
4697 this.trigger_on_type_formatting(text.to_string(), window, cx)
4698 {
4699 on_type_format_task.detach_and_log_err(cx);
4700 }
4701
4702 let editor_settings = EditorSettings::get_global(cx);
4703 if bracket_inserted
4704 && (editor_settings.auto_signature_help
4705 || editor_settings.show_signature_help_after_edits)
4706 {
4707 this.show_signature_help(&ShowSignatureHelp, window, cx);
4708 }
4709
4710 let trigger_in_words =
4711 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4712 if this.hard_wrap.is_some() {
4713 let latest: Range<Point> = this.selections.newest(&map).range();
4714 if latest.is_empty()
4715 && this
4716 .buffer()
4717 .read(cx)
4718 .snapshot(cx)
4719 .line_len(MultiBufferRow(latest.start.row))
4720 == latest.start.column
4721 {
4722 this.rewrap_impl(
4723 RewrapOptions {
4724 override_language_settings: true,
4725 preserve_existing_whitespace: true,
4726 },
4727 cx,
4728 )
4729 }
4730 }
4731 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4732 refresh_linked_ranges(this, window, cx);
4733 this.refresh_edit_prediction(true, false, window, cx);
4734 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4735 });
4736 }
4737
4738 fn find_possible_emoji_shortcode_at_position(
4739 snapshot: &MultiBufferSnapshot,
4740 position: Point,
4741 ) -> Option<String> {
4742 let mut chars = Vec::new();
4743 let mut found_colon = false;
4744 for char in snapshot.reversed_chars_at(position).take(100) {
4745 // Found a possible emoji shortcode in the middle of the buffer
4746 if found_colon {
4747 if char.is_whitespace() {
4748 chars.reverse();
4749 return Some(chars.iter().collect());
4750 }
4751 // If the previous character is not a whitespace, we are in the middle of a word
4752 // and we only want to complete the shortcode if the word is made up of other emojis
4753 let mut containing_word = String::new();
4754 for ch in snapshot
4755 .reversed_chars_at(position)
4756 .skip(chars.len() + 1)
4757 .take(100)
4758 {
4759 if ch.is_whitespace() {
4760 break;
4761 }
4762 containing_word.push(ch);
4763 }
4764 let containing_word = containing_word.chars().rev().collect::<String>();
4765 if util::word_consists_of_emojis(containing_word.as_str()) {
4766 chars.reverse();
4767 return Some(chars.iter().collect());
4768 }
4769 }
4770
4771 if char.is_whitespace() || !char.is_ascii() {
4772 return None;
4773 }
4774 if char == ':' {
4775 found_colon = true;
4776 } else {
4777 chars.push(char);
4778 }
4779 }
4780 // Found a possible emoji shortcode at the beginning of the buffer
4781 chars.reverse();
4782 Some(chars.iter().collect())
4783 }
4784
4785 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4786 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4787 self.transact(window, cx, |this, window, cx| {
4788 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4789 let selections = this
4790 .selections
4791 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4792 let multi_buffer = this.buffer.read(cx);
4793 let buffer = multi_buffer.snapshot(cx);
4794 selections
4795 .iter()
4796 .map(|selection| {
4797 let start_point = selection.start.to_point(&buffer);
4798 let mut existing_indent =
4799 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4800 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4801 let start = selection.start;
4802 let end = selection.end;
4803 let selection_is_empty = start == end;
4804 let language_scope = buffer.language_scope_at(start);
4805 let (
4806 comment_delimiter,
4807 doc_delimiter,
4808 insert_extra_newline,
4809 indent_on_newline,
4810 indent_on_extra_newline,
4811 ) = if let Some(language) = &language_scope {
4812 let mut insert_extra_newline =
4813 insert_extra_newline_brackets(&buffer, start..end, language)
4814 || insert_extra_newline_tree_sitter(&buffer, start..end);
4815
4816 // Comment extension on newline is allowed only for cursor selections
4817 let comment_delimiter = maybe!({
4818 if !selection_is_empty {
4819 return None;
4820 }
4821
4822 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4823 return None;
4824 }
4825
4826 let delimiters = language.line_comment_prefixes();
4827 let max_len_of_delimiter =
4828 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4829 let (snapshot, range) =
4830 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4831
4832 let num_of_whitespaces = snapshot
4833 .chars_for_range(range.clone())
4834 .take_while(|c| c.is_whitespace())
4835 .count();
4836 let comment_candidate = snapshot
4837 .chars_for_range(range.clone())
4838 .skip(num_of_whitespaces)
4839 .take(max_len_of_delimiter)
4840 .collect::<String>();
4841 let (delimiter, trimmed_len) = delimiters
4842 .iter()
4843 .filter_map(|delimiter| {
4844 let prefix = delimiter.trim_end();
4845 if comment_candidate.starts_with(prefix) {
4846 Some((delimiter, prefix.len()))
4847 } else {
4848 None
4849 }
4850 })
4851 .max_by_key(|(_, len)| *len)?;
4852
4853 if let Some(BlockCommentConfig {
4854 start: block_start, ..
4855 }) = language.block_comment()
4856 {
4857 let block_start_trimmed = block_start.trim_end();
4858 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4859 let line_content = snapshot
4860 .chars_for_range(range)
4861 .skip(num_of_whitespaces)
4862 .take(block_start_trimmed.len())
4863 .collect::<String>();
4864
4865 if line_content.starts_with(block_start_trimmed) {
4866 return None;
4867 }
4868 }
4869 }
4870
4871 let cursor_is_placed_after_comment_marker =
4872 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4873 if cursor_is_placed_after_comment_marker {
4874 Some(delimiter.clone())
4875 } else {
4876 None
4877 }
4878 });
4879
4880 let mut indent_on_newline = IndentSize::spaces(0);
4881 let mut indent_on_extra_newline = IndentSize::spaces(0);
4882
4883 let doc_delimiter = maybe!({
4884 if !selection_is_empty {
4885 return None;
4886 }
4887
4888 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4889 return None;
4890 }
4891
4892 let BlockCommentConfig {
4893 start: start_tag,
4894 end: end_tag,
4895 prefix: delimiter,
4896 tab_size: len,
4897 } = language.documentation_comment()?;
4898 let is_within_block_comment = buffer
4899 .language_scope_at(start_point)
4900 .is_some_and(|scope| scope.override_name() == Some("comment"));
4901 if !is_within_block_comment {
4902 return None;
4903 }
4904
4905 let (snapshot, range) =
4906 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4907
4908 let num_of_whitespaces = snapshot
4909 .chars_for_range(range.clone())
4910 .take_while(|c| c.is_whitespace())
4911 .count();
4912
4913 // 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.
4914 let column = start_point.column;
4915 let cursor_is_after_start_tag = {
4916 let start_tag_len = start_tag.len();
4917 let start_tag_line = snapshot
4918 .chars_for_range(range.clone())
4919 .skip(num_of_whitespaces)
4920 .take(start_tag_len)
4921 .collect::<String>();
4922 if start_tag_line.starts_with(start_tag.as_ref()) {
4923 num_of_whitespaces + start_tag_len <= column as usize
4924 } else {
4925 false
4926 }
4927 };
4928
4929 let cursor_is_after_delimiter = {
4930 let delimiter_trim = delimiter.trim_end();
4931 let delimiter_line = snapshot
4932 .chars_for_range(range.clone())
4933 .skip(num_of_whitespaces)
4934 .take(delimiter_trim.len())
4935 .collect::<String>();
4936 if delimiter_line.starts_with(delimiter_trim) {
4937 num_of_whitespaces + delimiter_trim.len() <= column as usize
4938 } else {
4939 false
4940 }
4941 };
4942
4943 let cursor_is_before_end_tag_if_exists = {
4944 let mut char_position = 0u32;
4945 let mut end_tag_offset = None;
4946
4947 'outer: for chunk in snapshot.text_for_range(range) {
4948 if let Some(byte_pos) = chunk.find(&**end_tag) {
4949 let chars_before_match =
4950 chunk[..byte_pos].chars().count() as u32;
4951 end_tag_offset =
4952 Some(char_position + chars_before_match);
4953 break 'outer;
4954 }
4955 char_position += chunk.chars().count() as u32;
4956 }
4957
4958 if let Some(end_tag_offset) = end_tag_offset {
4959 let cursor_is_before_end_tag = column <= end_tag_offset;
4960 if cursor_is_after_start_tag {
4961 if cursor_is_before_end_tag {
4962 insert_extra_newline = true;
4963 }
4964 let cursor_is_at_start_of_end_tag =
4965 column == end_tag_offset;
4966 if cursor_is_at_start_of_end_tag {
4967 indent_on_extra_newline.len = *len;
4968 }
4969 }
4970 cursor_is_before_end_tag
4971 } else {
4972 true
4973 }
4974 };
4975
4976 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4977 && cursor_is_before_end_tag_if_exists
4978 {
4979 if cursor_is_after_start_tag {
4980 indent_on_newline.len = *len;
4981 }
4982 Some(delimiter.clone())
4983 } else {
4984 None
4985 }
4986 });
4987
4988 (
4989 comment_delimiter,
4990 doc_delimiter,
4991 insert_extra_newline,
4992 indent_on_newline,
4993 indent_on_extra_newline,
4994 )
4995 } else {
4996 (
4997 None,
4998 None,
4999 false,
5000 IndentSize::default(),
5001 IndentSize::default(),
5002 )
5003 };
5004
5005 let prevent_auto_indent = doc_delimiter.is_some();
5006 let delimiter = comment_delimiter.or(doc_delimiter);
5007
5008 let capacity_for_delimiter =
5009 delimiter.as_deref().map(str::len).unwrap_or_default();
5010 let mut new_text = String::with_capacity(
5011 1 + capacity_for_delimiter
5012 + existing_indent.len as usize
5013 + indent_on_newline.len as usize
5014 + indent_on_extra_newline.len as usize,
5015 );
5016 new_text.push('\n');
5017 new_text.extend(existing_indent.chars());
5018 new_text.extend(indent_on_newline.chars());
5019
5020 if let Some(delimiter) = &delimiter {
5021 new_text.push_str(delimiter);
5022 }
5023
5024 if insert_extra_newline {
5025 new_text.push('\n');
5026 new_text.extend(existing_indent.chars());
5027 new_text.extend(indent_on_extra_newline.chars());
5028 }
5029
5030 let anchor = buffer.anchor_after(end);
5031 let new_selection = selection.map(|_| anchor);
5032 (
5033 ((start..end, new_text), prevent_auto_indent),
5034 (insert_extra_newline, new_selection),
5035 )
5036 })
5037 .unzip()
5038 };
5039
5040 let mut auto_indent_edits = Vec::new();
5041 let mut edits = Vec::new();
5042 for (edit, prevent_auto_indent) in edits_with_flags {
5043 if prevent_auto_indent {
5044 edits.push(edit);
5045 } else {
5046 auto_indent_edits.push(edit);
5047 }
5048 }
5049 if !edits.is_empty() {
5050 this.edit(edits, cx);
5051 }
5052 if !auto_indent_edits.is_empty() {
5053 this.edit_with_autoindent(auto_indent_edits, cx);
5054 }
5055
5056 let buffer = this.buffer.read(cx).snapshot(cx);
5057 let new_selections = selection_info
5058 .into_iter()
5059 .map(|(extra_newline_inserted, new_selection)| {
5060 let mut cursor = new_selection.end.to_point(&buffer);
5061 if extra_newline_inserted {
5062 cursor.row -= 1;
5063 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5064 }
5065 new_selection.map(|_| cursor)
5066 })
5067 .collect();
5068
5069 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5070 this.refresh_edit_prediction(true, false, window, cx);
5071 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5072 task.detach_and_log_err(cx);
5073 }
5074 });
5075 }
5076
5077 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5078 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5079
5080 let buffer = self.buffer.read(cx);
5081 let snapshot = buffer.snapshot(cx);
5082
5083 let mut edits = Vec::new();
5084 let mut rows = Vec::new();
5085
5086 for (rows_inserted, selection) in self
5087 .selections
5088 .all_adjusted(&self.display_snapshot(cx))
5089 .into_iter()
5090 .enumerate()
5091 {
5092 let cursor = selection.head();
5093 let row = cursor.row;
5094
5095 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5096
5097 let newline = "\n".to_string();
5098 edits.push((start_of_line..start_of_line, newline));
5099
5100 rows.push(row + rows_inserted as u32);
5101 }
5102
5103 self.transact(window, cx, |editor, window, cx| {
5104 editor.edit(edits, cx);
5105
5106 editor.change_selections(Default::default(), window, cx, |s| {
5107 let mut index = 0;
5108 s.move_cursors_with(|map, _, _| {
5109 let row = rows[index];
5110 index += 1;
5111
5112 let point = Point::new(row, 0);
5113 let boundary = map.next_line_boundary(point).1;
5114 let clipped = map.clip_point(boundary, Bias::Left);
5115
5116 (clipped, SelectionGoal::None)
5117 });
5118 });
5119
5120 let mut indent_edits = Vec::new();
5121 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5122 for row in rows {
5123 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5124 for (row, indent) in indents {
5125 if indent.len == 0 {
5126 continue;
5127 }
5128
5129 let text = match indent.kind {
5130 IndentKind::Space => " ".repeat(indent.len as usize),
5131 IndentKind::Tab => "\t".repeat(indent.len as usize),
5132 };
5133 let point = Point::new(row.0, 0);
5134 indent_edits.push((point..point, text));
5135 }
5136 }
5137 editor.edit(indent_edits, cx);
5138 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5139 format.detach_and_log_err(cx);
5140 }
5141 });
5142 }
5143
5144 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5145 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5146
5147 let buffer = self.buffer.read(cx);
5148 let snapshot = buffer.snapshot(cx);
5149
5150 let mut edits = Vec::new();
5151 let mut rows = Vec::new();
5152 let mut rows_inserted = 0;
5153
5154 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5155 let cursor = selection.head();
5156 let row = cursor.row;
5157
5158 let point = Point::new(row + 1, 0);
5159 let start_of_line = snapshot.clip_point(point, Bias::Left);
5160
5161 let newline = "\n".to_string();
5162 edits.push((start_of_line..start_of_line, newline));
5163
5164 rows_inserted += 1;
5165 rows.push(row + rows_inserted);
5166 }
5167
5168 self.transact(window, cx, |editor, window, cx| {
5169 editor.edit(edits, cx);
5170
5171 editor.change_selections(Default::default(), window, cx, |s| {
5172 let mut index = 0;
5173 s.move_cursors_with(|map, _, _| {
5174 let row = rows[index];
5175 index += 1;
5176
5177 let point = Point::new(row, 0);
5178 let boundary = map.next_line_boundary(point).1;
5179 let clipped = map.clip_point(boundary, Bias::Left);
5180
5181 (clipped, SelectionGoal::None)
5182 });
5183 });
5184
5185 let mut indent_edits = Vec::new();
5186 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5187 for row in rows {
5188 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5189 for (row, indent) in indents {
5190 if indent.len == 0 {
5191 continue;
5192 }
5193
5194 let text = match indent.kind {
5195 IndentKind::Space => " ".repeat(indent.len as usize),
5196 IndentKind::Tab => "\t".repeat(indent.len as usize),
5197 };
5198 let point = Point::new(row.0, 0);
5199 indent_edits.push((point..point, text));
5200 }
5201 }
5202 editor.edit(indent_edits, cx);
5203 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5204 format.detach_and_log_err(cx);
5205 }
5206 });
5207 }
5208
5209 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5210 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5211 original_indent_columns: Vec::new(),
5212 });
5213 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5214 }
5215
5216 fn insert_with_autoindent_mode(
5217 &mut self,
5218 text: &str,
5219 autoindent_mode: Option<AutoindentMode>,
5220 window: &mut Window,
5221 cx: &mut Context<Self>,
5222 ) {
5223 if self.read_only(cx) {
5224 return;
5225 }
5226
5227 let text: Arc<str> = text.into();
5228 self.transact(window, cx, |this, window, cx| {
5229 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5230 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5231 let anchors = {
5232 let snapshot = buffer.read(cx);
5233 old_selections
5234 .iter()
5235 .map(|s| {
5236 let anchor = snapshot.anchor_after(s.head());
5237 s.map(|_| anchor)
5238 })
5239 .collect::<Vec<_>>()
5240 };
5241 buffer.edit(
5242 old_selections
5243 .iter()
5244 .map(|s| (s.start..s.end, text.clone())),
5245 autoindent_mode,
5246 cx,
5247 );
5248 anchors
5249 });
5250
5251 this.change_selections(Default::default(), window, cx, |s| {
5252 s.select_anchors(selection_anchors);
5253 });
5254
5255 cx.notify();
5256 });
5257 }
5258
5259 fn trigger_completion_on_input(
5260 &mut self,
5261 text: &str,
5262 trigger_in_words: bool,
5263 window: &mut Window,
5264 cx: &mut Context<Self>,
5265 ) {
5266 let completions_source = self
5267 .context_menu
5268 .borrow()
5269 .as_ref()
5270 .and_then(|menu| match menu {
5271 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5272 CodeContextMenu::CodeActions(_) => None,
5273 });
5274
5275 match completions_source {
5276 Some(CompletionsMenuSource::Words { .. }) => {
5277 self.open_or_update_completions_menu(
5278 Some(CompletionsMenuSource::Words {
5279 ignore_threshold: false,
5280 }),
5281 None,
5282 trigger_in_words,
5283 window,
5284 cx,
5285 );
5286 }
5287 _ => self.open_or_update_completions_menu(
5288 None,
5289 Some(text.to_owned()).filter(|x| !x.is_empty()),
5290 true,
5291 window,
5292 cx,
5293 ),
5294 }
5295 }
5296
5297 /// If any empty selections is touching the start of its innermost containing autoclose
5298 /// region, expand it to select the brackets.
5299 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5300 let selections = self
5301 .selections
5302 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5303 let buffer = self.buffer.read(cx).read(cx);
5304 let new_selections = self
5305 .selections_with_autoclose_regions(selections, &buffer)
5306 .map(|(mut selection, region)| {
5307 if !selection.is_empty() {
5308 return selection;
5309 }
5310
5311 if let Some(region) = region {
5312 let mut range = region.range.to_offset(&buffer);
5313 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5314 range.start -= region.pair.start.len();
5315 if buffer.contains_str_at(range.start, ®ion.pair.start)
5316 && buffer.contains_str_at(range.end, ®ion.pair.end)
5317 {
5318 range.end += region.pair.end.len();
5319 selection.start = range.start;
5320 selection.end = range.end;
5321
5322 return selection;
5323 }
5324 }
5325 }
5326
5327 let always_treat_brackets_as_autoclosed = buffer
5328 .language_settings_at(selection.start, cx)
5329 .always_treat_brackets_as_autoclosed;
5330
5331 if !always_treat_brackets_as_autoclosed {
5332 return selection;
5333 }
5334
5335 if let Some(scope) = buffer.language_scope_at(selection.start) {
5336 for (pair, enabled) in scope.brackets() {
5337 if !enabled || !pair.close {
5338 continue;
5339 }
5340
5341 if buffer.contains_str_at(selection.start, &pair.end) {
5342 let pair_start_len = pair.start.len();
5343 if buffer.contains_str_at(
5344 selection.start.saturating_sub_usize(pair_start_len),
5345 &pair.start,
5346 ) {
5347 selection.start -= pair_start_len;
5348 selection.end += pair.end.len();
5349
5350 return selection;
5351 }
5352 }
5353 }
5354 }
5355
5356 selection
5357 })
5358 .collect();
5359
5360 drop(buffer);
5361 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5362 selections.select(new_selections)
5363 });
5364 }
5365
5366 /// Iterate the given selections, and for each one, find the smallest surrounding
5367 /// autoclose region. This uses the ordering of the selections and the autoclose
5368 /// regions to avoid repeated comparisons.
5369 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5370 &'a self,
5371 selections: impl IntoIterator<Item = Selection<D>>,
5372 buffer: &'a MultiBufferSnapshot,
5373 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5374 let mut i = 0;
5375 let mut regions = self.autoclose_regions.as_slice();
5376 selections.into_iter().map(move |selection| {
5377 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5378
5379 let mut enclosing = None;
5380 while let Some(pair_state) = regions.get(i) {
5381 if pair_state.range.end.to_offset(buffer) < range.start {
5382 regions = ®ions[i + 1..];
5383 i = 0;
5384 } else if pair_state.range.start.to_offset(buffer) > range.end {
5385 break;
5386 } else {
5387 if pair_state.selection_id == selection.id {
5388 enclosing = Some(pair_state);
5389 }
5390 i += 1;
5391 }
5392 }
5393
5394 (selection, enclosing)
5395 })
5396 }
5397
5398 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5399 fn invalidate_autoclose_regions(
5400 &mut self,
5401 mut selections: &[Selection<Anchor>],
5402 buffer: &MultiBufferSnapshot,
5403 ) {
5404 self.autoclose_regions.retain(|state| {
5405 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5406 return false;
5407 }
5408
5409 let mut i = 0;
5410 while let Some(selection) = selections.get(i) {
5411 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5412 selections = &selections[1..];
5413 continue;
5414 }
5415 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5416 break;
5417 }
5418 if selection.id == state.selection_id {
5419 return true;
5420 } else {
5421 i += 1;
5422 }
5423 }
5424 false
5425 });
5426 }
5427
5428 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5429 let offset = position.to_offset(buffer);
5430 let (word_range, kind) =
5431 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5432 if offset > word_range.start && kind == Some(CharKind::Word) {
5433 Some(
5434 buffer
5435 .text_for_range(word_range.start..offset)
5436 .collect::<String>(),
5437 )
5438 } else {
5439 None
5440 }
5441 }
5442
5443 pub fn visible_excerpts(
5444 &self,
5445 lsp_related_only: bool,
5446 cx: &mut Context<Editor>,
5447 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5448 let project = self.project().cloned();
5449 let multi_buffer = self.buffer().read(cx);
5450 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5451 let multi_buffer_visible_start = self
5452 .scroll_manager
5453 .anchor()
5454 .anchor
5455 .to_point(&multi_buffer_snapshot);
5456 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5457 multi_buffer_visible_start
5458 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5459 Bias::Left,
5460 );
5461 multi_buffer_snapshot
5462 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5463 .into_iter()
5464 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5465 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5466 if !lsp_related_only {
5467 return Some((
5468 excerpt_id,
5469 (
5470 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5471 buffer.version().clone(),
5472 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5473 ),
5474 ));
5475 }
5476
5477 let project = project.as_ref()?.read(cx);
5478 let buffer_file = project::File::from_dyn(buffer.file())?;
5479 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5480 let worktree_entry = buffer_worktree
5481 .read(cx)
5482 .entry_for_id(buffer_file.project_entry_id()?)?;
5483 if worktree_entry.is_ignored {
5484 None
5485 } else {
5486 Some((
5487 excerpt_id,
5488 (
5489 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5490 buffer.version().clone(),
5491 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5492 ),
5493 ))
5494 }
5495 })
5496 .collect()
5497 }
5498
5499 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5500 TextLayoutDetails {
5501 text_system: window.text_system().clone(),
5502 editor_style: self.style.clone().unwrap(),
5503 rem_size: window.rem_size(),
5504 scroll_anchor: self.scroll_manager.anchor(),
5505 visible_rows: self.visible_line_count(),
5506 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5507 }
5508 }
5509
5510 fn trigger_on_type_formatting(
5511 &self,
5512 input: String,
5513 window: &mut Window,
5514 cx: &mut Context<Self>,
5515 ) -> Option<Task<Result<()>>> {
5516 if input.chars().count() != 1 {
5517 return None;
5518 }
5519
5520 let project = self.project()?;
5521 let position = self.selections.newest_anchor().head();
5522 let (buffer, buffer_position) = self
5523 .buffer
5524 .read(cx)
5525 .text_anchor_for_position(position, cx)?;
5526
5527 let settings = language_settings::language_settings(
5528 buffer
5529 .read(cx)
5530 .language_at(buffer_position)
5531 .map(|l| l.name()),
5532 buffer.read(cx).file(),
5533 cx,
5534 );
5535 if !settings.use_on_type_format {
5536 return None;
5537 }
5538
5539 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5540 // hence we do LSP request & edit on host side only — add formats to host's history.
5541 let push_to_lsp_host_history = true;
5542 // If this is not the host, append its history with new edits.
5543 let push_to_client_history = project.read(cx).is_via_collab();
5544
5545 let on_type_formatting = project.update(cx, |project, cx| {
5546 project.on_type_format(
5547 buffer.clone(),
5548 buffer_position,
5549 input,
5550 push_to_lsp_host_history,
5551 cx,
5552 )
5553 });
5554 Some(cx.spawn_in(window, async move |editor, cx| {
5555 if let Some(transaction) = on_type_formatting.await? {
5556 if push_to_client_history {
5557 buffer
5558 .update(cx, |buffer, _| {
5559 buffer.push_transaction(transaction, Instant::now());
5560 buffer.finalize_last_transaction();
5561 })
5562 .ok();
5563 }
5564 editor.update(cx, |editor, cx| {
5565 editor.refresh_document_highlights(cx);
5566 })?;
5567 }
5568 Ok(())
5569 }))
5570 }
5571
5572 pub fn show_word_completions(
5573 &mut self,
5574 _: &ShowWordCompletions,
5575 window: &mut Window,
5576 cx: &mut Context<Self>,
5577 ) {
5578 self.open_or_update_completions_menu(
5579 Some(CompletionsMenuSource::Words {
5580 ignore_threshold: true,
5581 }),
5582 None,
5583 false,
5584 window,
5585 cx,
5586 );
5587 }
5588
5589 pub fn show_completions(
5590 &mut self,
5591 _: &ShowCompletions,
5592 window: &mut Window,
5593 cx: &mut Context<Self>,
5594 ) {
5595 self.open_or_update_completions_menu(None, None, false, window, cx);
5596 }
5597
5598 fn open_or_update_completions_menu(
5599 &mut self,
5600 requested_source: Option<CompletionsMenuSource>,
5601 trigger: Option<String>,
5602 trigger_in_words: bool,
5603 window: &mut Window,
5604 cx: &mut Context<Self>,
5605 ) {
5606 if self.pending_rename.is_some() {
5607 return;
5608 }
5609
5610 let completions_source = self
5611 .context_menu
5612 .borrow()
5613 .as_ref()
5614 .and_then(|menu| match menu {
5615 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5616 CodeContextMenu::CodeActions(_) => None,
5617 });
5618
5619 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5620
5621 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5622 // inserted and selected. To handle that case, the start of the selection is used so that
5623 // the menu starts with all choices.
5624 let position = self
5625 .selections
5626 .newest_anchor()
5627 .start
5628 .bias_right(&multibuffer_snapshot);
5629 if position.diff_base_anchor.is_some() {
5630 return;
5631 }
5632 let buffer_position = multibuffer_snapshot.anchor_before(position);
5633 let Some(buffer) = buffer_position
5634 .text_anchor
5635 .buffer_id
5636 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5637 else {
5638 return;
5639 };
5640 let buffer_snapshot = buffer.read(cx).snapshot();
5641
5642 let menu_is_open = matches!(
5643 self.context_menu.borrow().as_ref(),
5644 Some(CodeContextMenu::Completions(_))
5645 );
5646
5647 let language = buffer_snapshot
5648 .language_at(buffer_position.text_anchor)
5649 .map(|language| language.name());
5650
5651 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5652 let completion_settings = language_settings.completions.clone();
5653
5654 let show_completions_on_input = self
5655 .show_completions_on_input_override
5656 .unwrap_or(language_settings.show_completions_on_input);
5657 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5658 return;
5659 }
5660
5661 let query: Option<Arc<String>> =
5662 Self::completion_query(&multibuffer_snapshot, buffer_position)
5663 .map(|query| query.into());
5664
5665 drop(multibuffer_snapshot);
5666
5667 // Hide the current completions menu when query is empty. Without this, cached
5668 // completions from before the trigger char may be reused (#32774).
5669 if query.is_none() && menu_is_open {
5670 self.hide_context_menu(window, cx);
5671 }
5672
5673 let mut ignore_word_threshold = false;
5674 let provider = match requested_source {
5675 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5676 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5677 ignore_word_threshold = ignore_threshold;
5678 None
5679 }
5680 Some(CompletionsMenuSource::SnippetChoices)
5681 | Some(CompletionsMenuSource::SnippetsOnly) => {
5682 log::error!("bug: SnippetChoices requested_source is not handled");
5683 None
5684 }
5685 };
5686
5687 let sort_completions = provider
5688 .as_ref()
5689 .is_some_and(|provider| provider.sort_completions());
5690
5691 let filter_completions = provider
5692 .as_ref()
5693 .is_none_or(|provider| provider.filter_completions());
5694
5695 let was_snippets_only = matches!(
5696 completions_source,
5697 Some(CompletionsMenuSource::SnippetsOnly)
5698 );
5699
5700 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5701 if filter_completions {
5702 menu.filter(
5703 query.clone().unwrap_or_default(),
5704 buffer_position.text_anchor,
5705 &buffer,
5706 provider.clone(),
5707 window,
5708 cx,
5709 );
5710 }
5711 // When `is_incomplete` is false, no need to re-query completions when the current query
5712 // is a suffix of the initial query.
5713 let was_complete = !menu.is_incomplete;
5714 if was_complete && !was_snippets_only {
5715 // If the new query is a suffix of the old query (typing more characters) and
5716 // the previous result was complete, the existing completions can be filtered.
5717 //
5718 // Note that snippet completions are always complete.
5719 let query_matches = match (&menu.initial_query, &query) {
5720 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5721 (None, _) => true,
5722 _ => false,
5723 };
5724 if query_matches {
5725 let position_matches = if menu.initial_position == position {
5726 true
5727 } else {
5728 let snapshot = self.buffer.read(cx).read(cx);
5729 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5730 };
5731 if position_matches {
5732 return;
5733 }
5734 }
5735 }
5736 };
5737
5738 let Anchor {
5739 excerpt_id: buffer_excerpt_id,
5740 text_anchor: buffer_position,
5741 ..
5742 } = buffer_position;
5743
5744 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5745 buffer_snapshot.surrounding_word(buffer_position, None)
5746 {
5747 let word_to_exclude = buffer_snapshot
5748 .text_for_range(word_range.clone())
5749 .collect::<String>();
5750 (
5751 buffer_snapshot.anchor_before(word_range.start)
5752 ..buffer_snapshot.anchor_after(buffer_position),
5753 Some(word_to_exclude),
5754 )
5755 } else {
5756 (buffer_position..buffer_position, None)
5757 };
5758
5759 let show_completion_documentation = buffer_snapshot
5760 .settings_at(buffer_position, cx)
5761 .show_completion_documentation;
5762
5763 // The document can be large, so stay in reasonable bounds when searching for words,
5764 // otherwise completion pop-up might be slow to appear.
5765 const WORD_LOOKUP_ROWS: u32 = 5_000;
5766 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5767 let min_word_search = buffer_snapshot.clip_point(
5768 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5769 Bias::Left,
5770 );
5771 let max_word_search = buffer_snapshot.clip_point(
5772 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5773 Bias::Right,
5774 );
5775 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5776 ..buffer_snapshot.point_to_offset(max_word_search);
5777
5778 let skip_digits = query
5779 .as_ref()
5780 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5781
5782 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5783 trigger.as_ref().is_none_or(|trigger| {
5784 provider.is_completion_trigger(
5785 &buffer,
5786 position.text_anchor,
5787 trigger,
5788 trigger_in_words,
5789 cx,
5790 )
5791 })
5792 });
5793
5794 let provider_responses = if let Some(provider) = &provider
5795 && load_provider_completions
5796 {
5797 let trigger_character =
5798 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5799 let completion_context = CompletionContext {
5800 trigger_kind: match &trigger_character {
5801 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5802 None => CompletionTriggerKind::INVOKED,
5803 },
5804 trigger_character,
5805 };
5806
5807 provider.completions(
5808 buffer_excerpt_id,
5809 &buffer,
5810 buffer_position,
5811 completion_context,
5812 window,
5813 cx,
5814 )
5815 } else {
5816 Task::ready(Ok(Vec::new()))
5817 };
5818
5819 let load_word_completions = if !self.word_completions_enabled {
5820 false
5821 } else if requested_source
5822 == Some(CompletionsMenuSource::Words {
5823 ignore_threshold: true,
5824 })
5825 {
5826 true
5827 } else {
5828 load_provider_completions
5829 && completion_settings.words != WordsCompletionMode::Disabled
5830 && (ignore_word_threshold || {
5831 let words_min_length = completion_settings.words_min_length;
5832 // check whether word has at least `words_min_length` characters
5833 let query_chars = query.iter().flat_map(|q| q.chars());
5834 query_chars.take(words_min_length).count() == words_min_length
5835 })
5836 };
5837
5838 let mut words = if load_word_completions {
5839 cx.background_spawn({
5840 let buffer_snapshot = buffer_snapshot.clone();
5841 async move {
5842 buffer_snapshot.words_in_range(WordsQuery {
5843 fuzzy_contents: None,
5844 range: word_search_range,
5845 skip_digits,
5846 })
5847 }
5848 })
5849 } else {
5850 Task::ready(BTreeMap::default())
5851 };
5852
5853 let snippets = if let Some(provider) = &provider
5854 && provider.show_snippets()
5855 && let Some(project) = self.project()
5856 {
5857 let char_classifier = buffer_snapshot
5858 .char_classifier_at(buffer_position)
5859 .scope_context(Some(CharScopeContext::Completion));
5860 project.update(cx, |project, cx| {
5861 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5862 })
5863 } else {
5864 Task::ready(Ok(CompletionResponse {
5865 completions: Vec::new(),
5866 display_options: Default::default(),
5867 is_incomplete: false,
5868 }))
5869 };
5870
5871 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5872
5873 let id = post_inc(&mut self.next_completion_id);
5874 let task = cx.spawn_in(window, async move |editor, cx| {
5875 let Ok(()) = editor.update(cx, |this, _| {
5876 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5877 }) else {
5878 return;
5879 };
5880
5881 // TODO: Ideally completions from different sources would be selectively re-queried, so
5882 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5883 let mut completions = Vec::new();
5884 let mut is_incomplete = false;
5885 let mut display_options: Option<CompletionDisplayOptions> = None;
5886 if let Some(provider_responses) = provider_responses.await.log_err()
5887 && !provider_responses.is_empty()
5888 {
5889 for response in provider_responses {
5890 completions.extend(response.completions);
5891 is_incomplete = is_incomplete || response.is_incomplete;
5892 match display_options.as_mut() {
5893 None => {
5894 display_options = Some(response.display_options);
5895 }
5896 Some(options) => options.merge(&response.display_options),
5897 }
5898 }
5899 if completion_settings.words == WordsCompletionMode::Fallback {
5900 words = Task::ready(BTreeMap::default());
5901 }
5902 }
5903 let display_options = display_options.unwrap_or_default();
5904
5905 let mut words = words.await;
5906 if let Some(word_to_exclude) = &word_to_exclude {
5907 words.remove(word_to_exclude);
5908 }
5909 for lsp_completion in &completions {
5910 words.remove(&lsp_completion.new_text);
5911 }
5912 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5913 replace_range: word_replace_range.clone(),
5914 new_text: word.clone(),
5915 label: CodeLabel::plain(word, None),
5916 match_start: None,
5917 snippet_deduplication_key: None,
5918 icon_path: None,
5919 documentation: None,
5920 source: CompletionSource::BufferWord {
5921 word_range,
5922 resolved: false,
5923 },
5924 insert_text_mode: Some(InsertTextMode::AS_IS),
5925 confirm: None,
5926 }));
5927
5928 completions.extend(
5929 snippets
5930 .await
5931 .into_iter()
5932 .flat_map(|response| response.completions),
5933 );
5934
5935 let menu = if completions.is_empty() {
5936 None
5937 } else {
5938 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5939 let languages = editor
5940 .workspace
5941 .as_ref()
5942 .and_then(|(workspace, _)| workspace.upgrade())
5943 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5944 let menu = CompletionsMenu::new(
5945 id,
5946 requested_source.unwrap_or(if load_provider_completions {
5947 CompletionsMenuSource::Normal
5948 } else {
5949 CompletionsMenuSource::SnippetsOnly
5950 }),
5951 sort_completions,
5952 show_completion_documentation,
5953 position,
5954 query.clone(),
5955 is_incomplete,
5956 buffer.clone(),
5957 completions.into(),
5958 editor
5959 .context_menu()
5960 .borrow_mut()
5961 .as_ref()
5962 .map(|menu| menu.primary_scroll_handle()),
5963 display_options,
5964 snippet_sort_order,
5965 languages,
5966 language,
5967 cx,
5968 );
5969
5970 let query = if filter_completions { query } else { None };
5971 let matches_task = menu.do_async_filtering(
5972 query.unwrap_or_default(),
5973 buffer_position,
5974 &buffer,
5975 cx,
5976 );
5977 (menu, matches_task)
5978 }) else {
5979 return;
5980 };
5981
5982 let matches = matches_task.await;
5983
5984 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5985 // Newer menu already set, so exit.
5986 if let Some(CodeContextMenu::Completions(prev_menu)) =
5987 editor.context_menu.borrow().as_ref()
5988 && prev_menu.id > id
5989 {
5990 return;
5991 };
5992
5993 // Only valid to take prev_menu because either the new menu is immediately set
5994 // below, or the menu is hidden.
5995 if let Some(CodeContextMenu::Completions(prev_menu)) =
5996 editor.context_menu.borrow_mut().take()
5997 {
5998 let position_matches =
5999 if prev_menu.initial_position == menu.initial_position {
6000 true
6001 } else {
6002 let snapshot = editor.buffer.read(cx).read(cx);
6003 prev_menu.initial_position.to_offset(&snapshot)
6004 == menu.initial_position.to_offset(&snapshot)
6005 };
6006 if position_matches {
6007 // Preserve markdown cache before `set_filter_results` because it will
6008 // try to populate the documentation cache.
6009 menu.preserve_markdown_cache(prev_menu);
6010 }
6011 };
6012
6013 menu.set_filter_results(matches, provider, window, cx);
6014 }) else {
6015 return;
6016 };
6017
6018 menu.visible().then_some(menu)
6019 };
6020
6021 editor
6022 .update_in(cx, |editor, window, cx| {
6023 if editor.focus_handle.is_focused(window)
6024 && let Some(menu) = menu
6025 {
6026 *editor.context_menu.borrow_mut() =
6027 Some(CodeContextMenu::Completions(menu));
6028
6029 crate::hover_popover::hide_hover(editor, cx);
6030 if editor.show_edit_predictions_in_menu() {
6031 editor.update_visible_edit_prediction(window, cx);
6032 } else {
6033 editor.discard_edit_prediction(false, cx);
6034 }
6035
6036 cx.notify();
6037 return;
6038 }
6039
6040 if editor.completion_tasks.len() <= 1 {
6041 // If there are no more completion tasks and the last menu was empty, we should hide it.
6042 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6043 // If it was already hidden and we don't show edit predictions in the menu,
6044 // we should also show the edit prediction when available.
6045 if was_hidden && editor.show_edit_predictions_in_menu() {
6046 editor.update_visible_edit_prediction(window, cx);
6047 }
6048 }
6049 })
6050 .ok();
6051 });
6052
6053 self.completion_tasks.push((id, task));
6054 }
6055
6056 #[cfg(feature = "test-support")]
6057 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6058 let menu = self.context_menu.borrow();
6059 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6060 let completions = menu.completions.borrow();
6061 Some(completions.to_vec())
6062 } else {
6063 None
6064 }
6065 }
6066
6067 pub fn with_completions_menu_matching_id<R>(
6068 &self,
6069 id: CompletionId,
6070 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6071 ) -> R {
6072 let mut context_menu = self.context_menu.borrow_mut();
6073 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6074 return f(None);
6075 };
6076 if completions_menu.id != id {
6077 return f(None);
6078 }
6079 f(Some(completions_menu))
6080 }
6081
6082 pub fn confirm_completion(
6083 &mut self,
6084 action: &ConfirmCompletion,
6085 window: &mut Window,
6086 cx: &mut Context<Self>,
6087 ) -> Option<Task<Result<()>>> {
6088 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6089 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6090 }
6091
6092 pub fn confirm_completion_insert(
6093 &mut self,
6094 _: &ConfirmCompletionInsert,
6095 window: &mut Window,
6096 cx: &mut Context<Self>,
6097 ) -> Option<Task<Result<()>>> {
6098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6099 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6100 }
6101
6102 pub fn confirm_completion_replace(
6103 &mut self,
6104 _: &ConfirmCompletionReplace,
6105 window: &mut Window,
6106 cx: &mut Context<Self>,
6107 ) -> Option<Task<Result<()>>> {
6108 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6109 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6110 }
6111
6112 pub fn compose_completion(
6113 &mut self,
6114 action: &ComposeCompletion,
6115 window: &mut Window,
6116 cx: &mut Context<Self>,
6117 ) -> Option<Task<Result<()>>> {
6118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6119 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6120 }
6121
6122 fn do_completion(
6123 &mut self,
6124 item_ix: Option<usize>,
6125 intent: CompletionIntent,
6126 window: &mut Window,
6127 cx: &mut Context<Editor>,
6128 ) -> Option<Task<Result<()>>> {
6129 use language::ToOffset as _;
6130
6131 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6132 else {
6133 return None;
6134 };
6135
6136 let candidate_id = {
6137 let entries = completions_menu.entries.borrow();
6138 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6139 if self.show_edit_predictions_in_menu() {
6140 self.discard_edit_prediction(true, cx);
6141 }
6142 mat.candidate_id
6143 };
6144
6145 let completion = completions_menu
6146 .completions
6147 .borrow()
6148 .get(candidate_id)?
6149 .clone();
6150 cx.stop_propagation();
6151
6152 let buffer_handle = completions_menu.buffer.clone();
6153
6154 let CompletionEdit {
6155 new_text,
6156 snippet,
6157 replace_range,
6158 } = process_completion_for_edit(
6159 &completion,
6160 intent,
6161 &buffer_handle,
6162 &completions_menu.initial_position.text_anchor,
6163 cx,
6164 );
6165
6166 let buffer = buffer_handle.read(cx);
6167 let snapshot = self.buffer.read(cx).snapshot(cx);
6168 let newest_anchor = self.selections.newest_anchor();
6169 let replace_range_multibuffer = {
6170 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6171 excerpt.map_range_from_buffer(replace_range.clone())
6172 };
6173 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6174 return None;
6175 }
6176
6177 let old_text = buffer
6178 .text_for_range(replace_range.clone())
6179 .collect::<String>();
6180 let lookbehind = newest_anchor
6181 .start
6182 .text_anchor
6183 .to_offset(buffer)
6184 .saturating_sub(replace_range.start.0);
6185 let lookahead = replace_range
6186 .end
6187 .0
6188 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6189 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6190 let suffix = &old_text[lookbehind.min(old_text.len())..];
6191
6192 let selections = self
6193 .selections
6194 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6195 let mut ranges = Vec::new();
6196 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6197
6198 for selection in &selections {
6199 let range = if selection.id == newest_anchor.id {
6200 replace_range_multibuffer.clone()
6201 } else {
6202 let mut range = selection.range();
6203
6204 // if prefix is present, don't duplicate it
6205 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6206 range.start = range.start.saturating_sub_usize(lookbehind);
6207
6208 // if suffix is also present, mimic the newest cursor and replace it
6209 if selection.id != newest_anchor.id
6210 && snapshot.contains_str_at(range.end, suffix)
6211 {
6212 range.end += lookahead;
6213 }
6214 }
6215 range
6216 };
6217
6218 ranges.push(range.clone());
6219
6220 if !self.linked_edit_ranges.is_empty() {
6221 let start_anchor = snapshot.anchor_before(range.start);
6222 let end_anchor = snapshot.anchor_after(range.end);
6223 if let Some(ranges) = self
6224 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6225 {
6226 for (buffer, edits) in ranges {
6227 linked_edits
6228 .entry(buffer.clone())
6229 .or_default()
6230 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6231 }
6232 }
6233 }
6234 }
6235
6236 let common_prefix_len = old_text
6237 .chars()
6238 .zip(new_text.chars())
6239 .take_while(|(a, b)| a == b)
6240 .map(|(a, _)| a.len_utf8())
6241 .sum::<usize>();
6242
6243 cx.emit(EditorEvent::InputHandled {
6244 utf16_range_to_replace: None,
6245 text: new_text[common_prefix_len..].into(),
6246 });
6247
6248 self.transact(window, cx, |editor, window, cx| {
6249 if let Some(mut snippet) = snippet {
6250 snippet.text = new_text.to_string();
6251 editor
6252 .insert_snippet(&ranges, snippet, window, cx)
6253 .log_err();
6254 } else {
6255 editor.buffer.update(cx, |multi_buffer, cx| {
6256 let auto_indent = match completion.insert_text_mode {
6257 Some(InsertTextMode::AS_IS) => None,
6258 _ => editor.autoindent_mode.clone(),
6259 };
6260 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6261 multi_buffer.edit(edits, auto_indent, cx);
6262 });
6263 }
6264 for (buffer, edits) in linked_edits {
6265 buffer.update(cx, |buffer, cx| {
6266 let snapshot = buffer.snapshot();
6267 let edits = edits
6268 .into_iter()
6269 .map(|(range, text)| {
6270 use text::ToPoint as TP;
6271 let end_point = TP::to_point(&range.end, &snapshot);
6272 let start_point = TP::to_point(&range.start, &snapshot);
6273 (start_point..end_point, text)
6274 })
6275 .sorted_by_key(|(range, _)| range.start);
6276 buffer.edit(edits, None, cx);
6277 })
6278 }
6279
6280 editor.refresh_edit_prediction(true, false, window, cx);
6281 });
6282 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6283
6284 let show_new_completions_on_confirm = completion
6285 .confirm
6286 .as_ref()
6287 .is_some_and(|confirm| confirm(intent, window, cx));
6288 if show_new_completions_on_confirm {
6289 self.open_or_update_completions_menu(None, None, false, window, cx);
6290 }
6291
6292 let provider = self.completion_provider.as_ref()?;
6293
6294 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6295 let command = lsp_store.as_ref().and_then(|lsp_store| {
6296 let CompletionSource::Lsp {
6297 lsp_completion,
6298 server_id,
6299 ..
6300 } = &completion.source
6301 else {
6302 return None;
6303 };
6304 let lsp_command = lsp_completion.command.as_ref()?;
6305 let available_commands = lsp_store
6306 .read(cx)
6307 .lsp_server_capabilities
6308 .get(server_id)
6309 .and_then(|server_capabilities| {
6310 server_capabilities
6311 .execute_command_provider
6312 .as_ref()
6313 .map(|options| options.commands.as_slice())
6314 })?;
6315 if available_commands.contains(&lsp_command.command) {
6316 Some(CodeAction {
6317 server_id: *server_id,
6318 range: language::Anchor::MIN..language::Anchor::MIN,
6319 lsp_action: LspAction::Command(lsp_command.clone()),
6320 resolved: false,
6321 })
6322 } else {
6323 None
6324 }
6325 });
6326
6327 drop(completion);
6328 let apply_edits = provider.apply_additional_edits_for_completion(
6329 buffer_handle.clone(),
6330 completions_menu.completions.clone(),
6331 candidate_id,
6332 true,
6333 cx,
6334 );
6335
6336 let editor_settings = EditorSettings::get_global(cx);
6337 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6338 // After the code completion is finished, users often want to know what signatures are needed.
6339 // so we should automatically call signature_help
6340 self.show_signature_help(&ShowSignatureHelp, window, cx);
6341 }
6342
6343 Some(cx.spawn_in(window, async move |editor, cx| {
6344 apply_edits.await?;
6345
6346 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6347 let title = command.lsp_action.title().to_owned();
6348 let project_transaction = lsp_store
6349 .update(cx, |lsp_store, cx| {
6350 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6351 })?
6352 .await
6353 .context("applying post-completion command")?;
6354 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6355 Self::open_project_transaction(
6356 &editor,
6357 workspace.downgrade(),
6358 project_transaction,
6359 title,
6360 cx,
6361 )
6362 .await?;
6363 }
6364 }
6365
6366 Ok(())
6367 }))
6368 }
6369
6370 pub fn toggle_code_actions(
6371 &mut self,
6372 action: &ToggleCodeActions,
6373 window: &mut Window,
6374 cx: &mut Context<Self>,
6375 ) {
6376 let quick_launch = action.quick_launch;
6377 let mut context_menu = self.context_menu.borrow_mut();
6378 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6379 if code_actions.deployed_from == action.deployed_from {
6380 // Toggle if we're selecting the same one
6381 *context_menu = None;
6382 cx.notify();
6383 return;
6384 } else {
6385 // Otherwise, clear it and start a new one
6386 *context_menu = None;
6387 cx.notify();
6388 }
6389 }
6390 drop(context_menu);
6391 let snapshot = self.snapshot(window, cx);
6392 let deployed_from = action.deployed_from.clone();
6393 let action = action.clone();
6394 self.completion_tasks.clear();
6395 self.discard_edit_prediction(false, cx);
6396
6397 let multibuffer_point = match &action.deployed_from {
6398 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6399 DisplayPoint::new(*row, 0).to_point(&snapshot)
6400 }
6401 _ => self
6402 .selections
6403 .newest::<Point>(&snapshot.display_snapshot)
6404 .head(),
6405 };
6406 let Some((buffer, buffer_row)) = snapshot
6407 .buffer_snapshot()
6408 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6409 .and_then(|(buffer_snapshot, range)| {
6410 self.buffer()
6411 .read(cx)
6412 .buffer(buffer_snapshot.remote_id())
6413 .map(|buffer| (buffer, range.start.row))
6414 })
6415 else {
6416 return;
6417 };
6418 let buffer_id = buffer.read(cx).remote_id();
6419 let tasks = self
6420 .tasks
6421 .get(&(buffer_id, buffer_row))
6422 .map(|t| Arc::new(t.to_owned()));
6423
6424 if !self.focus_handle.is_focused(window) {
6425 return;
6426 }
6427 let project = self.project.clone();
6428
6429 let code_actions_task = match deployed_from {
6430 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6431 _ => self.code_actions(buffer_row, window, cx),
6432 };
6433
6434 let runnable_task = match deployed_from {
6435 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6436 _ => {
6437 let mut task_context_task = Task::ready(None);
6438 if let Some(tasks) = &tasks
6439 && let Some(project) = project
6440 {
6441 task_context_task =
6442 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6443 }
6444
6445 cx.spawn_in(window, {
6446 let buffer = buffer.clone();
6447 async move |editor, cx| {
6448 let task_context = task_context_task.await;
6449
6450 let resolved_tasks =
6451 tasks
6452 .zip(task_context.clone())
6453 .map(|(tasks, task_context)| ResolvedTasks {
6454 templates: tasks.resolve(&task_context).collect(),
6455 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6456 multibuffer_point.row,
6457 tasks.column,
6458 )),
6459 });
6460 let debug_scenarios = editor
6461 .update(cx, |editor, cx| {
6462 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6463 })?
6464 .await;
6465 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6466 }
6467 })
6468 }
6469 };
6470
6471 cx.spawn_in(window, async move |editor, cx| {
6472 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6473 let code_actions = code_actions_task.await;
6474 let spawn_straight_away = quick_launch
6475 && resolved_tasks
6476 .as_ref()
6477 .is_some_and(|tasks| tasks.templates.len() == 1)
6478 && code_actions
6479 .as_ref()
6480 .is_none_or(|actions| actions.is_empty())
6481 && debug_scenarios.is_empty();
6482
6483 editor.update_in(cx, |editor, window, cx| {
6484 crate::hover_popover::hide_hover(editor, cx);
6485 let actions = CodeActionContents::new(
6486 resolved_tasks,
6487 code_actions,
6488 debug_scenarios,
6489 task_context.unwrap_or_default(),
6490 );
6491
6492 // Don't show the menu if there are no actions available
6493 if actions.is_empty() {
6494 cx.notify();
6495 return Task::ready(Ok(()));
6496 }
6497
6498 *editor.context_menu.borrow_mut() =
6499 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6500 buffer,
6501 actions,
6502 selected_item: Default::default(),
6503 scroll_handle: UniformListScrollHandle::default(),
6504 deployed_from,
6505 }));
6506 cx.notify();
6507 if spawn_straight_away
6508 && let Some(task) = editor.confirm_code_action(
6509 &ConfirmCodeAction { item_ix: Some(0) },
6510 window,
6511 cx,
6512 )
6513 {
6514 return task;
6515 }
6516
6517 Task::ready(Ok(()))
6518 })
6519 })
6520 .detach_and_log_err(cx);
6521 }
6522
6523 fn debug_scenarios(
6524 &mut self,
6525 resolved_tasks: &Option<ResolvedTasks>,
6526 buffer: &Entity<Buffer>,
6527 cx: &mut App,
6528 ) -> Task<Vec<task::DebugScenario>> {
6529 maybe!({
6530 let project = self.project()?;
6531 let dap_store = project.read(cx).dap_store();
6532 let mut scenarios = vec![];
6533 let resolved_tasks = resolved_tasks.as_ref()?;
6534 let buffer = buffer.read(cx);
6535 let language = buffer.language()?;
6536 let file = buffer.file();
6537 let debug_adapter = language_settings(language.name().into(), file, cx)
6538 .debuggers
6539 .first()
6540 .map(SharedString::from)
6541 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6542
6543 dap_store.update(cx, |dap_store, cx| {
6544 for (_, task) in &resolved_tasks.templates {
6545 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6546 task.original_task().clone(),
6547 debug_adapter.clone().into(),
6548 task.display_label().to_owned().into(),
6549 cx,
6550 );
6551 scenarios.push(maybe_scenario);
6552 }
6553 });
6554 Some(cx.background_spawn(async move {
6555 futures::future::join_all(scenarios)
6556 .await
6557 .into_iter()
6558 .flatten()
6559 .collect::<Vec<_>>()
6560 }))
6561 })
6562 .unwrap_or_else(|| Task::ready(vec![]))
6563 }
6564
6565 fn code_actions(
6566 &mut self,
6567 buffer_row: u32,
6568 window: &mut Window,
6569 cx: &mut Context<Self>,
6570 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6571 let mut task = self.code_actions_task.take();
6572 cx.spawn_in(window, async move |editor, cx| {
6573 while let Some(prev_task) = task {
6574 prev_task.await.log_err();
6575 task = editor
6576 .update(cx, |this, _| this.code_actions_task.take())
6577 .ok()?;
6578 }
6579
6580 editor
6581 .update(cx, |editor, cx| {
6582 editor
6583 .available_code_actions
6584 .clone()
6585 .and_then(|(location, code_actions)| {
6586 let snapshot = location.buffer.read(cx).snapshot();
6587 let point_range = location.range.to_point(&snapshot);
6588 let point_range = point_range.start.row..=point_range.end.row;
6589 if point_range.contains(&buffer_row) {
6590 Some(code_actions)
6591 } else {
6592 None
6593 }
6594 })
6595 })
6596 .ok()
6597 .flatten()
6598 })
6599 }
6600
6601 pub fn confirm_code_action(
6602 &mut self,
6603 action: &ConfirmCodeAction,
6604 window: &mut Window,
6605 cx: &mut Context<Self>,
6606 ) -> Option<Task<Result<()>>> {
6607 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6608
6609 let actions_menu =
6610 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6611 menu
6612 } else {
6613 return None;
6614 };
6615
6616 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6617 let action = actions_menu.actions.get(action_ix)?;
6618 let title = action.label();
6619 let buffer = actions_menu.buffer;
6620 let workspace = self.workspace()?;
6621
6622 match action {
6623 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6624 workspace.update(cx, |workspace, cx| {
6625 workspace.schedule_resolved_task(
6626 task_source_kind,
6627 resolved_task,
6628 false,
6629 window,
6630 cx,
6631 );
6632
6633 Some(Task::ready(Ok(())))
6634 })
6635 }
6636 CodeActionsItem::CodeAction {
6637 excerpt_id,
6638 action,
6639 provider,
6640 } => {
6641 let apply_code_action =
6642 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6643 let workspace = workspace.downgrade();
6644 Some(cx.spawn_in(window, async move |editor, cx| {
6645 let project_transaction = apply_code_action.await?;
6646 Self::open_project_transaction(
6647 &editor,
6648 workspace,
6649 project_transaction,
6650 title,
6651 cx,
6652 )
6653 .await
6654 }))
6655 }
6656 CodeActionsItem::DebugScenario(scenario) => {
6657 let context = actions_menu.actions.context;
6658
6659 workspace.update(cx, |workspace, cx| {
6660 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6661 workspace.start_debug_session(
6662 scenario,
6663 context,
6664 Some(buffer),
6665 None,
6666 window,
6667 cx,
6668 );
6669 });
6670 Some(Task::ready(Ok(())))
6671 }
6672 }
6673 }
6674
6675 pub async fn open_project_transaction(
6676 editor: &WeakEntity<Editor>,
6677 workspace: WeakEntity<Workspace>,
6678 transaction: ProjectTransaction,
6679 title: String,
6680 cx: &mut AsyncWindowContext,
6681 ) -> Result<()> {
6682 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6683 cx.update(|_, cx| {
6684 entries.sort_unstable_by_key(|(buffer, _)| {
6685 buffer.read(cx).file().map(|f| f.path().clone())
6686 });
6687 })?;
6688 if entries.is_empty() {
6689 return Ok(());
6690 }
6691
6692 // If the project transaction's edits are all contained within this editor, then
6693 // avoid opening a new editor to display them.
6694
6695 if let [(buffer, transaction)] = &*entries {
6696 let excerpt = editor.update(cx, |editor, cx| {
6697 editor
6698 .buffer()
6699 .read(cx)
6700 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6701 })?;
6702 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6703 && excerpted_buffer == *buffer
6704 {
6705 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6706 let excerpt_range = excerpt_range.to_offset(buffer);
6707 buffer
6708 .edited_ranges_for_transaction::<usize>(transaction)
6709 .all(|range| {
6710 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6711 })
6712 })?;
6713
6714 if all_edits_within_excerpt {
6715 return Ok(());
6716 }
6717 }
6718 }
6719
6720 let mut ranges_to_highlight = Vec::new();
6721 let excerpt_buffer = cx.new(|cx| {
6722 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6723 for (buffer_handle, transaction) in &entries {
6724 let edited_ranges = buffer_handle
6725 .read(cx)
6726 .edited_ranges_for_transaction::<Point>(transaction)
6727 .collect::<Vec<_>>();
6728 let (ranges, _) = multibuffer.set_excerpts_for_path(
6729 PathKey::for_buffer(buffer_handle, cx),
6730 buffer_handle.clone(),
6731 edited_ranges,
6732 multibuffer_context_lines(cx),
6733 cx,
6734 );
6735
6736 ranges_to_highlight.extend(ranges);
6737 }
6738 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6739 multibuffer
6740 })?;
6741
6742 workspace.update_in(cx, |workspace, window, cx| {
6743 let project = workspace.project().clone();
6744 let editor =
6745 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6746 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6747 editor.update(cx, |editor, cx| {
6748 editor.highlight_background::<Self>(
6749 &ranges_to_highlight,
6750 |_, theme| theme.colors().editor_highlighted_line_background,
6751 cx,
6752 );
6753 });
6754 })?;
6755
6756 Ok(())
6757 }
6758
6759 pub fn clear_code_action_providers(&mut self) {
6760 self.code_action_providers.clear();
6761 self.available_code_actions.take();
6762 }
6763
6764 pub fn add_code_action_provider(
6765 &mut self,
6766 provider: Rc<dyn CodeActionProvider>,
6767 window: &mut Window,
6768 cx: &mut Context<Self>,
6769 ) {
6770 if self
6771 .code_action_providers
6772 .iter()
6773 .any(|existing_provider| existing_provider.id() == provider.id())
6774 {
6775 return;
6776 }
6777
6778 self.code_action_providers.push(provider);
6779 self.refresh_code_actions(window, cx);
6780 }
6781
6782 pub fn remove_code_action_provider(
6783 &mut self,
6784 id: Arc<str>,
6785 window: &mut Window,
6786 cx: &mut Context<Self>,
6787 ) {
6788 self.code_action_providers
6789 .retain(|provider| provider.id() != id);
6790 self.refresh_code_actions(window, cx);
6791 }
6792
6793 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6794 !self.code_action_providers.is_empty()
6795 && EditorSettings::get_global(cx).toolbar.code_actions
6796 }
6797
6798 pub fn has_available_code_actions(&self) -> bool {
6799 self.available_code_actions
6800 .as_ref()
6801 .is_some_and(|(_, actions)| !actions.is_empty())
6802 }
6803
6804 fn render_inline_code_actions(
6805 &self,
6806 icon_size: ui::IconSize,
6807 display_row: DisplayRow,
6808 is_active: bool,
6809 cx: &mut Context<Self>,
6810 ) -> AnyElement {
6811 let show_tooltip = !self.context_menu_visible();
6812 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6813 .icon_size(icon_size)
6814 .shape(ui::IconButtonShape::Square)
6815 .icon_color(ui::Color::Hidden)
6816 .toggle_state(is_active)
6817 .when(show_tooltip, |this| {
6818 this.tooltip({
6819 let focus_handle = self.focus_handle.clone();
6820 move |_window, cx| {
6821 Tooltip::for_action_in(
6822 "Toggle Code Actions",
6823 &ToggleCodeActions {
6824 deployed_from: None,
6825 quick_launch: false,
6826 },
6827 &focus_handle,
6828 cx,
6829 )
6830 }
6831 })
6832 })
6833 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6834 window.focus(&editor.focus_handle(cx));
6835 editor.toggle_code_actions(
6836 &crate::actions::ToggleCodeActions {
6837 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6838 display_row,
6839 )),
6840 quick_launch: false,
6841 },
6842 window,
6843 cx,
6844 );
6845 }))
6846 .into_any_element()
6847 }
6848
6849 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6850 &self.context_menu
6851 }
6852
6853 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6854 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6855 cx.background_executor()
6856 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6857 .await;
6858
6859 let (start_buffer, start, _, end, newest_selection) = this
6860 .update(cx, |this, cx| {
6861 let newest_selection = this.selections.newest_anchor().clone();
6862 if newest_selection.head().diff_base_anchor.is_some() {
6863 return None;
6864 }
6865 let display_snapshot = this.display_snapshot(cx);
6866 let newest_selection_adjusted =
6867 this.selections.newest_adjusted(&display_snapshot);
6868 let buffer = this.buffer.read(cx);
6869
6870 let (start_buffer, start) =
6871 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6872 let (end_buffer, end) =
6873 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6874
6875 Some((start_buffer, start, end_buffer, end, newest_selection))
6876 })?
6877 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6878 .context(
6879 "Expected selection to lie in a single buffer when refreshing code actions",
6880 )?;
6881 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6882 let providers = this.code_action_providers.clone();
6883 let tasks = this
6884 .code_action_providers
6885 .iter()
6886 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6887 .collect::<Vec<_>>();
6888 (providers, tasks)
6889 })?;
6890
6891 let mut actions = Vec::new();
6892 for (provider, provider_actions) in
6893 providers.into_iter().zip(future::join_all(tasks).await)
6894 {
6895 if let Some(provider_actions) = provider_actions.log_err() {
6896 actions.extend(provider_actions.into_iter().map(|action| {
6897 AvailableCodeAction {
6898 excerpt_id: newest_selection.start.excerpt_id,
6899 action,
6900 provider: provider.clone(),
6901 }
6902 }));
6903 }
6904 }
6905
6906 this.update(cx, |this, cx| {
6907 this.available_code_actions = if actions.is_empty() {
6908 None
6909 } else {
6910 Some((
6911 Location {
6912 buffer: start_buffer,
6913 range: start..end,
6914 },
6915 actions.into(),
6916 ))
6917 };
6918 cx.notify();
6919 })
6920 }));
6921 }
6922
6923 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6924 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6925 self.show_git_blame_inline = false;
6926
6927 self.show_git_blame_inline_delay_task =
6928 Some(cx.spawn_in(window, async move |this, cx| {
6929 cx.background_executor().timer(delay).await;
6930
6931 this.update(cx, |this, cx| {
6932 this.show_git_blame_inline = true;
6933 cx.notify();
6934 })
6935 .log_err();
6936 }));
6937 }
6938 }
6939
6940 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6941 let snapshot = self.snapshot(window, cx);
6942 let cursor = self
6943 .selections
6944 .newest::<Point>(&snapshot.display_snapshot)
6945 .head();
6946 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6947 else {
6948 return;
6949 };
6950
6951 if self.blame.is_none() {
6952 self.start_git_blame(true, window, cx);
6953 }
6954 let Some(blame) = self.blame.as_ref() else {
6955 return;
6956 };
6957
6958 let row_info = RowInfo {
6959 buffer_id: Some(buffer.remote_id()),
6960 buffer_row: Some(point.row),
6961 ..Default::default()
6962 };
6963 let Some((buffer, blame_entry)) = blame
6964 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6965 .flatten()
6966 else {
6967 return;
6968 };
6969
6970 let anchor = self.selections.newest_anchor().head();
6971 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6972 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6973 self.show_blame_popover(
6974 buffer,
6975 &blame_entry,
6976 position + last_bounds.origin,
6977 true,
6978 cx,
6979 );
6980 };
6981 }
6982
6983 fn show_blame_popover(
6984 &mut self,
6985 buffer: BufferId,
6986 blame_entry: &BlameEntry,
6987 position: gpui::Point<Pixels>,
6988 ignore_timeout: bool,
6989 cx: &mut Context<Self>,
6990 ) {
6991 if let Some(state) = &mut self.inline_blame_popover {
6992 state.hide_task.take();
6993 } else {
6994 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6995 let blame_entry = blame_entry.clone();
6996 let show_task = cx.spawn(async move |editor, cx| {
6997 if !ignore_timeout {
6998 cx.background_executor()
6999 .timer(std::time::Duration::from_millis(blame_popover_delay))
7000 .await;
7001 }
7002 editor
7003 .update(cx, |editor, cx| {
7004 editor.inline_blame_popover_show_task.take();
7005 let Some(blame) = editor.blame.as_ref() else {
7006 return;
7007 };
7008 let blame = blame.read(cx);
7009 let details = blame.details_for_entry(buffer, &blame_entry);
7010 let markdown = cx.new(|cx| {
7011 Markdown::new(
7012 details
7013 .as_ref()
7014 .map(|message| message.message.clone())
7015 .unwrap_or_default(),
7016 None,
7017 None,
7018 cx,
7019 )
7020 });
7021 editor.inline_blame_popover = Some(InlineBlamePopover {
7022 position,
7023 hide_task: None,
7024 popover_bounds: None,
7025 popover_state: InlineBlamePopoverState {
7026 scroll_handle: ScrollHandle::new(),
7027 commit_message: details,
7028 markdown,
7029 },
7030 keyboard_grace: ignore_timeout,
7031 });
7032 cx.notify();
7033 })
7034 .ok();
7035 });
7036 self.inline_blame_popover_show_task = Some(show_task);
7037 }
7038 }
7039
7040 pub fn has_mouse_context_menu(&self) -> bool {
7041 self.mouse_context_menu.is_some()
7042 }
7043
7044 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7045 self.inline_blame_popover_show_task.take();
7046 if let Some(state) = &mut self.inline_blame_popover {
7047 let hide_task = cx.spawn(async move |editor, cx| {
7048 if !ignore_timeout {
7049 cx.background_executor()
7050 .timer(std::time::Duration::from_millis(100))
7051 .await;
7052 }
7053 editor
7054 .update(cx, |editor, cx| {
7055 editor.inline_blame_popover.take();
7056 cx.notify();
7057 })
7058 .ok();
7059 });
7060 state.hide_task = Some(hide_task);
7061 true
7062 } else {
7063 false
7064 }
7065 }
7066
7067 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7068 if self.pending_rename.is_some() {
7069 return None;
7070 }
7071
7072 let provider = self.semantics_provider.clone()?;
7073 let buffer = self.buffer.read(cx);
7074 let newest_selection = self.selections.newest_anchor().clone();
7075 let cursor_position = newest_selection.head();
7076 let (cursor_buffer, cursor_buffer_position) =
7077 buffer.text_anchor_for_position(cursor_position, cx)?;
7078 let (tail_buffer, tail_buffer_position) =
7079 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7080 if cursor_buffer != tail_buffer {
7081 return None;
7082 }
7083
7084 let snapshot = cursor_buffer.read(cx).snapshot();
7085 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7086 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7087 if start_word_range != end_word_range {
7088 self.document_highlights_task.take();
7089 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7090 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7091 return None;
7092 }
7093
7094 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7095 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7096 cx.background_executor()
7097 .timer(Duration::from_millis(debounce))
7098 .await;
7099
7100 let highlights = if let Some(highlights) = cx
7101 .update(|cx| {
7102 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7103 })
7104 .ok()
7105 .flatten()
7106 {
7107 highlights.await.log_err()
7108 } else {
7109 None
7110 };
7111
7112 if let Some(highlights) = highlights {
7113 this.update(cx, |this, cx| {
7114 if this.pending_rename.is_some() {
7115 return;
7116 }
7117
7118 let buffer = this.buffer.read(cx);
7119 if buffer
7120 .text_anchor_for_position(cursor_position, cx)
7121 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7122 {
7123 return;
7124 }
7125
7126 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7127 let mut write_ranges = Vec::new();
7128 let mut read_ranges = Vec::new();
7129 for highlight in highlights {
7130 let buffer_id = cursor_buffer.read(cx).remote_id();
7131 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7132 {
7133 let start = highlight
7134 .range
7135 .start
7136 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7137 let end = highlight
7138 .range
7139 .end
7140 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7141 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7142 continue;
7143 }
7144
7145 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7146 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7147 write_ranges.push(range);
7148 } else {
7149 read_ranges.push(range);
7150 }
7151 }
7152 }
7153
7154 this.highlight_background::<DocumentHighlightRead>(
7155 &read_ranges,
7156 |_, theme| theme.colors().editor_document_highlight_read_background,
7157 cx,
7158 );
7159 this.highlight_background::<DocumentHighlightWrite>(
7160 &write_ranges,
7161 |_, theme| theme.colors().editor_document_highlight_write_background,
7162 cx,
7163 );
7164 cx.notify();
7165 })
7166 .log_err();
7167 }
7168 }));
7169 None
7170 }
7171
7172 fn prepare_highlight_query_from_selection(
7173 &mut self,
7174 window: &Window,
7175 cx: &mut Context<Editor>,
7176 ) -> Option<(String, Range<Anchor>)> {
7177 if matches!(self.mode, EditorMode::SingleLine) {
7178 return None;
7179 }
7180 if !EditorSettings::get_global(cx).selection_highlight {
7181 return None;
7182 }
7183 if self.selections.count() != 1 || self.selections.line_mode() {
7184 return None;
7185 }
7186 let snapshot = self.snapshot(window, cx);
7187 let selection = self.selections.newest::<Point>(&snapshot);
7188 // If the selection spans multiple rows OR it is empty
7189 if selection.start.row != selection.end.row
7190 || selection.start.column == selection.end.column
7191 {
7192 return None;
7193 }
7194 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7195 let query = snapshot
7196 .buffer_snapshot()
7197 .text_for_range(selection_anchor_range.clone())
7198 .collect::<String>();
7199 if query.trim().is_empty() {
7200 return None;
7201 }
7202 Some((query, selection_anchor_range))
7203 }
7204
7205 #[ztracing::instrument(skip_all)]
7206 fn update_selection_occurrence_highlights(
7207 &mut self,
7208 query_text: String,
7209 query_range: Range<Anchor>,
7210 multi_buffer_range_to_query: Range<Point>,
7211 use_debounce: bool,
7212 window: &mut Window,
7213 cx: &mut Context<Editor>,
7214 ) -> Task<()> {
7215 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7216 cx.spawn_in(window, async move |editor, cx| {
7217 if use_debounce {
7218 cx.background_executor()
7219 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7220 .await;
7221 }
7222 let match_task = cx.background_spawn(async move {
7223 let buffer_ranges = multi_buffer_snapshot
7224 .range_to_buffer_ranges(multi_buffer_range_to_query)
7225 .into_iter()
7226 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7227 let mut match_ranges = Vec::new();
7228 let Ok(regex) = project::search::SearchQuery::text(
7229 query_text.clone(),
7230 false,
7231 false,
7232 false,
7233 Default::default(),
7234 Default::default(),
7235 false,
7236 None,
7237 ) else {
7238 return Vec::default();
7239 };
7240 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7241 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7242 match_ranges.extend(
7243 regex
7244 .search(
7245 buffer_snapshot,
7246 Some(search_range.start.0..search_range.end.0),
7247 )
7248 .await
7249 .into_iter()
7250 .filter_map(|match_range| {
7251 let match_start = buffer_snapshot
7252 .anchor_after(search_range.start + match_range.start);
7253 let match_end = buffer_snapshot
7254 .anchor_before(search_range.start + match_range.end);
7255 let match_anchor_range =
7256 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7257 (match_anchor_range != query_range).then_some(match_anchor_range)
7258 }),
7259 );
7260 }
7261 match_ranges
7262 });
7263 let match_ranges = match_task.await;
7264 editor
7265 .update_in(cx, |editor, _, cx| {
7266 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7267 if !match_ranges.is_empty() {
7268 editor.highlight_background::<SelectedTextHighlight>(
7269 &match_ranges,
7270 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7271 cx,
7272 )
7273 }
7274 })
7275 .log_err();
7276 })
7277 }
7278
7279 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7280 struct NewlineFold;
7281 let type_id = std::any::TypeId::of::<NewlineFold>();
7282 if !self.mode.is_single_line() {
7283 return;
7284 }
7285 let snapshot = self.snapshot(window, cx);
7286 if snapshot.buffer_snapshot().max_point().row == 0 {
7287 return;
7288 }
7289 let task = cx.background_spawn(async move {
7290 let new_newlines = snapshot
7291 .buffer_chars_at(MultiBufferOffset(0))
7292 .filter_map(|(c, i)| {
7293 if c == '\n' {
7294 Some(
7295 snapshot.buffer_snapshot().anchor_after(i)
7296 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7297 )
7298 } else {
7299 None
7300 }
7301 })
7302 .collect::<Vec<_>>();
7303 let existing_newlines = snapshot
7304 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7305 .filter_map(|fold| {
7306 if fold.placeholder.type_tag == Some(type_id) {
7307 Some(fold.range.start..fold.range.end)
7308 } else {
7309 None
7310 }
7311 })
7312 .collect::<Vec<_>>();
7313
7314 (new_newlines, existing_newlines)
7315 });
7316 self.folding_newlines = cx.spawn(async move |this, cx| {
7317 let (new_newlines, existing_newlines) = task.await;
7318 if new_newlines == existing_newlines {
7319 return;
7320 }
7321 let placeholder = FoldPlaceholder {
7322 render: Arc::new(move |_, _, cx| {
7323 div()
7324 .bg(cx.theme().status().hint_background)
7325 .border_b_1()
7326 .size_full()
7327 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7328 .border_color(cx.theme().status().hint)
7329 .child("\\n")
7330 .into_any()
7331 }),
7332 constrain_width: false,
7333 merge_adjacent: false,
7334 type_tag: Some(type_id),
7335 };
7336 let creases = new_newlines
7337 .into_iter()
7338 .map(|range| Crease::simple(range, placeholder.clone()))
7339 .collect();
7340 this.update(cx, |this, cx| {
7341 this.display_map.update(cx, |display_map, cx| {
7342 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7343 display_map.fold(creases, cx);
7344 });
7345 })
7346 .ok();
7347 });
7348 }
7349
7350 #[ztracing::instrument(skip_all)]
7351 fn refresh_selected_text_highlights(
7352 &mut self,
7353 on_buffer_edit: bool,
7354 window: &mut Window,
7355 cx: &mut Context<Editor>,
7356 ) {
7357 let Some((query_text, query_range)) =
7358 self.prepare_highlight_query_from_selection(window, cx)
7359 else {
7360 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7361 self.quick_selection_highlight_task.take();
7362 self.debounced_selection_highlight_task.take();
7363 return;
7364 };
7365 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7366 if on_buffer_edit
7367 || self
7368 .quick_selection_highlight_task
7369 .as_ref()
7370 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7371 {
7372 let multi_buffer_visible_start = self
7373 .scroll_manager
7374 .anchor()
7375 .anchor
7376 .to_point(&multi_buffer_snapshot);
7377 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7378 multi_buffer_visible_start
7379 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7380 Bias::Left,
7381 );
7382 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7383 self.quick_selection_highlight_task = Some((
7384 query_range.clone(),
7385 self.update_selection_occurrence_highlights(
7386 query_text.clone(),
7387 query_range.clone(),
7388 multi_buffer_visible_range,
7389 false,
7390 window,
7391 cx,
7392 ),
7393 ));
7394 }
7395 if on_buffer_edit
7396 || self
7397 .debounced_selection_highlight_task
7398 .as_ref()
7399 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7400 {
7401 let multi_buffer_start = multi_buffer_snapshot
7402 .anchor_before(MultiBufferOffset(0))
7403 .to_point(&multi_buffer_snapshot);
7404 let multi_buffer_end = multi_buffer_snapshot
7405 .anchor_after(multi_buffer_snapshot.len())
7406 .to_point(&multi_buffer_snapshot);
7407 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7408 self.debounced_selection_highlight_task = Some((
7409 query_range.clone(),
7410 self.update_selection_occurrence_highlights(
7411 query_text,
7412 query_range,
7413 multi_buffer_full_range,
7414 true,
7415 window,
7416 cx,
7417 ),
7418 ));
7419 }
7420 }
7421
7422 pub fn refresh_edit_prediction(
7423 &mut self,
7424 debounce: bool,
7425 user_requested: bool,
7426 window: &mut Window,
7427 cx: &mut Context<Self>,
7428 ) -> Option<()> {
7429 if DisableAiSettings::get_global(cx).disable_ai {
7430 return None;
7431 }
7432
7433 let provider = self.edit_prediction_provider()?;
7434 let cursor = self.selections.newest_anchor().head();
7435 let (buffer, cursor_buffer_position) =
7436 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7437
7438 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7439 self.discard_edit_prediction(false, cx);
7440 return None;
7441 }
7442
7443 self.update_visible_edit_prediction(window, cx);
7444
7445 if !user_requested
7446 && (!self.should_show_edit_predictions()
7447 || !self.is_focused(window)
7448 || buffer.read(cx).is_empty())
7449 {
7450 self.discard_edit_prediction(false, cx);
7451 return None;
7452 }
7453
7454 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7455 Some(())
7456 }
7457
7458 fn show_edit_predictions_in_menu(&self) -> bool {
7459 match self.edit_prediction_settings {
7460 EditPredictionSettings::Disabled => false,
7461 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7462 }
7463 }
7464
7465 pub fn edit_predictions_enabled(&self) -> bool {
7466 match self.edit_prediction_settings {
7467 EditPredictionSettings::Disabled => false,
7468 EditPredictionSettings::Enabled { .. } => true,
7469 }
7470 }
7471
7472 fn edit_prediction_requires_modifier(&self) -> bool {
7473 match self.edit_prediction_settings {
7474 EditPredictionSettings::Disabled => false,
7475 EditPredictionSettings::Enabled {
7476 preview_requires_modifier,
7477 ..
7478 } => preview_requires_modifier,
7479 }
7480 }
7481
7482 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7483 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7484 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7485 self.discard_edit_prediction(false, cx);
7486 } else {
7487 let selection = self.selections.newest_anchor();
7488 let cursor = selection.head();
7489
7490 if let Some((buffer, cursor_buffer_position)) =
7491 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7492 {
7493 self.edit_prediction_settings =
7494 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7495 }
7496 }
7497 }
7498
7499 fn edit_prediction_settings_at_position(
7500 &self,
7501 buffer: &Entity<Buffer>,
7502 buffer_position: language::Anchor,
7503 cx: &App,
7504 ) -> EditPredictionSettings {
7505 if !self.mode.is_full()
7506 || !self.show_edit_predictions_override.unwrap_or(true)
7507 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7508 {
7509 return EditPredictionSettings::Disabled;
7510 }
7511
7512 let buffer = buffer.read(cx);
7513
7514 let file = buffer.file();
7515
7516 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7517 return EditPredictionSettings::Disabled;
7518 };
7519
7520 let by_provider = matches!(
7521 self.menu_edit_predictions_policy,
7522 MenuEditPredictionsPolicy::ByProvider
7523 );
7524
7525 let show_in_menu = by_provider
7526 && self
7527 .edit_prediction_provider
7528 .as_ref()
7529 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7530
7531 let preview_requires_modifier =
7532 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7533
7534 EditPredictionSettings::Enabled {
7535 show_in_menu,
7536 preview_requires_modifier,
7537 }
7538 }
7539
7540 fn should_show_edit_predictions(&self) -> bool {
7541 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7542 }
7543
7544 pub fn edit_prediction_preview_is_active(&self) -> bool {
7545 matches!(
7546 self.edit_prediction_preview,
7547 EditPredictionPreview::Active { .. }
7548 )
7549 }
7550
7551 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7552 let cursor = self.selections.newest_anchor().head();
7553 if let Some((buffer, cursor_position)) =
7554 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7555 {
7556 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7557 } else {
7558 false
7559 }
7560 }
7561
7562 pub fn supports_minimap(&self, cx: &App) -> bool {
7563 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7564 }
7565
7566 fn edit_predictions_enabled_in_buffer(
7567 &self,
7568 buffer: &Entity<Buffer>,
7569 buffer_position: language::Anchor,
7570 cx: &App,
7571 ) -> bool {
7572 maybe!({
7573 if self.read_only(cx) {
7574 return Some(false);
7575 }
7576 let provider = self.edit_prediction_provider()?;
7577 if !provider.is_enabled(buffer, buffer_position, cx) {
7578 return Some(false);
7579 }
7580 let buffer = buffer.read(cx);
7581 let Some(file) = buffer.file() else {
7582 return Some(true);
7583 };
7584 let settings = all_language_settings(Some(file), cx);
7585 Some(settings.edit_predictions_enabled_for_file(file, cx))
7586 })
7587 .unwrap_or(false)
7588 }
7589
7590 fn cycle_edit_prediction(
7591 &mut self,
7592 direction: Direction,
7593 window: &mut Window,
7594 cx: &mut Context<Self>,
7595 ) -> Option<()> {
7596 let provider = self.edit_prediction_provider()?;
7597 let cursor = self.selections.newest_anchor().head();
7598 let (buffer, cursor_buffer_position) =
7599 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7600 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7601 return None;
7602 }
7603
7604 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7605 self.update_visible_edit_prediction(window, cx);
7606
7607 Some(())
7608 }
7609
7610 pub fn show_edit_prediction(
7611 &mut self,
7612 _: &ShowEditPrediction,
7613 window: &mut Window,
7614 cx: &mut Context<Self>,
7615 ) {
7616 if !self.has_active_edit_prediction() {
7617 self.refresh_edit_prediction(false, true, window, cx);
7618 return;
7619 }
7620
7621 self.update_visible_edit_prediction(window, cx);
7622 }
7623
7624 pub fn display_cursor_names(
7625 &mut self,
7626 _: &DisplayCursorNames,
7627 window: &mut Window,
7628 cx: &mut Context<Self>,
7629 ) {
7630 self.show_cursor_names(window, cx);
7631 }
7632
7633 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7634 self.show_cursor_names = true;
7635 cx.notify();
7636 cx.spawn_in(window, async move |this, cx| {
7637 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7638 this.update(cx, |this, cx| {
7639 this.show_cursor_names = false;
7640 cx.notify()
7641 })
7642 .ok()
7643 })
7644 .detach();
7645 }
7646
7647 pub fn next_edit_prediction(
7648 &mut self,
7649 _: &NextEditPrediction,
7650 window: &mut Window,
7651 cx: &mut Context<Self>,
7652 ) {
7653 if self.has_active_edit_prediction() {
7654 self.cycle_edit_prediction(Direction::Next, window, cx);
7655 } else {
7656 let is_copilot_disabled = self
7657 .refresh_edit_prediction(false, true, window, cx)
7658 .is_none();
7659 if is_copilot_disabled {
7660 cx.propagate();
7661 }
7662 }
7663 }
7664
7665 pub fn previous_edit_prediction(
7666 &mut self,
7667 _: &PreviousEditPrediction,
7668 window: &mut Window,
7669 cx: &mut Context<Self>,
7670 ) {
7671 if self.has_active_edit_prediction() {
7672 self.cycle_edit_prediction(Direction::Prev, window, cx);
7673 } else {
7674 let is_copilot_disabled = self
7675 .refresh_edit_prediction(false, true, window, cx)
7676 .is_none();
7677 if is_copilot_disabled {
7678 cx.propagate();
7679 }
7680 }
7681 }
7682
7683 pub fn accept_partial_edit_prediction(
7684 &mut self,
7685 granularity: EditPredictionGranularity,
7686 window: &mut Window,
7687 cx: &mut Context<Self>,
7688 ) {
7689 if self.show_edit_predictions_in_menu() {
7690 self.hide_context_menu(window, cx);
7691 }
7692
7693 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7694 return;
7695 };
7696
7697 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7698 return;
7699 }
7700
7701 match &active_edit_prediction.completion {
7702 EditPrediction::MoveWithin { target, .. } => {
7703 let target = *target;
7704
7705 if matches!(granularity, EditPredictionGranularity::Full) {
7706 if let Some(position_map) = &self.last_position_map {
7707 let target_row = target.to_display_point(&position_map.snapshot).row();
7708 let is_visible = position_map.visible_row_range.contains(&target_row);
7709
7710 if is_visible || !self.edit_prediction_requires_modifier() {
7711 self.unfold_ranges(&[target..target], true, false, cx);
7712 self.change_selections(
7713 SelectionEffects::scroll(Autoscroll::newest()),
7714 window,
7715 cx,
7716 |selections| {
7717 selections.select_anchor_ranges([target..target]);
7718 },
7719 );
7720 self.clear_row_highlights::<EditPredictionPreview>();
7721 self.edit_prediction_preview
7722 .set_previous_scroll_position(None);
7723 } else {
7724 // Highlight and request scroll
7725 self.edit_prediction_preview
7726 .set_previous_scroll_position(Some(
7727 position_map.snapshot.scroll_anchor,
7728 ));
7729 self.highlight_rows::<EditPredictionPreview>(
7730 target..target,
7731 cx.theme().colors().editor_highlighted_line_background,
7732 RowHighlightOptions {
7733 autoscroll: true,
7734 ..Default::default()
7735 },
7736 cx,
7737 );
7738 self.request_autoscroll(Autoscroll::fit(), cx);
7739 }
7740 }
7741 } else {
7742 self.change_selections(
7743 SelectionEffects::scroll(Autoscroll::newest()),
7744 window,
7745 cx,
7746 |selections| {
7747 selections.select_anchor_ranges([target..target]);
7748 },
7749 );
7750 }
7751 }
7752 EditPrediction::MoveOutside { snapshot, target } => {
7753 if let Some(workspace) = self.workspace() {
7754 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7755 .detach_and_log_err(cx);
7756 }
7757 }
7758 EditPrediction::Edit { edits, .. } => {
7759 self.report_edit_prediction_event(
7760 active_edit_prediction.completion_id.clone(),
7761 true,
7762 cx,
7763 );
7764
7765 match granularity {
7766 EditPredictionGranularity::Full => {
7767 if let Some(provider) = self.edit_prediction_provider() {
7768 provider.accept(cx);
7769 }
7770
7771 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7772 let snapshot = self.buffer.read(cx).snapshot(cx);
7773 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7774
7775 self.buffer.update(cx, |buffer, cx| {
7776 buffer.edit(edits.iter().cloned(), None, cx)
7777 });
7778
7779 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7780 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7781 });
7782
7783 let selections = self.selections.disjoint_anchors_arc();
7784 if let Some(transaction_id_now) =
7785 self.buffer.read(cx).last_transaction_id(cx)
7786 {
7787 if transaction_id_prev != Some(transaction_id_now) {
7788 self.selection_history
7789 .insert_transaction(transaction_id_now, selections);
7790 }
7791 }
7792
7793 self.update_visible_edit_prediction(window, cx);
7794 if self.active_edit_prediction.is_none() {
7795 self.refresh_edit_prediction(true, true, window, cx);
7796 }
7797 cx.notify();
7798 }
7799 _ => {
7800 let snapshot = self.buffer.read(cx).snapshot(cx);
7801 let cursor_offset = self
7802 .selections
7803 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7804 .head();
7805
7806 let insertion = edits.iter().find_map(|(range, text)| {
7807 let range = range.to_offset(&snapshot);
7808 if range.is_empty() && range.start == cursor_offset {
7809 Some(text)
7810 } else {
7811 None
7812 }
7813 });
7814
7815 if let Some(text) = insertion {
7816 let text_to_insert = match granularity {
7817 EditPredictionGranularity::Word => {
7818 let mut partial = text
7819 .chars()
7820 .by_ref()
7821 .take_while(|c| c.is_alphabetic())
7822 .collect::<String>();
7823 if partial.is_empty() {
7824 partial = text
7825 .chars()
7826 .by_ref()
7827 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7828 .collect::<String>();
7829 }
7830 partial
7831 }
7832 EditPredictionGranularity::Line => {
7833 if let Some(line) = text.split_inclusive('\n').next() {
7834 line.to_string()
7835 } else {
7836 text.to_string()
7837 }
7838 }
7839 EditPredictionGranularity::Full => unreachable!(),
7840 };
7841
7842 cx.emit(EditorEvent::InputHandled {
7843 utf16_range_to_replace: None,
7844 text: text_to_insert.clone().into(),
7845 });
7846
7847 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7848 self.refresh_edit_prediction(true, true, window, cx);
7849 cx.notify();
7850 } else {
7851 self.accept_partial_edit_prediction(
7852 EditPredictionGranularity::Full,
7853 window,
7854 cx,
7855 );
7856 }
7857 }
7858 }
7859 }
7860 }
7861
7862 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7863 }
7864
7865 pub fn accept_next_word_edit_prediction(
7866 &mut self,
7867 _: &AcceptNextWordEditPrediction,
7868 window: &mut Window,
7869 cx: &mut Context<Self>,
7870 ) {
7871 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7872 }
7873
7874 pub fn accept_next_line_edit_prediction(
7875 &mut self,
7876 _: &AcceptNextLineEditPrediction,
7877 window: &mut Window,
7878 cx: &mut Context<Self>,
7879 ) {
7880 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7881 }
7882
7883 pub fn accept_edit_prediction(
7884 &mut self,
7885 _: &AcceptEditPrediction,
7886 window: &mut Window,
7887 cx: &mut Context<Self>,
7888 ) {
7889 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7890 }
7891
7892 fn discard_edit_prediction(
7893 &mut self,
7894 should_report_edit_prediction_event: bool,
7895 cx: &mut Context<Self>,
7896 ) -> bool {
7897 if should_report_edit_prediction_event {
7898 let completion_id = self
7899 .active_edit_prediction
7900 .as_ref()
7901 .and_then(|active_completion| active_completion.completion_id.clone());
7902
7903 self.report_edit_prediction_event(completion_id, false, cx);
7904 }
7905
7906 if let Some(provider) = self.edit_prediction_provider() {
7907 provider.discard(cx);
7908 }
7909
7910 self.take_active_edit_prediction(cx)
7911 }
7912
7913 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7914 let Some(provider) = self.edit_prediction_provider() else {
7915 return;
7916 };
7917
7918 let Some((_, buffer, _)) = self
7919 .buffer
7920 .read(cx)
7921 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7922 else {
7923 return;
7924 };
7925
7926 let extension = buffer
7927 .read(cx)
7928 .file()
7929 .and_then(|file| Some(file.path().extension()?.to_string()));
7930
7931 let event_type = match accepted {
7932 true => "Edit Prediction Accepted",
7933 false => "Edit Prediction Discarded",
7934 };
7935 telemetry::event!(
7936 event_type,
7937 provider = provider.name(),
7938 prediction_id = id,
7939 suggestion_accepted = accepted,
7940 file_extension = extension,
7941 );
7942 }
7943
7944 fn open_editor_at_anchor(
7945 snapshot: &language::BufferSnapshot,
7946 target: language::Anchor,
7947 workspace: &Entity<Workspace>,
7948 window: &mut Window,
7949 cx: &mut App,
7950 ) -> Task<Result<()>> {
7951 workspace.update(cx, |workspace, cx| {
7952 let path = snapshot.file().map(|file| file.full_path(cx));
7953 let Some(path) =
7954 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7955 else {
7956 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7957 };
7958 let target = text::ToPoint::to_point(&target, snapshot);
7959 let item = workspace.open_path(path, None, true, window, cx);
7960 window.spawn(cx, async move |cx| {
7961 let Some(editor) = item.await?.downcast::<Editor>() else {
7962 return Ok(());
7963 };
7964 editor
7965 .update_in(cx, |editor, window, cx| {
7966 editor.go_to_singleton_buffer_point(target, window, cx);
7967 })
7968 .ok();
7969 anyhow::Ok(())
7970 })
7971 })
7972 }
7973
7974 pub fn has_active_edit_prediction(&self) -> bool {
7975 self.active_edit_prediction.is_some()
7976 }
7977
7978 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7979 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7980 return false;
7981 };
7982
7983 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7984 self.clear_highlights::<EditPredictionHighlight>(cx);
7985 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7986 true
7987 }
7988
7989 /// Returns true when we're displaying the edit prediction popover below the cursor
7990 /// like we are not previewing and the LSP autocomplete menu is visible
7991 /// or we are in `when_holding_modifier` mode.
7992 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7993 if self.edit_prediction_preview_is_active()
7994 || !self.show_edit_predictions_in_menu()
7995 || !self.edit_predictions_enabled()
7996 {
7997 return false;
7998 }
7999
8000 if self.has_visible_completions_menu() {
8001 return true;
8002 }
8003
8004 has_completion && self.edit_prediction_requires_modifier()
8005 }
8006
8007 fn handle_modifiers_changed(
8008 &mut self,
8009 modifiers: Modifiers,
8010 position_map: &PositionMap,
8011 window: &mut Window,
8012 cx: &mut Context<Self>,
8013 ) {
8014 // Ensure that the edit prediction preview is updated, even when not
8015 // enabled, if there's an active edit prediction preview.
8016 if self.show_edit_predictions_in_menu()
8017 || matches!(
8018 self.edit_prediction_preview,
8019 EditPredictionPreview::Active { .. }
8020 )
8021 {
8022 self.update_edit_prediction_preview(&modifiers, window, cx);
8023 }
8024
8025 self.update_selection_mode(&modifiers, position_map, window, cx);
8026
8027 let mouse_position = window.mouse_position();
8028 if !position_map.text_hitbox.is_hovered(window) {
8029 return;
8030 }
8031
8032 self.update_hovered_link(
8033 position_map.point_for_position(mouse_position),
8034 &position_map.snapshot,
8035 modifiers,
8036 window,
8037 cx,
8038 )
8039 }
8040
8041 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8042 match EditorSettings::get_global(cx).multi_cursor_modifier {
8043 MultiCursorModifier::Alt => modifiers.secondary(),
8044 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8045 }
8046 }
8047
8048 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8049 match EditorSettings::get_global(cx).multi_cursor_modifier {
8050 MultiCursorModifier::Alt => modifiers.alt,
8051 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8052 }
8053 }
8054
8055 fn columnar_selection_mode(
8056 modifiers: &Modifiers,
8057 cx: &mut Context<Self>,
8058 ) -> Option<ColumnarMode> {
8059 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8060 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8061 Some(ColumnarMode::FromMouse)
8062 } else if Self::is_alt_pressed(modifiers, cx) {
8063 Some(ColumnarMode::FromSelection)
8064 } else {
8065 None
8066 }
8067 } else {
8068 None
8069 }
8070 }
8071
8072 fn update_selection_mode(
8073 &mut self,
8074 modifiers: &Modifiers,
8075 position_map: &PositionMap,
8076 window: &mut Window,
8077 cx: &mut Context<Self>,
8078 ) {
8079 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8080 return;
8081 };
8082 if self.selections.pending_anchor().is_none() {
8083 return;
8084 }
8085
8086 let mouse_position = window.mouse_position();
8087 let point_for_position = position_map.point_for_position(mouse_position);
8088 let position = point_for_position.previous_valid;
8089
8090 self.select(
8091 SelectPhase::BeginColumnar {
8092 position,
8093 reset: false,
8094 mode,
8095 goal_column: point_for_position.exact_unclipped.column(),
8096 },
8097 window,
8098 cx,
8099 );
8100 }
8101
8102 fn update_edit_prediction_preview(
8103 &mut self,
8104 modifiers: &Modifiers,
8105 window: &mut Window,
8106 cx: &mut Context<Self>,
8107 ) {
8108 let mut modifiers_held = false;
8109
8110 // Check bindings for all granularities.
8111 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8112 let granularities = [
8113 EditPredictionGranularity::Full,
8114 EditPredictionGranularity::Line,
8115 EditPredictionGranularity::Word,
8116 ];
8117
8118 for granularity in granularities {
8119 if let Some(keystroke) = self
8120 .accept_edit_prediction_keybind(granularity, window, cx)
8121 .keystroke()
8122 {
8123 modifiers_held = modifiers_held
8124 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8125 }
8126 }
8127
8128 if modifiers_held {
8129 if matches!(
8130 self.edit_prediction_preview,
8131 EditPredictionPreview::Inactive { .. }
8132 ) {
8133 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8134 provider.provider.did_show(cx)
8135 }
8136
8137 self.edit_prediction_preview = EditPredictionPreview::Active {
8138 previous_scroll_position: None,
8139 since: Instant::now(),
8140 };
8141
8142 self.update_visible_edit_prediction(window, cx);
8143 cx.notify();
8144 }
8145 } else if let EditPredictionPreview::Active {
8146 previous_scroll_position,
8147 since,
8148 } = self.edit_prediction_preview
8149 {
8150 if let (Some(previous_scroll_position), Some(position_map)) =
8151 (previous_scroll_position, self.last_position_map.as_ref())
8152 {
8153 self.set_scroll_position(
8154 previous_scroll_position
8155 .scroll_position(&position_map.snapshot.display_snapshot),
8156 window,
8157 cx,
8158 );
8159 }
8160
8161 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8162 released_too_fast: since.elapsed() < Duration::from_millis(200),
8163 };
8164 self.clear_row_highlights::<EditPredictionPreview>();
8165 self.update_visible_edit_prediction(window, cx);
8166 cx.notify();
8167 }
8168 }
8169
8170 fn update_visible_edit_prediction(
8171 &mut self,
8172 _window: &mut Window,
8173 cx: &mut Context<Self>,
8174 ) -> Option<()> {
8175 if DisableAiSettings::get_global(cx).disable_ai {
8176 return None;
8177 }
8178
8179 if self.ime_transaction.is_some() {
8180 self.discard_edit_prediction(false, cx);
8181 return None;
8182 }
8183
8184 let selection = self.selections.newest_anchor();
8185 let cursor = selection.head();
8186 let multibuffer = self.buffer.read(cx).snapshot(cx);
8187 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8188 let excerpt_id = cursor.excerpt_id;
8189
8190 let show_in_menu = self.show_edit_predictions_in_menu();
8191 let completions_menu_has_precedence = !show_in_menu
8192 && (self.context_menu.borrow().is_some()
8193 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8194
8195 if completions_menu_has_precedence
8196 || !offset_selection.is_empty()
8197 || self
8198 .active_edit_prediction
8199 .as_ref()
8200 .is_some_and(|completion| {
8201 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8202 return false;
8203 };
8204 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8205 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8206 !invalidation_range.contains(&offset_selection.head())
8207 })
8208 {
8209 self.discard_edit_prediction(false, cx);
8210 return None;
8211 }
8212
8213 self.take_active_edit_prediction(cx);
8214 let Some(provider) = self.edit_prediction_provider() else {
8215 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8216 return None;
8217 };
8218
8219 let (buffer, cursor_buffer_position) =
8220 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8221
8222 self.edit_prediction_settings =
8223 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8224
8225 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8226
8227 if self.edit_prediction_indent_conflict {
8228 let cursor_point = cursor.to_point(&multibuffer);
8229 let mut suggested_indent = None;
8230 multibuffer.suggested_indents_callback(
8231 cursor_point.row..cursor_point.row + 1,
8232 |_, indent| {
8233 suggested_indent = Some(indent);
8234 ControlFlow::Break(())
8235 },
8236 cx,
8237 );
8238
8239 if let Some(indent) = suggested_indent
8240 && indent.len == cursor_point.column
8241 {
8242 self.edit_prediction_indent_conflict = false;
8243 }
8244 }
8245
8246 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8247
8248 let (completion_id, edits, edit_preview) = match edit_prediction {
8249 edit_prediction_types::EditPrediction::Local {
8250 id,
8251 edits,
8252 edit_preview,
8253 } => (id, edits, edit_preview),
8254 edit_prediction_types::EditPrediction::Jump {
8255 id,
8256 snapshot,
8257 target,
8258 } => {
8259 self.stale_edit_prediction_in_menu = None;
8260 self.active_edit_prediction = Some(EditPredictionState {
8261 inlay_ids: vec![],
8262 completion: EditPrediction::MoveOutside { snapshot, target },
8263 completion_id: id,
8264 invalidation_range: None,
8265 });
8266 cx.notify();
8267 return Some(());
8268 }
8269 };
8270
8271 let edits = edits
8272 .into_iter()
8273 .flat_map(|(range, new_text)| {
8274 Some((
8275 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8276 new_text,
8277 ))
8278 })
8279 .collect::<Vec<_>>();
8280 if edits.is_empty() {
8281 return None;
8282 }
8283
8284 let first_edit_start = edits.first().unwrap().0.start;
8285 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8286 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8287
8288 let last_edit_end = edits.last().unwrap().0.end;
8289 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8290 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8291
8292 let cursor_row = cursor.to_point(&multibuffer).row;
8293
8294 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8295
8296 let mut inlay_ids = Vec::new();
8297 let invalidation_row_range;
8298 let move_invalidation_row_range = if cursor_row < edit_start_row {
8299 Some(cursor_row..edit_end_row)
8300 } else if cursor_row > edit_end_row {
8301 Some(edit_start_row..cursor_row)
8302 } else {
8303 None
8304 };
8305 let supports_jump = self
8306 .edit_prediction_provider
8307 .as_ref()
8308 .map(|provider| provider.provider.supports_jump_to_edit())
8309 .unwrap_or(true);
8310
8311 let is_move = supports_jump
8312 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8313 let completion = if is_move {
8314 invalidation_row_range =
8315 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8316 let target = first_edit_start;
8317 EditPrediction::MoveWithin { target, snapshot }
8318 } else {
8319 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8320 && !self.edit_predictions_hidden_for_vim_mode;
8321
8322 if show_completions_in_buffer {
8323 if let Some(provider) = &self.edit_prediction_provider {
8324 provider.provider.did_show(cx);
8325 }
8326 if edits
8327 .iter()
8328 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8329 {
8330 let mut inlays = Vec::new();
8331 for (range, new_text) in &edits {
8332 let inlay = Inlay::edit_prediction(
8333 post_inc(&mut self.next_inlay_id),
8334 range.start,
8335 new_text.as_ref(),
8336 );
8337 inlay_ids.push(inlay.id);
8338 inlays.push(inlay);
8339 }
8340
8341 self.splice_inlays(&[], inlays, cx);
8342 } else {
8343 let background_color = cx.theme().status().deleted_background;
8344 self.highlight_text::<EditPredictionHighlight>(
8345 edits.iter().map(|(range, _)| range.clone()).collect(),
8346 HighlightStyle {
8347 background_color: Some(background_color),
8348 ..Default::default()
8349 },
8350 cx,
8351 );
8352 }
8353 }
8354
8355 invalidation_row_range = edit_start_row..edit_end_row;
8356
8357 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8358 if provider.show_tab_accept_marker() {
8359 EditDisplayMode::TabAccept
8360 } else {
8361 EditDisplayMode::Inline
8362 }
8363 } else {
8364 EditDisplayMode::DiffPopover
8365 };
8366
8367 EditPrediction::Edit {
8368 edits,
8369 edit_preview,
8370 display_mode,
8371 snapshot,
8372 }
8373 };
8374
8375 let invalidation_range = multibuffer
8376 .anchor_before(Point::new(invalidation_row_range.start, 0))
8377 ..multibuffer.anchor_after(Point::new(
8378 invalidation_row_range.end,
8379 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8380 ));
8381
8382 self.stale_edit_prediction_in_menu = None;
8383 self.active_edit_prediction = Some(EditPredictionState {
8384 inlay_ids,
8385 completion,
8386 completion_id,
8387 invalidation_range: Some(invalidation_range),
8388 });
8389
8390 cx.notify();
8391
8392 Some(())
8393 }
8394
8395 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8396 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8397 }
8398
8399 fn clear_tasks(&mut self) {
8400 self.tasks.clear()
8401 }
8402
8403 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8404 if self.tasks.insert(key, value).is_some() {
8405 // This case should hopefully be rare, but just in case...
8406 log::error!(
8407 "multiple different run targets found on a single line, only the last target will be rendered"
8408 )
8409 }
8410 }
8411
8412 /// Get all display points of breakpoints that will be rendered within editor
8413 ///
8414 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8415 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8416 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8417 fn active_breakpoints(
8418 &self,
8419 range: Range<DisplayRow>,
8420 window: &mut Window,
8421 cx: &mut Context<Self>,
8422 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8423 let mut breakpoint_display_points = HashMap::default();
8424
8425 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8426 return breakpoint_display_points;
8427 };
8428
8429 let snapshot = self.snapshot(window, cx);
8430
8431 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8432 let Some(project) = self.project() else {
8433 return breakpoint_display_points;
8434 };
8435
8436 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8437 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8438
8439 for (buffer_snapshot, range, excerpt_id) in
8440 multi_buffer_snapshot.range_to_buffer_ranges(range)
8441 {
8442 let Some(buffer) = project
8443 .read(cx)
8444 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8445 else {
8446 continue;
8447 };
8448 let breakpoints = breakpoint_store.read(cx).breakpoints(
8449 &buffer,
8450 Some(
8451 buffer_snapshot.anchor_before(range.start)
8452 ..buffer_snapshot.anchor_after(range.end),
8453 ),
8454 buffer_snapshot,
8455 cx,
8456 );
8457 for (breakpoint, state) in breakpoints {
8458 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8459 let position = multi_buffer_anchor
8460 .to_point(&multi_buffer_snapshot)
8461 .to_display_point(&snapshot);
8462
8463 breakpoint_display_points.insert(
8464 position.row(),
8465 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8466 );
8467 }
8468 }
8469
8470 breakpoint_display_points
8471 }
8472
8473 fn breakpoint_context_menu(
8474 &self,
8475 anchor: Anchor,
8476 window: &mut Window,
8477 cx: &mut Context<Self>,
8478 ) -> Entity<ui::ContextMenu> {
8479 let weak_editor = cx.weak_entity();
8480 let focus_handle = self.focus_handle(cx);
8481
8482 let row = self
8483 .buffer
8484 .read(cx)
8485 .snapshot(cx)
8486 .summary_for_anchor::<Point>(&anchor)
8487 .row;
8488
8489 let breakpoint = self
8490 .breakpoint_at_row(row, window, cx)
8491 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8492
8493 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8494 "Edit Log Breakpoint"
8495 } else {
8496 "Set Log Breakpoint"
8497 };
8498
8499 let condition_breakpoint_msg = if breakpoint
8500 .as_ref()
8501 .is_some_and(|bp| bp.1.condition.is_some())
8502 {
8503 "Edit Condition Breakpoint"
8504 } else {
8505 "Set Condition Breakpoint"
8506 };
8507
8508 let hit_condition_breakpoint_msg = if breakpoint
8509 .as_ref()
8510 .is_some_and(|bp| bp.1.hit_condition.is_some())
8511 {
8512 "Edit Hit Condition Breakpoint"
8513 } else {
8514 "Set Hit Condition Breakpoint"
8515 };
8516
8517 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8518 "Unset Breakpoint"
8519 } else {
8520 "Set Breakpoint"
8521 };
8522
8523 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8524
8525 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8526 BreakpointState::Enabled => Some("Disable"),
8527 BreakpointState::Disabled => Some("Enable"),
8528 });
8529
8530 let (anchor, breakpoint) =
8531 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8532
8533 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8534 menu.on_blur_subscription(Subscription::new(|| {}))
8535 .context(focus_handle)
8536 .when(run_to_cursor, |this| {
8537 let weak_editor = weak_editor.clone();
8538 this.entry("Run to cursor", None, move |window, cx| {
8539 weak_editor
8540 .update(cx, |editor, cx| {
8541 editor.change_selections(
8542 SelectionEffects::no_scroll(),
8543 window,
8544 cx,
8545 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8546 );
8547 })
8548 .ok();
8549
8550 window.dispatch_action(Box::new(RunToCursor), cx);
8551 })
8552 .separator()
8553 })
8554 .when_some(toggle_state_msg, |this, msg| {
8555 this.entry(msg, None, {
8556 let weak_editor = weak_editor.clone();
8557 let breakpoint = breakpoint.clone();
8558 move |_window, cx| {
8559 weak_editor
8560 .update(cx, |this, cx| {
8561 this.edit_breakpoint_at_anchor(
8562 anchor,
8563 breakpoint.as_ref().clone(),
8564 BreakpointEditAction::InvertState,
8565 cx,
8566 );
8567 })
8568 .log_err();
8569 }
8570 })
8571 })
8572 .entry(set_breakpoint_msg, None, {
8573 let weak_editor = weak_editor.clone();
8574 let breakpoint = breakpoint.clone();
8575 move |_window, cx| {
8576 weak_editor
8577 .update(cx, |this, cx| {
8578 this.edit_breakpoint_at_anchor(
8579 anchor,
8580 breakpoint.as_ref().clone(),
8581 BreakpointEditAction::Toggle,
8582 cx,
8583 );
8584 })
8585 .log_err();
8586 }
8587 })
8588 .entry(log_breakpoint_msg, None, {
8589 let breakpoint = breakpoint.clone();
8590 let weak_editor = weak_editor.clone();
8591 move |window, cx| {
8592 weak_editor
8593 .update(cx, |this, cx| {
8594 this.add_edit_breakpoint_block(
8595 anchor,
8596 breakpoint.as_ref(),
8597 BreakpointPromptEditAction::Log,
8598 window,
8599 cx,
8600 );
8601 })
8602 .log_err();
8603 }
8604 })
8605 .entry(condition_breakpoint_msg, None, {
8606 let breakpoint = breakpoint.clone();
8607 let weak_editor = weak_editor.clone();
8608 move |window, cx| {
8609 weak_editor
8610 .update(cx, |this, cx| {
8611 this.add_edit_breakpoint_block(
8612 anchor,
8613 breakpoint.as_ref(),
8614 BreakpointPromptEditAction::Condition,
8615 window,
8616 cx,
8617 );
8618 })
8619 .log_err();
8620 }
8621 })
8622 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8623 weak_editor
8624 .update(cx, |this, cx| {
8625 this.add_edit_breakpoint_block(
8626 anchor,
8627 breakpoint.as_ref(),
8628 BreakpointPromptEditAction::HitCondition,
8629 window,
8630 cx,
8631 );
8632 })
8633 .log_err();
8634 })
8635 })
8636 }
8637
8638 fn render_breakpoint(
8639 &self,
8640 position: Anchor,
8641 row: DisplayRow,
8642 breakpoint: &Breakpoint,
8643 state: Option<BreakpointSessionState>,
8644 cx: &mut Context<Self>,
8645 ) -> IconButton {
8646 let is_rejected = state.is_some_and(|s| !s.verified);
8647 // Is it a breakpoint that shows up when hovering over gutter?
8648 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8649 (false, false),
8650 |PhantomBreakpointIndicator {
8651 is_active,
8652 display_row,
8653 collides_with_existing_breakpoint,
8654 }| {
8655 (
8656 is_active && display_row == row,
8657 collides_with_existing_breakpoint,
8658 )
8659 },
8660 );
8661
8662 let (color, icon) = {
8663 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8664 (false, false) => ui::IconName::DebugBreakpoint,
8665 (true, false) => ui::IconName::DebugLogBreakpoint,
8666 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8667 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8668 };
8669
8670 let color = cx.theme().colors();
8671
8672 let color = if is_phantom {
8673 if collides_with_existing {
8674 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8675 } else {
8676 Color::Hint
8677 }
8678 } else if is_rejected {
8679 Color::Disabled
8680 } else {
8681 Color::Debugger
8682 };
8683
8684 (color, icon)
8685 };
8686
8687 let breakpoint = Arc::from(breakpoint.clone());
8688
8689 let alt_as_text = gpui::Keystroke {
8690 modifiers: Modifiers::secondary_key(),
8691 ..Default::default()
8692 };
8693 let primary_action_text = if breakpoint.is_disabled() {
8694 "Enable breakpoint"
8695 } else if is_phantom && !collides_with_existing {
8696 "Set breakpoint"
8697 } else {
8698 "Unset breakpoint"
8699 };
8700 let focus_handle = self.focus_handle.clone();
8701
8702 let meta = if is_rejected {
8703 SharedString::from("No executable code is associated with this line.")
8704 } else if collides_with_existing && !breakpoint.is_disabled() {
8705 SharedString::from(format!(
8706 "{alt_as_text}-click to disable,\nright-click for more options."
8707 ))
8708 } else {
8709 SharedString::from("Right-click for more options.")
8710 };
8711 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8712 .icon_size(IconSize::XSmall)
8713 .size(ui::ButtonSize::None)
8714 .when(is_rejected, |this| {
8715 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8716 })
8717 .icon_color(color)
8718 .style(ButtonStyle::Transparent)
8719 .on_click(cx.listener({
8720 move |editor, event: &ClickEvent, window, cx| {
8721 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8722 BreakpointEditAction::InvertState
8723 } else {
8724 BreakpointEditAction::Toggle
8725 };
8726
8727 window.focus(&editor.focus_handle(cx));
8728 editor.edit_breakpoint_at_anchor(
8729 position,
8730 breakpoint.as_ref().clone(),
8731 edit_action,
8732 cx,
8733 );
8734 }
8735 }))
8736 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8737 editor.set_breakpoint_context_menu(
8738 row,
8739 Some(position),
8740 event.position(),
8741 window,
8742 cx,
8743 );
8744 }))
8745 .tooltip(move |_window, cx| {
8746 Tooltip::with_meta_in(
8747 primary_action_text,
8748 Some(&ToggleBreakpoint),
8749 meta.clone(),
8750 &focus_handle,
8751 cx,
8752 )
8753 })
8754 }
8755
8756 fn build_tasks_context(
8757 project: &Entity<Project>,
8758 buffer: &Entity<Buffer>,
8759 buffer_row: u32,
8760 tasks: &Arc<RunnableTasks>,
8761 cx: &mut Context<Self>,
8762 ) -> Task<Option<task::TaskContext>> {
8763 let position = Point::new(buffer_row, tasks.column);
8764 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8765 let location = Location {
8766 buffer: buffer.clone(),
8767 range: range_start..range_start,
8768 };
8769 // Fill in the environmental variables from the tree-sitter captures
8770 let mut captured_task_variables = TaskVariables::default();
8771 for (capture_name, value) in tasks.extra_variables.clone() {
8772 captured_task_variables.insert(
8773 task::VariableName::Custom(capture_name.into()),
8774 value.clone(),
8775 );
8776 }
8777 project.update(cx, |project, cx| {
8778 project.task_store().update(cx, |task_store, cx| {
8779 task_store.task_context_for_location(captured_task_variables, location, cx)
8780 })
8781 })
8782 }
8783
8784 pub fn spawn_nearest_task(
8785 &mut self,
8786 action: &SpawnNearestTask,
8787 window: &mut Window,
8788 cx: &mut Context<Self>,
8789 ) {
8790 let Some((workspace, _)) = self.workspace.clone() else {
8791 return;
8792 };
8793 let Some(project) = self.project.clone() else {
8794 return;
8795 };
8796
8797 // Try to find a closest, enclosing node using tree-sitter that has a task
8798 let Some((buffer, buffer_row, tasks)) = self
8799 .find_enclosing_node_task(cx)
8800 // Or find the task that's closest in row-distance.
8801 .or_else(|| self.find_closest_task(cx))
8802 else {
8803 return;
8804 };
8805
8806 let reveal_strategy = action.reveal;
8807 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8808 cx.spawn_in(window, async move |_, cx| {
8809 let context = task_context.await?;
8810 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8811
8812 let resolved = &mut resolved_task.resolved;
8813 resolved.reveal = reveal_strategy;
8814
8815 workspace
8816 .update_in(cx, |workspace, window, cx| {
8817 workspace.schedule_resolved_task(
8818 task_source_kind,
8819 resolved_task,
8820 false,
8821 window,
8822 cx,
8823 );
8824 })
8825 .ok()
8826 })
8827 .detach();
8828 }
8829
8830 fn find_closest_task(
8831 &mut self,
8832 cx: &mut Context<Self>,
8833 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8834 let cursor_row = self
8835 .selections
8836 .newest_adjusted(&self.display_snapshot(cx))
8837 .head()
8838 .row;
8839
8840 let ((buffer_id, row), tasks) = self
8841 .tasks
8842 .iter()
8843 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8844
8845 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8846 let tasks = Arc::new(tasks.to_owned());
8847 Some((buffer, *row, tasks))
8848 }
8849
8850 fn find_enclosing_node_task(
8851 &mut self,
8852 cx: &mut Context<Self>,
8853 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8854 let snapshot = self.buffer.read(cx).snapshot(cx);
8855 let offset = self
8856 .selections
8857 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8858 .head();
8859 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8860 let offset = excerpt.map_offset_to_buffer(offset);
8861 let buffer_id = excerpt.buffer().remote_id();
8862
8863 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8864 let mut cursor = layer.node().walk();
8865
8866 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8867 if cursor.node().end_byte() == offset.0 {
8868 cursor.goto_next_sibling();
8869 }
8870 }
8871
8872 // Ascend to the smallest ancestor that contains the range and has a task.
8873 loop {
8874 let node = cursor.node();
8875 let node_range = node.byte_range();
8876 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8877
8878 // Check if this node contains our offset
8879 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8880 // If it contains offset, check for task
8881 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8882 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8883 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8884 }
8885 }
8886
8887 if !cursor.goto_parent() {
8888 break;
8889 }
8890 }
8891 None
8892 }
8893
8894 fn render_run_indicator(
8895 &self,
8896 _style: &EditorStyle,
8897 is_active: bool,
8898 row: DisplayRow,
8899 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8900 cx: &mut Context<Self>,
8901 ) -> IconButton {
8902 let color = Color::Muted;
8903 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8904
8905 IconButton::new(
8906 ("run_indicator", row.0 as usize),
8907 ui::IconName::PlayOutlined,
8908 )
8909 .shape(ui::IconButtonShape::Square)
8910 .icon_size(IconSize::XSmall)
8911 .icon_color(color)
8912 .toggle_state(is_active)
8913 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8914 let quick_launch = match e {
8915 ClickEvent::Keyboard(_) => true,
8916 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8917 };
8918
8919 window.focus(&editor.focus_handle(cx));
8920 editor.toggle_code_actions(
8921 &ToggleCodeActions {
8922 deployed_from: Some(CodeActionSource::RunMenu(row)),
8923 quick_launch,
8924 },
8925 window,
8926 cx,
8927 );
8928 }))
8929 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8930 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8931 }))
8932 }
8933
8934 pub fn context_menu_visible(&self) -> bool {
8935 !self.edit_prediction_preview_is_active()
8936 && self
8937 .context_menu
8938 .borrow()
8939 .as_ref()
8940 .is_some_and(|menu| menu.visible())
8941 }
8942
8943 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8944 self.context_menu
8945 .borrow()
8946 .as_ref()
8947 .map(|menu| menu.origin())
8948 }
8949
8950 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8951 self.context_menu_options = Some(options);
8952 }
8953
8954 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8955 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8956
8957 fn render_edit_prediction_popover(
8958 &mut self,
8959 text_bounds: &Bounds<Pixels>,
8960 content_origin: gpui::Point<Pixels>,
8961 right_margin: Pixels,
8962 editor_snapshot: &EditorSnapshot,
8963 visible_row_range: Range<DisplayRow>,
8964 scroll_top: ScrollOffset,
8965 scroll_bottom: ScrollOffset,
8966 line_layouts: &[LineWithInvisibles],
8967 line_height: Pixels,
8968 scroll_position: gpui::Point<ScrollOffset>,
8969 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8970 newest_selection_head: Option<DisplayPoint>,
8971 editor_width: Pixels,
8972 style: &EditorStyle,
8973 window: &mut Window,
8974 cx: &mut App,
8975 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8976 if self.mode().is_minimap() {
8977 return None;
8978 }
8979 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8980
8981 if self.edit_prediction_visible_in_cursor_popover(true) {
8982 return None;
8983 }
8984
8985 match &active_edit_prediction.completion {
8986 EditPrediction::MoveWithin { target, .. } => {
8987 let target_display_point = target.to_display_point(editor_snapshot);
8988
8989 if self.edit_prediction_requires_modifier() {
8990 if !self.edit_prediction_preview_is_active() {
8991 return None;
8992 }
8993
8994 self.render_edit_prediction_modifier_jump_popover(
8995 text_bounds,
8996 content_origin,
8997 visible_row_range,
8998 line_layouts,
8999 line_height,
9000 scroll_pixel_position,
9001 newest_selection_head,
9002 target_display_point,
9003 window,
9004 cx,
9005 )
9006 } else {
9007 self.render_edit_prediction_eager_jump_popover(
9008 text_bounds,
9009 content_origin,
9010 editor_snapshot,
9011 visible_row_range,
9012 scroll_top,
9013 scroll_bottom,
9014 line_height,
9015 scroll_pixel_position,
9016 target_display_point,
9017 editor_width,
9018 window,
9019 cx,
9020 )
9021 }
9022 }
9023 EditPrediction::Edit {
9024 display_mode: EditDisplayMode::Inline,
9025 ..
9026 } => None,
9027 EditPrediction::Edit {
9028 display_mode: EditDisplayMode::TabAccept,
9029 edits,
9030 ..
9031 } => {
9032 let range = &edits.first()?.0;
9033 let target_display_point = range.end.to_display_point(editor_snapshot);
9034
9035 self.render_edit_prediction_end_of_line_popover(
9036 "Accept",
9037 editor_snapshot,
9038 visible_row_range,
9039 target_display_point,
9040 line_height,
9041 scroll_pixel_position,
9042 content_origin,
9043 editor_width,
9044 window,
9045 cx,
9046 )
9047 }
9048 EditPrediction::Edit {
9049 edits,
9050 edit_preview,
9051 display_mode: EditDisplayMode::DiffPopover,
9052 snapshot,
9053 } => self.render_edit_prediction_diff_popover(
9054 text_bounds,
9055 content_origin,
9056 right_margin,
9057 editor_snapshot,
9058 visible_row_range,
9059 line_layouts,
9060 line_height,
9061 scroll_position,
9062 scroll_pixel_position,
9063 newest_selection_head,
9064 editor_width,
9065 style,
9066 edits,
9067 edit_preview,
9068 snapshot,
9069 window,
9070 cx,
9071 ),
9072 EditPrediction::MoveOutside { snapshot, .. } => {
9073 let mut element = self
9074 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9075 .into_any();
9076
9077 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9078 let origin_x = text_bounds.size.width - size.width - px(30.);
9079 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9080 element.prepaint_at(origin, window, cx);
9081
9082 Some((element, origin))
9083 }
9084 }
9085 }
9086
9087 fn render_edit_prediction_modifier_jump_popover(
9088 &mut self,
9089 text_bounds: &Bounds<Pixels>,
9090 content_origin: gpui::Point<Pixels>,
9091 visible_row_range: Range<DisplayRow>,
9092 line_layouts: &[LineWithInvisibles],
9093 line_height: Pixels,
9094 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9095 newest_selection_head: Option<DisplayPoint>,
9096 target_display_point: DisplayPoint,
9097 window: &mut Window,
9098 cx: &mut App,
9099 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9100 let scrolled_content_origin =
9101 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9102
9103 const SCROLL_PADDING_Y: Pixels = px(12.);
9104
9105 if target_display_point.row() < visible_row_range.start {
9106 return self.render_edit_prediction_scroll_popover(
9107 |_| SCROLL_PADDING_Y,
9108 IconName::ArrowUp,
9109 visible_row_range,
9110 line_layouts,
9111 newest_selection_head,
9112 scrolled_content_origin,
9113 window,
9114 cx,
9115 );
9116 } else if target_display_point.row() >= visible_row_range.end {
9117 return self.render_edit_prediction_scroll_popover(
9118 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9119 IconName::ArrowDown,
9120 visible_row_range,
9121 line_layouts,
9122 newest_selection_head,
9123 scrolled_content_origin,
9124 window,
9125 cx,
9126 );
9127 }
9128
9129 const POLE_WIDTH: Pixels = px(2.);
9130
9131 let line_layout =
9132 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9133 let target_column = target_display_point.column() as usize;
9134
9135 let target_x = line_layout.x_for_index(target_column);
9136 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9137 - scroll_pixel_position.y;
9138
9139 let flag_on_right = target_x < text_bounds.size.width / 2.;
9140
9141 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9142 border_color.l += 0.001;
9143
9144 let mut element = v_flex()
9145 .items_end()
9146 .when(flag_on_right, |el| el.items_start())
9147 .child(if flag_on_right {
9148 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9149 .rounded_bl(px(0.))
9150 .rounded_tl(px(0.))
9151 .border_l_2()
9152 .border_color(border_color)
9153 } else {
9154 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9155 .rounded_br(px(0.))
9156 .rounded_tr(px(0.))
9157 .border_r_2()
9158 .border_color(border_color)
9159 })
9160 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9161 .into_any();
9162
9163 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9164
9165 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9166 - point(
9167 if flag_on_right {
9168 POLE_WIDTH
9169 } else {
9170 size.width - POLE_WIDTH
9171 },
9172 size.height - line_height,
9173 );
9174
9175 origin.x = origin.x.max(content_origin.x);
9176
9177 element.prepaint_at(origin, window, cx);
9178
9179 Some((element, origin))
9180 }
9181
9182 fn render_edit_prediction_scroll_popover(
9183 &mut self,
9184 to_y: impl Fn(Size<Pixels>) -> Pixels,
9185 scroll_icon: IconName,
9186 visible_row_range: Range<DisplayRow>,
9187 line_layouts: &[LineWithInvisibles],
9188 newest_selection_head: Option<DisplayPoint>,
9189 scrolled_content_origin: gpui::Point<Pixels>,
9190 window: &mut Window,
9191 cx: &mut App,
9192 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9193 let mut element = self
9194 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9195 .into_any();
9196
9197 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9198
9199 let cursor = newest_selection_head?;
9200 let cursor_row_layout =
9201 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9202 let cursor_column = cursor.column() as usize;
9203
9204 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9205
9206 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9207
9208 element.prepaint_at(origin, window, cx);
9209 Some((element, origin))
9210 }
9211
9212 fn render_edit_prediction_eager_jump_popover(
9213 &mut self,
9214 text_bounds: &Bounds<Pixels>,
9215 content_origin: gpui::Point<Pixels>,
9216 editor_snapshot: &EditorSnapshot,
9217 visible_row_range: Range<DisplayRow>,
9218 scroll_top: ScrollOffset,
9219 scroll_bottom: ScrollOffset,
9220 line_height: Pixels,
9221 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9222 target_display_point: DisplayPoint,
9223 editor_width: Pixels,
9224 window: &mut Window,
9225 cx: &mut App,
9226 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9227 if target_display_point.row().as_f64() < scroll_top {
9228 let mut element = self
9229 .render_edit_prediction_line_popover(
9230 "Jump to Edit",
9231 Some(IconName::ArrowUp),
9232 window,
9233 cx,
9234 )
9235 .into_any();
9236
9237 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9238 let offset = point(
9239 (text_bounds.size.width - size.width) / 2.,
9240 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9241 );
9242
9243 let origin = text_bounds.origin + offset;
9244 element.prepaint_at(origin, window, cx);
9245 Some((element, origin))
9246 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9247 let mut element = self
9248 .render_edit_prediction_line_popover(
9249 "Jump to Edit",
9250 Some(IconName::ArrowDown),
9251 window,
9252 cx,
9253 )
9254 .into_any();
9255
9256 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9257 let offset = point(
9258 (text_bounds.size.width - size.width) / 2.,
9259 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9260 );
9261
9262 let origin = text_bounds.origin + offset;
9263 element.prepaint_at(origin, window, cx);
9264 Some((element, origin))
9265 } else {
9266 self.render_edit_prediction_end_of_line_popover(
9267 "Jump to Edit",
9268 editor_snapshot,
9269 visible_row_range,
9270 target_display_point,
9271 line_height,
9272 scroll_pixel_position,
9273 content_origin,
9274 editor_width,
9275 window,
9276 cx,
9277 )
9278 }
9279 }
9280
9281 fn render_edit_prediction_end_of_line_popover(
9282 self: &mut Editor,
9283 label: &'static str,
9284 editor_snapshot: &EditorSnapshot,
9285 visible_row_range: Range<DisplayRow>,
9286 target_display_point: DisplayPoint,
9287 line_height: Pixels,
9288 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9289 content_origin: gpui::Point<Pixels>,
9290 editor_width: Pixels,
9291 window: &mut Window,
9292 cx: &mut App,
9293 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9294 let target_line_end = DisplayPoint::new(
9295 target_display_point.row(),
9296 editor_snapshot.line_len(target_display_point.row()),
9297 );
9298
9299 let mut element = self
9300 .render_edit_prediction_line_popover(label, None, window, cx)
9301 .into_any();
9302
9303 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9304
9305 let line_origin =
9306 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9307
9308 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9309 let mut origin = start_point
9310 + line_origin
9311 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9312 origin.x = origin.x.max(content_origin.x);
9313
9314 let max_x = content_origin.x + editor_width - size.width;
9315
9316 if origin.x > max_x {
9317 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9318
9319 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9320 origin.y += offset;
9321 IconName::ArrowUp
9322 } else {
9323 origin.y -= offset;
9324 IconName::ArrowDown
9325 };
9326
9327 element = self
9328 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9329 .into_any();
9330
9331 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9332
9333 origin.x = content_origin.x + editor_width - size.width - px(2.);
9334 }
9335
9336 element.prepaint_at(origin, window, cx);
9337 Some((element, origin))
9338 }
9339
9340 fn render_edit_prediction_diff_popover(
9341 self: &Editor,
9342 text_bounds: &Bounds<Pixels>,
9343 content_origin: gpui::Point<Pixels>,
9344 right_margin: Pixels,
9345 editor_snapshot: &EditorSnapshot,
9346 visible_row_range: Range<DisplayRow>,
9347 line_layouts: &[LineWithInvisibles],
9348 line_height: Pixels,
9349 scroll_position: gpui::Point<ScrollOffset>,
9350 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9351 newest_selection_head: Option<DisplayPoint>,
9352 editor_width: Pixels,
9353 style: &EditorStyle,
9354 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9355 edit_preview: &Option<language::EditPreview>,
9356 snapshot: &language::BufferSnapshot,
9357 window: &mut Window,
9358 cx: &mut App,
9359 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9360 let edit_start = edits
9361 .first()
9362 .unwrap()
9363 .0
9364 .start
9365 .to_display_point(editor_snapshot);
9366 let edit_end = edits
9367 .last()
9368 .unwrap()
9369 .0
9370 .end
9371 .to_display_point(editor_snapshot);
9372
9373 let is_visible = visible_row_range.contains(&edit_start.row())
9374 || visible_row_range.contains(&edit_end.row());
9375 if !is_visible {
9376 return None;
9377 }
9378
9379 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9380 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9381 } else {
9382 // Fallback for providers without edit_preview
9383 crate::edit_prediction_fallback_text(edits, cx)
9384 };
9385
9386 let styled_text = highlighted_edits.to_styled_text(&style.text);
9387 let line_count = highlighted_edits.text.lines().count();
9388
9389 const BORDER_WIDTH: Pixels = px(1.);
9390
9391 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9392 let has_keybind = keybind.is_some();
9393
9394 let mut element = h_flex()
9395 .items_start()
9396 .child(
9397 h_flex()
9398 .bg(cx.theme().colors().editor_background)
9399 .border(BORDER_WIDTH)
9400 .shadow_xs()
9401 .border_color(cx.theme().colors().border)
9402 .rounded_l_lg()
9403 .when(line_count > 1, |el| el.rounded_br_lg())
9404 .pr_1()
9405 .child(styled_text),
9406 )
9407 .child(
9408 h_flex()
9409 .h(line_height + BORDER_WIDTH * 2.)
9410 .px_1p5()
9411 .gap_1()
9412 // Workaround: For some reason, there's a gap if we don't do this
9413 .ml(-BORDER_WIDTH)
9414 .shadow(vec![gpui::BoxShadow {
9415 color: gpui::black().opacity(0.05),
9416 offset: point(px(1.), px(1.)),
9417 blur_radius: px(2.),
9418 spread_radius: px(0.),
9419 }])
9420 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9421 .border(BORDER_WIDTH)
9422 .border_color(cx.theme().colors().border)
9423 .rounded_r_lg()
9424 .id("edit_prediction_diff_popover_keybind")
9425 .when(!has_keybind, |el| {
9426 let status_colors = cx.theme().status();
9427
9428 el.bg(status_colors.error_background)
9429 .border_color(status_colors.error.opacity(0.6))
9430 .child(Icon::new(IconName::Info).color(Color::Error))
9431 .cursor_default()
9432 .hoverable_tooltip(move |_window, cx| {
9433 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9434 })
9435 })
9436 .children(keybind),
9437 )
9438 .into_any();
9439
9440 let longest_row =
9441 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9442 let longest_line_width = if visible_row_range.contains(&longest_row) {
9443 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9444 } else {
9445 layout_line(
9446 longest_row,
9447 editor_snapshot,
9448 style,
9449 editor_width,
9450 |_| false,
9451 window,
9452 cx,
9453 )
9454 .width
9455 };
9456
9457 let viewport_bounds =
9458 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9459 right: -right_margin,
9460 ..Default::default()
9461 });
9462
9463 let x_after_longest = Pixels::from(
9464 ScrollPixelOffset::from(
9465 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9466 ) - scroll_pixel_position.x,
9467 );
9468
9469 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9470
9471 // Fully visible if it can be displayed within the window (allow overlapping other
9472 // panes). However, this is only allowed if the popover starts within text_bounds.
9473 let can_position_to_the_right = x_after_longest < text_bounds.right()
9474 && x_after_longest + element_bounds.width < viewport_bounds.right();
9475
9476 let mut origin = if can_position_to_the_right {
9477 point(
9478 x_after_longest,
9479 text_bounds.origin.y
9480 + Pixels::from(
9481 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9482 - scroll_pixel_position.y,
9483 ),
9484 )
9485 } else {
9486 let cursor_row = newest_selection_head.map(|head| head.row());
9487 let above_edit = edit_start
9488 .row()
9489 .0
9490 .checked_sub(line_count as u32)
9491 .map(DisplayRow);
9492 let below_edit = Some(edit_end.row() + 1);
9493 let above_cursor =
9494 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9495 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9496
9497 // Place the edit popover adjacent to the edit if there is a location
9498 // available that is onscreen and does not obscure the cursor. Otherwise,
9499 // place it adjacent to the cursor.
9500 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9501 .into_iter()
9502 .flatten()
9503 .find(|&start_row| {
9504 let end_row = start_row + line_count as u32;
9505 visible_row_range.contains(&start_row)
9506 && visible_row_range.contains(&end_row)
9507 && cursor_row
9508 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9509 })?;
9510
9511 content_origin
9512 + point(
9513 Pixels::from(-scroll_pixel_position.x),
9514 Pixels::from(
9515 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9516 ),
9517 )
9518 };
9519
9520 origin.x -= BORDER_WIDTH;
9521
9522 window.defer_draw(element, origin, 1);
9523
9524 // Do not return an element, since it will already be drawn due to defer_draw.
9525 None
9526 }
9527
9528 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9529 px(30.)
9530 }
9531
9532 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9533 if self.read_only(cx) {
9534 cx.theme().players().read_only()
9535 } else {
9536 self.style.as_ref().unwrap().local_player
9537 }
9538 }
9539
9540 fn render_edit_prediction_accept_keybind(
9541 &self,
9542 window: &mut Window,
9543 cx: &mut App,
9544 ) -> Option<AnyElement> {
9545 let accept_binding =
9546 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9547 let accept_keystroke = accept_binding.keystroke()?;
9548
9549 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9550
9551 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9552 Color::Accent
9553 } else {
9554 Color::Muted
9555 };
9556
9557 h_flex()
9558 .px_0p5()
9559 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9560 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9561 .text_size(TextSize::XSmall.rems(cx))
9562 .child(h_flex().children(ui::render_modifiers(
9563 accept_keystroke.modifiers(),
9564 PlatformStyle::platform(),
9565 Some(modifiers_color),
9566 Some(IconSize::XSmall.rems().into()),
9567 true,
9568 )))
9569 .when(is_platform_style_mac, |parent| {
9570 parent.child(accept_keystroke.key().to_string())
9571 })
9572 .when(!is_platform_style_mac, |parent| {
9573 parent.child(
9574 Key::new(
9575 util::capitalize(accept_keystroke.key()),
9576 Some(Color::Default),
9577 )
9578 .size(Some(IconSize::XSmall.rems().into())),
9579 )
9580 })
9581 .into_any()
9582 .into()
9583 }
9584
9585 fn render_edit_prediction_line_popover(
9586 &self,
9587 label: impl Into<SharedString>,
9588 icon: Option<IconName>,
9589 window: &mut Window,
9590 cx: &mut App,
9591 ) -> Stateful<Div> {
9592 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9593
9594 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9595 let has_keybind = keybind.is_some();
9596
9597 h_flex()
9598 .id("ep-line-popover")
9599 .py_0p5()
9600 .pl_1()
9601 .pr(padding_right)
9602 .gap_1()
9603 .rounded_md()
9604 .border_1()
9605 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9606 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9607 .shadow_xs()
9608 .when(!has_keybind, |el| {
9609 let status_colors = cx.theme().status();
9610
9611 el.bg(status_colors.error_background)
9612 .border_color(status_colors.error.opacity(0.6))
9613 .pl_2()
9614 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9615 .cursor_default()
9616 .hoverable_tooltip(move |_window, cx| {
9617 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9618 })
9619 })
9620 .children(keybind)
9621 .child(
9622 Label::new(label)
9623 .size(LabelSize::Small)
9624 .when(!has_keybind, |el| {
9625 el.color(cx.theme().status().error.into()).strikethrough()
9626 }),
9627 )
9628 .when(!has_keybind, |el| {
9629 el.child(
9630 h_flex().ml_1().child(
9631 Icon::new(IconName::Info)
9632 .size(IconSize::Small)
9633 .color(cx.theme().status().error.into()),
9634 ),
9635 )
9636 })
9637 .when_some(icon, |element, icon| {
9638 element.child(
9639 div()
9640 .mt(px(1.5))
9641 .child(Icon::new(icon).size(IconSize::Small)),
9642 )
9643 })
9644 }
9645
9646 fn render_edit_prediction_jump_outside_popover(
9647 &self,
9648 snapshot: &BufferSnapshot,
9649 window: &mut Window,
9650 cx: &mut App,
9651 ) -> Stateful<Div> {
9652 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9653 let has_keybind = keybind.is_some();
9654
9655 let file_name = snapshot
9656 .file()
9657 .map(|file| SharedString::new(file.file_name(cx)))
9658 .unwrap_or(SharedString::new_static("untitled"));
9659
9660 h_flex()
9661 .id("ep-jump-outside-popover")
9662 .py_1()
9663 .px_2()
9664 .gap_1()
9665 .rounded_md()
9666 .border_1()
9667 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9668 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9669 .shadow_xs()
9670 .when(!has_keybind, |el| {
9671 let status_colors = cx.theme().status();
9672
9673 el.bg(status_colors.error_background)
9674 .border_color(status_colors.error.opacity(0.6))
9675 .pl_2()
9676 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9677 .cursor_default()
9678 .hoverable_tooltip(move |_window, cx| {
9679 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9680 })
9681 })
9682 .children(keybind)
9683 .child(
9684 Label::new(file_name)
9685 .size(LabelSize::Small)
9686 .buffer_font(cx)
9687 .when(!has_keybind, |el| {
9688 el.color(cx.theme().status().error.into()).strikethrough()
9689 }),
9690 )
9691 .when(!has_keybind, |el| {
9692 el.child(
9693 h_flex().ml_1().child(
9694 Icon::new(IconName::Info)
9695 .size(IconSize::Small)
9696 .color(cx.theme().status().error.into()),
9697 ),
9698 )
9699 })
9700 .child(
9701 div()
9702 .mt(px(1.5))
9703 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9704 )
9705 }
9706
9707 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9708 let accent_color = cx.theme().colors().text_accent;
9709 let editor_bg_color = cx.theme().colors().editor_background;
9710 editor_bg_color.blend(accent_color.opacity(0.1))
9711 }
9712
9713 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9714 let accent_color = cx.theme().colors().text_accent;
9715 let editor_bg_color = cx.theme().colors().editor_background;
9716 editor_bg_color.blend(accent_color.opacity(0.6))
9717 }
9718 fn get_prediction_provider_icon_name(
9719 provider: &Option<RegisteredEditPredictionDelegate>,
9720 ) -> IconName {
9721 match provider {
9722 Some(provider) => match provider.provider.name() {
9723 "copilot" => IconName::Copilot,
9724 "supermaven" => IconName::Supermaven,
9725 _ => IconName::ZedPredict,
9726 },
9727 None => IconName::ZedPredict,
9728 }
9729 }
9730
9731 fn render_edit_prediction_cursor_popover(
9732 &self,
9733 min_width: Pixels,
9734 max_width: Pixels,
9735 cursor_point: Point,
9736 style: &EditorStyle,
9737 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9738 _window: &Window,
9739 cx: &mut Context<Editor>,
9740 ) -> Option<AnyElement> {
9741 let provider = self.edit_prediction_provider.as_ref()?;
9742 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9743
9744 let is_refreshing = provider.provider.is_refreshing(cx);
9745
9746 fn pending_completion_container(icon: IconName) -> Div {
9747 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9748 }
9749
9750 let completion = match &self.active_edit_prediction {
9751 Some(prediction) => {
9752 if !self.has_visible_completions_menu() {
9753 const RADIUS: Pixels = px(6.);
9754 const BORDER_WIDTH: Pixels = px(1.);
9755
9756 return Some(
9757 h_flex()
9758 .elevation_2(cx)
9759 .border(BORDER_WIDTH)
9760 .border_color(cx.theme().colors().border)
9761 .when(accept_keystroke.is_none(), |el| {
9762 el.border_color(cx.theme().status().error)
9763 })
9764 .rounded(RADIUS)
9765 .rounded_tl(px(0.))
9766 .overflow_hidden()
9767 .child(div().px_1p5().child(match &prediction.completion {
9768 EditPrediction::MoveWithin { target, snapshot } => {
9769 use text::ToPoint as _;
9770 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9771 {
9772 Icon::new(IconName::ZedPredictDown)
9773 } else {
9774 Icon::new(IconName::ZedPredictUp)
9775 }
9776 }
9777 EditPrediction::MoveOutside { .. } => {
9778 // TODO [zeta2] custom icon for external jump?
9779 Icon::new(provider_icon)
9780 }
9781 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9782 }))
9783 .child(
9784 h_flex()
9785 .gap_1()
9786 .py_1()
9787 .px_2()
9788 .rounded_r(RADIUS - BORDER_WIDTH)
9789 .border_l_1()
9790 .border_color(cx.theme().colors().border)
9791 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9792 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9793 el.child(
9794 Label::new("Hold")
9795 .size(LabelSize::Small)
9796 .when(accept_keystroke.is_none(), |el| {
9797 el.strikethrough()
9798 })
9799 .line_height_style(LineHeightStyle::UiLabel),
9800 )
9801 })
9802 .id("edit_prediction_cursor_popover_keybind")
9803 .when(accept_keystroke.is_none(), |el| {
9804 let status_colors = cx.theme().status();
9805
9806 el.bg(status_colors.error_background)
9807 .border_color(status_colors.error.opacity(0.6))
9808 .child(Icon::new(IconName::Info).color(Color::Error))
9809 .cursor_default()
9810 .hoverable_tooltip(move |_window, cx| {
9811 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9812 .into()
9813 })
9814 })
9815 .when_some(
9816 accept_keystroke.as_ref(),
9817 |el, accept_keystroke| {
9818 el.child(h_flex().children(ui::render_modifiers(
9819 accept_keystroke.modifiers(),
9820 PlatformStyle::platform(),
9821 Some(Color::Default),
9822 Some(IconSize::XSmall.rems().into()),
9823 false,
9824 )))
9825 },
9826 ),
9827 )
9828 .into_any(),
9829 );
9830 }
9831
9832 self.render_edit_prediction_cursor_popover_preview(
9833 prediction,
9834 cursor_point,
9835 style,
9836 cx,
9837 )?
9838 }
9839
9840 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9841 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9842 stale_completion,
9843 cursor_point,
9844 style,
9845 cx,
9846 )?,
9847
9848 None => pending_completion_container(provider_icon)
9849 .child(Label::new("...").size(LabelSize::Small)),
9850 },
9851
9852 None => pending_completion_container(provider_icon)
9853 .child(Label::new("...").size(LabelSize::Small)),
9854 };
9855
9856 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9857 completion
9858 .with_animation(
9859 "loading-completion",
9860 Animation::new(Duration::from_secs(2))
9861 .repeat()
9862 .with_easing(pulsating_between(0.4, 0.8)),
9863 |label, delta| label.opacity(delta),
9864 )
9865 .into_any_element()
9866 } else {
9867 completion.into_any_element()
9868 };
9869
9870 let has_completion = self.active_edit_prediction.is_some();
9871
9872 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9873 Some(
9874 h_flex()
9875 .min_w(min_width)
9876 .max_w(max_width)
9877 .flex_1()
9878 .elevation_2(cx)
9879 .border_color(cx.theme().colors().border)
9880 .child(
9881 div()
9882 .flex_1()
9883 .py_1()
9884 .px_2()
9885 .overflow_hidden()
9886 .child(completion),
9887 )
9888 .when_some(accept_keystroke, |el, accept_keystroke| {
9889 if !accept_keystroke.modifiers().modified() {
9890 return el;
9891 }
9892
9893 el.child(
9894 h_flex()
9895 .h_full()
9896 .border_l_1()
9897 .rounded_r_lg()
9898 .border_color(cx.theme().colors().border)
9899 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9900 .gap_1()
9901 .py_1()
9902 .px_2()
9903 .child(
9904 h_flex()
9905 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9906 .when(is_platform_style_mac, |parent| parent.gap_1())
9907 .child(h_flex().children(ui::render_modifiers(
9908 accept_keystroke.modifiers(),
9909 PlatformStyle::platform(),
9910 Some(if !has_completion {
9911 Color::Muted
9912 } else {
9913 Color::Default
9914 }),
9915 None,
9916 false,
9917 ))),
9918 )
9919 .child(Label::new("Preview").into_any_element())
9920 .opacity(if has_completion { 1.0 } else { 0.4 }),
9921 )
9922 })
9923 .into_any(),
9924 )
9925 }
9926
9927 fn render_edit_prediction_cursor_popover_preview(
9928 &self,
9929 completion: &EditPredictionState,
9930 cursor_point: Point,
9931 style: &EditorStyle,
9932 cx: &mut Context<Editor>,
9933 ) -> Option<Div> {
9934 use text::ToPoint as _;
9935
9936 fn render_relative_row_jump(
9937 prefix: impl Into<String>,
9938 current_row: u32,
9939 target_row: u32,
9940 ) -> Div {
9941 let (row_diff, arrow) = if target_row < current_row {
9942 (current_row - target_row, IconName::ArrowUp)
9943 } else {
9944 (target_row - current_row, IconName::ArrowDown)
9945 };
9946
9947 h_flex()
9948 .child(
9949 Label::new(format!("{}{}", prefix.into(), row_diff))
9950 .color(Color::Muted)
9951 .size(LabelSize::Small),
9952 )
9953 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9954 }
9955
9956 let supports_jump = self
9957 .edit_prediction_provider
9958 .as_ref()
9959 .map(|provider| provider.provider.supports_jump_to_edit())
9960 .unwrap_or(true);
9961
9962 match &completion.completion {
9963 EditPrediction::MoveWithin {
9964 target, snapshot, ..
9965 } => {
9966 if !supports_jump {
9967 return None;
9968 }
9969
9970 Some(
9971 h_flex()
9972 .px_2()
9973 .gap_2()
9974 .flex_1()
9975 .child(
9976 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9977 Icon::new(IconName::ZedPredictDown)
9978 } else {
9979 Icon::new(IconName::ZedPredictUp)
9980 },
9981 )
9982 .child(Label::new("Jump to Edit")),
9983 )
9984 }
9985 EditPrediction::MoveOutside { snapshot, .. } => {
9986 let file_name = snapshot
9987 .file()
9988 .map(|file| file.file_name(cx))
9989 .unwrap_or("untitled");
9990 Some(
9991 h_flex()
9992 .px_2()
9993 .gap_2()
9994 .flex_1()
9995 .child(Icon::new(IconName::ZedPredict))
9996 .child(Label::new(format!("Jump to {file_name}"))),
9997 )
9998 }
9999 EditPrediction::Edit {
10000 edits,
10001 edit_preview,
10002 snapshot,
10003 display_mode: _,
10004 } => {
10005 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
10006
10007 let (highlighted_edits, has_more_lines) =
10008 if let Some(edit_preview) = edit_preview.as_ref() {
10009 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
10010 .first_line_preview()
10011 } else {
10012 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10013 };
10014
10015 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10016 .with_default_highlights(&style.text, highlighted_edits.highlights);
10017
10018 let preview = h_flex()
10019 .gap_1()
10020 .min_w_16()
10021 .child(styled_text)
10022 .when(has_more_lines, |parent| parent.child("…"));
10023
10024 let left = if supports_jump && first_edit_row != cursor_point.row {
10025 render_relative_row_jump("", cursor_point.row, first_edit_row)
10026 .into_any_element()
10027 } else {
10028 let icon_name =
10029 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
10030 Icon::new(icon_name).into_any_element()
10031 };
10032
10033 Some(
10034 h_flex()
10035 .h_full()
10036 .flex_1()
10037 .gap_2()
10038 .pr_1()
10039 .overflow_x_hidden()
10040 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10041 .child(left)
10042 .child(preview),
10043 )
10044 }
10045 }
10046 }
10047
10048 pub fn render_context_menu(
10049 &mut self,
10050 max_height_in_lines: u32,
10051 window: &mut Window,
10052 cx: &mut Context<Editor>,
10053 ) -> Option<AnyElement> {
10054 let menu = self.context_menu.borrow();
10055 let menu = menu.as_ref()?;
10056 if !menu.visible() {
10057 return None;
10058 };
10059 self.style
10060 .as_ref()
10061 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10062 }
10063
10064 fn render_context_menu_aside(
10065 &mut self,
10066 max_size: Size<Pixels>,
10067 window: &mut Window,
10068 cx: &mut Context<Editor>,
10069 ) -> Option<AnyElement> {
10070 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10071 if menu.visible() {
10072 menu.render_aside(max_size, window, cx)
10073 } else {
10074 None
10075 }
10076 })
10077 }
10078
10079 fn hide_context_menu(
10080 &mut self,
10081 window: &mut Window,
10082 cx: &mut Context<Self>,
10083 ) -> Option<CodeContextMenu> {
10084 cx.notify();
10085 self.completion_tasks.clear();
10086 let context_menu = self.context_menu.borrow_mut().take();
10087 self.stale_edit_prediction_in_menu.take();
10088 self.update_visible_edit_prediction(window, cx);
10089 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10090 && let Some(completion_provider) = &self.completion_provider
10091 {
10092 completion_provider.selection_changed(None, window, cx);
10093 }
10094 context_menu
10095 }
10096
10097 fn show_snippet_choices(
10098 &mut self,
10099 choices: &Vec<String>,
10100 selection: Range<Anchor>,
10101 cx: &mut Context<Self>,
10102 ) {
10103 let Some((_, buffer, _)) = self
10104 .buffer()
10105 .read(cx)
10106 .excerpt_containing(selection.start, cx)
10107 else {
10108 return;
10109 };
10110 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10111 else {
10112 return;
10113 };
10114 if buffer != end_buffer {
10115 log::error!("expected anchor range to have matching buffer IDs");
10116 return;
10117 }
10118
10119 let id = post_inc(&mut self.next_completion_id);
10120 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10121 let mut context_menu = self.context_menu.borrow_mut();
10122 let old_menu = context_menu.take();
10123 *context_menu = Some(CodeContextMenu::Completions(
10124 CompletionsMenu::new_snippet_choices(
10125 id,
10126 true,
10127 choices,
10128 selection,
10129 buffer,
10130 old_menu.map(|menu| menu.primary_scroll_handle()),
10131 snippet_sort_order,
10132 ),
10133 ));
10134 }
10135
10136 pub fn insert_snippet(
10137 &mut self,
10138 insertion_ranges: &[Range<MultiBufferOffset>],
10139 snippet: Snippet,
10140 window: &mut Window,
10141 cx: &mut Context<Self>,
10142 ) -> Result<()> {
10143 struct Tabstop<T> {
10144 is_end_tabstop: bool,
10145 ranges: Vec<Range<T>>,
10146 choices: Option<Vec<String>>,
10147 }
10148
10149 let tabstops = self.buffer.update(cx, |buffer, cx| {
10150 let snippet_text: Arc<str> = snippet.text.clone().into();
10151 let edits = insertion_ranges
10152 .iter()
10153 .cloned()
10154 .map(|range| (range, snippet_text.clone()));
10155 let autoindent_mode = AutoindentMode::Block {
10156 original_indent_columns: Vec::new(),
10157 };
10158 buffer.edit(edits, Some(autoindent_mode), cx);
10159
10160 let snapshot = &*buffer.read(cx);
10161 let snippet = &snippet;
10162 snippet
10163 .tabstops
10164 .iter()
10165 .map(|tabstop| {
10166 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10167 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10168 });
10169 let mut tabstop_ranges = tabstop
10170 .ranges
10171 .iter()
10172 .flat_map(|tabstop_range| {
10173 let mut delta = 0_isize;
10174 insertion_ranges.iter().map(move |insertion_range| {
10175 let insertion_start = insertion_range.start + delta;
10176 delta += snippet.text.len() as isize
10177 - (insertion_range.end - insertion_range.start) as isize;
10178
10179 let start =
10180 (insertion_start + tabstop_range.start).min(snapshot.len());
10181 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10182 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10183 })
10184 })
10185 .collect::<Vec<_>>();
10186 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10187
10188 Tabstop {
10189 is_end_tabstop,
10190 ranges: tabstop_ranges,
10191 choices: tabstop.choices.clone(),
10192 }
10193 })
10194 .collect::<Vec<_>>()
10195 });
10196 if let Some(tabstop) = tabstops.first() {
10197 self.change_selections(Default::default(), window, cx, |s| {
10198 // Reverse order so that the first range is the newest created selection.
10199 // Completions will use it and autoscroll will prioritize it.
10200 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10201 });
10202
10203 if let Some(choices) = &tabstop.choices
10204 && let Some(selection) = tabstop.ranges.first()
10205 {
10206 self.show_snippet_choices(choices, selection.clone(), cx)
10207 }
10208
10209 // If we're already at the last tabstop and it's at the end of the snippet,
10210 // we're done, we don't need to keep the state around.
10211 if !tabstop.is_end_tabstop {
10212 let choices = tabstops
10213 .iter()
10214 .map(|tabstop| tabstop.choices.clone())
10215 .collect();
10216
10217 let ranges = tabstops
10218 .into_iter()
10219 .map(|tabstop| tabstop.ranges)
10220 .collect::<Vec<_>>();
10221
10222 self.snippet_stack.push(SnippetState {
10223 active_index: 0,
10224 ranges,
10225 choices,
10226 });
10227 }
10228
10229 // Check whether the just-entered snippet ends with an auto-closable bracket.
10230 if self.autoclose_regions.is_empty() {
10231 let snapshot = self.buffer.read(cx).snapshot(cx);
10232 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10233 let selection_head = selection.head();
10234 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10235 continue;
10236 };
10237
10238 let mut bracket_pair = None;
10239 let max_lookup_length = scope
10240 .brackets()
10241 .map(|(pair, _)| {
10242 pair.start
10243 .as_str()
10244 .chars()
10245 .count()
10246 .max(pair.end.as_str().chars().count())
10247 })
10248 .max();
10249 if let Some(max_lookup_length) = max_lookup_length {
10250 let next_text = snapshot
10251 .chars_at(selection_head)
10252 .take(max_lookup_length)
10253 .collect::<String>();
10254 let prev_text = snapshot
10255 .reversed_chars_at(selection_head)
10256 .take(max_lookup_length)
10257 .collect::<String>();
10258
10259 for (pair, enabled) in scope.brackets() {
10260 if enabled
10261 && pair.close
10262 && prev_text.starts_with(pair.start.as_str())
10263 && next_text.starts_with(pair.end.as_str())
10264 {
10265 bracket_pair = Some(pair.clone());
10266 break;
10267 }
10268 }
10269 }
10270
10271 if let Some(pair) = bracket_pair {
10272 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10273 let autoclose_enabled =
10274 self.use_autoclose && snapshot_settings.use_autoclose;
10275 if autoclose_enabled {
10276 let start = snapshot.anchor_after(selection_head);
10277 let end = snapshot.anchor_after(selection_head);
10278 self.autoclose_regions.push(AutocloseRegion {
10279 selection_id: selection.id,
10280 range: start..end,
10281 pair,
10282 });
10283 }
10284 }
10285 }
10286 }
10287 }
10288 Ok(())
10289 }
10290
10291 pub fn move_to_next_snippet_tabstop(
10292 &mut self,
10293 window: &mut Window,
10294 cx: &mut Context<Self>,
10295 ) -> bool {
10296 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10297 }
10298
10299 pub fn move_to_prev_snippet_tabstop(
10300 &mut self,
10301 window: &mut Window,
10302 cx: &mut Context<Self>,
10303 ) -> bool {
10304 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10305 }
10306
10307 pub fn move_to_snippet_tabstop(
10308 &mut self,
10309 bias: Bias,
10310 window: &mut Window,
10311 cx: &mut Context<Self>,
10312 ) -> bool {
10313 if let Some(mut snippet) = self.snippet_stack.pop() {
10314 match bias {
10315 Bias::Left => {
10316 if snippet.active_index > 0 {
10317 snippet.active_index -= 1;
10318 } else {
10319 self.snippet_stack.push(snippet);
10320 return false;
10321 }
10322 }
10323 Bias::Right => {
10324 if snippet.active_index + 1 < snippet.ranges.len() {
10325 snippet.active_index += 1;
10326 } else {
10327 self.snippet_stack.push(snippet);
10328 return false;
10329 }
10330 }
10331 }
10332 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10333 self.change_selections(Default::default(), window, cx, |s| {
10334 // Reverse order so that the first range is the newest created selection.
10335 // Completions will use it and autoscroll will prioritize it.
10336 s.select_ranges(current_ranges.iter().rev().cloned())
10337 });
10338
10339 if let Some(choices) = &snippet.choices[snippet.active_index]
10340 && let Some(selection) = current_ranges.first()
10341 {
10342 self.show_snippet_choices(choices, selection.clone(), cx);
10343 }
10344
10345 // If snippet state is not at the last tabstop, push it back on the stack
10346 if snippet.active_index + 1 < snippet.ranges.len() {
10347 self.snippet_stack.push(snippet);
10348 }
10349 return true;
10350 }
10351 }
10352
10353 false
10354 }
10355
10356 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10357 self.transact(window, cx, |this, window, cx| {
10358 this.select_all(&SelectAll, window, cx);
10359 this.insert("", window, cx);
10360 });
10361 }
10362
10363 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10364 if self.read_only(cx) {
10365 return;
10366 }
10367 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10368 self.transact(window, cx, |this, window, cx| {
10369 this.select_autoclose_pair(window, cx);
10370
10371 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10372
10373 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10374 if !this.linked_edit_ranges.is_empty() {
10375 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10376 let snapshot = this.buffer.read(cx).snapshot(cx);
10377
10378 for selection in selections.iter() {
10379 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10380 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10381 if selection_start.buffer_id != selection_end.buffer_id {
10382 continue;
10383 }
10384 if let Some(ranges) =
10385 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10386 {
10387 for (buffer, entries) in ranges {
10388 linked_ranges.entry(buffer).or_default().extend(entries);
10389 }
10390 }
10391 }
10392 }
10393
10394 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10395 for selection in &mut selections {
10396 if selection.is_empty() {
10397 let old_head = selection.head();
10398 let mut new_head =
10399 movement::left(&display_map, old_head.to_display_point(&display_map))
10400 .to_point(&display_map);
10401 if let Some((buffer, line_buffer_range)) = display_map
10402 .buffer_snapshot()
10403 .buffer_line_for_row(MultiBufferRow(old_head.row))
10404 {
10405 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10406 let indent_len = match indent_size.kind {
10407 IndentKind::Space => {
10408 buffer.settings_at(line_buffer_range.start, cx).tab_size
10409 }
10410 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10411 };
10412 if old_head.column <= indent_size.len && old_head.column > 0 {
10413 let indent_len = indent_len.get();
10414 new_head = cmp::min(
10415 new_head,
10416 MultiBufferPoint::new(
10417 old_head.row,
10418 ((old_head.column - 1) / indent_len) * indent_len,
10419 ),
10420 );
10421 }
10422 }
10423
10424 selection.set_head(new_head, SelectionGoal::None);
10425 }
10426 }
10427
10428 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10429 this.insert("", window, cx);
10430 let empty_str: Arc<str> = Arc::from("");
10431 for (buffer, edits) in linked_ranges {
10432 let snapshot = buffer.read(cx).snapshot();
10433 use text::ToPoint as TP;
10434
10435 let edits = edits
10436 .into_iter()
10437 .map(|range| {
10438 let end_point = TP::to_point(&range.end, &snapshot);
10439 let mut start_point = TP::to_point(&range.start, &snapshot);
10440
10441 if end_point == start_point {
10442 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10443 .saturating_sub(1);
10444 start_point =
10445 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10446 };
10447
10448 (start_point..end_point, empty_str.clone())
10449 })
10450 .sorted_by_key(|(range, _)| range.start)
10451 .collect::<Vec<_>>();
10452 buffer.update(cx, |this, cx| {
10453 this.edit(edits, None, cx);
10454 })
10455 }
10456 this.refresh_edit_prediction(true, false, window, cx);
10457 refresh_linked_ranges(this, window, cx);
10458 });
10459 }
10460
10461 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10462 if self.read_only(cx) {
10463 return;
10464 }
10465 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10466 self.transact(window, cx, |this, window, cx| {
10467 this.change_selections(Default::default(), window, cx, |s| {
10468 s.move_with(|map, selection| {
10469 if selection.is_empty() {
10470 let cursor = movement::right(map, selection.head());
10471 selection.end = cursor;
10472 selection.reversed = true;
10473 selection.goal = SelectionGoal::None;
10474 }
10475 })
10476 });
10477 this.insert("", window, cx);
10478 this.refresh_edit_prediction(true, false, window, cx);
10479 });
10480 }
10481
10482 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10483 if self.mode.is_single_line() {
10484 cx.propagate();
10485 return;
10486 }
10487
10488 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10489 if self.move_to_prev_snippet_tabstop(window, cx) {
10490 return;
10491 }
10492 self.outdent(&Outdent, window, cx);
10493 }
10494
10495 pub fn next_snippet_tabstop(
10496 &mut self,
10497 _: &NextSnippetTabstop,
10498 window: &mut Window,
10499 cx: &mut Context<Self>,
10500 ) {
10501 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10502 cx.propagate();
10503 return;
10504 }
10505
10506 if self.move_to_next_snippet_tabstop(window, cx) {
10507 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10508 return;
10509 }
10510 cx.propagate();
10511 }
10512
10513 pub fn previous_snippet_tabstop(
10514 &mut self,
10515 _: &PreviousSnippetTabstop,
10516 window: &mut Window,
10517 cx: &mut Context<Self>,
10518 ) {
10519 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10520 cx.propagate();
10521 return;
10522 }
10523
10524 if self.move_to_prev_snippet_tabstop(window, cx) {
10525 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10526 return;
10527 }
10528 cx.propagate();
10529 }
10530
10531 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10532 if self.mode.is_single_line() {
10533 cx.propagate();
10534 return;
10535 }
10536
10537 if self.move_to_next_snippet_tabstop(window, cx) {
10538 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10539 return;
10540 }
10541 if self.read_only(cx) {
10542 return;
10543 }
10544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10545 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10546 let buffer = self.buffer.read(cx);
10547 let snapshot = buffer.snapshot(cx);
10548 let rows_iter = selections.iter().map(|s| s.head().row);
10549 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10550
10551 let has_some_cursor_in_whitespace = selections
10552 .iter()
10553 .filter(|selection| selection.is_empty())
10554 .any(|selection| {
10555 let cursor = selection.head();
10556 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10557 cursor.column < current_indent.len
10558 });
10559
10560 let mut edits = Vec::new();
10561 let mut prev_edited_row = 0;
10562 let mut row_delta = 0;
10563 for selection in &mut selections {
10564 if selection.start.row != prev_edited_row {
10565 row_delta = 0;
10566 }
10567 prev_edited_row = selection.end.row;
10568
10569 // If the selection is non-empty, then increase the indentation of the selected lines.
10570 if !selection.is_empty() {
10571 row_delta =
10572 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10573 continue;
10574 }
10575
10576 let cursor = selection.head();
10577 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10578 if let Some(suggested_indent) =
10579 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10580 {
10581 // Don't do anything if already at suggested indent
10582 // and there is any other cursor which is not
10583 if has_some_cursor_in_whitespace
10584 && cursor.column == current_indent.len
10585 && current_indent.len == suggested_indent.len
10586 {
10587 continue;
10588 }
10589
10590 // Adjust line and move cursor to suggested indent
10591 // if cursor is not at suggested indent
10592 if cursor.column < suggested_indent.len
10593 && cursor.column <= current_indent.len
10594 && current_indent.len <= suggested_indent.len
10595 {
10596 selection.start = Point::new(cursor.row, suggested_indent.len);
10597 selection.end = selection.start;
10598 if row_delta == 0 {
10599 edits.extend(Buffer::edit_for_indent_size_adjustment(
10600 cursor.row,
10601 current_indent,
10602 suggested_indent,
10603 ));
10604 row_delta = suggested_indent.len - current_indent.len;
10605 }
10606 continue;
10607 }
10608
10609 // If current indent is more than suggested indent
10610 // only move cursor to current indent and skip indent
10611 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10612 selection.start = Point::new(cursor.row, current_indent.len);
10613 selection.end = selection.start;
10614 continue;
10615 }
10616 }
10617
10618 // Otherwise, insert a hard or soft tab.
10619 let settings = buffer.language_settings_at(cursor, cx);
10620 let tab_size = if settings.hard_tabs {
10621 IndentSize::tab()
10622 } else {
10623 let tab_size = settings.tab_size.get();
10624 let indent_remainder = snapshot
10625 .text_for_range(Point::new(cursor.row, 0)..cursor)
10626 .flat_map(str::chars)
10627 .fold(row_delta % tab_size, |counter: u32, c| {
10628 if c == '\t' {
10629 0
10630 } else {
10631 (counter + 1) % tab_size
10632 }
10633 });
10634
10635 let chars_to_next_tab_stop = tab_size - indent_remainder;
10636 IndentSize::spaces(chars_to_next_tab_stop)
10637 };
10638 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10639 selection.end = selection.start;
10640 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10641 row_delta += tab_size.len;
10642 }
10643
10644 self.transact(window, cx, |this, window, cx| {
10645 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10646 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10647 this.refresh_edit_prediction(true, false, window, cx);
10648 });
10649 }
10650
10651 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10652 if self.read_only(cx) {
10653 return;
10654 }
10655 if self.mode.is_single_line() {
10656 cx.propagate();
10657 return;
10658 }
10659
10660 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10661 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10662 let mut prev_edited_row = 0;
10663 let mut row_delta = 0;
10664 let mut edits = Vec::new();
10665 let buffer = self.buffer.read(cx);
10666 let snapshot = buffer.snapshot(cx);
10667 for selection in &mut selections {
10668 if selection.start.row != prev_edited_row {
10669 row_delta = 0;
10670 }
10671 prev_edited_row = selection.end.row;
10672
10673 row_delta =
10674 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10675 }
10676
10677 self.transact(window, cx, |this, window, cx| {
10678 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10679 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10680 });
10681 }
10682
10683 fn indent_selection(
10684 buffer: &MultiBuffer,
10685 snapshot: &MultiBufferSnapshot,
10686 selection: &mut Selection<Point>,
10687 edits: &mut Vec<(Range<Point>, String)>,
10688 delta_for_start_row: u32,
10689 cx: &App,
10690 ) -> u32 {
10691 let settings = buffer.language_settings_at(selection.start, cx);
10692 let tab_size = settings.tab_size.get();
10693 let indent_kind = if settings.hard_tabs {
10694 IndentKind::Tab
10695 } else {
10696 IndentKind::Space
10697 };
10698 let mut start_row = selection.start.row;
10699 let mut end_row = selection.end.row + 1;
10700
10701 // If a selection ends at the beginning of a line, don't indent
10702 // that last line.
10703 if selection.end.column == 0 && selection.end.row > selection.start.row {
10704 end_row -= 1;
10705 }
10706
10707 // Avoid re-indenting a row that has already been indented by a
10708 // previous selection, but still update this selection's column
10709 // to reflect that indentation.
10710 if delta_for_start_row > 0 {
10711 start_row += 1;
10712 selection.start.column += delta_for_start_row;
10713 if selection.end.row == selection.start.row {
10714 selection.end.column += delta_for_start_row;
10715 }
10716 }
10717
10718 let mut delta_for_end_row = 0;
10719 let has_multiple_rows = start_row + 1 != end_row;
10720 for row in start_row..end_row {
10721 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10722 let indent_delta = match (current_indent.kind, indent_kind) {
10723 (IndentKind::Space, IndentKind::Space) => {
10724 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10725 IndentSize::spaces(columns_to_next_tab_stop)
10726 }
10727 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10728 (_, IndentKind::Tab) => IndentSize::tab(),
10729 };
10730
10731 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10732 0
10733 } else {
10734 selection.start.column
10735 };
10736 let row_start = Point::new(row, start);
10737 edits.push((
10738 row_start..row_start,
10739 indent_delta.chars().collect::<String>(),
10740 ));
10741
10742 // Update this selection's endpoints to reflect the indentation.
10743 if row == selection.start.row {
10744 selection.start.column += indent_delta.len;
10745 }
10746 if row == selection.end.row {
10747 selection.end.column += indent_delta.len;
10748 delta_for_end_row = indent_delta.len;
10749 }
10750 }
10751
10752 if selection.start.row == selection.end.row {
10753 delta_for_start_row + delta_for_end_row
10754 } else {
10755 delta_for_end_row
10756 }
10757 }
10758
10759 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10760 if self.read_only(cx) {
10761 return;
10762 }
10763 if self.mode.is_single_line() {
10764 cx.propagate();
10765 return;
10766 }
10767
10768 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10769 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10770 let selections = self.selections.all::<Point>(&display_map);
10771 let mut deletion_ranges = Vec::new();
10772 let mut last_outdent = None;
10773 {
10774 let buffer = self.buffer.read(cx);
10775 let snapshot = buffer.snapshot(cx);
10776 for selection in &selections {
10777 let settings = buffer.language_settings_at(selection.start, cx);
10778 let tab_size = settings.tab_size.get();
10779 let mut rows = selection.spanned_rows(false, &display_map);
10780
10781 // Avoid re-outdenting a row that has already been outdented by a
10782 // previous selection.
10783 if let Some(last_row) = last_outdent
10784 && last_row == rows.start
10785 {
10786 rows.start = rows.start.next_row();
10787 }
10788 let has_multiple_rows = rows.len() > 1;
10789 for row in rows.iter_rows() {
10790 let indent_size = snapshot.indent_size_for_line(row);
10791 if indent_size.len > 0 {
10792 let deletion_len = match indent_size.kind {
10793 IndentKind::Space => {
10794 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10795 if columns_to_prev_tab_stop == 0 {
10796 tab_size
10797 } else {
10798 columns_to_prev_tab_stop
10799 }
10800 }
10801 IndentKind::Tab => 1,
10802 };
10803 let start = if has_multiple_rows
10804 || deletion_len > selection.start.column
10805 || indent_size.len < selection.start.column
10806 {
10807 0
10808 } else {
10809 selection.start.column - deletion_len
10810 };
10811 deletion_ranges.push(
10812 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10813 );
10814 last_outdent = Some(row);
10815 }
10816 }
10817 }
10818 }
10819
10820 self.transact(window, cx, |this, window, cx| {
10821 this.buffer.update(cx, |buffer, cx| {
10822 let empty_str: Arc<str> = Arc::default();
10823 buffer.edit(
10824 deletion_ranges
10825 .into_iter()
10826 .map(|range| (range, empty_str.clone())),
10827 None,
10828 cx,
10829 );
10830 });
10831 let selections = this
10832 .selections
10833 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10834 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10835 });
10836 }
10837
10838 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10839 if self.read_only(cx) {
10840 return;
10841 }
10842 if self.mode.is_single_line() {
10843 cx.propagate();
10844 return;
10845 }
10846
10847 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10848 let selections = self
10849 .selections
10850 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10851 .into_iter()
10852 .map(|s| s.range());
10853
10854 self.transact(window, cx, |this, window, cx| {
10855 this.buffer.update(cx, |buffer, cx| {
10856 buffer.autoindent_ranges(selections, cx);
10857 });
10858 let selections = this
10859 .selections
10860 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10861 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10862 });
10863 }
10864
10865 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10866 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10867 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10868 let selections = self.selections.all::<Point>(&display_map);
10869
10870 let mut new_cursors = Vec::new();
10871 let mut edit_ranges = Vec::new();
10872 let mut selections = selections.iter().peekable();
10873 while let Some(selection) = selections.next() {
10874 let mut rows = selection.spanned_rows(false, &display_map);
10875
10876 // Accumulate contiguous regions of rows that we want to delete.
10877 while let Some(next_selection) = selections.peek() {
10878 let next_rows = next_selection.spanned_rows(false, &display_map);
10879 if next_rows.start <= rows.end {
10880 rows.end = next_rows.end;
10881 selections.next().unwrap();
10882 } else {
10883 break;
10884 }
10885 }
10886
10887 let buffer = display_map.buffer_snapshot();
10888 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10889 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10890 // If there's a line after the range, delete the \n from the end of the row range
10891 (
10892 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10893 rows.end,
10894 )
10895 } else {
10896 // If there isn't a line after the range, delete the \n from the line before the
10897 // start of the row range
10898 edit_start = edit_start.saturating_sub_usize(1);
10899 (buffer.len(), rows.start.previous_row())
10900 };
10901
10902 let text_layout_details = self.text_layout_details(window);
10903 let x = display_map.x_for_display_point(
10904 selection.head().to_display_point(&display_map),
10905 &text_layout_details,
10906 );
10907 let row = Point::new(target_row.0, 0)
10908 .to_display_point(&display_map)
10909 .row();
10910 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10911
10912 new_cursors.push((
10913 selection.id,
10914 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10915 SelectionGoal::None,
10916 ));
10917 edit_ranges.push(edit_start..edit_end);
10918 }
10919
10920 self.transact(window, cx, |this, window, cx| {
10921 let buffer = this.buffer.update(cx, |buffer, cx| {
10922 let empty_str: Arc<str> = Arc::default();
10923 buffer.edit(
10924 edit_ranges
10925 .into_iter()
10926 .map(|range| (range, empty_str.clone())),
10927 None,
10928 cx,
10929 );
10930 buffer.snapshot(cx)
10931 });
10932 let new_selections = new_cursors
10933 .into_iter()
10934 .map(|(id, cursor, goal)| {
10935 let cursor = cursor.to_point(&buffer);
10936 Selection {
10937 id,
10938 start: cursor,
10939 end: cursor,
10940 reversed: false,
10941 goal,
10942 }
10943 })
10944 .collect();
10945
10946 this.change_selections(Default::default(), window, cx, |s| {
10947 s.select(new_selections);
10948 });
10949 });
10950 }
10951
10952 pub fn join_lines_impl(
10953 &mut self,
10954 insert_whitespace: bool,
10955 window: &mut Window,
10956 cx: &mut Context<Self>,
10957 ) {
10958 if self.read_only(cx) {
10959 return;
10960 }
10961 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10962 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10963 let start = MultiBufferRow(selection.start.row);
10964 // Treat single line selections as if they include the next line. Otherwise this action
10965 // would do nothing for single line selections individual cursors.
10966 let end = if selection.start.row == selection.end.row {
10967 MultiBufferRow(selection.start.row + 1)
10968 } else {
10969 MultiBufferRow(selection.end.row)
10970 };
10971
10972 if let Some(last_row_range) = row_ranges.last_mut()
10973 && start <= last_row_range.end
10974 {
10975 last_row_range.end = end;
10976 continue;
10977 }
10978 row_ranges.push(start..end);
10979 }
10980
10981 let snapshot = self.buffer.read(cx).snapshot(cx);
10982 let mut cursor_positions = Vec::new();
10983 for row_range in &row_ranges {
10984 let anchor = snapshot.anchor_before(Point::new(
10985 row_range.end.previous_row().0,
10986 snapshot.line_len(row_range.end.previous_row()),
10987 ));
10988 cursor_positions.push(anchor..anchor);
10989 }
10990
10991 self.transact(window, cx, |this, window, cx| {
10992 for row_range in row_ranges.into_iter().rev() {
10993 for row in row_range.iter_rows().rev() {
10994 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10995 let next_line_row = row.next_row();
10996 let indent = snapshot.indent_size_for_line(next_line_row);
10997 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10998
10999 let replace =
11000 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
11001 " "
11002 } else {
11003 ""
11004 };
11005
11006 this.buffer.update(cx, |buffer, cx| {
11007 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
11008 });
11009 }
11010 }
11011
11012 this.change_selections(Default::default(), window, cx, |s| {
11013 s.select_anchor_ranges(cursor_positions)
11014 });
11015 });
11016 }
11017
11018 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11020 self.join_lines_impl(true, window, cx);
11021 }
11022
11023 pub fn sort_lines_case_sensitive(
11024 &mut self,
11025 _: &SortLinesCaseSensitive,
11026 window: &mut Window,
11027 cx: &mut Context<Self>,
11028 ) {
11029 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11030 }
11031
11032 pub fn sort_lines_by_length(
11033 &mut self,
11034 _: &SortLinesByLength,
11035 window: &mut Window,
11036 cx: &mut Context<Self>,
11037 ) {
11038 self.manipulate_immutable_lines(window, cx, |lines| {
11039 lines.sort_by_key(|&line| line.chars().count())
11040 })
11041 }
11042
11043 pub fn sort_lines_case_insensitive(
11044 &mut self,
11045 _: &SortLinesCaseInsensitive,
11046 window: &mut Window,
11047 cx: &mut Context<Self>,
11048 ) {
11049 self.manipulate_immutable_lines(window, cx, |lines| {
11050 lines.sort_by_key(|line| line.to_lowercase())
11051 })
11052 }
11053
11054 pub fn unique_lines_case_insensitive(
11055 &mut self,
11056 _: &UniqueLinesCaseInsensitive,
11057 window: &mut Window,
11058 cx: &mut Context<Self>,
11059 ) {
11060 self.manipulate_immutable_lines(window, cx, |lines| {
11061 let mut seen = HashSet::default();
11062 lines.retain(|line| seen.insert(line.to_lowercase()));
11063 })
11064 }
11065
11066 pub fn unique_lines_case_sensitive(
11067 &mut self,
11068 _: &UniqueLinesCaseSensitive,
11069 window: &mut Window,
11070 cx: &mut Context<Self>,
11071 ) {
11072 self.manipulate_immutable_lines(window, cx, |lines| {
11073 let mut seen = HashSet::default();
11074 lines.retain(|line| seen.insert(*line));
11075 })
11076 }
11077
11078 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11079 let snapshot = self.buffer.read(cx).snapshot(cx);
11080 for selection in self.selections.disjoint_anchors_arc().iter() {
11081 if snapshot
11082 .language_at(selection.start)
11083 .and_then(|lang| lang.config().wrap_characters.as_ref())
11084 .is_some()
11085 {
11086 return true;
11087 }
11088 }
11089 false
11090 }
11091
11092 fn wrap_selections_in_tag(
11093 &mut self,
11094 _: &WrapSelectionsInTag,
11095 window: &mut Window,
11096 cx: &mut Context<Self>,
11097 ) {
11098 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11099
11100 let snapshot = self.buffer.read(cx).snapshot(cx);
11101
11102 let mut edits = Vec::new();
11103 let mut boundaries = Vec::new();
11104
11105 for selection in self
11106 .selections
11107 .all_adjusted(&self.display_snapshot(cx))
11108 .iter()
11109 {
11110 let Some(wrap_config) = snapshot
11111 .language_at(selection.start)
11112 .and_then(|lang| lang.config().wrap_characters.clone())
11113 else {
11114 continue;
11115 };
11116
11117 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11118 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11119
11120 let start_before = snapshot.anchor_before(selection.start);
11121 let end_after = snapshot.anchor_after(selection.end);
11122
11123 edits.push((start_before..start_before, open_tag));
11124 edits.push((end_after..end_after, close_tag));
11125
11126 boundaries.push((
11127 start_before,
11128 end_after,
11129 wrap_config.start_prefix.len(),
11130 wrap_config.end_suffix.len(),
11131 ));
11132 }
11133
11134 if edits.is_empty() {
11135 return;
11136 }
11137
11138 self.transact(window, cx, |this, window, cx| {
11139 let buffer = this.buffer.update(cx, |buffer, cx| {
11140 buffer.edit(edits, None, cx);
11141 buffer.snapshot(cx)
11142 });
11143
11144 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11145 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11146 boundaries.into_iter()
11147 {
11148 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11149 let close_offset = end_after
11150 .to_offset(&buffer)
11151 .saturating_sub_usize(end_suffix_len);
11152 new_selections.push(open_offset..open_offset);
11153 new_selections.push(close_offset..close_offset);
11154 }
11155
11156 this.change_selections(Default::default(), window, cx, |s| {
11157 s.select_ranges(new_selections);
11158 });
11159
11160 this.request_autoscroll(Autoscroll::fit(), cx);
11161 });
11162 }
11163
11164 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11165 let Some(project) = self.project.clone() else {
11166 return;
11167 };
11168 self.reload(project, window, cx)
11169 .detach_and_notify_err(window, cx);
11170 }
11171
11172 pub fn restore_file(
11173 &mut self,
11174 _: &::git::RestoreFile,
11175 window: &mut Window,
11176 cx: &mut Context<Self>,
11177 ) {
11178 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11179 let mut buffer_ids = HashSet::default();
11180 let snapshot = self.buffer().read(cx).snapshot(cx);
11181 for selection in self
11182 .selections
11183 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11184 {
11185 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11186 }
11187
11188 let buffer = self.buffer().read(cx);
11189 let ranges = buffer_ids
11190 .into_iter()
11191 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11192 .collect::<Vec<_>>();
11193
11194 self.restore_hunks_in_ranges(ranges, window, cx);
11195 }
11196
11197 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11198 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11199 let selections = self
11200 .selections
11201 .all(&self.display_snapshot(cx))
11202 .into_iter()
11203 .map(|s| s.range())
11204 .collect();
11205 self.restore_hunks_in_ranges(selections, window, cx);
11206 }
11207
11208 pub fn restore_hunks_in_ranges(
11209 &mut self,
11210 ranges: Vec<Range<Point>>,
11211 window: &mut Window,
11212 cx: &mut Context<Editor>,
11213 ) {
11214 let mut revert_changes = HashMap::default();
11215 let chunk_by = self
11216 .snapshot(window, cx)
11217 .hunks_for_ranges(ranges)
11218 .into_iter()
11219 .chunk_by(|hunk| hunk.buffer_id);
11220 for (buffer_id, hunks) in &chunk_by {
11221 let hunks = hunks.collect::<Vec<_>>();
11222 for hunk in &hunks {
11223 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11224 }
11225 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11226 }
11227 drop(chunk_by);
11228 if !revert_changes.is_empty() {
11229 self.transact(window, cx, |editor, window, cx| {
11230 editor.restore(revert_changes, window, cx);
11231 });
11232 }
11233 }
11234
11235 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11236 if let Some(status) = self
11237 .addons
11238 .iter()
11239 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11240 {
11241 return Some(status);
11242 }
11243 self.project
11244 .as_ref()?
11245 .read(cx)
11246 .status_for_buffer_id(buffer_id, cx)
11247 }
11248
11249 pub fn open_active_item_in_terminal(
11250 &mut self,
11251 _: &OpenInTerminal,
11252 window: &mut Window,
11253 cx: &mut Context<Self>,
11254 ) {
11255 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11256 let project_path = buffer.read(cx).project_path(cx)?;
11257 let project = self.project()?.read(cx);
11258 let entry = project.entry_for_path(&project_path, cx)?;
11259 let parent = match &entry.canonical_path {
11260 Some(canonical_path) => canonical_path.to_path_buf(),
11261 None => project.absolute_path(&project_path, cx)?,
11262 }
11263 .parent()?
11264 .to_path_buf();
11265 Some(parent)
11266 }) {
11267 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11268 }
11269 }
11270
11271 fn set_breakpoint_context_menu(
11272 &mut self,
11273 display_row: DisplayRow,
11274 position: Option<Anchor>,
11275 clicked_point: gpui::Point<Pixels>,
11276 window: &mut Window,
11277 cx: &mut Context<Self>,
11278 ) {
11279 let source = self
11280 .buffer
11281 .read(cx)
11282 .snapshot(cx)
11283 .anchor_before(Point::new(display_row.0, 0u32));
11284
11285 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11286
11287 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11288 self,
11289 source,
11290 clicked_point,
11291 context_menu,
11292 window,
11293 cx,
11294 );
11295 }
11296
11297 fn add_edit_breakpoint_block(
11298 &mut self,
11299 anchor: Anchor,
11300 breakpoint: &Breakpoint,
11301 edit_action: BreakpointPromptEditAction,
11302 window: &mut Window,
11303 cx: &mut Context<Self>,
11304 ) {
11305 let weak_editor = cx.weak_entity();
11306 let bp_prompt = cx.new(|cx| {
11307 BreakpointPromptEditor::new(
11308 weak_editor,
11309 anchor,
11310 breakpoint.clone(),
11311 edit_action,
11312 window,
11313 cx,
11314 )
11315 });
11316
11317 let height = bp_prompt.update(cx, |this, cx| {
11318 this.prompt
11319 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11320 });
11321 let cloned_prompt = bp_prompt.clone();
11322 let blocks = vec![BlockProperties {
11323 style: BlockStyle::Sticky,
11324 placement: BlockPlacement::Above(anchor),
11325 height: Some(height),
11326 render: Arc::new(move |cx| {
11327 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11328 cloned_prompt.clone().into_any_element()
11329 }),
11330 priority: 0,
11331 }];
11332
11333 let focus_handle = bp_prompt.focus_handle(cx);
11334 window.focus(&focus_handle);
11335
11336 let block_ids = self.insert_blocks(blocks, None, cx);
11337 bp_prompt.update(cx, |prompt, _| {
11338 prompt.add_block_ids(block_ids);
11339 });
11340 }
11341
11342 pub(crate) fn breakpoint_at_row(
11343 &self,
11344 row: u32,
11345 window: &mut Window,
11346 cx: &mut Context<Self>,
11347 ) -> Option<(Anchor, Breakpoint)> {
11348 let snapshot = self.snapshot(window, cx);
11349 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11350
11351 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11352 }
11353
11354 pub(crate) fn breakpoint_at_anchor(
11355 &self,
11356 breakpoint_position: Anchor,
11357 snapshot: &EditorSnapshot,
11358 cx: &mut Context<Self>,
11359 ) -> Option<(Anchor, Breakpoint)> {
11360 let buffer = self
11361 .buffer
11362 .read(cx)
11363 .buffer_for_anchor(breakpoint_position, cx)?;
11364
11365 let enclosing_excerpt = breakpoint_position.excerpt_id;
11366 let buffer_snapshot = buffer.read(cx).snapshot();
11367
11368 let row = buffer_snapshot
11369 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11370 .row;
11371
11372 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11373 let anchor_end = snapshot
11374 .buffer_snapshot()
11375 .anchor_after(Point::new(row, line_len));
11376
11377 self.breakpoint_store
11378 .as_ref()?
11379 .read_with(cx, |breakpoint_store, cx| {
11380 breakpoint_store
11381 .breakpoints(
11382 &buffer,
11383 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11384 &buffer_snapshot,
11385 cx,
11386 )
11387 .next()
11388 .and_then(|(bp, _)| {
11389 let breakpoint_row = buffer_snapshot
11390 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11391 .row;
11392
11393 if breakpoint_row == row {
11394 snapshot
11395 .buffer_snapshot()
11396 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11397 .map(|position| (position, bp.bp.clone()))
11398 } else {
11399 None
11400 }
11401 })
11402 })
11403 }
11404
11405 pub fn edit_log_breakpoint(
11406 &mut self,
11407 _: &EditLogBreakpoint,
11408 window: &mut Window,
11409 cx: &mut Context<Self>,
11410 ) {
11411 if self.breakpoint_store.is_none() {
11412 return;
11413 }
11414
11415 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11416 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11417 message: None,
11418 state: BreakpointState::Enabled,
11419 condition: None,
11420 hit_condition: None,
11421 });
11422
11423 self.add_edit_breakpoint_block(
11424 anchor,
11425 &breakpoint,
11426 BreakpointPromptEditAction::Log,
11427 window,
11428 cx,
11429 );
11430 }
11431 }
11432
11433 fn breakpoints_at_cursors(
11434 &self,
11435 window: &mut Window,
11436 cx: &mut Context<Self>,
11437 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11438 let snapshot = self.snapshot(window, cx);
11439 let cursors = self
11440 .selections
11441 .disjoint_anchors_arc()
11442 .iter()
11443 .map(|selection| {
11444 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11445
11446 let breakpoint_position = self
11447 .breakpoint_at_row(cursor_position.row, window, cx)
11448 .map(|bp| bp.0)
11449 .unwrap_or_else(|| {
11450 snapshot
11451 .display_snapshot
11452 .buffer_snapshot()
11453 .anchor_after(Point::new(cursor_position.row, 0))
11454 });
11455
11456 let breakpoint = self
11457 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11458 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11459
11460 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11461 })
11462 // 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.
11463 .collect::<HashMap<Anchor, _>>();
11464
11465 cursors.into_iter().collect()
11466 }
11467
11468 pub fn enable_breakpoint(
11469 &mut self,
11470 _: &crate::actions::EnableBreakpoint,
11471 window: &mut Window,
11472 cx: &mut Context<Self>,
11473 ) {
11474 if self.breakpoint_store.is_none() {
11475 return;
11476 }
11477
11478 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11479 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11480 continue;
11481 };
11482 self.edit_breakpoint_at_anchor(
11483 anchor,
11484 breakpoint,
11485 BreakpointEditAction::InvertState,
11486 cx,
11487 );
11488 }
11489 }
11490
11491 pub fn disable_breakpoint(
11492 &mut self,
11493 _: &crate::actions::DisableBreakpoint,
11494 window: &mut Window,
11495 cx: &mut Context<Self>,
11496 ) {
11497 if self.breakpoint_store.is_none() {
11498 return;
11499 }
11500
11501 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11502 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11503 continue;
11504 };
11505 self.edit_breakpoint_at_anchor(
11506 anchor,
11507 breakpoint,
11508 BreakpointEditAction::InvertState,
11509 cx,
11510 );
11511 }
11512 }
11513
11514 pub fn toggle_breakpoint(
11515 &mut self,
11516 _: &crate::actions::ToggleBreakpoint,
11517 window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) {
11520 if self.breakpoint_store.is_none() {
11521 return;
11522 }
11523
11524 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11525 if let Some(breakpoint) = breakpoint {
11526 self.edit_breakpoint_at_anchor(
11527 anchor,
11528 breakpoint,
11529 BreakpointEditAction::Toggle,
11530 cx,
11531 );
11532 } else {
11533 self.edit_breakpoint_at_anchor(
11534 anchor,
11535 Breakpoint::new_standard(),
11536 BreakpointEditAction::Toggle,
11537 cx,
11538 );
11539 }
11540 }
11541 }
11542
11543 pub fn edit_breakpoint_at_anchor(
11544 &mut self,
11545 breakpoint_position: Anchor,
11546 breakpoint: Breakpoint,
11547 edit_action: BreakpointEditAction,
11548 cx: &mut Context<Self>,
11549 ) {
11550 let Some(breakpoint_store) = &self.breakpoint_store else {
11551 return;
11552 };
11553
11554 let Some(buffer) = self
11555 .buffer
11556 .read(cx)
11557 .buffer_for_anchor(breakpoint_position, cx)
11558 else {
11559 return;
11560 };
11561
11562 breakpoint_store.update(cx, |breakpoint_store, cx| {
11563 breakpoint_store.toggle_breakpoint(
11564 buffer,
11565 BreakpointWithPosition {
11566 position: breakpoint_position.text_anchor,
11567 bp: breakpoint,
11568 },
11569 edit_action,
11570 cx,
11571 );
11572 });
11573
11574 cx.notify();
11575 }
11576
11577 #[cfg(any(test, feature = "test-support"))]
11578 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11579 self.breakpoint_store.clone()
11580 }
11581
11582 pub fn prepare_restore_change(
11583 &self,
11584 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11585 hunk: &MultiBufferDiffHunk,
11586 cx: &mut App,
11587 ) -> Option<()> {
11588 if hunk.is_created_file() {
11589 return None;
11590 }
11591 let buffer = self.buffer.read(cx);
11592 let diff = buffer.diff_for(hunk.buffer_id)?;
11593 let buffer = buffer.buffer(hunk.buffer_id)?;
11594 let buffer = buffer.read(cx);
11595 let original_text = diff
11596 .read(cx)
11597 .base_text()
11598 .as_rope()
11599 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11600 let buffer_snapshot = buffer.snapshot();
11601 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11602 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11603 probe
11604 .0
11605 .start
11606 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11607 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11608 }) {
11609 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11610 Some(())
11611 } else {
11612 None
11613 }
11614 }
11615
11616 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11617 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11618 }
11619
11620 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11621 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11622 }
11623
11624 pub fn rotate_selections_forward(
11625 &mut self,
11626 _: &RotateSelectionsForward,
11627 window: &mut Window,
11628 cx: &mut Context<Self>,
11629 ) {
11630 self.rotate_selections(window, cx, false)
11631 }
11632
11633 pub fn rotate_selections_backward(
11634 &mut self,
11635 _: &RotateSelectionsBackward,
11636 window: &mut Window,
11637 cx: &mut Context<Self>,
11638 ) {
11639 self.rotate_selections(window, cx, true)
11640 }
11641
11642 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11643 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11644 let display_snapshot = self.display_snapshot(cx);
11645 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11646
11647 if selections.len() < 2 {
11648 return;
11649 }
11650
11651 let (edits, new_selections) = {
11652 let buffer = self.buffer.read(cx).read(cx);
11653 let has_selections = selections.iter().any(|s| !s.is_empty());
11654 if has_selections {
11655 let mut selected_texts: Vec<String> = selections
11656 .iter()
11657 .map(|selection| {
11658 buffer
11659 .text_for_range(selection.start..selection.end)
11660 .collect()
11661 })
11662 .collect();
11663
11664 if reverse {
11665 selected_texts.rotate_left(1);
11666 } else {
11667 selected_texts.rotate_right(1);
11668 }
11669
11670 let mut offset_delta: i64 = 0;
11671 let mut new_selections = Vec::new();
11672 let edits: Vec<_> = selections
11673 .iter()
11674 .zip(selected_texts.iter())
11675 .map(|(selection, new_text)| {
11676 let old_len = (selection.end.0 - selection.start.0) as i64;
11677 let new_len = new_text.len() as i64;
11678 let adjusted_start =
11679 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11680 let adjusted_end =
11681 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11682
11683 new_selections.push(Selection {
11684 id: selection.id,
11685 start: adjusted_start,
11686 end: adjusted_end,
11687 reversed: selection.reversed,
11688 goal: selection.goal,
11689 });
11690
11691 offset_delta += new_len - old_len;
11692 (selection.start..selection.end, new_text.clone())
11693 })
11694 .collect();
11695 (edits, new_selections)
11696 } else {
11697 let mut all_rows: Vec<u32> = selections
11698 .iter()
11699 .map(|selection| buffer.offset_to_point(selection.start).row)
11700 .collect();
11701 all_rows.sort_unstable();
11702 all_rows.dedup();
11703
11704 if all_rows.len() < 2 {
11705 return;
11706 }
11707
11708 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11709 .iter()
11710 .map(|&row| {
11711 let start = Point::new(row, 0);
11712 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11713 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11714 })
11715 .collect();
11716
11717 let mut line_texts: Vec<String> = line_ranges
11718 .iter()
11719 .map(|range| buffer.text_for_range(range.clone()).collect())
11720 .collect();
11721
11722 if reverse {
11723 line_texts.rotate_left(1);
11724 } else {
11725 line_texts.rotate_right(1);
11726 }
11727
11728 let edits = line_ranges
11729 .iter()
11730 .zip(line_texts.iter())
11731 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11732 .collect();
11733
11734 let num_rows = all_rows.len();
11735 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11736 .iter()
11737 .enumerate()
11738 .map(|(i, &row)| (row, i))
11739 .collect();
11740
11741 // Compute new line start offsets after rotation (handles CRLF)
11742 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11743 let first_line_start = line_ranges[0].start.0;
11744 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11745 for text in line_texts.iter().take(num_rows - 1) {
11746 let prev_start = *new_line_starts.last().unwrap();
11747 new_line_starts.push(prev_start + text.len() + newline_len);
11748 }
11749
11750 let new_selections = selections
11751 .iter()
11752 .map(|selection| {
11753 let point = buffer.offset_to_point(selection.start);
11754 let old_index = row_to_index[&point.row];
11755 let new_index = if reverse {
11756 (old_index + num_rows - 1) % num_rows
11757 } else {
11758 (old_index + 1) % num_rows
11759 };
11760 let new_offset =
11761 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11762 Selection {
11763 id: selection.id,
11764 start: new_offset,
11765 end: new_offset,
11766 reversed: selection.reversed,
11767 goal: selection.goal,
11768 }
11769 })
11770 .collect();
11771
11772 (edits, new_selections)
11773 }
11774 };
11775
11776 self.transact(window, cx, |this, window, cx| {
11777 this.buffer.update(cx, |buffer, cx| {
11778 buffer.edit(edits, None, cx);
11779 });
11780 this.change_selections(Default::default(), window, cx, |s| {
11781 s.select(new_selections);
11782 });
11783 });
11784 }
11785
11786 fn manipulate_lines<M>(
11787 &mut self,
11788 window: &mut Window,
11789 cx: &mut Context<Self>,
11790 mut manipulate: M,
11791 ) where
11792 M: FnMut(&str) -> LineManipulationResult,
11793 {
11794 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11795
11796 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11797 let buffer = self.buffer.read(cx).snapshot(cx);
11798
11799 let mut edits = Vec::new();
11800
11801 let selections = self.selections.all::<Point>(&display_map);
11802 let mut selections = selections.iter().peekable();
11803 let mut contiguous_row_selections = Vec::new();
11804 let mut new_selections = Vec::new();
11805 let mut added_lines = 0;
11806 let mut removed_lines = 0;
11807
11808 while let Some(selection) = selections.next() {
11809 let (start_row, end_row) = consume_contiguous_rows(
11810 &mut contiguous_row_selections,
11811 selection,
11812 &display_map,
11813 &mut selections,
11814 );
11815
11816 let start_point = Point::new(start_row.0, 0);
11817 let end_point = Point::new(
11818 end_row.previous_row().0,
11819 buffer.line_len(end_row.previous_row()),
11820 );
11821 let text = buffer
11822 .text_for_range(start_point..end_point)
11823 .collect::<String>();
11824
11825 let LineManipulationResult {
11826 new_text,
11827 line_count_before,
11828 line_count_after,
11829 } = manipulate(&text);
11830
11831 edits.push((start_point..end_point, new_text));
11832
11833 // Selections must change based on added and removed line count
11834 let start_row =
11835 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11836 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11837 new_selections.push(Selection {
11838 id: selection.id,
11839 start: start_row,
11840 end: end_row,
11841 goal: SelectionGoal::None,
11842 reversed: selection.reversed,
11843 });
11844
11845 if line_count_after > line_count_before {
11846 added_lines += line_count_after - line_count_before;
11847 } else if line_count_before > line_count_after {
11848 removed_lines += line_count_before - line_count_after;
11849 }
11850 }
11851
11852 self.transact(window, cx, |this, window, cx| {
11853 let buffer = this.buffer.update(cx, |buffer, cx| {
11854 buffer.edit(edits, None, cx);
11855 buffer.snapshot(cx)
11856 });
11857
11858 // Recalculate offsets on newly edited buffer
11859 let new_selections = new_selections
11860 .iter()
11861 .map(|s| {
11862 let start_point = Point::new(s.start.0, 0);
11863 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11864 Selection {
11865 id: s.id,
11866 start: buffer.point_to_offset(start_point),
11867 end: buffer.point_to_offset(end_point),
11868 goal: s.goal,
11869 reversed: s.reversed,
11870 }
11871 })
11872 .collect();
11873
11874 this.change_selections(Default::default(), window, cx, |s| {
11875 s.select(new_selections);
11876 });
11877
11878 this.request_autoscroll(Autoscroll::fit(), cx);
11879 });
11880 }
11881
11882 fn manipulate_immutable_lines<Fn>(
11883 &mut self,
11884 window: &mut Window,
11885 cx: &mut Context<Self>,
11886 mut callback: Fn,
11887 ) where
11888 Fn: FnMut(&mut Vec<&str>),
11889 {
11890 self.manipulate_lines(window, cx, |text| {
11891 let mut lines: Vec<&str> = text.split('\n').collect();
11892 let line_count_before = lines.len();
11893
11894 callback(&mut lines);
11895
11896 LineManipulationResult {
11897 new_text: lines.join("\n"),
11898 line_count_before,
11899 line_count_after: lines.len(),
11900 }
11901 });
11902 }
11903
11904 fn manipulate_mutable_lines<Fn>(
11905 &mut self,
11906 window: &mut Window,
11907 cx: &mut Context<Self>,
11908 mut callback: Fn,
11909 ) where
11910 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11911 {
11912 self.manipulate_lines(window, cx, |text| {
11913 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11914 let line_count_before = lines.len();
11915
11916 callback(&mut lines);
11917
11918 LineManipulationResult {
11919 new_text: lines.join("\n"),
11920 line_count_before,
11921 line_count_after: lines.len(),
11922 }
11923 });
11924 }
11925
11926 pub fn convert_indentation_to_spaces(
11927 &mut self,
11928 _: &ConvertIndentationToSpaces,
11929 window: &mut Window,
11930 cx: &mut Context<Self>,
11931 ) {
11932 let settings = self.buffer.read(cx).language_settings(cx);
11933 let tab_size = settings.tab_size.get() as usize;
11934
11935 self.manipulate_mutable_lines(window, cx, |lines| {
11936 // Allocates a reasonably sized scratch buffer once for the whole loop
11937 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11938 // Avoids recomputing spaces that could be inserted many times
11939 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11940 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11941 .collect();
11942
11943 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11944 let mut chars = line.as_ref().chars();
11945 let mut col = 0;
11946 let mut changed = false;
11947
11948 for ch in chars.by_ref() {
11949 match ch {
11950 ' ' => {
11951 reindented_line.push(' ');
11952 col += 1;
11953 }
11954 '\t' => {
11955 // \t are converted to spaces depending on the current column
11956 let spaces_len = tab_size - (col % tab_size);
11957 reindented_line.extend(&space_cache[spaces_len - 1]);
11958 col += spaces_len;
11959 changed = true;
11960 }
11961 _ => {
11962 // If we dont append before break, the character is consumed
11963 reindented_line.push(ch);
11964 break;
11965 }
11966 }
11967 }
11968
11969 if !changed {
11970 reindented_line.clear();
11971 continue;
11972 }
11973 // Append the rest of the line and replace old reference with new one
11974 reindented_line.extend(chars);
11975 *line = Cow::Owned(reindented_line.clone());
11976 reindented_line.clear();
11977 }
11978 });
11979 }
11980
11981 pub fn convert_indentation_to_tabs(
11982 &mut self,
11983 _: &ConvertIndentationToTabs,
11984 window: &mut Window,
11985 cx: &mut Context<Self>,
11986 ) {
11987 let settings = self.buffer.read(cx).language_settings(cx);
11988 let tab_size = settings.tab_size.get() as usize;
11989
11990 self.manipulate_mutable_lines(window, cx, |lines| {
11991 // Allocates a reasonably sized buffer once for the whole loop
11992 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11993 // Avoids recomputing spaces that could be inserted many times
11994 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11995 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11996 .collect();
11997
11998 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11999 let mut chars = line.chars();
12000 let mut spaces_count = 0;
12001 let mut first_non_indent_char = None;
12002 let mut changed = false;
12003
12004 for ch in chars.by_ref() {
12005 match ch {
12006 ' ' => {
12007 // Keep track of spaces. Append \t when we reach tab_size
12008 spaces_count += 1;
12009 changed = true;
12010 if spaces_count == tab_size {
12011 reindented_line.push('\t');
12012 spaces_count = 0;
12013 }
12014 }
12015 '\t' => {
12016 reindented_line.push('\t');
12017 spaces_count = 0;
12018 }
12019 _ => {
12020 // Dont append it yet, we might have remaining spaces
12021 first_non_indent_char = Some(ch);
12022 break;
12023 }
12024 }
12025 }
12026
12027 if !changed {
12028 reindented_line.clear();
12029 continue;
12030 }
12031 // Remaining spaces that didn't make a full tab stop
12032 if spaces_count > 0 {
12033 reindented_line.extend(&space_cache[spaces_count - 1]);
12034 }
12035 // If we consume an extra character that was not indentation, add it back
12036 if let Some(extra_char) = first_non_indent_char {
12037 reindented_line.push(extra_char);
12038 }
12039 // Append the rest of the line and replace old reference with new one
12040 reindented_line.extend(chars);
12041 *line = Cow::Owned(reindented_line.clone());
12042 reindented_line.clear();
12043 }
12044 });
12045 }
12046
12047 pub fn convert_to_upper_case(
12048 &mut self,
12049 _: &ConvertToUpperCase,
12050 window: &mut Window,
12051 cx: &mut Context<Self>,
12052 ) {
12053 self.manipulate_text(window, cx, |text| text.to_uppercase())
12054 }
12055
12056 pub fn convert_to_lower_case(
12057 &mut self,
12058 _: &ConvertToLowerCase,
12059 window: &mut Window,
12060 cx: &mut Context<Self>,
12061 ) {
12062 self.manipulate_text(window, cx, |text| text.to_lowercase())
12063 }
12064
12065 pub fn convert_to_title_case(
12066 &mut self,
12067 _: &ConvertToTitleCase,
12068 window: &mut Window,
12069 cx: &mut Context<Self>,
12070 ) {
12071 self.manipulate_text(window, cx, |text| {
12072 text.split('\n')
12073 .map(|line| line.to_case(Case::Title))
12074 .join("\n")
12075 })
12076 }
12077
12078 pub fn convert_to_snake_case(
12079 &mut self,
12080 _: &ConvertToSnakeCase,
12081 window: &mut Window,
12082 cx: &mut Context<Self>,
12083 ) {
12084 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12085 }
12086
12087 pub fn convert_to_kebab_case(
12088 &mut self,
12089 _: &ConvertToKebabCase,
12090 window: &mut Window,
12091 cx: &mut Context<Self>,
12092 ) {
12093 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12094 }
12095
12096 pub fn convert_to_upper_camel_case(
12097 &mut self,
12098 _: &ConvertToUpperCamelCase,
12099 window: &mut Window,
12100 cx: &mut Context<Self>,
12101 ) {
12102 self.manipulate_text(window, cx, |text| {
12103 text.split('\n')
12104 .map(|line| line.to_case(Case::UpperCamel))
12105 .join("\n")
12106 })
12107 }
12108
12109 pub fn convert_to_lower_camel_case(
12110 &mut self,
12111 _: &ConvertToLowerCamelCase,
12112 window: &mut Window,
12113 cx: &mut Context<Self>,
12114 ) {
12115 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12116 }
12117
12118 pub fn convert_to_opposite_case(
12119 &mut self,
12120 _: &ConvertToOppositeCase,
12121 window: &mut Window,
12122 cx: &mut Context<Self>,
12123 ) {
12124 self.manipulate_text(window, cx, |text| {
12125 text.chars()
12126 .fold(String::with_capacity(text.len()), |mut t, c| {
12127 if c.is_uppercase() {
12128 t.extend(c.to_lowercase());
12129 } else {
12130 t.extend(c.to_uppercase());
12131 }
12132 t
12133 })
12134 })
12135 }
12136
12137 pub fn convert_to_sentence_case(
12138 &mut self,
12139 _: &ConvertToSentenceCase,
12140 window: &mut Window,
12141 cx: &mut Context<Self>,
12142 ) {
12143 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12144 }
12145
12146 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12147 self.manipulate_text(window, cx, |text| {
12148 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12149 if has_upper_case_characters {
12150 text.to_lowercase()
12151 } else {
12152 text.to_uppercase()
12153 }
12154 })
12155 }
12156
12157 pub fn convert_to_rot13(
12158 &mut self,
12159 _: &ConvertToRot13,
12160 window: &mut Window,
12161 cx: &mut Context<Self>,
12162 ) {
12163 self.manipulate_text(window, cx, |text| {
12164 text.chars()
12165 .map(|c| match c {
12166 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12167 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12168 _ => c,
12169 })
12170 .collect()
12171 })
12172 }
12173
12174 pub fn convert_to_rot47(
12175 &mut self,
12176 _: &ConvertToRot47,
12177 window: &mut Window,
12178 cx: &mut Context<Self>,
12179 ) {
12180 self.manipulate_text(window, cx, |text| {
12181 text.chars()
12182 .map(|c| {
12183 let code_point = c as u32;
12184 if code_point >= 33 && code_point <= 126 {
12185 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12186 }
12187 c
12188 })
12189 .collect()
12190 })
12191 }
12192
12193 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12194 where
12195 Fn: FnMut(&str) -> String,
12196 {
12197 let buffer = self.buffer.read(cx).snapshot(cx);
12198
12199 let mut new_selections = Vec::new();
12200 let mut edits = Vec::new();
12201 let mut selection_adjustment = 0isize;
12202
12203 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12204 let selection_is_empty = selection.is_empty();
12205
12206 let (start, end) = if selection_is_empty {
12207 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12208 (word_range.start, word_range.end)
12209 } else {
12210 (
12211 buffer.point_to_offset(selection.start),
12212 buffer.point_to_offset(selection.end),
12213 )
12214 };
12215
12216 let text = buffer.text_for_range(start..end).collect::<String>();
12217 let old_length = text.len() as isize;
12218 let text = callback(&text);
12219
12220 new_selections.push(Selection {
12221 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12222 end: MultiBufferOffset(
12223 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12224 ),
12225 goal: SelectionGoal::None,
12226 id: selection.id,
12227 reversed: selection.reversed,
12228 });
12229
12230 selection_adjustment += old_length - text.len() as isize;
12231
12232 edits.push((start..end, text));
12233 }
12234
12235 self.transact(window, cx, |this, window, cx| {
12236 this.buffer.update(cx, |buffer, cx| {
12237 buffer.edit(edits, None, cx);
12238 });
12239
12240 this.change_selections(Default::default(), window, cx, |s| {
12241 s.select(new_selections);
12242 });
12243
12244 this.request_autoscroll(Autoscroll::fit(), cx);
12245 });
12246 }
12247
12248 pub fn move_selection_on_drop(
12249 &mut self,
12250 selection: &Selection<Anchor>,
12251 target: DisplayPoint,
12252 is_cut: bool,
12253 window: &mut Window,
12254 cx: &mut Context<Self>,
12255 ) {
12256 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12257 let buffer = display_map.buffer_snapshot();
12258 let mut edits = Vec::new();
12259 let insert_point = display_map
12260 .clip_point(target, Bias::Left)
12261 .to_point(&display_map);
12262 let text = buffer
12263 .text_for_range(selection.start..selection.end)
12264 .collect::<String>();
12265 if is_cut {
12266 edits.push(((selection.start..selection.end), String::new()));
12267 }
12268 let insert_anchor = buffer.anchor_before(insert_point);
12269 edits.push(((insert_anchor..insert_anchor), text));
12270 let last_edit_start = insert_anchor.bias_left(buffer);
12271 let last_edit_end = insert_anchor.bias_right(buffer);
12272 self.transact(window, cx, |this, window, cx| {
12273 this.buffer.update(cx, |buffer, cx| {
12274 buffer.edit(edits, None, cx);
12275 });
12276 this.change_selections(Default::default(), window, cx, |s| {
12277 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12278 });
12279 });
12280 }
12281
12282 pub fn clear_selection_drag_state(&mut self) {
12283 self.selection_drag_state = SelectionDragState::None;
12284 }
12285
12286 pub fn duplicate(
12287 &mut self,
12288 upwards: bool,
12289 whole_lines: bool,
12290 window: &mut Window,
12291 cx: &mut Context<Self>,
12292 ) {
12293 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12294
12295 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12296 let buffer = display_map.buffer_snapshot();
12297 let selections = self.selections.all::<Point>(&display_map);
12298
12299 let mut edits = Vec::new();
12300 let mut selections_iter = selections.iter().peekable();
12301 while let Some(selection) = selections_iter.next() {
12302 let mut rows = selection.spanned_rows(false, &display_map);
12303 // duplicate line-wise
12304 if whole_lines || selection.start == selection.end {
12305 // Avoid duplicating the same lines twice.
12306 while let Some(next_selection) = selections_iter.peek() {
12307 let next_rows = next_selection.spanned_rows(false, &display_map);
12308 if next_rows.start < rows.end {
12309 rows.end = next_rows.end;
12310 selections_iter.next().unwrap();
12311 } else {
12312 break;
12313 }
12314 }
12315
12316 // Copy the text from the selected row region and splice it either at the start
12317 // or end of the region.
12318 let start = Point::new(rows.start.0, 0);
12319 let end = Point::new(
12320 rows.end.previous_row().0,
12321 buffer.line_len(rows.end.previous_row()),
12322 );
12323
12324 let mut text = buffer.text_for_range(start..end).collect::<String>();
12325
12326 let insert_location = if upwards {
12327 // When duplicating upward, we need to insert before the current line.
12328 // If we're on the last line and it doesn't end with a newline,
12329 // we need to add a newline before the duplicated content.
12330 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12331 && buffer.max_point().column > 0
12332 && !text.ends_with('\n');
12333
12334 if needs_leading_newline {
12335 text.insert(0, '\n');
12336 end
12337 } else {
12338 text.push('\n');
12339 Point::new(rows.start.0, 0)
12340 }
12341 } else {
12342 text.push('\n');
12343 start
12344 };
12345 edits.push((insert_location..insert_location, text));
12346 } else {
12347 // duplicate character-wise
12348 let start = selection.start;
12349 let end = selection.end;
12350 let text = buffer.text_for_range(start..end).collect::<String>();
12351 edits.push((selection.end..selection.end, text));
12352 }
12353 }
12354
12355 self.transact(window, cx, |this, window, cx| {
12356 this.buffer.update(cx, |buffer, cx| {
12357 buffer.edit(edits, None, cx);
12358 });
12359
12360 // When duplicating upward with whole lines, move the cursor to the duplicated line
12361 if upwards && whole_lines {
12362 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12363
12364 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12365 let mut new_ranges = Vec::new();
12366 let selections = s.all::<Point>(&display_map);
12367 let mut selections_iter = selections.iter().peekable();
12368
12369 while let Some(first_selection) = selections_iter.next() {
12370 // Group contiguous selections together to find the total row span
12371 let mut group_selections = vec![first_selection];
12372 let mut rows = first_selection.spanned_rows(false, &display_map);
12373
12374 while let Some(next_selection) = selections_iter.peek() {
12375 let next_rows = next_selection.spanned_rows(false, &display_map);
12376 if next_rows.start < rows.end {
12377 rows.end = next_rows.end;
12378 group_selections.push(selections_iter.next().unwrap());
12379 } else {
12380 break;
12381 }
12382 }
12383
12384 let row_count = rows.end.0 - rows.start.0;
12385
12386 // Move all selections in this group up by the total number of duplicated rows
12387 for selection in group_selections {
12388 let new_start = Point::new(
12389 selection.start.row.saturating_sub(row_count),
12390 selection.start.column,
12391 );
12392
12393 let new_end = Point::new(
12394 selection.end.row.saturating_sub(row_count),
12395 selection.end.column,
12396 );
12397
12398 new_ranges.push(new_start..new_end);
12399 }
12400 }
12401
12402 s.select_ranges(new_ranges);
12403 });
12404 }
12405
12406 this.request_autoscroll(Autoscroll::fit(), cx);
12407 });
12408 }
12409
12410 pub fn duplicate_line_up(
12411 &mut self,
12412 _: &DuplicateLineUp,
12413 window: &mut Window,
12414 cx: &mut Context<Self>,
12415 ) {
12416 self.duplicate(true, true, window, cx);
12417 }
12418
12419 pub fn duplicate_line_down(
12420 &mut self,
12421 _: &DuplicateLineDown,
12422 window: &mut Window,
12423 cx: &mut Context<Self>,
12424 ) {
12425 self.duplicate(false, true, window, cx);
12426 }
12427
12428 pub fn duplicate_selection(
12429 &mut self,
12430 _: &DuplicateSelection,
12431 window: &mut Window,
12432 cx: &mut Context<Self>,
12433 ) {
12434 self.duplicate(false, false, window, cx);
12435 }
12436
12437 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12439 if self.mode.is_single_line() {
12440 cx.propagate();
12441 return;
12442 }
12443
12444 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12445 let buffer = self.buffer.read(cx).snapshot(cx);
12446
12447 let mut edits = Vec::new();
12448 let mut unfold_ranges = Vec::new();
12449 let mut refold_creases = Vec::new();
12450
12451 let selections = self.selections.all::<Point>(&display_map);
12452 let mut selections = selections.iter().peekable();
12453 let mut contiguous_row_selections = Vec::new();
12454 let mut new_selections = Vec::new();
12455
12456 while let Some(selection) = selections.next() {
12457 // Find all the selections that span a contiguous row range
12458 let (start_row, end_row) = consume_contiguous_rows(
12459 &mut contiguous_row_selections,
12460 selection,
12461 &display_map,
12462 &mut selections,
12463 );
12464
12465 // Move the text spanned by the row range to be before the line preceding the row range
12466 if start_row.0 > 0 {
12467 let range_to_move = Point::new(
12468 start_row.previous_row().0,
12469 buffer.line_len(start_row.previous_row()),
12470 )
12471 ..Point::new(
12472 end_row.previous_row().0,
12473 buffer.line_len(end_row.previous_row()),
12474 );
12475 let insertion_point = display_map
12476 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12477 .0;
12478
12479 // Don't move lines across excerpts
12480 if buffer
12481 .excerpt_containing(insertion_point..range_to_move.end)
12482 .is_some()
12483 {
12484 let text = buffer
12485 .text_for_range(range_to_move.clone())
12486 .flat_map(|s| s.chars())
12487 .skip(1)
12488 .chain(['\n'])
12489 .collect::<String>();
12490
12491 edits.push((
12492 buffer.anchor_after(range_to_move.start)
12493 ..buffer.anchor_before(range_to_move.end),
12494 String::new(),
12495 ));
12496 let insertion_anchor = buffer.anchor_after(insertion_point);
12497 edits.push((insertion_anchor..insertion_anchor, text));
12498
12499 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12500
12501 // Move selections up
12502 new_selections.extend(contiguous_row_selections.drain(..).map(
12503 |mut selection| {
12504 selection.start.row -= row_delta;
12505 selection.end.row -= row_delta;
12506 selection
12507 },
12508 ));
12509
12510 // Move folds up
12511 unfold_ranges.push(range_to_move.clone());
12512 for fold in display_map.folds_in_range(
12513 buffer.anchor_before(range_to_move.start)
12514 ..buffer.anchor_after(range_to_move.end),
12515 ) {
12516 let mut start = fold.range.start.to_point(&buffer);
12517 let mut end = fold.range.end.to_point(&buffer);
12518 start.row -= row_delta;
12519 end.row -= row_delta;
12520 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12521 }
12522 }
12523 }
12524
12525 // If we didn't move line(s), preserve the existing selections
12526 new_selections.append(&mut contiguous_row_selections);
12527 }
12528
12529 self.transact(window, cx, |this, window, cx| {
12530 this.unfold_ranges(&unfold_ranges, true, true, cx);
12531 this.buffer.update(cx, |buffer, cx| {
12532 for (range, text) in edits {
12533 buffer.edit([(range, text)], None, cx);
12534 }
12535 });
12536 this.fold_creases(refold_creases, true, window, cx);
12537 this.change_selections(Default::default(), window, cx, |s| {
12538 s.select(new_selections);
12539 })
12540 });
12541 }
12542
12543 pub fn move_line_down(
12544 &mut self,
12545 _: &MoveLineDown,
12546 window: &mut Window,
12547 cx: &mut Context<Self>,
12548 ) {
12549 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12550 if self.mode.is_single_line() {
12551 cx.propagate();
12552 return;
12553 }
12554
12555 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12556 let buffer = self.buffer.read(cx).snapshot(cx);
12557
12558 let mut edits = Vec::new();
12559 let mut unfold_ranges = Vec::new();
12560 let mut refold_creases = Vec::new();
12561
12562 let selections = self.selections.all::<Point>(&display_map);
12563 let mut selections = selections.iter().peekable();
12564 let mut contiguous_row_selections = Vec::new();
12565 let mut new_selections = Vec::new();
12566
12567 while let Some(selection) = selections.next() {
12568 // Find all the selections that span a contiguous row range
12569 let (start_row, end_row) = consume_contiguous_rows(
12570 &mut contiguous_row_selections,
12571 selection,
12572 &display_map,
12573 &mut selections,
12574 );
12575
12576 // Move the text spanned by the row range to be after the last line of the row range
12577 if end_row.0 <= buffer.max_point().row {
12578 let range_to_move =
12579 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12580 let insertion_point = display_map
12581 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12582 .0;
12583
12584 // Don't move lines across excerpt boundaries
12585 if buffer
12586 .excerpt_containing(range_to_move.start..insertion_point)
12587 .is_some()
12588 {
12589 let mut text = String::from("\n");
12590 text.extend(buffer.text_for_range(range_to_move.clone()));
12591 text.pop(); // Drop trailing newline
12592 edits.push((
12593 buffer.anchor_after(range_to_move.start)
12594 ..buffer.anchor_before(range_to_move.end),
12595 String::new(),
12596 ));
12597 let insertion_anchor = buffer.anchor_after(insertion_point);
12598 edits.push((insertion_anchor..insertion_anchor, text));
12599
12600 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12601
12602 // Move selections down
12603 new_selections.extend(contiguous_row_selections.drain(..).map(
12604 |mut selection| {
12605 selection.start.row += row_delta;
12606 selection.end.row += row_delta;
12607 selection
12608 },
12609 ));
12610
12611 // Move folds down
12612 unfold_ranges.push(range_to_move.clone());
12613 for fold in display_map.folds_in_range(
12614 buffer.anchor_before(range_to_move.start)
12615 ..buffer.anchor_after(range_to_move.end),
12616 ) {
12617 let mut start = fold.range.start.to_point(&buffer);
12618 let mut end = fold.range.end.to_point(&buffer);
12619 start.row += row_delta;
12620 end.row += row_delta;
12621 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12622 }
12623 }
12624 }
12625
12626 // If we didn't move line(s), preserve the existing selections
12627 new_selections.append(&mut contiguous_row_selections);
12628 }
12629
12630 self.transact(window, cx, |this, window, cx| {
12631 this.unfold_ranges(&unfold_ranges, true, true, cx);
12632 this.buffer.update(cx, |buffer, cx| {
12633 for (range, text) in edits {
12634 buffer.edit([(range, text)], None, cx);
12635 }
12636 });
12637 this.fold_creases(refold_creases, true, window, cx);
12638 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12639 });
12640 }
12641
12642 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12643 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12644 let text_layout_details = &self.text_layout_details(window);
12645 self.transact(window, cx, |this, window, cx| {
12646 let edits = this.change_selections(Default::default(), window, cx, |s| {
12647 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12648 s.move_with(|display_map, selection| {
12649 if !selection.is_empty() {
12650 return;
12651 }
12652
12653 let mut head = selection.head();
12654 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12655 if head.column() == display_map.line_len(head.row()) {
12656 transpose_offset = display_map
12657 .buffer_snapshot()
12658 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12659 }
12660
12661 if transpose_offset == MultiBufferOffset(0) {
12662 return;
12663 }
12664
12665 *head.column_mut() += 1;
12666 head = display_map.clip_point(head, Bias::Right);
12667 let goal = SelectionGoal::HorizontalPosition(
12668 display_map
12669 .x_for_display_point(head, text_layout_details)
12670 .into(),
12671 );
12672 selection.collapse_to(head, goal);
12673
12674 let transpose_start = display_map
12675 .buffer_snapshot()
12676 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12677 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12678 let transpose_end = display_map
12679 .buffer_snapshot()
12680 .clip_offset(transpose_offset + 1usize, Bias::Right);
12681 if let Some(ch) = display_map
12682 .buffer_snapshot()
12683 .chars_at(transpose_start)
12684 .next()
12685 {
12686 edits.push((transpose_start..transpose_offset, String::new()));
12687 edits.push((transpose_end..transpose_end, ch.to_string()));
12688 }
12689 }
12690 });
12691 edits
12692 });
12693 this.buffer
12694 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12695 let selections = this
12696 .selections
12697 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12698 this.change_selections(Default::default(), window, cx, |s| {
12699 s.select(selections);
12700 });
12701 });
12702 }
12703
12704 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12705 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12706 if self.mode.is_single_line() {
12707 cx.propagate();
12708 return;
12709 }
12710
12711 self.rewrap_impl(RewrapOptions::default(), cx)
12712 }
12713
12714 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12715 let buffer = self.buffer.read(cx).snapshot(cx);
12716 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12717
12718 #[derive(Clone, Debug, PartialEq)]
12719 enum CommentFormat {
12720 /// single line comment, with prefix for line
12721 Line(String),
12722 /// single line within a block comment, with prefix for line
12723 BlockLine(String),
12724 /// a single line of a block comment that includes the initial delimiter
12725 BlockCommentWithStart(BlockCommentConfig),
12726 /// a single line of a block comment that includes the ending delimiter
12727 BlockCommentWithEnd(BlockCommentConfig),
12728 }
12729
12730 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12731 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12732 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12733 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12734 .peekable();
12735
12736 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12737 row
12738 } else {
12739 return Vec::new();
12740 };
12741
12742 let language_settings = buffer.language_settings_at(selection.head(), cx);
12743 let language_scope = buffer.language_scope_at(selection.head());
12744
12745 let indent_and_prefix_for_row =
12746 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12747 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12748 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12749 &language_scope
12750 {
12751 let indent_end = Point::new(row, indent.len);
12752 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12753 let line_text_after_indent = buffer
12754 .text_for_range(indent_end..line_end)
12755 .collect::<String>();
12756
12757 let is_within_comment_override = buffer
12758 .language_scope_at(indent_end)
12759 .is_some_and(|scope| scope.override_name() == Some("comment"));
12760 let comment_delimiters = if is_within_comment_override {
12761 // we are within a comment syntax node, but we don't
12762 // yet know what kind of comment: block, doc or line
12763 match (
12764 language_scope.documentation_comment(),
12765 language_scope.block_comment(),
12766 ) {
12767 (Some(config), _) | (_, Some(config))
12768 if buffer.contains_str_at(indent_end, &config.start) =>
12769 {
12770 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12771 }
12772 (Some(config), _) | (_, Some(config))
12773 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12774 {
12775 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12776 }
12777 (Some(config), _) | (_, Some(config))
12778 if buffer.contains_str_at(indent_end, &config.prefix) =>
12779 {
12780 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12781 }
12782 (_, _) => language_scope
12783 .line_comment_prefixes()
12784 .iter()
12785 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12786 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12787 }
12788 } else {
12789 // we not in an overridden comment node, but we may
12790 // be within a non-overridden line comment node
12791 language_scope
12792 .line_comment_prefixes()
12793 .iter()
12794 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12795 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12796 };
12797
12798 let rewrap_prefix = language_scope
12799 .rewrap_prefixes()
12800 .iter()
12801 .find_map(|prefix_regex| {
12802 prefix_regex.find(&line_text_after_indent).map(|mat| {
12803 if mat.start() == 0 {
12804 Some(mat.as_str().to_string())
12805 } else {
12806 None
12807 }
12808 })
12809 })
12810 .flatten();
12811 (comment_delimiters, rewrap_prefix)
12812 } else {
12813 (None, None)
12814 };
12815 (indent, comment_prefix, rewrap_prefix)
12816 };
12817
12818 let mut ranges = Vec::new();
12819 let from_empty_selection = selection.is_empty();
12820
12821 let mut current_range_start = first_row;
12822 let mut prev_row = first_row;
12823 let (
12824 mut current_range_indent,
12825 mut current_range_comment_delimiters,
12826 mut current_range_rewrap_prefix,
12827 ) = indent_and_prefix_for_row(first_row);
12828
12829 for row in non_blank_rows_iter.skip(1) {
12830 let has_paragraph_break = row > prev_row + 1;
12831
12832 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12833 indent_and_prefix_for_row(row);
12834
12835 let has_indent_change = row_indent != current_range_indent;
12836 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12837
12838 let has_boundary_change = has_comment_change
12839 || row_rewrap_prefix.is_some()
12840 || (has_indent_change && current_range_comment_delimiters.is_some());
12841
12842 if has_paragraph_break || has_boundary_change {
12843 ranges.push((
12844 language_settings.clone(),
12845 Point::new(current_range_start, 0)
12846 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12847 current_range_indent,
12848 current_range_comment_delimiters.clone(),
12849 current_range_rewrap_prefix.clone(),
12850 from_empty_selection,
12851 ));
12852 current_range_start = row;
12853 current_range_indent = row_indent;
12854 current_range_comment_delimiters = row_comment_delimiters;
12855 current_range_rewrap_prefix = row_rewrap_prefix;
12856 }
12857 prev_row = row;
12858 }
12859
12860 ranges.push((
12861 language_settings.clone(),
12862 Point::new(current_range_start, 0)
12863 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12864 current_range_indent,
12865 current_range_comment_delimiters,
12866 current_range_rewrap_prefix,
12867 from_empty_selection,
12868 ));
12869
12870 ranges
12871 });
12872
12873 let mut edits = Vec::new();
12874 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12875
12876 for (
12877 language_settings,
12878 wrap_range,
12879 mut indent_size,
12880 comment_prefix,
12881 rewrap_prefix,
12882 from_empty_selection,
12883 ) in wrap_ranges
12884 {
12885 let mut start_row = wrap_range.start.row;
12886 let mut end_row = wrap_range.end.row;
12887
12888 // Skip selections that overlap with a range that has already been rewrapped.
12889 let selection_range = start_row..end_row;
12890 if rewrapped_row_ranges
12891 .iter()
12892 .any(|range| range.overlaps(&selection_range))
12893 {
12894 continue;
12895 }
12896
12897 let tab_size = language_settings.tab_size;
12898
12899 let (line_prefix, inside_comment) = match &comment_prefix {
12900 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12901 (Some(prefix.as_str()), true)
12902 }
12903 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12904 (Some(prefix.as_ref()), true)
12905 }
12906 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12907 start: _,
12908 end: _,
12909 prefix,
12910 tab_size,
12911 })) => {
12912 indent_size.len += tab_size;
12913 (Some(prefix.as_ref()), true)
12914 }
12915 None => (None, false),
12916 };
12917 let indent_prefix = indent_size.chars().collect::<String>();
12918 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12919
12920 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12921 RewrapBehavior::InComments => inside_comment,
12922 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12923 RewrapBehavior::Anywhere => true,
12924 };
12925
12926 let should_rewrap = options.override_language_settings
12927 || allow_rewrap_based_on_language
12928 || self.hard_wrap.is_some();
12929 if !should_rewrap {
12930 continue;
12931 }
12932
12933 if from_empty_selection {
12934 'expand_upwards: while start_row > 0 {
12935 let prev_row = start_row - 1;
12936 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12937 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12938 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12939 {
12940 start_row = prev_row;
12941 } else {
12942 break 'expand_upwards;
12943 }
12944 }
12945
12946 'expand_downwards: while end_row < buffer.max_point().row {
12947 let next_row = end_row + 1;
12948 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12949 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12950 && !buffer.is_line_blank(MultiBufferRow(next_row))
12951 {
12952 end_row = next_row;
12953 } else {
12954 break 'expand_downwards;
12955 }
12956 }
12957 }
12958
12959 let start = Point::new(start_row, 0);
12960 let start_offset = ToOffset::to_offset(&start, &buffer);
12961 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12962 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12963 let mut first_line_delimiter = None;
12964 let mut last_line_delimiter = None;
12965 let Some(lines_without_prefixes) = selection_text
12966 .lines()
12967 .enumerate()
12968 .map(|(ix, line)| {
12969 let line_trimmed = line.trim_start();
12970 if rewrap_prefix.is_some() && ix > 0 {
12971 Ok(line_trimmed)
12972 } else if let Some(
12973 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12974 start,
12975 prefix,
12976 end,
12977 tab_size,
12978 })
12979 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12980 start,
12981 prefix,
12982 end,
12983 tab_size,
12984 }),
12985 ) = &comment_prefix
12986 {
12987 let line_trimmed = line_trimmed
12988 .strip_prefix(start.as_ref())
12989 .map(|s| {
12990 let mut indent_size = indent_size;
12991 indent_size.len -= tab_size;
12992 let indent_prefix: String = indent_size.chars().collect();
12993 first_line_delimiter = Some((indent_prefix, start));
12994 s.trim_start()
12995 })
12996 .unwrap_or(line_trimmed);
12997 let line_trimmed = line_trimmed
12998 .strip_suffix(end.as_ref())
12999 .map(|s| {
13000 last_line_delimiter = Some(end);
13001 s.trim_end()
13002 })
13003 .unwrap_or(line_trimmed);
13004 let line_trimmed = line_trimmed
13005 .strip_prefix(prefix.as_ref())
13006 .unwrap_or(line_trimmed);
13007 Ok(line_trimmed)
13008 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
13009 line_trimmed.strip_prefix(prefix).with_context(|| {
13010 format!("line did not start with prefix {prefix:?}: {line:?}")
13011 })
13012 } else {
13013 line_trimmed
13014 .strip_prefix(&line_prefix.trim_start())
13015 .with_context(|| {
13016 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13017 })
13018 }
13019 })
13020 .collect::<Result<Vec<_>, _>>()
13021 .log_err()
13022 else {
13023 continue;
13024 };
13025
13026 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13027 buffer
13028 .language_settings_at(Point::new(start_row, 0), cx)
13029 .preferred_line_length as usize
13030 });
13031
13032 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13033 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13034 } else {
13035 line_prefix.clone()
13036 };
13037
13038 let wrapped_text = {
13039 let mut wrapped_text = wrap_with_prefix(
13040 line_prefix,
13041 subsequent_lines_prefix,
13042 lines_without_prefixes.join("\n"),
13043 wrap_column,
13044 tab_size,
13045 options.preserve_existing_whitespace,
13046 );
13047
13048 if let Some((indent, delimiter)) = first_line_delimiter {
13049 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13050 }
13051 if let Some(last_line) = last_line_delimiter {
13052 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13053 }
13054
13055 wrapped_text
13056 };
13057
13058 // TODO: should always use char-based diff while still supporting cursor behavior that
13059 // matches vim.
13060 let mut diff_options = DiffOptions::default();
13061 if options.override_language_settings {
13062 diff_options.max_word_diff_len = 0;
13063 diff_options.max_word_diff_line_count = 0;
13064 } else {
13065 diff_options.max_word_diff_len = usize::MAX;
13066 diff_options.max_word_diff_line_count = usize::MAX;
13067 }
13068
13069 for (old_range, new_text) in
13070 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13071 {
13072 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13073 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13074 edits.push((edit_start..edit_end, new_text));
13075 }
13076
13077 rewrapped_row_ranges.push(start_row..=end_row);
13078 }
13079
13080 self.buffer
13081 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13082 }
13083
13084 pub fn cut_common(
13085 &mut self,
13086 cut_no_selection_line: bool,
13087 window: &mut Window,
13088 cx: &mut Context<Self>,
13089 ) -> ClipboardItem {
13090 let mut text = String::new();
13091 let buffer = self.buffer.read(cx).snapshot(cx);
13092 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13093 let mut clipboard_selections = Vec::with_capacity(selections.len());
13094 {
13095 let max_point = buffer.max_point();
13096 let mut is_first = true;
13097 let mut prev_selection_was_entire_line = false;
13098 for selection in &mut selections {
13099 let is_entire_line =
13100 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13101 if is_entire_line {
13102 selection.start = Point::new(selection.start.row, 0);
13103 if !selection.is_empty() && selection.end.column == 0 {
13104 selection.end = cmp::min(max_point, selection.end);
13105 } else {
13106 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13107 }
13108 selection.goal = SelectionGoal::None;
13109 }
13110 if is_first {
13111 is_first = false;
13112 } else if !prev_selection_was_entire_line {
13113 text += "\n";
13114 }
13115 prev_selection_was_entire_line = is_entire_line;
13116 let mut len = 0;
13117 for chunk in buffer.text_for_range(selection.start..selection.end) {
13118 text.push_str(chunk);
13119 len += chunk.len();
13120 }
13121
13122 clipboard_selections.push(ClipboardSelection::for_buffer(
13123 len,
13124 is_entire_line,
13125 selection.range(),
13126 &buffer,
13127 self.project.as_ref(),
13128 cx,
13129 ));
13130 }
13131 }
13132
13133 self.transact(window, cx, |this, window, cx| {
13134 this.change_selections(Default::default(), window, cx, |s| {
13135 s.select(selections);
13136 });
13137 this.insert("", window, cx);
13138 });
13139 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13140 }
13141
13142 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13143 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13144 let item = self.cut_common(true, window, cx);
13145 cx.write_to_clipboard(item);
13146 }
13147
13148 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13149 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13150 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13151 s.move_with(|snapshot, sel| {
13152 if sel.is_empty() {
13153 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13154 }
13155 if sel.is_empty() {
13156 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13157 }
13158 });
13159 });
13160 let item = self.cut_common(false, window, cx);
13161 cx.set_global(KillRing(item))
13162 }
13163
13164 pub fn kill_ring_yank(
13165 &mut self,
13166 _: &KillRingYank,
13167 window: &mut Window,
13168 cx: &mut Context<Self>,
13169 ) {
13170 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13171 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13172 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13173 (kill_ring.text().to_string(), kill_ring.metadata_json())
13174 } else {
13175 return;
13176 }
13177 } else {
13178 return;
13179 };
13180 self.do_paste(&text, metadata, false, window, cx);
13181 }
13182
13183 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13184 self.do_copy(true, cx);
13185 }
13186
13187 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13188 self.do_copy(false, cx);
13189 }
13190
13191 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13192 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13193 let buffer = self.buffer.read(cx).read(cx);
13194 let mut text = String::new();
13195
13196 let mut clipboard_selections = Vec::with_capacity(selections.len());
13197 {
13198 let max_point = buffer.max_point();
13199 let mut is_first = true;
13200 let mut prev_selection_was_entire_line = false;
13201 for selection in &selections {
13202 let mut start = selection.start;
13203 let mut end = selection.end;
13204 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13205 let mut add_trailing_newline = false;
13206 if is_entire_line {
13207 start = Point::new(start.row, 0);
13208 let next_line_start = Point::new(end.row + 1, 0);
13209 if next_line_start <= max_point {
13210 end = next_line_start;
13211 } else {
13212 // We're on the last line without a trailing newline.
13213 // Copy to the end of the line and add a newline afterwards.
13214 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13215 add_trailing_newline = true;
13216 }
13217 }
13218
13219 let mut trimmed_selections = Vec::new();
13220 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13221 let row = MultiBufferRow(start.row);
13222 let first_indent = buffer.indent_size_for_line(row);
13223 if first_indent.len == 0 || start.column > first_indent.len {
13224 trimmed_selections.push(start..end);
13225 } else {
13226 trimmed_selections.push(
13227 Point::new(row.0, first_indent.len)
13228 ..Point::new(row.0, buffer.line_len(row)),
13229 );
13230 for row in start.row + 1..=end.row {
13231 let mut line_len = buffer.line_len(MultiBufferRow(row));
13232 if row == end.row {
13233 line_len = end.column;
13234 }
13235 if line_len == 0 {
13236 trimmed_selections
13237 .push(Point::new(row, 0)..Point::new(row, line_len));
13238 continue;
13239 }
13240 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13241 if row_indent_size.len >= first_indent.len {
13242 trimmed_selections.push(
13243 Point::new(row, first_indent.len)..Point::new(row, line_len),
13244 );
13245 } else {
13246 trimmed_selections.clear();
13247 trimmed_selections.push(start..end);
13248 break;
13249 }
13250 }
13251 }
13252 } else {
13253 trimmed_selections.push(start..end);
13254 }
13255
13256 for trimmed_range in trimmed_selections {
13257 if is_first {
13258 is_first = false;
13259 } else if !prev_selection_was_entire_line {
13260 text += "\n";
13261 }
13262 prev_selection_was_entire_line = is_entire_line;
13263 let mut len = 0;
13264 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13265 text.push_str(chunk);
13266 len += chunk.len();
13267 }
13268 if add_trailing_newline {
13269 text.push('\n');
13270 len += 1;
13271 }
13272 clipboard_selections.push(ClipboardSelection::for_buffer(
13273 len,
13274 is_entire_line,
13275 trimmed_range,
13276 &buffer,
13277 self.project.as_ref(),
13278 cx,
13279 ));
13280 }
13281 }
13282 }
13283
13284 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13285 text,
13286 clipboard_selections,
13287 ));
13288 }
13289
13290 pub fn do_paste(
13291 &mut self,
13292 text: &String,
13293 clipboard_selections: Option<Vec<ClipboardSelection>>,
13294 handle_entire_lines: bool,
13295 window: &mut Window,
13296 cx: &mut Context<Self>,
13297 ) {
13298 if self.read_only(cx) {
13299 return;
13300 }
13301
13302 let clipboard_text = Cow::Borrowed(text.as_str());
13303
13304 self.transact(window, cx, |this, window, cx| {
13305 let had_active_edit_prediction = this.has_active_edit_prediction();
13306 let display_map = this.display_snapshot(cx);
13307 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13308 let cursor_offset = this
13309 .selections
13310 .last::<MultiBufferOffset>(&display_map)
13311 .head();
13312
13313 if let Some(mut clipboard_selections) = clipboard_selections {
13314 let all_selections_were_entire_line =
13315 clipboard_selections.iter().all(|s| s.is_entire_line);
13316 let first_selection_indent_column =
13317 clipboard_selections.first().map(|s| s.first_line_indent);
13318 if clipboard_selections.len() != old_selections.len() {
13319 clipboard_selections.drain(..);
13320 }
13321 let mut auto_indent_on_paste = true;
13322
13323 this.buffer.update(cx, |buffer, cx| {
13324 let snapshot = buffer.read(cx);
13325 auto_indent_on_paste = snapshot
13326 .language_settings_at(cursor_offset, cx)
13327 .auto_indent_on_paste;
13328
13329 let mut start_offset = 0;
13330 let mut edits = Vec::new();
13331 let mut original_indent_columns = Vec::new();
13332 for (ix, selection) in old_selections.iter().enumerate() {
13333 let to_insert;
13334 let entire_line;
13335 let original_indent_column;
13336 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13337 let end_offset = start_offset + clipboard_selection.len;
13338 to_insert = &clipboard_text[start_offset..end_offset];
13339 entire_line = clipboard_selection.is_entire_line;
13340 start_offset = if entire_line {
13341 end_offset
13342 } else {
13343 end_offset + 1
13344 };
13345 original_indent_column = Some(clipboard_selection.first_line_indent);
13346 } else {
13347 to_insert = &*clipboard_text;
13348 entire_line = all_selections_were_entire_line;
13349 original_indent_column = first_selection_indent_column
13350 }
13351
13352 let (range, to_insert) =
13353 if selection.is_empty() && handle_entire_lines && entire_line {
13354 // If the corresponding selection was empty when this slice of the
13355 // clipboard text was written, then the entire line containing the
13356 // selection was copied. If this selection is also currently empty,
13357 // then paste the line before the current line of the buffer.
13358 let column = selection.start.to_point(&snapshot).column as usize;
13359 let line_start = selection.start - column;
13360 (line_start..line_start, Cow::Borrowed(to_insert))
13361 } else {
13362 let language = snapshot.language_at(selection.head());
13363 let range = selection.range();
13364 if let Some(language) = language
13365 && language.name() == "Markdown".into()
13366 {
13367 edit_for_markdown_paste(
13368 &snapshot,
13369 range,
13370 to_insert,
13371 url::Url::parse(to_insert).ok(),
13372 )
13373 } else {
13374 (range, Cow::Borrowed(to_insert))
13375 }
13376 };
13377
13378 edits.push((range, to_insert));
13379 original_indent_columns.push(original_indent_column);
13380 }
13381 drop(snapshot);
13382
13383 buffer.edit(
13384 edits,
13385 if auto_indent_on_paste {
13386 Some(AutoindentMode::Block {
13387 original_indent_columns,
13388 })
13389 } else {
13390 None
13391 },
13392 cx,
13393 );
13394 });
13395
13396 let selections = this
13397 .selections
13398 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13399 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13400 } else {
13401 let url = url::Url::parse(&clipboard_text).ok();
13402
13403 let auto_indent_mode = if !clipboard_text.is_empty() {
13404 Some(AutoindentMode::Block {
13405 original_indent_columns: Vec::new(),
13406 })
13407 } else {
13408 None
13409 };
13410
13411 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13412 let snapshot = buffer.snapshot(cx);
13413
13414 let anchors = old_selections
13415 .iter()
13416 .map(|s| {
13417 let anchor = snapshot.anchor_after(s.head());
13418 s.map(|_| anchor)
13419 })
13420 .collect::<Vec<_>>();
13421
13422 let mut edits = Vec::new();
13423
13424 for selection in old_selections.iter() {
13425 let language = snapshot.language_at(selection.head());
13426 let range = selection.range();
13427
13428 let (edit_range, edit_text) = if let Some(language) = language
13429 && language.name() == "Markdown".into()
13430 {
13431 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13432 } else {
13433 (range, clipboard_text.clone())
13434 };
13435
13436 edits.push((edit_range, edit_text));
13437 }
13438
13439 drop(snapshot);
13440 buffer.edit(edits, auto_indent_mode, cx);
13441
13442 anchors
13443 });
13444
13445 this.change_selections(Default::default(), window, cx, |s| {
13446 s.select_anchors(selection_anchors);
13447 });
13448 }
13449
13450 // 🤔 | .. | show_in_menu |
13451 // | .. | true true
13452 // | had_edit_prediction | false true
13453
13454 let trigger_in_words =
13455 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13456
13457 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13458 });
13459 }
13460
13461 pub fn diff_clipboard_with_selection(
13462 &mut self,
13463 _: &DiffClipboardWithSelection,
13464 window: &mut Window,
13465 cx: &mut Context<Self>,
13466 ) {
13467 let selections = self
13468 .selections
13469 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13470
13471 if selections.is_empty() {
13472 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13473 return;
13474 };
13475
13476 let clipboard_text = match cx.read_from_clipboard() {
13477 Some(item) => match item.entries().first() {
13478 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13479 _ => None,
13480 },
13481 None => None,
13482 };
13483
13484 let Some(clipboard_text) = clipboard_text else {
13485 log::warn!("Clipboard doesn't contain text.");
13486 return;
13487 };
13488
13489 window.dispatch_action(
13490 Box::new(DiffClipboardWithSelectionData {
13491 clipboard_text,
13492 editor: cx.entity(),
13493 }),
13494 cx,
13495 );
13496 }
13497
13498 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13499 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13500 if let Some(item) = cx.read_from_clipboard() {
13501 let entries = item.entries();
13502
13503 match entries.first() {
13504 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13505 // of all the pasted entries.
13506 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13507 .do_paste(
13508 clipboard_string.text(),
13509 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13510 true,
13511 window,
13512 cx,
13513 ),
13514 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13515 }
13516 }
13517 }
13518
13519 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13520 if self.read_only(cx) {
13521 return;
13522 }
13523
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13525
13526 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13527 if let Some((selections, _)) =
13528 self.selection_history.transaction(transaction_id).cloned()
13529 {
13530 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13531 s.select_anchors(selections.to_vec());
13532 });
13533 } else {
13534 log::error!(
13535 "No entry in selection_history found for undo. \
13536 This may correspond to a bug where undo does not update the selection. \
13537 If this is occurring, please add details to \
13538 https://github.com/zed-industries/zed/issues/22692"
13539 );
13540 }
13541 self.request_autoscroll(Autoscroll::fit(), cx);
13542 self.unmark_text(window, cx);
13543 self.refresh_edit_prediction(true, false, window, cx);
13544 cx.emit(EditorEvent::Edited { transaction_id });
13545 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13546 }
13547 }
13548
13549 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13550 if self.read_only(cx) {
13551 return;
13552 }
13553
13554 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13555
13556 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13557 if let Some((_, Some(selections))) =
13558 self.selection_history.transaction(transaction_id).cloned()
13559 {
13560 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13561 s.select_anchors(selections.to_vec());
13562 });
13563 } else {
13564 log::error!(
13565 "No entry in selection_history found for redo. \
13566 This may correspond to a bug where undo does not update the selection. \
13567 If this is occurring, please add details to \
13568 https://github.com/zed-industries/zed/issues/22692"
13569 );
13570 }
13571 self.request_autoscroll(Autoscroll::fit(), cx);
13572 self.unmark_text(window, cx);
13573 self.refresh_edit_prediction(true, false, window, cx);
13574 cx.emit(EditorEvent::Edited { transaction_id });
13575 }
13576 }
13577
13578 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13579 self.buffer
13580 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13581 }
13582
13583 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13584 self.buffer
13585 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13586 }
13587
13588 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13590 self.change_selections(Default::default(), window, cx, |s| {
13591 s.move_with(|map, selection| {
13592 let cursor = if selection.is_empty() {
13593 movement::left(map, selection.start)
13594 } else {
13595 selection.start
13596 };
13597 selection.collapse_to(cursor, SelectionGoal::None);
13598 });
13599 })
13600 }
13601
13602 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13604 self.change_selections(Default::default(), window, cx, |s| {
13605 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13606 })
13607 }
13608
13609 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13611 self.change_selections(Default::default(), window, cx, |s| {
13612 s.move_with(|map, selection| {
13613 let cursor = if selection.is_empty() {
13614 movement::right(map, selection.end)
13615 } else {
13616 selection.end
13617 };
13618 selection.collapse_to(cursor, SelectionGoal::None)
13619 });
13620 })
13621 }
13622
13623 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13625 self.change_selections(Default::default(), window, cx, |s| {
13626 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13627 });
13628 }
13629
13630 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13631 if self.take_rename(true, window, cx).is_some() {
13632 return;
13633 }
13634
13635 if self.mode.is_single_line() {
13636 cx.propagate();
13637 return;
13638 }
13639
13640 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13641
13642 let text_layout_details = &self.text_layout_details(window);
13643 let selection_count = self.selections.count();
13644 let first_selection = self.selections.first_anchor();
13645
13646 self.change_selections(Default::default(), window, cx, |s| {
13647 s.move_with(|map, selection| {
13648 if !selection.is_empty() {
13649 selection.goal = SelectionGoal::None;
13650 }
13651 let (cursor, goal) = movement::up(
13652 map,
13653 selection.start,
13654 selection.goal,
13655 false,
13656 text_layout_details,
13657 );
13658 selection.collapse_to(cursor, goal);
13659 });
13660 });
13661
13662 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13663 {
13664 cx.propagate();
13665 }
13666 }
13667
13668 pub fn move_up_by_lines(
13669 &mut self,
13670 action: &MoveUpByLines,
13671 window: &mut Window,
13672 cx: &mut Context<Self>,
13673 ) {
13674 if self.take_rename(true, window, cx).is_some() {
13675 return;
13676 }
13677
13678 if self.mode.is_single_line() {
13679 cx.propagate();
13680 return;
13681 }
13682
13683 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13684
13685 let text_layout_details = &self.text_layout_details(window);
13686
13687 self.change_selections(Default::default(), window, cx, |s| {
13688 s.move_with(|map, selection| {
13689 if !selection.is_empty() {
13690 selection.goal = SelectionGoal::None;
13691 }
13692 let (cursor, goal) = movement::up_by_rows(
13693 map,
13694 selection.start,
13695 action.lines,
13696 selection.goal,
13697 false,
13698 text_layout_details,
13699 );
13700 selection.collapse_to(cursor, goal);
13701 });
13702 })
13703 }
13704
13705 pub fn move_down_by_lines(
13706 &mut self,
13707 action: &MoveDownByLines,
13708 window: &mut Window,
13709 cx: &mut Context<Self>,
13710 ) {
13711 if self.take_rename(true, window, cx).is_some() {
13712 return;
13713 }
13714
13715 if self.mode.is_single_line() {
13716 cx.propagate();
13717 return;
13718 }
13719
13720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13721
13722 let text_layout_details = &self.text_layout_details(window);
13723
13724 self.change_selections(Default::default(), window, cx, |s| {
13725 s.move_with(|map, selection| {
13726 if !selection.is_empty() {
13727 selection.goal = SelectionGoal::None;
13728 }
13729 let (cursor, goal) = movement::down_by_rows(
13730 map,
13731 selection.start,
13732 action.lines,
13733 selection.goal,
13734 false,
13735 text_layout_details,
13736 );
13737 selection.collapse_to(cursor, goal);
13738 });
13739 })
13740 }
13741
13742 pub fn select_down_by_lines(
13743 &mut self,
13744 action: &SelectDownByLines,
13745 window: &mut Window,
13746 cx: &mut Context<Self>,
13747 ) {
13748 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13749 let text_layout_details = &self.text_layout_details(window);
13750 self.change_selections(Default::default(), window, cx, |s| {
13751 s.move_heads_with(|map, head, goal| {
13752 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13753 })
13754 })
13755 }
13756
13757 pub fn select_up_by_lines(
13758 &mut self,
13759 action: &SelectUpByLines,
13760 window: &mut Window,
13761 cx: &mut Context<Self>,
13762 ) {
13763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13764 let text_layout_details = &self.text_layout_details(window);
13765 self.change_selections(Default::default(), window, cx, |s| {
13766 s.move_heads_with(|map, head, goal| {
13767 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13768 })
13769 })
13770 }
13771
13772 pub fn select_page_up(
13773 &mut self,
13774 _: &SelectPageUp,
13775 window: &mut Window,
13776 cx: &mut Context<Self>,
13777 ) {
13778 let Some(row_count) = self.visible_row_count() else {
13779 return;
13780 };
13781
13782 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13783
13784 let text_layout_details = &self.text_layout_details(window);
13785
13786 self.change_selections(Default::default(), window, cx, |s| {
13787 s.move_heads_with(|map, head, goal| {
13788 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13789 })
13790 })
13791 }
13792
13793 pub fn move_page_up(
13794 &mut self,
13795 action: &MovePageUp,
13796 window: &mut Window,
13797 cx: &mut Context<Self>,
13798 ) {
13799 if self.take_rename(true, window, cx).is_some() {
13800 return;
13801 }
13802
13803 if self
13804 .context_menu
13805 .borrow_mut()
13806 .as_mut()
13807 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13808 .unwrap_or(false)
13809 {
13810 return;
13811 }
13812
13813 if matches!(self.mode, EditorMode::SingleLine) {
13814 cx.propagate();
13815 return;
13816 }
13817
13818 let Some(row_count) = self.visible_row_count() else {
13819 return;
13820 };
13821
13822 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13823
13824 let effects = if action.center_cursor {
13825 SelectionEffects::scroll(Autoscroll::center())
13826 } else {
13827 SelectionEffects::default()
13828 };
13829
13830 let text_layout_details = &self.text_layout_details(window);
13831
13832 self.change_selections(effects, window, cx, |s| {
13833 s.move_with(|map, selection| {
13834 if !selection.is_empty() {
13835 selection.goal = SelectionGoal::None;
13836 }
13837 let (cursor, goal) = movement::up_by_rows(
13838 map,
13839 selection.end,
13840 row_count,
13841 selection.goal,
13842 false,
13843 text_layout_details,
13844 );
13845 selection.collapse_to(cursor, goal);
13846 });
13847 });
13848 }
13849
13850 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13851 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13852 let text_layout_details = &self.text_layout_details(window);
13853 self.change_selections(Default::default(), window, cx, |s| {
13854 s.move_heads_with(|map, head, goal| {
13855 movement::up(map, head, goal, false, text_layout_details)
13856 })
13857 })
13858 }
13859
13860 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13861 self.take_rename(true, window, cx);
13862
13863 if self.mode.is_single_line() {
13864 cx.propagate();
13865 return;
13866 }
13867
13868 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13869
13870 let text_layout_details = &self.text_layout_details(window);
13871 let selection_count = self.selections.count();
13872 let first_selection = self.selections.first_anchor();
13873
13874 self.change_selections(Default::default(), window, cx, |s| {
13875 s.move_with(|map, selection| {
13876 if !selection.is_empty() {
13877 selection.goal = SelectionGoal::None;
13878 }
13879 let (cursor, goal) = movement::down(
13880 map,
13881 selection.end,
13882 selection.goal,
13883 false,
13884 text_layout_details,
13885 );
13886 selection.collapse_to(cursor, goal);
13887 });
13888 });
13889
13890 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13891 {
13892 cx.propagate();
13893 }
13894 }
13895
13896 pub fn select_page_down(
13897 &mut self,
13898 _: &SelectPageDown,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) {
13902 let Some(row_count) = self.visible_row_count() else {
13903 return;
13904 };
13905
13906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13907
13908 let text_layout_details = &self.text_layout_details(window);
13909
13910 self.change_selections(Default::default(), window, cx, |s| {
13911 s.move_heads_with(|map, head, goal| {
13912 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13913 })
13914 })
13915 }
13916
13917 pub fn move_page_down(
13918 &mut self,
13919 action: &MovePageDown,
13920 window: &mut Window,
13921 cx: &mut Context<Self>,
13922 ) {
13923 if self.take_rename(true, window, cx).is_some() {
13924 return;
13925 }
13926
13927 if self
13928 .context_menu
13929 .borrow_mut()
13930 .as_mut()
13931 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13932 .unwrap_or(false)
13933 {
13934 return;
13935 }
13936
13937 if matches!(self.mode, EditorMode::SingleLine) {
13938 cx.propagate();
13939 return;
13940 }
13941
13942 let Some(row_count) = self.visible_row_count() else {
13943 return;
13944 };
13945
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947
13948 let effects = if action.center_cursor {
13949 SelectionEffects::scroll(Autoscroll::center())
13950 } else {
13951 SelectionEffects::default()
13952 };
13953
13954 let text_layout_details = &self.text_layout_details(window);
13955 self.change_selections(effects, window, cx, |s| {
13956 s.move_with(|map, selection| {
13957 if !selection.is_empty() {
13958 selection.goal = SelectionGoal::None;
13959 }
13960 let (cursor, goal) = movement::down_by_rows(
13961 map,
13962 selection.end,
13963 row_count,
13964 selection.goal,
13965 false,
13966 text_layout_details,
13967 );
13968 selection.collapse_to(cursor, goal);
13969 });
13970 });
13971 }
13972
13973 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13974 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13975 let text_layout_details = &self.text_layout_details(window);
13976 self.change_selections(Default::default(), window, cx, |s| {
13977 s.move_heads_with(|map, head, goal| {
13978 movement::down(map, head, goal, false, text_layout_details)
13979 })
13980 });
13981 }
13982
13983 pub fn context_menu_first(
13984 &mut self,
13985 _: &ContextMenuFirst,
13986 window: &mut Window,
13987 cx: &mut Context<Self>,
13988 ) {
13989 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13990 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13991 }
13992 }
13993
13994 pub fn context_menu_prev(
13995 &mut self,
13996 _: &ContextMenuPrevious,
13997 window: &mut Window,
13998 cx: &mut Context<Self>,
13999 ) {
14000 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14001 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
14002 }
14003 }
14004
14005 pub fn context_menu_next(
14006 &mut self,
14007 _: &ContextMenuNext,
14008 window: &mut Window,
14009 cx: &mut Context<Self>,
14010 ) {
14011 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14012 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14013 }
14014 }
14015
14016 pub fn context_menu_last(
14017 &mut self,
14018 _: &ContextMenuLast,
14019 window: &mut Window,
14020 cx: &mut Context<Self>,
14021 ) {
14022 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14023 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14024 }
14025 }
14026
14027 pub fn signature_help_prev(
14028 &mut self,
14029 _: &SignatureHelpPrevious,
14030 _: &mut Window,
14031 cx: &mut Context<Self>,
14032 ) {
14033 if let Some(popover) = self.signature_help_state.popover_mut() {
14034 if popover.current_signature == 0 {
14035 popover.current_signature = popover.signatures.len() - 1;
14036 } else {
14037 popover.current_signature -= 1;
14038 }
14039 cx.notify();
14040 }
14041 }
14042
14043 pub fn signature_help_next(
14044 &mut self,
14045 _: &SignatureHelpNext,
14046 _: &mut Window,
14047 cx: &mut Context<Self>,
14048 ) {
14049 if let Some(popover) = self.signature_help_state.popover_mut() {
14050 if popover.current_signature + 1 == popover.signatures.len() {
14051 popover.current_signature = 0;
14052 } else {
14053 popover.current_signature += 1;
14054 }
14055 cx.notify();
14056 }
14057 }
14058
14059 pub fn move_to_previous_word_start(
14060 &mut self,
14061 _: &MoveToPreviousWordStart,
14062 window: &mut Window,
14063 cx: &mut Context<Self>,
14064 ) {
14065 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14066 self.change_selections(Default::default(), window, cx, |s| {
14067 s.move_cursors_with(|map, head, _| {
14068 (
14069 movement::previous_word_start(map, head),
14070 SelectionGoal::None,
14071 )
14072 });
14073 })
14074 }
14075
14076 pub fn move_to_previous_subword_start(
14077 &mut self,
14078 _: &MoveToPreviousSubwordStart,
14079 window: &mut Window,
14080 cx: &mut Context<Self>,
14081 ) {
14082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14083 self.change_selections(Default::default(), window, cx, |s| {
14084 s.move_cursors_with(|map, head, _| {
14085 (
14086 movement::previous_subword_start(map, head),
14087 SelectionGoal::None,
14088 )
14089 });
14090 })
14091 }
14092
14093 pub fn select_to_previous_word_start(
14094 &mut self,
14095 _: &SelectToPreviousWordStart,
14096 window: &mut Window,
14097 cx: &mut Context<Self>,
14098 ) {
14099 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14100 self.change_selections(Default::default(), window, cx, |s| {
14101 s.move_heads_with(|map, head, _| {
14102 (
14103 movement::previous_word_start(map, head),
14104 SelectionGoal::None,
14105 )
14106 });
14107 })
14108 }
14109
14110 pub fn select_to_previous_subword_start(
14111 &mut self,
14112 _: &SelectToPreviousSubwordStart,
14113 window: &mut Window,
14114 cx: &mut Context<Self>,
14115 ) {
14116 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14117 self.change_selections(Default::default(), window, cx, |s| {
14118 s.move_heads_with(|map, head, _| {
14119 (
14120 movement::previous_subword_start(map, head),
14121 SelectionGoal::None,
14122 )
14123 });
14124 })
14125 }
14126
14127 pub fn delete_to_previous_word_start(
14128 &mut self,
14129 action: &DeleteToPreviousWordStart,
14130 window: &mut Window,
14131 cx: &mut Context<Self>,
14132 ) {
14133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14134 self.transact(window, cx, |this, window, cx| {
14135 this.select_autoclose_pair(window, cx);
14136 this.change_selections(Default::default(), window, cx, |s| {
14137 s.move_with(|map, selection| {
14138 if selection.is_empty() {
14139 let mut cursor = if action.ignore_newlines {
14140 movement::previous_word_start(map, selection.head())
14141 } else {
14142 movement::previous_word_start_or_newline(map, selection.head())
14143 };
14144 cursor = movement::adjust_greedy_deletion(
14145 map,
14146 selection.head(),
14147 cursor,
14148 action.ignore_brackets,
14149 );
14150 selection.set_head(cursor, SelectionGoal::None);
14151 }
14152 });
14153 });
14154 this.insert("", window, cx);
14155 });
14156 }
14157
14158 pub fn delete_to_previous_subword_start(
14159 &mut self,
14160 _: &DeleteToPreviousSubwordStart,
14161 window: &mut Window,
14162 cx: &mut Context<Self>,
14163 ) {
14164 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14165 self.transact(window, cx, |this, window, cx| {
14166 this.select_autoclose_pair(window, cx);
14167 this.change_selections(Default::default(), window, cx, |s| {
14168 s.move_with(|map, selection| {
14169 if selection.is_empty() {
14170 let mut cursor = movement::previous_subword_start(map, selection.head());
14171 cursor =
14172 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14173 selection.set_head(cursor, SelectionGoal::None);
14174 }
14175 });
14176 });
14177 this.insert("", window, cx);
14178 });
14179 }
14180
14181 pub fn move_to_next_word_end(
14182 &mut self,
14183 _: &MoveToNextWordEnd,
14184 window: &mut Window,
14185 cx: &mut Context<Self>,
14186 ) {
14187 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14188 self.change_selections(Default::default(), window, cx, |s| {
14189 s.move_cursors_with(|map, head, _| {
14190 (movement::next_word_end(map, head), SelectionGoal::None)
14191 });
14192 })
14193 }
14194
14195 pub fn move_to_next_subword_end(
14196 &mut self,
14197 _: &MoveToNextSubwordEnd,
14198 window: &mut Window,
14199 cx: &mut Context<Self>,
14200 ) {
14201 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14202 self.change_selections(Default::default(), window, cx, |s| {
14203 s.move_cursors_with(|map, head, _| {
14204 (movement::next_subword_end(map, head), SelectionGoal::None)
14205 });
14206 })
14207 }
14208
14209 pub fn select_to_next_word_end(
14210 &mut self,
14211 _: &SelectToNextWordEnd,
14212 window: &mut Window,
14213 cx: &mut Context<Self>,
14214 ) {
14215 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14216 self.change_selections(Default::default(), window, cx, |s| {
14217 s.move_heads_with(|map, head, _| {
14218 (movement::next_word_end(map, head), SelectionGoal::None)
14219 });
14220 })
14221 }
14222
14223 pub fn select_to_next_subword_end(
14224 &mut self,
14225 _: &SelectToNextSubwordEnd,
14226 window: &mut Window,
14227 cx: &mut Context<Self>,
14228 ) {
14229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14230 self.change_selections(Default::default(), window, cx, |s| {
14231 s.move_heads_with(|map, head, _| {
14232 (movement::next_subword_end(map, head), SelectionGoal::None)
14233 });
14234 })
14235 }
14236
14237 pub fn delete_to_next_word_end(
14238 &mut self,
14239 action: &DeleteToNextWordEnd,
14240 window: &mut Window,
14241 cx: &mut Context<Self>,
14242 ) {
14243 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14244 self.transact(window, cx, |this, window, cx| {
14245 this.change_selections(Default::default(), window, cx, |s| {
14246 s.move_with(|map, selection| {
14247 if selection.is_empty() {
14248 let mut cursor = if action.ignore_newlines {
14249 movement::next_word_end(map, selection.head())
14250 } else {
14251 movement::next_word_end_or_newline(map, selection.head())
14252 };
14253 cursor = movement::adjust_greedy_deletion(
14254 map,
14255 selection.head(),
14256 cursor,
14257 action.ignore_brackets,
14258 );
14259 selection.set_head(cursor, SelectionGoal::None);
14260 }
14261 });
14262 });
14263 this.insert("", window, cx);
14264 });
14265 }
14266
14267 pub fn delete_to_next_subword_end(
14268 &mut self,
14269 _: &DeleteToNextSubwordEnd,
14270 window: &mut Window,
14271 cx: &mut Context<Self>,
14272 ) {
14273 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14274 self.transact(window, cx, |this, window, cx| {
14275 this.change_selections(Default::default(), window, cx, |s| {
14276 s.move_with(|map, selection| {
14277 if selection.is_empty() {
14278 let mut cursor = movement::next_subword_end(map, selection.head());
14279 cursor =
14280 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14281 selection.set_head(cursor, SelectionGoal::None);
14282 }
14283 });
14284 });
14285 this.insert("", window, cx);
14286 });
14287 }
14288
14289 pub fn move_to_beginning_of_line(
14290 &mut self,
14291 action: &MoveToBeginningOfLine,
14292 window: &mut Window,
14293 cx: &mut Context<Self>,
14294 ) {
14295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14296 self.change_selections(Default::default(), window, cx, |s| {
14297 s.move_cursors_with(|map, head, _| {
14298 (
14299 movement::indented_line_beginning(
14300 map,
14301 head,
14302 action.stop_at_soft_wraps,
14303 action.stop_at_indent,
14304 ),
14305 SelectionGoal::None,
14306 )
14307 });
14308 })
14309 }
14310
14311 pub fn select_to_beginning_of_line(
14312 &mut self,
14313 action: &SelectToBeginningOfLine,
14314 window: &mut Window,
14315 cx: &mut Context<Self>,
14316 ) {
14317 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14318 self.change_selections(Default::default(), window, cx, |s| {
14319 s.move_heads_with(|map, head, _| {
14320 (
14321 movement::indented_line_beginning(
14322 map,
14323 head,
14324 action.stop_at_soft_wraps,
14325 action.stop_at_indent,
14326 ),
14327 SelectionGoal::None,
14328 )
14329 });
14330 });
14331 }
14332
14333 pub fn delete_to_beginning_of_line(
14334 &mut self,
14335 action: &DeleteToBeginningOfLine,
14336 window: &mut Window,
14337 cx: &mut Context<Self>,
14338 ) {
14339 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14340 self.transact(window, cx, |this, window, cx| {
14341 this.change_selections(Default::default(), window, cx, |s| {
14342 s.move_with(|_, selection| {
14343 selection.reversed = true;
14344 });
14345 });
14346
14347 this.select_to_beginning_of_line(
14348 &SelectToBeginningOfLine {
14349 stop_at_soft_wraps: false,
14350 stop_at_indent: action.stop_at_indent,
14351 },
14352 window,
14353 cx,
14354 );
14355 this.backspace(&Backspace, window, cx);
14356 });
14357 }
14358
14359 pub fn move_to_end_of_line(
14360 &mut self,
14361 action: &MoveToEndOfLine,
14362 window: &mut Window,
14363 cx: &mut Context<Self>,
14364 ) {
14365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14366 self.change_selections(Default::default(), window, cx, |s| {
14367 s.move_cursors_with(|map, head, _| {
14368 (
14369 movement::line_end(map, head, action.stop_at_soft_wraps),
14370 SelectionGoal::None,
14371 )
14372 });
14373 })
14374 }
14375
14376 pub fn select_to_end_of_line(
14377 &mut self,
14378 action: &SelectToEndOfLine,
14379 window: &mut Window,
14380 cx: &mut Context<Self>,
14381 ) {
14382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14383 self.change_selections(Default::default(), window, cx, |s| {
14384 s.move_heads_with(|map, head, _| {
14385 (
14386 movement::line_end(map, head, action.stop_at_soft_wraps),
14387 SelectionGoal::None,
14388 )
14389 });
14390 })
14391 }
14392
14393 pub fn delete_to_end_of_line(
14394 &mut self,
14395 _: &DeleteToEndOfLine,
14396 window: &mut Window,
14397 cx: &mut Context<Self>,
14398 ) {
14399 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14400 self.transact(window, cx, |this, window, cx| {
14401 this.select_to_end_of_line(
14402 &SelectToEndOfLine {
14403 stop_at_soft_wraps: false,
14404 },
14405 window,
14406 cx,
14407 );
14408 this.delete(&Delete, window, cx);
14409 });
14410 }
14411
14412 pub fn cut_to_end_of_line(
14413 &mut self,
14414 action: &CutToEndOfLine,
14415 window: &mut Window,
14416 cx: &mut Context<Self>,
14417 ) {
14418 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14419 self.transact(window, cx, |this, window, cx| {
14420 this.select_to_end_of_line(
14421 &SelectToEndOfLine {
14422 stop_at_soft_wraps: false,
14423 },
14424 window,
14425 cx,
14426 );
14427 if !action.stop_at_newlines {
14428 this.change_selections(Default::default(), window, cx, |s| {
14429 s.move_with(|_, sel| {
14430 if sel.is_empty() {
14431 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14432 }
14433 });
14434 });
14435 }
14436 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14437 let item = this.cut_common(false, window, cx);
14438 cx.write_to_clipboard(item);
14439 });
14440 }
14441
14442 pub fn move_to_start_of_paragraph(
14443 &mut self,
14444 _: &MoveToStartOfParagraph,
14445 window: &mut Window,
14446 cx: &mut Context<Self>,
14447 ) {
14448 if matches!(self.mode, EditorMode::SingleLine) {
14449 cx.propagate();
14450 return;
14451 }
14452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14453 self.change_selections(Default::default(), window, cx, |s| {
14454 s.move_with(|map, selection| {
14455 selection.collapse_to(
14456 movement::start_of_paragraph(map, selection.head(), 1),
14457 SelectionGoal::None,
14458 )
14459 });
14460 })
14461 }
14462
14463 pub fn move_to_end_of_paragraph(
14464 &mut self,
14465 _: &MoveToEndOfParagraph,
14466 window: &mut Window,
14467 cx: &mut Context<Self>,
14468 ) {
14469 if matches!(self.mode, EditorMode::SingleLine) {
14470 cx.propagate();
14471 return;
14472 }
14473 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14474 self.change_selections(Default::default(), window, cx, |s| {
14475 s.move_with(|map, selection| {
14476 selection.collapse_to(
14477 movement::end_of_paragraph(map, selection.head(), 1),
14478 SelectionGoal::None,
14479 )
14480 });
14481 })
14482 }
14483
14484 pub fn select_to_start_of_paragraph(
14485 &mut self,
14486 _: &SelectToStartOfParagraph,
14487 window: &mut Window,
14488 cx: &mut Context<Self>,
14489 ) {
14490 if matches!(self.mode, EditorMode::SingleLine) {
14491 cx.propagate();
14492 return;
14493 }
14494 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14495 self.change_selections(Default::default(), window, cx, |s| {
14496 s.move_heads_with(|map, head, _| {
14497 (
14498 movement::start_of_paragraph(map, head, 1),
14499 SelectionGoal::None,
14500 )
14501 });
14502 })
14503 }
14504
14505 pub fn select_to_end_of_paragraph(
14506 &mut self,
14507 _: &SelectToEndOfParagraph,
14508 window: &mut Window,
14509 cx: &mut Context<Self>,
14510 ) {
14511 if matches!(self.mode, EditorMode::SingleLine) {
14512 cx.propagate();
14513 return;
14514 }
14515 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14516 self.change_selections(Default::default(), window, cx, |s| {
14517 s.move_heads_with(|map, head, _| {
14518 (
14519 movement::end_of_paragraph(map, head, 1),
14520 SelectionGoal::None,
14521 )
14522 });
14523 })
14524 }
14525
14526 pub fn move_to_start_of_excerpt(
14527 &mut self,
14528 _: &MoveToStartOfExcerpt,
14529 window: &mut Window,
14530 cx: &mut Context<Self>,
14531 ) {
14532 if matches!(self.mode, EditorMode::SingleLine) {
14533 cx.propagate();
14534 return;
14535 }
14536 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14537 self.change_selections(Default::default(), window, cx, |s| {
14538 s.move_with(|map, selection| {
14539 selection.collapse_to(
14540 movement::start_of_excerpt(
14541 map,
14542 selection.head(),
14543 workspace::searchable::Direction::Prev,
14544 ),
14545 SelectionGoal::None,
14546 )
14547 });
14548 })
14549 }
14550
14551 pub fn move_to_start_of_next_excerpt(
14552 &mut self,
14553 _: &MoveToStartOfNextExcerpt,
14554 window: &mut Window,
14555 cx: &mut Context<Self>,
14556 ) {
14557 if matches!(self.mode, EditorMode::SingleLine) {
14558 cx.propagate();
14559 return;
14560 }
14561
14562 self.change_selections(Default::default(), window, cx, |s| {
14563 s.move_with(|map, selection| {
14564 selection.collapse_to(
14565 movement::start_of_excerpt(
14566 map,
14567 selection.head(),
14568 workspace::searchable::Direction::Next,
14569 ),
14570 SelectionGoal::None,
14571 )
14572 });
14573 })
14574 }
14575
14576 pub fn move_to_end_of_excerpt(
14577 &mut self,
14578 _: &MoveToEndOfExcerpt,
14579 window: &mut Window,
14580 cx: &mut Context<Self>,
14581 ) {
14582 if matches!(self.mode, EditorMode::SingleLine) {
14583 cx.propagate();
14584 return;
14585 }
14586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14587 self.change_selections(Default::default(), window, cx, |s| {
14588 s.move_with(|map, selection| {
14589 selection.collapse_to(
14590 movement::end_of_excerpt(
14591 map,
14592 selection.head(),
14593 workspace::searchable::Direction::Next,
14594 ),
14595 SelectionGoal::None,
14596 )
14597 });
14598 })
14599 }
14600
14601 pub fn move_to_end_of_previous_excerpt(
14602 &mut self,
14603 _: &MoveToEndOfPreviousExcerpt,
14604 window: &mut Window,
14605 cx: &mut Context<Self>,
14606 ) {
14607 if matches!(self.mode, EditorMode::SingleLine) {
14608 cx.propagate();
14609 return;
14610 }
14611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14612 self.change_selections(Default::default(), window, cx, |s| {
14613 s.move_with(|map, selection| {
14614 selection.collapse_to(
14615 movement::end_of_excerpt(
14616 map,
14617 selection.head(),
14618 workspace::searchable::Direction::Prev,
14619 ),
14620 SelectionGoal::None,
14621 )
14622 });
14623 })
14624 }
14625
14626 pub fn select_to_start_of_excerpt(
14627 &mut self,
14628 _: &SelectToStartOfExcerpt,
14629 window: &mut Window,
14630 cx: &mut Context<Self>,
14631 ) {
14632 if matches!(self.mode, EditorMode::SingleLine) {
14633 cx.propagate();
14634 return;
14635 }
14636 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14637 self.change_selections(Default::default(), window, cx, |s| {
14638 s.move_heads_with(|map, head, _| {
14639 (
14640 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14641 SelectionGoal::None,
14642 )
14643 });
14644 })
14645 }
14646
14647 pub fn select_to_start_of_next_excerpt(
14648 &mut self,
14649 _: &SelectToStartOfNextExcerpt,
14650 window: &mut Window,
14651 cx: &mut Context<Self>,
14652 ) {
14653 if matches!(self.mode, EditorMode::SingleLine) {
14654 cx.propagate();
14655 return;
14656 }
14657 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14658 self.change_selections(Default::default(), window, cx, |s| {
14659 s.move_heads_with(|map, head, _| {
14660 (
14661 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14662 SelectionGoal::None,
14663 )
14664 });
14665 })
14666 }
14667
14668 pub fn select_to_end_of_excerpt(
14669 &mut self,
14670 _: &SelectToEndOfExcerpt,
14671 window: &mut Window,
14672 cx: &mut Context<Self>,
14673 ) {
14674 if matches!(self.mode, EditorMode::SingleLine) {
14675 cx.propagate();
14676 return;
14677 }
14678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14679 self.change_selections(Default::default(), window, cx, |s| {
14680 s.move_heads_with(|map, head, _| {
14681 (
14682 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14683 SelectionGoal::None,
14684 )
14685 });
14686 })
14687 }
14688
14689 pub fn select_to_end_of_previous_excerpt(
14690 &mut self,
14691 _: &SelectToEndOfPreviousExcerpt,
14692 window: &mut Window,
14693 cx: &mut Context<Self>,
14694 ) {
14695 if matches!(self.mode, EditorMode::SingleLine) {
14696 cx.propagate();
14697 return;
14698 }
14699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14700 self.change_selections(Default::default(), window, cx, |s| {
14701 s.move_heads_with(|map, head, _| {
14702 (
14703 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14704 SelectionGoal::None,
14705 )
14706 });
14707 })
14708 }
14709
14710 pub fn move_to_beginning(
14711 &mut self,
14712 _: &MoveToBeginning,
14713 window: &mut Window,
14714 cx: &mut Context<Self>,
14715 ) {
14716 if matches!(self.mode, EditorMode::SingleLine) {
14717 cx.propagate();
14718 return;
14719 }
14720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14721 self.change_selections(Default::default(), window, cx, |s| {
14722 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14723 });
14724 }
14725
14726 pub fn select_to_beginning(
14727 &mut self,
14728 _: &SelectToBeginning,
14729 window: &mut Window,
14730 cx: &mut Context<Self>,
14731 ) {
14732 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14733 selection.set_head(Point::zero(), SelectionGoal::None);
14734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14735 self.change_selections(Default::default(), window, cx, |s| {
14736 s.select(vec![selection]);
14737 });
14738 }
14739
14740 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14741 if matches!(self.mode, EditorMode::SingleLine) {
14742 cx.propagate();
14743 return;
14744 }
14745 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14746 let cursor = self.buffer.read(cx).read(cx).len();
14747 self.change_selections(Default::default(), window, cx, |s| {
14748 s.select_ranges(vec![cursor..cursor])
14749 });
14750 }
14751
14752 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14753 self.nav_history = nav_history;
14754 }
14755
14756 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14757 self.nav_history.as_ref()
14758 }
14759
14760 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14761 self.push_to_nav_history(
14762 self.selections.newest_anchor().head(),
14763 None,
14764 false,
14765 true,
14766 cx,
14767 );
14768 }
14769
14770 fn push_to_nav_history(
14771 &mut self,
14772 cursor_anchor: Anchor,
14773 new_position: Option<Point>,
14774 is_deactivate: bool,
14775 always: bool,
14776 cx: &mut Context<Self>,
14777 ) {
14778 if let Some(nav_history) = self.nav_history.as_mut() {
14779 let buffer = self.buffer.read(cx).read(cx);
14780 let cursor_position = cursor_anchor.to_point(&buffer);
14781 let scroll_state = self.scroll_manager.anchor();
14782 let scroll_top_row = scroll_state.top_row(&buffer);
14783 drop(buffer);
14784
14785 if let Some(new_position) = new_position {
14786 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14787 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14788 return;
14789 }
14790 }
14791
14792 nav_history.push(
14793 Some(NavigationData {
14794 cursor_anchor,
14795 cursor_position,
14796 scroll_anchor: scroll_state,
14797 scroll_top_row,
14798 }),
14799 cx,
14800 );
14801 cx.emit(EditorEvent::PushedToNavHistory {
14802 anchor: cursor_anchor,
14803 is_deactivate,
14804 })
14805 }
14806 }
14807
14808 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14810 let buffer = self.buffer.read(cx).snapshot(cx);
14811 let mut selection = self
14812 .selections
14813 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14814 selection.set_head(buffer.len(), SelectionGoal::None);
14815 self.change_selections(Default::default(), window, cx, |s| {
14816 s.select(vec![selection]);
14817 });
14818 }
14819
14820 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14821 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14822 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14823 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14824 });
14825 }
14826
14827 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14828 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14829 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14830 let mut selections = self.selections.all::<Point>(&display_map);
14831 let max_point = display_map.buffer_snapshot().max_point();
14832 for selection in &mut selections {
14833 let rows = selection.spanned_rows(true, &display_map);
14834 selection.start = Point::new(rows.start.0, 0);
14835 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14836 selection.reversed = false;
14837 }
14838 self.change_selections(Default::default(), window, cx, |s| {
14839 s.select(selections);
14840 });
14841 }
14842
14843 pub fn split_selection_into_lines(
14844 &mut self,
14845 action: &SplitSelectionIntoLines,
14846 window: &mut Window,
14847 cx: &mut Context<Self>,
14848 ) {
14849 let selections = self
14850 .selections
14851 .all::<Point>(&self.display_snapshot(cx))
14852 .into_iter()
14853 .map(|selection| selection.start..selection.end)
14854 .collect::<Vec<_>>();
14855 self.unfold_ranges(&selections, true, true, cx);
14856
14857 let mut new_selection_ranges = Vec::new();
14858 {
14859 let buffer = self.buffer.read(cx).read(cx);
14860 for selection in selections {
14861 for row in selection.start.row..selection.end.row {
14862 let line_start = Point::new(row, 0);
14863 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14864
14865 if action.keep_selections {
14866 // Keep the selection range for each line
14867 let selection_start = if row == selection.start.row {
14868 selection.start
14869 } else {
14870 line_start
14871 };
14872 new_selection_ranges.push(selection_start..line_end);
14873 } else {
14874 // Collapse to cursor at end of line
14875 new_selection_ranges.push(line_end..line_end);
14876 }
14877 }
14878
14879 let is_multiline_selection = selection.start.row != selection.end.row;
14880 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14881 // so this action feels more ergonomic when paired with other selection operations
14882 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14883 if !should_skip_last {
14884 if action.keep_selections {
14885 if is_multiline_selection {
14886 let line_start = Point::new(selection.end.row, 0);
14887 new_selection_ranges.push(line_start..selection.end);
14888 } else {
14889 new_selection_ranges.push(selection.start..selection.end);
14890 }
14891 } else {
14892 new_selection_ranges.push(selection.end..selection.end);
14893 }
14894 }
14895 }
14896 }
14897 self.change_selections(Default::default(), window, cx, |s| {
14898 s.select_ranges(new_selection_ranges);
14899 });
14900 }
14901
14902 pub fn add_selection_above(
14903 &mut self,
14904 action: &AddSelectionAbove,
14905 window: &mut Window,
14906 cx: &mut Context<Self>,
14907 ) {
14908 self.add_selection(true, action.skip_soft_wrap, window, cx);
14909 }
14910
14911 pub fn add_selection_below(
14912 &mut self,
14913 action: &AddSelectionBelow,
14914 window: &mut Window,
14915 cx: &mut Context<Self>,
14916 ) {
14917 self.add_selection(false, action.skip_soft_wrap, window, cx);
14918 }
14919
14920 fn add_selection(
14921 &mut self,
14922 above: bool,
14923 skip_soft_wrap: bool,
14924 window: &mut Window,
14925 cx: &mut Context<Self>,
14926 ) {
14927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14928
14929 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14930 let all_selections = self.selections.all::<Point>(&display_map);
14931 let text_layout_details = self.text_layout_details(window);
14932
14933 let (mut columnar_selections, new_selections_to_columnarize) = {
14934 if let Some(state) = self.add_selections_state.as_ref() {
14935 let columnar_selection_ids: HashSet<_> = state
14936 .groups
14937 .iter()
14938 .flat_map(|group| group.stack.iter())
14939 .copied()
14940 .collect();
14941
14942 all_selections
14943 .into_iter()
14944 .partition(|s| columnar_selection_ids.contains(&s.id))
14945 } else {
14946 (Vec::new(), all_selections)
14947 }
14948 };
14949
14950 let mut state = self
14951 .add_selections_state
14952 .take()
14953 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14954
14955 for selection in new_selections_to_columnarize {
14956 let range = selection.display_range(&display_map).sorted();
14957 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14958 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14959 let positions = start_x.min(end_x)..start_x.max(end_x);
14960 let mut stack = Vec::new();
14961 for row in range.start.row().0..=range.end.row().0 {
14962 if let Some(selection) = self.selections.build_columnar_selection(
14963 &display_map,
14964 DisplayRow(row),
14965 &positions,
14966 selection.reversed,
14967 &text_layout_details,
14968 ) {
14969 stack.push(selection.id);
14970 columnar_selections.push(selection);
14971 }
14972 }
14973 if !stack.is_empty() {
14974 if above {
14975 stack.reverse();
14976 }
14977 state.groups.push(AddSelectionsGroup { above, stack });
14978 }
14979 }
14980
14981 let mut final_selections = Vec::new();
14982 let end_row = if above {
14983 DisplayRow(0)
14984 } else {
14985 display_map.max_point().row()
14986 };
14987
14988 let mut last_added_item_per_group = HashMap::default();
14989 for group in state.groups.iter_mut() {
14990 if let Some(last_id) = group.stack.last() {
14991 last_added_item_per_group.insert(*last_id, group);
14992 }
14993 }
14994
14995 for selection in columnar_selections {
14996 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14997 if above == group.above {
14998 let range = selection.display_range(&display_map).sorted();
14999 debug_assert_eq!(range.start.row(), range.end.row());
15000 let mut row = range.start.row();
15001 let positions =
15002 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
15003 Pixels::from(start)..Pixels::from(end)
15004 } else {
15005 let start_x =
15006 display_map.x_for_display_point(range.start, &text_layout_details);
15007 let end_x =
15008 display_map.x_for_display_point(range.end, &text_layout_details);
15009 start_x.min(end_x)..start_x.max(end_x)
15010 };
15011
15012 let mut maybe_new_selection = None;
15013 let direction = if above { -1 } else { 1 };
15014
15015 while row != end_row {
15016 if skip_soft_wrap {
15017 row = display_map
15018 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
15019 .row();
15020 } else if above {
15021 row.0 -= 1;
15022 } else {
15023 row.0 += 1;
15024 }
15025
15026 if let Some(new_selection) = self.selections.build_columnar_selection(
15027 &display_map,
15028 row,
15029 &positions,
15030 selection.reversed,
15031 &text_layout_details,
15032 ) {
15033 maybe_new_selection = Some(new_selection);
15034 break;
15035 }
15036 }
15037
15038 if let Some(new_selection) = maybe_new_selection {
15039 group.stack.push(new_selection.id);
15040 if above {
15041 final_selections.push(new_selection);
15042 final_selections.push(selection);
15043 } else {
15044 final_selections.push(selection);
15045 final_selections.push(new_selection);
15046 }
15047 } else {
15048 final_selections.push(selection);
15049 }
15050 } else {
15051 group.stack.pop();
15052 }
15053 } else {
15054 final_selections.push(selection);
15055 }
15056 }
15057
15058 self.change_selections(Default::default(), window, cx, |s| {
15059 s.select(final_selections);
15060 });
15061
15062 let final_selection_ids: HashSet<_> = self
15063 .selections
15064 .all::<Point>(&display_map)
15065 .iter()
15066 .map(|s| s.id)
15067 .collect();
15068 state.groups.retain_mut(|group| {
15069 // selections might get merged above so we remove invalid items from stacks
15070 group.stack.retain(|id| final_selection_ids.contains(id));
15071
15072 // single selection in stack can be treated as initial state
15073 group.stack.len() > 1
15074 });
15075
15076 if !state.groups.is_empty() {
15077 self.add_selections_state = Some(state);
15078 }
15079 }
15080
15081 pub fn insert_snippet_at_selections(
15082 &mut self,
15083 action: &InsertSnippet,
15084 window: &mut Window,
15085 cx: &mut Context<Self>,
15086 ) {
15087 self.try_insert_snippet_at_selections(action, window, cx)
15088 .log_err();
15089 }
15090
15091 fn try_insert_snippet_at_selections(
15092 &mut self,
15093 action: &InsertSnippet,
15094 window: &mut Window,
15095 cx: &mut Context<Self>,
15096 ) -> Result<()> {
15097 let insertion_ranges = self
15098 .selections
15099 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15100 .into_iter()
15101 .map(|selection| selection.range())
15102 .collect_vec();
15103
15104 let snippet = if let Some(snippet_body) = &action.snippet {
15105 if action.language.is_none() && action.name.is_none() {
15106 Snippet::parse(snippet_body)?
15107 } else {
15108 bail!("`snippet` is mutually exclusive with `language` and `name`")
15109 }
15110 } else if let Some(name) = &action.name {
15111 let project = self.project().context("no project")?;
15112 let snippet_store = project.read(cx).snippets().read(cx);
15113 let snippet = snippet_store
15114 .snippets_for(action.language.clone(), cx)
15115 .into_iter()
15116 .find(|snippet| snippet.name == *name)
15117 .context("snippet not found")?;
15118 Snippet::parse(&snippet.body)?
15119 } else {
15120 // todo(andrew): open modal to select snippet
15121 bail!("`name` or `snippet` is required")
15122 };
15123
15124 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15125 }
15126
15127 fn select_match_ranges(
15128 &mut self,
15129 range: Range<MultiBufferOffset>,
15130 reversed: bool,
15131 replace_newest: bool,
15132 auto_scroll: Option<Autoscroll>,
15133 window: &mut Window,
15134 cx: &mut Context<Editor>,
15135 ) {
15136 self.unfold_ranges(
15137 std::slice::from_ref(&range),
15138 false,
15139 auto_scroll.is_some(),
15140 cx,
15141 );
15142 let effects = if let Some(scroll) = auto_scroll {
15143 SelectionEffects::scroll(scroll)
15144 } else {
15145 SelectionEffects::no_scroll()
15146 };
15147 self.change_selections(effects, window, cx, |s| {
15148 if replace_newest {
15149 s.delete(s.newest_anchor().id);
15150 }
15151 if reversed {
15152 s.insert_range(range.end..range.start);
15153 } else {
15154 s.insert_range(range);
15155 }
15156 });
15157 }
15158
15159 pub fn select_next_match_internal(
15160 &mut self,
15161 display_map: &DisplaySnapshot,
15162 replace_newest: bool,
15163 autoscroll: Option<Autoscroll>,
15164 window: &mut Window,
15165 cx: &mut Context<Self>,
15166 ) -> Result<()> {
15167 let buffer = display_map.buffer_snapshot();
15168 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15169 if let Some(mut select_next_state) = self.select_next_state.take() {
15170 let query = &select_next_state.query;
15171 if !select_next_state.done {
15172 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15173 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15174 let mut next_selected_range = None;
15175
15176 let bytes_after_last_selection =
15177 buffer.bytes_in_range(last_selection.end..buffer.len());
15178 let bytes_before_first_selection =
15179 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15180 let query_matches = query
15181 .stream_find_iter(bytes_after_last_selection)
15182 .map(|result| (last_selection.end, result))
15183 .chain(
15184 query
15185 .stream_find_iter(bytes_before_first_selection)
15186 .map(|result| (MultiBufferOffset(0), result)),
15187 );
15188
15189 for (start_offset, query_match) in query_matches {
15190 let query_match = query_match.unwrap(); // can only fail due to I/O
15191 let offset_range =
15192 start_offset + query_match.start()..start_offset + query_match.end();
15193
15194 if !select_next_state.wordwise
15195 || (!buffer.is_inside_word(offset_range.start, None)
15196 && !buffer.is_inside_word(offset_range.end, None))
15197 {
15198 let idx = selections
15199 .partition_point(|selection| selection.end <= offset_range.start);
15200 let overlaps = selections
15201 .get(idx)
15202 .map_or(false, |selection| selection.start < offset_range.end);
15203
15204 if !overlaps {
15205 next_selected_range = Some(offset_range);
15206 break;
15207 }
15208 }
15209 }
15210
15211 if let Some(next_selected_range) = next_selected_range {
15212 self.select_match_ranges(
15213 next_selected_range,
15214 last_selection.reversed,
15215 replace_newest,
15216 autoscroll,
15217 window,
15218 cx,
15219 );
15220 } else {
15221 select_next_state.done = true;
15222 }
15223 }
15224
15225 self.select_next_state = Some(select_next_state);
15226 } else {
15227 let mut only_carets = true;
15228 let mut same_text_selected = true;
15229 let mut selected_text = None;
15230
15231 let mut selections_iter = selections.iter().peekable();
15232 while let Some(selection) = selections_iter.next() {
15233 if selection.start != selection.end {
15234 only_carets = false;
15235 }
15236
15237 if same_text_selected {
15238 if selected_text.is_none() {
15239 selected_text =
15240 Some(buffer.text_for_range(selection.range()).collect::<String>());
15241 }
15242
15243 if let Some(next_selection) = selections_iter.peek() {
15244 if next_selection.len() == selection.len() {
15245 let next_selected_text = buffer
15246 .text_for_range(next_selection.range())
15247 .collect::<String>();
15248 if Some(next_selected_text) != selected_text {
15249 same_text_selected = false;
15250 selected_text = None;
15251 }
15252 } else {
15253 same_text_selected = false;
15254 selected_text = None;
15255 }
15256 }
15257 }
15258 }
15259
15260 if only_carets {
15261 for selection in &mut selections {
15262 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15263 selection.start = word_range.start;
15264 selection.end = word_range.end;
15265 selection.goal = SelectionGoal::None;
15266 selection.reversed = false;
15267 self.select_match_ranges(
15268 selection.start..selection.end,
15269 selection.reversed,
15270 replace_newest,
15271 autoscroll,
15272 window,
15273 cx,
15274 );
15275 }
15276
15277 if selections.len() == 1 {
15278 let selection = selections
15279 .last()
15280 .expect("ensured that there's only one selection");
15281 let query = buffer
15282 .text_for_range(selection.start..selection.end)
15283 .collect::<String>();
15284 let is_empty = query.is_empty();
15285 let select_state = SelectNextState {
15286 query: self.build_query(&[query], cx)?,
15287 wordwise: true,
15288 done: is_empty,
15289 };
15290 self.select_next_state = Some(select_state);
15291 } else {
15292 self.select_next_state = None;
15293 }
15294 } else if let Some(selected_text) = selected_text {
15295 self.select_next_state = Some(SelectNextState {
15296 query: self.build_query(&[selected_text], cx)?,
15297 wordwise: false,
15298 done: false,
15299 });
15300 self.select_next_match_internal(
15301 display_map,
15302 replace_newest,
15303 autoscroll,
15304 window,
15305 cx,
15306 )?;
15307 }
15308 }
15309 Ok(())
15310 }
15311
15312 pub fn select_all_matches(
15313 &mut self,
15314 _action: &SelectAllMatches,
15315 window: &mut Window,
15316 cx: &mut Context<Self>,
15317 ) -> Result<()> {
15318 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15319
15320 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15321
15322 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15323 let Some(select_next_state) = self.select_next_state.as_mut() else {
15324 return Ok(());
15325 };
15326 if select_next_state.done {
15327 return Ok(());
15328 }
15329
15330 let mut new_selections = Vec::new();
15331
15332 let reversed = self
15333 .selections
15334 .oldest::<MultiBufferOffset>(&display_map)
15335 .reversed;
15336 let buffer = display_map.buffer_snapshot();
15337 let query_matches = select_next_state
15338 .query
15339 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15340
15341 for query_match in query_matches.into_iter() {
15342 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15343 let offset_range = if reversed {
15344 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15345 } else {
15346 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15347 };
15348
15349 if !select_next_state.wordwise
15350 || (!buffer.is_inside_word(offset_range.start, None)
15351 && !buffer.is_inside_word(offset_range.end, None))
15352 {
15353 new_selections.push(offset_range.start..offset_range.end);
15354 }
15355 }
15356
15357 select_next_state.done = true;
15358
15359 if new_selections.is_empty() {
15360 log::error!("bug: new_selections is empty in select_all_matches");
15361 return Ok(());
15362 }
15363
15364 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15365 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15366 selections.select_ranges(new_selections)
15367 });
15368
15369 Ok(())
15370 }
15371
15372 pub fn select_next(
15373 &mut self,
15374 action: &SelectNext,
15375 window: &mut Window,
15376 cx: &mut Context<Self>,
15377 ) -> Result<()> {
15378 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15379 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15380 self.select_next_match_internal(
15381 &display_map,
15382 action.replace_newest,
15383 Some(Autoscroll::newest()),
15384 window,
15385 cx,
15386 )?;
15387 Ok(())
15388 }
15389
15390 pub fn select_previous(
15391 &mut self,
15392 action: &SelectPrevious,
15393 window: &mut Window,
15394 cx: &mut Context<Self>,
15395 ) -> Result<()> {
15396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15397 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15398 let buffer = display_map.buffer_snapshot();
15399 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15400 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15401 let query = &select_prev_state.query;
15402 if !select_prev_state.done {
15403 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15404 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15405 let mut next_selected_range = None;
15406 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15407 let bytes_before_last_selection =
15408 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15409 let bytes_after_first_selection =
15410 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15411 let query_matches = query
15412 .stream_find_iter(bytes_before_last_selection)
15413 .map(|result| (last_selection.start, result))
15414 .chain(
15415 query
15416 .stream_find_iter(bytes_after_first_selection)
15417 .map(|result| (buffer.len(), result)),
15418 );
15419 for (end_offset, query_match) in query_matches {
15420 let query_match = query_match.unwrap(); // can only fail due to I/O
15421 let offset_range =
15422 end_offset - query_match.end()..end_offset - query_match.start();
15423
15424 if !select_prev_state.wordwise
15425 || (!buffer.is_inside_word(offset_range.start, None)
15426 && !buffer.is_inside_word(offset_range.end, None))
15427 {
15428 next_selected_range = Some(offset_range);
15429 break;
15430 }
15431 }
15432
15433 if let Some(next_selected_range) = next_selected_range {
15434 self.select_match_ranges(
15435 next_selected_range,
15436 last_selection.reversed,
15437 action.replace_newest,
15438 Some(Autoscroll::newest()),
15439 window,
15440 cx,
15441 );
15442 } else {
15443 select_prev_state.done = true;
15444 }
15445 }
15446
15447 self.select_prev_state = Some(select_prev_state);
15448 } else {
15449 let mut only_carets = true;
15450 let mut same_text_selected = true;
15451 let mut selected_text = None;
15452
15453 let mut selections_iter = selections.iter().peekable();
15454 while let Some(selection) = selections_iter.next() {
15455 if selection.start != selection.end {
15456 only_carets = false;
15457 }
15458
15459 if same_text_selected {
15460 if selected_text.is_none() {
15461 selected_text =
15462 Some(buffer.text_for_range(selection.range()).collect::<String>());
15463 }
15464
15465 if let Some(next_selection) = selections_iter.peek() {
15466 if next_selection.len() == selection.len() {
15467 let next_selected_text = buffer
15468 .text_for_range(next_selection.range())
15469 .collect::<String>();
15470 if Some(next_selected_text) != selected_text {
15471 same_text_selected = false;
15472 selected_text = None;
15473 }
15474 } else {
15475 same_text_selected = false;
15476 selected_text = None;
15477 }
15478 }
15479 }
15480 }
15481
15482 if only_carets {
15483 for selection in &mut selections {
15484 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15485 selection.start = word_range.start;
15486 selection.end = word_range.end;
15487 selection.goal = SelectionGoal::None;
15488 selection.reversed = false;
15489 self.select_match_ranges(
15490 selection.start..selection.end,
15491 selection.reversed,
15492 action.replace_newest,
15493 Some(Autoscroll::newest()),
15494 window,
15495 cx,
15496 );
15497 }
15498 if selections.len() == 1 {
15499 let selection = selections
15500 .last()
15501 .expect("ensured that there's only one selection");
15502 let query = buffer
15503 .text_for_range(selection.start..selection.end)
15504 .collect::<String>();
15505 let is_empty = query.is_empty();
15506 let select_state = SelectNextState {
15507 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15508 wordwise: true,
15509 done: is_empty,
15510 };
15511 self.select_prev_state = Some(select_state);
15512 } else {
15513 self.select_prev_state = None;
15514 }
15515 } else if let Some(selected_text) = selected_text {
15516 self.select_prev_state = Some(SelectNextState {
15517 query: self
15518 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15519 wordwise: false,
15520 done: false,
15521 });
15522 self.select_previous(action, window, cx)?;
15523 }
15524 }
15525 Ok(())
15526 }
15527
15528 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15529 /// setting the case sensitivity based on the global
15530 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15531 /// editor's settings.
15532 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15533 where
15534 I: IntoIterator<Item = P>,
15535 P: AsRef<[u8]>,
15536 {
15537 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15538 || EditorSettings::get_global(cx).search.case_sensitive,
15539 |value| value,
15540 );
15541
15542 let mut builder = AhoCorasickBuilder::new();
15543 builder.ascii_case_insensitive(!case_sensitive);
15544 builder.build(patterns)
15545 }
15546
15547 pub fn find_next_match(
15548 &mut self,
15549 _: &FindNextMatch,
15550 window: &mut Window,
15551 cx: &mut Context<Self>,
15552 ) -> Result<()> {
15553 let selections = self.selections.disjoint_anchors_arc();
15554 match selections.first() {
15555 Some(first) if selections.len() >= 2 => {
15556 self.change_selections(Default::default(), window, cx, |s| {
15557 s.select_ranges([first.range()]);
15558 });
15559 }
15560 _ => self.select_next(
15561 &SelectNext {
15562 replace_newest: true,
15563 },
15564 window,
15565 cx,
15566 )?,
15567 }
15568 Ok(())
15569 }
15570
15571 pub fn find_previous_match(
15572 &mut self,
15573 _: &FindPreviousMatch,
15574 window: &mut Window,
15575 cx: &mut Context<Self>,
15576 ) -> Result<()> {
15577 let selections = self.selections.disjoint_anchors_arc();
15578 match selections.last() {
15579 Some(last) if selections.len() >= 2 => {
15580 self.change_selections(Default::default(), window, cx, |s| {
15581 s.select_ranges([last.range()]);
15582 });
15583 }
15584 _ => self.select_previous(
15585 &SelectPrevious {
15586 replace_newest: true,
15587 },
15588 window,
15589 cx,
15590 )?,
15591 }
15592 Ok(())
15593 }
15594
15595 pub fn toggle_comments(
15596 &mut self,
15597 action: &ToggleComments,
15598 window: &mut Window,
15599 cx: &mut Context<Self>,
15600 ) {
15601 if self.read_only(cx) {
15602 return;
15603 }
15604 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15605 let text_layout_details = &self.text_layout_details(window);
15606 self.transact(window, cx, |this, window, cx| {
15607 let mut selections = this
15608 .selections
15609 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15610 let mut edits = Vec::new();
15611 let mut selection_edit_ranges = Vec::new();
15612 let mut last_toggled_row = None;
15613 let snapshot = this.buffer.read(cx).read(cx);
15614 let empty_str: Arc<str> = Arc::default();
15615 let mut suffixes_inserted = Vec::new();
15616 let ignore_indent = action.ignore_indent;
15617
15618 fn comment_prefix_range(
15619 snapshot: &MultiBufferSnapshot,
15620 row: MultiBufferRow,
15621 comment_prefix: &str,
15622 comment_prefix_whitespace: &str,
15623 ignore_indent: bool,
15624 ) -> Range<Point> {
15625 let indent_size = if ignore_indent {
15626 0
15627 } else {
15628 snapshot.indent_size_for_line(row).len
15629 };
15630
15631 let start = Point::new(row.0, indent_size);
15632
15633 let mut line_bytes = snapshot
15634 .bytes_in_range(start..snapshot.max_point())
15635 .flatten()
15636 .copied();
15637
15638 // If this line currently begins with the line comment prefix, then record
15639 // the range containing the prefix.
15640 if line_bytes
15641 .by_ref()
15642 .take(comment_prefix.len())
15643 .eq(comment_prefix.bytes())
15644 {
15645 // Include any whitespace that matches the comment prefix.
15646 let matching_whitespace_len = line_bytes
15647 .zip(comment_prefix_whitespace.bytes())
15648 .take_while(|(a, b)| a == b)
15649 .count() as u32;
15650 let end = Point::new(
15651 start.row,
15652 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15653 );
15654 start..end
15655 } else {
15656 start..start
15657 }
15658 }
15659
15660 fn comment_suffix_range(
15661 snapshot: &MultiBufferSnapshot,
15662 row: MultiBufferRow,
15663 comment_suffix: &str,
15664 comment_suffix_has_leading_space: bool,
15665 ) -> Range<Point> {
15666 let end = Point::new(row.0, snapshot.line_len(row));
15667 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15668
15669 let mut line_end_bytes = snapshot
15670 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15671 .flatten()
15672 .copied();
15673
15674 let leading_space_len = if suffix_start_column > 0
15675 && line_end_bytes.next() == Some(b' ')
15676 && comment_suffix_has_leading_space
15677 {
15678 1
15679 } else {
15680 0
15681 };
15682
15683 // If this line currently begins with the line comment prefix, then record
15684 // the range containing the prefix.
15685 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15686 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15687 start..end
15688 } else {
15689 end..end
15690 }
15691 }
15692
15693 // TODO: Handle selections that cross excerpts
15694 for selection in &mut selections {
15695 let start_column = snapshot
15696 .indent_size_for_line(MultiBufferRow(selection.start.row))
15697 .len;
15698 let language = if let Some(language) =
15699 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15700 {
15701 language
15702 } else {
15703 continue;
15704 };
15705
15706 selection_edit_ranges.clear();
15707
15708 // If multiple selections contain a given row, avoid processing that
15709 // row more than once.
15710 let mut start_row = MultiBufferRow(selection.start.row);
15711 if last_toggled_row == Some(start_row) {
15712 start_row = start_row.next_row();
15713 }
15714 let end_row =
15715 if selection.end.row > selection.start.row && selection.end.column == 0 {
15716 MultiBufferRow(selection.end.row - 1)
15717 } else {
15718 MultiBufferRow(selection.end.row)
15719 };
15720 last_toggled_row = Some(end_row);
15721
15722 if start_row > end_row {
15723 continue;
15724 }
15725
15726 // If the language has line comments, toggle those.
15727 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15728
15729 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15730 if ignore_indent {
15731 full_comment_prefixes = full_comment_prefixes
15732 .into_iter()
15733 .map(|s| Arc::from(s.trim_end()))
15734 .collect();
15735 }
15736
15737 if !full_comment_prefixes.is_empty() {
15738 let first_prefix = full_comment_prefixes
15739 .first()
15740 .expect("prefixes is non-empty");
15741 let prefix_trimmed_lengths = full_comment_prefixes
15742 .iter()
15743 .map(|p| p.trim_end_matches(' ').len())
15744 .collect::<SmallVec<[usize; 4]>>();
15745
15746 let mut all_selection_lines_are_comments = true;
15747
15748 for row in start_row.0..=end_row.0 {
15749 let row = MultiBufferRow(row);
15750 if start_row < end_row && snapshot.is_line_blank(row) {
15751 continue;
15752 }
15753
15754 let prefix_range = full_comment_prefixes
15755 .iter()
15756 .zip(prefix_trimmed_lengths.iter().copied())
15757 .map(|(prefix, trimmed_prefix_len)| {
15758 comment_prefix_range(
15759 snapshot.deref(),
15760 row,
15761 &prefix[..trimmed_prefix_len],
15762 &prefix[trimmed_prefix_len..],
15763 ignore_indent,
15764 )
15765 })
15766 .max_by_key(|range| range.end.column - range.start.column)
15767 .expect("prefixes is non-empty");
15768
15769 if prefix_range.is_empty() {
15770 all_selection_lines_are_comments = false;
15771 }
15772
15773 selection_edit_ranges.push(prefix_range);
15774 }
15775
15776 if all_selection_lines_are_comments {
15777 edits.extend(
15778 selection_edit_ranges
15779 .iter()
15780 .cloned()
15781 .map(|range| (range, empty_str.clone())),
15782 );
15783 } else {
15784 let min_column = selection_edit_ranges
15785 .iter()
15786 .map(|range| range.start.column)
15787 .min()
15788 .unwrap_or(0);
15789 edits.extend(selection_edit_ranges.iter().map(|range| {
15790 let position = Point::new(range.start.row, min_column);
15791 (position..position, first_prefix.clone())
15792 }));
15793 }
15794 } else if let Some(BlockCommentConfig {
15795 start: full_comment_prefix,
15796 end: comment_suffix,
15797 ..
15798 }) = language.block_comment()
15799 {
15800 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15801 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15802 let prefix_range = comment_prefix_range(
15803 snapshot.deref(),
15804 start_row,
15805 comment_prefix,
15806 comment_prefix_whitespace,
15807 ignore_indent,
15808 );
15809 let suffix_range = comment_suffix_range(
15810 snapshot.deref(),
15811 end_row,
15812 comment_suffix.trim_start_matches(' '),
15813 comment_suffix.starts_with(' '),
15814 );
15815
15816 if prefix_range.is_empty() || suffix_range.is_empty() {
15817 edits.push((
15818 prefix_range.start..prefix_range.start,
15819 full_comment_prefix.clone(),
15820 ));
15821 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15822 suffixes_inserted.push((end_row, comment_suffix.len()));
15823 } else {
15824 edits.push((prefix_range, empty_str.clone()));
15825 edits.push((suffix_range, empty_str.clone()));
15826 }
15827 } else {
15828 continue;
15829 }
15830 }
15831
15832 drop(snapshot);
15833 this.buffer.update(cx, |buffer, cx| {
15834 buffer.edit(edits, None, cx);
15835 });
15836
15837 // Adjust selections so that they end before any comment suffixes that
15838 // were inserted.
15839 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15840 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15841 let snapshot = this.buffer.read(cx).read(cx);
15842 for selection in &mut selections {
15843 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15844 match row.cmp(&MultiBufferRow(selection.end.row)) {
15845 Ordering::Less => {
15846 suffixes_inserted.next();
15847 continue;
15848 }
15849 Ordering::Greater => break,
15850 Ordering::Equal => {
15851 if selection.end.column == snapshot.line_len(row) {
15852 if selection.is_empty() {
15853 selection.start.column -= suffix_len as u32;
15854 }
15855 selection.end.column -= suffix_len as u32;
15856 }
15857 break;
15858 }
15859 }
15860 }
15861 }
15862
15863 drop(snapshot);
15864 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15865
15866 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15867 let selections_on_single_row = selections.windows(2).all(|selections| {
15868 selections[0].start.row == selections[1].start.row
15869 && selections[0].end.row == selections[1].end.row
15870 && selections[0].start.row == selections[0].end.row
15871 });
15872 let selections_selecting = selections
15873 .iter()
15874 .any(|selection| selection.start != selection.end);
15875 let advance_downwards = action.advance_downwards
15876 && selections_on_single_row
15877 && !selections_selecting
15878 && !matches!(this.mode, EditorMode::SingleLine);
15879
15880 if advance_downwards {
15881 let snapshot = this.buffer.read(cx).snapshot(cx);
15882
15883 this.change_selections(Default::default(), window, cx, |s| {
15884 s.move_cursors_with(|display_snapshot, display_point, _| {
15885 let mut point = display_point.to_point(display_snapshot);
15886 point.row += 1;
15887 point = snapshot.clip_point(point, Bias::Left);
15888 let display_point = point.to_display_point(display_snapshot);
15889 let goal = SelectionGoal::HorizontalPosition(
15890 display_snapshot
15891 .x_for_display_point(display_point, text_layout_details)
15892 .into(),
15893 );
15894 (display_point, goal)
15895 })
15896 });
15897 }
15898 });
15899 }
15900
15901 pub fn select_enclosing_symbol(
15902 &mut self,
15903 _: &SelectEnclosingSymbol,
15904 window: &mut Window,
15905 cx: &mut Context<Self>,
15906 ) {
15907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15908
15909 let buffer = self.buffer.read(cx).snapshot(cx);
15910 let old_selections = self
15911 .selections
15912 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15913 .into_boxed_slice();
15914
15915 fn update_selection(
15916 selection: &Selection<MultiBufferOffset>,
15917 buffer_snap: &MultiBufferSnapshot,
15918 ) -> Option<Selection<MultiBufferOffset>> {
15919 let cursor = selection.head();
15920 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15921 for symbol in symbols.iter().rev() {
15922 let start = symbol.range.start.to_offset(buffer_snap);
15923 let end = symbol.range.end.to_offset(buffer_snap);
15924 let new_range = start..end;
15925 if start < selection.start || end > selection.end {
15926 return Some(Selection {
15927 id: selection.id,
15928 start: new_range.start,
15929 end: new_range.end,
15930 goal: SelectionGoal::None,
15931 reversed: selection.reversed,
15932 });
15933 }
15934 }
15935 None
15936 }
15937
15938 let mut selected_larger_symbol = false;
15939 let new_selections = old_selections
15940 .iter()
15941 .map(|selection| match update_selection(selection, &buffer) {
15942 Some(new_selection) => {
15943 if new_selection.range() != selection.range() {
15944 selected_larger_symbol = true;
15945 }
15946 new_selection
15947 }
15948 None => selection.clone(),
15949 })
15950 .collect::<Vec<_>>();
15951
15952 if selected_larger_symbol {
15953 self.change_selections(Default::default(), window, cx, |s| {
15954 s.select(new_selections);
15955 });
15956 }
15957 }
15958
15959 pub fn select_larger_syntax_node(
15960 &mut self,
15961 _: &SelectLargerSyntaxNode,
15962 window: &mut Window,
15963 cx: &mut Context<Self>,
15964 ) {
15965 let Some(visible_row_count) = self.visible_row_count() else {
15966 return;
15967 };
15968 let old_selections: Box<[_]> = self
15969 .selections
15970 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15971 .into();
15972 if old_selections.is_empty() {
15973 return;
15974 }
15975
15976 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15977
15978 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15979 let buffer = self.buffer.read(cx).snapshot(cx);
15980
15981 let mut selected_larger_node = false;
15982 let mut new_selections = old_selections
15983 .iter()
15984 .map(|selection| {
15985 let old_range = selection.start..selection.end;
15986
15987 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15988 // manually select word at selection
15989 if ["string_content", "inline"].contains(&node.kind()) {
15990 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15991 // ignore if word is already selected
15992 if !word_range.is_empty() && old_range != word_range {
15993 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15994 // only select word if start and end point belongs to same word
15995 if word_range == last_word_range {
15996 selected_larger_node = true;
15997 return Selection {
15998 id: selection.id,
15999 start: word_range.start,
16000 end: word_range.end,
16001 goal: SelectionGoal::None,
16002 reversed: selection.reversed,
16003 };
16004 }
16005 }
16006 }
16007 }
16008
16009 let mut new_range = old_range.clone();
16010 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16011 new_range = range;
16012 if !node.is_named() {
16013 continue;
16014 }
16015 if !display_map.intersects_fold(new_range.start)
16016 && !display_map.intersects_fold(new_range.end)
16017 {
16018 break;
16019 }
16020 }
16021
16022 selected_larger_node |= new_range != old_range;
16023 Selection {
16024 id: selection.id,
16025 start: new_range.start,
16026 end: new_range.end,
16027 goal: SelectionGoal::None,
16028 reversed: selection.reversed,
16029 }
16030 })
16031 .collect::<Vec<_>>();
16032
16033 if !selected_larger_node {
16034 return; // don't put this call in the history
16035 }
16036
16037 // scroll based on transformation done to the last selection created by the user
16038 let (last_old, last_new) = old_selections
16039 .last()
16040 .zip(new_selections.last().cloned())
16041 .expect("old_selections isn't empty");
16042
16043 // revert selection
16044 let is_selection_reversed = {
16045 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16046 new_selections.last_mut().expect("checked above").reversed =
16047 should_newest_selection_be_reversed;
16048 should_newest_selection_be_reversed
16049 };
16050
16051 if selected_larger_node {
16052 self.select_syntax_node_history.disable_clearing = true;
16053 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16054 s.select(new_selections.clone());
16055 });
16056 self.select_syntax_node_history.disable_clearing = false;
16057 }
16058
16059 let start_row = last_new.start.to_display_point(&display_map).row().0;
16060 let end_row = last_new.end.to_display_point(&display_map).row().0;
16061 let selection_height = end_row - start_row + 1;
16062 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16063
16064 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16065 let scroll_behavior = if fits_on_the_screen {
16066 self.request_autoscroll(Autoscroll::fit(), cx);
16067 SelectSyntaxNodeScrollBehavior::FitSelection
16068 } else if is_selection_reversed {
16069 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16070 SelectSyntaxNodeScrollBehavior::CursorTop
16071 } else {
16072 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16073 SelectSyntaxNodeScrollBehavior::CursorBottom
16074 };
16075
16076 self.select_syntax_node_history.push((
16077 old_selections,
16078 scroll_behavior,
16079 is_selection_reversed,
16080 ));
16081 }
16082
16083 pub fn select_smaller_syntax_node(
16084 &mut self,
16085 _: &SelectSmallerSyntaxNode,
16086 window: &mut Window,
16087 cx: &mut Context<Self>,
16088 ) {
16089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16090
16091 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16092 self.select_syntax_node_history.pop()
16093 {
16094 if let Some(selection) = selections.last_mut() {
16095 selection.reversed = is_selection_reversed;
16096 }
16097
16098 self.select_syntax_node_history.disable_clearing = true;
16099 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16100 s.select(selections.to_vec());
16101 });
16102 self.select_syntax_node_history.disable_clearing = false;
16103
16104 match scroll_behavior {
16105 SelectSyntaxNodeScrollBehavior::CursorTop => {
16106 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16107 }
16108 SelectSyntaxNodeScrollBehavior::FitSelection => {
16109 self.request_autoscroll(Autoscroll::fit(), cx);
16110 }
16111 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16112 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16113 }
16114 }
16115 }
16116 }
16117
16118 pub fn unwrap_syntax_node(
16119 &mut self,
16120 _: &UnwrapSyntaxNode,
16121 window: &mut Window,
16122 cx: &mut Context<Self>,
16123 ) {
16124 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16125
16126 let buffer = self.buffer.read(cx).snapshot(cx);
16127 let selections = self
16128 .selections
16129 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16130 .into_iter()
16131 // subtracting the offset requires sorting
16132 .sorted_by_key(|i| i.start);
16133
16134 let full_edits = selections
16135 .into_iter()
16136 .filter_map(|selection| {
16137 let child = if selection.is_empty()
16138 && let Some((_, ancestor_range)) =
16139 buffer.syntax_ancestor(selection.start..selection.end)
16140 {
16141 ancestor_range
16142 } else {
16143 selection.range()
16144 };
16145
16146 let mut parent = child.clone();
16147 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16148 parent = ancestor_range;
16149 if parent.start < child.start || parent.end > child.end {
16150 break;
16151 }
16152 }
16153
16154 if parent == child {
16155 return None;
16156 }
16157 let text = buffer.text_for_range(child).collect::<String>();
16158 Some((selection.id, parent, text))
16159 })
16160 .collect::<Vec<_>>();
16161 if full_edits.is_empty() {
16162 return;
16163 }
16164
16165 self.transact(window, cx, |this, window, cx| {
16166 this.buffer.update(cx, |buffer, cx| {
16167 buffer.edit(
16168 full_edits
16169 .iter()
16170 .map(|(_, p, t)| (p.clone(), t.clone()))
16171 .collect::<Vec<_>>(),
16172 None,
16173 cx,
16174 );
16175 });
16176 this.change_selections(Default::default(), window, cx, |s| {
16177 let mut offset = 0;
16178 let mut selections = vec![];
16179 for (id, parent, text) in full_edits {
16180 let start = parent.start - offset;
16181 offset += (parent.end - parent.start) - text.len();
16182 selections.push(Selection {
16183 id,
16184 start,
16185 end: start + text.len(),
16186 reversed: false,
16187 goal: Default::default(),
16188 });
16189 }
16190 s.select(selections);
16191 });
16192 });
16193 }
16194
16195 pub fn select_next_syntax_node(
16196 &mut self,
16197 _: &SelectNextSyntaxNode,
16198 window: &mut Window,
16199 cx: &mut Context<Self>,
16200 ) {
16201 let old_selections: Box<[_]> = self
16202 .selections
16203 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16204 .into();
16205 if old_selections.is_empty() {
16206 return;
16207 }
16208
16209 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16210
16211 let buffer = self.buffer.read(cx).snapshot(cx);
16212 let mut selected_sibling = false;
16213
16214 let new_selections = old_selections
16215 .iter()
16216 .map(|selection| {
16217 let old_range = selection.start..selection.end;
16218
16219 let old_range =
16220 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16221 let excerpt = buffer.excerpt_containing(old_range.clone());
16222
16223 if let Some(mut excerpt) = excerpt
16224 && let Some(node) = excerpt
16225 .buffer()
16226 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16227 {
16228 let new_range = excerpt.map_range_from_buffer(
16229 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16230 );
16231 selected_sibling = true;
16232 Selection {
16233 id: selection.id,
16234 start: new_range.start,
16235 end: new_range.end,
16236 goal: SelectionGoal::None,
16237 reversed: selection.reversed,
16238 }
16239 } else {
16240 selection.clone()
16241 }
16242 })
16243 .collect::<Vec<_>>();
16244
16245 if selected_sibling {
16246 self.change_selections(
16247 SelectionEffects::scroll(Autoscroll::fit()),
16248 window,
16249 cx,
16250 |s| {
16251 s.select(new_selections);
16252 },
16253 );
16254 }
16255 }
16256
16257 pub fn select_prev_syntax_node(
16258 &mut self,
16259 _: &SelectPreviousSyntaxNode,
16260 window: &mut Window,
16261 cx: &mut Context<Self>,
16262 ) {
16263 let old_selections: Box<[_]> = self
16264 .selections
16265 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16266 .into();
16267 if old_selections.is_empty() {
16268 return;
16269 }
16270
16271 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16272
16273 let buffer = self.buffer.read(cx).snapshot(cx);
16274 let mut selected_sibling = false;
16275
16276 let new_selections = old_selections
16277 .iter()
16278 .map(|selection| {
16279 let old_range = selection.start..selection.end;
16280 let old_range =
16281 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16282 let excerpt = buffer.excerpt_containing(old_range.clone());
16283
16284 if let Some(mut excerpt) = excerpt
16285 && let Some(node) = excerpt
16286 .buffer()
16287 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16288 {
16289 let new_range = excerpt.map_range_from_buffer(
16290 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16291 );
16292 selected_sibling = true;
16293 Selection {
16294 id: selection.id,
16295 start: new_range.start,
16296 end: new_range.end,
16297 goal: SelectionGoal::None,
16298 reversed: selection.reversed,
16299 }
16300 } else {
16301 selection.clone()
16302 }
16303 })
16304 .collect::<Vec<_>>();
16305
16306 if selected_sibling {
16307 self.change_selections(
16308 SelectionEffects::scroll(Autoscroll::fit()),
16309 window,
16310 cx,
16311 |s| {
16312 s.select(new_selections);
16313 },
16314 );
16315 }
16316 }
16317
16318 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16319 if !EditorSettings::get_global(cx).gutter.runnables {
16320 self.clear_tasks();
16321 return Task::ready(());
16322 }
16323 let project = self.project().map(Entity::downgrade);
16324 let task_sources = self.lsp_task_sources(cx);
16325 let multi_buffer = self.buffer.downgrade();
16326 cx.spawn_in(window, async move |editor, cx| {
16327 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16328 let Some(project) = project.and_then(|p| p.upgrade()) else {
16329 return;
16330 };
16331 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16332 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16333 }) else {
16334 return;
16335 };
16336
16337 let hide_runnables = project
16338 .update(cx, |project, _| project.is_via_collab())
16339 .unwrap_or(true);
16340 if hide_runnables {
16341 return;
16342 }
16343 let new_rows =
16344 cx.background_spawn({
16345 let snapshot = display_snapshot.clone();
16346 async move {
16347 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16348 }
16349 })
16350 .await;
16351 let Ok(lsp_tasks) =
16352 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16353 else {
16354 return;
16355 };
16356 let lsp_tasks = lsp_tasks.await;
16357
16358 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16359 lsp_tasks
16360 .into_iter()
16361 .flat_map(|(kind, tasks)| {
16362 tasks.into_iter().filter_map(move |(location, task)| {
16363 Some((kind.clone(), location?, task))
16364 })
16365 })
16366 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16367 let buffer = location.target.buffer;
16368 let buffer_snapshot = buffer.read(cx).snapshot();
16369 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16370 |(excerpt_id, snapshot, _)| {
16371 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16372 display_snapshot
16373 .buffer_snapshot()
16374 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16375 } else {
16376 None
16377 }
16378 },
16379 );
16380 if let Some(offset) = offset {
16381 let task_buffer_range =
16382 location.target.range.to_point(&buffer_snapshot);
16383 let context_buffer_range =
16384 task_buffer_range.to_offset(&buffer_snapshot);
16385 let context_range = BufferOffset(context_buffer_range.start)
16386 ..BufferOffset(context_buffer_range.end);
16387
16388 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16389 .or_insert_with(|| RunnableTasks {
16390 templates: Vec::new(),
16391 offset,
16392 column: task_buffer_range.start.column,
16393 extra_variables: HashMap::default(),
16394 context_range,
16395 })
16396 .templates
16397 .push((kind, task.original_task().clone()));
16398 }
16399
16400 acc
16401 })
16402 }) else {
16403 return;
16404 };
16405
16406 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16407 buffer.language_settings(cx).tasks.prefer_lsp
16408 }) else {
16409 return;
16410 };
16411
16412 let rows = Self::runnable_rows(
16413 project,
16414 display_snapshot,
16415 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16416 new_rows,
16417 cx.clone(),
16418 )
16419 .await;
16420 editor
16421 .update(cx, |editor, _| {
16422 editor.clear_tasks();
16423 for (key, mut value) in rows {
16424 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16425 value.templates.extend(lsp_tasks.templates);
16426 }
16427
16428 editor.insert_tasks(key, value);
16429 }
16430 for (key, value) in lsp_tasks_by_rows {
16431 editor.insert_tasks(key, value);
16432 }
16433 })
16434 .ok();
16435 })
16436 }
16437 fn fetch_runnable_ranges(
16438 snapshot: &DisplaySnapshot,
16439 range: Range<Anchor>,
16440 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16441 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16442 }
16443
16444 fn runnable_rows(
16445 project: Entity<Project>,
16446 snapshot: DisplaySnapshot,
16447 prefer_lsp: bool,
16448 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16449 cx: AsyncWindowContext,
16450 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16451 cx.spawn(async move |cx| {
16452 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16453 for (run_range, mut runnable) in runnable_ranges {
16454 let Some(tasks) = cx
16455 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16456 .ok()
16457 else {
16458 continue;
16459 };
16460 let mut tasks = tasks.await;
16461
16462 if prefer_lsp {
16463 tasks.retain(|(task_kind, _)| {
16464 !matches!(task_kind, TaskSourceKind::Language { .. })
16465 });
16466 }
16467 if tasks.is_empty() {
16468 continue;
16469 }
16470
16471 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16472 let Some(row) = snapshot
16473 .buffer_snapshot()
16474 .buffer_line_for_row(MultiBufferRow(point.row))
16475 .map(|(_, range)| range.start.row)
16476 else {
16477 continue;
16478 };
16479
16480 let context_range =
16481 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16482 runnable_rows.push((
16483 (runnable.buffer_id, row),
16484 RunnableTasks {
16485 templates: tasks,
16486 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16487 context_range,
16488 column: point.column,
16489 extra_variables: runnable.extra_captures,
16490 },
16491 ));
16492 }
16493 runnable_rows
16494 })
16495 }
16496
16497 fn templates_with_tags(
16498 project: &Entity<Project>,
16499 runnable: &mut Runnable,
16500 cx: &mut App,
16501 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16502 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16503 let (worktree_id, file) = project
16504 .buffer_for_id(runnable.buffer, cx)
16505 .and_then(|buffer| buffer.read(cx).file())
16506 .map(|file| (file.worktree_id(cx), file.clone()))
16507 .unzip();
16508
16509 (
16510 project.task_store().read(cx).task_inventory().cloned(),
16511 worktree_id,
16512 file,
16513 )
16514 });
16515
16516 let tags = mem::take(&mut runnable.tags);
16517 let language = runnable.language.clone();
16518 cx.spawn(async move |cx| {
16519 let mut templates_with_tags = Vec::new();
16520 if let Some(inventory) = inventory {
16521 for RunnableTag(tag) in tags {
16522 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16523 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16524 }) else {
16525 return templates_with_tags;
16526 };
16527 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16528 move |(_, template)| {
16529 template.tags.iter().any(|source_tag| source_tag == &tag)
16530 },
16531 ));
16532 }
16533 }
16534 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16535
16536 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16537 // Strongest source wins; if we have worktree tag binding, prefer that to
16538 // global and language bindings;
16539 // if we have a global binding, prefer that to language binding.
16540 let first_mismatch = templates_with_tags
16541 .iter()
16542 .position(|(tag_source, _)| tag_source != leading_tag_source);
16543 if let Some(index) = first_mismatch {
16544 templates_with_tags.truncate(index);
16545 }
16546 }
16547
16548 templates_with_tags
16549 })
16550 }
16551
16552 pub fn move_to_enclosing_bracket(
16553 &mut self,
16554 _: &MoveToEnclosingBracket,
16555 window: &mut Window,
16556 cx: &mut Context<Self>,
16557 ) {
16558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16559 self.change_selections(Default::default(), window, cx, |s| {
16560 s.move_offsets_with(|snapshot, selection| {
16561 let Some(enclosing_bracket_ranges) =
16562 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16563 else {
16564 return;
16565 };
16566
16567 let mut best_length = usize::MAX;
16568 let mut best_inside = false;
16569 let mut best_in_bracket_range = false;
16570 let mut best_destination = None;
16571 for (open, close) in enclosing_bracket_ranges {
16572 let close = close.to_inclusive();
16573 let length = *close.end() - open.start;
16574 let inside = selection.start >= open.end && selection.end <= *close.start();
16575 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16576 || close.contains(&selection.head());
16577
16578 // If best is next to a bracket and current isn't, skip
16579 if !in_bracket_range && best_in_bracket_range {
16580 continue;
16581 }
16582
16583 // Prefer smaller lengths unless best is inside and current isn't
16584 if length > best_length && (best_inside || !inside) {
16585 continue;
16586 }
16587
16588 best_length = length;
16589 best_inside = inside;
16590 best_in_bracket_range = in_bracket_range;
16591 best_destination = Some(
16592 if close.contains(&selection.start) && close.contains(&selection.end) {
16593 if inside { open.end } else { open.start }
16594 } else if inside {
16595 *close.start()
16596 } else {
16597 *close.end()
16598 },
16599 );
16600 }
16601
16602 if let Some(destination) = best_destination {
16603 selection.collapse_to(destination, SelectionGoal::None);
16604 }
16605 })
16606 });
16607 }
16608
16609 pub fn undo_selection(
16610 &mut self,
16611 _: &UndoSelection,
16612 window: &mut Window,
16613 cx: &mut Context<Self>,
16614 ) {
16615 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16616 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16617 self.selection_history.mode = SelectionHistoryMode::Undoing;
16618 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16619 this.end_selection(window, cx);
16620 this.change_selections(
16621 SelectionEffects::scroll(Autoscroll::newest()),
16622 window,
16623 cx,
16624 |s| s.select_anchors(entry.selections.to_vec()),
16625 );
16626 });
16627 self.selection_history.mode = SelectionHistoryMode::Normal;
16628
16629 self.select_next_state = entry.select_next_state;
16630 self.select_prev_state = entry.select_prev_state;
16631 self.add_selections_state = entry.add_selections_state;
16632 }
16633 }
16634
16635 pub fn redo_selection(
16636 &mut self,
16637 _: &RedoSelection,
16638 window: &mut Window,
16639 cx: &mut Context<Self>,
16640 ) {
16641 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16642 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16643 self.selection_history.mode = SelectionHistoryMode::Redoing;
16644 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16645 this.end_selection(window, cx);
16646 this.change_selections(
16647 SelectionEffects::scroll(Autoscroll::newest()),
16648 window,
16649 cx,
16650 |s| s.select_anchors(entry.selections.to_vec()),
16651 );
16652 });
16653 self.selection_history.mode = SelectionHistoryMode::Normal;
16654
16655 self.select_next_state = entry.select_next_state;
16656 self.select_prev_state = entry.select_prev_state;
16657 self.add_selections_state = entry.add_selections_state;
16658 }
16659 }
16660
16661 pub fn expand_excerpts(
16662 &mut self,
16663 action: &ExpandExcerpts,
16664 _: &mut Window,
16665 cx: &mut Context<Self>,
16666 ) {
16667 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16668 }
16669
16670 pub fn expand_excerpts_down(
16671 &mut self,
16672 action: &ExpandExcerptsDown,
16673 _: &mut Window,
16674 cx: &mut Context<Self>,
16675 ) {
16676 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16677 }
16678
16679 pub fn expand_excerpts_up(
16680 &mut self,
16681 action: &ExpandExcerptsUp,
16682 _: &mut Window,
16683 cx: &mut Context<Self>,
16684 ) {
16685 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16686 }
16687
16688 pub fn expand_excerpts_for_direction(
16689 &mut self,
16690 lines: u32,
16691 direction: ExpandExcerptDirection,
16692
16693 cx: &mut Context<Self>,
16694 ) {
16695 let selections = self.selections.disjoint_anchors_arc();
16696
16697 let lines = if lines == 0 {
16698 EditorSettings::get_global(cx).expand_excerpt_lines
16699 } else {
16700 lines
16701 };
16702
16703 self.buffer.update(cx, |buffer, cx| {
16704 let snapshot = buffer.snapshot(cx);
16705 let mut excerpt_ids = selections
16706 .iter()
16707 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16708 .collect::<Vec<_>>();
16709 excerpt_ids.sort();
16710 excerpt_ids.dedup();
16711 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16712 })
16713 }
16714
16715 pub fn expand_excerpt(
16716 &mut self,
16717 excerpt: ExcerptId,
16718 direction: ExpandExcerptDirection,
16719 window: &mut Window,
16720 cx: &mut Context<Self>,
16721 ) {
16722 let current_scroll_position = self.scroll_position(cx);
16723 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16724 let mut scroll = None;
16725
16726 if direction == ExpandExcerptDirection::Down {
16727 let multi_buffer = self.buffer.read(cx);
16728 let snapshot = multi_buffer.snapshot(cx);
16729 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16730 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16731 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16732 {
16733 let buffer_snapshot = buffer.read(cx).snapshot();
16734 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16735 let last_row = buffer_snapshot.max_point().row;
16736 let lines_below = last_row.saturating_sub(excerpt_end_row);
16737 if lines_below >= lines_to_expand {
16738 scroll = Some(
16739 current_scroll_position
16740 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16741 );
16742 }
16743 }
16744 }
16745 if direction == ExpandExcerptDirection::Up
16746 && self
16747 .buffer
16748 .read(cx)
16749 .snapshot(cx)
16750 .excerpt_before(excerpt)
16751 .is_none()
16752 {
16753 scroll = Some(current_scroll_position);
16754 }
16755
16756 self.buffer.update(cx, |buffer, cx| {
16757 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16758 });
16759
16760 if let Some(new_scroll_position) = scroll {
16761 self.set_scroll_position(new_scroll_position, window, cx);
16762 }
16763 }
16764
16765 pub fn go_to_singleton_buffer_point(
16766 &mut self,
16767 point: Point,
16768 window: &mut Window,
16769 cx: &mut Context<Self>,
16770 ) {
16771 self.go_to_singleton_buffer_range(point..point, window, cx);
16772 }
16773
16774 pub fn go_to_singleton_buffer_range(
16775 &mut self,
16776 range: Range<Point>,
16777 window: &mut Window,
16778 cx: &mut Context<Self>,
16779 ) {
16780 let multibuffer = self.buffer().read(cx);
16781 let Some(buffer) = multibuffer.as_singleton() else {
16782 return;
16783 };
16784 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16785 return;
16786 };
16787 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16788 return;
16789 };
16790 self.change_selections(
16791 SelectionEffects::default().nav_history(true),
16792 window,
16793 cx,
16794 |s| s.select_anchor_ranges([start..end]),
16795 );
16796 }
16797
16798 pub fn go_to_diagnostic(
16799 &mut self,
16800 action: &GoToDiagnostic,
16801 window: &mut Window,
16802 cx: &mut Context<Self>,
16803 ) {
16804 if !self.diagnostics_enabled() {
16805 return;
16806 }
16807 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16808 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16809 }
16810
16811 pub fn go_to_prev_diagnostic(
16812 &mut self,
16813 action: &GoToPreviousDiagnostic,
16814 window: &mut Window,
16815 cx: &mut Context<Self>,
16816 ) {
16817 if !self.diagnostics_enabled() {
16818 return;
16819 }
16820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16821 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16822 }
16823
16824 pub fn go_to_diagnostic_impl(
16825 &mut self,
16826 direction: Direction,
16827 severity: GoToDiagnosticSeverityFilter,
16828 window: &mut Window,
16829 cx: &mut Context<Self>,
16830 ) {
16831 let buffer = self.buffer.read(cx).snapshot(cx);
16832 let selection = self
16833 .selections
16834 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16835
16836 let mut active_group_id = None;
16837 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16838 && active_group.active_range.start.to_offset(&buffer) == selection.start
16839 {
16840 active_group_id = Some(active_group.group_id);
16841 }
16842
16843 fn filtered<'a>(
16844 severity: GoToDiagnosticSeverityFilter,
16845 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16846 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16847 diagnostics
16848 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16849 .filter(|entry| entry.range.start != entry.range.end)
16850 .filter(|entry| !entry.diagnostic.is_unnecessary)
16851 }
16852
16853 let before = filtered(
16854 severity,
16855 buffer
16856 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16857 .filter(|entry| entry.range.start <= selection.start),
16858 );
16859 let after = filtered(
16860 severity,
16861 buffer
16862 .diagnostics_in_range(selection.start..buffer.len())
16863 .filter(|entry| entry.range.start >= selection.start),
16864 );
16865
16866 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16867 if direction == Direction::Prev {
16868 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16869 {
16870 for diagnostic in prev_diagnostics.into_iter().rev() {
16871 if diagnostic.range.start != selection.start
16872 || active_group_id
16873 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16874 {
16875 found = Some(diagnostic);
16876 break 'outer;
16877 }
16878 }
16879 }
16880 } else {
16881 for diagnostic in after.chain(before) {
16882 if diagnostic.range.start != selection.start
16883 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16884 {
16885 found = Some(diagnostic);
16886 break;
16887 }
16888 }
16889 }
16890 let Some(next_diagnostic) = found else {
16891 return;
16892 };
16893
16894 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16895 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16896 return;
16897 };
16898 let snapshot = self.snapshot(window, cx);
16899 if snapshot.intersects_fold(next_diagnostic.range.start) {
16900 self.unfold_ranges(
16901 std::slice::from_ref(&next_diagnostic.range),
16902 true,
16903 false,
16904 cx,
16905 );
16906 }
16907 self.change_selections(Default::default(), window, cx, |s| {
16908 s.select_ranges(vec![
16909 next_diagnostic.range.start..next_diagnostic.range.start,
16910 ])
16911 });
16912 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16913 self.refresh_edit_prediction(false, true, window, cx);
16914 }
16915
16916 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16918 let snapshot = self.snapshot(window, cx);
16919 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16920 self.go_to_hunk_before_or_after_position(
16921 &snapshot,
16922 selection.head(),
16923 Direction::Next,
16924 window,
16925 cx,
16926 );
16927 }
16928
16929 pub fn go_to_hunk_before_or_after_position(
16930 &mut self,
16931 snapshot: &EditorSnapshot,
16932 position: Point,
16933 direction: Direction,
16934 window: &mut Window,
16935 cx: &mut Context<Editor>,
16936 ) {
16937 let row = if direction == Direction::Next {
16938 self.hunk_after_position(snapshot, position)
16939 .map(|hunk| hunk.row_range.start)
16940 } else {
16941 self.hunk_before_position(snapshot, position)
16942 };
16943
16944 if let Some(row) = row {
16945 let destination = Point::new(row.0, 0);
16946 let autoscroll = Autoscroll::center();
16947
16948 self.unfold_ranges(&[destination..destination], false, false, cx);
16949 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16950 s.select_ranges([destination..destination]);
16951 });
16952 }
16953 }
16954
16955 fn hunk_after_position(
16956 &mut self,
16957 snapshot: &EditorSnapshot,
16958 position: Point,
16959 ) -> Option<MultiBufferDiffHunk> {
16960 snapshot
16961 .buffer_snapshot()
16962 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16963 .find(|hunk| hunk.row_range.start.0 > position.row)
16964 .or_else(|| {
16965 snapshot
16966 .buffer_snapshot()
16967 .diff_hunks_in_range(Point::zero()..position)
16968 .find(|hunk| hunk.row_range.end.0 < position.row)
16969 })
16970 }
16971
16972 fn go_to_prev_hunk(
16973 &mut self,
16974 _: &GoToPreviousHunk,
16975 window: &mut Window,
16976 cx: &mut Context<Self>,
16977 ) {
16978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16979 let snapshot = self.snapshot(window, cx);
16980 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16981 self.go_to_hunk_before_or_after_position(
16982 &snapshot,
16983 selection.head(),
16984 Direction::Prev,
16985 window,
16986 cx,
16987 );
16988 }
16989
16990 fn hunk_before_position(
16991 &mut self,
16992 snapshot: &EditorSnapshot,
16993 position: Point,
16994 ) -> Option<MultiBufferRow> {
16995 snapshot
16996 .buffer_snapshot()
16997 .diff_hunk_before(position)
16998 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16999 }
17000
17001 fn go_to_next_change(
17002 &mut self,
17003 _: &GoToNextChange,
17004 window: &mut Window,
17005 cx: &mut Context<Self>,
17006 ) {
17007 if let Some(selections) = self
17008 .change_list
17009 .next_change(1, Direction::Next)
17010 .map(|s| s.to_vec())
17011 {
17012 self.change_selections(Default::default(), window, cx, |s| {
17013 let map = s.display_snapshot();
17014 s.select_display_ranges(selections.iter().map(|a| {
17015 let point = a.to_display_point(&map);
17016 point..point
17017 }))
17018 })
17019 }
17020 }
17021
17022 fn go_to_previous_change(
17023 &mut self,
17024 _: &GoToPreviousChange,
17025 window: &mut Window,
17026 cx: &mut Context<Self>,
17027 ) {
17028 if let Some(selections) = self
17029 .change_list
17030 .next_change(1, Direction::Prev)
17031 .map(|s| s.to_vec())
17032 {
17033 self.change_selections(Default::default(), window, cx, |s| {
17034 let map = s.display_snapshot();
17035 s.select_display_ranges(selections.iter().map(|a| {
17036 let point = a.to_display_point(&map);
17037 point..point
17038 }))
17039 })
17040 }
17041 }
17042
17043 pub fn go_to_next_document_highlight(
17044 &mut self,
17045 _: &GoToNextDocumentHighlight,
17046 window: &mut Window,
17047 cx: &mut Context<Self>,
17048 ) {
17049 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17050 }
17051
17052 pub fn go_to_prev_document_highlight(
17053 &mut self,
17054 _: &GoToPreviousDocumentHighlight,
17055 window: &mut Window,
17056 cx: &mut Context<Self>,
17057 ) {
17058 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17059 }
17060
17061 pub fn go_to_document_highlight_before_or_after_position(
17062 &mut self,
17063 direction: Direction,
17064 window: &mut Window,
17065 cx: &mut Context<Editor>,
17066 ) {
17067 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17068 let snapshot = self.snapshot(window, cx);
17069 let buffer = &snapshot.buffer_snapshot();
17070 let position = self
17071 .selections
17072 .newest::<Point>(&snapshot.display_snapshot)
17073 .head();
17074 let anchor_position = buffer.anchor_after(position);
17075
17076 // Get all document highlights (both read and write)
17077 let mut all_highlights = Vec::new();
17078
17079 if let Some((_, read_highlights)) = self
17080 .background_highlights
17081 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17082 {
17083 all_highlights.extend(read_highlights.iter());
17084 }
17085
17086 if let Some((_, write_highlights)) = self
17087 .background_highlights
17088 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17089 {
17090 all_highlights.extend(write_highlights.iter());
17091 }
17092
17093 if all_highlights.is_empty() {
17094 return;
17095 }
17096
17097 // Sort highlights by position
17098 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17099
17100 let target_highlight = match direction {
17101 Direction::Next => {
17102 // Find the first highlight after the current position
17103 all_highlights
17104 .iter()
17105 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17106 }
17107 Direction::Prev => {
17108 // Find the last highlight before the current position
17109 all_highlights
17110 .iter()
17111 .rev()
17112 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17113 }
17114 };
17115
17116 if let Some(highlight) = target_highlight {
17117 let destination = highlight.start.to_point(buffer);
17118 let autoscroll = Autoscroll::center();
17119
17120 self.unfold_ranges(&[destination..destination], false, false, cx);
17121 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17122 s.select_ranges([destination..destination]);
17123 });
17124 }
17125 }
17126
17127 fn go_to_line<T: 'static>(
17128 &mut self,
17129 position: Anchor,
17130 highlight_color: Option<Hsla>,
17131 window: &mut Window,
17132 cx: &mut Context<Self>,
17133 ) {
17134 let snapshot = self.snapshot(window, cx).display_snapshot;
17135 let position = position.to_point(&snapshot.buffer_snapshot());
17136 let start = snapshot
17137 .buffer_snapshot()
17138 .clip_point(Point::new(position.row, 0), Bias::Left);
17139 let end = start + Point::new(1, 0);
17140 let start = snapshot.buffer_snapshot().anchor_before(start);
17141 let end = snapshot.buffer_snapshot().anchor_before(end);
17142
17143 self.highlight_rows::<T>(
17144 start..end,
17145 highlight_color
17146 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17147 Default::default(),
17148 cx,
17149 );
17150
17151 if self.buffer.read(cx).is_singleton() {
17152 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17153 }
17154 }
17155
17156 pub fn go_to_definition(
17157 &mut self,
17158 _: &GoToDefinition,
17159 window: &mut Window,
17160 cx: &mut Context<Self>,
17161 ) -> Task<Result<Navigated>> {
17162 let definition =
17163 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17164 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17165 cx.spawn_in(window, async move |editor, cx| {
17166 if definition.await? == Navigated::Yes {
17167 return Ok(Navigated::Yes);
17168 }
17169 match fallback_strategy {
17170 GoToDefinitionFallback::None => Ok(Navigated::No),
17171 GoToDefinitionFallback::FindAllReferences => {
17172 match editor.update_in(cx, |editor, window, cx| {
17173 editor.find_all_references(&FindAllReferences::default(), window, cx)
17174 })? {
17175 Some(references) => references.await,
17176 None => Ok(Navigated::No),
17177 }
17178 }
17179 }
17180 })
17181 }
17182
17183 pub fn go_to_declaration(
17184 &mut self,
17185 _: &GoToDeclaration,
17186 window: &mut Window,
17187 cx: &mut Context<Self>,
17188 ) -> Task<Result<Navigated>> {
17189 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17190 }
17191
17192 pub fn go_to_declaration_split(
17193 &mut self,
17194 _: &GoToDeclaration,
17195 window: &mut Window,
17196 cx: &mut Context<Self>,
17197 ) -> Task<Result<Navigated>> {
17198 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17199 }
17200
17201 pub fn go_to_implementation(
17202 &mut self,
17203 _: &GoToImplementation,
17204 window: &mut Window,
17205 cx: &mut Context<Self>,
17206 ) -> Task<Result<Navigated>> {
17207 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17208 }
17209
17210 pub fn go_to_implementation_split(
17211 &mut self,
17212 _: &GoToImplementationSplit,
17213 window: &mut Window,
17214 cx: &mut Context<Self>,
17215 ) -> Task<Result<Navigated>> {
17216 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17217 }
17218
17219 pub fn go_to_type_definition(
17220 &mut self,
17221 _: &GoToTypeDefinition,
17222 window: &mut Window,
17223 cx: &mut Context<Self>,
17224 ) -> Task<Result<Navigated>> {
17225 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17226 }
17227
17228 pub fn go_to_definition_split(
17229 &mut self,
17230 _: &GoToDefinitionSplit,
17231 window: &mut Window,
17232 cx: &mut Context<Self>,
17233 ) -> Task<Result<Navigated>> {
17234 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17235 }
17236
17237 pub fn go_to_type_definition_split(
17238 &mut self,
17239 _: &GoToTypeDefinitionSplit,
17240 window: &mut Window,
17241 cx: &mut Context<Self>,
17242 ) -> Task<Result<Navigated>> {
17243 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17244 }
17245
17246 fn go_to_definition_of_kind(
17247 &mut self,
17248 kind: GotoDefinitionKind,
17249 split: bool,
17250 window: &mut Window,
17251 cx: &mut Context<Self>,
17252 ) -> Task<Result<Navigated>> {
17253 let Some(provider) = self.semantics_provider.clone() else {
17254 return Task::ready(Ok(Navigated::No));
17255 };
17256 let head = self
17257 .selections
17258 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17259 .head();
17260 let buffer = self.buffer.read(cx);
17261 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17262 return Task::ready(Ok(Navigated::No));
17263 };
17264 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17265 return Task::ready(Ok(Navigated::No));
17266 };
17267
17268 cx.spawn_in(window, async move |editor, cx| {
17269 let Some(definitions) = definitions.await? else {
17270 return Ok(Navigated::No);
17271 };
17272 let navigated = editor
17273 .update_in(cx, |editor, window, cx| {
17274 editor.navigate_to_hover_links(
17275 Some(kind),
17276 definitions
17277 .into_iter()
17278 .filter(|location| {
17279 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17280 })
17281 .map(HoverLink::Text)
17282 .collect::<Vec<_>>(),
17283 split,
17284 window,
17285 cx,
17286 )
17287 })?
17288 .await?;
17289 anyhow::Ok(navigated)
17290 })
17291 }
17292
17293 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17294 let selection = self.selections.newest_anchor();
17295 let head = selection.head();
17296 let tail = selection.tail();
17297
17298 let Some((buffer, start_position)) =
17299 self.buffer.read(cx).text_anchor_for_position(head, cx)
17300 else {
17301 return;
17302 };
17303
17304 let end_position = if head != tail {
17305 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17306 return;
17307 };
17308 Some(pos)
17309 } else {
17310 None
17311 };
17312
17313 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17314 let url = if let Some(end_pos) = end_position {
17315 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17316 } else {
17317 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17318 };
17319
17320 if let Some(url) = url {
17321 cx.update(|window, cx| {
17322 if parse_zed_link(&url, cx).is_some() {
17323 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17324 } else {
17325 cx.open_url(&url);
17326 }
17327 })?;
17328 }
17329
17330 anyhow::Ok(())
17331 });
17332
17333 url_finder.detach();
17334 }
17335
17336 pub fn open_selected_filename(
17337 &mut self,
17338 _: &OpenSelectedFilename,
17339 window: &mut Window,
17340 cx: &mut Context<Self>,
17341 ) {
17342 let Some(workspace) = self.workspace() else {
17343 return;
17344 };
17345
17346 let position = self.selections.newest_anchor().head();
17347
17348 let Some((buffer, buffer_position)) =
17349 self.buffer.read(cx).text_anchor_for_position(position, cx)
17350 else {
17351 return;
17352 };
17353
17354 let project = self.project.clone();
17355
17356 cx.spawn_in(window, async move |_, cx| {
17357 let result = find_file(&buffer, project, buffer_position, cx).await;
17358
17359 if let Some((_, path)) = result {
17360 workspace
17361 .update_in(cx, |workspace, window, cx| {
17362 workspace.open_resolved_path(path, window, cx)
17363 })?
17364 .await?;
17365 }
17366 anyhow::Ok(())
17367 })
17368 .detach();
17369 }
17370
17371 pub(crate) fn navigate_to_hover_links(
17372 &mut self,
17373 kind: Option<GotoDefinitionKind>,
17374 definitions: Vec<HoverLink>,
17375 split: bool,
17376 window: &mut Window,
17377 cx: &mut Context<Editor>,
17378 ) -> Task<Result<Navigated>> {
17379 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17380 let mut first_url_or_file = None;
17381 let definitions: Vec<_> = definitions
17382 .into_iter()
17383 .filter_map(|def| match def {
17384 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17385 HoverLink::InlayHint(lsp_location, server_id) => {
17386 let computation =
17387 self.compute_target_location(lsp_location, server_id, window, cx);
17388 Some(cx.background_spawn(computation))
17389 }
17390 HoverLink::Url(url) => {
17391 first_url_or_file = Some(Either::Left(url));
17392 None
17393 }
17394 HoverLink::File(path) => {
17395 first_url_or_file = Some(Either::Right(path));
17396 None
17397 }
17398 })
17399 .collect();
17400
17401 let workspace = self.workspace();
17402
17403 cx.spawn_in(window, async move |editor, cx| {
17404 let locations: Vec<Location> = future::join_all(definitions)
17405 .await
17406 .into_iter()
17407 .filter_map(|location| location.transpose())
17408 .collect::<Result<_>>()
17409 .context("location tasks")?;
17410 let mut locations = cx.update(|_, cx| {
17411 locations
17412 .into_iter()
17413 .map(|location| {
17414 let buffer = location.buffer.read(cx);
17415 (location.buffer, location.range.to_point(buffer))
17416 })
17417 .into_group_map()
17418 })?;
17419 let mut num_locations = 0;
17420 for ranges in locations.values_mut() {
17421 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17422 ranges.dedup();
17423 num_locations += ranges.len();
17424 }
17425
17426 if num_locations > 1 {
17427 let tab_kind = match kind {
17428 Some(GotoDefinitionKind::Implementation) => "Implementations",
17429 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17430 Some(GotoDefinitionKind::Declaration) => "Declarations",
17431 Some(GotoDefinitionKind::Type) => "Types",
17432 };
17433 let title = editor
17434 .update_in(cx, |_, _, cx| {
17435 let target = locations
17436 .iter()
17437 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17438 .map(|(buffer, location)| {
17439 buffer
17440 .read(cx)
17441 .text_for_range(location.clone())
17442 .collect::<String>()
17443 })
17444 .filter(|text| !text.contains('\n'))
17445 .unique()
17446 .take(3)
17447 .join(", ");
17448 if target.is_empty() {
17449 tab_kind.to_owned()
17450 } else {
17451 format!("{tab_kind} for {target}")
17452 }
17453 })
17454 .context("buffer title")?;
17455
17456 let Some(workspace) = workspace else {
17457 return Ok(Navigated::No);
17458 };
17459
17460 let opened = workspace
17461 .update_in(cx, |workspace, window, cx| {
17462 let allow_preview = PreviewTabsSettings::get_global(cx)
17463 .enable_preview_multibuffer_from_code_navigation;
17464 Self::open_locations_in_multibuffer(
17465 workspace,
17466 locations,
17467 title,
17468 split,
17469 allow_preview,
17470 MultibufferSelectionMode::First,
17471 window,
17472 cx,
17473 )
17474 })
17475 .is_ok();
17476
17477 anyhow::Ok(Navigated::from_bool(opened))
17478 } else if num_locations == 0 {
17479 // If there is one url or file, open it directly
17480 match first_url_or_file {
17481 Some(Either::Left(url)) => {
17482 cx.update(|window, cx| {
17483 if parse_zed_link(&url, cx).is_some() {
17484 window
17485 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17486 } else {
17487 cx.open_url(&url);
17488 }
17489 })?;
17490 Ok(Navigated::Yes)
17491 }
17492 Some(Either::Right(path)) => {
17493 // TODO(andrew): respect preview tab settings
17494 // `enable_keep_preview_on_code_navigation` and
17495 // `enable_preview_file_from_code_navigation`
17496 let Some(workspace) = workspace else {
17497 return Ok(Navigated::No);
17498 };
17499 workspace
17500 .update_in(cx, |workspace, window, cx| {
17501 workspace.open_resolved_path(path, window, cx)
17502 })?
17503 .await?;
17504 Ok(Navigated::Yes)
17505 }
17506 None => Ok(Navigated::No),
17507 }
17508 } else {
17509 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17510 let target_range = target_ranges.first().unwrap().clone();
17511
17512 editor.update_in(cx, |editor, window, cx| {
17513 let range = target_range.to_point(target_buffer.read(cx));
17514 let range = editor.range_for_match(&range);
17515 let range = collapse_multiline_range(range);
17516
17517 if !split
17518 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17519 {
17520 editor.go_to_singleton_buffer_range(range, window, cx);
17521 } else {
17522 let Some(workspace) = workspace else {
17523 return Navigated::No;
17524 };
17525 let pane = workspace.read(cx).active_pane().clone();
17526 window.defer(cx, move |window, cx| {
17527 let target_editor: Entity<Self> =
17528 workspace.update(cx, |workspace, cx| {
17529 let pane = if split {
17530 workspace.adjacent_pane(window, cx)
17531 } else {
17532 workspace.active_pane().clone()
17533 };
17534
17535 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17536 let keep_old_preview = preview_tabs_settings
17537 .enable_keep_preview_on_code_navigation;
17538 let allow_new_preview = preview_tabs_settings
17539 .enable_preview_file_from_code_navigation;
17540
17541 workspace.open_project_item(
17542 pane,
17543 target_buffer.clone(),
17544 true,
17545 true,
17546 keep_old_preview,
17547 allow_new_preview,
17548 window,
17549 cx,
17550 )
17551 });
17552 target_editor.update(cx, |target_editor, cx| {
17553 // When selecting a definition in a different buffer, disable the nav history
17554 // to avoid creating a history entry at the previous cursor location.
17555 pane.update(cx, |pane, _| pane.disable_history());
17556 target_editor.go_to_singleton_buffer_range(range, window, cx);
17557 pane.update(cx, |pane, _| pane.enable_history());
17558 });
17559 });
17560 }
17561 Navigated::Yes
17562 })
17563 }
17564 })
17565 }
17566
17567 fn compute_target_location(
17568 &self,
17569 lsp_location: lsp::Location,
17570 server_id: LanguageServerId,
17571 window: &mut Window,
17572 cx: &mut Context<Self>,
17573 ) -> Task<anyhow::Result<Option<Location>>> {
17574 let Some(project) = self.project.clone() else {
17575 return Task::ready(Ok(None));
17576 };
17577
17578 cx.spawn_in(window, async move |editor, cx| {
17579 let location_task = editor.update(cx, |_, cx| {
17580 project.update(cx, |project, cx| {
17581 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17582 })
17583 })?;
17584 let location = Some({
17585 let target_buffer_handle = location_task.await.context("open local buffer")?;
17586 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17587 let target_start = target_buffer
17588 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17589 let target_end = target_buffer
17590 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17591 target_buffer.anchor_after(target_start)
17592 ..target_buffer.anchor_before(target_end)
17593 })?;
17594 Location {
17595 buffer: target_buffer_handle,
17596 range,
17597 }
17598 });
17599 Ok(location)
17600 })
17601 }
17602
17603 fn go_to_next_reference(
17604 &mut self,
17605 _: &GoToNextReference,
17606 window: &mut Window,
17607 cx: &mut Context<Self>,
17608 ) {
17609 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17610 if let Some(task) = task {
17611 task.detach();
17612 };
17613 }
17614
17615 fn go_to_prev_reference(
17616 &mut self,
17617 _: &GoToPreviousReference,
17618 window: &mut Window,
17619 cx: &mut Context<Self>,
17620 ) {
17621 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17622 if let Some(task) = task {
17623 task.detach();
17624 };
17625 }
17626
17627 pub fn go_to_reference_before_or_after_position(
17628 &mut self,
17629 direction: Direction,
17630 count: usize,
17631 window: &mut Window,
17632 cx: &mut Context<Self>,
17633 ) -> Option<Task<Result<()>>> {
17634 let selection = self.selections.newest_anchor();
17635 let head = selection.head();
17636
17637 let multi_buffer = self.buffer.read(cx);
17638
17639 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17640 let workspace = self.workspace()?;
17641 let project = workspace.read(cx).project().clone();
17642 let references =
17643 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17644 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17645 let Some(locations) = references.await? else {
17646 return Ok(());
17647 };
17648
17649 if locations.is_empty() {
17650 // totally normal - the cursor may be on something which is not
17651 // a symbol (e.g. a keyword)
17652 log::info!("no references found under cursor");
17653 return Ok(());
17654 }
17655
17656 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17657
17658 let (locations, current_location_index) =
17659 multi_buffer.update(cx, |multi_buffer, cx| {
17660 let mut locations = locations
17661 .into_iter()
17662 .filter_map(|loc| {
17663 let start = multi_buffer.buffer_anchor_to_anchor(
17664 &loc.buffer,
17665 loc.range.start,
17666 cx,
17667 )?;
17668 let end = multi_buffer.buffer_anchor_to_anchor(
17669 &loc.buffer,
17670 loc.range.end,
17671 cx,
17672 )?;
17673 Some(start..end)
17674 })
17675 .collect::<Vec<_>>();
17676
17677 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17678 // There is an O(n) implementation, but given this list will be
17679 // small (usually <100 items), the extra O(log(n)) factor isn't
17680 // worth the (surprisingly large amount of) extra complexity.
17681 locations
17682 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17683
17684 let head_offset = head.to_offset(&multi_buffer_snapshot);
17685
17686 let current_location_index = locations.iter().position(|loc| {
17687 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17688 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17689 });
17690
17691 (locations, current_location_index)
17692 })?;
17693
17694 let Some(current_location_index) = current_location_index else {
17695 // This indicates something has gone wrong, because we already
17696 // handle the "no references" case above
17697 log::error!(
17698 "failed to find current reference under cursor. Total references: {}",
17699 locations.len()
17700 );
17701 return Ok(());
17702 };
17703
17704 let destination_location_index = match direction {
17705 Direction::Next => (current_location_index + count) % locations.len(),
17706 Direction::Prev => {
17707 (current_location_index + locations.len() - count % locations.len())
17708 % locations.len()
17709 }
17710 };
17711
17712 // TODO(cameron): is this needed?
17713 // the thinking is to avoid "jumping to the current location" (avoid
17714 // polluting "jumplist" in vim terms)
17715 if current_location_index == destination_location_index {
17716 return Ok(());
17717 }
17718
17719 let Range { start, end } = locations[destination_location_index];
17720
17721 editor.update_in(cx, |editor, window, cx| {
17722 let effects = SelectionEffects::default();
17723
17724 editor.unfold_ranges(&[start..end], false, false, cx);
17725 editor.change_selections(effects, window, cx, |s| {
17726 s.select_ranges([start..start]);
17727 });
17728 })?;
17729
17730 Ok(())
17731 }))
17732 }
17733
17734 pub fn find_all_references(
17735 &mut self,
17736 action: &FindAllReferences,
17737 window: &mut Window,
17738 cx: &mut Context<Self>,
17739 ) -> Option<Task<Result<Navigated>>> {
17740 let always_open_multibuffer = action.always_open_multibuffer;
17741 let selection = self.selections.newest_anchor();
17742 let multi_buffer = self.buffer.read(cx);
17743 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17744 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17745 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17746 let head = selection_offset.head();
17747
17748 let head_anchor = multi_buffer_snapshot.anchor_at(
17749 head,
17750 if head < selection_offset.tail() {
17751 Bias::Right
17752 } else {
17753 Bias::Left
17754 },
17755 );
17756
17757 match self
17758 .find_all_references_task_sources
17759 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17760 {
17761 Ok(_) => {
17762 log::info!(
17763 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17764 );
17765 return None;
17766 }
17767 Err(i) => {
17768 self.find_all_references_task_sources.insert(i, head_anchor);
17769 }
17770 }
17771
17772 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17773 let workspace = self.workspace()?;
17774 let project = workspace.read(cx).project().clone();
17775 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17776 Some(cx.spawn_in(window, async move |editor, cx| {
17777 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17778 if let Ok(i) = editor
17779 .find_all_references_task_sources
17780 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17781 {
17782 editor.find_all_references_task_sources.remove(i);
17783 }
17784 });
17785
17786 let Some(locations) = references.await? else {
17787 return anyhow::Ok(Navigated::No);
17788 };
17789 let mut locations = cx.update(|_, cx| {
17790 locations
17791 .into_iter()
17792 .map(|location| {
17793 let buffer = location.buffer.read(cx);
17794 (location.buffer, location.range.to_point(buffer))
17795 })
17796 // if special-casing the single-match case, remove ranges
17797 // that intersect current selection
17798 .filter(|(location_buffer, location)| {
17799 if always_open_multibuffer || &buffer != location_buffer {
17800 return true;
17801 }
17802
17803 !location.contains_inclusive(&selection_point.range())
17804 })
17805 .into_group_map()
17806 })?;
17807 if locations.is_empty() {
17808 return anyhow::Ok(Navigated::No);
17809 }
17810 for ranges in locations.values_mut() {
17811 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17812 ranges.dedup();
17813 }
17814 let mut num_locations = 0;
17815 for ranges in locations.values_mut() {
17816 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17817 ranges.dedup();
17818 num_locations += ranges.len();
17819 }
17820
17821 if num_locations == 1 && !always_open_multibuffer {
17822 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17823 let target_range = target_ranges.first().unwrap().clone();
17824
17825 return editor.update_in(cx, |editor, window, cx| {
17826 let range = target_range.to_point(target_buffer.read(cx));
17827 let range = editor.range_for_match(&range);
17828 let range = range.start..range.start;
17829
17830 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17831 editor.go_to_singleton_buffer_range(range, window, cx);
17832 } else {
17833 let pane = workspace.read(cx).active_pane().clone();
17834 window.defer(cx, move |window, cx| {
17835 let target_editor: Entity<Self> =
17836 workspace.update(cx, |workspace, cx| {
17837 let pane = workspace.active_pane().clone();
17838
17839 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17840 let keep_old_preview = preview_tabs_settings
17841 .enable_keep_preview_on_code_navigation;
17842 let allow_new_preview = preview_tabs_settings
17843 .enable_preview_file_from_code_navigation;
17844
17845 workspace.open_project_item(
17846 pane,
17847 target_buffer.clone(),
17848 true,
17849 true,
17850 keep_old_preview,
17851 allow_new_preview,
17852 window,
17853 cx,
17854 )
17855 });
17856 target_editor.update(cx, |target_editor, cx| {
17857 // When selecting a definition in a different buffer, disable the nav history
17858 // to avoid creating a history entry at the previous cursor location.
17859 pane.update(cx, |pane, _| pane.disable_history());
17860 target_editor.go_to_singleton_buffer_range(range, window, cx);
17861 pane.update(cx, |pane, _| pane.enable_history());
17862 });
17863 });
17864 }
17865 Navigated::No
17866 });
17867 }
17868
17869 workspace.update_in(cx, |workspace, window, cx| {
17870 let target = locations
17871 .iter()
17872 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17873 .map(|(buffer, location)| {
17874 buffer
17875 .read(cx)
17876 .text_for_range(location.clone())
17877 .collect::<String>()
17878 })
17879 .filter(|text| !text.contains('\n'))
17880 .unique()
17881 .take(3)
17882 .join(", ");
17883 let title = if target.is_empty() {
17884 "References".to_owned()
17885 } else {
17886 format!("References to {target}")
17887 };
17888 let allow_preview = PreviewTabsSettings::get_global(cx)
17889 .enable_preview_multibuffer_from_code_navigation;
17890 Self::open_locations_in_multibuffer(
17891 workspace,
17892 locations,
17893 title,
17894 false,
17895 allow_preview,
17896 MultibufferSelectionMode::First,
17897 window,
17898 cx,
17899 );
17900 Navigated::Yes
17901 })
17902 }))
17903 }
17904
17905 /// Opens a multibuffer with the given project locations in it.
17906 pub fn open_locations_in_multibuffer(
17907 workspace: &mut Workspace,
17908 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17909 title: String,
17910 split: bool,
17911 allow_preview: bool,
17912 multibuffer_selection_mode: MultibufferSelectionMode,
17913 window: &mut Window,
17914 cx: &mut Context<Workspace>,
17915 ) {
17916 if locations.is_empty() {
17917 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17918 return;
17919 }
17920
17921 let capability = workspace.project().read(cx).capability();
17922 let mut ranges = <Vec<Range<Anchor>>>::new();
17923
17924 // a key to find existing multibuffer editors with the same set of locations
17925 // to prevent us from opening more and more multibuffer tabs for searches and the like
17926 let mut key = (title.clone(), vec![]);
17927 let excerpt_buffer = cx.new(|cx| {
17928 let key = &mut key.1;
17929 let mut multibuffer = MultiBuffer::new(capability);
17930 for (buffer, mut ranges_for_buffer) in locations {
17931 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17932 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17933 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17934 PathKey::for_buffer(&buffer, cx),
17935 buffer.clone(),
17936 ranges_for_buffer,
17937 multibuffer_context_lines(cx),
17938 cx,
17939 );
17940 ranges.extend(new_ranges)
17941 }
17942
17943 multibuffer.with_title(title)
17944 });
17945 let existing = workspace.active_pane().update(cx, |pane, cx| {
17946 pane.items()
17947 .filter_map(|item| item.downcast::<Editor>())
17948 .find(|editor| {
17949 editor
17950 .read(cx)
17951 .lookup_key
17952 .as_ref()
17953 .and_then(|it| {
17954 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17955 })
17956 .is_some_and(|it| *it == key)
17957 })
17958 });
17959 let was_existing = existing.is_some();
17960 let editor = existing.unwrap_or_else(|| {
17961 cx.new(|cx| {
17962 let mut editor = Editor::for_multibuffer(
17963 excerpt_buffer,
17964 Some(workspace.project().clone()),
17965 window,
17966 cx,
17967 );
17968 editor.lookup_key = Some(Box::new(key));
17969 editor
17970 })
17971 });
17972 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17973 MultibufferSelectionMode::First => {
17974 if let Some(first_range) = ranges.first() {
17975 editor.change_selections(
17976 SelectionEffects::no_scroll(),
17977 window,
17978 cx,
17979 |selections| {
17980 selections.clear_disjoint();
17981 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17982 },
17983 );
17984 }
17985 editor.highlight_background::<Self>(
17986 &ranges,
17987 |_, theme| theme.colors().editor_highlighted_line_background,
17988 cx,
17989 );
17990 }
17991 MultibufferSelectionMode::All => {
17992 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17993 selections.clear_disjoint();
17994 selections.select_anchor_ranges(ranges);
17995 });
17996 }
17997 });
17998
17999 let item = Box::new(editor);
18000
18001 let pane = if split {
18002 workspace.adjacent_pane(window, cx)
18003 } else {
18004 workspace.active_pane().clone()
18005 };
18006 let activate_pane = split;
18007
18008 let mut destination_index = None;
18009 pane.update(cx, |pane, cx| {
18010 if allow_preview && !was_existing {
18011 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18012 }
18013 if was_existing && !allow_preview {
18014 pane.unpreview_item_if_preview(item.item_id());
18015 }
18016 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18017 });
18018 }
18019
18020 pub fn rename(
18021 &mut self,
18022 _: &Rename,
18023 window: &mut Window,
18024 cx: &mut Context<Self>,
18025 ) -> Option<Task<Result<()>>> {
18026 use language::ToOffset as _;
18027
18028 let provider = self.semantics_provider.clone()?;
18029 let selection = self.selections.newest_anchor().clone();
18030 let (cursor_buffer, cursor_buffer_position) = self
18031 .buffer
18032 .read(cx)
18033 .text_anchor_for_position(selection.head(), cx)?;
18034 let (tail_buffer, cursor_buffer_position_end) = self
18035 .buffer
18036 .read(cx)
18037 .text_anchor_for_position(selection.tail(), cx)?;
18038 if tail_buffer != cursor_buffer {
18039 return None;
18040 }
18041
18042 let snapshot = cursor_buffer.read(cx).snapshot();
18043 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18044 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18045 let prepare_rename = provider
18046 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18047 .unwrap_or_else(|| Task::ready(Ok(None)));
18048 drop(snapshot);
18049
18050 Some(cx.spawn_in(window, async move |this, cx| {
18051 let rename_range = if let Some(range) = prepare_rename.await? {
18052 Some(range)
18053 } else {
18054 this.update(cx, |this, cx| {
18055 let buffer = this.buffer.read(cx).snapshot(cx);
18056 let mut buffer_highlights = this
18057 .document_highlights_for_position(selection.head(), &buffer)
18058 .filter(|highlight| {
18059 highlight.start.excerpt_id == selection.head().excerpt_id
18060 && highlight.end.excerpt_id == selection.head().excerpt_id
18061 });
18062 buffer_highlights
18063 .next()
18064 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18065 })?
18066 };
18067 if let Some(rename_range) = rename_range {
18068 this.update_in(cx, |this, window, cx| {
18069 let snapshot = cursor_buffer.read(cx).snapshot();
18070 let rename_buffer_range = rename_range.to_offset(&snapshot);
18071 let cursor_offset_in_rename_range =
18072 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18073 let cursor_offset_in_rename_range_end =
18074 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18075
18076 this.take_rename(false, window, cx);
18077 let buffer = this.buffer.read(cx).read(cx);
18078 let cursor_offset = selection.head().to_offset(&buffer);
18079 let rename_start =
18080 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18081 let rename_end = rename_start + rename_buffer_range.len();
18082 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18083 let mut old_highlight_id = None;
18084 let old_name: Arc<str> = buffer
18085 .chunks(rename_start..rename_end, true)
18086 .map(|chunk| {
18087 if old_highlight_id.is_none() {
18088 old_highlight_id = chunk.syntax_highlight_id;
18089 }
18090 chunk.text
18091 })
18092 .collect::<String>()
18093 .into();
18094
18095 drop(buffer);
18096
18097 // Position the selection in the rename editor so that it matches the current selection.
18098 this.show_local_selections = false;
18099 let rename_editor = cx.new(|cx| {
18100 let mut editor = Editor::single_line(window, cx);
18101 editor.buffer.update(cx, |buffer, cx| {
18102 buffer.edit(
18103 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18104 None,
18105 cx,
18106 )
18107 });
18108 let cursor_offset_in_rename_range =
18109 MultiBufferOffset(cursor_offset_in_rename_range);
18110 let cursor_offset_in_rename_range_end =
18111 MultiBufferOffset(cursor_offset_in_rename_range_end);
18112 let rename_selection_range = match cursor_offset_in_rename_range
18113 .cmp(&cursor_offset_in_rename_range_end)
18114 {
18115 Ordering::Equal => {
18116 editor.select_all(&SelectAll, window, cx);
18117 return editor;
18118 }
18119 Ordering::Less => {
18120 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18121 }
18122 Ordering::Greater => {
18123 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18124 }
18125 };
18126 if rename_selection_range.end.0 > old_name.len() {
18127 editor.select_all(&SelectAll, window, cx);
18128 } else {
18129 editor.change_selections(Default::default(), window, cx, |s| {
18130 s.select_ranges([rename_selection_range]);
18131 });
18132 }
18133 editor
18134 });
18135 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18136 if e == &EditorEvent::Focused {
18137 cx.emit(EditorEvent::FocusedIn)
18138 }
18139 })
18140 .detach();
18141
18142 let write_highlights =
18143 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18144 let read_highlights =
18145 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18146 let ranges = write_highlights
18147 .iter()
18148 .flat_map(|(_, ranges)| ranges.iter())
18149 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18150 .cloned()
18151 .collect();
18152
18153 this.highlight_text::<Rename>(
18154 ranges,
18155 HighlightStyle {
18156 fade_out: Some(0.6),
18157 ..Default::default()
18158 },
18159 cx,
18160 );
18161 let rename_focus_handle = rename_editor.focus_handle(cx);
18162 window.focus(&rename_focus_handle);
18163 let block_id = this.insert_blocks(
18164 [BlockProperties {
18165 style: BlockStyle::Flex,
18166 placement: BlockPlacement::Below(range.start),
18167 height: Some(1),
18168 render: Arc::new({
18169 let rename_editor = rename_editor.clone();
18170 move |cx: &mut BlockContext| {
18171 let mut text_style = cx.editor_style.text.clone();
18172 if let Some(highlight_style) = old_highlight_id
18173 .and_then(|h| h.style(&cx.editor_style.syntax))
18174 {
18175 text_style = text_style.highlight(highlight_style);
18176 }
18177 div()
18178 .block_mouse_except_scroll()
18179 .pl(cx.anchor_x)
18180 .child(EditorElement::new(
18181 &rename_editor,
18182 EditorStyle {
18183 background: cx.theme().system().transparent,
18184 local_player: cx.editor_style.local_player,
18185 text: text_style,
18186 scrollbar_width: cx.editor_style.scrollbar_width,
18187 syntax: cx.editor_style.syntax.clone(),
18188 status: cx.editor_style.status.clone(),
18189 inlay_hints_style: HighlightStyle {
18190 font_weight: Some(FontWeight::BOLD),
18191 ..make_inlay_hints_style(cx.app)
18192 },
18193 edit_prediction_styles: make_suggestion_styles(
18194 cx.app,
18195 ),
18196 ..EditorStyle::default()
18197 },
18198 ))
18199 .into_any_element()
18200 }
18201 }),
18202 priority: 0,
18203 }],
18204 Some(Autoscroll::fit()),
18205 cx,
18206 )[0];
18207 this.pending_rename = Some(RenameState {
18208 range,
18209 old_name,
18210 editor: rename_editor,
18211 block_id,
18212 });
18213 })?;
18214 }
18215
18216 Ok(())
18217 }))
18218 }
18219
18220 pub fn confirm_rename(
18221 &mut self,
18222 _: &ConfirmRename,
18223 window: &mut Window,
18224 cx: &mut Context<Self>,
18225 ) -> Option<Task<Result<()>>> {
18226 let rename = self.take_rename(false, window, cx)?;
18227 let workspace = self.workspace()?.downgrade();
18228 let (buffer, start) = self
18229 .buffer
18230 .read(cx)
18231 .text_anchor_for_position(rename.range.start, cx)?;
18232 let (end_buffer, _) = self
18233 .buffer
18234 .read(cx)
18235 .text_anchor_for_position(rename.range.end, cx)?;
18236 if buffer != end_buffer {
18237 return None;
18238 }
18239
18240 let old_name = rename.old_name;
18241 let new_name = rename.editor.read(cx).text(cx);
18242
18243 let rename = self.semantics_provider.as_ref()?.perform_rename(
18244 &buffer,
18245 start,
18246 new_name.clone(),
18247 cx,
18248 )?;
18249
18250 Some(cx.spawn_in(window, async move |editor, cx| {
18251 let project_transaction = rename.await?;
18252 Self::open_project_transaction(
18253 &editor,
18254 workspace,
18255 project_transaction,
18256 format!("Rename: {} → {}", old_name, new_name),
18257 cx,
18258 )
18259 .await?;
18260
18261 editor.update(cx, |editor, cx| {
18262 editor.refresh_document_highlights(cx);
18263 })?;
18264 Ok(())
18265 }))
18266 }
18267
18268 fn take_rename(
18269 &mut self,
18270 moving_cursor: bool,
18271 window: &mut Window,
18272 cx: &mut Context<Self>,
18273 ) -> Option<RenameState> {
18274 let rename = self.pending_rename.take()?;
18275 if rename.editor.focus_handle(cx).is_focused(window) {
18276 window.focus(&self.focus_handle);
18277 }
18278
18279 self.remove_blocks(
18280 [rename.block_id].into_iter().collect(),
18281 Some(Autoscroll::fit()),
18282 cx,
18283 );
18284 self.clear_highlights::<Rename>(cx);
18285 self.show_local_selections = true;
18286
18287 if moving_cursor {
18288 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18289 editor
18290 .selections
18291 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18292 .head()
18293 });
18294
18295 // Update the selection to match the position of the selection inside
18296 // the rename editor.
18297 let snapshot = self.buffer.read(cx).read(cx);
18298 let rename_range = rename.range.to_offset(&snapshot);
18299 let cursor_in_editor = snapshot
18300 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18301 .min(rename_range.end);
18302 drop(snapshot);
18303
18304 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18305 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18306 });
18307 } else {
18308 self.refresh_document_highlights(cx);
18309 }
18310
18311 Some(rename)
18312 }
18313
18314 pub fn pending_rename(&self) -> Option<&RenameState> {
18315 self.pending_rename.as_ref()
18316 }
18317
18318 fn format(
18319 &mut self,
18320 _: &Format,
18321 window: &mut Window,
18322 cx: &mut Context<Self>,
18323 ) -> Option<Task<Result<()>>> {
18324 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18325
18326 let project = match &self.project {
18327 Some(project) => project.clone(),
18328 None => return None,
18329 };
18330
18331 Some(self.perform_format(
18332 project,
18333 FormatTrigger::Manual,
18334 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18335 window,
18336 cx,
18337 ))
18338 }
18339
18340 fn format_selections(
18341 &mut self,
18342 _: &FormatSelections,
18343 window: &mut Window,
18344 cx: &mut Context<Self>,
18345 ) -> Option<Task<Result<()>>> {
18346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18347
18348 let project = match &self.project {
18349 Some(project) => project.clone(),
18350 None => return None,
18351 };
18352
18353 let ranges = self
18354 .selections
18355 .all_adjusted(&self.display_snapshot(cx))
18356 .into_iter()
18357 .map(|selection| selection.range())
18358 .collect_vec();
18359
18360 Some(self.perform_format(
18361 project,
18362 FormatTrigger::Manual,
18363 FormatTarget::Ranges(ranges),
18364 window,
18365 cx,
18366 ))
18367 }
18368
18369 fn perform_format(
18370 &mut self,
18371 project: Entity<Project>,
18372 trigger: FormatTrigger,
18373 target: FormatTarget,
18374 window: &mut Window,
18375 cx: &mut Context<Self>,
18376 ) -> Task<Result<()>> {
18377 let buffer = self.buffer.clone();
18378 let (buffers, target) = match target {
18379 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18380 FormatTarget::Ranges(selection_ranges) => {
18381 let multi_buffer = buffer.read(cx);
18382 let snapshot = multi_buffer.read(cx);
18383 let mut buffers = HashSet::default();
18384 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18385 BTreeMap::new();
18386 for selection_range in selection_ranges {
18387 for (buffer, buffer_range, _) in
18388 snapshot.range_to_buffer_ranges(selection_range)
18389 {
18390 let buffer_id = buffer.remote_id();
18391 let start = buffer.anchor_before(buffer_range.start);
18392 let end = buffer.anchor_after(buffer_range.end);
18393 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18394 buffer_id_to_ranges
18395 .entry(buffer_id)
18396 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18397 .or_insert_with(|| vec![start..end]);
18398 }
18399 }
18400 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18401 }
18402 };
18403
18404 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18405 let selections_prev = transaction_id_prev
18406 .and_then(|transaction_id_prev| {
18407 // default to selections as they were after the last edit, if we have them,
18408 // instead of how they are now.
18409 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18410 // will take you back to where you made the last edit, instead of staying where you scrolled
18411 self.selection_history
18412 .transaction(transaction_id_prev)
18413 .map(|t| t.0.clone())
18414 })
18415 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18416
18417 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18418 let format = project.update(cx, |project, cx| {
18419 project.format(buffers, target, true, trigger, cx)
18420 });
18421
18422 cx.spawn_in(window, async move |editor, cx| {
18423 let transaction = futures::select_biased! {
18424 transaction = format.log_err().fuse() => transaction,
18425 () = timeout => {
18426 log::warn!("timed out waiting for formatting");
18427 None
18428 }
18429 };
18430
18431 buffer
18432 .update(cx, |buffer, cx| {
18433 if let Some(transaction) = transaction
18434 && !buffer.is_singleton()
18435 {
18436 buffer.push_transaction(&transaction.0, cx);
18437 }
18438 cx.notify();
18439 })
18440 .ok();
18441
18442 if let Some(transaction_id_now) =
18443 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18444 {
18445 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18446 if has_new_transaction {
18447 _ = editor.update(cx, |editor, _| {
18448 editor
18449 .selection_history
18450 .insert_transaction(transaction_id_now, selections_prev);
18451 });
18452 }
18453 }
18454
18455 Ok(())
18456 })
18457 }
18458
18459 fn organize_imports(
18460 &mut self,
18461 _: &OrganizeImports,
18462 window: &mut Window,
18463 cx: &mut Context<Self>,
18464 ) -> Option<Task<Result<()>>> {
18465 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18466 let project = match &self.project {
18467 Some(project) => project.clone(),
18468 None => return None,
18469 };
18470 Some(self.perform_code_action_kind(
18471 project,
18472 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18473 window,
18474 cx,
18475 ))
18476 }
18477
18478 fn perform_code_action_kind(
18479 &mut self,
18480 project: Entity<Project>,
18481 kind: CodeActionKind,
18482 window: &mut Window,
18483 cx: &mut Context<Self>,
18484 ) -> Task<Result<()>> {
18485 let buffer = self.buffer.clone();
18486 let buffers = buffer.read(cx).all_buffers();
18487 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18488 let apply_action = project.update(cx, |project, cx| {
18489 project.apply_code_action_kind(buffers, kind, true, cx)
18490 });
18491 cx.spawn_in(window, async move |_, cx| {
18492 let transaction = futures::select_biased! {
18493 () = timeout => {
18494 log::warn!("timed out waiting for executing code action");
18495 None
18496 }
18497 transaction = apply_action.log_err().fuse() => transaction,
18498 };
18499 buffer
18500 .update(cx, |buffer, cx| {
18501 // check if we need this
18502 if let Some(transaction) = transaction
18503 && !buffer.is_singleton()
18504 {
18505 buffer.push_transaction(&transaction.0, cx);
18506 }
18507 cx.notify();
18508 })
18509 .ok();
18510 Ok(())
18511 })
18512 }
18513
18514 pub fn restart_language_server(
18515 &mut self,
18516 _: &RestartLanguageServer,
18517 _: &mut Window,
18518 cx: &mut Context<Self>,
18519 ) {
18520 if let Some(project) = self.project.clone() {
18521 self.buffer.update(cx, |multi_buffer, cx| {
18522 project.update(cx, |project, cx| {
18523 project.restart_language_servers_for_buffers(
18524 multi_buffer.all_buffers().into_iter().collect(),
18525 HashSet::default(),
18526 cx,
18527 );
18528 });
18529 })
18530 }
18531 }
18532
18533 pub fn stop_language_server(
18534 &mut self,
18535 _: &StopLanguageServer,
18536 _: &mut Window,
18537 cx: &mut Context<Self>,
18538 ) {
18539 if let Some(project) = self.project.clone() {
18540 self.buffer.update(cx, |multi_buffer, cx| {
18541 project.update(cx, |project, cx| {
18542 project.stop_language_servers_for_buffers(
18543 multi_buffer.all_buffers().into_iter().collect(),
18544 HashSet::default(),
18545 cx,
18546 );
18547 });
18548 });
18549 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18550 }
18551 }
18552
18553 fn cancel_language_server_work(
18554 workspace: &mut Workspace,
18555 _: &actions::CancelLanguageServerWork,
18556 _: &mut Window,
18557 cx: &mut Context<Workspace>,
18558 ) {
18559 let project = workspace.project();
18560 let buffers = workspace
18561 .active_item(cx)
18562 .and_then(|item| item.act_as::<Editor>(cx))
18563 .map_or(HashSet::default(), |editor| {
18564 editor.read(cx).buffer.read(cx).all_buffers()
18565 });
18566 project.update(cx, |project, cx| {
18567 project.cancel_language_server_work_for_buffers(buffers, cx);
18568 });
18569 }
18570
18571 fn show_character_palette(
18572 &mut self,
18573 _: &ShowCharacterPalette,
18574 window: &mut Window,
18575 _: &mut Context<Self>,
18576 ) {
18577 window.show_character_palette();
18578 }
18579
18580 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18581 if !self.diagnostics_enabled() {
18582 return;
18583 }
18584
18585 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18586 let buffer = self.buffer.read(cx).snapshot(cx);
18587 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18588 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18589 let is_valid = buffer
18590 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18591 .any(|entry| {
18592 entry.diagnostic.is_primary
18593 && !entry.range.is_empty()
18594 && entry.range.start == primary_range_start
18595 && entry.diagnostic.message == active_diagnostics.active_message
18596 });
18597
18598 if !is_valid {
18599 self.dismiss_diagnostics(cx);
18600 }
18601 }
18602 }
18603
18604 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18605 match &self.active_diagnostics {
18606 ActiveDiagnostic::Group(group) => Some(group),
18607 _ => None,
18608 }
18609 }
18610
18611 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18612 if !self.diagnostics_enabled() {
18613 return;
18614 }
18615 self.dismiss_diagnostics(cx);
18616 self.active_diagnostics = ActiveDiagnostic::All;
18617 }
18618
18619 fn activate_diagnostics(
18620 &mut self,
18621 buffer_id: BufferId,
18622 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18623 window: &mut Window,
18624 cx: &mut Context<Self>,
18625 ) {
18626 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18627 return;
18628 }
18629 self.dismiss_diagnostics(cx);
18630 let snapshot = self.snapshot(window, cx);
18631 let buffer = self.buffer.read(cx).snapshot(cx);
18632 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18633 return;
18634 };
18635
18636 let diagnostic_group = buffer
18637 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18638 .collect::<Vec<_>>();
18639
18640 let language_registry = self
18641 .project()
18642 .map(|project| project.read(cx).languages().clone());
18643
18644 let blocks = renderer.render_group(
18645 diagnostic_group,
18646 buffer_id,
18647 snapshot,
18648 cx.weak_entity(),
18649 language_registry,
18650 cx,
18651 );
18652
18653 let blocks = self.display_map.update(cx, |display_map, cx| {
18654 display_map.insert_blocks(blocks, cx).into_iter().collect()
18655 });
18656 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18657 active_range: buffer.anchor_before(diagnostic.range.start)
18658 ..buffer.anchor_after(diagnostic.range.end),
18659 active_message: diagnostic.diagnostic.message.clone(),
18660 group_id: diagnostic.diagnostic.group_id,
18661 blocks,
18662 });
18663 cx.notify();
18664 }
18665
18666 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18667 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18668 return;
18669 };
18670
18671 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18672 if let ActiveDiagnostic::Group(group) = prev {
18673 self.display_map.update(cx, |display_map, cx| {
18674 display_map.remove_blocks(group.blocks, cx);
18675 });
18676 cx.notify();
18677 }
18678 }
18679
18680 /// Disable inline diagnostics rendering for this editor.
18681 pub fn disable_inline_diagnostics(&mut self) {
18682 self.inline_diagnostics_enabled = false;
18683 self.inline_diagnostics_update = Task::ready(());
18684 self.inline_diagnostics.clear();
18685 }
18686
18687 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18688 self.diagnostics_enabled = false;
18689 self.dismiss_diagnostics(cx);
18690 self.inline_diagnostics_update = Task::ready(());
18691 self.inline_diagnostics.clear();
18692 }
18693
18694 pub fn disable_word_completions(&mut self) {
18695 self.word_completions_enabled = false;
18696 }
18697
18698 pub fn diagnostics_enabled(&self) -> bool {
18699 self.diagnostics_enabled && self.mode.is_full()
18700 }
18701
18702 pub fn inline_diagnostics_enabled(&self) -> bool {
18703 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18704 }
18705
18706 pub fn show_inline_diagnostics(&self) -> bool {
18707 self.show_inline_diagnostics
18708 }
18709
18710 pub fn toggle_inline_diagnostics(
18711 &mut self,
18712 _: &ToggleInlineDiagnostics,
18713 window: &mut Window,
18714 cx: &mut Context<Editor>,
18715 ) {
18716 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18717 self.refresh_inline_diagnostics(false, window, cx);
18718 }
18719
18720 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18721 self.diagnostics_max_severity = severity;
18722 self.display_map.update(cx, |display_map, _| {
18723 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18724 });
18725 }
18726
18727 pub fn toggle_diagnostics(
18728 &mut self,
18729 _: &ToggleDiagnostics,
18730 window: &mut Window,
18731 cx: &mut Context<Editor>,
18732 ) {
18733 if !self.diagnostics_enabled() {
18734 return;
18735 }
18736
18737 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18738 EditorSettings::get_global(cx)
18739 .diagnostics_max_severity
18740 .filter(|severity| severity != &DiagnosticSeverity::Off)
18741 .unwrap_or(DiagnosticSeverity::Hint)
18742 } else {
18743 DiagnosticSeverity::Off
18744 };
18745 self.set_max_diagnostics_severity(new_severity, cx);
18746 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18747 self.active_diagnostics = ActiveDiagnostic::None;
18748 self.inline_diagnostics_update = Task::ready(());
18749 self.inline_diagnostics.clear();
18750 } else {
18751 self.refresh_inline_diagnostics(false, window, cx);
18752 }
18753
18754 cx.notify();
18755 }
18756
18757 pub fn toggle_minimap(
18758 &mut self,
18759 _: &ToggleMinimap,
18760 window: &mut Window,
18761 cx: &mut Context<Editor>,
18762 ) {
18763 if self.supports_minimap(cx) {
18764 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18765 }
18766 }
18767
18768 fn refresh_inline_diagnostics(
18769 &mut self,
18770 debounce: bool,
18771 window: &mut Window,
18772 cx: &mut Context<Self>,
18773 ) {
18774 let max_severity = ProjectSettings::get_global(cx)
18775 .diagnostics
18776 .inline
18777 .max_severity
18778 .unwrap_or(self.diagnostics_max_severity);
18779
18780 if !self.inline_diagnostics_enabled()
18781 || !self.diagnostics_enabled()
18782 || !self.show_inline_diagnostics
18783 || max_severity == DiagnosticSeverity::Off
18784 {
18785 self.inline_diagnostics_update = Task::ready(());
18786 self.inline_diagnostics.clear();
18787 return;
18788 }
18789
18790 let debounce_ms = ProjectSettings::get_global(cx)
18791 .diagnostics
18792 .inline
18793 .update_debounce_ms;
18794 let debounce = if debounce && debounce_ms > 0 {
18795 Some(Duration::from_millis(debounce_ms))
18796 } else {
18797 None
18798 };
18799 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18800 if let Some(debounce) = debounce {
18801 cx.background_executor().timer(debounce).await;
18802 }
18803 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18804 editor
18805 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18806 .ok()
18807 }) else {
18808 return;
18809 };
18810
18811 let new_inline_diagnostics = cx
18812 .background_spawn(async move {
18813 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18814 for diagnostic_entry in
18815 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18816 {
18817 let message = diagnostic_entry
18818 .diagnostic
18819 .message
18820 .split_once('\n')
18821 .map(|(line, _)| line)
18822 .map(SharedString::new)
18823 .unwrap_or_else(|| {
18824 SharedString::new(&*diagnostic_entry.diagnostic.message)
18825 });
18826 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18827 let (Ok(i) | Err(i)) = inline_diagnostics
18828 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18829 inline_diagnostics.insert(
18830 i,
18831 (
18832 start_anchor,
18833 InlineDiagnostic {
18834 message,
18835 group_id: diagnostic_entry.diagnostic.group_id,
18836 start: diagnostic_entry.range.start.to_point(&snapshot),
18837 is_primary: diagnostic_entry.diagnostic.is_primary,
18838 severity: diagnostic_entry.diagnostic.severity,
18839 },
18840 ),
18841 );
18842 }
18843 inline_diagnostics
18844 })
18845 .await;
18846
18847 editor
18848 .update(cx, |editor, cx| {
18849 editor.inline_diagnostics = new_inline_diagnostics;
18850 cx.notify();
18851 })
18852 .ok();
18853 });
18854 }
18855
18856 fn pull_diagnostics(
18857 &mut self,
18858 buffer_id: Option<BufferId>,
18859 window: &Window,
18860 cx: &mut Context<Self>,
18861 ) -> Option<()> {
18862 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18863 return None;
18864 }
18865 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18866 .diagnostics
18867 .lsp_pull_diagnostics;
18868 if !pull_diagnostics_settings.enabled {
18869 return None;
18870 }
18871 let project = self.project()?.downgrade();
18872
18873 let mut edited_buffer_ids = HashSet::default();
18874 let mut edited_worktree_ids = HashSet::default();
18875 let edited_buffers = match buffer_id {
18876 Some(buffer_id) => {
18877 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18878 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18879 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18880 edited_worktree_ids.insert(worktree_id);
18881 vec![buffer]
18882 }
18883 None => self
18884 .buffer()
18885 .read(cx)
18886 .all_buffers()
18887 .into_iter()
18888 .filter(|buffer| {
18889 let buffer = buffer.read(cx);
18890 match buffer.file().map(|f| f.worktree_id(cx)) {
18891 Some(worktree_id) => {
18892 edited_buffer_ids.insert(buffer.remote_id());
18893 edited_worktree_ids.insert(worktree_id);
18894 true
18895 }
18896 None => false,
18897 }
18898 })
18899 .collect::<Vec<_>>(),
18900 };
18901
18902 if edited_buffers.is_empty() {
18903 self.pull_diagnostics_task = Task::ready(());
18904 self.pull_diagnostics_background_task = Task::ready(());
18905 return None;
18906 }
18907
18908 let mut already_used_buffers = HashSet::default();
18909 let related_open_buffers = self
18910 .workspace
18911 .as_ref()
18912 .and_then(|(workspace, _)| workspace.upgrade())
18913 .into_iter()
18914 .flat_map(|workspace| workspace.read(cx).panes())
18915 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18916 .filter(|editor| editor != &cx.entity())
18917 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18918 .filter(|buffer| {
18919 let buffer = buffer.read(cx);
18920 let buffer_id = buffer.remote_id();
18921 if already_used_buffers.insert(buffer_id) {
18922 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18923 return !edited_buffer_ids.contains(&buffer_id)
18924 && !edited_worktree_ids.contains(&worktree_id);
18925 }
18926 }
18927 false
18928 })
18929 .collect::<Vec<_>>();
18930
18931 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18932 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18933 if buffers.is_empty() {
18934 return Task::ready(());
18935 }
18936 let project_weak = project.clone();
18937 cx.spawn_in(window, async move |_, cx| {
18938 cx.background_executor().timer(delay).await;
18939
18940 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18941 buffers
18942 .into_iter()
18943 .filter_map(|buffer| {
18944 project_weak
18945 .update(cx, |project, cx| {
18946 project.lsp_store().update(cx, |lsp_store, cx| {
18947 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18948 })
18949 })
18950 .ok()
18951 })
18952 .collect::<FuturesUnordered<_>>()
18953 }) else {
18954 return;
18955 };
18956
18957 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18958 if let Err(e) = pull_task {
18959 log::error!("Failed to update project diagnostics: {e:#}");
18960 }
18961 }
18962 })
18963 };
18964
18965 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18966 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18967
18968 Some(())
18969 }
18970
18971 pub fn set_selections_from_remote(
18972 &mut self,
18973 selections: Vec<Selection<Anchor>>,
18974 pending_selection: Option<Selection<Anchor>>,
18975 window: &mut Window,
18976 cx: &mut Context<Self>,
18977 ) {
18978 let old_cursor_position = self.selections.newest_anchor().head();
18979 self.selections
18980 .change_with(&self.display_snapshot(cx), |s| {
18981 s.select_anchors(selections);
18982 if let Some(pending_selection) = pending_selection {
18983 s.set_pending(pending_selection, SelectMode::Character);
18984 } else {
18985 s.clear_pending();
18986 }
18987 });
18988 self.selections_did_change(
18989 false,
18990 &old_cursor_position,
18991 SelectionEffects::default(),
18992 window,
18993 cx,
18994 );
18995 }
18996
18997 pub fn transact(
18998 &mut self,
18999 window: &mut Window,
19000 cx: &mut Context<Self>,
19001 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19002 ) -> Option<TransactionId> {
19003 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19004 this.start_transaction_at(Instant::now(), window, cx);
19005 update(this, window, cx);
19006 this.end_transaction_at(Instant::now(), cx)
19007 })
19008 }
19009
19010 pub fn start_transaction_at(
19011 &mut self,
19012 now: Instant,
19013 window: &mut Window,
19014 cx: &mut Context<Self>,
19015 ) -> Option<TransactionId> {
19016 self.end_selection(window, cx);
19017 if let Some(tx_id) = self
19018 .buffer
19019 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19020 {
19021 self.selection_history
19022 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19023 cx.emit(EditorEvent::TransactionBegun {
19024 transaction_id: tx_id,
19025 });
19026 Some(tx_id)
19027 } else {
19028 None
19029 }
19030 }
19031
19032 pub fn end_transaction_at(
19033 &mut self,
19034 now: Instant,
19035 cx: &mut Context<Self>,
19036 ) -> Option<TransactionId> {
19037 if let Some(transaction_id) = self
19038 .buffer
19039 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19040 {
19041 if let Some((_, end_selections)) =
19042 self.selection_history.transaction_mut(transaction_id)
19043 {
19044 *end_selections = Some(self.selections.disjoint_anchors_arc());
19045 } else {
19046 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19047 }
19048
19049 cx.emit(EditorEvent::Edited { transaction_id });
19050 Some(transaction_id)
19051 } else {
19052 None
19053 }
19054 }
19055
19056 pub fn modify_transaction_selection_history(
19057 &mut self,
19058 transaction_id: TransactionId,
19059 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19060 ) -> bool {
19061 self.selection_history
19062 .transaction_mut(transaction_id)
19063 .map(modify)
19064 .is_some()
19065 }
19066
19067 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19068 if self.selection_mark_mode {
19069 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19070 s.move_with(|_, sel| {
19071 sel.collapse_to(sel.head(), SelectionGoal::None);
19072 });
19073 })
19074 }
19075 self.selection_mark_mode = true;
19076 cx.notify();
19077 }
19078
19079 pub fn swap_selection_ends(
19080 &mut self,
19081 _: &actions::SwapSelectionEnds,
19082 window: &mut Window,
19083 cx: &mut Context<Self>,
19084 ) {
19085 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19086 s.move_with(|_, sel| {
19087 if sel.start != sel.end {
19088 sel.reversed = !sel.reversed
19089 }
19090 });
19091 });
19092 self.request_autoscroll(Autoscroll::newest(), cx);
19093 cx.notify();
19094 }
19095
19096 pub fn toggle_focus(
19097 workspace: &mut Workspace,
19098 _: &actions::ToggleFocus,
19099 window: &mut Window,
19100 cx: &mut Context<Workspace>,
19101 ) {
19102 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19103 return;
19104 };
19105 workspace.activate_item(&item, true, true, window, cx);
19106 }
19107
19108 pub fn toggle_fold(
19109 &mut self,
19110 _: &actions::ToggleFold,
19111 window: &mut Window,
19112 cx: &mut Context<Self>,
19113 ) {
19114 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19115 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19116 let selection = self.selections.newest::<Point>(&display_map);
19117
19118 let range = if selection.is_empty() {
19119 let point = selection.head().to_display_point(&display_map);
19120 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19121 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19122 .to_point(&display_map);
19123 start..end
19124 } else {
19125 selection.range()
19126 };
19127 if display_map.folds_in_range(range).next().is_some() {
19128 self.unfold_lines(&Default::default(), window, cx)
19129 } else {
19130 self.fold(&Default::default(), window, cx)
19131 }
19132 } else {
19133 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19134 let buffer_ids: HashSet<_> = self
19135 .selections
19136 .disjoint_anchor_ranges()
19137 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19138 .collect();
19139
19140 let should_unfold = buffer_ids
19141 .iter()
19142 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19143
19144 for buffer_id in buffer_ids {
19145 if should_unfold {
19146 self.unfold_buffer(buffer_id, cx);
19147 } else {
19148 self.fold_buffer(buffer_id, cx);
19149 }
19150 }
19151 }
19152 }
19153
19154 pub fn toggle_fold_recursive(
19155 &mut self,
19156 _: &actions::ToggleFoldRecursive,
19157 window: &mut Window,
19158 cx: &mut Context<Self>,
19159 ) {
19160 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19161
19162 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19163 let range = if selection.is_empty() {
19164 let point = selection.head().to_display_point(&display_map);
19165 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19166 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19167 .to_point(&display_map);
19168 start..end
19169 } else {
19170 selection.range()
19171 };
19172 if display_map.folds_in_range(range).next().is_some() {
19173 self.unfold_recursive(&Default::default(), window, cx)
19174 } else {
19175 self.fold_recursive(&Default::default(), window, cx)
19176 }
19177 }
19178
19179 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19180 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19181 let mut to_fold = Vec::new();
19182 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19183 let selections = self.selections.all_adjusted(&display_map);
19184
19185 for selection in selections {
19186 let range = selection.range().sorted();
19187 let buffer_start_row = range.start.row;
19188
19189 if range.start.row != range.end.row {
19190 let mut found = false;
19191 let mut row = range.start.row;
19192 while row <= range.end.row {
19193 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19194 {
19195 found = true;
19196 row = crease.range().end.row + 1;
19197 to_fold.push(crease);
19198 } else {
19199 row += 1
19200 }
19201 }
19202 if found {
19203 continue;
19204 }
19205 }
19206
19207 for row in (0..=range.start.row).rev() {
19208 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19209 && crease.range().end.row >= buffer_start_row
19210 {
19211 to_fold.push(crease);
19212 if row <= range.start.row {
19213 break;
19214 }
19215 }
19216 }
19217 }
19218
19219 self.fold_creases(to_fold, true, window, cx);
19220 } else {
19221 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19222 let buffer_ids = self
19223 .selections
19224 .disjoint_anchor_ranges()
19225 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19226 .collect::<HashSet<_>>();
19227 for buffer_id in buffer_ids {
19228 self.fold_buffer(buffer_id, cx);
19229 }
19230 }
19231 }
19232
19233 pub fn toggle_fold_all(
19234 &mut self,
19235 _: &actions::ToggleFoldAll,
19236 window: &mut Window,
19237 cx: &mut Context<Self>,
19238 ) {
19239 if self.buffer.read(cx).is_singleton() {
19240 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19241 let has_folds = display_map
19242 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19243 .next()
19244 .is_some();
19245
19246 if has_folds {
19247 self.unfold_all(&actions::UnfoldAll, window, cx);
19248 } else {
19249 self.fold_all(&actions::FoldAll, window, cx);
19250 }
19251 } else {
19252 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19253 let should_unfold = buffer_ids
19254 .iter()
19255 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19256
19257 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19258 editor
19259 .update_in(cx, |editor, _, cx| {
19260 for buffer_id in buffer_ids {
19261 if should_unfold {
19262 editor.unfold_buffer(buffer_id, cx);
19263 } else {
19264 editor.fold_buffer(buffer_id, cx);
19265 }
19266 }
19267 })
19268 .ok();
19269 });
19270 }
19271 }
19272
19273 fn fold_at_level(
19274 &mut self,
19275 fold_at: &FoldAtLevel,
19276 window: &mut Window,
19277 cx: &mut Context<Self>,
19278 ) {
19279 if !self.buffer.read(cx).is_singleton() {
19280 return;
19281 }
19282
19283 let fold_at_level = fold_at.0;
19284 let snapshot = self.buffer.read(cx).snapshot(cx);
19285 let mut to_fold = Vec::new();
19286 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19287
19288 let row_ranges_to_keep: Vec<Range<u32>> = self
19289 .selections
19290 .all::<Point>(&self.display_snapshot(cx))
19291 .into_iter()
19292 .map(|sel| sel.start.row..sel.end.row)
19293 .collect();
19294
19295 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19296 while start_row < end_row {
19297 match self
19298 .snapshot(window, cx)
19299 .crease_for_buffer_row(MultiBufferRow(start_row))
19300 {
19301 Some(crease) => {
19302 let nested_start_row = crease.range().start.row + 1;
19303 let nested_end_row = crease.range().end.row;
19304
19305 if current_level < fold_at_level {
19306 stack.push((nested_start_row, nested_end_row, current_level + 1));
19307 } else if current_level == fold_at_level {
19308 // Fold iff there is no selection completely contained within the fold region
19309 if !row_ranges_to_keep.iter().any(|selection| {
19310 selection.end >= nested_start_row
19311 && selection.start <= nested_end_row
19312 }) {
19313 to_fold.push(crease);
19314 }
19315 }
19316
19317 start_row = nested_end_row + 1;
19318 }
19319 None => start_row += 1,
19320 }
19321 }
19322 }
19323
19324 self.fold_creases(to_fold, true, window, cx);
19325 }
19326
19327 pub fn fold_at_level_1(
19328 &mut self,
19329 _: &actions::FoldAtLevel1,
19330 window: &mut Window,
19331 cx: &mut Context<Self>,
19332 ) {
19333 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19334 }
19335
19336 pub fn fold_at_level_2(
19337 &mut self,
19338 _: &actions::FoldAtLevel2,
19339 window: &mut Window,
19340 cx: &mut Context<Self>,
19341 ) {
19342 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19343 }
19344
19345 pub fn fold_at_level_3(
19346 &mut self,
19347 _: &actions::FoldAtLevel3,
19348 window: &mut Window,
19349 cx: &mut Context<Self>,
19350 ) {
19351 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19352 }
19353
19354 pub fn fold_at_level_4(
19355 &mut self,
19356 _: &actions::FoldAtLevel4,
19357 window: &mut Window,
19358 cx: &mut Context<Self>,
19359 ) {
19360 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19361 }
19362
19363 pub fn fold_at_level_5(
19364 &mut self,
19365 _: &actions::FoldAtLevel5,
19366 window: &mut Window,
19367 cx: &mut Context<Self>,
19368 ) {
19369 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19370 }
19371
19372 pub fn fold_at_level_6(
19373 &mut self,
19374 _: &actions::FoldAtLevel6,
19375 window: &mut Window,
19376 cx: &mut Context<Self>,
19377 ) {
19378 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19379 }
19380
19381 pub fn fold_at_level_7(
19382 &mut self,
19383 _: &actions::FoldAtLevel7,
19384 window: &mut Window,
19385 cx: &mut Context<Self>,
19386 ) {
19387 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19388 }
19389
19390 pub fn fold_at_level_8(
19391 &mut self,
19392 _: &actions::FoldAtLevel8,
19393 window: &mut Window,
19394 cx: &mut Context<Self>,
19395 ) {
19396 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19397 }
19398
19399 pub fn fold_at_level_9(
19400 &mut self,
19401 _: &actions::FoldAtLevel9,
19402 window: &mut Window,
19403 cx: &mut Context<Self>,
19404 ) {
19405 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19406 }
19407
19408 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19409 if self.buffer.read(cx).is_singleton() {
19410 let mut fold_ranges = Vec::new();
19411 let snapshot = self.buffer.read(cx).snapshot(cx);
19412
19413 for row in 0..snapshot.max_row().0 {
19414 if let Some(foldable_range) = self
19415 .snapshot(window, cx)
19416 .crease_for_buffer_row(MultiBufferRow(row))
19417 {
19418 fold_ranges.push(foldable_range);
19419 }
19420 }
19421
19422 self.fold_creases(fold_ranges, true, window, cx);
19423 } else {
19424 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19425 editor
19426 .update_in(cx, |editor, _, cx| {
19427 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19428 editor.fold_buffer(buffer_id, cx);
19429 }
19430 })
19431 .ok();
19432 });
19433 }
19434 }
19435
19436 pub fn fold_function_bodies(
19437 &mut self,
19438 _: &actions::FoldFunctionBodies,
19439 window: &mut Window,
19440 cx: &mut Context<Self>,
19441 ) {
19442 let snapshot = self.buffer.read(cx).snapshot(cx);
19443
19444 let ranges = snapshot
19445 .text_object_ranges(
19446 MultiBufferOffset(0)..snapshot.len(),
19447 TreeSitterOptions::default(),
19448 )
19449 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19450 .collect::<Vec<_>>();
19451
19452 let creases = ranges
19453 .into_iter()
19454 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19455 .collect();
19456
19457 self.fold_creases(creases, true, window, cx);
19458 }
19459
19460 pub fn fold_recursive(
19461 &mut self,
19462 _: &actions::FoldRecursive,
19463 window: &mut Window,
19464 cx: &mut Context<Self>,
19465 ) {
19466 let mut to_fold = Vec::new();
19467 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19468 let selections = self.selections.all_adjusted(&display_map);
19469
19470 for selection in selections {
19471 let range = selection.range().sorted();
19472 let buffer_start_row = range.start.row;
19473
19474 if range.start.row != range.end.row {
19475 let mut found = false;
19476 for row in range.start.row..=range.end.row {
19477 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19478 found = true;
19479 to_fold.push(crease);
19480 }
19481 }
19482 if found {
19483 continue;
19484 }
19485 }
19486
19487 for row in (0..=range.start.row).rev() {
19488 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19489 if crease.range().end.row >= buffer_start_row {
19490 to_fold.push(crease);
19491 } else {
19492 break;
19493 }
19494 }
19495 }
19496 }
19497
19498 self.fold_creases(to_fold, true, window, cx);
19499 }
19500
19501 pub fn fold_at(
19502 &mut self,
19503 buffer_row: MultiBufferRow,
19504 window: &mut Window,
19505 cx: &mut Context<Self>,
19506 ) {
19507 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19508
19509 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19510 let autoscroll = self
19511 .selections
19512 .all::<Point>(&display_map)
19513 .iter()
19514 .any(|selection| crease.range().overlaps(&selection.range()));
19515
19516 self.fold_creases(vec![crease], autoscroll, window, cx);
19517 }
19518 }
19519
19520 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19521 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19522 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19523 let buffer = display_map.buffer_snapshot();
19524 let selections = self.selections.all::<Point>(&display_map);
19525 let ranges = selections
19526 .iter()
19527 .map(|s| {
19528 let range = s.display_range(&display_map).sorted();
19529 let mut start = range.start.to_point(&display_map);
19530 let mut end = range.end.to_point(&display_map);
19531 start.column = 0;
19532 end.column = buffer.line_len(MultiBufferRow(end.row));
19533 start..end
19534 })
19535 .collect::<Vec<_>>();
19536
19537 self.unfold_ranges(&ranges, true, true, cx);
19538 } else {
19539 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19540 let buffer_ids = self
19541 .selections
19542 .disjoint_anchor_ranges()
19543 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19544 .collect::<HashSet<_>>();
19545 for buffer_id in buffer_ids {
19546 self.unfold_buffer(buffer_id, cx);
19547 }
19548 }
19549 }
19550
19551 pub fn unfold_recursive(
19552 &mut self,
19553 _: &UnfoldRecursive,
19554 _window: &mut Window,
19555 cx: &mut Context<Self>,
19556 ) {
19557 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19558 let selections = self.selections.all::<Point>(&display_map);
19559 let ranges = selections
19560 .iter()
19561 .map(|s| {
19562 let mut range = s.display_range(&display_map).sorted();
19563 *range.start.column_mut() = 0;
19564 *range.end.column_mut() = display_map.line_len(range.end.row());
19565 let start = range.start.to_point(&display_map);
19566 let end = range.end.to_point(&display_map);
19567 start..end
19568 })
19569 .collect::<Vec<_>>();
19570
19571 self.unfold_ranges(&ranges, true, true, cx);
19572 }
19573
19574 pub fn unfold_at(
19575 &mut self,
19576 buffer_row: MultiBufferRow,
19577 _window: &mut Window,
19578 cx: &mut Context<Self>,
19579 ) {
19580 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19581
19582 let intersection_range = Point::new(buffer_row.0, 0)
19583 ..Point::new(
19584 buffer_row.0,
19585 display_map.buffer_snapshot().line_len(buffer_row),
19586 );
19587
19588 let autoscroll = self
19589 .selections
19590 .all::<Point>(&display_map)
19591 .iter()
19592 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19593
19594 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19595 }
19596
19597 pub fn unfold_all(
19598 &mut self,
19599 _: &actions::UnfoldAll,
19600 _window: &mut Window,
19601 cx: &mut Context<Self>,
19602 ) {
19603 if self.buffer.read(cx).is_singleton() {
19604 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19605 self.unfold_ranges(
19606 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19607 true,
19608 true,
19609 cx,
19610 );
19611 } else {
19612 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19613 editor
19614 .update(cx, |editor, cx| {
19615 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19616 editor.unfold_buffer(buffer_id, cx);
19617 }
19618 })
19619 .ok();
19620 });
19621 }
19622 }
19623
19624 pub fn fold_selected_ranges(
19625 &mut self,
19626 _: &FoldSelectedRanges,
19627 window: &mut Window,
19628 cx: &mut Context<Self>,
19629 ) {
19630 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19631 let selections = self.selections.all_adjusted(&display_map);
19632 let ranges = selections
19633 .into_iter()
19634 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19635 .collect::<Vec<_>>();
19636 self.fold_creases(ranges, true, window, cx);
19637 }
19638
19639 pub fn fold_ranges<T: ToOffset + Clone>(
19640 &mut self,
19641 ranges: Vec<Range<T>>,
19642 auto_scroll: bool,
19643 window: &mut Window,
19644 cx: &mut Context<Self>,
19645 ) {
19646 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19647 let ranges = ranges
19648 .into_iter()
19649 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19650 .collect::<Vec<_>>();
19651 self.fold_creases(ranges, auto_scroll, window, cx);
19652 }
19653
19654 pub fn fold_creases<T: ToOffset + Clone>(
19655 &mut self,
19656 creases: Vec<Crease<T>>,
19657 auto_scroll: bool,
19658 _window: &mut Window,
19659 cx: &mut Context<Self>,
19660 ) {
19661 if creases.is_empty() {
19662 return;
19663 }
19664
19665 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19666
19667 if auto_scroll {
19668 self.request_autoscroll(Autoscroll::fit(), cx);
19669 }
19670
19671 cx.notify();
19672
19673 self.scrollbar_marker_state.dirty = true;
19674 self.folds_did_change(cx);
19675 }
19676
19677 /// Removes any folds whose ranges intersect any of the given ranges.
19678 pub fn unfold_ranges<T: ToOffset + Clone>(
19679 &mut self,
19680 ranges: &[Range<T>],
19681 inclusive: bool,
19682 auto_scroll: bool,
19683 cx: &mut Context<Self>,
19684 ) {
19685 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19686 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19687 });
19688 self.folds_did_change(cx);
19689 }
19690
19691 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19692 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19693 return;
19694 }
19695
19696 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19697 self.display_map.update(cx, |display_map, cx| {
19698 display_map.fold_buffers([buffer_id], cx)
19699 });
19700
19701 let snapshot = self.display_snapshot(cx);
19702 self.selections.change_with(&snapshot, |selections| {
19703 selections.remove_selections_from_buffer(buffer_id);
19704 });
19705
19706 cx.emit(EditorEvent::BufferFoldToggled {
19707 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19708 folded: true,
19709 });
19710 cx.notify();
19711 }
19712
19713 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19714 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19715 return;
19716 }
19717 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19718 self.display_map.update(cx, |display_map, cx| {
19719 display_map.unfold_buffers([buffer_id], cx);
19720 });
19721 cx.emit(EditorEvent::BufferFoldToggled {
19722 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19723 folded: false,
19724 });
19725 cx.notify();
19726 }
19727
19728 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19729 self.display_map.read(cx).is_buffer_folded(buffer)
19730 }
19731
19732 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19733 self.display_map.read(cx).folded_buffers()
19734 }
19735
19736 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19737 self.display_map.update(cx, |display_map, cx| {
19738 display_map.disable_header_for_buffer(buffer_id, cx);
19739 });
19740 cx.notify();
19741 }
19742
19743 /// Removes any folds with the given ranges.
19744 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19745 &mut self,
19746 ranges: &[Range<T>],
19747 type_id: TypeId,
19748 auto_scroll: bool,
19749 cx: &mut Context<Self>,
19750 ) {
19751 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19752 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19753 });
19754 self.folds_did_change(cx);
19755 }
19756
19757 fn remove_folds_with<T: ToOffset + Clone>(
19758 &mut self,
19759 ranges: &[Range<T>],
19760 auto_scroll: bool,
19761 cx: &mut Context<Self>,
19762 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19763 ) {
19764 if ranges.is_empty() {
19765 return;
19766 }
19767
19768 let mut buffers_affected = HashSet::default();
19769 let multi_buffer = self.buffer().read(cx);
19770 for range in ranges {
19771 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19772 buffers_affected.insert(buffer.read(cx).remote_id());
19773 };
19774 }
19775
19776 self.display_map.update(cx, update);
19777
19778 if auto_scroll {
19779 self.request_autoscroll(Autoscroll::fit(), cx);
19780 }
19781
19782 cx.notify();
19783 self.scrollbar_marker_state.dirty = true;
19784 self.active_indent_guides_state.dirty = true;
19785 }
19786
19787 pub fn update_renderer_widths(
19788 &mut self,
19789 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19790 cx: &mut Context<Self>,
19791 ) -> bool {
19792 self.display_map
19793 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19794 }
19795
19796 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19797 self.display_map.read(cx).fold_placeholder.clone()
19798 }
19799
19800 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19801 self.use_base_text_line_numbers = show;
19802 }
19803
19804 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19805 self.buffer.update(cx, |buffer, cx| {
19806 buffer.set_all_diff_hunks_expanded(cx);
19807 });
19808 }
19809
19810 pub fn expand_all_diff_hunks(
19811 &mut self,
19812 _: &ExpandAllDiffHunks,
19813 _window: &mut Window,
19814 cx: &mut Context<Self>,
19815 ) {
19816 self.buffer.update(cx, |buffer, cx| {
19817 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19818 });
19819 }
19820
19821 pub fn collapse_all_diff_hunks(
19822 &mut self,
19823 _: &CollapseAllDiffHunks,
19824 _window: &mut Window,
19825 cx: &mut Context<Self>,
19826 ) {
19827 self.buffer.update(cx, |buffer, cx| {
19828 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19829 });
19830 }
19831
19832 pub fn toggle_selected_diff_hunks(
19833 &mut self,
19834 _: &ToggleSelectedDiffHunks,
19835 _window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) {
19838 let ranges: Vec<_> = self
19839 .selections
19840 .disjoint_anchors()
19841 .iter()
19842 .map(|s| s.range())
19843 .collect();
19844 self.toggle_diff_hunks_in_ranges(ranges, cx);
19845 }
19846
19847 pub fn diff_hunks_in_ranges<'a>(
19848 &'a self,
19849 ranges: &'a [Range<Anchor>],
19850 buffer: &'a MultiBufferSnapshot,
19851 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19852 ranges.iter().flat_map(move |range| {
19853 let end_excerpt_id = range.end.excerpt_id;
19854 let range = range.to_point(buffer);
19855 let mut peek_end = range.end;
19856 if range.end.row < buffer.max_row().0 {
19857 peek_end = Point::new(range.end.row + 1, 0);
19858 }
19859 buffer
19860 .diff_hunks_in_range(range.start..peek_end)
19861 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19862 })
19863 }
19864
19865 pub fn has_stageable_diff_hunks_in_ranges(
19866 &self,
19867 ranges: &[Range<Anchor>],
19868 snapshot: &MultiBufferSnapshot,
19869 ) -> bool {
19870 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19871 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19872 }
19873
19874 pub fn toggle_staged_selected_diff_hunks(
19875 &mut self,
19876 _: &::git::ToggleStaged,
19877 _: &mut Window,
19878 cx: &mut Context<Self>,
19879 ) {
19880 let snapshot = self.buffer.read(cx).snapshot(cx);
19881 let ranges: Vec<_> = self
19882 .selections
19883 .disjoint_anchors()
19884 .iter()
19885 .map(|s| s.range())
19886 .collect();
19887 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19888 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19889 }
19890
19891 pub fn set_render_diff_hunk_controls(
19892 &mut self,
19893 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19894 cx: &mut Context<Self>,
19895 ) {
19896 self.render_diff_hunk_controls = render_diff_hunk_controls;
19897 cx.notify();
19898 }
19899
19900 pub fn stage_and_next(
19901 &mut self,
19902 _: &::git::StageAndNext,
19903 window: &mut Window,
19904 cx: &mut Context<Self>,
19905 ) {
19906 self.do_stage_or_unstage_and_next(true, window, cx);
19907 }
19908
19909 pub fn unstage_and_next(
19910 &mut self,
19911 _: &::git::UnstageAndNext,
19912 window: &mut Window,
19913 cx: &mut Context<Self>,
19914 ) {
19915 self.do_stage_or_unstage_and_next(false, window, cx);
19916 }
19917
19918 pub fn stage_or_unstage_diff_hunks(
19919 &mut self,
19920 stage: bool,
19921 ranges: Vec<Range<Anchor>>,
19922 cx: &mut Context<Self>,
19923 ) {
19924 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19925 cx.spawn(async move |this, cx| {
19926 task.await?;
19927 this.update(cx, |this, cx| {
19928 let snapshot = this.buffer.read(cx).snapshot(cx);
19929 let chunk_by = this
19930 .diff_hunks_in_ranges(&ranges, &snapshot)
19931 .chunk_by(|hunk| hunk.buffer_id);
19932 for (buffer_id, hunks) in &chunk_by {
19933 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19934 }
19935 })
19936 })
19937 .detach_and_log_err(cx);
19938 }
19939
19940 fn save_buffers_for_ranges_if_needed(
19941 &mut self,
19942 ranges: &[Range<Anchor>],
19943 cx: &mut Context<Editor>,
19944 ) -> Task<Result<()>> {
19945 let multibuffer = self.buffer.read(cx);
19946 let snapshot = multibuffer.read(cx);
19947 let buffer_ids: HashSet<_> = ranges
19948 .iter()
19949 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19950 .collect();
19951 drop(snapshot);
19952
19953 let mut buffers = HashSet::default();
19954 for buffer_id in buffer_ids {
19955 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19956 let buffer = buffer_entity.read(cx);
19957 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19958 {
19959 buffers.insert(buffer_entity);
19960 }
19961 }
19962 }
19963
19964 if let Some(project) = &self.project {
19965 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19966 } else {
19967 Task::ready(Ok(()))
19968 }
19969 }
19970
19971 fn do_stage_or_unstage_and_next(
19972 &mut self,
19973 stage: bool,
19974 window: &mut Window,
19975 cx: &mut Context<Self>,
19976 ) {
19977 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19978
19979 if ranges.iter().any(|range| range.start != range.end) {
19980 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19981 return;
19982 }
19983
19984 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19985 let snapshot = self.snapshot(window, cx);
19986 let position = self
19987 .selections
19988 .newest::<Point>(&snapshot.display_snapshot)
19989 .head();
19990 let mut row = snapshot
19991 .buffer_snapshot()
19992 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19993 .find(|hunk| hunk.row_range.start.0 > position.row)
19994 .map(|hunk| hunk.row_range.start);
19995
19996 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19997 // Outside of the project diff editor, wrap around to the beginning.
19998 if !all_diff_hunks_expanded {
19999 row = row.or_else(|| {
20000 snapshot
20001 .buffer_snapshot()
20002 .diff_hunks_in_range(Point::zero()..position)
20003 .find(|hunk| hunk.row_range.end.0 < position.row)
20004 .map(|hunk| hunk.row_range.start)
20005 });
20006 }
20007
20008 if let Some(row) = row {
20009 let destination = Point::new(row.0, 0);
20010 let autoscroll = Autoscroll::center();
20011
20012 self.unfold_ranges(&[destination..destination], false, false, cx);
20013 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20014 s.select_ranges([destination..destination]);
20015 });
20016 }
20017 }
20018
20019 fn do_stage_or_unstage(
20020 &self,
20021 stage: bool,
20022 buffer_id: BufferId,
20023 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20024 cx: &mut App,
20025 ) -> Option<()> {
20026 let project = self.project()?;
20027 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20028 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20029 let buffer_snapshot = buffer.read(cx).snapshot();
20030 let file_exists = buffer_snapshot
20031 .file()
20032 .is_some_and(|file| file.disk_state().exists());
20033 diff.update(cx, |diff, cx| {
20034 diff.stage_or_unstage_hunks(
20035 stage,
20036 &hunks
20037 .map(|hunk| buffer_diff::DiffHunk {
20038 buffer_range: hunk.buffer_range,
20039 // We don't need to pass in word diffs here because they're only used for rendering and
20040 // this function changes internal state
20041 base_word_diffs: Vec::default(),
20042 buffer_word_diffs: Vec::default(),
20043 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20044 ..hunk.diff_base_byte_range.end.0,
20045 secondary_status: hunk.secondary_status,
20046 range: Point::zero()..Point::zero(), // unused
20047 })
20048 .collect::<Vec<_>>(),
20049 &buffer_snapshot,
20050 file_exists,
20051 cx,
20052 )
20053 });
20054 None
20055 }
20056
20057 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20058 let ranges: Vec<_> = self
20059 .selections
20060 .disjoint_anchors()
20061 .iter()
20062 .map(|s| s.range())
20063 .collect();
20064 self.buffer
20065 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20066 }
20067
20068 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20069 self.buffer.update(cx, |buffer, cx| {
20070 let ranges = vec![Anchor::min()..Anchor::max()];
20071 if !buffer.all_diff_hunks_expanded()
20072 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20073 {
20074 buffer.collapse_diff_hunks(ranges, cx);
20075 true
20076 } else {
20077 false
20078 }
20079 })
20080 }
20081
20082 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20083 if self.buffer.read(cx).all_diff_hunks_expanded() {
20084 return true;
20085 }
20086 let ranges = vec![Anchor::min()..Anchor::max()];
20087 self.buffer
20088 .read(cx)
20089 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20090 }
20091
20092 fn toggle_diff_hunks_in_ranges(
20093 &mut self,
20094 ranges: Vec<Range<Anchor>>,
20095 cx: &mut Context<Editor>,
20096 ) {
20097 self.buffer.update(cx, |buffer, cx| {
20098 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20099 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20100 })
20101 }
20102
20103 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20104 self.buffer.update(cx, |buffer, cx| {
20105 let snapshot = buffer.snapshot(cx);
20106 let excerpt_id = range.end.excerpt_id;
20107 let point_range = range.to_point(&snapshot);
20108 let expand = !buffer.single_hunk_is_expanded(range, cx);
20109 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20110 })
20111 }
20112
20113 pub(crate) fn apply_all_diff_hunks(
20114 &mut self,
20115 _: &ApplyAllDiffHunks,
20116 window: &mut Window,
20117 cx: &mut Context<Self>,
20118 ) {
20119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20120
20121 let buffers = self.buffer.read(cx).all_buffers();
20122 for branch_buffer in buffers {
20123 branch_buffer.update(cx, |branch_buffer, cx| {
20124 branch_buffer.merge_into_base(Vec::new(), cx);
20125 });
20126 }
20127
20128 if let Some(project) = self.project.clone() {
20129 self.save(
20130 SaveOptions {
20131 format: true,
20132 autosave: false,
20133 },
20134 project,
20135 window,
20136 cx,
20137 )
20138 .detach_and_log_err(cx);
20139 }
20140 }
20141
20142 pub(crate) fn apply_selected_diff_hunks(
20143 &mut self,
20144 _: &ApplyDiffHunk,
20145 window: &mut Window,
20146 cx: &mut Context<Self>,
20147 ) {
20148 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20149 let snapshot = self.snapshot(window, cx);
20150 let hunks = snapshot.hunks_for_ranges(
20151 self.selections
20152 .all(&snapshot.display_snapshot)
20153 .into_iter()
20154 .map(|selection| selection.range()),
20155 );
20156 let mut ranges_by_buffer = HashMap::default();
20157 self.transact(window, cx, |editor, _window, cx| {
20158 for hunk in hunks {
20159 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20160 ranges_by_buffer
20161 .entry(buffer.clone())
20162 .or_insert_with(Vec::new)
20163 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20164 }
20165 }
20166
20167 for (buffer, ranges) in ranges_by_buffer {
20168 buffer.update(cx, |buffer, cx| {
20169 buffer.merge_into_base(ranges, cx);
20170 });
20171 }
20172 });
20173
20174 if let Some(project) = self.project.clone() {
20175 self.save(
20176 SaveOptions {
20177 format: true,
20178 autosave: false,
20179 },
20180 project,
20181 window,
20182 cx,
20183 )
20184 .detach_and_log_err(cx);
20185 }
20186 }
20187
20188 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20189 if hovered != self.gutter_hovered {
20190 self.gutter_hovered = hovered;
20191 cx.notify();
20192 }
20193 }
20194
20195 pub fn insert_blocks(
20196 &mut self,
20197 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20198 autoscroll: Option<Autoscroll>,
20199 cx: &mut Context<Self>,
20200 ) -> Vec<CustomBlockId> {
20201 let blocks = self
20202 .display_map
20203 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20204 if let Some(autoscroll) = autoscroll {
20205 self.request_autoscroll(autoscroll, cx);
20206 }
20207 cx.notify();
20208 blocks
20209 }
20210
20211 pub fn resize_blocks(
20212 &mut self,
20213 heights: HashMap<CustomBlockId, u32>,
20214 autoscroll: Option<Autoscroll>,
20215 cx: &mut Context<Self>,
20216 ) {
20217 self.display_map
20218 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20219 if let Some(autoscroll) = autoscroll {
20220 self.request_autoscroll(autoscroll, cx);
20221 }
20222 cx.notify();
20223 }
20224
20225 pub fn replace_blocks(
20226 &mut self,
20227 renderers: HashMap<CustomBlockId, RenderBlock>,
20228 autoscroll: Option<Autoscroll>,
20229 cx: &mut Context<Self>,
20230 ) {
20231 self.display_map
20232 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20233 if let Some(autoscroll) = autoscroll {
20234 self.request_autoscroll(autoscroll, cx);
20235 }
20236 cx.notify();
20237 }
20238
20239 pub fn remove_blocks(
20240 &mut self,
20241 block_ids: HashSet<CustomBlockId>,
20242 autoscroll: Option<Autoscroll>,
20243 cx: &mut Context<Self>,
20244 ) {
20245 self.display_map.update(cx, |display_map, cx| {
20246 display_map.remove_blocks(block_ids, cx)
20247 });
20248 if let Some(autoscroll) = autoscroll {
20249 self.request_autoscroll(autoscroll, cx);
20250 }
20251 cx.notify();
20252 }
20253
20254 pub fn row_for_block(
20255 &self,
20256 block_id: CustomBlockId,
20257 cx: &mut Context<Self>,
20258 ) -> Option<DisplayRow> {
20259 self.display_map
20260 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20261 }
20262
20263 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20264 self.focused_block = Some(focused_block);
20265 }
20266
20267 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20268 self.focused_block.take()
20269 }
20270
20271 pub fn insert_creases(
20272 &mut self,
20273 creases: impl IntoIterator<Item = Crease<Anchor>>,
20274 cx: &mut Context<Self>,
20275 ) -> Vec<CreaseId> {
20276 self.display_map
20277 .update(cx, |map, cx| map.insert_creases(creases, cx))
20278 }
20279
20280 pub fn remove_creases(
20281 &mut self,
20282 ids: impl IntoIterator<Item = CreaseId>,
20283 cx: &mut Context<Self>,
20284 ) -> Vec<(CreaseId, Range<Anchor>)> {
20285 self.display_map
20286 .update(cx, |map, cx| map.remove_creases(ids, cx))
20287 }
20288
20289 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20290 self.display_map
20291 .update(cx, |map, cx| map.snapshot(cx))
20292 .longest_row()
20293 }
20294
20295 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20296 self.display_map
20297 .update(cx, |map, cx| map.snapshot(cx))
20298 .max_point()
20299 }
20300
20301 pub fn text(&self, cx: &App) -> String {
20302 self.buffer.read(cx).read(cx).text()
20303 }
20304
20305 pub fn is_empty(&self, cx: &App) -> bool {
20306 self.buffer.read(cx).read(cx).is_empty()
20307 }
20308
20309 pub fn text_option(&self, cx: &App) -> Option<String> {
20310 let text = self.text(cx);
20311 let text = text.trim();
20312
20313 if text.is_empty() {
20314 return None;
20315 }
20316
20317 Some(text.to_string())
20318 }
20319
20320 pub fn set_text(
20321 &mut self,
20322 text: impl Into<Arc<str>>,
20323 window: &mut Window,
20324 cx: &mut Context<Self>,
20325 ) {
20326 self.transact(window, cx, |this, _, cx| {
20327 this.buffer
20328 .read(cx)
20329 .as_singleton()
20330 .expect("you can only call set_text on editors for singleton buffers")
20331 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20332 });
20333 }
20334
20335 pub fn display_text(&self, cx: &mut App) -> String {
20336 self.display_map
20337 .update(cx, |map, cx| map.snapshot(cx))
20338 .text()
20339 }
20340
20341 fn create_minimap(
20342 &self,
20343 minimap_settings: MinimapSettings,
20344 window: &mut Window,
20345 cx: &mut Context<Self>,
20346 ) -> Option<Entity<Self>> {
20347 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20348 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20349 }
20350
20351 fn initialize_new_minimap(
20352 &self,
20353 minimap_settings: MinimapSettings,
20354 window: &mut Window,
20355 cx: &mut Context<Self>,
20356 ) -> Entity<Self> {
20357 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20358
20359 let mut minimap = Editor::new_internal(
20360 EditorMode::Minimap {
20361 parent: cx.weak_entity(),
20362 },
20363 self.buffer.clone(),
20364 None,
20365 Some(self.display_map.clone()),
20366 window,
20367 cx,
20368 );
20369 minimap.scroll_manager.clone_state(&self.scroll_manager);
20370 minimap.set_text_style_refinement(TextStyleRefinement {
20371 font_size: Some(MINIMAP_FONT_SIZE),
20372 font_weight: Some(MINIMAP_FONT_WEIGHT),
20373 ..Default::default()
20374 });
20375 minimap.update_minimap_configuration(minimap_settings, cx);
20376 cx.new(|_| minimap)
20377 }
20378
20379 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20380 let current_line_highlight = minimap_settings
20381 .current_line_highlight
20382 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20383 self.set_current_line_highlight(Some(current_line_highlight));
20384 }
20385
20386 pub fn minimap(&self) -> Option<&Entity<Self>> {
20387 self.minimap
20388 .as_ref()
20389 .filter(|_| self.minimap_visibility.visible())
20390 }
20391
20392 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20393 let mut wrap_guides = smallvec![];
20394
20395 if self.show_wrap_guides == Some(false) {
20396 return wrap_guides;
20397 }
20398
20399 let settings = self.buffer.read(cx).language_settings(cx);
20400 if settings.show_wrap_guides {
20401 match self.soft_wrap_mode(cx) {
20402 SoftWrap::Column(soft_wrap) => {
20403 wrap_guides.push((soft_wrap as usize, true));
20404 }
20405 SoftWrap::Bounded(soft_wrap) => {
20406 wrap_guides.push((soft_wrap as usize, true));
20407 }
20408 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20409 }
20410 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20411 }
20412
20413 wrap_guides
20414 }
20415
20416 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20417 let settings = self.buffer.read(cx).language_settings(cx);
20418 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20419 match mode {
20420 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20421 SoftWrap::None
20422 }
20423 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20424 language_settings::SoftWrap::PreferredLineLength => {
20425 SoftWrap::Column(settings.preferred_line_length)
20426 }
20427 language_settings::SoftWrap::Bounded => {
20428 SoftWrap::Bounded(settings.preferred_line_length)
20429 }
20430 }
20431 }
20432
20433 pub fn set_soft_wrap_mode(
20434 &mut self,
20435 mode: language_settings::SoftWrap,
20436
20437 cx: &mut Context<Self>,
20438 ) {
20439 self.soft_wrap_mode_override = Some(mode);
20440 cx.notify();
20441 }
20442
20443 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20444 self.hard_wrap = hard_wrap;
20445 cx.notify();
20446 }
20447
20448 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20449 self.text_style_refinement = Some(style);
20450 }
20451
20452 /// called by the Element so we know what style we were most recently rendered with.
20453 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20454 // We intentionally do not inform the display map about the minimap style
20455 // so that wrapping is not recalculated and stays consistent for the editor
20456 // and its linked minimap.
20457 if !self.mode.is_minimap() {
20458 let font = style.text.font();
20459 let font_size = style.text.font_size.to_pixels(window.rem_size());
20460 let display_map = self
20461 .placeholder_display_map
20462 .as_ref()
20463 .filter(|_| self.is_empty(cx))
20464 .unwrap_or(&self.display_map);
20465
20466 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20467 }
20468 self.style = Some(style);
20469 }
20470
20471 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20472 if self.style.is_none() {
20473 self.style = Some(self.create_style(cx));
20474 }
20475 self.style.as_ref().unwrap()
20476 }
20477
20478 // Called by the element. This method is not designed to be called outside of the editor
20479 // element's layout code because it does not notify when rewrapping is computed synchronously.
20480 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20481 if self.is_empty(cx) {
20482 self.placeholder_display_map
20483 .as_ref()
20484 .map_or(false, |display_map| {
20485 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20486 })
20487 } else {
20488 self.display_map
20489 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20490 }
20491 }
20492
20493 pub fn set_soft_wrap(&mut self) {
20494 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20495 }
20496
20497 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20498 if self.soft_wrap_mode_override.is_some() {
20499 self.soft_wrap_mode_override.take();
20500 } else {
20501 let soft_wrap = match self.soft_wrap_mode(cx) {
20502 SoftWrap::GitDiff => return,
20503 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20504 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20505 language_settings::SoftWrap::None
20506 }
20507 };
20508 self.soft_wrap_mode_override = Some(soft_wrap);
20509 }
20510 cx.notify();
20511 }
20512
20513 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20514 let Some(workspace) = self.workspace() else {
20515 return;
20516 };
20517 let fs = workspace.read(cx).app_state().fs.clone();
20518 let current_show = TabBarSettings::get_global(cx).show;
20519 update_settings_file(fs, cx, move |setting, _| {
20520 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20521 });
20522 }
20523
20524 pub fn toggle_indent_guides(
20525 &mut self,
20526 _: &ToggleIndentGuides,
20527 _: &mut Window,
20528 cx: &mut Context<Self>,
20529 ) {
20530 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20531 self.buffer
20532 .read(cx)
20533 .language_settings(cx)
20534 .indent_guides
20535 .enabled
20536 });
20537 self.show_indent_guides = Some(!currently_enabled);
20538 cx.notify();
20539 }
20540
20541 fn should_show_indent_guides(&self) -> Option<bool> {
20542 self.show_indent_guides
20543 }
20544
20545 pub fn disable_indent_guides_for_buffer(
20546 &mut self,
20547 buffer_id: BufferId,
20548 cx: &mut Context<Self>,
20549 ) {
20550 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20551 cx.notify();
20552 }
20553
20554 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20555 self.buffers_with_disabled_indent_guides
20556 .contains(&buffer_id)
20557 }
20558
20559 pub fn toggle_line_numbers(
20560 &mut self,
20561 _: &ToggleLineNumbers,
20562 _: &mut Window,
20563 cx: &mut Context<Self>,
20564 ) {
20565 let mut editor_settings = EditorSettings::get_global(cx).clone();
20566 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20567 EditorSettings::override_global(editor_settings, cx);
20568 }
20569
20570 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20571 if let Some(show_line_numbers) = self.show_line_numbers {
20572 return show_line_numbers;
20573 }
20574 EditorSettings::get_global(cx).gutter.line_numbers
20575 }
20576
20577 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20578 match (
20579 self.use_relative_line_numbers,
20580 EditorSettings::get_global(cx).relative_line_numbers,
20581 ) {
20582 (None, setting) => setting,
20583 (Some(false), _) => RelativeLineNumbers::Disabled,
20584 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20585 (Some(true), _) => RelativeLineNumbers::Enabled,
20586 }
20587 }
20588
20589 pub fn toggle_relative_line_numbers(
20590 &mut self,
20591 _: &ToggleRelativeLineNumbers,
20592 _: &mut Window,
20593 cx: &mut Context<Self>,
20594 ) {
20595 let is_relative = self.relative_line_numbers(cx);
20596 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20597 }
20598
20599 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20600 self.use_relative_line_numbers = is_relative;
20601 cx.notify();
20602 }
20603
20604 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20605 self.show_gutter = show_gutter;
20606 cx.notify();
20607 }
20608
20609 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20610 self.show_scrollbars = ScrollbarAxes {
20611 horizontal: show,
20612 vertical: show,
20613 };
20614 cx.notify();
20615 }
20616
20617 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20618 self.show_scrollbars.vertical = show;
20619 cx.notify();
20620 }
20621
20622 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20623 self.show_scrollbars.horizontal = show;
20624 cx.notify();
20625 }
20626
20627 pub fn set_minimap_visibility(
20628 &mut self,
20629 minimap_visibility: MinimapVisibility,
20630 window: &mut Window,
20631 cx: &mut Context<Self>,
20632 ) {
20633 if self.minimap_visibility != minimap_visibility {
20634 if minimap_visibility.visible() && self.minimap.is_none() {
20635 let minimap_settings = EditorSettings::get_global(cx).minimap;
20636 self.minimap =
20637 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20638 }
20639 self.minimap_visibility = minimap_visibility;
20640 cx.notify();
20641 }
20642 }
20643
20644 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20645 self.set_show_scrollbars(false, cx);
20646 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20647 }
20648
20649 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20650 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20651 }
20652
20653 /// Normally the text in full mode and auto height editors is padded on the
20654 /// left side by roughly half a character width for improved hit testing.
20655 ///
20656 /// Use this method to disable this for cases where this is not wanted (e.g.
20657 /// if you want to align the editor text with some other text above or below)
20658 /// or if you want to add this padding to single-line editors.
20659 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20660 self.offset_content = offset_content;
20661 cx.notify();
20662 }
20663
20664 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20665 self.show_line_numbers = Some(show_line_numbers);
20666 cx.notify();
20667 }
20668
20669 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20670 self.disable_expand_excerpt_buttons = true;
20671 cx.notify();
20672 }
20673
20674 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20675 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20676 cx.notify();
20677 }
20678
20679 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20680 self.show_code_actions = Some(show_code_actions);
20681 cx.notify();
20682 }
20683
20684 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20685 self.show_runnables = Some(show_runnables);
20686 cx.notify();
20687 }
20688
20689 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20690 self.show_breakpoints = Some(show_breakpoints);
20691 cx.notify();
20692 }
20693
20694 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20695 if self.display_map.read(cx).masked != masked {
20696 self.display_map.update(cx, |map, _| map.masked = masked);
20697 }
20698 cx.notify()
20699 }
20700
20701 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20702 self.show_wrap_guides = Some(show_wrap_guides);
20703 cx.notify();
20704 }
20705
20706 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20707 self.show_indent_guides = Some(show_indent_guides);
20708 cx.notify();
20709 }
20710
20711 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20712 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20713 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20714 && let Some(dir) = file.abs_path(cx).parent()
20715 {
20716 return Some(dir.to_owned());
20717 }
20718 }
20719
20720 None
20721 }
20722
20723 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20724 self.active_excerpt(cx)?
20725 .1
20726 .read(cx)
20727 .file()
20728 .and_then(|f| f.as_local())
20729 }
20730
20731 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20732 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20733 let buffer = buffer.read(cx);
20734 if let Some(project_path) = buffer.project_path(cx) {
20735 let project = self.project()?.read(cx);
20736 project.absolute_path(&project_path, cx)
20737 } else {
20738 buffer
20739 .file()
20740 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20741 }
20742 })
20743 }
20744
20745 pub fn reveal_in_finder(
20746 &mut self,
20747 _: &RevealInFileManager,
20748 _window: &mut Window,
20749 cx: &mut Context<Self>,
20750 ) {
20751 if let Some(target) = self.target_file(cx) {
20752 cx.reveal_path(&target.abs_path(cx));
20753 }
20754 }
20755
20756 pub fn copy_path(
20757 &mut self,
20758 _: &zed_actions::workspace::CopyPath,
20759 _window: &mut Window,
20760 cx: &mut Context<Self>,
20761 ) {
20762 if let Some(path) = self.target_file_abs_path(cx)
20763 && let Some(path) = path.to_str()
20764 {
20765 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20766 } else {
20767 cx.propagate();
20768 }
20769 }
20770
20771 pub fn copy_relative_path(
20772 &mut self,
20773 _: &zed_actions::workspace::CopyRelativePath,
20774 _window: &mut Window,
20775 cx: &mut Context<Self>,
20776 ) {
20777 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20778 let project = self.project()?.read(cx);
20779 let path = buffer.read(cx).file()?.path();
20780 let path = path.display(project.path_style(cx));
20781 Some(path)
20782 }) {
20783 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20784 } else {
20785 cx.propagate();
20786 }
20787 }
20788
20789 /// Returns the project path for the editor's buffer, if any buffer is
20790 /// opened in the editor.
20791 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20792 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20793 buffer.read(cx).project_path(cx)
20794 } else {
20795 None
20796 }
20797 }
20798
20799 // Returns true if the editor handled a go-to-line request
20800 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20801 maybe!({
20802 let breakpoint_store = self.breakpoint_store.as_ref()?;
20803
20804 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20805 else {
20806 self.clear_row_highlights::<ActiveDebugLine>();
20807 return None;
20808 };
20809
20810 let position = active_stack_frame.position;
20811 let buffer_id = position.buffer_id?;
20812 let snapshot = self
20813 .project
20814 .as_ref()?
20815 .read(cx)
20816 .buffer_for_id(buffer_id, cx)?
20817 .read(cx)
20818 .snapshot();
20819
20820 let mut handled = false;
20821 for (id, ExcerptRange { context, .. }) in
20822 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20823 {
20824 if context.start.cmp(&position, &snapshot).is_ge()
20825 || context.end.cmp(&position, &snapshot).is_lt()
20826 {
20827 continue;
20828 }
20829 let snapshot = self.buffer.read(cx).snapshot(cx);
20830 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20831
20832 handled = true;
20833 self.clear_row_highlights::<ActiveDebugLine>();
20834
20835 self.go_to_line::<ActiveDebugLine>(
20836 multibuffer_anchor,
20837 Some(cx.theme().colors().editor_debugger_active_line_background),
20838 window,
20839 cx,
20840 );
20841
20842 cx.notify();
20843 }
20844
20845 handled.then_some(())
20846 })
20847 .is_some()
20848 }
20849
20850 pub fn copy_file_name_without_extension(
20851 &mut self,
20852 _: &CopyFileNameWithoutExtension,
20853 _: &mut Window,
20854 cx: &mut Context<Self>,
20855 ) {
20856 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20857 let file = buffer.read(cx).file()?;
20858 file.path().file_stem()
20859 }) {
20860 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20861 }
20862 }
20863
20864 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20865 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20866 let file = buffer.read(cx).file()?;
20867 Some(file.file_name(cx))
20868 }) {
20869 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20870 }
20871 }
20872
20873 pub fn toggle_git_blame(
20874 &mut self,
20875 _: &::git::Blame,
20876 window: &mut Window,
20877 cx: &mut Context<Self>,
20878 ) {
20879 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20880
20881 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20882 self.start_git_blame(true, window, cx);
20883 }
20884
20885 cx.notify();
20886 }
20887
20888 pub fn toggle_git_blame_inline(
20889 &mut self,
20890 _: &ToggleGitBlameInline,
20891 window: &mut Window,
20892 cx: &mut Context<Self>,
20893 ) {
20894 self.toggle_git_blame_inline_internal(true, window, cx);
20895 cx.notify();
20896 }
20897
20898 pub fn open_git_blame_commit(
20899 &mut self,
20900 _: &OpenGitBlameCommit,
20901 window: &mut Window,
20902 cx: &mut Context<Self>,
20903 ) {
20904 self.open_git_blame_commit_internal(window, cx);
20905 }
20906
20907 fn open_git_blame_commit_internal(
20908 &mut self,
20909 window: &mut Window,
20910 cx: &mut Context<Self>,
20911 ) -> Option<()> {
20912 let blame = self.blame.as_ref()?;
20913 let snapshot = self.snapshot(window, cx);
20914 let cursor = self
20915 .selections
20916 .newest::<Point>(&snapshot.display_snapshot)
20917 .head();
20918 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20919 let (_, blame_entry) = blame
20920 .update(cx, |blame, cx| {
20921 blame
20922 .blame_for_rows(
20923 &[RowInfo {
20924 buffer_id: Some(buffer.remote_id()),
20925 buffer_row: Some(point.row),
20926 ..Default::default()
20927 }],
20928 cx,
20929 )
20930 .next()
20931 })
20932 .flatten()?;
20933 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20934 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20935 let workspace = self.workspace()?.downgrade();
20936 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20937 None
20938 }
20939
20940 pub fn git_blame_inline_enabled(&self) -> bool {
20941 self.git_blame_inline_enabled
20942 }
20943
20944 pub fn toggle_selection_menu(
20945 &mut self,
20946 _: &ToggleSelectionMenu,
20947 _: &mut Window,
20948 cx: &mut Context<Self>,
20949 ) {
20950 self.show_selection_menu = self
20951 .show_selection_menu
20952 .map(|show_selections_menu| !show_selections_menu)
20953 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20954
20955 cx.notify();
20956 }
20957
20958 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20959 self.show_selection_menu
20960 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20961 }
20962
20963 fn start_git_blame(
20964 &mut self,
20965 user_triggered: bool,
20966 window: &mut Window,
20967 cx: &mut Context<Self>,
20968 ) {
20969 if let Some(project) = self.project() {
20970 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20971 && buffer.read(cx).file().is_none()
20972 {
20973 return;
20974 }
20975
20976 let focused = self.focus_handle(cx).contains_focused(window, cx);
20977
20978 let project = project.clone();
20979 let blame = cx
20980 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20981 self.blame_subscription =
20982 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20983 self.blame = Some(blame);
20984 }
20985 }
20986
20987 fn toggle_git_blame_inline_internal(
20988 &mut self,
20989 user_triggered: bool,
20990 window: &mut Window,
20991 cx: &mut Context<Self>,
20992 ) {
20993 if self.git_blame_inline_enabled {
20994 self.git_blame_inline_enabled = false;
20995 self.show_git_blame_inline = false;
20996 self.show_git_blame_inline_delay_task.take();
20997 } else {
20998 self.git_blame_inline_enabled = true;
20999 self.start_git_blame_inline(user_triggered, window, cx);
21000 }
21001
21002 cx.notify();
21003 }
21004
21005 fn start_git_blame_inline(
21006 &mut self,
21007 user_triggered: bool,
21008 window: &mut Window,
21009 cx: &mut Context<Self>,
21010 ) {
21011 self.start_git_blame(user_triggered, window, cx);
21012
21013 if ProjectSettings::get_global(cx)
21014 .git
21015 .inline_blame_delay()
21016 .is_some()
21017 {
21018 self.start_inline_blame_timer(window, cx);
21019 } else {
21020 self.show_git_blame_inline = true
21021 }
21022 }
21023
21024 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
21025 self.blame.as_ref()
21026 }
21027
21028 pub fn show_git_blame_gutter(&self) -> bool {
21029 self.show_git_blame_gutter
21030 }
21031
21032 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
21033 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
21034 }
21035
21036 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
21037 self.show_git_blame_inline
21038 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
21039 && !self.newest_selection_head_on_empty_line(cx)
21040 && self.has_blame_entries(cx)
21041 }
21042
21043 fn has_blame_entries(&self, cx: &App) -> bool {
21044 self.blame()
21045 .is_some_and(|blame| blame.read(cx).has_generated_entries())
21046 }
21047
21048 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
21049 let cursor_anchor = self.selections.newest_anchor().head();
21050
21051 let snapshot = self.buffer.read(cx).snapshot(cx);
21052 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
21053
21054 snapshot.line_len(buffer_row) == 0
21055 }
21056
21057 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
21058 let buffer_and_selection = maybe!({
21059 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21060 let selection_range = selection.range();
21061
21062 let multi_buffer = self.buffer().read(cx);
21063 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21064 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21065
21066 let (buffer, range, _) = if selection.reversed {
21067 buffer_ranges.first()
21068 } else {
21069 buffer_ranges.last()
21070 }?;
21071
21072 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21073 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21074
21075 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21076 let selection = start_row_in_buffer..end_row_in_buffer;
21077
21078 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21079 };
21080
21081 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21082
21083 Some((
21084 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21085 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
21086 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
21087 ))
21088 });
21089
21090 let Some((buffer, selection)) = buffer_and_selection else {
21091 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21092 };
21093
21094 let Some(project) = self.project() else {
21095 return Task::ready(Err(anyhow!("editor does not have project")));
21096 };
21097
21098 project.update(cx, |project, cx| {
21099 project.get_permalink_to_line(&buffer, selection, cx)
21100 })
21101 }
21102
21103 pub fn copy_permalink_to_line(
21104 &mut self,
21105 _: &CopyPermalinkToLine,
21106 window: &mut Window,
21107 cx: &mut Context<Self>,
21108 ) {
21109 let permalink_task = self.get_permalink_to_line(cx);
21110 let workspace = self.workspace();
21111
21112 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21113 Ok(permalink) => {
21114 cx.update(|_, cx| {
21115 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21116 })
21117 .ok();
21118 }
21119 Err(err) => {
21120 let message = format!("Failed to copy permalink: {err}");
21121
21122 anyhow::Result::<()>::Err(err).log_err();
21123
21124 if let Some(workspace) = workspace {
21125 workspace
21126 .update_in(cx, |workspace, _, cx| {
21127 struct CopyPermalinkToLine;
21128
21129 workspace.show_toast(
21130 Toast::new(
21131 NotificationId::unique::<CopyPermalinkToLine>(),
21132 message,
21133 ),
21134 cx,
21135 )
21136 })
21137 .ok();
21138 }
21139 }
21140 })
21141 .detach();
21142 }
21143
21144 pub fn copy_file_location(
21145 &mut self,
21146 _: &CopyFileLocation,
21147 _: &mut Window,
21148 cx: &mut Context<Self>,
21149 ) {
21150 let selection = self
21151 .selections
21152 .newest::<Point>(&self.display_snapshot(cx))
21153 .start
21154 .row
21155 + 1;
21156 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21157 let project = self.project()?.read(cx);
21158 let file = buffer.read(cx).file()?;
21159 let path = file.path().display(project.path_style(cx));
21160
21161 Some(format!("{path}:{selection}"))
21162 }) {
21163 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21164 }
21165 }
21166
21167 pub fn open_permalink_to_line(
21168 &mut self,
21169 _: &OpenPermalinkToLine,
21170 window: &mut Window,
21171 cx: &mut Context<Self>,
21172 ) {
21173 let permalink_task = self.get_permalink_to_line(cx);
21174 let workspace = self.workspace();
21175
21176 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21177 Ok(permalink) => {
21178 cx.update(|_, cx| {
21179 cx.open_url(permalink.as_ref());
21180 })
21181 .ok();
21182 }
21183 Err(err) => {
21184 let message = format!("Failed to open permalink: {err}");
21185
21186 anyhow::Result::<()>::Err(err).log_err();
21187
21188 if let Some(workspace) = workspace {
21189 workspace
21190 .update(cx, |workspace, cx| {
21191 struct OpenPermalinkToLine;
21192
21193 workspace.show_toast(
21194 Toast::new(
21195 NotificationId::unique::<OpenPermalinkToLine>(),
21196 message,
21197 ),
21198 cx,
21199 )
21200 })
21201 .ok();
21202 }
21203 }
21204 })
21205 .detach();
21206 }
21207
21208 pub fn insert_uuid_v4(
21209 &mut self,
21210 _: &InsertUuidV4,
21211 window: &mut Window,
21212 cx: &mut Context<Self>,
21213 ) {
21214 self.insert_uuid(UuidVersion::V4, window, cx);
21215 }
21216
21217 pub fn insert_uuid_v7(
21218 &mut self,
21219 _: &InsertUuidV7,
21220 window: &mut Window,
21221 cx: &mut Context<Self>,
21222 ) {
21223 self.insert_uuid(UuidVersion::V7, window, cx);
21224 }
21225
21226 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21227 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21228 self.transact(window, cx, |this, window, cx| {
21229 let edits = this
21230 .selections
21231 .all::<Point>(&this.display_snapshot(cx))
21232 .into_iter()
21233 .map(|selection| {
21234 let uuid = match version {
21235 UuidVersion::V4 => uuid::Uuid::new_v4(),
21236 UuidVersion::V7 => uuid::Uuid::now_v7(),
21237 };
21238
21239 (selection.range(), uuid.to_string())
21240 });
21241 this.edit(edits, cx);
21242 this.refresh_edit_prediction(true, false, window, cx);
21243 });
21244 }
21245
21246 pub fn open_selections_in_multibuffer(
21247 &mut self,
21248 _: &OpenSelectionsInMultibuffer,
21249 window: &mut Window,
21250 cx: &mut Context<Self>,
21251 ) {
21252 let multibuffer = self.buffer.read(cx);
21253
21254 let Some(buffer) = multibuffer.as_singleton() else {
21255 return;
21256 };
21257
21258 let Some(workspace) = self.workspace() else {
21259 return;
21260 };
21261
21262 let title = multibuffer.title(cx).to_string();
21263
21264 let locations = self
21265 .selections
21266 .all_anchors(&self.display_snapshot(cx))
21267 .iter()
21268 .map(|selection| {
21269 (
21270 buffer.clone(),
21271 (selection.start.text_anchor..selection.end.text_anchor)
21272 .to_point(buffer.read(cx)),
21273 )
21274 })
21275 .into_group_map();
21276
21277 cx.spawn_in(window, async move |_, cx| {
21278 workspace.update_in(cx, |workspace, window, cx| {
21279 Self::open_locations_in_multibuffer(
21280 workspace,
21281 locations,
21282 format!("Selections for '{title}'"),
21283 false,
21284 false,
21285 MultibufferSelectionMode::All,
21286 window,
21287 cx,
21288 );
21289 })
21290 })
21291 .detach();
21292 }
21293
21294 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21295 /// last highlight added will be used.
21296 ///
21297 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21298 pub fn highlight_rows<T: 'static>(
21299 &mut self,
21300 range: Range<Anchor>,
21301 color: Hsla,
21302 options: RowHighlightOptions,
21303 cx: &mut Context<Self>,
21304 ) {
21305 let snapshot = self.buffer().read(cx).snapshot(cx);
21306 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21307 let ix = row_highlights.binary_search_by(|highlight| {
21308 Ordering::Equal
21309 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21310 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21311 });
21312
21313 if let Err(mut ix) = ix {
21314 let index = post_inc(&mut self.highlight_order);
21315
21316 // If this range intersects with the preceding highlight, then merge it with
21317 // the preceding highlight. Otherwise insert a new highlight.
21318 let mut merged = false;
21319 if ix > 0 {
21320 let prev_highlight = &mut row_highlights[ix - 1];
21321 if prev_highlight
21322 .range
21323 .end
21324 .cmp(&range.start, &snapshot)
21325 .is_ge()
21326 {
21327 ix -= 1;
21328 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21329 prev_highlight.range.end = range.end;
21330 }
21331 merged = true;
21332 prev_highlight.index = index;
21333 prev_highlight.color = color;
21334 prev_highlight.options = options;
21335 }
21336 }
21337
21338 if !merged {
21339 row_highlights.insert(
21340 ix,
21341 RowHighlight {
21342 range,
21343 index,
21344 color,
21345 options,
21346 type_id: TypeId::of::<T>(),
21347 },
21348 );
21349 }
21350
21351 // If any of the following highlights intersect with this one, merge them.
21352 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21353 let highlight = &row_highlights[ix];
21354 if next_highlight
21355 .range
21356 .start
21357 .cmp(&highlight.range.end, &snapshot)
21358 .is_le()
21359 {
21360 if next_highlight
21361 .range
21362 .end
21363 .cmp(&highlight.range.end, &snapshot)
21364 .is_gt()
21365 {
21366 row_highlights[ix].range.end = next_highlight.range.end;
21367 }
21368 row_highlights.remove(ix + 1);
21369 } else {
21370 break;
21371 }
21372 }
21373 }
21374 }
21375
21376 /// Remove any highlighted row ranges of the given type that intersect the
21377 /// given ranges.
21378 pub fn remove_highlighted_rows<T: 'static>(
21379 &mut self,
21380 ranges_to_remove: Vec<Range<Anchor>>,
21381 cx: &mut Context<Self>,
21382 ) {
21383 let snapshot = self.buffer().read(cx).snapshot(cx);
21384 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21385 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21386 row_highlights.retain(|highlight| {
21387 while let Some(range_to_remove) = ranges_to_remove.peek() {
21388 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21389 Ordering::Less | Ordering::Equal => {
21390 ranges_to_remove.next();
21391 }
21392 Ordering::Greater => {
21393 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21394 Ordering::Less | Ordering::Equal => {
21395 return false;
21396 }
21397 Ordering::Greater => break,
21398 }
21399 }
21400 }
21401 }
21402
21403 true
21404 })
21405 }
21406
21407 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21408 pub fn clear_row_highlights<T: 'static>(&mut self) {
21409 self.highlighted_rows.remove(&TypeId::of::<T>());
21410 }
21411
21412 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21413 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21414 self.highlighted_rows
21415 .get(&TypeId::of::<T>())
21416 .map_or(&[] as &[_], |vec| vec.as_slice())
21417 .iter()
21418 .map(|highlight| (highlight.range.clone(), highlight.color))
21419 }
21420
21421 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21422 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21423 /// Allows to ignore certain kinds of highlights.
21424 pub fn highlighted_display_rows(
21425 &self,
21426 window: &mut Window,
21427 cx: &mut App,
21428 ) -> BTreeMap<DisplayRow, LineHighlight> {
21429 let snapshot = self.snapshot(window, cx);
21430 let mut used_highlight_orders = HashMap::default();
21431 self.highlighted_rows
21432 .iter()
21433 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21434 .fold(
21435 BTreeMap::<DisplayRow, LineHighlight>::new(),
21436 |mut unique_rows, highlight| {
21437 let start = highlight.range.start.to_display_point(&snapshot);
21438 let end = highlight.range.end.to_display_point(&snapshot);
21439 let start_row = start.row().0;
21440 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21441 {
21442 end.row().0.saturating_sub(1)
21443 } else {
21444 end.row().0
21445 };
21446 for row in start_row..=end_row {
21447 let used_index =
21448 used_highlight_orders.entry(row).or_insert(highlight.index);
21449 if highlight.index >= *used_index {
21450 *used_index = highlight.index;
21451 unique_rows.insert(
21452 DisplayRow(row),
21453 LineHighlight {
21454 include_gutter: highlight.options.include_gutter,
21455 border: None,
21456 background: highlight.color.into(),
21457 type_id: Some(highlight.type_id),
21458 },
21459 );
21460 }
21461 }
21462 unique_rows
21463 },
21464 )
21465 }
21466
21467 pub fn highlighted_display_row_for_autoscroll(
21468 &self,
21469 snapshot: &DisplaySnapshot,
21470 ) -> Option<DisplayRow> {
21471 self.highlighted_rows
21472 .values()
21473 .flat_map(|highlighted_rows| highlighted_rows.iter())
21474 .filter_map(|highlight| {
21475 if highlight.options.autoscroll {
21476 Some(highlight.range.start.to_display_point(snapshot).row())
21477 } else {
21478 None
21479 }
21480 })
21481 .min()
21482 }
21483
21484 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21485 self.highlight_background::<SearchWithinRange>(
21486 ranges,
21487 |_, colors| colors.colors().editor_document_highlight_read_background,
21488 cx,
21489 )
21490 }
21491
21492 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21493 self.breadcrumb_header = Some(new_header);
21494 }
21495
21496 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21497 self.clear_background_highlights::<SearchWithinRange>(cx);
21498 }
21499
21500 pub fn highlight_background<T: 'static>(
21501 &mut self,
21502 ranges: &[Range<Anchor>],
21503 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21504 cx: &mut Context<Self>,
21505 ) {
21506 self.background_highlights.insert(
21507 HighlightKey::Type(TypeId::of::<T>()),
21508 (Arc::new(color_fetcher), Arc::from(ranges)),
21509 );
21510 self.scrollbar_marker_state.dirty = true;
21511 cx.notify();
21512 }
21513
21514 pub fn highlight_background_key<T: 'static>(
21515 &mut self,
21516 key: usize,
21517 ranges: &[Range<Anchor>],
21518 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21519 cx: &mut Context<Self>,
21520 ) {
21521 self.background_highlights.insert(
21522 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21523 (Arc::new(color_fetcher), Arc::from(ranges)),
21524 );
21525 self.scrollbar_marker_state.dirty = true;
21526 cx.notify();
21527 }
21528
21529 pub fn clear_background_highlights<T: 'static>(
21530 &mut self,
21531 cx: &mut Context<Self>,
21532 ) -> Option<BackgroundHighlight> {
21533 let text_highlights = self
21534 .background_highlights
21535 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21536 if !text_highlights.1.is_empty() {
21537 self.scrollbar_marker_state.dirty = true;
21538 cx.notify();
21539 }
21540 Some(text_highlights)
21541 }
21542
21543 pub fn highlight_gutter<T: 'static>(
21544 &mut self,
21545 ranges: impl Into<Vec<Range<Anchor>>>,
21546 color_fetcher: fn(&App) -> Hsla,
21547 cx: &mut Context<Self>,
21548 ) {
21549 self.gutter_highlights
21550 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21551 cx.notify();
21552 }
21553
21554 pub fn clear_gutter_highlights<T: 'static>(
21555 &mut self,
21556 cx: &mut Context<Self>,
21557 ) -> Option<GutterHighlight> {
21558 cx.notify();
21559 self.gutter_highlights.remove(&TypeId::of::<T>())
21560 }
21561
21562 pub fn insert_gutter_highlight<T: 'static>(
21563 &mut self,
21564 range: Range<Anchor>,
21565 color_fetcher: fn(&App) -> Hsla,
21566 cx: &mut Context<Self>,
21567 ) {
21568 let snapshot = self.buffer().read(cx).snapshot(cx);
21569 let mut highlights = self
21570 .gutter_highlights
21571 .remove(&TypeId::of::<T>())
21572 .map(|(_, highlights)| highlights)
21573 .unwrap_or_default();
21574 let ix = highlights.binary_search_by(|highlight| {
21575 Ordering::Equal
21576 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21577 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21578 });
21579 if let Err(ix) = ix {
21580 highlights.insert(ix, range);
21581 }
21582 self.gutter_highlights
21583 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21584 }
21585
21586 pub fn remove_gutter_highlights<T: 'static>(
21587 &mut self,
21588 ranges_to_remove: Vec<Range<Anchor>>,
21589 cx: &mut Context<Self>,
21590 ) {
21591 let snapshot = self.buffer().read(cx).snapshot(cx);
21592 let Some((color_fetcher, mut gutter_highlights)) =
21593 self.gutter_highlights.remove(&TypeId::of::<T>())
21594 else {
21595 return;
21596 };
21597 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21598 gutter_highlights.retain(|highlight| {
21599 while let Some(range_to_remove) = ranges_to_remove.peek() {
21600 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21601 Ordering::Less | Ordering::Equal => {
21602 ranges_to_remove.next();
21603 }
21604 Ordering::Greater => {
21605 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21606 Ordering::Less | Ordering::Equal => {
21607 return false;
21608 }
21609 Ordering::Greater => break,
21610 }
21611 }
21612 }
21613 }
21614
21615 true
21616 });
21617 self.gutter_highlights
21618 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21619 }
21620
21621 #[cfg(feature = "test-support")]
21622 pub fn all_text_highlights(
21623 &self,
21624 window: &mut Window,
21625 cx: &mut Context<Self>,
21626 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21627 let snapshot = self.snapshot(window, cx);
21628 self.display_map.update(cx, |display_map, _| {
21629 display_map
21630 .all_text_highlights()
21631 .map(|highlight| {
21632 let (style, ranges) = highlight.as_ref();
21633 (
21634 *style,
21635 ranges
21636 .iter()
21637 .map(|range| range.clone().to_display_points(&snapshot))
21638 .collect(),
21639 )
21640 })
21641 .collect()
21642 })
21643 }
21644
21645 #[cfg(feature = "test-support")]
21646 pub fn all_text_background_highlights(
21647 &self,
21648 window: &mut Window,
21649 cx: &mut Context<Self>,
21650 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21651 let snapshot = self.snapshot(window, cx);
21652 let buffer = &snapshot.buffer_snapshot();
21653 let start = buffer.anchor_before(MultiBufferOffset(0));
21654 let end = buffer.anchor_after(buffer.len());
21655 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21656 }
21657
21658 #[cfg(any(test, feature = "test-support"))]
21659 pub fn sorted_background_highlights_in_range(
21660 &self,
21661 search_range: Range<Anchor>,
21662 display_snapshot: &DisplaySnapshot,
21663 theme: &Theme,
21664 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21665 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21666 res.sort_by(|a, b| {
21667 a.0.start
21668 .cmp(&b.0.start)
21669 .then_with(|| a.0.end.cmp(&b.0.end))
21670 .then_with(|| a.1.cmp(&b.1))
21671 });
21672 res
21673 }
21674
21675 #[cfg(feature = "test-support")]
21676 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21677 let snapshot = self.buffer().read(cx).snapshot(cx);
21678
21679 let highlights = self
21680 .background_highlights
21681 .get(&HighlightKey::Type(TypeId::of::<
21682 items::BufferSearchHighlights,
21683 >()));
21684
21685 if let Some((_color, ranges)) = highlights {
21686 ranges
21687 .iter()
21688 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21689 .collect_vec()
21690 } else {
21691 vec![]
21692 }
21693 }
21694
21695 fn document_highlights_for_position<'a>(
21696 &'a self,
21697 position: Anchor,
21698 buffer: &'a MultiBufferSnapshot,
21699 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21700 let read_highlights = self
21701 .background_highlights
21702 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21703 .map(|h| &h.1);
21704 let write_highlights = self
21705 .background_highlights
21706 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21707 .map(|h| &h.1);
21708 let left_position = position.bias_left(buffer);
21709 let right_position = position.bias_right(buffer);
21710 read_highlights
21711 .into_iter()
21712 .chain(write_highlights)
21713 .flat_map(move |ranges| {
21714 let start_ix = match ranges.binary_search_by(|probe| {
21715 let cmp = probe.end.cmp(&left_position, buffer);
21716 if cmp.is_ge() {
21717 Ordering::Greater
21718 } else {
21719 Ordering::Less
21720 }
21721 }) {
21722 Ok(i) | Err(i) => i,
21723 };
21724
21725 ranges[start_ix..]
21726 .iter()
21727 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21728 })
21729 }
21730
21731 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21732 self.background_highlights
21733 .get(&HighlightKey::Type(TypeId::of::<T>()))
21734 .is_some_and(|(_, highlights)| !highlights.is_empty())
21735 }
21736
21737 /// Returns all background highlights for a given range.
21738 ///
21739 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21740 pub fn background_highlights_in_range(
21741 &self,
21742 search_range: Range<Anchor>,
21743 display_snapshot: &DisplaySnapshot,
21744 theme: &Theme,
21745 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21746 let mut results = Vec::new();
21747 for (color_fetcher, ranges) in self.background_highlights.values() {
21748 let start_ix = match ranges.binary_search_by(|probe| {
21749 let cmp = probe
21750 .end
21751 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21752 if cmp.is_gt() {
21753 Ordering::Greater
21754 } else {
21755 Ordering::Less
21756 }
21757 }) {
21758 Ok(i) | Err(i) => i,
21759 };
21760 for (index, range) in ranges[start_ix..].iter().enumerate() {
21761 if range
21762 .start
21763 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21764 .is_ge()
21765 {
21766 break;
21767 }
21768
21769 let color = color_fetcher(&(start_ix + index), theme);
21770 let start = range.start.to_display_point(display_snapshot);
21771 let end = range.end.to_display_point(display_snapshot);
21772 results.push((start..end, color))
21773 }
21774 }
21775 results
21776 }
21777
21778 pub fn gutter_highlights_in_range(
21779 &self,
21780 search_range: Range<Anchor>,
21781 display_snapshot: &DisplaySnapshot,
21782 cx: &App,
21783 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21784 let mut results = Vec::new();
21785 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21786 let color = color_fetcher(cx);
21787 let start_ix = match ranges.binary_search_by(|probe| {
21788 let cmp = probe
21789 .end
21790 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21791 if cmp.is_gt() {
21792 Ordering::Greater
21793 } else {
21794 Ordering::Less
21795 }
21796 }) {
21797 Ok(i) | Err(i) => i,
21798 };
21799 for range in &ranges[start_ix..] {
21800 if range
21801 .start
21802 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21803 .is_ge()
21804 {
21805 break;
21806 }
21807
21808 let start = range.start.to_display_point(display_snapshot);
21809 let end = range.end.to_display_point(display_snapshot);
21810 results.push((start..end, color))
21811 }
21812 }
21813 results
21814 }
21815
21816 /// Get the text ranges corresponding to the redaction query
21817 pub fn redacted_ranges(
21818 &self,
21819 search_range: Range<Anchor>,
21820 display_snapshot: &DisplaySnapshot,
21821 cx: &App,
21822 ) -> Vec<Range<DisplayPoint>> {
21823 display_snapshot
21824 .buffer_snapshot()
21825 .redacted_ranges(search_range, |file| {
21826 if let Some(file) = file {
21827 file.is_private()
21828 && EditorSettings::get(
21829 Some(SettingsLocation {
21830 worktree_id: file.worktree_id(cx),
21831 path: file.path().as_ref(),
21832 }),
21833 cx,
21834 )
21835 .redact_private_values
21836 } else {
21837 false
21838 }
21839 })
21840 .map(|range| {
21841 range.start.to_display_point(display_snapshot)
21842 ..range.end.to_display_point(display_snapshot)
21843 })
21844 .collect()
21845 }
21846
21847 pub fn highlight_text_key<T: 'static>(
21848 &mut self,
21849 key: usize,
21850 ranges: Vec<Range<Anchor>>,
21851 style: HighlightStyle,
21852 merge: bool,
21853 cx: &mut Context<Self>,
21854 ) {
21855 self.display_map.update(cx, |map, cx| {
21856 map.highlight_text(
21857 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21858 ranges,
21859 style,
21860 merge,
21861 cx,
21862 );
21863 });
21864 cx.notify();
21865 }
21866
21867 pub fn highlight_text<T: 'static>(
21868 &mut self,
21869 ranges: Vec<Range<Anchor>>,
21870 style: HighlightStyle,
21871 cx: &mut Context<Self>,
21872 ) {
21873 self.display_map.update(cx, |map, cx| {
21874 map.highlight_text(
21875 HighlightKey::Type(TypeId::of::<T>()),
21876 ranges,
21877 style,
21878 false,
21879 cx,
21880 )
21881 });
21882 cx.notify();
21883 }
21884
21885 pub fn text_highlights<'a, T: 'static>(
21886 &'a self,
21887 cx: &'a App,
21888 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21889 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21890 }
21891
21892 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21893 let cleared = self
21894 .display_map
21895 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21896 if cleared {
21897 cx.notify();
21898 }
21899 }
21900
21901 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21902 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21903 && self.focus_handle.is_focused(window)
21904 }
21905
21906 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21907 self.show_cursor_when_unfocused = is_enabled;
21908 cx.notify();
21909 }
21910
21911 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21912 cx.notify();
21913 }
21914
21915 fn on_debug_session_event(
21916 &mut self,
21917 _session: Entity<Session>,
21918 event: &SessionEvent,
21919 cx: &mut Context<Self>,
21920 ) {
21921 if let SessionEvent::InvalidateInlineValue = event {
21922 self.refresh_inline_values(cx);
21923 }
21924 }
21925
21926 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21927 let Some(project) = self.project.clone() else {
21928 return;
21929 };
21930
21931 if !self.inline_value_cache.enabled {
21932 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21933 self.splice_inlays(&inlays, Vec::new(), cx);
21934 return;
21935 }
21936
21937 let current_execution_position = self
21938 .highlighted_rows
21939 .get(&TypeId::of::<ActiveDebugLine>())
21940 .and_then(|lines| lines.last().map(|line| line.range.end));
21941
21942 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21943 let inline_values = editor
21944 .update(cx, |editor, cx| {
21945 let Some(current_execution_position) = current_execution_position else {
21946 return Some(Task::ready(Ok(Vec::new())));
21947 };
21948
21949 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21950 let snapshot = buffer.snapshot(cx);
21951
21952 let excerpt = snapshot.excerpt_containing(
21953 current_execution_position..current_execution_position,
21954 )?;
21955
21956 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21957 })?;
21958
21959 let range =
21960 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21961
21962 project.inline_values(buffer, range, cx)
21963 })
21964 .ok()
21965 .flatten()?
21966 .await
21967 .context("refreshing debugger inlays")
21968 .log_err()?;
21969
21970 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21971
21972 for (buffer_id, inline_value) in inline_values
21973 .into_iter()
21974 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21975 {
21976 buffer_inline_values
21977 .entry(buffer_id)
21978 .or_default()
21979 .push(inline_value);
21980 }
21981
21982 editor
21983 .update(cx, |editor, cx| {
21984 let snapshot = editor.buffer.read(cx).snapshot(cx);
21985 let mut new_inlays = Vec::default();
21986
21987 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21988 let buffer_id = buffer_snapshot.remote_id();
21989 buffer_inline_values
21990 .get(&buffer_id)
21991 .into_iter()
21992 .flatten()
21993 .for_each(|hint| {
21994 let inlay = Inlay::debugger(
21995 post_inc(&mut editor.next_inlay_id),
21996 Anchor::in_buffer(excerpt_id, hint.position),
21997 hint.text(),
21998 );
21999 if !inlay.text().chars().contains(&'\n') {
22000 new_inlays.push(inlay);
22001 }
22002 });
22003 }
22004
22005 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
22006 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
22007
22008 editor.splice_inlays(&inlay_ids, new_inlays, cx);
22009 })
22010 .ok()?;
22011 Some(())
22012 });
22013 }
22014
22015 fn on_buffer_event(
22016 &mut self,
22017 multibuffer: &Entity<MultiBuffer>,
22018 event: &multi_buffer::Event,
22019 window: &mut Window,
22020 cx: &mut Context<Self>,
22021 ) {
22022 match event {
22023 multi_buffer::Event::Edited { edited_buffer } => {
22024 self.scrollbar_marker_state.dirty = true;
22025 self.active_indent_guides_state.dirty = true;
22026 self.refresh_active_diagnostics(cx);
22027 self.refresh_code_actions(window, cx);
22028 self.refresh_single_line_folds(window, cx);
22029 self.refresh_matching_bracket_highlights(window, cx);
22030 if self.has_active_edit_prediction() {
22031 self.update_visible_edit_prediction(window, cx);
22032 }
22033
22034 if let Some(buffer) = edited_buffer {
22035 if buffer.read(cx).file().is_none() {
22036 cx.emit(EditorEvent::TitleChanged);
22037 }
22038
22039 if self.project.is_some() {
22040 let buffer_id = buffer.read(cx).remote_id();
22041 self.register_buffer(buffer_id, cx);
22042 self.update_lsp_data(Some(buffer_id), window, cx);
22043 self.refresh_inlay_hints(
22044 InlayHintRefreshReason::BufferEdited(buffer_id),
22045 cx,
22046 );
22047 }
22048 }
22049
22050 cx.emit(EditorEvent::BufferEdited);
22051 cx.emit(SearchEvent::MatchesInvalidated);
22052
22053 let Some(project) = &self.project else { return };
22054 let (telemetry, is_via_ssh) = {
22055 let project = project.read(cx);
22056 let telemetry = project.client().telemetry().clone();
22057 let is_via_ssh = project.is_via_remote_server();
22058 (telemetry, is_via_ssh)
22059 };
22060 telemetry.log_edit_event("editor", is_via_ssh);
22061 }
22062 multi_buffer::Event::ExcerptsAdded {
22063 buffer,
22064 predecessor,
22065 excerpts,
22066 } => {
22067 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22068 let buffer_id = buffer.read(cx).remote_id();
22069 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22070 && let Some(project) = &self.project
22071 {
22072 update_uncommitted_diff_for_buffer(
22073 cx.entity(),
22074 project,
22075 [buffer.clone()],
22076 self.buffer.clone(),
22077 cx,
22078 )
22079 .detach();
22080 }
22081 self.update_lsp_data(Some(buffer_id), window, cx);
22082 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22083 self.colorize_brackets(false, cx);
22084 cx.emit(EditorEvent::ExcerptsAdded {
22085 buffer: buffer.clone(),
22086 predecessor: *predecessor,
22087 excerpts: excerpts.clone(),
22088 });
22089 }
22090 multi_buffer::Event::ExcerptsRemoved {
22091 ids,
22092 removed_buffer_ids,
22093 } => {
22094 if let Some(inlay_hints) = &mut self.inlay_hints {
22095 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22096 }
22097 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22098 for buffer_id in removed_buffer_ids {
22099 self.registered_buffers.remove(buffer_id);
22100 }
22101 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22102 cx.emit(EditorEvent::ExcerptsRemoved {
22103 ids: ids.clone(),
22104 removed_buffer_ids: removed_buffer_ids.clone(),
22105 });
22106 }
22107 multi_buffer::Event::ExcerptsEdited {
22108 excerpt_ids,
22109 buffer_ids,
22110 } => {
22111 self.display_map.update(cx, |map, cx| {
22112 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22113 });
22114 cx.emit(EditorEvent::ExcerptsEdited {
22115 ids: excerpt_ids.clone(),
22116 });
22117 }
22118 multi_buffer::Event::ExcerptsExpanded { ids } => {
22119 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22120 self.refresh_document_highlights(cx);
22121 for id in ids {
22122 self.fetched_tree_sitter_chunks.remove(id);
22123 }
22124 self.colorize_brackets(false, cx);
22125 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22126 }
22127 multi_buffer::Event::Reparsed(buffer_id) => {
22128 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22129 self.refresh_selected_text_highlights(true, window, cx);
22130 self.colorize_brackets(true, cx);
22131 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22132
22133 cx.emit(EditorEvent::Reparsed(*buffer_id));
22134 }
22135 multi_buffer::Event::DiffHunksToggled => {
22136 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22137 }
22138 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22139 if !is_fresh_language {
22140 self.registered_buffers.remove(&buffer_id);
22141 }
22142 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22143 cx.emit(EditorEvent::Reparsed(*buffer_id));
22144 cx.notify();
22145 }
22146 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22147 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22148 multi_buffer::Event::FileHandleChanged
22149 | multi_buffer::Event::Reloaded
22150 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22151 multi_buffer::Event::DiagnosticsUpdated => {
22152 self.update_diagnostics_state(window, cx);
22153 }
22154 _ => {}
22155 };
22156 }
22157
22158 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22159 if !self.diagnostics_enabled() {
22160 return;
22161 }
22162 self.refresh_active_diagnostics(cx);
22163 self.refresh_inline_diagnostics(true, window, cx);
22164 self.scrollbar_marker_state.dirty = true;
22165 cx.notify();
22166 }
22167
22168 pub fn start_temporary_diff_override(&mut self) {
22169 self.load_diff_task.take();
22170 self.temporary_diff_override = true;
22171 }
22172
22173 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22174 self.temporary_diff_override = false;
22175 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22176 self.buffer.update(cx, |buffer, cx| {
22177 buffer.set_all_diff_hunks_collapsed(cx);
22178 });
22179
22180 if let Some(project) = self.project.clone() {
22181 self.load_diff_task = Some(
22182 update_uncommitted_diff_for_buffer(
22183 cx.entity(),
22184 &project,
22185 self.buffer.read(cx).all_buffers(),
22186 self.buffer.clone(),
22187 cx,
22188 )
22189 .shared(),
22190 );
22191 }
22192 }
22193
22194 fn on_display_map_changed(
22195 &mut self,
22196 _: Entity<DisplayMap>,
22197 _: &mut Window,
22198 cx: &mut Context<Self>,
22199 ) {
22200 cx.notify();
22201 }
22202
22203 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22204 if !self.mode.is_full() {
22205 return None;
22206 }
22207
22208 let theme_settings = theme::ThemeSettings::get_global(cx);
22209 let theme = cx.theme();
22210 let accent_colors = theme.accents().clone();
22211
22212 let accent_overrides = theme_settings
22213 .theme_overrides
22214 .get(theme.name.as_ref())
22215 .map(|theme_style| &theme_style.accents)
22216 .into_iter()
22217 .flatten()
22218 .chain(
22219 theme_settings
22220 .experimental_theme_overrides
22221 .as_ref()
22222 .map(|overrides| &overrides.accents)
22223 .into_iter()
22224 .flatten(),
22225 )
22226 .flat_map(|accent| accent.0.clone())
22227 .collect();
22228
22229 Some(AccentData {
22230 colors: accent_colors,
22231 overrides: accent_overrides,
22232 })
22233 }
22234
22235 fn fetch_applicable_language_settings(
22236 &self,
22237 cx: &App,
22238 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22239 if !self.mode.is_full() {
22240 return HashMap::default();
22241 }
22242
22243 self.buffer().read(cx).all_buffers().into_iter().fold(
22244 HashMap::default(),
22245 |mut acc, buffer| {
22246 let buffer = buffer.read(cx);
22247 let language = buffer.language().map(|language| language.name());
22248 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22249 let file = buffer.file();
22250 v.insert(language_settings(language, file, cx).into_owned());
22251 }
22252 acc
22253 },
22254 )
22255 }
22256
22257 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22258 let new_language_settings = self.fetch_applicable_language_settings(cx);
22259 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22260 self.applicable_language_settings = new_language_settings;
22261
22262 let new_accents = self.fetch_accent_data(cx);
22263 let accents_changed = new_accents != self.accent_data;
22264 self.accent_data = new_accents;
22265
22266 if self.diagnostics_enabled() {
22267 let new_severity = EditorSettings::get_global(cx)
22268 .diagnostics_max_severity
22269 .unwrap_or(DiagnosticSeverity::Hint);
22270 self.set_max_diagnostics_severity(new_severity, cx);
22271 }
22272 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22273 self.update_edit_prediction_settings(cx);
22274 self.refresh_edit_prediction(true, false, window, cx);
22275 self.refresh_inline_values(cx);
22276 self.refresh_inlay_hints(
22277 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22278 self.selections.newest_anchor().head(),
22279 &self.buffer.read(cx).snapshot(cx),
22280 cx,
22281 )),
22282 cx,
22283 );
22284
22285 let old_cursor_shape = self.cursor_shape;
22286 let old_show_breadcrumbs = self.show_breadcrumbs;
22287
22288 {
22289 let editor_settings = EditorSettings::get_global(cx);
22290 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22291 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22292 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22293 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22294 }
22295
22296 if old_cursor_shape != self.cursor_shape {
22297 cx.emit(EditorEvent::CursorShapeChanged);
22298 }
22299
22300 if old_show_breadcrumbs != self.show_breadcrumbs {
22301 cx.emit(EditorEvent::BreadcrumbsChanged);
22302 }
22303
22304 let project_settings = ProjectSettings::get_global(cx);
22305 self.buffer_serialization = self
22306 .should_serialize_buffer()
22307 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22308
22309 if self.mode.is_full() {
22310 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22311 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22312 if self.show_inline_diagnostics != show_inline_diagnostics {
22313 self.show_inline_diagnostics = show_inline_diagnostics;
22314 self.refresh_inline_diagnostics(false, window, cx);
22315 }
22316
22317 if self.git_blame_inline_enabled != inline_blame_enabled {
22318 self.toggle_git_blame_inline_internal(false, window, cx);
22319 }
22320
22321 let minimap_settings = EditorSettings::get_global(cx).minimap;
22322 if self.minimap_visibility != MinimapVisibility::Disabled {
22323 if self.minimap_visibility.settings_visibility()
22324 != minimap_settings.minimap_enabled()
22325 {
22326 self.set_minimap_visibility(
22327 MinimapVisibility::for_mode(self.mode(), cx),
22328 window,
22329 cx,
22330 );
22331 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22332 minimap_entity.update(cx, |minimap_editor, cx| {
22333 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22334 })
22335 }
22336 }
22337
22338 if language_settings_changed || accents_changed {
22339 self.colorize_brackets(true, cx);
22340 }
22341
22342 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22343 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22344 }) {
22345 if !inlay_splice.is_empty() {
22346 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22347 }
22348 self.refresh_colors_for_visible_range(None, window, cx);
22349 }
22350 }
22351
22352 cx.notify();
22353 }
22354
22355 pub fn set_searchable(&mut self, searchable: bool) {
22356 self.searchable = searchable;
22357 }
22358
22359 pub fn searchable(&self) -> bool {
22360 self.searchable
22361 }
22362
22363 pub fn open_excerpts_in_split(
22364 &mut self,
22365 _: &OpenExcerptsSplit,
22366 window: &mut Window,
22367 cx: &mut Context<Self>,
22368 ) {
22369 self.open_excerpts_common(None, true, window, cx)
22370 }
22371
22372 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22373 self.open_excerpts_common(None, false, window, cx)
22374 }
22375
22376 fn open_excerpts_common(
22377 &mut self,
22378 jump_data: Option<JumpData>,
22379 split: bool,
22380 window: &mut Window,
22381 cx: &mut Context<Self>,
22382 ) {
22383 let Some(workspace) = self.workspace() else {
22384 cx.propagate();
22385 return;
22386 };
22387
22388 if self.buffer.read(cx).is_singleton() {
22389 cx.propagate();
22390 return;
22391 }
22392
22393 let mut new_selections_by_buffer = HashMap::default();
22394 match &jump_data {
22395 Some(JumpData::MultiBufferPoint {
22396 excerpt_id,
22397 position,
22398 anchor,
22399 line_offset_from_top,
22400 }) => {
22401 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22402 if let Some(buffer) = multi_buffer_snapshot
22403 .buffer_id_for_excerpt(*excerpt_id)
22404 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22405 {
22406 let buffer_snapshot = buffer.read(cx).snapshot();
22407 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22408 language::ToPoint::to_point(anchor, &buffer_snapshot)
22409 } else {
22410 buffer_snapshot.clip_point(*position, Bias::Left)
22411 };
22412 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22413 new_selections_by_buffer.insert(
22414 buffer,
22415 (
22416 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22417 Some(*line_offset_from_top),
22418 ),
22419 );
22420 }
22421 }
22422 Some(JumpData::MultiBufferRow {
22423 row,
22424 line_offset_from_top,
22425 }) => {
22426 let point = MultiBufferPoint::new(row.0, 0);
22427 if let Some((buffer, buffer_point, _)) =
22428 self.buffer.read(cx).point_to_buffer_point(point, cx)
22429 {
22430 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22431 new_selections_by_buffer
22432 .entry(buffer)
22433 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22434 .0
22435 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22436 }
22437 }
22438 None => {
22439 let selections = self
22440 .selections
22441 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22442 let multi_buffer = self.buffer.read(cx);
22443 for selection in selections {
22444 for (snapshot, range, _, anchor) in multi_buffer
22445 .snapshot(cx)
22446 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22447 {
22448 if let Some(anchor) = anchor {
22449 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22450 else {
22451 continue;
22452 };
22453 let offset = text::ToOffset::to_offset(
22454 &anchor.text_anchor,
22455 &buffer_handle.read(cx).snapshot(),
22456 );
22457 let range = BufferOffset(offset)..BufferOffset(offset);
22458 new_selections_by_buffer
22459 .entry(buffer_handle)
22460 .or_insert((Vec::new(), None))
22461 .0
22462 .push(range)
22463 } else {
22464 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22465 else {
22466 continue;
22467 };
22468 new_selections_by_buffer
22469 .entry(buffer_handle)
22470 .or_insert((Vec::new(), None))
22471 .0
22472 .push(range)
22473 }
22474 }
22475 }
22476 }
22477 }
22478
22479 new_selections_by_buffer
22480 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22481
22482 if new_selections_by_buffer.is_empty() {
22483 return;
22484 }
22485
22486 // We defer the pane interaction because we ourselves are a workspace item
22487 // and activating a new item causes the pane to call a method on us reentrantly,
22488 // which panics if we're on the stack.
22489 window.defer(cx, move |window, cx| {
22490 workspace.update(cx, |workspace, cx| {
22491 let pane = if split {
22492 workspace.adjacent_pane(window, cx)
22493 } else {
22494 workspace.active_pane().clone()
22495 };
22496
22497 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22498 let buffer_read = buffer.read(cx);
22499 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22500 (true, project::File::from_dyn(Some(file)).is_some())
22501 } else {
22502 (false, false)
22503 };
22504
22505 // If project file is none workspace.open_project_item will fail to open the excerpt
22506 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22507 // so we check if there's a tab match in that case first
22508 let editor = (!has_file || !is_project_file)
22509 .then(|| {
22510 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22511 // so `workspace.open_project_item` will never find them, always opening a new editor.
22512 // Instead, we try to activate the existing editor in the pane first.
22513 let (editor, pane_item_index, pane_item_id) =
22514 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22515 let editor = item.downcast::<Editor>()?;
22516 let singleton_buffer =
22517 editor.read(cx).buffer().read(cx).as_singleton()?;
22518 if singleton_buffer == buffer {
22519 Some((editor, i, item.item_id()))
22520 } else {
22521 None
22522 }
22523 })?;
22524 pane.update(cx, |pane, cx| {
22525 pane.activate_item(pane_item_index, true, true, window, cx);
22526 if !PreviewTabsSettings::get_global(cx)
22527 .enable_preview_from_multibuffer
22528 {
22529 pane.unpreview_item_if_preview(pane_item_id);
22530 }
22531 });
22532 Some(editor)
22533 })
22534 .flatten()
22535 .unwrap_or_else(|| {
22536 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22537 .enable_keep_preview_on_code_navigation;
22538 let allow_new_preview =
22539 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22540 workspace.open_project_item::<Self>(
22541 pane.clone(),
22542 buffer,
22543 true,
22544 true,
22545 keep_old_preview,
22546 allow_new_preview,
22547 window,
22548 cx,
22549 )
22550 });
22551
22552 editor.update(cx, |editor, cx| {
22553 if has_file && !is_project_file {
22554 editor.set_read_only(true);
22555 }
22556 let autoscroll = match scroll_offset {
22557 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22558 None => Autoscroll::newest(),
22559 };
22560 let nav_history = editor.nav_history.take();
22561 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22562 let Some((&excerpt_id, _, buffer_snapshot)) =
22563 multibuffer_snapshot.as_singleton()
22564 else {
22565 return;
22566 };
22567 editor.change_selections(
22568 SelectionEffects::scroll(autoscroll),
22569 window,
22570 cx,
22571 |s| {
22572 s.select_ranges(ranges.into_iter().map(|range| {
22573 let range = buffer_snapshot.anchor_before(range.start)
22574 ..buffer_snapshot.anchor_after(range.end);
22575 multibuffer_snapshot
22576 .anchor_range_in_excerpt(excerpt_id, range)
22577 .unwrap()
22578 }));
22579 },
22580 );
22581 editor.nav_history = nav_history;
22582 });
22583 }
22584 })
22585 });
22586 }
22587
22588 // Allow opening excerpts for buffers that either belong to the current project
22589 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22590 // are also supported so tests and other in-memory views keep working.
22591 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22592 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22593 }
22594
22595 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22596 let snapshot = self.buffer.read(cx).read(cx);
22597 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22598 Some(
22599 ranges
22600 .iter()
22601 .map(move |range| {
22602 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22603 })
22604 .collect(),
22605 )
22606 }
22607
22608 fn selection_replacement_ranges(
22609 &self,
22610 range: Range<MultiBufferOffsetUtf16>,
22611 cx: &mut App,
22612 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22613 let selections = self
22614 .selections
22615 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22616 let newest_selection = selections
22617 .iter()
22618 .max_by_key(|selection| selection.id)
22619 .unwrap();
22620 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22621 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22622 let snapshot = self.buffer.read(cx).read(cx);
22623 selections
22624 .into_iter()
22625 .map(|mut selection| {
22626 selection.start.0.0 =
22627 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22628 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22629 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22630 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22631 })
22632 .collect()
22633 }
22634
22635 fn report_editor_event(
22636 &self,
22637 reported_event: ReportEditorEvent,
22638 file_extension: Option<String>,
22639 cx: &App,
22640 ) {
22641 if cfg!(any(test, feature = "test-support")) {
22642 return;
22643 }
22644
22645 let Some(project) = &self.project else { return };
22646
22647 // If None, we are in a file without an extension
22648 let file = self
22649 .buffer
22650 .read(cx)
22651 .as_singleton()
22652 .and_then(|b| b.read(cx).file());
22653 let file_extension = file_extension.or(file
22654 .as_ref()
22655 .and_then(|file| Path::new(file.file_name(cx)).extension())
22656 .and_then(|e| e.to_str())
22657 .map(|a| a.to_string()));
22658
22659 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22660 .map(|vim_mode| vim_mode.0)
22661 .unwrap_or(false);
22662
22663 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22664 let copilot_enabled = edit_predictions_provider
22665 == language::language_settings::EditPredictionProvider::Copilot;
22666 let copilot_enabled_for_language = self
22667 .buffer
22668 .read(cx)
22669 .language_settings(cx)
22670 .show_edit_predictions;
22671
22672 let project = project.read(cx);
22673 let event_type = reported_event.event_type();
22674
22675 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22676 telemetry::event!(
22677 event_type,
22678 type = if auto_saved {"autosave"} else {"manual"},
22679 file_extension,
22680 vim_mode,
22681 copilot_enabled,
22682 copilot_enabled_for_language,
22683 edit_predictions_provider,
22684 is_via_ssh = project.is_via_remote_server(),
22685 );
22686 } else {
22687 telemetry::event!(
22688 event_type,
22689 file_extension,
22690 vim_mode,
22691 copilot_enabled,
22692 copilot_enabled_for_language,
22693 edit_predictions_provider,
22694 is_via_ssh = project.is_via_remote_server(),
22695 );
22696 };
22697 }
22698
22699 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22700 /// with each line being an array of {text, highlight} objects.
22701 fn copy_highlight_json(
22702 &mut self,
22703 _: &CopyHighlightJson,
22704 window: &mut Window,
22705 cx: &mut Context<Self>,
22706 ) {
22707 #[derive(Serialize)]
22708 struct Chunk<'a> {
22709 text: String,
22710 highlight: Option<&'a str>,
22711 }
22712
22713 let snapshot = self.buffer.read(cx).snapshot(cx);
22714 let range = self
22715 .selected_text_range(false, window, cx)
22716 .and_then(|selection| {
22717 if selection.range.is_empty() {
22718 None
22719 } else {
22720 Some(
22721 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22722 selection.range.start,
22723 )))
22724 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22725 selection.range.end,
22726 ))),
22727 )
22728 }
22729 })
22730 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22731
22732 let chunks = snapshot.chunks(range, true);
22733 let mut lines = Vec::new();
22734 let mut line: VecDeque<Chunk> = VecDeque::new();
22735
22736 let Some(style) = self.style.as_ref() else {
22737 return;
22738 };
22739
22740 for chunk in chunks {
22741 let highlight = chunk
22742 .syntax_highlight_id
22743 .and_then(|id| id.name(&style.syntax));
22744 let mut chunk_lines = chunk.text.split('\n').peekable();
22745 while let Some(text) = chunk_lines.next() {
22746 let mut merged_with_last_token = false;
22747 if let Some(last_token) = line.back_mut()
22748 && last_token.highlight == highlight
22749 {
22750 last_token.text.push_str(text);
22751 merged_with_last_token = true;
22752 }
22753
22754 if !merged_with_last_token {
22755 line.push_back(Chunk {
22756 text: text.into(),
22757 highlight,
22758 });
22759 }
22760
22761 if chunk_lines.peek().is_some() {
22762 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22763 line.pop_front();
22764 }
22765 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22766 line.pop_back();
22767 }
22768
22769 lines.push(mem::take(&mut line));
22770 }
22771 }
22772 }
22773
22774 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22775 return;
22776 };
22777 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22778 }
22779
22780 pub fn open_context_menu(
22781 &mut self,
22782 _: &OpenContextMenu,
22783 window: &mut Window,
22784 cx: &mut Context<Self>,
22785 ) {
22786 self.request_autoscroll(Autoscroll::newest(), cx);
22787 let position = self
22788 .selections
22789 .newest_display(&self.display_snapshot(cx))
22790 .start;
22791 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22792 }
22793
22794 pub fn replay_insert_event(
22795 &mut self,
22796 text: &str,
22797 relative_utf16_range: Option<Range<isize>>,
22798 window: &mut Window,
22799 cx: &mut Context<Self>,
22800 ) {
22801 if !self.input_enabled {
22802 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22803 return;
22804 }
22805 if let Some(relative_utf16_range) = relative_utf16_range {
22806 let selections = self
22807 .selections
22808 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22809 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22810 let new_ranges = selections.into_iter().map(|range| {
22811 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22812 range
22813 .head()
22814 .0
22815 .0
22816 .saturating_add_signed(relative_utf16_range.start),
22817 ));
22818 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22819 range
22820 .head()
22821 .0
22822 .0
22823 .saturating_add_signed(relative_utf16_range.end),
22824 ));
22825 start..end
22826 });
22827 s.select_ranges(new_ranges);
22828 });
22829 }
22830
22831 self.handle_input(text, window, cx);
22832 }
22833
22834 pub fn is_focused(&self, window: &Window) -> bool {
22835 self.focus_handle.is_focused(window)
22836 }
22837
22838 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22839 cx.emit(EditorEvent::Focused);
22840
22841 if let Some(descendant) = self
22842 .last_focused_descendant
22843 .take()
22844 .and_then(|descendant| descendant.upgrade())
22845 {
22846 window.focus(&descendant);
22847 } else {
22848 if let Some(blame) = self.blame.as_ref() {
22849 blame.update(cx, GitBlame::focus)
22850 }
22851
22852 self.blink_manager.update(cx, BlinkManager::enable);
22853 self.show_cursor_names(window, cx);
22854 self.buffer.update(cx, |buffer, cx| {
22855 buffer.finalize_last_transaction(cx);
22856 if self.leader_id.is_none() {
22857 buffer.set_active_selections(
22858 &self.selections.disjoint_anchors_arc(),
22859 self.selections.line_mode(),
22860 self.cursor_shape,
22861 cx,
22862 );
22863 }
22864 });
22865
22866 if let Some(position_map) = self.last_position_map.clone() {
22867 EditorElement::mouse_moved(
22868 self,
22869 &MouseMoveEvent {
22870 position: window.mouse_position(),
22871 pressed_button: None,
22872 modifiers: window.modifiers(),
22873 },
22874 &position_map,
22875 window,
22876 cx,
22877 );
22878 }
22879 }
22880 }
22881
22882 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22883 cx.emit(EditorEvent::FocusedIn)
22884 }
22885
22886 fn handle_focus_out(
22887 &mut self,
22888 event: FocusOutEvent,
22889 _window: &mut Window,
22890 cx: &mut Context<Self>,
22891 ) {
22892 if event.blurred != self.focus_handle {
22893 self.last_focused_descendant = Some(event.blurred);
22894 }
22895 self.selection_drag_state = SelectionDragState::None;
22896 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22897 }
22898
22899 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22900 self.blink_manager.update(cx, BlinkManager::disable);
22901 self.buffer
22902 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22903
22904 if let Some(blame) = self.blame.as_ref() {
22905 blame.update(cx, GitBlame::blur)
22906 }
22907 if !self.hover_state.focused(window, cx) {
22908 hide_hover(self, cx);
22909 }
22910 if !self
22911 .context_menu
22912 .borrow()
22913 .as_ref()
22914 .is_some_and(|context_menu| context_menu.focused(window, cx))
22915 {
22916 self.hide_context_menu(window, cx);
22917 }
22918 self.take_active_edit_prediction(cx);
22919 cx.emit(EditorEvent::Blurred);
22920 cx.notify();
22921 }
22922
22923 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22924 let mut pending: String = window
22925 .pending_input_keystrokes()
22926 .into_iter()
22927 .flatten()
22928 .filter_map(|keystroke| keystroke.key_char.clone())
22929 .collect();
22930
22931 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22932 pending = "".to_string();
22933 }
22934
22935 let existing_pending = self
22936 .text_highlights::<PendingInput>(cx)
22937 .map(|(_, ranges)| ranges.to_vec());
22938 if existing_pending.is_none() && pending.is_empty() {
22939 return;
22940 }
22941 let transaction =
22942 self.transact(window, cx, |this, window, cx| {
22943 let selections = this
22944 .selections
22945 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22946 let edits = selections
22947 .iter()
22948 .map(|selection| (selection.end..selection.end, pending.clone()));
22949 this.edit(edits, cx);
22950 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22951 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22952 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22953 }));
22954 });
22955 if let Some(existing_ranges) = existing_pending {
22956 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22957 this.edit(edits, cx);
22958 }
22959 });
22960
22961 let snapshot = self.snapshot(window, cx);
22962 let ranges = self
22963 .selections
22964 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22965 .into_iter()
22966 .map(|selection| {
22967 snapshot.buffer_snapshot().anchor_after(selection.end)
22968 ..snapshot
22969 .buffer_snapshot()
22970 .anchor_before(selection.end + pending.len())
22971 })
22972 .collect();
22973
22974 if pending.is_empty() {
22975 self.clear_highlights::<PendingInput>(cx);
22976 } else {
22977 self.highlight_text::<PendingInput>(
22978 ranges,
22979 HighlightStyle {
22980 underline: Some(UnderlineStyle {
22981 thickness: px(1.),
22982 color: None,
22983 wavy: false,
22984 }),
22985 ..Default::default()
22986 },
22987 cx,
22988 );
22989 }
22990
22991 self.ime_transaction = self.ime_transaction.or(transaction);
22992 if let Some(transaction) = self.ime_transaction {
22993 self.buffer.update(cx, |buffer, cx| {
22994 buffer.group_until_transaction(transaction, cx);
22995 });
22996 }
22997
22998 if self.text_highlights::<PendingInput>(cx).is_none() {
22999 self.ime_transaction.take();
23000 }
23001 }
23002
23003 pub fn register_action_renderer(
23004 &mut self,
23005 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
23006 ) -> Subscription {
23007 let id = self.next_editor_action_id.post_inc();
23008 self.editor_actions
23009 .borrow_mut()
23010 .insert(id, Box::new(listener));
23011
23012 let editor_actions = self.editor_actions.clone();
23013 Subscription::new(move || {
23014 editor_actions.borrow_mut().remove(&id);
23015 })
23016 }
23017
23018 pub fn register_action<A: Action>(
23019 &mut self,
23020 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
23021 ) -> Subscription {
23022 let id = self.next_editor_action_id.post_inc();
23023 let listener = Arc::new(listener);
23024 self.editor_actions.borrow_mut().insert(
23025 id,
23026 Box::new(move |_, window, _| {
23027 let listener = listener.clone();
23028 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
23029 let action = action.downcast_ref().unwrap();
23030 if phase == DispatchPhase::Bubble {
23031 listener(action, window, cx)
23032 }
23033 })
23034 }),
23035 );
23036
23037 let editor_actions = self.editor_actions.clone();
23038 Subscription::new(move || {
23039 editor_actions.borrow_mut().remove(&id);
23040 })
23041 }
23042
23043 pub fn file_header_size(&self) -> u32 {
23044 FILE_HEADER_HEIGHT
23045 }
23046
23047 pub fn restore(
23048 &mut self,
23049 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
23050 window: &mut Window,
23051 cx: &mut Context<Self>,
23052 ) {
23053 self.buffer().update(cx, |multi_buffer, cx| {
23054 for (buffer_id, changes) in revert_changes {
23055 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
23056 buffer.update(cx, |buffer, cx| {
23057 buffer.edit(
23058 changes
23059 .into_iter()
23060 .map(|(range, text)| (range, text.to_string())),
23061 None,
23062 cx,
23063 );
23064 });
23065 }
23066 }
23067 });
23068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23069 selections.refresh()
23070 });
23071 }
23072
23073 pub fn to_pixel_point(
23074 &mut self,
23075 source: multi_buffer::Anchor,
23076 editor_snapshot: &EditorSnapshot,
23077 window: &mut Window,
23078 cx: &App,
23079 ) -> Option<gpui::Point<Pixels>> {
23080 let source_point = source.to_display_point(editor_snapshot);
23081 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23082 }
23083
23084 pub fn display_to_pixel_point(
23085 &mut self,
23086 source: DisplayPoint,
23087 editor_snapshot: &EditorSnapshot,
23088 window: &mut Window,
23089 cx: &App,
23090 ) -> Option<gpui::Point<Pixels>> {
23091 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23092 let text_layout_details = self.text_layout_details(window);
23093 let scroll_top = text_layout_details
23094 .scroll_anchor
23095 .scroll_position(editor_snapshot)
23096 .y;
23097
23098 if source.row().as_f64() < scroll_top.floor() {
23099 return None;
23100 }
23101 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23102 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23103 Some(gpui::Point::new(source_x, source_y))
23104 }
23105
23106 pub fn has_visible_completions_menu(&self) -> bool {
23107 !self.edit_prediction_preview_is_active()
23108 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23109 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23110 })
23111 }
23112
23113 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23114 if self.mode.is_minimap() {
23115 return;
23116 }
23117 self.addons
23118 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23119 }
23120
23121 pub fn unregister_addon<T: Addon>(&mut self) {
23122 self.addons.remove(&std::any::TypeId::of::<T>());
23123 }
23124
23125 pub fn addon<T: Addon>(&self) -> Option<&T> {
23126 let type_id = std::any::TypeId::of::<T>();
23127 self.addons
23128 .get(&type_id)
23129 .and_then(|item| item.to_any().downcast_ref::<T>())
23130 }
23131
23132 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23133 let type_id = std::any::TypeId::of::<T>();
23134 self.addons
23135 .get_mut(&type_id)
23136 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23137 }
23138
23139 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23140 let text_layout_details = self.text_layout_details(window);
23141 let style = &text_layout_details.editor_style;
23142 let font_id = window.text_system().resolve_font(&style.text.font());
23143 let font_size = style.text.font_size.to_pixels(window.rem_size());
23144 let line_height = style.text.line_height_in_pixels(window.rem_size());
23145 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23146 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23147
23148 CharacterDimensions {
23149 em_width,
23150 em_advance,
23151 line_height,
23152 }
23153 }
23154
23155 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23156 self.load_diff_task.clone()
23157 }
23158
23159 fn read_metadata_from_db(
23160 &mut self,
23161 item_id: u64,
23162 workspace_id: WorkspaceId,
23163 window: &mut Window,
23164 cx: &mut Context<Editor>,
23165 ) {
23166 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23167 && !self.mode.is_minimap()
23168 && WorkspaceSettings::get(None, cx).restore_on_startup
23169 != RestoreOnStartupBehavior::EmptyTab
23170 {
23171 let buffer_snapshot = OnceCell::new();
23172
23173 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23174 && !folds.is_empty()
23175 {
23176 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23177 self.fold_ranges(
23178 folds
23179 .into_iter()
23180 .map(|(start, end)| {
23181 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23182 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23183 })
23184 .collect(),
23185 false,
23186 window,
23187 cx,
23188 );
23189 }
23190
23191 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23192 && !selections.is_empty()
23193 {
23194 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23195 // skip adding the initial selection to selection history
23196 self.selection_history.mode = SelectionHistoryMode::Skipping;
23197 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23198 s.select_ranges(selections.into_iter().map(|(start, end)| {
23199 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23200 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23201 }));
23202 });
23203 self.selection_history.mode = SelectionHistoryMode::Normal;
23204 };
23205 }
23206
23207 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23208 }
23209
23210 fn update_lsp_data(
23211 &mut self,
23212 for_buffer: Option<BufferId>,
23213 window: &mut Window,
23214 cx: &mut Context<'_, Self>,
23215 ) {
23216 self.pull_diagnostics(for_buffer, window, cx);
23217 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23218 }
23219
23220 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23221 if self.ignore_lsp_data() {
23222 return;
23223 }
23224 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23225 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23226 }
23227 }
23228
23229 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23230 if self.ignore_lsp_data() {
23231 return;
23232 }
23233
23234 if !self.registered_buffers.contains_key(&buffer_id)
23235 && let Some(project) = self.project.as_ref()
23236 {
23237 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23238 project.update(cx, |project, cx| {
23239 self.registered_buffers.insert(
23240 buffer_id,
23241 project.register_buffer_with_language_servers(&buffer, cx),
23242 );
23243 });
23244 } else {
23245 self.registered_buffers.remove(&buffer_id);
23246 }
23247 }
23248 }
23249
23250 fn ignore_lsp_data(&self) -> bool {
23251 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23252 // skip any LSP updates for it.
23253 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23254 }
23255
23256 fn create_style(&self, cx: &App) -> EditorStyle {
23257 let settings = ThemeSettings::get_global(cx);
23258
23259 let mut text_style = match self.mode {
23260 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23261 color: cx.theme().colors().editor_foreground,
23262 font_family: settings.ui_font.family.clone(),
23263 font_features: settings.ui_font.features.clone(),
23264 font_fallbacks: settings.ui_font.fallbacks.clone(),
23265 font_size: rems(0.875).into(),
23266 font_weight: settings.ui_font.weight,
23267 line_height: relative(settings.buffer_line_height.value()),
23268 ..Default::default()
23269 },
23270 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23271 color: cx.theme().colors().editor_foreground,
23272 font_family: settings.buffer_font.family.clone(),
23273 font_features: settings.buffer_font.features.clone(),
23274 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23275 font_size: settings.buffer_font_size(cx).into(),
23276 font_weight: settings.buffer_font.weight,
23277 line_height: relative(settings.buffer_line_height.value()),
23278 ..Default::default()
23279 },
23280 };
23281 if let Some(text_style_refinement) = &self.text_style_refinement {
23282 text_style.refine(text_style_refinement)
23283 }
23284
23285 let background = match self.mode {
23286 EditorMode::SingleLine => cx.theme().system().transparent,
23287 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23288 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23289 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23290 };
23291
23292 EditorStyle {
23293 background,
23294 border: cx.theme().colors().border,
23295 local_player: cx.theme().players().local(),
23296 text: text_style,
23297 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23298 syntax: cx.theme().syntax().clone(),
23299 status: cx.theme().status().clone(),
23300 inlay_hints_style: make_inlay_hints_style(cx),
23301 edit_prediction_styles: make_suggestion_styles(cx),
23302 unnecessary_code_fade: settings.unnecessary_code_fade,
23303 show_underlines: self.diagnostics_enabled(),
23304 }
23305 }
23306}
23307
23308fn edit_for_markdown_paste<'a>(
23309 buffer: &MultiBufferSnapshot,
23310 range: Range<MultiBufferOffset>,
23311 to_insert: &'a str,
23312 url: Option<url::Url>,
23313) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23314 if url.is_none() {
23315 return (range, Cow::Borrowed(to_insert));
23316 };
23317
23318 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23319
23320 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23321 Cow::Borrowed(to_insert)
23322 } else {
23323 Cow::Owned(format!("[{old_text}]({to_insert})"))
23324 };
23325 (range, new_text)
23326}
23327
23328fn process_completion_for_edit(
23329 completion: &Completion,
23330 intent: CompletionIntent,
23331 buffer: &Entity<Buffer>,
23332 cursor_position: &text::Anchor,
23333 cx: &mut Context<Editor>,
23334) -> CompletionEdit {
23335 let buffer = buffer.read(cx);
23336 let buffer_snapshot = buffer.snapshot();
23337 let (snippet, new_text) = if completion.is_snippet() {
23338 let mut snippet_source = completion.new_text.clone();
23339 // Workaround for typescript language server issues so that methods don't expand within
23340 // strings and functions with type expressions. The previous point is used because the query
23341 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23342 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23343 let previous_point = if previous_point.column > 0 {
23344 cursor_position.to_previous_offset(&buffer_snapshot)
23345 } else {
23346 cursor_position.to_offset(&buffer_snapshot)
23347 };
23348 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23349 && scope.prefers_label_for_snippet_in_completion()
23350 && let Some(label) = completion.label()
23351 && matches!(
23352 completion.kind(),
23353 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23354 )
23355 {
23356 snippet_source = label;
23357 }
23358 match Snippet::parse(&snippet_source).log_err() {
23359 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23360 None => (None, completion.new_text.clone()),
23361 }
23362 } else {
23363 (None, completion.new_text.clone())
23364 };
23365
23366 let mut range_to_replace = {
23367 let replace_range = &completion.replace_range;
23368 if let CompletionSource::Lsp {
23369 insert_range: Some(insert_range),
23370 ..
23371 } = &completion.source
23372 {
23373 debug_assert_eq!(
23374 insert_range.start, replace_range.start,
23375 "insert_range and replace_range should start at the same position"
23376 );
23377 debug_assert!(
23378 insert_range
23379 .start
23380 .cmp(cursor_position, &buffer_snapshot)
23381 .is_le(),
23382 "insert_range should start before or at cursor position"
23383 );
23384 debug_assert!(
23385 replace_range
23386 .start
23387 .cmp(cursor_position, &buffer_snapshot)
23388 .is_le(),
23389 "replace_range should start before or at cursor position"
23390 );
23391
23392 let should_replace = match intent {
23393 CompletionIntent::CompleteWithInsert => false,
23394 CompletionIntent::CompleteWithReplace => true,
23395 CompletionIntent::Complete | CompletionIntent::Compose => {
23396 let insert_mode =
23397 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23398 .completions
23399 .lsp_insert_mode;
23400 match insert_mode {
23401 LspInsertMode::Insert => false,
23402 LspInsertMode::Replace => true,
23403 LspInsertMode::ReplaceSubsequence => {
23404 let mut text_to_replace = buffer.chars_for_range(
23405 buffer.anchor_before(replace_range.start)
23406 ..buffer.anchor_after(replace_range.end),
23407 );
23408 let mut current_needle = text_to_replace.next();
23409 for haystack_ch in completion.label.text.chars() {
23410 if let Some(needle_ch) = current_needle
23411 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23412 {
23413 current_needle = text_to_replace.next();
23414 }
23415 }
23416 current_needle.is_none()
23417 }
23418 LspInsertMode::ReplaceSuffix => {
23419 if replace_range
23420 .end
23421 .cmp(cursor_position, &buffer_snapshot)
23422 .is_gt()
23423 {
23424 let range_after_cursor = *cursor_position..replace_range.end;
23425 let text_after_cursor = buffer
23426 .text_for_range(
23427 buffer.anchor_before(range_after_cursor.start)
23428 ..buffer.anchor_after(range_after_cursor.end),
23429 )
23430 .collect::<String>()
23431 .to_ascii_lowercase();
23432 completion
23433 .label
23434 .text
23435 .to_ascii_lowercase()
23436 .ends_with(&text_after_cursor)
23437 } else {
23438 true
23439 }
23440 }
23441 }
23442 }
23443 };
23444
23445 if should_replace {
23446 replace_range.clone()
23447 } else {
23448 insert_range.clone()
23449 }
23450 } else {
23451 replace_range.clone()
23452 }
23453 };
23454
23455 if range_to_replace
23456 .end
23457 .cmp(cursor_position, &buffer_snapshot)
23458 .is_lt()
23459 {
23460 range_to_replace.end = *cursor_position;
23461 }
23462
23463 let replace_range = range_to_replace.to_offset(buffer);
23464 CompletionEdit {
23465 new_text,
23466 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23467 snippet,
23468 }
23469}
23470
23471struct CompletionEdit {
23472 new_text: String,
23473 replace_range: Range<BufferOffset>,
23474 snippet: Option<Snippet>,
23475}
23476
23477fn insert_extra_newline_brackets(
23478 buffer: &MultiBufferSnapshot,
23479 range: Range<MultiBufferOffset>,
23480 language: &language::LanguageScope,
23481) -> bool {
23482 let leading_whitespace_len = buffer
23483 .reversed_chars_at(range.start)
23484 .take_while(|c| c.is_whitespace() && *c != '\n')
23485 .map(|c| c.len_utf8())
23486 .sum::<usize>();
23487 let trailing_whitespace_len = buffer
23488 .chars_at(range.end)
23489 .take_while(|c| c.is_whitespace() && *c != '\n')
23490 .map(|c| c.len_utf8())
23491 .sum::<usize>();
23492 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23493
23494 language.brackets().any(|(pair, enabled)| {
23495 let pair_start = pair.start.trim_end();
23496 let pair_end = pair.end.trim_start();
23497
23498 enabled
23499 && pair.newline
23500 && buffer.contains_str_at(range.end, pair_end)
23501 && buffer.contains_str_at(
23502 range.start.saturating_sub_usize(pair_start.len()),
23503 pair_start,
23504 )
23505 })
23506}
23507
23508fn insert_extra_newline_tree_sitter(
23509 buffer: &MultiBufferSnapshot,
23510 range: Range<MultiBufferOffset>,
23511) -> bool {
23512 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23513 [(buffer, range, _)] => (*buffer, range.clone()),
23514 _ => return false,
23515 };
23516 let pair = {
23517 let mut result: Option<BracketMatch<usize>> = None;
23518
23519 for pair in buffer
23520 .all_bracket_ranges(range.start.0..range.end.0)
23521 .filter(move |pair| {
23522 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23523 })
23524 {
23525 let len = pair.close_range.end - pair.open_range.start;
23526
23527 if let Some(existing) = &result {
23528 let existing_len = existing.close_range.end - existing.open_range.start;
23529 if len > existing_len {
23530 continue;
23531 }
23532 }
23533
23534 result = Some(pair);
23535 }
23536
23537 result
23538 };
23539 let Some(pair) = pair else {
23540 return false;
23541 };
23542 pair.newline_only
23543 && buffer
23544 .chars_for_range(pair.open_range.end..range.start.0)
23545 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23546 .all(|c| c.is_whitespace() && c != '\n')
23547}
23548
23549fn update_uncommitted_diff_for_buffer(
23550 editor: Entity<Editor>,
23551 project: &Entity<Project>,
23552 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23553 buffer: Entity<MultiBuffer>,
23554 cx: &mut App,
23555) -> Task<()> {
23556 let mut tasks = Vec::new();
23557 project.update(cx, |project, cx| {
23558 for buffer in buffers {
23559 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23560 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23561 }
23562 }
23563 });
23564 cx.spawn(async move |cx| {
23565 let diffs = future::join_all(tasks).await;
23566 if editor
23567 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23568 .unwrap_or(false)
23569 {
23570 return;
23571 }
23572
23573 buffer
23574 .update(cx, |buffer, cx| {
23575 for diff in diffs.into_iter().flatten() {
23576 buffer.add_diff(diff, cx);
23577 }
23578 })
23579 .ok();
23580 })
23581}
23582
23583fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23584 let tab_size = tab_size.get() as usize;
23585 let mut width = offset;
23586
23587 for ch in text.chars() {
23588 width += if ch == '\t' {
23589 tab_size - (width % tab_size)
23590 } else {
23591 1
23592 };
23593 }
23594
23595 width - offset
23596}
23597
23598#[cfg(test)]
23599mod tests {
23600 use super::*;
23601
23602 #[test]
23603 fn test_string_size_with_expanded_tabs() {
23604 let nz = |val| NonZeroU32::new(val).unwrap();
23605 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23606 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23607 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23608 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23609 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23610 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23611 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23612 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23613 }
23614}
23615
23616/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23617struct WordBreakingTokenizer<'a> {
23618 input: &'a str,
23619}
23620
23621impl<'a> WordBreakingTokenizer<'a> {
23622 fn new(input: &'a str) -> Self {
23623 Self { input }
23624 }
23625}
23626
23627fn is_char_ideographic(ch: char) -> bool {
23628 use unicode_script::Script::*;
23629 use unicode_script::UnicodeScript;
23630 matches!(ch.script(), Han | Tangut | Yi)
23631}
23632
23633fn is_grapheme_ideographic(text: &str) -> bool {
23634 text.chars().any(is_char_ideographic)
23635}
23636
23637fn is_grapheme_whitespace(text: &str) -> bool {
23638 text.chars().any(|x| x.is_whitespace())
23639}
23640
23641fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23642 text.chars()
23643 .next()
23644 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23645}
23646
23647#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23648enum WordBreakToken<'a> {
23649 Word { token: &'a str, grapheme_len: usize },
23650 InlineWhitespace { token: &'a str, grapheme_len: usize },
23651 Newline,
23652}
23653
23654impl<'a> Iterator for WordBreakingTokenizer<'a> {
23655 /// Yields a span, the count of graphemes in the token, and whether it was
23656 /// whitespace. Note that it also breaks at word boundaries.
23657 type Item = WordBreakToken<'a>;
23658
23659 fn next(&mut self) -> Option<Self::Item> {
23660 use unicode_segmentation::UnicodeSegmentation;
23661 if self.input.is_empty() {
23662 return None;
23663 }
23664
23665 let mut iter = self.input.graphemes(true).peekable();
23666 let mut offset = 0;
23667 let mut grapheme_len = 0;
23668 if let Some(first_grapheme) = iter.next() {
23669 let is_newline = first_grapheme == "\n";
23670 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23671 offset += first_grapheme.len();
23672 grapheme_len += 1;
23673 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23674 if let Some(grapheme) = iter.peek().copied()
23675 && should_stay_with_preceding_ideograph(grapheme)
23676 {
23677 offset += grapheme.len();
23678 grapheme_len += 1;
23679 }
23680 } else {
23681 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23682 let mut next_word_bound = words.peek().copied();
23683 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23684 next_word_bound = words.next();
23685 }
23686 while let Some(grapheme) = iter.peek().copied() {
23687 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23688 break;
23689 };
23690 if is_grapheme_whitespace(grapheme) != is_whitespace
23691 || (grapheme == "\n") != is_newline
23692 {
23693 break;
23694 };
23695 offset += grapheme.len();
23696 grapheme_len += 1;
23697 iter.next();
23698 }
23699 }
23700 let token = &self.input[..offset];
23701 self.input = &self.input[offset..];
23702 if token == "\n" {
23703 Some(WordBreakToken::Newline)
23704 } else if is_whitespace {
23705 Some(WordBreakToken::InlineWhitespace {
23706 token,
23707 grapheme_len,
23708 })
23709 } else {
23710 Some(WordBreakToken::Word {
23711 token,
23712 grapheme_len,
23713 })
23714 }
23715 } else {
23716 None
23717 }
23718 }
23719}
23720
23721#[test]
23722fn test_word_breaking_tokenizer() {
23723 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23724 ("", &[]),
23725 (" ", &[whitespace(" ", 2)]),
23726 ("Ʒ", &[word("Ʒ", 1)]),
23727 ("Ǽ", &[word("Ǽ", 1)]),
23728 ("⋑", &[word("⋑", 1)]),
23729 ("⋑⋑", &[word("⋑⋑", 2)]),
23730 (
23731 "原理,进而",
23732 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23733 ),
23734 (
23735 "hello world",
23736 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23737 ),
23738 (
23739 "hello, world",
23740 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23741 ),
23742 (
23743 " hello world",
23744 &[
23745 whitespace(" ", 2),
23746 word("hello", 5),
23747 whitespace(" ", 1),
23748 word("world", 5),
23749 ],
23750 ),
23751 (
23752 "这是什么 \n 钢笔",
23753 &[
23754 word("这", 1),
23755 word("是", 1),
23756 word("什", 1),
23757 word("么", 1),
23758 whitespace(" ", 1),
23759 newline(),
23760 whitespace(" ", 1),
23761 word("钢", 1),
23762 word("笔", 1),
23763 ],
23764 ),
23765 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23766 ];
23767
23768 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23769 WordBreakToken::Word {
23770 token,
23771 grapheme_len,
23772 }
23773 }
23774
23775 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23776 WordBreakToken::InlineWhitespace {
23777 token,
23778 grapheme_len,
23779 }
23780 }
23781
23782 fn newline() -> WordBreakToken<'static> {
23783 WordBreakToken::Newline
23784 }
23785
23786 for (input, result) in tests {
23787 assert_eq!(
23788 WordBreakingTokenizer::new(input)
23789 .collect::<Vec<_>>()
23790 .as_slice(),
23791 *result,
23792 );
23793 }
23794}
23795
23796fn wrap_with_prefix(
23797 first_line_prefix: String,
23798 subsequent_lines_prefix: String,
23799 unwrapped_text: String,
23800 wrap_column: usize,
23801 tab_size: NonZeroU32,
23802 preserve_existing_whitespace: bool,
23803) -> String {
23804 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23805 let subsequent_lines_prefix_len =
23806 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23807 let mut wrapped_text = String::new();
23808 let mut current_line = first_line_prefix;
23809 let mut is_first_line = true;
23810
23811 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23812 let mut current_line_len = first_line_prefix_len;
23813 let mut in_whitespace = false;
23814 for token in tokenizer {
23815 let have_preceding_whitespace = in_whitespace;
23816 match token {
23817 WordBreakToken::Word {
23818 token,
23819 grapheme_len,
23820 } => {
23821 in_whitespace = false;
23822 let current_prefix_len = if is_first_line {
23823 first_line_prefix_len
23824 } else {
23825 subsequent_lines_prefix_len
23826 };
23827 if current_line_len + grapheme_len > wrap_column
23828 && current_line_len != current_prefix_len
23829 {
23830 wrapped_text.push_str(current_line.trim_end());
23831 wrapped_text.push('\n');
23832 is_first_line = false;
23833 current_line = subsequent_lines_prefix.clone();
23834 current_line_len = subsequent_lines_prefix_len;
23835 }
23836 current_line.push_str(token);
23837 current_line_len += grapheme_len;
23838 }
23839 WordBreakToken::InlineWhitespace {
23840 mut token,
23841 mut grapheme_len,
23842 } => {
23843 in_whitespace = true;
23844 if have_preceding_whitespace && !preserve_existing_whitespace {
23845 continue;
23846 }
23847 if !preserve_existing_whitespace {
23848 // Keep a single whitespace grapheme as-is
23849 if let Some(first) =
23850 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23851 {
23852 token = first;
23853 } else {
23854 token = " ";
23855 }
23856 grapheme_len = 1;
23857 }
23858 let current_prefix_len = if is_first_line {
23859 first_line_prefix_len
23860 } else {
23861 subsequent_lines_prefix_len
23862 };
23863 if current_line_len + grapheme_len > wrap_column {
23864 wrapped_text.push_str(current_line.trim_end());
23865 wrapped_text.push('\n');
23866 is_first_line = false;
23867 current_line = subsequent_lines_prefix.clone();
23868 current_line_len = subsequent_lines_prefix_len;
23869 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23870 current_line.push_str(token);
23871 current_line_len += grapheme_len;
23872 }
23873 }
23874 WordBreakToken::Newline => {
23875 in_whitespace = true;
23876 let current_prefix_len = if is_first_line {
23877 first_line_prefix_len
23878 } else {
23879 subsequent_lines_prefix_len
23880 };
23881 if preserve_existing_whitespace {
23882 wrapped_text.push_str(current_line.trim_end());
23883 wrapped_text.push('\n');
23884 is_first_line = false;
23885 current_line = subsequent_lines_prefix.clone();
23886 current_line_len = subsequent_lines_prefix_len;
23887 } else if have_preceding_whitespace {
23888 continue;
23889 } else if current_line_len + 1 > wrap_column
23890 && current_line_len != current_prefix_len
23891 {
23892 wrapped_text.push_str(current_line.trim_end());
23893 wrapped_text.push('\n');
23894 is_first_line = false;
23895 current_line = subsequent_lines_prefix.clone();
23896 current_line_len = subsequent_lines_prefix_len;
23897 } else if current_line_len != current_prefix_len {
23898 current_line.push(' ');
23899 current_line_len += 1;
23900 }
23901 }
23902 }
23903 }
23904
23905 if !current_line.is_empty() {
23906 wrapped_text.push_str(¤t_line);
23907 }
23908 wrapped_text
23909}
23910
23911#[test]
23912fn test_wrap_with_prefix() {
23913 assert_eq!(
23914 wrap_with_prefix(
23915 "# ".to_string(),
23916 "# ".to_string(),
23917 "abcdefg".to_string(),
23918 4,
23919 NonZeroU32::new(4).unwrap(),
23920 false,
23921 ),
23922 "# abcdefg"
23923 );
23924 assert_eq!(
23925 wrap_with_prefix(
23926 "".to_string(),
23927 "".to_string(),
23928 "\thello world".to_string(),
23929 8,
23930 NonZeroU32::new(4).unwrap(),
23931 false,
23932 ),
23933 "hello\nworld"
23934 );
23935 assert_eq!(
23936 wrap_with_prefix(
23937 "// ".to_string(),
23938 "// ".to_string(),
23939 "xx \nyy zz aa bb cc".to_string(),
23940 12,
23941 NonZeroU32::new(4).unwrap(),
23942 false,
23943 ),
23944 "// xx yy zz\n// aa bb cc"
23945 );
23946 assert_eq!(
23947 wrap_with_prefix(
23948 String::new(),
23949 String::new(),
23950 "这是什么 \n 钢笔".to_string(),
23951 3,
23952 NonZeroU32::new(4).unwrap(),
23953 false,
23954 ),
23955 "这是什\n么 钢\n笔"
23956 );
23957 assert_eq!(
23958 wrap_with_prefix(
23959 String::new(),
23960 String::new(),
23961 format!("foo{}bar", '\u{2009}'), // thin space
23962 80,
23963 NonZeroU32::new(4).unwrap(),
23964 false,
23965 ),
23966 format!("foo{}bar", '\u{2009}')
23967 );
23968}
23969
23970pub trait CollaborationHub {
23971 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23972 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23973 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23974}
23975
23976impl CollaborationHub for Entity<Project> {
23977 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23978 self.read(cx).collaborators()
23979 }
23980
23981 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23982 self.read(cx).user_store().read(cx).participant_indices()
23983 }
23984
23985 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23986 let this = self.read(cx);
23987 let user_ids = this.collaborators().values().map(|c| c.user_id);
23988 this.user_store().read(cx).participant_names(user_ids, cx)
23989 }
23990}
23991
23992pub trait SemanticsProvider {
23993 fn hover(
23994 &self,
23995 buffer: &Entity<Buffer>,
23996 position: text::Anchor,
23997 cx: &mut App,
23998 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23999
24000 fn inline_values(
24001 &self,
24002 buffer_handle: Entity<Buffer>,
24003 range: Range<text::Anchor>,
24004 cx: &mut App,
24005 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24006
24007 fn applicable_inlay_chunks(
24008 &self,
24009 buffer: &Entity<Buffer>,
24010 ranges: &[Range<text::Anchor>],
24011 cx: &mut App,
24012 ) -> Vec<Range<BufferRow>>;
24013
24014 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24015
24016 fn inlay_hints(
24017 &self,
24018 invalidate: InvalidationStrategy,
24019 buffer: Entity<Buffer>,
24020 ranges: Vec<Range<text::Anchor>>,
24021 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24022 cx: &mut App,
24023 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24024
24025 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24026
24027 fn document_highlights(
24028 &self,
24029 buffer: &Entity<Buffer>,
24030 position: text::Anchor,
24031 cx: &mut App,
24032 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24033
24034 fn definitions(
24035 &self,
24036 buffer: &Entity<Buffer>,
24037 position: text::Anchor,
24038 kind: GotoDefinitionKind,
24039 cx: &mut App,
24040 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24041
24042 fn range_for_rename(
24043 &self,
24044 buffer: &Entity<Buffer>,
24045 position: text::Anchor,
24046 cx: &mut App,
24047 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24048
24049 fn perform_rename(
24050 &self,
24051 buffer: &Entity<Buffer>,
24052 position: text::Anchor,
24053 new_name: String,
24054 cx: &mut App,
24055 ) -> Option<Task<Result<ProjectTransaction>>>;
24056}
24057
24058pub trait CompletionProvider {
24059 fn completions(
24060 &self,
24061 excerpt_id: ExcerptId,
24062 buffer: &Entity<Buffer>,
24063 buffer_position: text::Anchor,
24064 trigger: CompletionContext,
24065 window: &mut Window,
24066 cx: &mut Context<Editor>,
24067 ) -> Task<Result<Vec<CompletionResponse>>>;
24068
24069 fn resolve_completions(
24070 &self,
24071 _buffer: Entity<Buffer>,
24072 _completion_indices: Vec<usize>,
24073 _completions: Rc<RefCell<Box<[Completion]>>>,
24074 _cx: &mut Context<Editor>,
24075 ) -> Task<Result<bool>> {
24076 Task::ready(Ok(false))
24077 }
24078
24079 fn apply_additional_edits_for_completion(
24080 &self,
24081 _buffer: Entity<Buffer>,
24082 _completions: Rc<RefCell<Box<[Completion]>>>,
24083 _completion_index: usize,
24084 _push_to_history: bool,
24085 _cx: &mut Context<Editor>,
24086 ) -> Task<Result<Option<language::Transaction>>> {
24087 Task::ready(Ok(None))
24088 }
24089
24090 fn is_completion_trigger(
24091 &self,
24092 buffer: &Entity<Buffer>,
24093 position: language::Anchor,
24094 text: &str,
24095 trigger_in_words: bool,
24096 cx: &mut Context<Editor>,
24097 ) -> bool;
24098
24099 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24100
24101 fn sort_completions(&self) -> bool {
24102 true
24103 }
24104
24105 fn filter_completions(&self) -> bool {
24106 true
24107 }
24108
24109 fn show_snippets(&self) -> bool {
24110 false
24111 }
24112}
24113
24114pub trait CodeActionProvider {
24115 fn id(&self) -> Arc<str>;
24116
24117 fn code_actions(
24118 &self,
24119 buffer: &Entity<Buffer>,
24120 range: Range<text::Anchor>,
24121 window: &mut Window,
24122 cx: &mut App,
24123 ) -> Task<Result<Vec<CodeAction>>>;
24124
24125 fn apply_code_action(
24126 &self,
24127 buffer_handle: Entity<Buffer>,
24128 action: CodeAction,
24129 excerpt_id: ExcerptId,
24130 push_to_history: bool,
24131 window: &mut Window,
24132 cx: &mut App,
24133 ) -> Task<Result<ProjectTransaction>>;
24134}
24135
24136impl CodeActionProvider for Entity<Project> {
24137 fn id(&self) -> Arc<str> {
24138 "project".into()
24139 }
24140
24141 fn code_actions(
24142 &self,
24143 buffer: &Entity<Buffer>,
24144 range: Range<text::Anchor>,
24145 _window: &mut Window,
24146 cx: &mut App,
24147 ) -> Task<Result<Vec<CodeAction>>> {
24148 self.update(cx, |project, cx| {
24149 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24150 let code_actions = project.code_actions(buffer, range, None, cx);
24151 cx.background_spawn(async move {
24152 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24153 Ok(code_lens_actions
24154 .context("code lens fetch")?
24155 .into_iter()
24156 .flatten()
24157 .chain(
24158 code_actions
24159 .context("code action fetch")?
24160 .into_iter()
24161 .flatten(),
24162 )
24163 .collect())
24164 })
24165 })
24166 }
24167
24168 fn apply_code_action(
24169 &self,
24170 buffer_handle: Entity<Buffer>,
24171 action: CodeAction,
24172 _excerpt_id: ExcerptId,
24173 push_to_history: bool,
24174 _window: &mut Window,
24175 cx: &mut App,
24176 ) -> Task<Result<ProjectTransaction>> {
24177 self.update(cx, |project, cx| {
24178 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24179 })
24180 }
24181}
24182
24183fn snippet_completions(
24184 project: &Project,
24185 buffer: &Entity<Buffer>,
24186 buffer_anchor: text::Anchor,
24187 classifier: CharClassifier,
24188 cx: &mut App,
24189) -> Task<Result<CompletionResponse>> {
24190 let languages = buffer.read(cx).languages_at(buffer_anchor);
24191 let snippet_store = project.snippets().read(cx);
24192
24193 let scopes: Vec<_> = languages
24194 .iter()
24195 .filter_map(|language| {
24196 let language_name = language.lsp_id();
24197 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24198
24199 if snippets.is_empty() {
24200 None
24201 } else {
24202 Some((language.default_scope(), snippets))
24203 }
24204 })
24205 .collect();
24206
24207 if scopes.is_empty() {
24208 return Task::ready(Ok(CompletionResponse {
24209 completions: vec![],
24210 display_options: CompletionDisplayOptions::default(),
24211 is_incomplete: false,
24212 }));
24213 }
24214
24215 let snapshot = buffer.read(cx).text_snapshot();
24216 let executor = cx.background_executor().clone();
24217
24218 cx.background_spawn(async move {
24219 let is_word_char = |c| classifier.is_word(c);
24220
24221 let mut is_incomplete = false;
24222 let mut completions: Vec<Completion> = Vec::new();
24223
24224 const MAX_PREFIX_LEN: usize = 128;
24225 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24226 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24227 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24228
24229 let max_buffer_window: String = snapshot
24230 .text_for_range(window_start..buffer_offset)
24231 .collect();
24232
24233 if max_buffer_window.is_empty() {
24234 return Ok(CompletionResponse {
24235 completions: vec![],
24236 display_options: CompletionDisplayOptions::default(),
24237 is_incomplete: true,
24238 });
24239 }
24240
24241 for (_scope, snippets) in scopes.into_iter() {
24242 // Sort snippets by word count to match longer snippet prefixes first.
24243 let mut sorted_snippet_candidates = snippets
24244 .iter()
24245 .enumerate()
24246 .flat_map(|(snippet_ix, snippet)| {
24247 snippet
24248 .prefix
24249 .iter()
24250 .enumerate()
24251 .map(move |(prefix_ix, prefix)| {
24252 let word_count =
24253 snippet_candidate_suffixes(prefix, is_word_char).count();
24254 ((snippet_ix, prefix_ix), prefix, word_count)
24255 })
24256 })
24257 .collect_vec();
24258 sorted_snippet_candidates
24259 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24260
24261 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24262
24263 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24264 .take(
24265 sorted_snippet_candidates
24266 .first()
24267 .map(|(_, _, word_count)| *word_count)
24268 .unwrap_or_default(),
24269 )
24270 .collect_vec();
24271
24272 const MAX_RESULTS: usize = 100;
24273 // Each match also remembers how many characters from the buffer it consumed
24274 let mut matches: Vec<(StringMatch, usize)> = vec![];
24275
24276 let mut snippet_list_cutoff_index = 0;
24277 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24278 let word_count = buffer_index + 1;
24279 // Increase `snippet_list_cutoff_index` until we have all of the
24280 // snippets with sufficiently many words.
24281 while sorted_snippet_candidates
24282 .get(snippet_list_cutoff_index)
24283 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24284 *snippet_word_count >= word_count
24285 })
24286 {
24287 snippet_list_cutoff_index += 1;
24288 }
24289
24290 // Take only the candidates with at least `word_count` many words
24291 let snippet_candidates_at_word_len =
24292 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24293
24294 let candidates = snippet_candidates_at_word_len
24295 .iter()
24296 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24297 .enumerate() // index in `sorted_snippet_candidates`
24298 // First char must match
24299 .filter(|(_ix, prefix)| {
24300 itertools::equal(
24301 prefix
24302 .chars()
24303 .next()
24304 .into_iter()
24305 .flat_map(|c| c.to_lowercase()),
24306 buffer_window
24307 .chars()
24308 .next()
24309 .into_iter()
24310 .flat_map(|c| c.to_lowercase()),
24311 )
24312 })
24313 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24314 .collect::<Vec<StringMatchCandidate>>();
24315
24316 matches.extend(
24317 fuzzy::match_strings(
24318 &candidates,
24319 &buffer_window,
24320 buffer_window.chars().any(|c| c.is_uppercase()),
24321 true,
24322 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24323 &Default::default(),
24324 executor.clone(),
24325 )
24326 .await
24327 .into_iter()
24328 .map(|string_match| (string_match, buffer_window.len())),
24329 );
24330
24331 if matches.len() >= MAX_RESULTS {
24332 break;
24333 }
24334 }
24335
24336 let to_lsp = |point: &text::Anchor| {
24337 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24338 point_to_lsp(end)
24339 };
24340 let lsp_end = to_lsp(&buffer_anchor);
24341
24342 if matches.len() >= MAX_RESULTS {
24343 is_incomplete = true;
24344 }
24345
24346 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24347 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24348 sorted_snippet_candidates[string_match.candidate_id];
24349 let snippet = &snippets[snippet_index];
24350 let start = buffer_offset - buffer_window_len;
24351 let start = snapshot.anchor_before(start);
24352 let range = start..buffer_anchor;
24353 let lsp_start = to_lsp(&start);
24354 let lsp_range = lsp::Range {
24355 start: lsp_start,
24356 end: lsp_end,
24357 };
24358 Completion {
24359 replace_range: range,
24360 new_text: snippet.body.clone(),
24361 source: CompletionSource::Lsp {
24362 insert_range: None,
24363 server_id: LanguageServerId(usize::MAX),
24364 resolved: true,
24365 lsp_completion: Box::new(lsp::CompletionItem {
24366 label: snippet.prefix.first().unwrap().clone(),
24367 kind: Some(CompletionItemKind::SNIPPET),
24368 label_details: snippet.description.as_ref().map(|description| {
24369 lsp::CompletionItemLabelDetails {
24370 detail: Some(description.clone()),
24371 description: None,
24372 }
24373 }),
24374 insert_text_format: Some(InsertTextFormat::SNIPPET),
24375 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24376 lsp::InsertReplaceEdit {
24377 new_text: snippet.body.clone(),
24378 insert: lsp_range,
24379 replace: lsp_range,
24380 },
24381 )),
24382 filter_text: Some(snippet.body.clone()),
24383 sort_text: Some(char::MAX.to_string()),
24384 ..lsp::CompletionItem::default()
24385 }),
24386 lsp_defaults: None,
24387 },
24388 label: CodeLabel {
24389 text: matching_prefix.clone(),
24390 runs: Vec::new(),
24391 filter_range: 0..matching_prefix.len(),
24392 },
24393 icon_path: None,
24394 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24395 single_line: snippet.name.clone().into(),
24396 plain_text: snippet
24397 .description
24398 .clone()
24399 .map(|description| description.into()),
24400 }),
24401 insert_text_mode: None,
24402 confirm: None,
24403 match_start: Some(start),
24404 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24405 }
24406 }));
24407 }
24408
24409 Ok(CompletionResponse {
24410 completions,
24411 display_options: CompletionDisplayOptions::default(),
24412 is_incomplete,
24413 })
24414 })
24415}
24416
24417impl CompletionProvider for Entity<Project> {
24418 fn completions(
24419 &self,
24420 _excerpt_id: ExcerptId,
24421 buffer: &Entity<Buffer>,
24422 buffer_position: text::Anchor,
24423 options: CompletionContext,
24424 _window: &mut Window,
24425 cx: &mut Context<Editor>,
24426 ) -> Task<Result<Vec<CompletionResponse>>> {
24427 self.update(cx, |project, cx| {
24428 let task = project.completions(buffer, buffer_position, options, cx);
24429 cx.background_spawn(task)
24430 })
24431 }
24432
24433 fn resolve_completions(
24434 &self,
24435 buffer: Entity<Buffer>,
24436 completion_indices: Vec<usize>,
24437 completions: Rc<RefCell<Box<[Completion]>>>,
24438 cx: &mut Context<Editor>,
24439 ) -> Task<Result<bool>> {
24440 self.update(cx, |project, cx| {
24441 project.lsp_store().update(cx, |lsp_store, cx| {
24442 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24443 })
24444 })
24445 }
24446
24447 fn apply_additional_edits_for_completion(
24448 &self,
24449 buffer: Entity<Buffer>,
24450 completions: Rc<RefCell<Box<[Completion]>>>,
24451 completion_index: usize,
24452 push_to_history: bool,
24453 cx: &mut Context<Editor>,
24454 ) -> Task<Result<Option<language::Transaction>>> {
24455 self.update(cx, |project, cx| {
24456 project.lsp_store().update(cx, |lsp_store, cx| {
24457 lsp_store.apply_additional_edits_for_completion(
24458 buffer,
24459 completions,
24460 completion_index,
24461 push_to_history,
24462 cx,
24463 )
24464 })
24465 })
24466 }
24467
24468 fn is_completion_trigger(
24469 &self,
24470 buffer: &Entity<Buffer>,
24471 position: language::Anchor,
24472 text: &str,
24473 trigger_in_words: bool,
24474 cx: &mut Context<Editor>,
24475 ) -> bool {
24476 let mut chars = text.chars();
24477 let char = if let Some(char) = chars.next() {
24478 char
24479 } else {
24480 return false;
24481 };
24482 if chars.next().is_some() {
24483 return false;
24484 }
24485
24486 let buffer = buffer.read(cx);
24487 let snapshot = buffer.snapshot();
24488 let classifier = snapshot
24489 .char_classifier_at(position)
24490 .scope_context(Some(CharScopeContext::Completion));
24491 if trigger_in_words && classifier.is_word(char) {
24492 return true;
24493 }
24494
24495 buffer.completion_triggers().contains(text)
24496 }
24497
24498 fn show_snippets(&self) -> bool {
24499 true
24500 }
24501}
24502
24503impl SemanticsProvider for Entity<Project> {
24504 fn hover(
24505 &self,
24506 buffer: &Entity<Buffer>,
24507 position: text::Anchor,
24508 cx: &mut App,
24509 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24510 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24511 }
24512
24513 fn document_highlights(
24514 &self,
24515 buffer: &Entity<Buffer>,
24516 position: text::Anchor,
24517 cx: &mut App,
24518 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24519 Some(self.update(cx, |project, cx| {
24520 project.document_highlights(buffer, position, cx)
24521 }))
24522 }
24523
24524 fn definitions(
24525 &self,
24526 buffer: &Entity<Buffer>,
24527 position: text::Anchor,
24528 kind: GotoDefinitionKind,
24529 cx: &mut App,
24530 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24531 Some(self.update(cx, |project, cx| match kind {
24532 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24533 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24534 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24535 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24536 }))
24537 }
24538
24539 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24540 self.update(cx, |project, cx| {
24541 if project
24542 .active_debug_session(cx)
24543 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24544 {
24545 return true;
24546 }
24547
24548 buffer.update(cx, |buffer, cx| {
24549 project.any_language_server_supports_inlay_hints(buffer, cx)
24550 })
24551 })
24552 }
24553
24554 fn inline_values(
24555 &self,
24556 buffer_handle: Entity<Buffer>,
24557 range: Range<text::Anchor>,
24558 cx: &mut App,
24559 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24560 self.update(cx, |project, cx| {
24561 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24562
24563 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24564 })
24565 }
24566
24567 fn applicable_inlay_chunks(
24568 &self,
24569 buffer: &Entity<Buffer>,
24570 ranges: &[Range<text::Anchor>],
24571 cx: &mut App,
24572 ) -> Vec<Range<BufferRow>> {
24573 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24574 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24575 })
24576 }
24577
24578 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24579 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24580 lsp_store.invalidate_inlay_hints(for_buffers)
24581 });
24582 }
24583
24584 fn inlay_hints(
24585 &self,
24586 invalidate: InvalidationStrategy,
24587 buffer: Entity<Buffer>,
24588 ranges: Vec<Range<text::Anchor>>,
24589 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24590 cx: &mut App,
24591 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24592 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24593 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24594 }))
24595 }
24596
24597 fn range_for_rename(
24598 &self,
24599 buffer: &Entity<Buffer>,
24600 position: text::Anchor,
24601 cx: &mut App,
24602 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24603 Some(self.update(cx, |project, cx| {
24604 let buffer = buffer.clone();
24605 let task = project.prepare_rename(buffer.clone(), position, cx);
24606 cx.spawn(async move |_, cx| {
24607 Ok(match task.await? {
24608 PrepareRenameResponse::Success(range) => Some(range),
24609 PrepareRenameResponse::InvalidPosition => None,
24610 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24611 // Fallback on using TreeSitter info to determine identifier range
24612 buffer.read_with(cx, |buffer, _| {
24613 let snapshot = buffer.snapshot();
24614 let (range, kind) = snapshot.surrounding_word(position, None);
24615 if kind != Some(CharKind::Word) {
24616 return None;
24617 }
24618 Some(
24619 snapshot.anchor_before(range.start)
24620 ..snapshot.anchor_after(range.end),
24621 )
24622 })?
24623 }
24624 })
24625 })
24626 }))
24627 }
24628
24629 fn perform_rename(
24630 &self,
24631 buffer: &Entity<Buffer>,
24632 position: text::Anchor,
24633 new_name: String,
24634 cx: &mut App,
24635 ) -> Option<Task<Result<ProjectTransaction>>> {
24636 Some(self.update(cx, |project, cx| {
24637 project.perform_rename(buffer.clone(), position, new_name, cx)
24638 }))
24639 }
24640}
24641
24642fn consume_contiguous_rows(
24643 contiguous_row_selections: &mut Vec<Selection<Point>>,
24644 selection: &Selection<Point>,
24645 display_map: &DisplaySnapshot,
24646 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24647) -> (MultiBufferRow, MultiBufferRow) {
24648 contiguous_row_selections.push(selection.clone());
24649 let start_row = starting_row(selection, display_map);
24650 let mut end_row = ending_row(selection, display_map);
24651
24652 while let Some(next_selection) = selections.peek() {
24653 if next_selection.start.row <= end_row.0 {
24654 end_row = ending_row(next_selection, display_map);
24655 contiguous_row_selections.push(selections.next().unwrap().clone());
24656 } else {
24657 break;
24658 }
24659 }
24660 (start_row, end_row)
24661}
24662
24663fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24664 if selection.start.column > 0 {
24665 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24666 } else {
24667 MultiBufferRow(selection.start.row)
24668 }
24669}
24670
24671fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24672 if next_selection.end.column > 0 || next_selection.is_empty() {
24673 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24674 } else {
24675 MultiBufferRow(next_selection.end.row)
24676 }
24677}
24678
24679impl EditorSnapshot {
24680 pub fn remote_selections_in_range<'a>(
24681 &'a self,
24682 range: &'a Range<Anchor>,
24683 collaboration_hub: &dyn CollaborationHub,
24684 cx: &'a App,
24685 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24686 let participant_names = collaboration_hub.user_names(cx);
24687 let participant_indices = collaboration_hub.user_participant_indices(cx);
24688 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24689 let collaborators_by_replica_id = collaborators_by_peer_id
24690 .values()
24691 .map(|collaborator| (collaborator.replica_id, collaborator))
24692 .collect::<HashMap<_, _>>();
24693 self.buffer_snapshot()
24694 .selections_in_range(range, false)
24695 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24696 if replica_id == ReplicaId::AGENT {
24697 Some(RemoteSelection {
24698 replica_id,
24699 selection,
24700 cursor_shape,
24701 line_mode,
24702 collaborator_id: CollaboratorId::Agent,
24703 user_name: Some("Agent".into()),
24704 color: cx.theme().players().agent(),
24705 })
24706 } else {
24707 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24708 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24709 let user_name = participant_names.get(&collaborator.user_id).cloned();
24710 Some(RemoteSelection {
24711 replica_id,
24712 selection,
24713 cursor_shape,
24714 line_mode,
24715 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24716 user_name,
24717 color: if let Some(index) = participant_index {
24718 cx.theme().players().color_for_participant(index.0)
24719 } else {
24720 cx.theme().players().absent()
24721 },
24722 })
24723 }
24724 })
24725 }
24726
24727 pub fn hunks_for_ranges(
24728 &self,
24729 ranges: impl IntoIterator<Item = Range<Point>>,
24730 ) -> Vec<MultiBufferDiffHunk> {
24731 let mut hunks = Vec::new();
24732 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24733 HashMap::default();
24734 for query_range in ranges {
24735 let query_rows =
24736 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24737 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24738 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24739 ) {
24740 // Include deleted hunks that are adjacent to the query range, because
24741 // otherwise they would be missed.
24742 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24743 if hunk.status().is_deleted() {
24744 intersects_range |= hunk.row_range.start == query_rows.end;
24745 intersects_range |= hunk.row_range.end == query_rows.start;
24746 }
24747 if intersects_range {
24748 if !processed_buffer_rows
24749 .entry(hunk.buffer_id)
24750 .or_default()
24751 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24752 {
24753 continue;
24754 }
24755 hunks.push(hunk);
24756 }
24757 }
24758 }
24759
24760 hunks
24761 }
24762
24763 fn display_diff_hunks_for_rows<'a>(
24764 &'a self,
24765 display_rows: Range<DisplayRow>,
24766 folded_buffers: &'a HashSet<BufferId>,
24767 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24768 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24769 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24770
24771 self.buffer_snapshot()
24772 .diff_hunks_in_range(buffer_start..buffer_end)
24773 .filter_map(|hunk| {
24774 if folded_buffers.contains(&hunk.buffer_id) {
24775 return None;
24776 }
24777
24778 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24779 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24780
24781 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24782 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24783
24784 let display_hunk = if hunk_display_start.column() != 0 {
24785 DisplayDiffHunk::Folded {
24786 display_row: hunk_display_start.row(),
24787 }
24788 } else {
24789 let mut end_row = hunk_display_end.row();
24790 if hunk_display_end.column() > 0 {
24791 end_row.0 += 1;
24792 }
24793 let is_created_file = hunk.is_created_file();
24794
24795 DisplayDiffHunk::Unfolded {
24796 status: hunk.status(),
24797 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24798 ..hunk.diff_base_byte_range.end.0,
24799 word_diffs: hunk.word_diffs,
24800 display_row_range: hunk_display_start.row()..end_row,
24801 multi_buffer_range: Anchor::range_in_buffer(
24802 hunk.excerpt_id,
24803 hunk.buffer_range,
24804 ),
24805 is_created_file,
24806 }
24807 };
24808
24809 Some(display_hunk)
24810 })
24811 }
24812
24813 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24814 self.display_snapshot
24815 .buffer_snapshot()
24816 .language_at(position)
24817 }
24818
24819 pub fn is_focused(&self) -> bool {
24820 self.is_focused
24821 }
24822
24823 pub fn placeholder_text(&self) -> Option<String> {
24824 self.placeholder_display_snapshot
24825 .as_ref()
24826 .map(|display_map| display_map.text())
24827 }
24828
24829 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24830 self.scroll_anchor.scroll_position(&self.display_snapshot)
24831 }
24832
24833 pub fn gutter_dimensions(
24834 &self,
24835 font_id: FontId,
24836 font_size: Pixels,
24837 style: &EditorStyle,
24838 window: &mut Window,
24839 cx: &App,
24840 ) -> GutterDimensions {
24841 if self.show_gutter
24842 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24843 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24844 {
24845 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24846 matches!(
24847 ProjectSettings::get_global(cx).git.git_gutter,
24848 GitGutterSetting::TrackedFiles
24849 )
24850 });
24851 let gutter_settings = EditorSettings::get_global(cx).gutter;
24852 let show_line_numbers = self
24853 .show_line_numbers
24854 .unwrap_or(gutter_settings.line_numbers);
24855 let line_gutter_width = if show_line_numbers {
24856 // Avoid flicker-like gutter resizes when the line number gains another digit by
24857 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24858 let min_width_for_number_on_gutter =
24859 ch_advance * gutter_settings.min_line_number_digits as f32;
24860 self.max_line_number_width(style, window)
24861 .max(min_width_for_number_on_gutter)
24862 } else {
24863 0.0.into()
24864 };
24865
24866 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24867 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24868
24869 let git_blame_entries_width =
24870 self.git_blame_gutter_max_author_length
24871 .map(|max_author_length| {
24872 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24873 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24874
24875 /// The number of characters to dedicate to gaps and margins.
24876 const SPACING_WIDTH: usize = 4;
24877
24878 let max_char_count = max_author_length.min(renderer.max_author_length())
24879 + ::git::SHORT_SHA_LENGTH
24880 + MAX_RELATIVE_TIMESTAMP.len()
24881 + SPACING_WIDTH;
24882
24883 ch_advance * max_char_count
24884 });
24885
24886 let is_singleton = self.buffer_snapshot().is_singleton();
24887
24888 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24889 left_padding += if !is_singleton {
24890 ch_width * 4.0
24891 } else if show_runnables || show_breakpoints {
24892 ch_width * 3.0
24893 } else if show_git_gutter && show_line_numbers {
24894 ch_width * 2.0
24895 } else if show_git_gutter || show_line_numbers {
24896 ch_width
24897 } else {
24898 px(0.)
24899 };
24900
24901 let shows_folds = is_singleton && gutter_settings.folds;
24902
24903 let right_padding = if shows_folds && show_line_numbers {
24904 ch_width * 4.0
24905 } else if shows_folds || (!is_singleton && show_line_numbers) {
24906 ch_width * 3.0
24907 } else if show_line_numbers {
24908 ch_width
24909 } else {
24910 px(0.)
24911 };
24912
24913 GutterDimensions {
24914 left_padding,
24915 right_padding,
24916 width: line_gutter_width + left_padding + right_padding,
24917 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24918 git_blame_entries_width,
24919 }
24920 } else if self.offset_content {
24921 GutterDimensions::default_with_margin(font_id, font_size, cx)
24922 } else {
24923 GutterDimensions::default()
24924 }
24925 }
24926
24927 pub fn render_crease_toggle(
24928 &self,
24929 buffer_row: MultiBufferRow,
24930 row_contains_cursor: bool,
24931 editor: Entity<Editor>,
24932 window: &mut Window,
24933 cx: &mut App,
24934 ) -> Option<AnyElement> {
24935 let folded = self.is_line_folded(buffer_row);
24936 let mut is_foldable = false;
24937
24938 if let Some(crease) = self
24939 .crease_snapshot
24940 .query_row(buffer_row, self.buffer_snapshot())
24941 {
24942 is_foldable = true;
24943 match crease {
24944 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24945 if let Some(render_toggle) = render_toggle {
24946 let toggle_callback =
24947 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24948 if folded {
24949 editor.update(cx, |editor, cx| {
24950 editor.fold_at(buffer_row, window, cx)
24951 });
24952 } else {
24953 editor.update(cx, |editor, cx| {
24954 editor.unfold_at(buffer_row, window, cx)
24955 });
24956 }
24957 });
24958 return Some((render_toggle)(
24959 buffer_row,
24960 folded,
24961 toggle_callback,
24962 window,
24963 cx,
24964 ));
24965 }
24966 }
24967 }
24968 }
24969
24970 is_foldable |= self.starts_indent(buffer_row);
24971
24972 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24973 Some(
24974 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24975 .toggle_state(folded)
24976 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24977 if folded {
24978 this.unfold_at(buffer_row, window, cx);
24979 } else {
24980 this.fold_at(buffer_row, window, cx);
24981 }
24982 }))
24983 .into_any_element(),
24984 )
24985 } else {
24986 None
24987 }
24988 }
24989
24990 pub fn render_crease_trailer(
24991 &self,
24992 buffer_row: MultiBufferRow,
24993 window: &mut Window,
24994 cx: &mut App,
24995 ) -> Option<AnyElement> {
24996 let folded = self.is_line_folded(buffer_row);
24997 if let Crease::Inline { render_trailer, .. } = self
24998 .crease_snapshot
24999 .query_row(buffer_row, self.buffer_snapshot())?
25000 {
25001 let render_trailer = render_trailer.as_ref()?;
25002 Some(render_trailer(buffer_row, folded, window, cx))
25003 } else {
25004 None
25005 }
25006 }
25007
25008 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25009 let digit_count = self.widest_line_number().ilog10() + 1;
25010 column_pixels(style, digit_count as usize, window)
25011 }
25012}
25013
25014pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25015 let font_size = style.text.font_size.to_pixels(window.rem_size());
25016 let layout = window.text_system().shape_line(
25017 SharedString::from(" ".repeat(column)),
25018 font_size,
25019 &[TextRun {
25020 len: column,
25021 font: style.text.font(),
25022 color: Hsla::default(),
25023 ..Default::default()
25024 }],
25025 None,
25026 );
25027
25028 layout.width
25029}
25030
25031impl Deref for EditorSnapshot {
25032 type Target = DisplaySnapshot;
25033
25034 fn deref(&self) -> &Self::Target {
25035 &self.display_snapshot
25036 }
25037}
25038
25039#[derive(Clone, Debug, PartialEq, Eq)]
25040pub enum EditorEvent {
25041 InputIgnored {
25042 text: Arc<str>,
25043 },
25044 InputHandled {
25045 utf16_range_to_replace: Option<Range<isize>>,
25046 text: Arc<str>,
25047 },
25048 ExcerptsAdded {
25049 buffer: Entity<Buffer>,
25050 predecessor: ExcerptId,
25051 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25052 },
25053 ExcerptsRemoved {
25054 ids: Vec<ExcerptId>,
25055 removed_buffer_ids: Vec<BufferId>,
25056 },
25057 BufferFoldToggled {
25058 ids: Vec<ExcerptId>,
25059 folded: bool,
25060 },
25061 ExcerptsEdited {
25062 ids: Vec<ExcerptId>,
25063 },
25064 ExcerptsExpanded {
25065 ids: Vec<ExcerptId>,
25066 },
25067 BufferEdited,
25068 Edited {
25069 transaction_id: clock::Lamport,
25070 },
25071 Reparsed(BufferId),
25072 Focused,
25073 FocusedIn,
25074 Blurred,
25075 DirtyChanged,
25076 Saved,
25077 TitleChanged,
25078 SelectionsChanged {
25079 local: bool,
25080 },
25081 ScrollPositionChanged {
25082 local: bool,
25083 autoscroll: bool,
25084 },
25085 TransactionUndone {
25086 transaction_id: clock::Lamport,
25087 },
25088 TransactionBegun {
25089 transaction_id: clock::Lamport,
25090 },
25091 CursorShapeChanged,
25092 BreadcrumbsChanged,
25093 PushedToNavHistory {
25094 anchor: Anchor,
25095 is_deactivate: bool,
25096 },
25097}
25098
25099impl EventEmitter<EditorEvent> for Editor {}
25100
25101impl Focusable for Editor {
25102 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25103 self.focus_handle.clone()
25104 }
25105}
25106
25107impl Render for Editor {
25108 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25109 EditorElement::new(&cx.entity(), self.create_style(cx))
25110 }
25111}
25112
25113impl EntityInputHandler for Editor {
25114 fn text_for_range(
25115 &mut self,
25116 range_utf16: Range<usize>,
25117 adjusted_range: &mut Option<Range<usize>>,
25118 _: &mut Window,
25119 cx: &mut Context<Self>,
25120 ) -> Option<String> {
25121 let snapshot = self.buffer.read(cx).read(cx);
25122 let start = snapshot.clip_offset_utf16(
25123 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25124 Bias::Left,
25125 );
25126 let end = snapshot.clip_offset_utf16(
25127 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25128 Bias::Right,
25129 );
25130 if (start.0.0..end.0.0) != range_utf16 {
25131 adjusted_range.replace(start.0.0..end.0.0);
25132 }
25133 Some(snapshot.text_for_range(start..end).collect())
25134 }
25135
25136 fn selected_text_range(
25137 &mut self,
25138 ignore_disabled_input: bool,
25139 _: &mut Window,
25140 cx: &mut Context<Self>,
25141 ) -> Option<UTF16Selection> {
25142 // Prevent the IME menu from appearing when holding down an alphabetic key
25143 // while input is disabled.
25144 if !ignore_disabled_input && !self.input_enabled {
25145 return None;
25146 }
25147
25148 let selection = self
25149 .selections
25150 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25151 let range = selection.range();
25152
25153 Some(UTF16Selection {
25154 range: range.start.0.0..range.end.0.0,
25155 reversed: selection.reversed,
25156 })
25157 }
25158
25159 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25160 let snapshot = self.buffer.read(cx).read(cx);
25161 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25162 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25163 }
25164
25165 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25166 self.clear_highlights::<InputComposition>(cx);
25167 self.ime_transaction.take();
25168 }
25169
25170 fn replace_text_in_range(
25171 &mut self,
25172 range_utf16: Option<Range<usize>>,
25173 text: &str,
25174 window: &mut Window,
25175 cx: &mut Context<Self>,
25176 ) {
25177 if !self.input_enabled {
25178 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25179 return;
25180 }
25181
25182 self.transact(window, cx, |this, window, cx| {
25183 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25184 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25185 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25186 Some(this.selection_replacement_ranges(range_utf16, cx))
25187 } else {
25188 this.marked_text_ranges(cx)
25189 };
25190
25191 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25192 let newest_selection_id = this.selections.newest_anchor().id;
25193 this.selections
25194 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25195 .iter()
25196 .zip(ranges_to_replace.iter())
25197 .find_map(|(selection, range)| {
25198 if selection.id == newest_selection_id {
25199 Some(
25200 (range.start.0.0 as isize - selection.head().0.0 as isize)
25201 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25202 )
25203 } else {
25204 None
25205 }
25206 })
25207 });
25208
25209 cx.emit(EditorEvent::InputHandled {
25210 utf16_range_to_replace: range_to_replace,
25211 text: text.into(),
25212 });
25213
25214 if let Some(new_selected_ranges) = new_selected_ranges {
25215 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25216 selections.select_ranges(new_selected_ranges)
25217 });
25218 this.backspace(&Default::default(), window, cx);
25219 }
25220
25221 this.handle_input(text, window, cx);
25222 });
25223
25224 if let Some(transaction) = self.ime_transaction {
25225 self.buffer.update(cx, |buffer, cx| {
25226 buffer.group_until_transaction(transaction, cx);
25227 });
25228 }
25229
25230 self.unmark_text(window, cx);
25231 }
25232
25233 fn replace_and_mark_text_in_range(
25234 &mut self,
25235 range_utf16: Option<Range<usize>>,
25236 text: &str,
25237 new_selected_range_utf16: Option<Range<usize>>,
25238 window: &mut Window,
25239 cx: &mut Context<Self>,
25240 ) {
25241 if !self.input_enabled {
25242 return;
25243 }
25244
25245 let transaction = self.transact(window, cx, |this, window, cx| {
25246 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25247 let snapshot = this.buffer.read(cx).read(cx);
25248 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25249 for marked_range in &mut marked_ranges {
25250 marked_range.end = marked_range.start + relative_range_utf16.end;
25251 marked_range.start += relative_range_utf16.start;
25252 marked_range.start =
25253 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25254 marked_range.end =
25255 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25256 }
25257 }
25258 Some(marked_ranges)
25259 } else if let Some(range_utf16) = range_utf16 {
25260 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25261 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25262 Some(this.selection_replacement_ranges(range_utf16, cx))
25263 } else {
25264 None
25265 };
25266
25267 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25268 let newest_selection_id = this.selections.newest_anchor().id;
25269 this.selections
25270 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25271 .iter()
25272 .zip(ranges_to_replace.iter())
25273 .find_map(|(selection, range)| {
25274 if selection.id == newest_selection_id {
25275 Some(
25276 (range.start.0.0 as isize - selection.head().0.0 as isize)
25277 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25278 )
25279 } else {
25280 None
25281 }
25282 })
25283 });
25284
25285 cx.emit(EditorEvent::InputHandled {
25286 utf16_range_to_replace: range_to_replace,
25287 text: text.into(),
25288 });
25289
25290 if let Some(ranges) = ranges_to_replace {
25291 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25292 s.select_ranges(ranges)
25293 });
25294 }
25295
25296 let marked_ranges = {
25297 let snapshot = this.buffer.read(cx).read(cx);
25298 this.selections
25299 .disjoint_anchors_arc()
25300 .iter()
25301 .map(|selection| {
25302 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25303 })
25304 .collect::<Vec<_>>()
25305 };
25306
25307 if text.is_empty() {
25308 this.unmark_text(window, cx);
25309 } else {
25310 this.highlight_text::<InputComposition>(
25311 marked_ranges.clone(),
25312 HighlightStyle {
25313 underline: Some(UnderlineStyle {
25314 thickness: px(1.),
25315 color: None,
25316 wavy: false,
25317 }),
25318 ..Default::default()
25319 },
25320 cx,
25321 );
25322 }
25323
25324 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25325 let use_autoclose = this.use_autoclose;
25326 let use_auto_surround = this.use_auto_surround;
25327 this.set_use_autoclose(false);
25328 this.set_use_auto_surround(false);
25329 this.handle_input(text, window, cx);
25330 this.set_use_autoclose(use_autoclose);
25331 this.set_use_auto_surround(use_auto_surround);
25332
25333 if let Some(new_selected_range) = new_selected_range_utf16 {
25334 let snapshot = this.buffer.read(cx).read(cx);
25335 let new_selected_ranges = marked_ranges
25336 .into_iter()
25337 .map(|marked_range| {
25338 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25339 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25340 insertion_start.0 + new_selected_range.start,
25341 ));
25342 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25343 insertion_start.0 + new_selected_range.end,
25344 ));
25345 snapshot.clip_offset_utf16(new_start, Bias::Left)
25346 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25347 })
25348 .collect::<Vec<_>>();
25349
25350 drop(snapshot);
25351 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25352 selections.select_ranges(new_selected_ranges)
25353 });
25354 }
25355 });
25356
25357 self.ime_transaction = self.ime_transaction.or(transaction);
25358 if let Some(transaction) = self.ime_transaction {
25359 self.buffer.update(cx, |buffer, cx| {
25360 buffer.group_until_transaction(transaction, cx);
25361 });
25362 }
25363
25364 if self.text_highlights::<InputComposition>(cx).is_none() {
25365 self.ime_transaction.take();
25366 }
25367 }
25368
25369 fn bounds_for_range(
25370 &mut self,
25371 range_utf16: Range<usize>,
25372 element_bounds: gpui::Bounds<Pixels>,
25373 window: &mut Window,
25374 cx: &mut Context<Self>,
25375 ) -> Option<gpui::Bounds<Pixels>> {
25376 let text_layout_details = self.text_layout_details(window);
25377 let CharacterDimensions {
25378 em_width,
25379 em_advance,
25380 line_height,
25381 } = self.character_dimensions(window);
25382
25383 let snapshot = self.snapshot(window, cx);
25384 let scroll_position = snapshot.scroll_position();
25385 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25386
25387 let start =
25388 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25389 let x = Pixels::from(
25390 ScrollOffset::from(
25391 snapshot.x_for_display_point(start, &text_layout_details)
25392 + self.gutter_dimensions.full_width(),
25393 ) - scroll_left,
25394 );
25395 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25396
25397 Some(Bounds {
25398 origin: element_bounds.origin + point(x, y),
25399 size: size(em_width, line_height),
25400 })
25401 }
25402
25403 fn character_index_for_point(
25404 &mut self,
25405 point: gpui::Point<Pixels>,
25406 _window: &mut Window,
25407 _cx: &mut Context<Self>,
25408 ) -> Option<usize> {
25409 let position_map = self.last_position_map.as_ref()?;
25410 if !position_map.text_hitbox.contains(&point) {
25411 return None;
25412 }
25413 let display_point = position_map.point_for_position(point).previous_valid;
25414 let anchor = position_map
25415 .snapshot
25416 .display_point_to_anchor(display_point, Bias::Left);
25417 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25418 Some(utf16_offset.0.0)
25419 }
25420
25421 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25422 self.input_enabled
25423 }
25424}
25425
25426trait SelectionExt {
25427 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25428 fn spanned_rows(
25429 &self,
25430 include_end_if_at_line_start: bool,
25431 map: &DisplaySnapshot,
25432 ) -> Range<MultiBufferRow>;
25433}
25434
25435impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25436 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25437 let start = self
25438 .start
25439 .to_point(map.buffer_snapshot())
25440 .to_display_point(map);
25441 let end = self
25442 .end
25443 .to_point(map.buffer_snapshot())
25444 .to_display_point(map);
25445 if self.reversed {
25446 end..start
25447 } else {
25448 start..end
25449 }
25450 }
25451
25452 fn spanned_rows(
25453 &self,
25454 include_end_if_at_line_start: bool,
25455 map: &DisplaySnapshot,
25456 ) -> Range<MultiBufferRow> {
25457 let start = self.start.to_point(map.buffer_snapshot());
25458 let mut end = self.end.to_point(map.buffer_snapshot());
25459 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25460 end.row -= 1;
25461 }
25462
25463 let buffer_start = map.prev_line_boundary(start).0;
25464 let buffer_end = map.next_line_boundary(end).0;
25465 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25466 }
25467}
25468
25469impl<T: InvalidationRegion> InvalidationStack<T> {
25470 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25471 where
25472 S: Clone + ToOffset,
25473 {
25474 while let Some(region) = self.last() {
25475 let all_selections_inside_invalidation_ranges =
25476 if selections.len() == region.ranges().len() {
25477 selections
25478 .iter()
25479 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25480 .all(|(selection, invalidation_range)| {
25481 let head = selection.head().to_offset(buffer);
25482 invalidation_range.start <= head && invalidation_range.end >= head
25483 })
25484 } else {
25485 false
25486 };
25487
25488 if all_selections_inside_invalidation_ranges {
25489 break;
25490 } else {
25491 self.pop();
25492 }
25493 }
25494 }
25495}
25496
25497impl<T> Default for InvalidationStack<T> {
25498 fn default() -> Self {
25499 Self(Default::default())
25500 }
25501}
25502
25503impl<T> Deref for InvalidationStack<T> {
25504 type Target = Vec<T>;
25505
25506 fn deref(&self) -> &Self::Target {
25507 &self.0
25508 }
25509}
25510
25511impl<T> DerefMut for InvalidationStack<T> {
25512 fn deref_mut(&mut self) -> &mut Self::Target {
25513 &mut self.0
25514 }
25515}
25516
25517impl InvalidationRegion for SnippetState {
25518 fn ranges(&self) -> &[Range<Anchor>] {
25519 &self.ranges[self.active_index]
25520 }
25521}
25522
25523fn edit_prediction_edit_text(
25524 current_snapshot: &BufferSnapshot,
25525 edits: &[(Range<Anchor>, impl AsRef<str>)],
25526 edit_preview: &EditPreview,
25527 include_deletions: bool,
25528 cx: &App,
25529) -> HighlightedText {
25530 let edits = edits
25531 .iter()
25532 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25533 .collect::<Vec<_>>();
25534
25535 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25536}
25537
25538fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25539 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25540 // Just show the raw edit text with basic styling
25541 let mut text = String::new();
25542 let mut highlights = Vec::new();
25543
25544 let insertion_highlight_style = HighlightStyle {
25545 color: Some(cx.theme().colors().text),
25546 ..Default::default()
25547 };
25548
25549 for (_, edit_text) in edits {
25550 let start_offset = text.len();
25551 text.push_str(edit_text);
25552 let end_offset = text.len();
25553
25554 if start_offset < end_offset {
25555 highlights.push((start_offset..end_offset, insertion_highlight_style));
25556 }
25557 }
25558
25559 HighlightedText {
25560 text: text.into(),
25561 highlights,
25562 }
25563}
25564
25565pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25566 match severity {
25567 lsp::DiagnosticSeverity::ERROR => colors.error,
25568 lsp::DiagnosticSeverity::WARNING => colors.warning,
25569 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25570 lsp::DiagnosticSeverity::HINT => colors.info,
25571 _ => colors.ignored,
25572 }
25573}
25574
25575pub fn styled_runs_for_code_label<'a>(
25576 label: &'a CodeLabel,
25577 syntax_theme: &'a theme::SyntaxTheme,
25578 local_player: &'a theme::PlayerColor,
25579) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25580 let fade_out = HighlightStyle {
25581 fade_out: Some(0.35),
25582 ..Default::default()
25583 };
25584
25585 let mut prev_end = label.filter_range.end;
25586 label
25587 .runs
25588 .iter()
25589 .enumerate()
25590 .flat_map(move |(ix, (range, highlight_id))| {
25591 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25592 HighlightStyle {
25593 color: Some(local_player.cursor),
25594 ..Default::default()
25595 }
25596 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25597 HighlightStyle {
25598 background_color: Some(local_player.selection),
25599 ..Default::default()
25600 }
25601 } else if let Some(style) = highlight_id.style(syntax_theme) {
25602 style
25603 } else {
25604 return Default::default();
25605 };
25606 let muted_style = style.highlight(fade_out);
25607
25608 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25609 if range.start >= label.filter_range.end {
25610 if range.start > prev_end {
25611 runs.push((prev_end..range.start, fade_out));
25612 }
25613 runs.push((range.clone(), muted_style));
25614 } else if range.end <= label.filter_range.end {
25615 runs.push((range.clone(), style));
25616 } else {
25617 runs.push((range.start..label.filter_range.end, style));
25618 runs.push((label.filter_range.end..range.end, muted_style));
25619 }
25620 prev_end = cmp::max(prev_end, range.end);
25621
25622 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25623 runs.push((prev_end..label.text.len(), fade_out));
25624 }
25625
25626 runs
25627 })
25628}
25629
25630pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25631 let mut prev_index = 0;
25632 let mut prev_codepoint: Option<char> = None;
25633 text.char_indices()
25634 .chain([(text.len(), '\0')])
25635 .filter_map(move |(index, codepoint)| {
25636 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25637 let is_boundary = index == text.len()
25638 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25639 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25640 if is_boundary {
25641 let chunk = &text[prev_index..index];
25642 prev_index = index;
25643 Some(chunk)
25644 } else {
25645 None
25646 }
25647 })
25648}
25649
25650/// Given a string of text immediately before the cursor, iterates over possible
25651/// strings a snippet could match to. More precisely: returns an iterator over
25652/// suffixes of `text` created by splitting at word boundaries (before & after
25653/// every non-word character).
25654///
25655/// Shorter suffixes are returned first.
25656pub(crate) fn snippet_candidate_suffixes(
25657 text: &str,
25658 is_word_char: impl Fn(char) -> bool,
25659) -> impl std::iter::Iterator<Item = &str> {
25660 let mut prev_index = text.len();
25661 let mut prev_codepoint = None;
25662 text.char_indices()
25663 .rev()
25664 .chain([(0, '\0')])
25665 .filter_map(move |(index, codepoint)| {
25666 let prev_index = std::mem::replace(&mut prev_index, index);
25667 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25668 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25669 None
25670 } else {
25671 let chunk = &text[prev_index..]; // go to end of string
25672 Some(chunk)
25673 }
25674 })
25675}
25676
25677pub trait RangeToAnchorExt: Sized {
25678 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25679
25680 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25681 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25682 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25683 }
25684}
25685
25686impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25687 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25688 let start_offset = self.start.to_offset(snapshot);
25689 let end_offset = self.end.to_offset(snapshot);
25690 if start_offset == end_offset {
25691 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25692 } else {
25693 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25694 }
25695 }
25696}
25697
25698pub trait RowExt {
25699 fn as_f64(&self) -> f64;
25700
25701 fn next_row(&self) -> Self;
25702
25703 fn previous_row(&self) -> Self;
25704
25705 fn minus(&self, other: Self) -> u32;
25706}
25707
25708impl RowExt for DisplayRow {
25709 fn as_f64(&self) -> f64 {
25710 self.0 as _
25711 }
25712
25713 fn next_row(&self) -> Self {
25714 Self(self.0 + 1)
25715 }
25716
25717 fn previous_row(&self) -> Self {
25718 Self(self.0.saturating_sub(1))
25719 }
25720
25721 fn minus(&self, other: Self) -> u32 {
25722 self.0 - other.0
25723 }
25724}
25725
25726impl RowExt for MultiBufferRow {
25727 fn as_f64(&self) -> f64 {
25728 self.0 as _
25729 }
25730
25731 fn next_row(&self) -> Self {
25732 Self(self.0 + 1)
25733 }
25734
25735 fn previous_row(&self) -> Self {
25736 Self(self.0.saturating_sub(1))
25737 }
25738
25739 fn minus(&self, other: Self) -> u32 {
25740 self.0 - other.0
25741 }
25742}
25743
25744trait RowRangeExt {
25745 type Row;
25746
25747 fn len(&self) -> usize;
25748
25749 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25750}
25751
25752impl RowRangeExt for Range<MultiBufferRow> {
25753 type Row = MultiBufferRow;
25754
25755 fn len(&self) -> usize {
25756 (self.end.0 - self.start.0) as usize
25757 }
25758
25759 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25760 (self.start.0..self.end.0).map(MultiBufferRow)
25761 }
25762}
25763
25764impl RowRangeExt for Range<DisplayRow> {
25765 type Row = DisplayRow;
25766
25767 fn len(&self) -> usize {
25768 (self.end.0 - self.start.0) as usize
25769 }
25770
25771 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25772 (self.start.0..self.end.0).map(DisplayRow)
25773 }
25774}
25775
25776/// If select range has more than one line, we
25777/// just point the cursor to range.start.
25778fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25779 if range.start.row == range.end.row {
25780 range
25781 } else {
25782 range.start..range.start
25783 }
25784}
25785pub struct KillRing(ClipboardItem);
25786impl Global for KillRing {}
25787
25788const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25789
25790enum BreakpointPromptEditAction {
25791 Log,
25792 Condition,
25793 HitCondition,
25794}
25795
25796struct BreakpointPromptEditor {
25797 pub(crate) prompt: Entity<Editor>,
25798 editor: WeakEntity<Editor>,
25799 breakpoint_anchor: Anchor,
25800 breakpoint: Breakpoint,
25801 edit_action: BreakpointPromptEditAction,
25802 block_ids: HashSet<CustomBlockId>,
25803 editor_margins: Arc<Mutex<EditorMargins>>,
25804 _subscriptions: Vec<Subscription>,
25805}
25806
25807impl BreakpointPromptEditor {
25808 const MAX_LINES: u8 = 4;
25809
25810 fn new(
25811 editor: WeakEntity<Editor>,
25812 breakpoint_anchor: Anchor,
25813 breakpoint: Breakpoint,
25814 edit_action: BreakpointPromptEditAction,
25815 window: &mut Window,
25816 cx: &mut Context<Self>,
25817 ) -> Self {
25818 let base_text = match edit_action {
25819 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25820 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25821 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25822 }
25823 .map(|msg| msg.to_string())
25824 .unwrap_or_default();
25825
25826 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25827 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25828
25829 let prompt = cx.new(|cx| {
25830 let mut prompt = Editor::new(
25831 EditorMode::AutoHeight {
25832 min_lines: 1,
25833 max_lines: Some(Self::MAX_LINES as usize),
25834 },
25835 buffer,
25836 None,
25837 window,
25838 cx,
25839 );
25840 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25841 prompt.set_show_cursor_when_unfocused(false, cx);
25842 prompt.set_placeholder_text(
25843 match edit_action {
25844 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25845 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25846 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25847 },
25848 window,
25849 cx,
25850 );
25851
25852 prompt
25853 });
25854
25855 Self {
25856 prompt,
25857 editor,
25858 breakpoint_anchor,
25859 breakpoint,
25860 edit_action,
25861 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25862 block_ids: Default::default(),
25863 _subscriptions: vec![],
25864 }
25865 }
25866
25867 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25868 self.block_ids.extend(block_ids)
25869 }
25870
25871 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25872 if let Some(editor) = self.editor.upgrade() {
25873 let message = self
25874 .prompt
25875 .read(cx)
25876 .buffer
25877 .read(cx)
25878 .as_singleton()
25879 .expect("A multi buffer in breakpoint prompt isn't possible")
25880 .read(cx)
25881 .as_rope()
25882 .to_string();
25883
25884 editor.update(cx, |editor, cx| {
25885 editor.edit_breakpoint_at_anchor(
25886 self.breakpoint_anchor,
25887 self.breakpoint.clone(),
25888 match self.edit_action {
25889 BreakpointPromptEditAction::Log => {
25890 BreakpointEditAction::EditLogMessage(message.into())
25891 }
25892 BreakpointPromptEditAction::Condition => {
25893 BreakpointEditAction::EditCondition(message.into())
25894 }
25895 BreakpointPromptEditAction::HitCondition => {
25896 BreakpointEditAction::EditHitCondition(message.into())
25897 }
25898 },
25899 cx,
25900 );
25901
25902 editor.remove_blocks(self.block_ids.clone(), None, cx);
25903 cx.focus_self(window);
25904 });
25905 }
25906 }
25907
25908 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25909 self.editor
25910 .update(cx, |editor, cx| {
25911 editor.remove_blocks(self.block_ids.clone(), None, cx);
25912 window.focus(&editor.focus_handle);
25913 })
25914 .log_err();
25915 }
25916
25917 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25918 let settings = ThemeSettings::get_global(cx);
25919 let text_style = TextStyle {
25920 color: if self.prompt.read(cx).read_only(cx) {
25921 cx.theme().colors().text_disabled
25922 } else {
25923 cx.theme().colors().text
25924 },
25925 font_family: settings.buffer_font.family.clone(),
25926 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25927 font_size: settings.buffer_font_size(cx).into(),
25928 font_weight: settings.buffer_font.weight,
25929 line_height: relative(settings.buffer_line_height.value()),
25930 ..Default::default()
25931 };
25932 EditorElement::new(
25933 &self.prompt,
25934 EditorStyle {
25935 background: cx.theme().colors().editor_background,
25936 local_player: cx.theme().players().local(),
25937 text: text_style,
25938 ..Default::default()
25939 },
25940 )
25941 }
25942}
25943
25944impl Render for BreakpointPromptEditor {
25945 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25946 let editor_margins = *self.editor_margins.lock();
25947 let gutter_dimensions = editor_margins.gutter;
25948 h_flex()
25949 .key_context("Editor")
25950 .bg(cx.theme().colors().editor_background)
25951 .border_y_1()
25952 .border_color(cx.theme().status().info_border)
25953 .size_full()
25954 .py(window.line_height() / 2.5)
25955 .on_action(cx.listener(Self::confirm))
25956 .on_action(cx.listener(Self::cancel))
25957 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25958 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25959 }
25960}
25961
25962impl Focusable for BreakpointPromptEditor {
25963 fn focus_handle(&self, cx: &App) -> FocusHandle {
25964 self.prompt.focus_handle(cx)
25965 }
25966}
25967
25968fn all_edits_insertions_or_deletions(
25969 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25970 snapshot: &MultiBufferSnapshot,
25971) -> bool {
25972 let mut all_insertions = true;
25973 let mut all_deletions = true;
25974
25975 for (range, new_text) in edits.iter() {
25976 let range_is_empty = range.to_offset(snapshot).is_empty();
25977 let text_is_empty = new_text.is_empty();
25978
25979 if range_is_empty != text_is_empty {
25980 if range_is_empty {
25981 all_deletions = false;
25982 } else {
25983 all_insertions = false;
25984 }
25985 } else {
25986 return false;
25987 }
25988
25989 if !all_insertions && !all_deletions {
25990 return false;
25991 }
25992 }
25993 all_insertions || all_deletions
25994}
25995
25996struct MissingEditPredictionKeybindingTooltip;
25997
25998impl Render for MissingEditPredictionKeybindingTooltip {
25999 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26000 ui::tooltip_container(cx, |container, cx| {
26001 container
26002 .flex_shrink_0()
26003 .max_w_80()
26004 .min_h(rems_from_px(124.))
26005 .justify_between()
26006 .child(
26007 v_flex()
26008 .flex_1()
26009 .text_ui_sm(cx)
26010 .child(Label::new("Conflict with Accept Keybinding"))
26011 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26012 )
26013 .child(
26014 h_flex()
26015 .pb_1()
26016 .gap_1()
26017 .items_end()
26018 .w_full()
26019 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26020 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26021 }))
26022 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26023 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26024 })),
26025 )
26026 })
26027 }
26028}
26029
26030#[derive(Debug, Clone, Copy, PartialEq)]
26031pub struct LineHighlight {
26032 pub background: Background,
26033 pub border: Option<gpui::Hsla>,
26034 pub include_gutter: bool,
26035 pub type_id: Option<TypeId>,
26036}
26037
26038struct LineManipulationResult {
26039 pub new_text: String,
26040 pub line_count_before: usize,
26041 pub line_count_after: usize,
26042}
26043
26044fn render_diff_hunk_controls(
26045 row: u32,
26046 status: &DiffHunkStatus,
26047 hunk_range: Range<Anchor>,
26048 is_created_file: bool,
26049 line_height: Pixels,
26050 editor: &Entity<Editor>,
26051 _window: &mut Window,
26052 cx: &mut App,
26053) -> AnyElement {
26054 h_flex()
26055 .h(line_height)
26056 .mr_1()
26057 .gap_1()
26058 .px_0p5()
26059 .pb_1()
26060 .border_x_1()
26061 .border_b_1()
26062 .border_color(cx.theme().colors().border_variant)
26063 .rounded_b_lg()
26064 .bg(cx.theme().colors().editor_background)
26065 .gap_1()
26066 .block_mouse_except_scroll()
26067 .shadow_md()
26068 .child(if status.has_secondary_hunk() {
26069 Button::new(("stage", row as u64), "Stage")
26070 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26071 .tooltip({
26072 let focus_handle = editor.focus_handle(cx);
26073 move |_window, cx| {
26074 Tooltip::for_action_in(
26075 "Stage Hunk",
26076 &::git::ToggleStaged,
26077 &focus_handle,
26078 cx,
26079 )
26080 }
26081 })
26082 .on_click({
26083 let editor = editor.clone();
26084 move |_event, _window, cx| {
26085 editor.update(cx, |editor, cx| {
26086 editor.stage_or_unstage_diff_hunks(
26087 true,
26088 vec![hunk_range.start..hunk_range.start],
26089 cx,
26090 );
26091 });
26092 }
26093 })
26094 } else {
26095 Button::new(("unstage", row as u64), "Unstage")
26096 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26097 .tooltip({
26098 let focus_handle = editor.focus_handle(cx);
26099 move |_window, cx| {
26100 Tooltip::for_action_in(
26101 "Unstage Hunk",
26102 &::git::ToggleStaged,
26103 &focus_handle,
26104 cx,
26105 )
26106 }
26107 })
26108 .on_click({
26109 let editor = editor.clone();
26110 move |_event, _window, cx| {
26111 editor.update(cx, |editor, cx| {
26112 editor.stage_or_unstage_diff_hunks(
26113 false,
26114 vec![hunk_range.start..hunk_range.start],
26115 cx,
26116 );
26117 });
26118 }
26119 })
26120 })
26121 .child(
26122 Button::new(("restore", row as u64), "Restore")
26123 .tooltip({
26124 let focus_handle = editor.focus_handle(cx);
26125 move |_window, cx| {
26126 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26127 }
26128 })
26129 .on_click({
26130 let editor = editor.clone();
26131 move |_event, window, cx| {
26132 editor.update(cx, |editor, cx| {
26133 let snapshot = editor.snapshot(window, cx);
26134 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26135 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26136 });
26137 }
26138 })
26139 .disabled(is_created_file),
26140 )
26141 .when(
26142 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26143 |el| {
26144 el.child(
26145 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26146 .shape(IconButtonShape::Square)
26147 .icon_size(IconSize::Small)
26148 // .disabled(!has_multiple_hunks)
26149 .tooltip({
26150 let focus_handle = editor.focus_handle(cx);
26151 move |_window, cx| {
26152 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26153 }
26154 })
26155 .on_click({
26156 let editor = editor.clone();
26157 move |_event, window, cx| {
26158 editor.update(cx, |editor, cx| {
26159 let snapshot = editor.snapshot(window, cx);
26160 let position =
26161 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26162 editor.go_to_hunk_before_or_after_position(
26163 &snapshot,
26164 position,
26165 Direction::Next,
26166 window,
26167 cx,
26168 );
26169 editor.expand_selected_diff_hunks(cx);
26170 });
26171 }
26172 }),
26173 )
26174 .child(
26175 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26176 .shape(IconButtonShape::Square)
26177 .icon_size(IconSize::Small)
26178 // .disabled(!has_multiple_hunks)
26179 .tooltip({
26180 let focus_handle = editor.focus_handle(cx);
26181 move |_window, cx| {
26182 Tooltip::for_action_in(
26183 "Previous Hunk",
26184 &GoToPreviousHunk,
26185 &focus_handle,
26186 cx,
26187 )
26188 }
26189 })
26190 .on_click({
26191 let editor = editor.clone();
26192 move |_event, window, cx| {
26193 editor.update(cx, |editor, cx| {
26194 let snapshot = editor.snapshot(window, cx);
26195 let point =
26196 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26197 editor.go_to_hunk_before_or_after_position(
26198 &snapshot,
26199 point,
26200 Direction::Prev,
26201 window,
26202 cx,
26203 );
26204 editor.expand_selected_diff_hunks(cx);
26205 });
26206 }
26207 }),
26208 )
26209 },
26210 )
26211 .into_any_element()
26212}
26213
26214pub fn multibuffer_context_lines(cx: &App) -> u32 {
26215 EditorSettings::try_get(cx)
26216 .map(|settings| settings.excerpt_context_lines)
26217 .unwrap_or(2)
26218 .min(32)
26219}