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
15538 .select_next_is_case_sensitive
15539 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15540
15541 let mut builder = AhoCorasickBuilder::new();
15542 builder.ascii_case_insensitive(!case_sensitive);
15543 builder.build(patterns)
15544 }
15545
15546 pub fn find_next_match(
15547 &mut self,
15548 _: &FindNextMatch,
15549 window: &mut Window,
15550 cx: &mut Context<Self>,
15551 ) -> Result<()> {
15552 let selections = self.selections.disjoint_anchors_arc();
15553 match selections.first() {
15554 Some(first) if selections.len() >= 2 => {
15555 self.change_selections(Default::default(), window, cx, |s| {
15556 s.select_ranges([first.range()]);
15557 });
15558 }
15559 _ => self.select_next(
15560 &SelectNext {
15561 replace_newest: true,
15562 },
15563 window,
15564 cx,
15565 )?,
15566 }
15567 Ok(())
15568 }
15569
15570 pub fn find_previous_match(
15571 &mut self,
15572 _: &FindPreviousMatch,
15573 window: &mut Window,
15574 cx: &mut Context<Self>,
15575 ) -> Result<()> {
15576 let selections = self.selections.disjoint_anchors_arc();
15577 match selections.last() {
15578 Some(last) if selections.len() >= 2 => {
15579 self.change_selections(Default::default(), window, cx, |s| {
15580 s.select_ranges([last.range()]);
15581 });
15582 }
15583 _ => self.select_previous(
15584 &SelectPrevious {
15585 replace_newest: true,
15586 },
15587 window,
15588 cx,
15589 )?,
15590 }
15591 Ok(())
15592 }
15593
15594 pub fn toggle_comments(
15595 &mut self,
15596 action: &ToggleComments,
15597 window: &mut Window,
15598 cx: &mut Context<Self>,
15599 ) {
15600 if self.read_only(cx) {
15601 return;
15602 }
15603 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15604 let text_layout_details = &self.text_layout_details(window);
15605 self.transact(window, cx, |this, window, cx| {
15606 let mut selections = this
15607 .selections
15608 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15609 let mut edits = Vec::new();
15610 let mut selection_edit_ranges = Vec::new();
15611 let mut last_toggled_row = None;
15612 let snapshot = this.buffer.read(cx).read(cx);
15613 let empty_str: Arc<str> = Arc::default();
15614 let mut suffixes_inserted = Vec::new();
15615 let ignore_indent = action.ignore_indent;
15616
15617 fn comment_prefix_range(
15618 snapshot: &MultiBufferSnapshot,
15619 row: MultiBufferRow,
15620 comment_prefix: &str,
15621 comment_prefix_whitespace: &str,
15622 ignore_indent: bool,
15623 ) -> Range<Point> {
15624 let indent_size = if ignore_indent {
15625 0
15626 } else {
15627 snapshot.indent_size_for_line(row).len
15628 };
15629
15630 let start = Point::new(row.0, indent_size);
15631
15632 let mut line_bytes = snapshot
15633 .bytes_in_range(start..snapshot.max_point())
15634 .flatten()
15635 .copied();
15636
15637 // If this line currently begins with the line comment prefix, then record
15638 // the range containing the prefix.
15639 if line_bytes
15640 .by_ref()
15641 .take(comment_prefix.len())
15642 .eq(comment_prefix.bytes())
15643 {
15644 // Include any whitespace that matches the comment prefix.
15645 let matching_whitespace_len = line_bytes
15646 .zip(comment_prefix_whitespace.bytes())
15647 .take_while(|(a, b)| a == b)
15648 .count() as u32;
15649 let end = Point::new(
15650 start.row,
15651 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15652 );
15653 start..end
15654 } else {
15655 start..start
15656 }
15657 }
15658
15659 fn comment_suffix_range(
15660 snapshot: &MultiBufferSnapshot,
15661 row: MultiBufferRow,
15662 comment_suffix: &str,
15663 comment_suffix_has_leading_space: bool,
15664 ) -> Range<Point> {
15665 let end = Point::new(row.0, snapshot.line_len(row));
15666 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15667
15668 let mut line_end_bytes = snapshot
15669 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15670 .flatten()
15671 .copied();
15672
15673 let leading_space_len = if suffix_start_column > 0
15674 && line_end_bytes.next() == Some(b' ')
15675 && comment_suffix_has_leading_space
15676 {
15677 1
15678 } else {
15679 0
15680 };
15681
15682 // If this line currently begins with the line comment prefix, then record
15683 // the range containing the prefix.
15684 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15685 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15686 start..end
15687 } else {
15688 end..end
15689 }
15690 }
15691
15692 // TODO: Handle selections that cross excerpts
15693 for selection in &mut selections {
15694 let start_column = snapshot
15695 .indent_size_for_line(MultiBufferRow(selection.start.row))
15696 .len;
15697 let language = if let Some(language) =
15698 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15699 {
15700 language
15701 } else {
15702 continue;
15703 };
15704
15705 selection_edit_ranges.clear();
15706
15707 // If multiple selections contain a given row, avoid processing that
15708 // row more than once.
15709 let mut start_row = MultiBufferRow(selection.start.row);
15710 if last_toggled_row == Some(start_row) {
15711 start_row = start_row.next_row();
15712 }
15713 let end_row =
15714 if selection.end.row > selection.start.row && selection.end.column == 0 {
15715 MultiBufferRow(selection.end.row - 1)
15716 } else {
15717 MultiBufferRow(selection.end.row)
15718 };
15719 last_toggled_row = Some(end_row);
15720
15721 if start_row > end_row {
15722 continue;
15723 }
15724
15725 // If the language has line comments, toggle those.
15726 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15727
15728 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15729 if ignore_indent {
15730 full_comment_prefixes = full_comment_prefixes
15731 .into_iter()
15732 .map(|s| Arc::from(s.trim_end()))
15733 .collect();
15734 }
15735
15736 if !full_comment_prefixes.is_empty() {
15737 let first_prefix = full_comment_prefixes
15738 .first()
15739 .expect("prefixes is non-empty");
15740 let prefix_trimmed_lengths = full_comment_prefixes
15741 .iter()
15742 .map(|p| p.trim_end_matches(' ').len())
15743 .collect::<SmallVec<[usize; 4]>>();
15744
15745 let mut all_selection_lines_are_comments = true;
15746
15747 for row in start_row.0..=end_row.0 {
15748 let row = MultiBufferRow(row);
15749 if start_row < end_row && snapshot.is_line_blank(row) {
15750 continue;
15751 }
15752
15753 let prefix_range = full_comment_prefixes
15754 .iter()
15755 .zip(prefix_trimmed_lengths.iter().copied())
15756 .map(|(prefix, trimmed_prefix_len)| {
15757 comment_prefix_range(
15758 snapshot.deref(),
15759 row,
15760 &prefix[..trimmed_prefix_len],
15761 &prefix[trimmed_prefix_len..],
15762 ignore_indent,
15763 )
15764 })
15765 .max_by_key(|range| range.end.column - range.start.column)
15766 .expect("prefixes is non-empty");
15767
15768 if prefix_range.is_empty() {
15769 all_selection_lines_are_comments = false;
15770 }
15771
15772 selection_edit_ranges.push(prefix_range);
15773 }
15774
15775 if all_selection_lines_are_comments {
15776 edits.extend(
15777 selection_edit_ranges
15778 .iter()
15779 .cloned()
15780 .map(|range| (range, empty_str.clone())),
15781 );
15782 } else {
15783 let min_column = selection_edit_ranges
15784 .iter()
15785 .map(|range| range.start.column)
15786 .min()
15787 .unwrap_or(0);
15788 edits.extend(selection_edit_ranges.iter().map(|range| {
15789 let position = Point::new(range.start.row, min_column);
15790 (position..position, first_prefix.clone())
15791 }));
15792 }
15793 } else if let Some(BlockCommentConfig {
15794 start: full_comment_prefix,
15795 end: comment_suffix,
15796 ..
15797 }) = language.block_comment()
15798 {
15799 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15800 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15801 let prefix_range = comment_prefix_range(
15802 snapshot.deref(),
15803 start_row,
15804 comment_prefix,
15805 comment_prefix_whitespace,
15806 ignore_indent,
15807 );
15808 let suffix_range = comment_suffix_range(
15809 snapshot.deref(),
15810 end_row,
15811 comment_suffix.trim_start_matches(' '),
15812 comment_suffix.starts_with(' '),
15813 );
15814
15815 if prefix_range.is_empty() || suffix_range.is_empty() {
15816 edits.push((
15817 prefix_range.start..prefix_range.start,
15818 full_comment_prefix.clone(),
15819 ));
15820 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15821 suffixes_inserted.push((end_row, comment_suffix.len()));
15822 } else {
15823 edits.push((prefix_range, empty_str.clone()));
15824 edits.push((suffix_range, empty_str.clone()));
15825 }
15826 } else {
15827 continue;
15828 }
15829 }
15830
15831 drop(snapshot);
15832 this.buffer.update(cx, |buffer, cx| {
15833 buffer.edit(edits, None, cx);
15834 });
15835
15836 // Adjust selections so that they end before any comment suffixes that
15837 // were inserted.
15838 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15839 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15840 let snapshot = this.buffer.read(cx).read(cx);
15841 for selection in &mut selections {
15842 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15843 match row.cmp(&MultiBufferRow(selection.end.row)) {
15844 Ordering::Less => {
15845 suffixes_inserted.next();
15846 continue;
15847 }
15848 Ordering::Greater => break,
15849 Ordering::Equal => {
15850 if selection.end.column == snapshot.line_len(row) {
15851 if selection.is_empty() {
15852 selection.start.column -= suffix_len as u32;
15853 }
15854 selection.end.column -= suffix_len as u32;
15855 }
15856 break;
15857 }
15858 }
15859 }
15860 }
15861
15862 drop(snapshot);
15863 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15864
15865 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15866 let selections_on_single_row = selections.windows(2).all(|selections| {
15867 selections[0].start.row == selections[1].start.row
15868 && selections[0].end.row == selections[1].end.row
15869 && selections[0].start.row == selections[0].end.row
15870 });
15871 let selections_selecting = selections
15872 .iter()
15873 .any(|selection| selection.start != selection.end);
15874 let advance_downwards = action.advance_downwards
15875 && selections_on_single_row
15876 && !selections_selecting
15877 && !matches!(this.mode, EditorMode::SingleLine);
15878
15879 if advance_downwards {
15880 let snapshot = this.buffer.read(cx).snapshot(cx);
15881
15882 this.change_selections(Default::default(), window, cx, |s| {
15883 s.move_cursors_with(|display_snapshot, display_point, _| {
15884 let mut point = display_point.to_point(display_snapshot);
15885 point.row += 1;
15886 point = snapshot.clip_point(point, Bias::Left);
15887 let display_point = point.to_display_point(display_snapshot);
15888 let goal = SelectionGoal::HorizontalPosition(
15889 display_snapshot
15890 .x_for_display_point(display_point, text_layout_details)
15891 .into(),
15892 );
15893 (display_point, goal)
15894 })
15895 });
15896 }
15897 });
15898 }
15899
15900 pub fn select_enclosing_symbol(
15901 &mut self,
15902 _: &SelectEnclosingSymbol,
15903 window: &mut Window,
15904 cx: &mut Context<Self>,
15905 ) {
15906 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15907
15908 let buffer = self.buffer.read(cx).snapshot(cx);
15909 let old_selections = self
15910 .selections
15911 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15912 .into_boxed_slice();
15913
15914 fn update_selection(
15915 selection: &Selection<MultiBufferOffset>,
15916 buffer_snap: &MultiBufferSnapshot,
15917 ) -> Option<Selection<MultiBufferOffset>> {
15918 let cursor = selection.head();
15919 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15920 for symbol in symbols.iter().rev() {
15921 let start = symbol.range.start.to_offset(buffer_snap);
15922 let end = symbol.range.end.to_offset(buffer_snap);
15923 let new_range = start..end;
15924 if start < selection.start || end > selection.end {
15925 return Some(Selection {
15926 id: selection.id,
15927 start: new_range.start,
15928 end: new_range.end,
15929 goal: SelectionGoal::None,
15930 reversed: selection.reversed,
15931 });
15932 }
15933 }
15934 None
15935 }
15936
15937 let mut selected_larger_symbol = false;
15938 let new_selections = old_selections
15939 .iter()
15940 .map(|selection| match update_selection(selection, &buffer) {
15941 Some(new_selection) => {
15942 if new_selection.range() != selection.range() {
15943 selected_larger_symbol = true;
15944 }
15945 new_selection
15946 }
15947 None => selection.clone(),
15948 })
15949 .collect::<Vec<_>>();
15950
15951 if selected_larger_symbol {
15952 self.change_selections(Default::default(), window, cx, |s| {
15953 s.select(new_selections);
15954 });
15955 }
15956 }
15957
15958 pub fn select_larger_syntax_node(
15959 &mut self,
15960 _: &SelectLargerSyntaxNode,
15961 window: &mut Window,
15962 cx: &mut Context<Self>,
15963 ) {
15964 let Some(visible_row_count) = self.visible_row_count() else {
15965 return;
15966 };
15967 let old_selections: Box<[_]> = self
15968 .selections
15969 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15970 .into();
15971 if old_selections.is_empty() {
15972 return;
15973 }
15974
15975 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15976
15977 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15978 let buffer = self.buffer.read(cx).snapshot(cx);
15979
15980 let mut selected_larger_node = false;
15981 let mut new_selections = old_selections
15982 .iter()
15983 .map(|selection| {
15984 let old_range = selection.start..selection.end;
15985
15986 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15987 // manually select word at selection
15988 if ["string_content", "inline"].contains(&node.kind()) {
15989 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15990 // ignore if word is already selected
15991 if !word_range.is_empty() && old_range != word_range {
15992 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15993 // only select word if start and end point belongs to same word
15994 if word_range == last_word_range {
15995 selected_larger_node = true;
15996 return Selection {
15997 id: selection.id,
15998 start: word_range.start,
15999 end: word_range.end,
16000 goal: SelectionGoal::None,
16001 reversed: selection.reversed,
16002 };
16003 }
16004 }
16005 }
16006 }
16007
16008 let mut new_range = old_range.clone();
16009 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
16010 new_range = range;
16011 if !node.is_named() {
16012 continue;
16013 }
16014 if !display_map.intersects_fold(new_range.start)
16015 && !display_map.intersects_fold(new_range.end)
16016 {
16017 break;
16018 }
16019 }
16020
16021 selected_larger_node |= new_range != old_range;
16022 Selection {
16023 id: selection.id,
16024 start: new_range.start,
16025 end: new_range.end,
16026 goal: SelectionGoal::None,
16027 reversed: selection.reversed,
16028 }
16029 })
16030 .collect::<Vec<_>>();
16031
16032 if !selected_larger_node {
16033 return; // don't put this call in the history
16034 }
16035
16036 // scroll based on transformation done to the last selection created by the user
16037 let (last_old, last_new) = old_selections
16038 .last()
16039 .zip(new_selections.last().cloned())
16040 .expect("old_selections isn't empty");
16041
16042 // revert selection
16043 let is_selection_reversed = {
16044 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16045 new_selections.last_mut().expect("checked above").reversed =
16046 should_newest_selection_be_reversed;
16047 should_newest_selection_be_reversed
16048 };
16049
16050 if selected_larger_node {
16051 self.select_syntax_node_history.disable_clearing = true;
16052 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16053 s.select(new_selections.clone());
16054 });
16055 self.select_syntax_node_history.disable_clearing = false;
16056 }
16057
16058 let start_row = last_new.start.to_display_point(&display_map).row().0;
16059 let end_row = last_new.end.to_display_point(&display_map).row().0;
16060 let selection_height = end_row - start_row + 1;
16061 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16062
16063 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16064 let scroll_behavior = if fits_on_the_screen {
16065 self.request_autoscroll(Autoscroll::fit(), cx);
16066 SelectSyntaxNodeScrollBehavior::FitSelection
16067 } else if is_selection_reversed {
16068 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16069 SelectSyntaxNodeScrollBehavior::CursorTop
16070 } else {
16071 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16072 SelectSyntaxNodeScrollBehavior::CursorBottom
16073 };
16074
16075 self.select_syntax_node_history.push((
16076 old_selections,
16077 scroll_behavior,
16078 is_selection_reversed,
16079 ));
16080 }
16081
16082 pub fn select_smaller_syntax_node(
16083 &mut self,
16084 _: &SelectSmallerSyntaxNode,
16085 window: &mut Window,
16086 cx: &mut Context<Self>,
16087 ) {
16088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16089
16090 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16091 self.select_syntax_node_history.pop()
16092 {
16093 if let Some(selection) = selections.last_mut() {
16094 selection.reversed = is_selection_reversed;
16095 }
16096
16097 self.select_syntax_node_history.disable_clearing = true;
16098 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16099 s.select(selections.to_vec());
16100 });
16101 self.select_syntax_node_history.disable_clearing = false;
16102
16103 match scroll_behavior {
16104 SelectSyntaxNodeScrollBehavior::CursorTop => {
16105 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16106 }
16107 SelectSyntaxNodeScrollBehavior::FitSelection => {
16108 self.request_autoscroll(Autoscroll::fit(), cx);
16109 }
16110 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16111 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16112 }
16113 }
16114 }
16115 }
16116
16117 pub fn unwrap_syntax_node(
16118 &mut self,
16119 _: &UnwrapSyntaxNode,
16120 window: &mut Window,
16121 cx: &mut Context<Self>,
16122 ) {
16123 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16124
16125 let buffer = self.buffer.read(cx).snapshot(cx);
16126 let selections = self
16127 .selections
16128 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16129 .into_iter()
16130 // subtracting the offset requires sorting
16131 .sorted_by_key(|i| i.start);
16132
16133 let full_edits = selections
16134 .into_iter()
16135 .filter_map(|selection| {
16136 let child = if selection.is_empty()
16137 && let Some((_, ancestor_range)) =
16138 buffer.syntax_ancestor(selection.start..selection.end)
16139 {
16140 ancestor_range
16141 } else {
16142 selection.range()
16143 };
16144
16145 let mut parent = child.clone();
16146 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16147 parent = ancestor_range;
16148 if parent.start < child.start || parent.end > child.end {
16149 break;
16150 }
16151 }
16152
16153 if parent == child {
16154 return None;
16155 }
16156 let text = buffer.text_for_range(child).collect::<String>();
16157 Some((selection.id, parent, text))
16158 })
16159 .collect::<Vec<_>>();
16160 if full_edits.is_empty() {
16161 return;
16162 }
16163
16164 self.transact(window, cx, |this, window, cx| {
16165 this.buffer.update(cx, |buffer, cx| {
16166 buffer.edit(
16167 full_edits
16168 .iter()
16169 .map(|(_, p, t)| (p.clone(), t.clone()))
16170 .collect::<Vec<_>>(),
16171 None,
16172 cx,
16173 );
16174 });
16175 this.change_selections(Default::default(), window, cx, |s| {
16176 let mut offset = 0;
16177 let mut selections = vec![];
16178 for (id, parent, text) in full_edits {
16179 let start = parent.start - offset;
16180 offset += (parent.end - parent.start) - text.len();
16181 selections.push(Selection {
16182 id,
16183 start,
16184 end: start + text.len(),
16185 reversed: false,
16186 goal: Default::default(),
16187 });
16188 }
16189 s.select(selections);
16190 });
16191 });
16192 }
16193
16194 pub fn select_next_syntax_node(
16195 &mut self,
16196 _: &SelectNextSyntaxNode,
16197 window: &mut Window,
16198 cx: &mut Context<Self>,
16199 ) {
16200 let old_selections: Box<[_]> = self
16201 .selections
16202 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16203 .into();
16204 if old_selections.is_empty() {
16205 return;
16206 }
16207
16208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16209
16210 let buffer = self.buffer.read(cx).snapshot(cx);
16211 let mut selected_sibling = false;
16212
16213 let new_selections = old_selections
16214 .iter()
16215 .map(|selection| {
16216 let old_range = selection.start..selection.end;
16217
16218 let old_range =
16219 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16220 let excerpt = buffer.excerpt_containing(old_range.clone());
16221
16222 if let Some(mut excerpt) = excerpt
16223 && let Some(node) = excerpt
16224 .buffer()
16225 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16226 {
16227 let new_range = excerpt.map_range_from_buffer(
16228 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16229 );
16230 selected_sibling = true;
16231 Selection {
16232 id: selection.id,
16233 start: new_range.start,
16234 end: new_range.end,
16235 goal: SelectionGoal::None,
16236 reversed: selection.reversed,
16237 }
16238 } else {
16239 selection.clone()
16240 }
16241 })
16242 .collect::<Vec<_>>();
16243
16244 if selected_sibling {
16245 self.change_selections(
16246 SelectionEffects::scroll(Autoscroll::fit()),
16247 window,
16248 cx,
16249 |s| {
16250 s.select(new_selections);
16251 },
16252 );
16253 }
16254 }
16255
16256 pub fn select_prev_syntax_node(
16257 &mut self,
16258 _: &SelectPreviousSyntaxNode,
16259 window: &mut Window,
16260 cx: &mut Context<Self>,
16261 ) {
16262 let old_selections: Box<[_]> = self
16263 .selections
16264 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16265 .into();
16266 if old_selections.is_empty() {
16267 return;
16268 }
16269
16270 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16271
16272 let buffer = self.buffer.read(cx).snapshot(cx);
16273 let mut selected_sibling = false;
16274
16275 let new_selections = old_selections
16276 .iter()
16277 .map(|selection| {
16278 let old_range = selection.start..selection.end;
16279 let old_range =
16280 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16281 let excerpt = buffer.excerpt_containing(old_range.clone());
16282
16283 if let Some(mut excerpt) = excerpt
16284 && let Some(node) = excerpt
16285 .buffer()
16286 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16287 {
16288 let new_range = excerpt.map_range_from_buffer(
16289 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16290 );
16291 selected_sibling = true;
16292 Selection {
16293 id: selection.id,
16294 start: new_range.start,
16295 end: new_range.end,
16296 goal: SelectionGoal::None,
16297 reversed: selection.reversed,
16298 }
16299 } else {
16300 selection.clone()
16301 }
16302 })
16303 .collect::<Vec<_>>();
16304
16305 if selected_sibling {
16306 self.change_selections(
16307 SelectionEffects::scroll(Autoscroll::fit()),
16308 window,
16309 cx,
16310 |s| {
16311 s.select(new_selections);
16312 },
16313 );
16314 }
16315 }
16316
16317 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16318 if !EditorSettings::get_global(cx).gutter.runnables {
16319 self.clear_tasks();
16320 return Task::ready(());
16321 }
16322 let project = self.project().map(Entity::downgrade);
16323 let task_sources = self.lsp_task_sources(cx);
16324 let multi_buffer = self.buffer.downgrade();
16325 cx.spawn_in(window, async move |editor, cx| {
16326 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16327 let Some(project) = project.and_then(|p| p.upgrade()) else {
16328 return;
16329 };
16330 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16331 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16332 }) else {
16333 return;
16334 };
16335
16336 let hide_runnables = project
16337 .update(cx, |project, _| project.is_via_collab())
16338 .unwrap_or(true);
16339 if hide_runnables {
16340 return;
16341 }
16342 let new_rows =
16343 cx.background_spawn({
16344 let snapshot = display_snapshot.clone();
16345 async move {
16346 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16347 }
16348 })
16349 .await;
16350 let Ok(lsp_tasks) =
16351 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16352 else {
16353 return;
16354 };
16355 let lsp_tasks = lsp_tasks.await;
16356
16357 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16358 lsp_tasks
16359 .into_iter()
16360 .flat_map(|(kind, tasks)| {
16361 tasks.into_iter().filter_map(move |(location, task)| {
16362 Some((kind.clone(), location?, task))
16363 })
16364 })
16365 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16366 let buffer = location.target.buffer;
16367 let buffer_snapshot = buffer.read(cx).snapshot();
16368 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16369 |(excerpt_id, snapshot, _)| {
16370 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16371 display_snapshot
16372 .buffer_snapshot()
16373 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16374 } else {
16375 None
16376 }
16377 },
16378 );
16379 if let Some(offset) = offset {
16380 let task_buffer_range =
16381 location.target.range.to_point(&buffer_snapshot);
16382 let context_buffer_range =
16383 task_buffer_range.to_offset(&buffer_snapshot);
16384 let context_range = BufferOffset(context_buffer_range.start)
16385 ..BufferOffset(context_buffer_range.end);
16386
16387 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16388 .or_insert_with(|| RunnableTasks {
16389 templates: Vec::new(),
16390 offset,
16391 column: task_buffer_range.start.column,
16392 extra_variables: HashMap::default(),
16393 context_range,
16394 })
16395 .templates
16396 .push((kind, task.original_task().clone()));
16397 }
16398
16399 acc
16400 })
16401 }) else {
16402 return;
16403 };
16404
16405 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16406 buffer.language_settings(cx).tasks.prefer_lsp
16407 }) else {
16408 return;
16409 };
16410
16411 let rows = Self::runnable_rows(
16412 project,
16413 display_snapshot,
16414 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16415 new_rows,
16416 cx.clone(),
16417 )
16418 .await;
16419 editor
16420 .update(cx, |editor, _| {
16421 editor.clear_tasks();
16422 for (key, mut value) in rows {
16423 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16424 value.templates.extend(lsp_tasks.templates);
16425 }
16426
16427 editor.insert_tasks(key, value);
16428 }
16429 for (key, value) in lsp_tasks_by_rows {
16430 editor.insert_tasks(key, value);
16431 }
16432 })
16433 .ok();
16434 })
16435 }
16436 fn fetch_runnable_ranges(
16437 snapshot: &DisplaySnapshot,
16438 range: Range<Anchor>,
16439 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16440 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16441 }
16442
16443 fn runnable_rows(
16444 project: Entity<Project>,
16445 snapshot: DisplaySnapshot,
16446 prefer_lsp: bool,
16447 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16448 cx: AsyncWindowContext,
16449 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16450 cx.spawn(async move |cx| {
16451 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16452 for (run_range, mut runnable) in runnable_ranges {
16453 let Some(tasks) = cx
16454 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16455 .ok()
16456 else {
16457 continue;
16458 };
16459 let mut tasks = tasks.await;
16460
16461 if prefer_lsp {
16462 tasks.retain(|(task_kind, _)| {
16463 !matches!(task_kind, TaskSourceKind::Language { .. })
16464 });
16465 }
16466 if tasks.is_empty() {
16467 continue;
16468 }
16469
16470 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16471 let Some(row) = snapshot
16472 .buffer_snapshot()
16473 .buffer_line_for_row(MultiBufferRow(point.row))
16474 .map(|(_, range)| range.start.row)
16475 else {
16476 continue;
16477 };
16478
16479 let context_range =
16480 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16481 runnable_rows.push((
16482 (runnable.buffer_id, row),
16483 RunnableTasks {
16484 templates: tasks,
16485 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16486 context_range,
16487 column: point.column,
16488 extra_variables: runnable.extra_captures,
16489 },
16490 ));
16491 }
16492 runnable_rows
16493 })
16494 }
16495
16496 fn templates_with_tags(
16497 project: &Entity<Project>,
16498 runnable: &mut Runnable,
16499 cx: &mut App,
16500 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16501 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16502 let (worktree_id, file) = project
16503 .buffer_for_id(runnable.buffer, cx)
16504 .and_then(|buffer| buffer.read(cx).file())
16505 .map(|file| (file.worktree_id(cx), file.clone()))
16506 .unzip();
16507
16508 (
16509 project.task_store().read(cx).task_inventory().cloned(),
16510 worktree_id,
16511 file,
16512 )
16513 });
16514
16515 let tags = mem::take(&mut runnable.tags);
16516 let language = runnable.language.clone();
16517 cx.spawn(async move |cx| {
16518 let mut templates_with_tags = Vec::new();
16519 if let Some(inventory) = inventory {
16520 for RunnableTag(tag) in tags {
16521 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16522 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16523 }) else {
16524 return templates_with_tags;
16525 };
16526 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16527 move |(_, template)| {
16528 template.tags.iter().any(|source_tag| source_tag == &tag)
16529 },
16530 ));
16531 }
16532 }
16533 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16534
16535 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16536 // Strongest source wins; if we have worktree tag binding, prefer that to
16537 // global and language bindings;
16538 // if we have a global binding, prefer that to language binding.
16539 let first_mismatch = templates_with_tags
16540 .iter()
16541 .position(|(tag_source, _)| tag_source != leading_tag_source);
16542 if let Some(index) = first_mismatch {
16543 templates_with_tags.truncate(index);
16544 }
16545 }
16546
16547 templates_with_tags
16548 })
16549 }
16550
16551 pub fn move_to_enclosing_bracket(
16552 &mut self,
16553 _: &MoveToEnclosingBracket,
16554 window: &mut Window,
16555 cx: &mut Context<Self>,
16556 ) {
16557 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16558 self.change_selections(Default::default(), window, cx, |s| {
16559 s.move_offsets_with(|snapshot, selection| {
16560 let Some(enclosing_bracket_ranges) =
16561 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16562 else {
16563 return;
16564 };
16565
16566 let mut best_length = usize::MAX;
16567 let mut best_inside = false;
16568 let mut best_in_bracket_range = false;
16569 let mut best_destination = None;
16570 for (open, close) in enclosing_bracket_ranges {
16571 let close = close.to_inclusive();
16572 let length = *close.end() - open.start;
16573 let inside = selection.start >= open.end && selection.end <= *close.start();
16574 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16575 || close.contains(&selection.head());
16576
16577 // If best is next to a bracket and current isn't, skip
16578 if !in_bracket_range && best_in_bracket_range {
16579 continue;
16580 }
16581
16582 // Prefer smaller lengths unless best is inside and current isn't
16583 if length > best_length && (best_inside || !inside) {
16584 continue;
16585 }
16586
16587 best_length = length;
16588 best_inside = inside;
16589 best_in_bracket_range = in_bracket_range;
16590 best_destination = Some(
16591 if close.contains(&selection.start) && close.contains(&selection.end) {
16592 if inside { open.end } else { open.start }
16593 } else if inside {
16594 *close.start()
16595 } else {
16596 *close.end()
16597 },
16598 );
16599 }
16600
16601 if let Some(destination) = best_destination {
16602 selection.collapse_to(destination, SelectionGoal::None);
16603 }
16604 })
16605 });
16606 }
16607
16608 pub fn undo_selection(
16609 &mut self,
16610 _: &UndoSelection,
16611 window: &mut Window,
16612 cx: &mut Context<Self>,
16613 ) {
16614 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16615 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16616 self.selection_history.mode = SelectionHistoryMode::Undoing;
16617 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16618 this.end_selection(window, cx);
16619 this.change_selections(
16620 SelectionEffects::scroll(Autoscroll::newest()),
16621 window,
16622 cx,
16623 |s| s.select_anchors(entry.selections.to_vec()),
16624 );
16625 });
16626 self.selection_history.mode = SelectionHistoryMode::Normal;
16627
16628 self.select_next_state = entry.select_next_state;
16629 self.select_prev_state = entry.select_prev_state;
16630 self.add_selections_state = entry.add_selections_state;
16631 }
16632 }
16633
16634 pub fn redo_selection(
16635 &mut self,
16636 _: &RedoSelection,
16637 window: &mut Window,
16638 cx: &mut Context<Self>,
16639 ) {
16640 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16641 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16642 self.selection_history.mode = SelectionHistoryMode::Redoing;
16643 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16644 this.end_selection(window, cx);
16645 this.change_selections(
16646 SelectionEffects::scroll(Autoscroll::newest()),
16647 window,
16648 cx,
16649 |s| s.select_anchors(entry.selections.to_vec()),
16650 );
16651 });
16652 self.selection_history.mode = SelectionHistoryMode::Normal;
16653
16654 self.select_next_state = entry.select_next_state;
16655 self.select_prev_state = entry.select_prev_state;
16656 self.add_selections_state = entry.add_selections_state;
16657 }
16658 }
16659
16660 pub fn expand_excerpts(
16661 &mut self,
16662 action: &ExpandExcerpts,
16663 _: &mut Window,
16664 cx: &mut Context<Self>,
16665 ) {
16666 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16667 }
16668
16669 pub fn expand_excerpts_down(
16670 &mut self,
16671 action: &ExpandExcerptsDown,
16672 _: &mut Window,
16673 cx: &mut Context<Self>,
16674 ) {
16675 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16676 }
16677
16678 pub fn expand_excerpts_up(
16679 &mut self,
16680 action: &ExpandExcerptsUp,
16681 _: &mut Window,
16682 cx: &mut Context<Self>,
16683 ) {
16684 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16685 }
16686
16687 pub fn expand_excerpts_for_direction(
16688 &mut self,
16689 lines: u32,
16690 direction: ExpandExcerptDirection,
16691
16692 cx: &mut Context<Self>,
16693 ) {
16694 let selections = self.selections.disjoint_anchors_arc();
16695
16696 let lines = if lines == 0 {
16697 EditorSettings::get_global(cx).expand_excerpt_lines
16698 } else {
16699 lines
16700 };
16701
16702 self.buffer.update(cx, |buffer, cx| {
16703 let snapshot = buffer.snapshot(cx);
16704 let mut excerpt_ids = selections
16705 .iter()
16706 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16707 .collect::<Vec<_>>();
16708 excerpt_ids.sort();
16709 excerpt_ids.dedup();
16710 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16711 })
16712 }
16713
16714 pub fn expand_excerpt(
16715 &mut self,
16716 excerpt: ExcerptId,
16717 direction: ExpandExcerptDirection,
16718 window: &mut Window,
16719 cx: &mut Context<Self>,
16720 ) {
16721 let current_scroll_position = self.scroll_position(cx);
16722 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16723 let mut scroll = None;
16724
16725 if direction == ExpandExcerptDirection::Down {
16726 let multi_buffer = self.buffer.read(cx);
16727 let snapshot = multi_buffer.snapshot(cx);
16728 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16729 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16730 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16731 {
16732 let buffer_snapshot = buffer.read(cx).snapshot();
16733 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16734 let last_row = buffer_snapshot.max_point().row;
16735 let lines_below = last_row.saturating_sub(excerpt_end_row);
16736 if lines_below >= lines_to_expand {
16737 scroll = Some(
16738 current_scroll_position
16739 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16740 );
16741 }
16742 }
16743 }
16744 if direction == ExpandExcerptDirection::Up
16745 && self
16746 .buffer
16747 .read(cx)
16748 .snapshot(cx)
16749 .excerpt_before(excerpt)
16750 .is_none()
16751 {
16752 scroll = Some(current_scroll_position);
16753 }
16754
16755 self.buffer.update(cx, |buffer, cx| {
16756 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16757 });
16758
16759 if let Some(new_scroll_position) = scroll {
16760 self.set_scroll_position(new_scroll_position, window, cx);
16761 }
16762 }
16763
16764 pub fn go_to_singleton_buffer_point(
16765 &mut self,
16766 point: Point,
16767 window: &mut Window,
16768 cx: &mut Context<Self>,
16769 ) {
16770 self.go_to_singleton_buffer_range(point..point, window, cx);
16771 }
16772
16773 pub fn go_to_singleton_buffer_range(
16774 &mut self,
16775 range: Range<Point>,
16776 window: &mut Window,
16777 cx: &mut Context<Self>,
16778 ) {
16779 let multibuffer = self.buffer().read(cx);
16780 let Some(buffer) = multibuffer.as_singleton() else {
16781 return;
16782 };
16783 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16784 return;
16785 };
16786 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16787 return;
16788 };
16789 self.change_selections(
16790 SelectionEffects::default().nav_history(true),
16791 window,
16792 cx,
16793 |s| s.select_anchor_ranges([start..end]),
16794 );
16795 }
16796
16797 pub fn go_to_diagnostic(
16798 &mut self,
16799 action: &GoToDiagnostic,
16800 window: &mut Window,
16801 cx: &mut Context<Self>,
16802 ) {
16803 if !self.diagnostics_enabled() {
16804 return;
16805 }
16806 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16807 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16808 }
16809
16810 pub fn go_to_prev_diagnostic(
16811 &mut self,
16812 action: &GoToPreviousDiagnostic,
16813 window: &mut Window,
16814 cx: &mut Context<Self>,
16815 ) {
16816 if !self.diagnostics_enabled() {
16817 return;
16818 }
16819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16820 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16821 }
16822
16823 pub fn go_to_diagnostic_impl(
16824 &mut self,
16825 direction: Direction,
16826 severity: GoToDiagnosticSeverityFilter,
16827 window: &mut Window,
16828 cx: &mut Context<Self>,
16829 ) {
16830 let buffer = self.buffer.read(cx).snapshot(cx);
16831 let selection = self
16832 .selections
16833 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16834
16835 let mut active_group_id = None;
16836 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16837 && active_group.active_range.start.to_offset(&buffer) == selection.start
16838 {
16839 active_group_id = Some(active_group.group_id);
16840 }
16841
16842 fn filtered<'a>(
16843 severity: GoToDiagnosticSeverityFilter,
16844 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16845 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16846 diagnostics
16847 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16848 .filter(|entry| entry.range.start != entry.range.end)
16849 .filter(|entry| !entry.diagnostic.is_unnecessary)
16850 }
16851
16852 let before = filtered(
16853 severity,
16854 buffer
16855 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16856 .filter(|entry| entry.range.start <= selection.start),
16857 );
16858 let after = filtered(
16859 severity,
16860 buffer
16861 .diagnostics_in_range(selection.start..buffer.len())
16862 .filter(|entry| entry.range.start >= selection.start),
16863 );
16864
16865 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16866 if direction == Direction::Prev {
16867 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16868 {
16869 for diagnostic in prev_diagnostics.into_iter().rev() {
16870 if diagnostic.range.start != selection.start
16871 || active_group_id
16872 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16873 {
16874 found = Some(diagnostic);
16875 break 'outer;
16876 }
16877 }
16878 }
16879 } else {
16880 for diagnostic in after.chain(before) {
16881 if diagnostic.range.start != selection.start
16882 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16883 {
16884 found = Some(diagnostic);
16885 break;
16886 }
16887 }
16888 }
16889 let Some(next_diagnostic) = found else {
16890 return;
16891 };
16892
16893 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16894 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16895 return;
16896 };
16897 let snapshot = self.snapshot(window, cx);
16898 if snapshot.intersects_fold(next_diagnostic.range.start) {
16899 self.unfold_ranges(
16900 std::slice::from_ref(&next_diagnostic.range),
16901 true,
16902 false,
16903 cx,
16904 );
16905 }
16906 self.change_selections(Default::default(), window, cx, |s| {
16907 s.select_ranges(vec![
16908 next_diagnostic.range.start..next_diagnostic.range.start,
16909 ])
16910 });
16911 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16912 self.refresh_edit_prediction(false, true, window, cx);
16913 }
16914
16915 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16916 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16917 let snapshot = self.snapshot(window, cx);
16918 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16919 self.go_to_hunk_before_or_after_position(
16920 &snapshot,
16921 selection.head(),
16922 Direction::Next,
16923 window,
16924 cx,
16925 );
16926 }
16927
16928 pub fn go_to_hunk_before_or_after_position(
16929 &mut self,
16930 snapshot: &EditorSnapshot,
16931 position: Point,
16932 direction: Direction,
16933 window: &mut Window,
16934 cx: &mut Context<Editor>,
16935 ) {
16936 let row = if direction == Direction::Next {
16937 self.hunk_after_position(snapshot, position)
16938 .map(|hunk| hunk.row_range.start)
16939 } else {
16940 self.hunk_before_position(snapshot, position)
16941 };
16942
16943 if let Some(row) = row {
16944 let destination = Point::new(row.0, 0);
16945 let autoscroll = Autoscroll::center();
16946
16947 self.unfold_ranges(&[destination..destination], false, false, cx);
16948 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16949 s.select_ranges([destination..destination]);
16950 });
16951 }
16952 }
16953
16954 fn hunk_after_position(
16955 &mut self,
16956 snapshot: &EditorSnapshot,
16957 position: Point,
16958 ) -> Option<MultiBufferDiffHunk> {
16959 snapshot
16960 .buffer_snapshot()
16961 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16962 .find(|hunk| hunk.row_range.start.0 > position.row)
16963 .or_else(|| {
16964 snapshot
16965 .buffer_snapshot()
16966 .diff_hunks_in_range(Point::zero()..position)
16967 .find(|hunk| hunk.row_range.end.0 < position.row)
16968 })
16969 }
16970
16971 fn go_to_prev_hunk(
16972 &mut self,
16973 _: &GoToPreviousHunk,
16974 window: &mut Window,
16975 cx: &mut Context<Self>,
16976 ) {
16977 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16978 let snapshot = self.snapshot(window, cx);
16979 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16980 self.go_to_hunk_before_or_after_position(
16981 &snapshot,
16982 selection.head(),
16983 Direction::Prev,
16984 window,
16985 cx,
16986 );
16987 }
16988
16989 fn hunk_before_position(
16990 &mut self,
16991 snapshot: &EditorSnapshot,
16992 position: Point,
16993 ) -> Option<MultiBufferRow> {
16994 snapshot
16995 .buffer_snapshot()
16996 .diff_hunk_before(position)
16997 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16998 }
16999
17000 fn go_to_next_change(
17001 &mut self,
17002 _: &GoToNextChange,
17003 window: &mut Window,
17004 cx: &mut Context<Self>,
17005 ) {
17006 if let Some(selections) = self
17007 .change_list
17008 .next_change(1, Direction::Next)
17009 .map(|s| s.to_vec())
17010 {
17011 self.change_selections(Default::default(), window, cx, |s| {
17012 let map = s.display_snapshot();
17013 s.select_display_ranges(selections.iter().map(|a| {
17014 let point = a.to_display_point(&map);
17015 point..point
17016 }))
17017 })
17018 }
17019 }
17020
17021 fn go_to_previous_change(
17022 &mut self,
17023 _: &GoToPreviousChange,
17024 window: &mut Window,
17025 cx: &mut Context<Self>,
17026 ) {
17027 if let Some(selections) = self
17028 .change_list
17029 .next_change(1, Direction::Prev)
17030 .map(|s| s.to_vec())
17031 {
17032 self.change_selections(Default::default(), window, cx, |s| {
17033 let map = s.display_snapshot();
17034 s.select_display_ranges(selections.iter().map(|a| {
17035 let point = a.to_display_point(&map);
17036 point..point
17037 }))
17038 })
17039 }
17040 }
17041
17042 pub fn go_to_next_document_highlight(
17043 &mut self,
17044 _: &GoToNextDocumentHighlight,
17045 window: &mut Window,
17046 cx: &mut Context<Self>,
17047 ) {
17048 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17049 }
17050
17051 pub fn go_to_prev_document_highlight(
17052 &mut self,
17053 _: &GoToPreviousDocumentHighlight,
17054 window: &mut Window,
17055 cx: &mut Context<Self>,
17056 ) {
17057 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17058 }
17059
17060 pub fn go_to_document_highlight_before_or_after_position(
17061 &mut self,
17062 direction: Direction,
17063 window: &mut Window,
17064 cx: &mut Context<Editor>,
17065 ) {
17066 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17067 let snapshot = self.snapshot(window, cx);
17068 let buffer = &snapshot.buffer_snapshot();
17069 let position = self
17070 .selections
17071 .newest::<Point>(&snapshot.display_snapshot)
17072 .head();
17073 let anchor_position = buffer.anchor_after(position);
17074
17075 // Get all document highlights (both read and write)
17076 let mut all_highlights = Vec::new();
17077
17078 if let Some((_, read_highlights)) = self
17079 .background_highlights
17080 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17081 {
17082 all_highlights.extend(read_highlights.iter());
17083 }
17084
17085 if let Some((_, write_highlights)) = self
17086 .background_highlights
17087 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17088 {
17089 all_highlights.extend(write_highlights.iter());
17090 }
17091
17092 if all_highlights.is_empty() {
17093 return;
17094 }
17095
17096 // Sort highlights by position
17097 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17098
17099 let target_highlight = match direction {
17100 Direction::Next => {
17101 // Find the first highlight after the current position
17102 all_highlights
17103 .iter()
17104 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17105 }
17106 Direction::Prev => {
17107 // Find the last highlight before the current position
17108 all_highlights
17109 .iter()
17110 .rev()
17111 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17112 }
17113 };
17114
17115 if let Some(highlight) = target_highlight {
17116 let destination = highlight.start.to_point(buffer);
17117 let autoscroll = Autoscroll::center();
17118
17119 self.unfold_ranges(&[destination..destination], false, false, cx);
17120 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17121 s.select_ranges([destination..destination]);
17122 });
17123 }
17124 }
17125
17126 fn go_to_line<T: 'static>(
17127 &mut self,
17128 position: Anchor,
17129 highlight_color: Option<Hsla>,
17130 window: &mut Window,
17131 cx: &mut Context<Self>,
17132 ) {
17133 let snapshot = self.snapshot(window, cx).display_snapshot;
17134 let position = position.to_point(&snapshot.buffer_snapshot());
17135 let start = snapshot
17136 .buffer_snapshot()
17137 .clip_point(Point::new(position.row, 0), Bias::Left);
17138 let end = start + Point::new(1, 0);
17139 let start = snapshot.buffer_snapshot().anchor_before(start);
17140 let end = snapshot.buffer_snapshot().anchor_before(end);
17141
17142 self.highlight_rows::<T>(
17143 start..end,
17144 highlight_color
17145 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17146 Default::default(),
17147 cx,
17148 );
17149
17150 if self.buffer.read(cx).is_singleton() {
17151 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17152 }
17153 }
17154
17155 pub fn go_to_definition(
17156 &mut self,
17157 _: &GoToDefinition,
17158 window: &mut Window,
17159 cx: &mut Context<Self>,
17160 ) -> Task<Result<Navigated>> {
17161 let definition =
17162 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17163 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17164 cx.spawn_in(window, async move |editor, cx| {
17165 if definition.await? == Navigated::Yes {
17166 return Ok(Navigated::Yes);
17167 }
17168 match fallback_strategy {
17169 GoToDefinitionFallback::None => Ok(Navigated::No),
17170 GoToDefinitionFallback::FindAllReferences => {
17171 match editor.update_in(cx, |editor, window, cx| {
17172 editor.find_all_references(&FindAllReferences::default(), window, cx)
17173 })? {
17174 Some(references) => references.await,
17175 None => Ok(Navigated::No),
17176 }
17177 }
17178 }
17179 })
17180 }
17181
17182 pub fn go_to_declaration(
17183 &mut self,
17184 _: &GoToDeclaration,
17185 window: &mut Window,
17186 cx: &mut Context<Self>,
17187 ) -> Task<Result<Navigated>> {
17188 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17189 }
17190
17191 pub fn go_to_declaration_split(
17192 &mut self,
17193 _: &GoToDeclaration,
17194 window: &mut Window,
17195 cx: &mut Context<Self>,
17196 ) -> Task<Result<Navigated>> {
17197 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17198 }
17199
17200 pub fn go_to_implementation(
17201 &mut self,
17202 _: &GoToImplementation,
17203 window: &mut Window,
17204 cx: &mut Context<Self>,
17205 ) -> Task<Result<Navigated>> {
17206 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17207 }
17208
17209 pub fn go_to_implementation_split(
17210 &mut self,
17211 _: &GoToImplementationSplit,
17212 window: &mut Window,
17213 cx: &mut Context<Self>,
17214 ) -> Task<Result<Navigated>> {
17215 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17216 }
17217
17218 pub fn go_to_type_definition(
17219 &mut self,
17220 _: &GoToTypeDefinition,
17221 window: &mut Window,
17222 cx: &mut Context<Self>,
17223 ) -> Task<Result<Navigated>> {
17224 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17225 }
17226
17227 pub fn go_to_definition_split(
17228 &mut self,
17229 _: &GoToDefinitionSplit,
17230 window: &mut Window,
17231 cx: &mut Context<Self>,
17232 ) -> Task<Result<Navigated>> {
17233 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17234 }
17235
17236 pub fn go_to_type_definition_split(
17237 &mut self,
17238 _: &GoToTypeDefinitionSplit,
17239 window: &mut Window,
17240 cx: &mut Context<Self>,
17241 ) -> Task<Result<Navigated>> {
17242 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17243 }
17244
17245 fn go_to_definition_of_kind(
17246 &mut self,
17247 kind: GotoDefinitionKind,
17248 split: bool,
17249 window: &mut Window,
17250 cx: &mut Context<Self>,
17251 ) -> Task<Result<Navigated>> {
17252 let Some(provider) = self.semantics_provider.clone() else {
17253 return Task::ready(Ok(Navigated::No));
17254 };
17255 let head = self
17256 .selections
17257 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17258 .head();
17259 let buffer = self.buffer.read(cx);
17260 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17261 return Task::ready(Ok(Navigated::No));
17262 };
17263 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17264 return Task::ready(Ok(Navigated::No));
17265 };
17266
17267 cx.spawn_in(window, async move |editor, cx| {
17268 let Some(definitions) = definitions.await? else {
17269 return Ok(Navigated::No);
17270 };
17271 let navigated = editor
17272 .update_in(cx, |editor, window, cx| {
17273 editor.navigate_to_hover_links(
17274 Some(kind),
17275 definitions
17276 .into_iter()
17277 .filter(|location| {
17278 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17279 })
17280 .map(HoverLink::Text)
17281 .collect::<Vec<_>>(),
17282 split,
17283 window,
17284 cx,
17285 )
17286 })?
17287 .await?;
17288 anyhow::Ok(navigated)
17289 })
17290 }
17291
17292 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17293 let selection = self.selections.newest_anchor();
17294 let head = selection.head();
17295 let tail = selection.tail();
17296
17297 let Some((buffer, start_position)) =
17298 self.buffer.read(cx).text_anchor_for_position(head, cx)
17299 else {
17300 return;
17301 };
17302
17303 let end_position = if head != tail {
17304 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17305 return;
17306 };
17307 Some(pos)
17308 } else {
17309 None
17310 };
17311
17312 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17313 let url = if let Some(end_pos) = end_position {
17314 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17315 } else {
17316 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17317 };
17318
17319 if let Some(url) = url {
17320 cx.update(|window, cx| {
17321 if parse_zed_link(&url, cx).is_some() {
17322 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17323 } else {
17324 cx.open_url(&url);
17325 }
17326 })?;
17327 }
17328
17329 anyhow::Ok(())
17330 });
17331
17332 url_finder.detach();
17333 }
17334
17335 pub fn open_selected_filename(
17336 &mut self,
17337 _: &OpenSelectedFilename,
17338 window: &mut Window,
17339 cx: &mut Context<Self>,
17340 ) {
17341 let Some(workspace) = self.workspace() else {
17342 return;
17343 };
17344
17345 let position = self.selections.newest_anchor().head();
17346
17347 let Some((buffer, buffer_position)) =
17348 self.buffer.read(cx).text_anchor_for_position(position, cx)
17349 else {
17350 return;
17351 };
17352
17353 let project = self.project.clone();
17354
17355 cx.spawn_in(window, async move |_, cx| {
17356 let result = find_file(&buffer, project, buffer_position, cx).await;
17357
17358 if let Some((_, path)) = result {
17359 workspace
17360 .update_in(cx, |workspace, window, cx| {
17361 workspace.open_resolved_path(path, window, cx)
17362 })?
17363 .await?;
17364 }
17365 anyhow::Ok(())
17366 })
17367 .detach();
17368 }
17369
17370 pub(crate) fn navigate_to_hover_links(
17371 &mut self,
17372 kind: Option<GotoDefinitionKind>,
17373 definitions: Vec<HoverLink>,
17374 split: bool,
17375 window: &mut Window,
17376 cx: &mut Context<Editor>,
17377 ) -> Task<Result<Navigated>> {
17378 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17379 let mut first_url_or_file = None;
17380 let definitions: Vec<_> = definitions
17381 .into_iter()
17382 .filter_map(|def| match def {
17383 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17384 HoverLink::InlayHint(lsp_location, server_id) => {
17385 let computation =
17386 self.compute_target_location(lsp_location, server_id, window, cx);
17387 Some(cx.background_spawn(computation))
17388 }
17389 HoverLink::Url(url) => {
17390 first_url_or_file = Some(Either::Left(url));
17391 None
17392 }
17393 HoverLink::File(path) => {
17394 first_url_or_file = Some(Either::Right(path));
17395 None
17396 }
17397 })
17398 .collect();
17399
17400 let workspace = self.workspace();
17401
17402 cx.spawn_in(window, async move |editor, cx| {
17403 let locations: Vec<Location> = future::join_all(definitions)
17404 .await
17405 .into_iter()
17406 .filter_map(|location| location.transpose())
17407 .collect::<Result<_>>()
17408 .context("location tasks")?;
17409 let mut locations = cx.update(|_, cx| {
17410 locations
17411 .into_iter()
17412 .map(|location| {
17413 let buffer = location.buffer.read(cx);
17414 (location.buffer, location.range.to_point(buffer))
17415 })
17416 .into_group_map()
17417 })?;
17418 let mut num_locations = 0;
17419 for ranges in locations.values_mut() {
17420 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17421 ranges.dedup();
17422 num_locations += ranges.len();
17423 }
17424
17425 if num_locations > 1 {
17426 let tab_kind = match kind {
17427 Some(GotoDefinitionKind::Implementation) => "Implementations",
17428 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17429 Some(GotoDefinitionKind::Declaration) => "Declarations",
17430 Some(GotoDefinitionKind::Type) => "Types",
17431 };
17432 let title = editor
17433 .update_in(cx, |_, _, cx| {
17434 let target = locations
17435 .iter()
17436 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17437 .map(|(buffer, location)| {
17438 buffer
17439 .read(cx)
17440 .text_for_range(location.clone())
17441 .collect::<String>()
17442 })
17443 .filter(|text| !text.contains('\n'))
17444 .unique()
17445 .take(3)
17446 .join(", ");
17447 if target.is_empty() {
17448 tab_kind.to_owned()
17449 } else {
17450 format!("{tab_kind} for {target}")
17451 }
17452 })
17453 .context("buffer title")?;
17454
17455 let Some(workspace) = workspace else {
17456 return Ok(Navigated::No);
17457 };
17458
17459 let opened = workspace
17460 .update_in(cx, |workspace, window, cx| {
17461 let allow_preview = PreviewTabsSettings::get_global(cx)
17462 .enable_preview_multibuffer_from_code_navigation;
17463 Self::open_locations_in_multibuffer(
17464 workspace,
17465 locations,
17466 title,
17467 split,
17468 allow_preview,
17469 MultibufferSelectionMode::First,
17470 window,
17471 cx,
17472 )
17473 })
17474 .is_ok();
17475
17476 anyhow::Ok(Navigated::from_bool(opened))
17477 } else if num_locations == 0 {
17478 // If there is one url or file, open it directly
17479 match first_url_or_file {
17480 Some(Either::Left(url)) => {
17481 cx.update(|window, cx| {
17482 if parse_zed_link(&url, cx).is_some() {
17483 window
17484 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17485 } else {
17486 cx.open_url(&url);
17487 }
17488 })?;
17489 Ok(Navigated::Yes)
17490 }
17491 Some(Either::Right(path)) => {
17492 // TODO(andrew): respect preview tab settings
17493 // `enable_keep_preview_on_code_navigation` and
17494 // `enable_preview_file_from_code_navigation`
17495 let Some(workspace) = workspace else {
17496 return Ok(Navigated::No);
17497 };
17498 workspace
17499 .update_in(cx, |workspace, window, cx| {
17500 workspace.open_resolved_path(path, window, cx)
17501 })?
17502 .await?;
17503 Ok(Navigated::Yes)
17504 }
17505 None => Ok(Navigated::No),
17506 }
17507 } else {
17508 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17509 let target_range = target_ranges.first().unwrap().clone();
17510
17511 editor.update_in(cx, |editor, window, cx| {
17512 let range = target_range.to_point(target_buffer.read(cx));
17513 let range = editor.range_for_match(&range);
17514 let range = collapse_multiline_range(range);
17515
17516 if !split
17517 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17518 {
17519 editor.go_to_singleton_buffer_range(range, window, cx);
17520 } else {
17521 let Some(workspace) = workspace else {
17522 return Navigated::No;
17523 };
17524 let pane = workspace.read(cx).active_pane().clone();
17525 window.defer(cx, move |window, cx| {
17526 let target_editor: Entity<Self> =
17527 workspace.update(cx, |workspace, cx| {
17528 let pane = if split {
17529 workspace.adjacent_pane(window, cx)
17530 } else {
17531 workspace.active_pane().clone()
17532 };
17533
17534 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17535 let keep_old_preview = preview_tabs_settings
17536 .enable_keep_preview_on_code_navigation;
17537 let allow_new_preview = preview_tabs_settings
17538 .enable_preview_file_from_code_navigation;
17539
17540 workspace.open_project_item(
17541 pane,
17542 target_buffer.clone(),
17543 true,
17544 true,
17545 keep_old_preview,
17546 allow_new_preview,
17547 window,
17548 cx,
17549 )
17550 });
17551 target_editor.update(cx, |target_editor, cx| {
17552 // When selecting a definition in a different buffer, disable the nav history
17553 // to avoid creating a history entry at the previous cursor location.
17554 pane.update(cx, |pane, _| pane.disable_history());
17555 target_editor.go_to_singleton_buffer_range(range, window, cx);
17556 pane.update(cx, |pane, _| pane.enable_history());
17557 });
17558 });
17559 }
17560 Navigated::Yes
17561 })
17562 }
17563 })
17564 }
17565
17566 fn compute_target_location(
17567 &self,
17568 lsp_location: lsp::Location,
17569 server_id: LanguageServerId,
17570 window: &mut Window,
17571 cx: &mut Context<Self>,
17572 ) -> Task<anyhow::Result<Option<Location>>> {
17573 let Some(project) = self.project.clone() else {
17574 return Task::ready(Ok(None));
17575 };
17576
17577 cx.spawn_in(window, async move |editor, cx| {
17578 let location_task = editor.update(cx, |_, cx| {
17579 project.update(cx, |project, cx| {
17580 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17581 })
17582 })?;
17583 let location = Some({
17584 let target_buffer_handle = location_task.await.context("open local buffer")?;
17585 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17586 let target_start = target_buffer
17587 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17588 let target_end = target_buffer
17589 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17590 target_buffer.anchor_after(target_start)
17591 ..target_buffer.anchor_before(target_end)
17592 })?;
17593 Location {
17594 buffer: target_buffer_handle,
17595 range,
17596 }
17597 });
17598 Ok(location)
17599 })
17600 }
17601
17602 fn go_to_next_reference(
17603 &mut self,
17604 _: &GoToNextReference,
17605 window: &mut Window,
17606 cx: &mut Context<Self>,
17607 ) {
17608 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17609 if let Some(task) = task {
17610 task.detach();
17611 };
17612 }
17613
17614 fn go_to_prev_reference(
17615 &mut self,
17616 _: &GoToPreviousReference,
17617 window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) {
17620 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17621 if let Some(task) = task {
17622 task.detach();
17623 };
17624 }
17625
17626 pub fn go_to_reference_before_or_after_position(
17627 &mut self,
17628 direction: Direction,
17629 count: usize,
17630 window: &mut Window,
17631 cx: &mut Context<Self>,
17632 ) -> Option<Task<Result<()>>> {
17633 let selection = self.selections.newest_anchor();
17634 let head = selection.head();
17635
17636 let multi_buffer = self.buffer.read(cx);
17637
17638 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17639 let workspace = self.workspace()?;
17640 let project = workspace.read(cx).project().clone();
17641 let references =
17642 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17643 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17644 let Some(locations) = references.await? else {
17645 return Ok(());
17646 };
17647
17648 if locations.is_empty() {
17649 // totally normal - the cursor may be on something which is not
17650 // a symbol (e.g. a keyword)
17651 log::info!("no references found under cursor");
17652 return Ok(());
17653 }
17654
17655 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17656
17657 let (locations, current_location_index) =
17658 multi_buffer.update(cx, |multi_buffer, cx| {
17659 let mut locations = locations
17660 .into_iter()
17661 .filter_map(|loc| {
17662 let start = multi_buffer.buffer_anchor_to_anchor(
17663 &loc.buffer,
17664 loc.range.start,
17665 cx,
17666 )?;
17667 let end = multi_buffer.buffer_anchor_to_anchor(
17668 &loc.buffer,
17669 loc.range.end,
17670 cx,
17671 )?;
17672 Some(start..end)
17673 })
17674 .collect::<Vec<_>>();
17675
17676 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17677 // There is an O(n) implementation, but given this list will be
17678 // small (usually <100 items), the extra O(log(n)) factor isn't
17679 // worth the (surprisingly large amount of) extra complexity.
17680 locations
17681 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17682
17683 let head_offset = head.to_offset(&multi_buffer_snapshot);
17684
17685 let current_location_index = locations.iter().position(|loc| {
17686 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17687 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17688 });
17689
17690 (locations, current_location_index)
17691 })?;
17692
17693 let Some(current_location_index) = current_location_index else {
17694 // This indicates something has gone wrong, because we already
17695 // handle the "no references" case above
17696 log::error!(
17697 "failed to find current reference under cursor. Total references: {}",
17698 locations.len()
17699 );
17700 return Ok(());
17701 };
17702
17703 let destination_location_index = match direction {
17704 Direction::Next => (current_location_index + count) % locations.len(),
17705 Direction::Prev => {
17706 (current_location_index + locations.len() - count % locations.len())
17707 % locations.len()
17708 }
17709 };
17710
17711 // TODO(cameron): is this needed?
17712 // the thinking is to avoid "jumping to the current location" (avoid
17713 // polluting "jumplist" in vim terms)
17714 if current_location_index == destination_location_index {
17715 return Ok(());
17716 }
17717
17718 let Range { start, end } = locations[destination_location_index];
17719
17720 editor.update_in(cx, |editor, window, cx| {
17721 let effects = SelectionEffects::default();
17722
17723 editor.unfold_ranges(&[start..end], false, false, cx);
17724 editor.change_selections(effects, window, cx, |s| {
17725 s.select_ranges([start..start]);
17726 });
17727 })?;
17728
17729 Ok(())
17730 }))
17731 }
17732
17733 pub fn find_all_references(
17734 &mut self,
17735 action: &FindAllReferences,
17736 window: &mut Window,
17737 cx: &mut Context<Self>,
17738 ) -> Option<Task<Result<Navigated>>> {
17739 let always_open_multibuffer = action.always_open_multibuffer;
17740 let selection = self.selections.newest_anchor();
17741 let multi_buffer = self.buffer.read(cx);
17742 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17743 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17744 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17745 let head = selection_offset.head();
17746
17747 let head_anchor = multi_buffer_snapshot.anchor_at(
17748 head,
17749 if head < selection_offset.tail() {
17750 Bias::Right
17751 } else {
17752 Bias::Left
17753 },
17754 );
17755
17756 match self
17757 .find_all_references_task_sources
17758 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17759 {
17760 Ok(_) => {
17761 log::info!(
17762 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17763 );
17764 return None;
17765 }
17766 Err(i) => {
17767 self.find_all_references_task_sources.insert(i, head_anchor);
17768 }
17769 }
17770
17771 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17772 let workspace = self.workspace()?;
17773 let project = workspace.read(cx).project().clone();
17774 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17775 Some(cx.spawn_in(window, async move |editor, cx| {
17776 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17777 if let Ok(i) = editor
17778 .find_all_references_task_sources
17779 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17780 {
17781 editor.find_all_references_task_sources.remove(i);
17782 }
17783 });
17784
17785 let Some(locations) = references.await? else {
17786 return anyhow::Ok(Navigated::No);
17787 };
17788 let mut locations = cx.update(|_, cx| {
17789 locations
17790 .into_iter()
17791 .map(|location| {
17792 let buffer = location.buffer.read(cx);
17793 (location.buffer, location.range.to_point(buffer))
17794 })
17795 // if special-casing the single-match case, remove ranges
17796 // that intersect current selection
17797 .filter(|(location_buffer, location)| {
17798 if always_open_multibuffer || &buffer != location_buffer {
17799 return true;
17800 }
17801
17802 !location.contains_inclusive(&selection_point.range())
17803 })
17804 .into_group_map()
17805 })?;
17806 if locations.is_empty() {
17807 return anyhow::Ok(Navigated::No);
17808 }
17809 for ranges in locations.values_mut() {
17810 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17811 ranges.dedup();
17812 }
17813 let mut num_locations = 0;
17814 for ranges in locations.values_mut() {
17815 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17816 ranges.dedup();
17817 num_locations += ranges.len();
17818 }
17819
17820 if num_locations == 1 && !always_open_multibuffer {
17821 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17822 let target_range = target_ranges.first().unwrap().clone();
17823
17824 return editor.update_in(cx, |editor, window, cx| {
17825 let range = target_range.to_point(target_buffer.read(cx));
17826 let range = editor.range_for_match(&range);
17827 let range = range.start..range.start;
17828
17829 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17830 editor.go_to_singleton_buffer_range(range, window, cx);
17831 } else {
17832 let pane = workspace.read(cx).active_pane().clone();
17833 window.defer(cx, move |window, cx| {
17834 let target_editor: Entity<Self> =
17835 workspace.update(cx, |workspace, cx| {
17836 let pane = workspace.active_pane().clone();
17837
17838 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17839 let keep_old_preview = preview_tabs_settings
17840 .enable_keep_preview_on_code_navigation;
17841 let allow_new_preview = preview_tabs_settings
17842 .enable_preview_file_from_code_navigation;
17843
17844 workspace.open_project_item(
17845 pane,
17846 target_buffer.clone(),
17847 true,
17848 true,
17849 keep_old_preview,
17850 allow_new_preview,
17851 window,
17852 cx,
17853 )
17854 });
17855 target_editor.update(cx, |target_editor, cx| {
17856 // When selecting a definition in a different buffer, disable the nav history
17857 // to avoid creating a history entry at the previous cursor location.
17858 pane.update(cx, |pane, _| pane.disable_history());
17859 target_editor.go_to_singleton_buffer_range(range, window, cx);
17860 pane.update(cx, |pane, _| pane.enable_history());
17861 });
17862 });
17863 }
17864 Navigated::No
17865 });
17866 }
17867
17868 workspace.update_in(cx, |workspace, window, cx| {
17869 let target = locations
17870 .iter()
17871 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17872 .map(|(buffer, location)| {
17873 buffer
17874 .read(cx)
17875 .text_for_range(location.clone())
17876 .collect::<String>()
17877 })
17878 .filter(|text| !text.contains('\n'))
17879 .unique()
17880 .take(3)
17881 .join(", ");
17882 let title = if target.is_empty() {
17883 "References".to_owned()
17884 } else {
17885 format!("References to {target}")
17886 };
17887 let allow_preview = PreviewTabsSettings::get_global(cx)
17888 .enable_preview_multibuffer_from_code_navigation;
17889 Self::open_locations_in_multibuffer(
17890 workspace,
17891 locations,
17892 title,
17893 false,
17894 allow_preview,
17895 MultibufferSelectionMode::First,
17896 window,
17897 cx,
17898 );
17899 Navigated::Yes
17900 })
17901 }))
17902 }
17903
17904 /// Opens a multibuffer with the given project locations in it.
17905 pub fn open_locations_in_multibuffer(
17906 workspace: &mut Workspace,
17907 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17908 title: String,
17909 split: bool,
17910 allow_preview: bool,
17911 multibuffer_selection_mode: MultibufferSelectionMode,
17912 window: &mut Window,
17913 cx: &mut Context<Workspace>,
17914 ) {
17915 if locations.is_empty() {
17916 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17917 return;
17918 }
17919
17920 let capability = workspace.project().read(cx).capability();
17921 let mut ranges = <Vec<Range<Anchor>>>::new();
17922
17923 // a key to find existing multibuffer editors with the same set of locations
17924 // to prevent us from opening more and more multibuffer tabs for searches and the like
17925 let mut key = (title.clone(), vec![]);
17926 let excerpt_buffer = cx.new(|cx| {
17927 let key = &mut key.1;
17928 let mut multibuffer = MultiBuffer::new(capability);
17929 for (buffer, mut ranges_for_buffer) in locations {
17930 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17931 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17932 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17933 PathKey::for_buffer(&buffer, cx),
17934 buffer.clone(),
17935 ranges_for_buffer,
17936 multibuffer_context_lines(cx),
17937 cx,
17938 );
17939 ranges.extend(new_ranges)
17940 }
17941
17942 multibuffer.with_title(title)
17943 });
17944 let existing = workspace.active_pane().update(cx, |pane, cx| {
17945 pane.items()
17946 .filter_map(|item| item.downcast::<Editor>())
17947 .find(|editor| {
17948 editor
17949 .read(cx)
17950 .lookup_key
17951 .as_ref()
17952 .and_then(|it| {
17953 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17954 })
17955 .is_some_and(|it| *it == key)
17956 })
17957 });
17958 let was_existing = existing.is_some();
17959 let editor = existing.unwrap_or_else(|| {
17960 cx.new(|cx| {
17961 let mut editor = Editor::for_multibuffer(
17962 excerpt_buffer,
17963 Some(workspace.project().clone()),
17964 window,
17965 cx,
17966 );
17967 editor.lookup_key = Some(Box::new(key));
17968 editor
17969 })
17970 });
17971 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17972 MultibufferSelectionMode::First => {
17973 if let Some(first_range) = ranges.first() {
17974 editor.change_selections(
17975 SelectionEffects::no_scroll(),
17976 window,
17977 cx,
17978 |selections| {
17979 selections.clear_disjoint();
17980 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17981 },
17982 );
17983 }
17984 editor.highlight_background::<Self>(
17985 &ranges,
17986 |_, theme| theme.colors().editor_highlighted_line_background,
17987 cx,
17988 );
17989 }
17990 MultibufferSelectionMode::All => {
17991 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17992 selections.clear_disjoint();
17993 selections.select_anchor_ranges(ranges);
17994 });
17995 }
17996 });
17997
17998 let item = Box::new(editor);
17999
18000 let pane = if split {
18001 workspace.adjacent_pane(window, cx)
18002 } else {
18003 workspace.active_pane().clone()
18004 };
18005 let activate_pane = split;
18006
18007 let mut destination_index = None;
18008 pane.update(cx, |pane, cx| {
18009 if allow_preview && !was_existing {
18010 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
18011 }
18012 if was_existing && !allow_preview {
18013 pane.unpreview_item_if_preview(item.item_id());
18014 }
18015 pane.add_item(item, activate_pane, true, destination_index, window, cx);
18016 });
18017 }
18018
18019 pub fn rename(
18020 &mut self,
18021 _: &Rename,
18022 window: &mut Window,
18023 cx: &mut Context<Self>,
18024 ) -> Option<Task<Result<()>>> {
18025 use language::ToOffset as _;
18026
18027 let provider = self.semantics_provider.clone()?;
18028 let selection = self.selections.newest_anchor().clone();
18029 let (cursor_buffer, cursor_buffer_position) = self
18030 .buffer
18031 .read(cx)
18032 .text_anchor_for_position(selection.head(), cx)?;
18033 let (tail_buffer, cursor_buffer_position_end) = self
18034 .buffer
18035 .read(cx)
18036 .text_anchor_for_position(selection.tail(), cx)?;
18037 if tail_buffer != cursor_buffer {
18038 return None;
18039 }
18040
18041 let snapshot = cursor_buffer.read(cx).snapshot();
18042 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18043 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18044 let prepare_rename = provider
18045 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18046 .unwrap_or_else(|| Task::ready(Ok(None)));
18047 drop(snapshot);
18048
18049 Some(cx.spawn_in(window, async move |this, cx| {
18050 let rename_range = if let Some(range) = prepare_rename.await? {
18051 Some(range)
18052 } else {
18053 this.update(cx, |this, cx| {
18054 let buffer = this.buffer.read(cx).snapshot(cx);
18055 let mut buffer_highlights = this
18056 .document_highlights_for_position(selection.head(), &buffer)
18057 .filter(|highlight| {
18058 highlight.start.excerpt_id == selection.head().excerpt_id
18059 && highlight.end.excerpt_id == selection.head().excerpt_id
18060 });
18061 buffer_highlights
18062 .next()
18063 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18064 })?
18065 };
18066 if let Some(rename_range) = rename_range {
18067 this.update_in(cx, |this, window, cx| {
18068 let snapshot = cursor_buffer.read(cx).snapshot();
18069 let rename_buffer_range = rename_range.to_offset(&snapshot);
18070 let cursor_offset_in_rename_range =
18071 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18072 let cursor_offset_in_rename_range_end =
18073 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18074
18075 this.take_rename(false, window, cx);
18076 let buffer = this.buffer.read(cx).read(cx);
18077 let cursor_offset = selection.head().to_offset(&buffer);
18078 let rename_start =
18079 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18080 let rename_end = rename_start + rename_buffer_range.len();
18081 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18082 let mut old_highlight_id = None;
18083 let old_name: Arc<str> = buffer
18084 .chunks(rename_start..rename_end, true)
18085 .map(|chunk| {
18086 if old_highlight_id.is_none() {
18087 old_highlight_id = chunk.syntax_highlight_id;
18088 }
18089 chunk.text
18090 })
18091 .collect::<String>()
18092 .into();
18093
18094 drop(buffer);
18095
18096 // Position the selection in the rename editor so that it matches the current selection.
18097 this.show_local_selections = false;
18098 let rename_editor = cx.new(|cx| {
18099 let mut editor = Editor::single_line(window, cx);
18100 editor.buffer.update(cx, |buffer, cx| {
18101 buffer.edit(
18102 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18103 None,
18104 cx,
18105 )
18106 });
18107 let cursor_offset_in_rename_range =
18108 MultiBufferOffset(cursor_offset_in_rename_range);
18109 let cursor_offset_in_rename_range_end =
18110 MultiBufferOffset(cursor_offset_in_rename_range_end);
18111 let rename_selection_range = match cursor_offset_in_rename_range
18112 .cmp(&cursor_offset_in_rename_range_end)
18113 {
18114 Ordering::Equal => {
18115 editor.select_all(&SelectAll, window, cx);
18116 return editor;
18117 }
18118 Ordering::Less => {
18119 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18120 }
18121 Ordering::Greater => {
18122 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18123 }
18124 };
18125 if rename_selection_range.end.0 > old_name.len() {
18126 editor.select_all(&SelectAll, window, cx);
18127 } else {
18128 editor.change_selections(Default::default(), window, cx, |s| {
18129 s.select_ranges([rename_selection_range]);
18130 });
18131 }
18132 editor
18133 });
18134 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18135 if e == &EditorEvent::Focused {
18136 cx.emit(EditorEvent::FocusedIn)
18137 }
18138 })
18139 .detach();
18140
18141 let write_highlights =
18142 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18143 let read_highlights =
18144 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18145 let ranges = write_highlights
18146 .iter()
18147 .flat_map(|(_, ranges)| ranges.iter())
18148 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18149 .cloned()
18150 .collect();
18151
18152 this.highlight_text::<Rename>(
18153 ranges,
18154 HighlightStyle {
18155 fade_out: Some(0.6),
18156 ..Default::default()
18157 },
18158 cx,
18159 );
18160 let rename_focus_handle = rename_editor.focus_handle(cx);
18161 window.focus(&rename_focus_handle);
18162 let block_id = this.insert_blocks(
18163 [BlockProperties {
18164 style: BlockStyle::Flex,
18165 placement: BlockPlacement::Below(range.start),
18166 height: Some(1),
18167 render: Arc::new({
18168 let rename_editor = rename_editor.clone();
18169 move |cx: &mut BlockContext| {
18170 let mut text_style = cx.editor_style.text.clone();
18171 if let Some(highlight_style) = old_highlight_id
18172 .and_then(|h| h.style(&cx.editor_style.syntax))
18173 {
18174 text_style = text_style.highlight(highlight_style);
18175 }
18176 div()
18177 .block_mouse_except_scroll()
18178 .pl(cx.anchor_x)
18179 .child(EditorElement::new(
18180 &rename_editor,
18181 EditorStyle {
18182 background: cx.theme().system().transparent,
18183 local_player: cx.editor_style.local_player,
18184 text: text_style,
18185 scrollbar_width: cx.editor_style.scrollbar_width,
18186 syntax: cx.editor_style.syntax.clone(),
18187 status: cx.editor_style.status.clone(),
18188 inlay_hints_style: HighlightStyle {
18189 font_weight: Some(FontWeight::BOLD),
18190 ..make_inlay_hints_style(cx.app)
18191 },
18192 edit_prediction_styles: make_suggestion_styles(
18193 cx.app,
18194 ),
18195 ..EditorStyle::default()
18196 },
18197 ))
18198 .into_any_element()
18199 }
18200 }),
18201 priority: 0,
18202 }],
18203 Some(Autoscroll::fit()),
18204 cx,
18205 )[0];
18206 this.pending_rename = Some(RenameState {
18207 range,
18208 old_name,
18209 editor: rename_editor,
18210 block_id,
18211 });
18212 })?;
18213 }
18214
18215 Ok(())
18216 }))
18217 }
18218
18219 pub fn confirm_rename(
18220 &mut self,
18221 _: &ConfirmRename,
18222 window: &mut Window,
18223 cx: &mut Context<Self>,
18224 ) -> Option<Task<Result<()>>> {
18225 let rename = self.take_rename(false, window, cx)?;
18226 let workspace = self.workspace()?.downgrade();
18227 let (buffer, start) = self
18228 .buffer
18229 .read(cx)
18230 .text_anchor_for_position(rename.range.start, cx)?;
18231 let (end_buffer, _) = self
18232 .buffer
18233 .read(cx)
18234 .text_anchor_for_position(rename.range.end, cx)?;
18235 if buffer != end_buffer {
18236 return None;
18237 }
18238
18239 let old_name = rename.old_name;
18240 let new_name = rename.editor.read(cx).text(cx);
18241
18242 let rename = self.semantics_provider.as_ref()?.perform_rename(
18243 &buffer,
18244 start,
18245 new_name.clone(),
18246 cx,
18247 )?;
18248
18249 Some(cx.spawn_in(window, async move |editor, cx| {
18250 let project_transaction = rename.await?;
18251 Self::open_project_transaction(
18252 &editor,
18253 workspace,
18254 project_transaction,
18255 format!("Rename: {} → {}", old_name, new_name),
18256 cx,
18257 )
18258 .await?;
18259
18260 editor.update(cx, |editor, cx| {
18261 editor.refresh_document_highlights(cx);
18262 })?;
18263 Ok(())
18264 }))
18265 }
18266
18267 fn take_rename(
18268 &mut self,
18269 moving_cursor: bool,
18270 window: &mut Window,
18271 cx: &mut Context<Self>,
18272 ) -> Option<RenameState> {
18273 let rename = self.pending_rename.take()?;
18274 if rename.editor.focus_handle(cx).is_focused(window) {
18275 window.focus(&self.focus_handle);
18276 }
18277
18278 self.remove_blocks(
18279 [rename.block_id].into_iter().collect(),
18280 Some(Autoscroll::fit()),
18281 cx,
18282 );
18283 self.clear_highlights::<Rename>(cx);
18284 self.show_local_selections = true;
18285
18286 if moving_cursor {
18287 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18288 editor
18289 .selections
18290 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18291 .head()
18292 });
18293
18294 // Update the selection to match the position of the selection inside
18295 // the rename editor.
18296 let snapshot = self.buffer.read(cx).read(cx);
18297 let rename_range = rename.range.to_offset(&snapshot);
18298 let cursor_in_editor = snapshot
18299 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18300 .min(rename_range.end);
18301 drop(snapshot);
18302
18303 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18304 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18305 });
18306 } else {
18307 self.refresh_document_highlights(cx);
18308 }
18309
18310 Some(rename)
18311 }
18312
18313 pub fn pending_rename(&self) -> Option<&RenameState> {
18314 self.pending_rename.as_ref()
18315 }
18316
18317 fn format(
18318 &mut self,
18319 _: &Format,
18320 window: &mut Window,
18321 cx: &mut Context<Self>,
18322 ) -> Option<Task<Result<()>>> {
18323 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18324
18325 let project = match &self.project {
18326 Some(project) => project.clone(),
18327 None => return None,
18328 };
18329
18330 Some(self.perform_format(
18331 project,
18332 FormatTrigger::Manual,
18333 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18334 window,
18335 cx,
18336 ))
18337 }
18338
18339 fn format_selections(
18340 &mut self,
18341 _: &FormatSelections,
18342 window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) -> Option<Task<Result<()>>> {
18345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18346
18347 let project = match &self.project {
18348 Some(project) => project.clone(),
18349 None => return None,
18350 };
18351
18352 let ranges = self
18353 .selections
18354 .all_adjusted(&self.display_snapshot(cx))
18355 .into_iter()
18356 .map(|selection| selection.range())
18357 .collect_vec();
18358
18359 Some(self.perform_format(
18360 project,
18361 FormatTrigger::Manual,
18362 FormatTarget::Ranges(ranges),
18363 window,
18364 cx,
18365 ))
18366 }
18367
18368 fn perform_format(
18369 &mut self,
18370 project: Entity<Project>,
18371 trigger: FormatTrigger,
18372 target: FormatTarget,
18373 window: &mut Window,
18374 cx: &mut Context<Self>,
18375 ) -> Task<Result<()>> {
18376 let buffer = self.buffer.clone();
18377 let (buffers, target) = match target {
18378 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18379 FormatTarget::Ranges(selection_ranges) => {
18380 let multi_buffer = buffer.read(cx);
18381 let snapshot = multi_buffer.read(cx);
18382 let mut buffers = HashSet::default();
18383 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18384 BTreeMap::new();
18385 for selection_range in selection_ranges {
18386 for (buffer, buffer_range, _) in
18387 snapshot.range_to_buffer_ranges(selection_range)
18388 {
18389 let buffer_id = buffer.remote_id();
18390 let start = buffer.anchor_before(buffer_range.start);
18391 let end = buffer.anchor_after(buffer_range.end);
18392 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18393 buffer_id_to_ranges
18394 .entry(buffer_id)
18395 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18396 .or_insert_with(|| vec![start..end]);
18397 }
18398 }
18399 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18400 }
18401 };
18402
18403 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18404 let selections_prev = transaction_id_prev
18405 .and_then(|transaction_id_prev| {
18406 // default to selections as they were after the last edit, if we have them,
18407 // instead of how they are now.
18408 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18409 // will take you back to where you made the last edit, instead of staying where you scrolled
18410 self.selection_history
18411 .transaction(transaction_id_prev)
18412 .map(|t| t.0.clone())
18413 })
18414 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18415
18416 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18417 let format = project.update(cx, |project, cx| {
18418 project.format(buffers, target, true, trigger, cx)
18419 });
18420
18421 cx.spawn_in(window, async move |editor, cx| {
18422 let transaction = futures::select_biased! {
18423 transaction = format.log_err().fuse() => transaction,
18424 () = timeout => {
18425 log::warn!("timed out waiting for formatting");
18426 None
18427 }
18428 };
18429
18430 buffer
18431 .update(cx, |buffer, cx| {
18432 if let Some(transaction) = transaction
18433 && !buffer.is_singleton()
18434 {
18435 buffer.push_transaction(&transaction.0, cx);
18436 }
18437 cx.notify();
18438 })
18439 .ok();
18440
18441 if let Some(transaction_id_now) =
18442 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18443 {
18444 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18445 if has_new_transaction {
18446 _ = editor.update(cx, |editor, _| {
18447 editor
18448 .selection_history
18449 .insert_transaction(transaction_id_now, selections_prev);
18450 });
18451 }
18452 }
18453
18454 Ok(())
18455 })
18456 }
18457
18458 fn organize_imports(
18459 &mut self,
18460 _: &OrganizeImports,
18461 window: &mut Window,
18462 cx: &mut Context<Self>,
18463 ) -> Option<Task<Result<()>>> {
18464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18465 let project = match &self.project {
18466 Some(project) => project.clone(),
18467 None => return None,
18468 };
18469 Some(self.perform_code_action_kind(
18470 project,
18471 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18472 window,
18473 cx,
18474 ))
18475 }
18476
18477 fn perform_code_action_kind(
18478 &mut self,
18479 project: Entity<Project>,
18480 kind: CodeActionKind,
18481 window: &mut Window,
18482 cx: &mut Context<Self>,
18483 ) -> Task<Result<()>> {
18484 let buffer = self.buffer.clone();
18485 let buffers = buffer.read(cx).all_buffers();
18486 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18487 let apply_action = project.update(cx, |project, cx| {
18488 project.apply_code_action_kind(buffers, kind, true, cx)
18489 });
18490 cx.spawn_in(window, async move |_, cx| {
18491 let transaction = futures::select_biased! {
18492 () = timeout => {
18493 log::warn!("timed out waiting for executing code action");
18494 None
18495 }
18496 transaction = apply_action.log_err().fuse() => transaction,
18497 };
18498 buffer
18499 .update(cx, |buffer, cx| {
18500 // check if we need this
18501 if let Some(transaction) = transaction
18502 && !buffer.is_singleton()
18503 {
18504 buffer.push_transaction(&transaction.0, cx);
18505 }
18506 cx.notify();
18507 })
18508 .ok();
18509 Ok(())
18510 })
18511 }
18512
18513 pub fn restart_language_server(
18514 &mut self,
18515 _: &RestartLanguageServer,
18516 _: &mut Window,
18517 cx: &mut Context<Self>,
18518 ) {
18519 if let Some(project) = self.project.clone() {
18520 self.buffer.update(cx, |multi_buffer, cx| {
18521 project.update(cx, |project, cx| {
18522 project.restart_language_servers_for_buffers(
18523 multi_buffer.all_buffers().into_iter().collect(),
18524 HashSet::default(),
18525 cx,
18526 );
18527 });
18528 })
18529 }
18530 }
18531
18532 pub fn stop_language_server(
18533 &mut self,
18534 _: &StopLanguageServer,
18535 _: &mut Window,
18536 cx: &mut Context<Self>,
18537 ) {
18538 if let Some(project) = self.project.clone() {
18539 self.buffer.update(cx, |multi_buffer, cx| {
18540 project.update(cx, |project, cx| {
18541 project.stop_language_servers_for_buffers(
18542 multi_buffer.all_buffers().into_iter().collect(),
18543 HashSet::default(),
18544 cx,
18545 );
18546 });
18547 });
18548 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18549 }
18550 }
18551
18552 fn cancel_language_server_work(
18553 workspace: &mut Workspace,
18554 _: &actions::CancelLanguageServerWork,
18555 _: &mut Window,
18556 cx: &mut Context<Workspace>,
18557 ) {
18558 let project = workspace.project();
18559 let buffers = workspace
18560 .active_item(cx)
18561 .and_then(|item| item.act_as::<Editor>(cx))
18562 .map_or(HashSet::default(), |editor| {
18563 editor.read(cx).buffer.read(cx).all_buffers()
18564 });
18565 project.update(cx, |project, cx| {
18566 project.cancel_language_server_work_for_buffers(buffers, cx);
18567 });
18568 }
18569
18570 fn show_character_palette(
18571 &mut self,
18572 _: &ShowCharacterPalette,
18573 window: &mut Window,
18574 _: &mut Context<Self>,
18575 ) {
18576 window.show_character_palette();
18577 }
18578
18579 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18580 if !self.diagnostics_enabled() {
18581 return;
18582 }
18583
18584 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18585 let buffer = self.buffer.read(cx).snapshot(cx);
18586 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18587 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18588 let is_valid = buffer
18589 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18590 .any(|entry| {
18591 entry.diagnostic.is_primary
18592 && !entry.range.is_empty()
18593 && entry.range.start == primary_range_start
18594 && entry.diagnostic.message == active_diagnostics.active_message
18595 });
18596
18597 if !is_valid {
18598 self.dismiss_diagnostics(cx);
18599 }
18600 }
18601 }
18602
18603 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18604 match &self.active_diagnostics {
18605 ActiveDiagnostic::Group(group) => Some(group),
18606 _ => None,
18607 }
18608 }
18609
18610 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18611 if !self.diagnostics_enabled() {
18612 return;
18613 }
18614 self.dismiss_diagnostics(cx);
18615 self.active_diagnostics = ActiveDiagnostic::All;
18616 }
18617
18618 fn activate_diagnostics(
18619 &mut self,
18620 buffer_id: BufferId,
18621 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18622 window: &mut Window,
18623 cx: &mut Context<Self>,
18624 ) {
18625 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18626 return;
18627 }
18628 self.dismiss_diagnostics(cx);
18629 let snapshot = self.snapshot(window, cx);
18630 let buffer = self.buffer.read(cx).snapshot(cx);
18631 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18632 return;
18633 };
18634
18635 let diagnostic_group = buffer
18636 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18637 .collect::<Vec<_>>();
18638
18639 let language_registry = self
18640 .project()
18641 .map(|project| project.read(cx).languages().clone());
18642
18643 let blocks = renderer.render_group(
18644 diagnostic_group,
18645 buffer_id,
18646 snapshot,
18647 cx.weak_entity(),
18648 language_registry,
18649 cx,
18650 );
18651
18652 let blocks = self.display_map.update(cx, |display_map, cx| {
18653 display_map.insert_blocks(blocks, cx).into_iter().collect()
18654 });
18655 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18656 active_range: buffer.anchor_before(diagnostic.range.start)
18657 ..buffer.anchor_after(diagnostic.range.end),
18658 active_message: diagnostic.diagnostic.message.clone(),
18659 group_id: diagnostic.diagnostic.group_id,
18660 blocks,
18661 });
18662 cx.notify();
18663 }
18664
18665 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18666 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18667 return;
18668 };
18669
18670 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18671 if let ActiveDiagnostic::Group(group) = prev {
18672 self.display_map.update(cx, |display_map, cx| {
18673 display_map.remove_blocks(group.blocks, cx);
18674 });
18675 cx.notify();
18676 }
18677 }
18678
18679 /// Disable inline diagnostics rendering for this editor.
18680 pub fn disable_inline_diagnostics(&mut self) {
18681 self.inline_diagnostics_enabled = false;
18682 self.inline_diagnostics_update = Task::ready(());
18683 self.inline_diagnostics.clear();
18684 }
18685
18686 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18687 self.diagnostics_enabled = false;
18688 self.dismiss_diagnostics(cx);
18689 self.inline_diagnostics_update = Task::ready(());
18690 self.inline_diagnostics.clear();
18691 }
18692
18693 pub fn disable_word_completions(&mut self) {
18694 self.word_completions_enabled = false;
18695 }
18696
18697 pub fn diagnostics_enabled(&self) -> bool {
18698 self.diagnostics_enabled && self.mode.is_full()
18699 }
18700
18701 pub fn inline_diagnostics_enabled(&self) -> bool {
18702 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18703 }
18704
18705 pub fn show_inline_diagnostics(&self) -> bool {
18706 self.show_inline_diagnostics
18707 }
18708
18709 pub fn toggle_inline_diagnostics(
18710 &mut self,
18711 _: &ToggleInlineDiagnostics,
18712 window: &mut Window,
18713 cx: &mut Context<Editor>,
18714 ) {
18715 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18716 self.refresh_inline_diagnostics(false, window, cx);
18717 }
18718
18719 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18720 self.diagnostics_max_severity = severity;
18721 self.display_map.update(cx, |display_map, _| {
18722 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18723 });
18724 }
18725
18726 pub fn toggle_diagnostics(
18727 &mut self,
18728 _: &ToggleDiagnostics,
18729 window: &mut Window,
18730 cx: &mut Context<Editor>,
18731 ) {
18732 if !self.diagnostics_enabled() {
18733 return;
18734 }
18735
18736 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18737 EditorSettings::get_global(cx)
18738 .diagnostics_max_severity
18739 .filter(|severity| severity != &DiagnosticSeverity::Off)
18740 .unwrap_or(DiagnosticSeverity::Hint)
18741 } else {
18742 DiagnosticSeverity::Off
18743 };
18744 self.set_max_diagnostics_severity(new_severity, cx);
18745 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18746 self.active_diagnostics = ActiveDiagnostic::None;
18747 self.inline_diagnostics_update = Task::ready(());
18748 self.inline_diagnostics.clear();
18749 } else {
18750 self.refresh_inline_diagnostics(false, window, cx);
18751 }
18752
18753 cx.notify();
18754 }
18755
18756 pub fn toggle_minimap(
18757 &mut self,
18758 _: &ToggleMinimap,
18759 window: &mut Window,
18760 cx: &mut Context<Editor>,
18761 ) {
18762 if self.supports_minimap(cx) {
18763 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18764 }
18765 }
18766
18767 fn refresh_inline_diagnostics(
18768 &mut self,
18769 debounce: bool,
18770 window: &mut Window,
18771 cx: &mut Context<Self>,
18772 ) {
18773 let max_severity = ProjectSettings::get_global(cx)
18774 .diagnostics
18775 .inline
18776 .max_severity
18777 .unwrap_or(self.diagnostics_max_severity);
18778
18779 if !self.inline_diagnostics_enabled()
18780 || !self.diagnostics_enabled()
18781 || !self.show_inline_diagnostics
18782 || max_severity == DiagnosticSeverity::Off
18783 {
18784 self.inline_diagnostics_update = Task::ready(());
18785 self.inline_diagnostics.clear();
18786 return;
18787 }
18788
18789 let debounce_ms = ProjectSettings::get_global(cx)
18790 .diagnostics
18791 .inline
18792 .update_debounce_ms;
18793 let debounce = if debounce && debounce_ms > 0 {
18794 Some(Duration::from_millis(debounce_ms))
18795 } else {
18796 None
18797 };
18798 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18799 if let Some(debounce) = debounce {
18800 cx.background_executor().timer(debounce).await;
18801 }
18802 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18803 editor
18804 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18805 .ok()
18806 }) else {
18807 return;
18808 };
18809
18810 let new_inline_diagnostics = cx
18811 .background_spawn(async move {
18812 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18813 for diagnostic_entry in
18814 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18815 {
18816 let message = diagnostic_entry
18817 .diagnostic
18818 .message
18819 .split_once('\n')
18820 .map(|(line, _)| line)
18821 .map(SharedString::new)
18822 .unwrap_or_else(|| {
18823 SharedString::new(&*diagnostic_entry.diagnostic.message)
18824 });
18825 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18826 let (Ok(i) | Err(i)) = inline_diagnostics
18827 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18828 inline_diagnostics.insert(
18829 i,
18830 (
18831 start_anchor,
18832 InlineDiagnostic {
18833 message,
18834 group_id: diagnostic_entry.diagnostic.group_id,
18835 start: diagnostic_entry.range.start.to_point(&snapshot),
18836 is_primary: diagnostic_entry.diagnostic.is_primary,
18837 severity: diagnostic_entry.diagnostic.severity,
18838 },
18839 ),
18840 );
18841 }
18842 inline_diagnostics
18843 })
18844 .await;
18845
18846 editor
18847 .update(cx, |editor, cx| {
18848 editor.inline_diagnostics = new_inline_diagnostics;
18849 cx.notify();
18850 })
18851 .ok();
18852 });
18853 }
18854
18855 fn pull_diagnostics(
18856 &mut self,
18857 buffer_id: Option<BufferId>,
18858 window: &Window,
18859 cx: &mut Context<Self>,
18860 ) -> Option<()> {
18861 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18862 return None;
18863 }
18864 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18865 .diagnostics
18866 .lsp_pull_diagnostics;
18867 if !pull_diagnostics_settings.enabled {
18868 return None;
18869 }
18870 let project = self.project()?.downgrade();
18871
18872 let mut edited_buffer_ids = HashSet::default();
18873 let mut edited_worktree_ids = HashSet::default();
18874 let edited_buffers = match buffer_id {
18875 Some(buffer_id) => {
18876 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18877 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18878 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18879 edited_worktree_ids.insert(worktree_id);
18880 vec![buffer]
18881 }
18882 None => self
18883 .buffer()
18884 .read(cx)
18885 .all_buffers()
18886 .into_iter()
18887 .filter(|buffer| {
18888 let buffer = buffer.read(cx);
18889 match buffer.file().map(|f| f.worktree_id(cx)) {
18890 Some(worktree_id) => {
18891 edited_buffer_ids.insert(buffer.remote_id());
18892 edited_worktree_ids.insert(worktree_id);
18893 true
18894 }
18895 None => false,
18896 }
18897 })
18898 .collect::<Vec<_>>(),
18899 };
18900
18901 if edited_buffers.is_empty() {
18902 self.pull_diagnostics_task = Task::ready(());
18903 self.pull_diagnostics_background_task = Task::ready(());
18904 return None;
18905 }
18906
18907 let mut already_used_buffers = HashSet::default();
18908 let related_open_buffers = self
18909 .workspace
18910 .as_ref()
18911 .and_then(|(workspace, _)| workspace.upgrade())
18912 .into_iter()
18913 .flat_map(|workspace| workspace.read(cx).panes())
18914 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18915 .filter(|editor| editor != &cx.entity())
18916 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18917 .filter(|buffer| {
18918 let buffer = buffer.read(cx);
18919 let buffer_id = buffer.remote_id();
18920 if already_used_buffers.insert(buffer_id) {
18921 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18922 return !edited_buffer_ids.contains(&buffer_id)
18923 && !edited_worktree_ids.contains(&worktree_id);
18924 }
18925 }
18926 false
18927 })
18928 .collect::<Vec<_>>();
18929
18930 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18931 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18932 if buffers.is_empty() {
18933 return Task::ready(());
18934 }
18935 let project_weak = project.clone();
18936 cx.spawn_in(window, async move |_, cx| {
18937 cx.background_executor().timer(delay).await;
18938
18939 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18940 buffers
18941 .into_iter()
18942 .filter_map(|buffer| {
18943 project_weak
18944 .update(cx, |project, cx| {
18945 project.lsp_store().update(cx, |lsp_store, cx| {
18946 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18947 })
18948 })
18949 .ok()
18950 })
18951 .collect::<FuturesUnordered<_>>()
18952 }) else {
18953 return;
18954 };
18955
18956 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18957 if let Err(e) = pull_task {
18958 log::error!("Failed to update project diagnostics: {e:#}");
18959 }
18960 }
18961 })
18962 };
18963
18964 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18965 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18966
18967 Some(())
18968 }
18969
18970 pub fn set_selections_from_remote(
18971 &mut self,
18972 selections: Vec<Selection<Anchor>>,
18973 pending_selection: Option<Selection<Anchor>>,
18974 window: &mut Window,
18975 cx: &mut Context<Self>,
18976 ) {
18977 let old_cursor_position = self.selections.newest_anchor().head();
18978 self.selections
18979 .change_with(&self.display_snapshot(cx), |s| {
18980 s.select_anchors(selections);
18981 if let Some(pending_selection) = pending_selection {
18982 s.set_pending(pending_selection, SelectMode::Character);
18983 } else {
18984 s.clear_pending();
18985 }
18986 });
18987 self.selections_did_change(
18988 false,
18989 &old_cursor_position,
18990 SelectionEffects::default(),
18991 window,
18992 cx,
18993 );
18994 }
18995
18996 pub fn transact(
18997 &mut self,
18998 window: &mut Window,
18999 cx: &mut Context<Self>,
19000 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
19001 ) -> Option<TransactionId> {
19002 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
19003 this.start_transaction_at(Instant::now(), window, cx);
19004 update(this, window, cx);
19005 this.end_transaction_at(Instant::now(), cx)
19006 })
19007 }
19008
19009 pub fn start_transaction_at(
19010 &mut self,
19011 now: Instant,
19012 window: &mut Window,
19013 cx: &mut Context<Self>,
19014 ) -> Option<TransactionId> {
19015 self.end_selection(window, cx);
19016 if let Some(tx_id) = self
19017 .buffer
19018 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19019 {
19020 self.selection_history
19021 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19022 cx.emit(EditorEvent::TransactionBegun {
19023 transaction_id: tx_id,
19024 });
19025 Some(tx_id)
19026 } else {
19027 None
19028 }
19029 }
19030
19031 pub fn end_transaction_at(
19032 &mut self,
19033 now: Instant,
19034 cx: &mut Context<Self>,
19035 ) -> Option<TransactionId> {
19036 if let Some(transaction_id) = self
19037 .buffer
19038 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19039 {
19040 if let Some((_, end_selections)) =
19041 self.selection_history.transaction_mut(transaction_id)
19042 {
19043 *end_selections = Some(self.selections.disjoint_anchors_arc());
19044 } else {
19045 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19046 }
19047
19048 cx.emit(EditorEvent::Edited { transaction_id });
19049 Some(transaction_id)
19050 } else {
19051 None
19052 }
19053 }
19054
19055 pub fn modify_transaction_selection_history(
19056 &mut self,
19057 transaction_id: TransactionId,
19058 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19059 ) -> bool {
19060 self.selection_history
19061 .transaction_mut(transaction_id)
19062 .map(modify)
19063 .is_some()
19064 }
19065
19066 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19067 if self.selection_mark_mode {
19068 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19069 s.move_with(|_, sel| {
19070 sel.collapse_to(sel.head(), SelectionGoal::None);
19071 });
19072 })
19073 }
19074 self.selection_mark_mode = true;
19075 cx.notify();
19076 }
19077
19078 pub fn swap_selection_ends(
19079 &mut self,
19080 _: &actions::SwapSelectionEnds,
19081 window: &mut Window,
19082 cx: &mut Context<Self>,
19083 ) {
19084 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19085 s.move_with(|_, sel| {
19086 if sel.start != sel.end {
19087 sel.reversed = !sel.reversed
19088 }
19089 });
19090 });
19091 self.request_autoscroll(Autoscroll::newest(), cx);
19092 cx.notify();
19093 }
19094
19095 pub fn toggle_focus(
19096 workspace: &mut Workspace,
19097 _: &actions::ToggleFocus,
19098 window: &mut Window,
19099 cx: &mut Context<Workspace>,
19100 ) {
19101 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19102 return;
19103 };
19104 workspace.activate_item(&item, true, true, window, cx);
19105 }
19106
19107 pub fn toggle_fold(
19108 &mut self,
19109 _: &actions::ToggleFold,
19110 window: &mut Window,
19111 cx: &mut Context<Self>,
19112 ) {
19113 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19114 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19115 let selection = self.selections.newest::<Point>(&display_map);
19116
19117 let range = if selection.is_empty() {
19118 let point = selection.head().to_display_point(&display_map);
19119 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19120 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19121 .to_point(&display_map);
19122 start..end
19123 } else {
19124 selection.range()
19125 };
19126 if display_map.folds_in_range(range).next().is_some() {
19127 self.unfold_lines(&Default::default(), window, cx)
19128 } else {
19129 self.fold(&Default::default(), window, cx)
19130 }
19131 } else {
19132 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19133 let buffer_ids: HashSet<_> = self
19134 .selections
19135 .disjoint_anchor_ranges()
19136 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19137 .collect();
19138
19139 let should_unfold = buffer_ids
19140 .iter()
19141 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19142
19143 for buffer_id in buffer_ids {
19144 if should_unfold {
19145 self.unfold_buffer(buffer_id, cx);
19146 } else {
19147 self.fold_buffer(buffer_id, cx);
19148 }
19149 }
19150 }
19151 }
19152
19153 pub fn toggle_fold_recursive(
19154 &mut self,
19155 _: &actions::ToggleFoldRecursive,
19156 window: &mut Window,
19157 cx: &mut Context<Self>,
19158 ) {
19159 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19160
19161 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19162 let range = if selection.is_empty() {
19163 let point = selection.head().to_display_point(&display_map);
19164 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19165 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19166 .to_point(&display_map);
19167 start..end
19168 } else {
19169 selection.range()
19170 };
19171 if display_map.folds_in_range(range).next().is_some() {
19172 self.unfold_recursive(&Default::default(), window, cx)
19173 } else {
19174 self.fold_recursive(&Default::default(), window, cx)
19175 }
19176 }
19177
19178 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19179 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19180 let mut to_fold = Vec::new();
19181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19182 let selections = self.selections.all_adjusted(&display_map);
19183
19184 for selection in selections {
19185 let range = selection.range().sorted();
19186 let buffer_start_row = range.start.row;
19187
19188 if range.start.row != range.end.row {
19189 let mut found = false;
19190 let mut row = range.start.row;
19191 while row <= range.end.row {
19192 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19193 {
19194 found = true;
19195 row = crease.range().end.row + 1;
19196 to_fold.push(crease);
19197 } else {
19198 row += 1
19199 }
19200 }
19201 if found {
19202 continue;
19203 }
19204 }
19205
19206 for row in (0..=range.start.row).rev() {
19207 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19208 && crease.range().end.row >= buffer_start_row
19209 {
19210 to_fold.push(crease);
19211 if row <= range.start.row {
19212 break;
19213 }
19214 }
19215 }
19216 }
19217
19218 self.fold_creases(to_fold, true, window, cx);
19219 } else {
19220 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19221 let buffer_ids = self
19222 .selections
19223 .disjoint_anchor_ranges()
19224 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19225 .collect::<HashSet<_>>();
19226 for buffer_id in buffer_ids {
19227 self.fold_buffer(buffer_id, cx);
19228 }
19229 }
19230 }
19231
19232 pub fn toggle_fold_all(
19233 &mut self,
19234 _: &actions::ToggleFoldAll,
19235 window: &mut Window,
19236 cx: &mut Context<Self>,
19237 ) {
19238 if self.buffer.read(cx).is_singleton() {
19239 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19240 let has_folds = display_map
19241 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19242 .next()
19243 .is_some();
19244
19245 if has_folds {
19246 self.unfold_all(&actions::UnfoldAll, window, cx);
19247 } else {
19248 self.fold_all(&actions::FoldAll, window, cx);
19249 }
19250 } else {
19251 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19252 let should_unfold = buffer_ids
19253 .iter()
19254 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19255
19256 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19257 editor
19258 .update_in(cx, |editor, _, cx| {
19259 for buffer_id in buffer_ids {
19260 if should_unfold {
19261 editor.unfold_buffer(buffer_id, cx);
19262 } else {
19263 editor.fold_buffer(buffer_id, cx);
19264 }
19265 }
19266 })
19267 .ok();
19268 });
19269 }
19270 }
19271
19272 fn fold_at_level(
19273 &mut self,
19274 fold_at: &FoldAtLevel,
19275 window: &mut Window,
19276 cx: &mut Context<Self>,
19277 ) {
19278 if !self.buffer.read(cx).is_singleton() {
19279 return;
19280 }
19281
19282 let fold_at_level = fold_at.0;
19283 let snapshot = self.buffer.read(cx).snapshot(cx);
19284 let mut to_fold = Vec::new();
19285 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19286
19287 let row_ranges_to_keep: Vec<Range<u32>> = self
19288 .selections
19289 .all::<Point>(&self.display_snapshot(cx))
19290 .into_iter()
19291 .map(|sel| sel.start.row..sel.end.row)
19292 .collect();
19293
19294 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19295 while start_row < end_row {
19296 match self
19297 .snapshot(window, cx)
19298 .crease_for_buffer_row(MultiBufferRow(start_row))
19299 {
19300 Some(crease) => {
19301 let nested_start_row = crease.range().start.row + 1;
19302 let nested_end_row = crease.range().end.row;
19303
19304 if current_level < fold_at_level {
19305 stack.push((nested_start_row, nested_end_row, current_level + 1));
19306 } else if current_level == fold_at_level {
19307 // Fold iff there is no selection completely contained within the fold region
19308 if !row_ranges_to_keep.iter().any(|selection| {
19309 selection.end >= nested_start_row
19310 && selection.start <= nested_end_row
19311 }) {
19312 to_fold.push(crease);
19313 }
19314 }
19315
19316 start_row = nested_end_row + 1;
19317 }
19318 None => start_row += 1,
19319 }
19320 }
19321 }
19322
19323 self.fold_creases(to_fold, true, window, cx);
19324 }
19325
19326 pub fn fold_at_level_1(
19327 &mut self,
19328 _: &actions::FoldAtLevel1,
19329 window: &mut Window,
19330 cx: &mut Context<Self>,
19331 ) {
19332 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19333 }
19334
19335 pub fn fold_at_level_2(
19336 &mut self,
19337 _: &actions::FoldAtLevel2,
19338 window: &mut Window,
19339 cx: &mut Context<Self>,
19340 ) {
19341 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19342 }
19343
19344 pub fn fold_at_level_3(
19345 &mut self,
19346 _: &actions::FoldAtLevel3,
19347 window: &mut Window,
19348 cx: &mut Context<Self>,
19349 ) {
19350 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19351 }
19352
19353 pub fn fold_at_level_4(
19354 &mut self,
19355 _: &actions::FoldAtLevel4,
19356 window: &mut Window,
19357 cx: &mut Context<Self>,
19358 ) {
19359 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19360 }
19361
19362 pub fn fold_at_level_5(
19363 &mut self,
19364 _: &actions::FoldAtLevel5,
19365 window: &mut Window,
19366 cx: &mut Context<Self>,
19367 ) {
19368 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19369 }
19370
19371 pub fn fold_at_level_6(
19372 &mut self,
19373 _: &actions::FoldAtLevel6,
19374 window: &mut Window,
19375 cx: &mut Context<Self>,
19376 ) {
19377 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19378 }
19379
19380 pub fn fold_at_level_7(
19381 &mut self,
19382 _: &actions::FoldAtLevel7,
19383 window: &mut Window,
19384 cx: &mut Context<Self>,
19385 ) {
19386 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19387 }
19388
19389 pub fn fold_at_level_8(
19390 &mut self,
19391 _: &actions::FoldAtLevel8,
19392 window: &mut Window,
19393 cx: &mut Context<Self>,
19394 ) {
19395 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19396 }
19397
19398 pub fn fold_at_level_9(
19399 &mut self,
19400 _: &actions::FoldAtLevel9,
19401 window: &mut Window,
19402 cx: &mut Context<Self>,
19403 ) {
19404 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19405 }
19406
19407 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19408 if self.buffer.read(cx).is_singleton() {
19409 let mut fold_ranges = Vec::new();
19410 let snapshot = self.buffer.read(cx).snapshot(cx);
19411
19412 for row in 0..snapshot.max_row().0 {
19413 if let Some(foldable_range) = self
19414 .snapshot(window, cx)
19415 .crease_for_buffer_row(MultiBufferRow(row))
19416 {
19417 fold_ranges.push(foldable_range);
19418 }
19419 }
19420
19421 self.fold_creases(fold_ranges, true, window, cx);
19422 } else {
19423 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19424 editor
19425 .update_in(cx, |editor, _, cx| {
19426 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19427 editor.fold_buffer(buffer_id, cx);
19428 }
19429 })
19430 .ok();
19431 });
19432 }
19433 }
19434
19435 pub fn fold_function_bodies(
19436 &mut self,
19437 _: &actions::FoldFunctionBodies,
19438 window: &mut Window,
19439 cx: &mut Context<Self>,
19440 ) {
19441 let snapshot = self.buffer.read(cx).snapshot(cx);
19442
19443 let ranges = snapshot
19444 .text_object_ranges(
19445 MultiBufferOffset(0)..snapshot.len(),
19446 TreeSitterOptions::default(),
19447 )
19448 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19449 .collect::<Vec<_>>();
19450
19451 let creases = ranges
19452 .into_iter()
19453 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19454 .collect();
19455
19456 self.fold_creases(creases, true, window, cx);
19457 }
19458
19459 pub fn fold_recursive(
19460 &mut self,
19461 _: &actions::FoldRecursive,
19462 window: &mut Window,
19463 cx: &mut Context<Self>,
19464 ) {
19465 let mut to_fold = Vec::new();
19466 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19467 let selections = self.selections.all_adjusted(&display_map);
19468
19469 for selection in selections {
19470 let range = selection.range().sorted();
19471 let buffer_start_row = range.start.row;
19472
19473 if range.start.row != range.end.row {
19474 let mut found = false;
19475 for row in range.start.row..=range.end.row {
19476 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19477 found = true;
19478 to_fold.push(crease);
19479 }
19480 }
19481 if found {
19482 continue;
19483 }
19484 }
19485
19486 for row in (0..=range.start.row).rev() {
19487 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19488 if crease.range().end.row >= buffer_start_row {
19489 to_fold.push(crease);
19490 } else {
19491 break;
19492 }
19493 }
19494 }
19495 }
19496
19497 self.fold_creases(to_fold, true, window, cx);
19498 }
19499
19500 pub fn fold_at(
19501 &mut self,
19502 buffer_row: MultiBufferRow,
19503 window: &mut Window,
19504 cx: &mut Context<Self>,
19505 ) {
19506 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19507
19508 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19509 let autoscroll = self
19510 .selections
19511 .all::<Point>(&display_map)
19512 .iter()
19513 .any(|selection| crease.range().overlaps(&selection.range()));
19514
19515 self.fold_creases(vec![crease], autoscroll, window, cx);
19516 }
19517 }
19518
19519 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19520 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19522 let buffer = display_map.buffer_snapshot();
19523 let selections = self.selections.all::<Point>(&display_map);
19524 let ranges = selections
19525 .iter()
19526 .map(|s| {
19527 let range = s.display_range(&display_map).sorted();
19528 let mut start = range.start.to_point(&display_map);
19529 let mut end = range.end.to_point(&display_map);
19530 start.column = 0;
19531 end.column = buffer.line_len(MultiBufferRow(end.row));
19532 start..end
19533 })
19534 .collect::<Vec<_>>();
19535
19536 self.unfold_ranges(&ranges, true, true, cx);
19537 } else {
19538 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19539 let buffer_ids = self
19540 .selections
19541 .disjoint_anchor_ranges()
19542 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19543 .collect::<HashSet<_>>();
19544 for buffer_id in buffer_ids {
19545 self.unfold_buffer(buffer_id, cx);
19546 }
19547 }
19548 }
19549
19550 pub fn unfold_recursive(
19551 &mut self,
19552 _: &UnfoldRecursive,
19553 _window: &mut Window,
19554 cx: &mut Context<Self>,
19555 ) {
19556 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19557 let selections = self.selections.all::<Point>(&display_map);
19558 let ranges = selections
19559 .iter()
19560 .map(|s| {
19561 let mut range = s.display_range(&display_map).sorted();
19562 *range.start.column_mut() = 0;
19563 *range.end.column_mut() = display_map.line_len(range.end.row());
19564 let start = range.start.to_point(&display_map);
19565 let end = range.end.to_point(&display_map);
19566 start..end
19567 })
19568 .collect::<Vec<_>>();
19569
19570 self.unfold_ranges(&ranges, true, true, cx);
19571 }
19572
19573 pub fn unfold_at(
19574 &mut self,
19575 buffer_row: MultiBufferRow,
19576 _window: &mut Window,
19577 cx: &mut Context<Self>,
19578 ) {
19579 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19580
19581 let intersection_range = Point::new(buffer_row.0, 0)
19582 ..Point::new(
19583 buffer_row.0,
19584 display_map.buffer_snapshot().line_len(buffer_row),
19585 );
19586
19587 let autoscroll = self
19588 .selections
19589 .all::<Point>(&display_map)
19590 .iter()
19591 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19592
19593 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19594 }
19595
19596 pub fn unfold_all(
19597 &mut self,
19598 _: &actions::UnfoldAll,
19599 _window: &mut Window,
19600 cx: &mut Context<Self>,
19601 ) {
19602 if self.buffer.read(cx).is_singleton() {
19603 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19604 self.unfold_ranges(
19605 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19606 true,
19607 true,
19608 cx,
19609 );
19610 } else {
19611 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19612 editor
19613 .update(cx, |editor, cx| {
19614 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19615 editor.unfold_buffer(buffer_id, cx);
19616 }
19617 })
19618 .ok();
19619 });
19620 }
19621 }
19622
19623 pub fn fold_selected_ranges(
19624 &mut self,
19625 _: &FoldSelectedRanges,
19626 window: &mut Window,
19627 cx: &mut Context<Self>,
19628 ) {
19629 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19630 let selections = self.selections.all_adjusted(&display_map);
19631 let ranges = selections
19632 .into_iter()
19633 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19634 .collect::<Vec<_>>();
19635 self.fold_creases(ranges, true, window, cx);
19636 }
19637
19638 pub fn fold_ranges<T: ToOffset + Clone>(
19639 &mut self,
19640 ranges: Vec<Range<T>>,
19641 auto_scroll: bool,
19642 window: &mut Window,
19643 cx: &mut Context<Self>,
19644 ) {
19645 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19646 let ranges = ranges
19647 .into_iter()
19648 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19649 .collect::<Vec<_>>();
19650 self.fold_creases(ranges, auto_scroll, window, cx);
19651 }
19652
19653 pub fn fold_creases<T: ToOffset + Clone>(
19654 &mut self,
19655 creases: Vec<Crease<T>>,
19656 auto_scroll: bool,
19657 _window: &mut Window,
19658 cx: &mut Context<Self>,
19659 ) {
19660 if creases.is_empty() {
19661 return;
19662 }
19663
19664 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19665
19666 if auto_scroll {
19667 self.request_autoscroll(Autoscroll::fit(), cx);
19668 }
19669
19670 cx.notify();
19671
19672 self.scrollbar_marker_state.dirty = true;
19673 self.folds_did_change(cx);
19674 }
19675
19676 /// Removes any folds whose ranges intersect any of the given ranges.
19677 pub fn unfold_ranges<T: ToOffset + Clone>(
19678 &mut self,
19679 ranges: &[Range<T>],
19680 inclusive: bool,
19681 auto_scroll: bool,
19682 cx: &mut Context<Self>,
19683 ) {
19684 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19685 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19686 });
19687 self.folds_did_change(cx);
19688 }
19689
19690 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19691 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19692 return;
19693 }
19694
19695 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19696 self.display_map.update(cx, |display_map, cx| {
19697 display_map.fold_buffers([buffer_id], cx)
19698 });
19699
19700 let snapshot = self.display_snapshot(cx);
19701 self.selections.change_with(&snapshot, |selections| {
19702 selections.remove_selections_from_buffer(buffer_id);
19703 });
19704
19705 cx.emit(EditorEvent::BufferFoldToggled {
19706 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19707 folded: true,
19708 });
19709 cx.notify();
19710 }
19711
19712 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19713 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19714 return;
19715 }
19716 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19717 self.display_map.update(cx, |display_map, cx| {
19718 display_map.unfold_buffers([buffer_id], cx);
19719 });
19720 cx.emit(EditorEvent::BufferFoldToggled {
19721 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19722 folded: false,
19723 });
19724 cx.notify();
19725 }
19726
19727 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19728 self.display_map.read(cx).is_buffer_folded(buffer)
19729 }
19730
19731 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19732 self.display_map.read(cx).folded_buffers()
19733 }
19734
19735 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19736 self.display_map.update(cx, |display_map, cx| {
19737 display_map.disable_header_for_buffer(buffer_id, cx);
19738 });
19739 cx.notify();
19740 }
19741
19742 /// Removes any folds with the given ranges.
19743 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19744 &mut self,
19745 ranges: &[Range<T>],
19746 type_id: TypeId,
19747 auto_scroll: bool,
19748 cx: &mut Context<Self>,
19749 ) {
19750 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19751 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19752 });
19753 self.folds_did_change(cx);
19754 }
19755
19756 fn remove_folds_with<T: ToOffset + Clone>(
19757 &mut self,
19758 ranges: &[Range<T>],
19759 auto_scroll: bool,
19760 cx: &mut Context<Self>,
19761 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19762 ) {
19763 if ranges.is_empty() {
19764 return;
19765 }
19766
19767 let mut buffers_affected = HashSet::default();
19768 let multi_buffer = self.buffer().read(cx);
19769 for range in ranges {
19770 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19771 buffers_affected.insert(buffer.read(cx).remote_id());
19772 };
19773 }
19774
19775 self.display_map.update(cx, update);
19776
19777 if auto_scroll {
19778 self.request_autoscroll(Autoscroll::fit(), cx);
19779 }
19780
19781 cx.notify();
19782 self.scrollbar_marker_state.dirty = true;
19783 self.active_indent_guides_state.dirty = true;
19784 }
19785
19786 pub fn update_renderer_widths(
19787 &mut self,
19788 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19789 cx: &mut Context<Self>,
19790 ) -> bool {
19791 self.display_map
19792 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19793 }
19794
19795 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19796 self.display_map.read(cx).fold_placeholder.clone()
19797 }
19798
19799 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19800 self.use_base_text_line_numbers = show;
19801 }
19802
19803 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19804 self.buffer.update(cx, |buffer, cx| {
19805 buffer.set_all_diff_hunks_expanded(cx);
19806 });
19807 }
19808
19809 pub fn expand_all_diff_hunks(
19810 &mut self,
19811 _: &ExpandAllDiffHunks,
19812 _window: &mut Window,
19813 cx: &mut Context<Self>,
19814 ) {
19815 self.buffer.update(cx, |buffer, cx| {
19816 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19817 });
19818 }
19819
19820 pub fn collapse_all_diff_hunks(
19821 &mut self,
19822 _: &CollapseAllDiffHunks,
19823 _window: &mut Window,
19824 cx: &mut Context<Self>,
19825 ) {
19826 self.buffer.update(cx, |buffer, cx| {
19827 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19828 });
19829 }
19830
19831 pub fn toggle_selected_diff_hunks(
19832 &mut self,
19833 _: &ToggleSelectedDiffHunks,
19834 _window: &mut Window,
19835 cx: &mut Context<Self>,
19836 ) {
19837 let ranges: Vec<_> = self
19838 .selections
19839 .disjoint_anchors()
19840 .iter()
19841 .map(|s| s.range())
19842 .collect();
19843 self.toggle_diff_hunks_in_ranges(ranges, cx);
19844 }
19845
19846 pub fn diff_hunks_in_ranges<'a>(
19847 &'a self,
19848 ranges: &'a [Range<Anchor>],
19849 buffer: &'a MultiBufferSnapshot,
19850 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19851 ranges.iter().flat_map(move |range| {
19852 let end_excerpt_id = range.end.excerpt_id;
19853 let range = range.to_point(buffer);
19854 let mut peek_end = range.end;
19855 if range.end.row < buffer.max_row().0 {
19856 peek_end = Point::new(range.end.row + 1, 0);
19857 }
19858 buffer
19859 .diff_hunks_in_range(range.start..peek_end)
19860 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19861 })
19862 }
19863
19864 pub fn has_stageable_diff_hunks_in_ranges(
19865 &self,
19866 ranges: &[Range<Anchor>],
19867 snapshot: &MultiBufferSnapshot,
19868 ) -> bool {
19869 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19870 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19871 }
19872
19873 pub fn toggle_staged_selected_diff_hunks(
19874 &mut self,
19875 _: &::git::ToggleStaged,
19876 _: &mut Window,
19877 cx: &mut Context<Self>,
19878 ) {
19879 let snapshot = self.buffer.read(cx).snapshot(cx);
19880 let ranges: Vec<_> = self
19881 .selections
19882 .disjoint_anchors()
19883 .iter()
19884 .map(|s| s.range())
19885 .collect();
19886 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19887 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19888 }
19889
19890 pub fn set_render_diff_hunk_controls(
19891 &mut self,
19892 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19893 cx: &mut Context<Self>,
19894 ) {
19895 self.render_diff_hunk_controls = render_diff_hunk_controls;
19896 cx.notify();
19897 }
19898
19899 pub fn stage_and_next(
19900 &mut self,
19901 _: &::git::StageAndNext,
19902 window: &mut Window,
19903 cx: &mut Context<Self>,
19904 ) {
19905 self.do_stage_or_unstage_and_next(true, window, cx);
19906 }
19907
19908 pub fn unstage_and_next(
19909 &mut self,
19910 _: &::git::UnstageAndNext,
19911 window: &mut Window,
19912 cx: &mut Context<Self>,
19913 ) {
19914 self.do_stage_or_unstage_and_next(false, window, cx);
19915 }
19916
19917 pub fn stage_or_unstage_diff_hunks(
19918 &mut self,
19919 stage: bool,
19920 ranges: Vec<Range<Anchor>>,
19921 cx: &mut Context<Self>,
19922 ) {
19923 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19924 cx.spawn(async move |this, cx| {
19925 task.await?;
19926 this.update(cx, |this, cx| {
19927 let snapshot = this.buffer.read(cx).snapshot(cx);
19928 let chunk_by = this
19929 .diff_hunks_in_ranges(&ranges, &snapshot)
19930 .chunk_by(|hunk| hunk.buffer_id);
19931 for (buffer_id, hunks) in &chunk_by {
19932 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19933 }
19934 })
19935 })
19936 .detach_and_log_err(cx);
19937 }
19938
19939 fn save_buffers_for_ranges_if_needed(
19940 &mut self,
19941 ranges: &[Range<Anchor>],
19942 cx: &mut Context<Editor>,
19943 ) -> Task<Result<()>> {
19944 let multibuffer = self.buffer.read(cx);
19945 let snapshot = multibuffer.read(cx);
19946 let buffer_ids: HashSet<_> = ranges
19947 .iter()
19948 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19949 .collect();
19950 drop(snapshot);
19951
19952 let mut buffers = HashSet::default();
19953 for buffer_id in buffer_ids {
19954 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19955 let buffer = buffer_entity.read(cx);
19956 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19957 {
19958 buffers.insert(buffer_entity);
19959 }
19960 }
19961 }
19962
19963 if let Some(project) = &self.project {
19964 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19965 } else {
19966 Task::ready(Ok(()))
19967 }
19968 }
19969
19970 fn do_stage_or_unstage_and_next(
19971 &mut self,
19972 stage: bool,
19973 window: &mut Window,
19974 cx: &mut Context<Self>,
19975 ) {
19976 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19977
19978 if ranges.iter().any(|range| range.start != range.end) {
19979 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19980 return;
19981 }
19982
19983 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19984 let snapshot = self.snapshot(window, cx);
19985 let position = self
19986 .selections
19987 .newest::<Point>(&snapshot.display_snapshot)
19988 .head();
19989 let mut row = snapshot
19990 .buffer_snapshot()
19991 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19992 .find(|hunk| hunk.row_range.start.0 > position.row)
19993 .map(|hunk| hunk.row_range.start);
19994
19995 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19996 // Outside of the project diff editor, wrap around to the beginning.
19997 if !all_diff_hunks_expanded {
19998 row = row.or_else(|| {
19999 snapshot
20000 .buffer_snapshot()
20001 .diff_hunks_in_range(Point::zero()..position)
20002 .find(|hunk| hunk.row_range.end.0 < position.row)
20003 .map(|hunk| hunk.row_range.start)
20004 });
20005 }
20006
20007 if let Some(row) = row {
20008 let destination = Point::new(row.0, 0);
20009 let autoscroll = Autoscroll::center();
20010
20011 self.unfold_ranges(&[destination..destination], false, false, cx);
20012 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
20013 s.select_ranges([destination..destination]);
20014 });
20015 }
20016 }
20017
20018 fn do_stage_or_unstage(
20019 &self,
20020 stage: bool,
20021 buffer_id: BufferId,
20022 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20023 cx: &mut App,
20024 ) -> Option<()> {
20025 let project = self.project()?;
20026 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20027 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20028 let buffer_snapshot = buffer.read(cx).snapshot();
20029 let file_exists = buffer_snapshot
20030 .file()
20031 .is_some_and(|file| file.disk_state().exists());
20032 diff.update(cx, |diff, cx| {
20033 diff.stage_or_unstage_hunks(
20034 stage,
20035 &hunks
20036 .map(|hunk| buffer_diff::DiffHunk {
20037 buffer_range: hunk.buffer_range,
20038 // We don't need to pass in word diffs here because they're only used for rendering and
20039 // this function changes internal state
20040 base_word_diffs: Vec::default(),
20041 buffer_word_diffs: Vec::default(),
20042 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20043 ..hunk.diff_base_byte_range.end.0,
20044 secondary_status: hunk.secondary_status,
20045 range: Point::zero()..Point::zero(), // unused
20046 })
20047 .collect::<Vec<_>>(),
20048 &buffer_snapshot,
20049 file_exists,
20050 cx,
20051 )
20052 });
20053 None
20054 }
20055
20056 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20057 let ranges: Vec<_> = self
20058 .selections
20059 .disjoint_anchors()
20060 .iter()
20061 .map(|s| s.range())
20062 .collect();
20063 self.buffer
20064 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20065 }
20066
20067 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20068 self.buffer.update(cx, |buffer, cx| {
20069 let ranges = vec![Anchor::min()..Anchor::max()];
20070 if !buffer.all_diff_hunks_expanded()
20071 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20072 {
20073 buffer.collapse_diff_hunks(ranges, cx);
20074 true
20075 } else {
20076 false
20077 }
20078 })
20079 }
20080
20081 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20082 if self.buffer.read(cx).all_diff_hunks_expanded() {
20083 return true;
20084 }
20085 let ranges = vec![Anchor::min()..Anchor::max()];
20086 self.buffer
20087 .read(cx)
20088 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20089 }
20090
20091 fn toggle_diff_hunks_in_ranges(
20092 &mut self,
20093 ranges: Vec<Range<Anchor>>,
20094 cx: &mut Context<Editor>,
20095 ) {
20096 self.buffer.update(cx, |buffer, cx| {
20097 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20098 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20099 })
20100 }
20101
20102 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20103 self.buffer.update(cx, |buffer, cx| {
20104 let snapshot = buffer.snapshot(cx);
20105 let excerpt_id = range.end.excerpt_id;
20106 let point_range = range.to_point(&snapshot);
20107 let expand = !buffer.single_hunk_is_expanded(range, cx);
20108 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20109 })
20110 }
20111
20112 pub(crate) fn apply_all_diff_hunks(
20113 &mut self,
20114 _: &ApplyAllDiffHunks,
20115 window: &mut Window,
20116 cx: &mut Context<Self>,
20117 ) {
20118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20119
20120 let buffers = self.buffer.read(cx).all_buffers();
20121 for branch_buffer in buffers {
20122 branch_buffer.update(cx, |branch_buffer, cx| {
20123 branch_buffer.merge_into_base(Vec::new(), cx);
20124 });
20125 }
20126
20127 if let Some(project) = self.project.clone() {
20128 self.save(
20129 SaveOptions {
20130 format: true,
20131 autosave: false,
20132 },
20133 project,
20134 window,
20135 cx,
20136 )
20137 .detach_and_log_err(cx);
20138 }
20139 }
20140
20141 pub(crate) fn apply_selected_diff_hunks(
20142 &mut self,
20143 _: &ApplyDiffHunk,
20144 window: &mut Window,
20145 cx: &mut Context<Self>,
20146 ) {
20147 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20148 let snapshot = self.snapshot(window, cx);
20149 let hunks = snapshot.hunks_for_ranges(
20150 self.selections
20151 .all(&snapshot.display_snapshot)
20152 .into_iter()
20153 .map(|selection| selection.range()),
20154 );
20155 let mut ranges_by_buffer = HashMap::default();
20156 self.transact(window, cx, |editor, _window, cx| {
20157 for hunk in hunks {
20158 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20159 ranges_by_buffer
20160 .entry(buffer.clone())
20161 .or_insert_with(Vec::new)
20162 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20163 }
20164 }
20165
20166 for (buffer, ranges) in ranges_by_buffer {
20167 buffer.update(cx, |buffer, cx| {
20168 buffer.merge_into_base(ranges, cx);
20169 });
20170 }
20171 });
20172
20173 if let Some(project) = self.project.clone() {
20174 self.save(
20175 SaveOptions {
20176 format: true,
20177 autosave: false,
20178 },
20179 project,
20180 window,
20181 cx,
20182 )
20183 .detach_and_log_err(cx);
20184 }
20185 }
20186
20187 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20188 if hovered != self.gutter_hovered {
20189 self.gutter_hovered = hovered;
20190 cx.notify();
20191 }
20192 }
20193
20194 pub fn insert_blocks(
20195 &mut self,
20196 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20197 autoscroll: Option<Autoscroll>,
20198 cx: &mut Context<Self>,
20199 ) -> Vec<CustomBlockId> {
20200 let blocks = self
20201 .display_map
20202 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20203 if let Some(autoscroll) = autoscroll {
20204 self.request_autoscroll(autoscroll, cx);
20205 }
20206 cx.notify();
20207 blocks
20208 }
20209
20210 pub fn resize_blocks(
20211 &mut self,
20212 heights: HashMap<CustomBlockId, u32>,
20213 autoscroll: Option<Autoscroll>,
20214 cx: &mut Context<Self>,
20215 ) {
20216 self.display_map
20217 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20218 if let Some(autoscroll) = autoscroll {
20219 self.request_autoscroll(autoscroll, cx);
20220 }
20221 cx.notify();
20222 }
20223
20224 pub fn replace_blocks(
20225 &mut self,
20226 renderers: HashMap<CustomBlockId, RenderBlock>,
20227 autoscroll: Option<Autoscroll>,
20228 cx: &mut Context<Self>,
20229 ) {
20230 self.display_map
20231 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20232 if let Some(autoscroll) = autoscroll {
20233 self.request_autoscroll(autoscroll, cx);
20234 }
20235 cx.notify();
20236 }
20237
20238 pub fn remove_blocks(
20239 &mut self,
20240 block_ids: HashSet<CustomBlockId>,
20241 autoscroll: Option<Autoscroll>,
20242 cx: &mut Context<Self>,
20243 ) {
20244 self.display_map.update(cx, |display_map, cx| {
20245 display_map.remove_blocks(block_ids, cx)
20246 });
20247 if let Some(autoscroll) = autoscroll {
20248 self.request_autoscroll(autoscroll, cx);
20249 }
20250 cx.notify();
20251 }
20252
20253 pub fn row_for_block(
20254 &self,
20255 block_id: CustomBlockId,
20256 cx: &mut Context<Self>,
20257 ) -> Option<DisplayRow> {
20258 self.display_map
20259 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20260 }
20261
20262 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20263 self.focused_block = Some(focused_block);
20264 }
20265
20266 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20267 self.focused_block.take()
20268 }
20269
20270 pub fn insert_creases(
20271 &mut self,
20272 creases: impl IntoIterator<Item = Crease<Anchor>>,
20273 cx: &mut Context<Self>,
20274 ) -> Vec<CreaseId> {
20275 self.display_map
20276 .update(cx, |map, cx| map.insert_creases(creases, cx))
20277 }
20278
20279 pub fn remove_creases(
20280 &mut self,
20281 ids: impl IntoIterator<Item = CreaseId>,
20282 cx: &mut Context<Self>,
20283 ) -> Vec<(CreaseId, Range<Anchor>)> {
20284 self.display_map
20285 .update(cx, |map, cx| map.remove_creases(ids, cx))
20286 }
20287
20288 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20289 self.display_map
20290 .update(cx, |map, cx| map.snapshot(cx))
20291 .longest_row()
20292 }
20293
20294 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20295 self.display_map
20296 .update(cx, |map, cx| map.snapshot(cx))
20297 .max_point()
20298 }
20299
20300 pub fn text(&self, cx: &App) -> String {
20301 self.buffer.read(cx).read(cx).text()
20302 }
20303
20304 pub fn is_empty(&self, cx: &App) -> bool {
20305 self.buffer.read(cx).read(cx).is_empty()
20306 }
20307
20308 pub fn text_option(&self, cx: &App) -> Option<String> {
20309 let text = self.text(cx);
20310 let text = text.trim();
20311
20312 if text.is_empty() {
20313 return None;
20314 }
20315
20316 Some(text.to_string())
20317 }
20318
20319 pub fn set_text(
20320 &mut self,
20321 text: impl Into<Arc<str>>,
20322 window: &mut Window,
20323 cx: &mut Context<Self>,
20324 ) {
20325 self.transact(window, cx, |this, _, cx| {
20326 this.buffer
20327 .read(cx)
20328 .as_singleton()
20329 .expect("you can only call set_text on editors for singleton buffers")
20330 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20331 });
20332 }
20333
20334 pub fn display_text(&self, cx: &mut App) -> String {
20335 self.display_map
20336 .update(cx, |map, cx| map.snapshot(cx))
20337 .text()
20338 }
20339
20340 fn create_minimap(
20341 &self,
20342 minimap_settings: MinimapSettings,
20343 window: &mut Window,
20344 cx: &mut Context<Self>,
20345 ) -> Option<Entity<Self>> {
20346 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20347 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20348 }
20349
20350 fn initialize_new_minimap(
20351 &self,
20352 minimap_settings: MinimapSettings,
20353 window: &mut Window,
20354 cx: &mut Context<Self>,
20355 ) -> Entity<Self> {
20356 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20357
20358 let mut minimap = Editor::new_internal(
20359 EditorMode::Minimap {
20360 parent: cx.weak_entity(),
20361 },
20362 self.buffer.clone(),
20363 None,
20364 Some(self.display_map.clone()),
20365 window,
20366 cx,
20367 );
20368 minimap.scroll_manager.clone_state(&self.scroll_manager);
20369 minimap.set_text_style_refinement(TextStyleRefinement {
20370 font_size: Some(MINIMAP_FONT_SIZE),
20371 font_weight: Some(MINIMAP_FONT_WEIGHT),
20372 ..Default::default()
20373 });
20374 minimap.update_minimap_configuration(minimap_settings, cx);
20375 cx.new(|_| minimap)
20376 }
20377
20378 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20379 let current_line_highlight = minimap_settings
20380 .current_line_highlight
20381 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20382 self.set_current_line_highlight(Some(current_line_highlight));
20383 }
20384
20385 pub fn minimap(&self) -> Option<&Entity<Self>> {
20386 self.minimap
20387 .as_ref()
20388 .filter(|_| self.minimap_visibility.visible())
20389 }
20390
20391 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20392 let mut wrap_guides = smallvec![];
20393
20394 if self.show_wrap_guides == Some(false) {
20395 return wrap_guides;
20396 }
20397
20398 let settings = self.buffer.read(cx).language_settings(cx);
20399 if settings.show_wrap_guides {
20400 match self.soft_wrap_mode(cx) {
20401 SoftWrap::Column(soft_wrap) => {
20402 wrap_guides.push((soft_wrap as usize, true));
20403 }
20404 SoftWrap::Bounded(soft_wrap) => {
20405 wrap_guides.push((soft_wrap as usize, true));
20406 }
20407 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20408 }
20409 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20410 }
20411
20412 wrap_guides
20413 }
20414
20415 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20416 let settings = self.buffer.read(cx).language_settings(cx);
20417 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20418 match mode {
20419 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20420 SoftWrap::None
20421 }
20422 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20423 language_settings::SoftWrap::PreferredLineLength => {
20424 SoftWrap::Column(settings.preferred_line_length)
20425 }
20426 language_settings::SoftWrap::Bounded => {
20427 SoftWrap::Bounded(settings.preferred_line_length)
20428 }
20429 }
20430 }
20431
20432 pub fn set_soft_wrap_mode(
20433 &mut self,
20434 mode: language_settings::SoftWrap,
20435
20436 cx: &mut Context<Self>,
20437 ) {
20438 self.soft_wrap_mode_override = Some(mode);
20439 cx.notify();
20440 }
20441
20442 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20443 self.hard_wrap = hard_wrap;
20444 cx.notify();
20445 }
20446
20447 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20448 self.text_style_refinement = Some(style);
20449 }
20450
20451 /// called by the Element so we know what style we were most recently rendered with.
20452 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20453 // We intentionally do not inform the display map about the minimap style
20454 // so that wrapping is not recalculated and stays consistent for the editor
20455 // and its linked minimap.
20456 if !self.mode.is_minimap() {
20457 let font = style.text.font();
20458 let font_size = style.text.font_size.to_pixels(window.rem_size());
20459 let display_map = self
20460 .placeholder_display_map
20461 .as_ref()
20462 .filter(|_| self.is_empty(cx))
20463 .unwrap_or(&self.display_map);
20464
20465 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20466 }
20467 self.style = Some(style);
20468 }
20469
20470 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20471 if self.style.is_none() {
20472 self.style = Some(self.create_style(cx));
20473 }
20474 self.style.as_ref().unwrap()
20475 }
20476
20477 // Called by the element. This method is not designed to be called outside of the editor
20478 // element's layout code because it does not notify when rewrapping is computed synchronously.
20479 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20480 if self.is_empty(cx) {
20481 self.placeholder_display_map
20482 .as_ref()
20483 .map_or(false, |display_map| {
20484 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20485 })
20486 } else {
20487 self.display_map
20488 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20489 }
20490 }
20491
20492 pub fn set_soft_wrap(&mut self) {
20493 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20494 }
20495
20496 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20497 if self.soft_wrap_mode_override.is_some() {
20498 self.soft_wrap_mode_override.take();
20499 } else {
20500 let soft_wrap = match self.soft_wrap_mode(cx) {
20501 SoftWrap::GitDiff => return,
20502 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20503 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20504 language_settings::SoftWrap::None
20505 }
20506 };
20507 self.soft_wrap_mode_override = Some(soft_wrap);
20508 }
20509 cx.notify();
20510 }
20511
20512 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20513 let Some(workspace) = self.workspace() else {
20514 return;
20515 };
20516 let fs = workspace.read(cx).app_state().fs.clone();
20517 let current_show = TabBarSettings::get_global(cx).show;
20518 update_settings_file(fs, cx, move |setting, _| {
20519 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20520 });
20521 }
20522
20523 pub fn toggle_indent_guides(
20524 &mut self,
20525 _: &ToggleIndentGuides,
20526 _: &mut Window,
20527 cx: &mut Context<Self>,
20528 ) {
20529 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20530 self.buffer
20531 .read(cx)
20532 .language_settings(cx)
20533 .indent_guides
20534 .enabled
20535 });
20536 self.show_indent_guides = Some(!currently_enabled);
20537 cx.notify();
20538 }
20539
20540 fn should_show_indent_guides(&self) -> Option<bool> {
20541 self.show_indent_guides
20542 }
20543
20544 pub fn disable_indent_guides_for_buffer(
20545 &mut self,
20546 buffer_id: BufferId,
20547 cx: &mut Context<Self>,
20548 ) {
20549 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20550 cx.notify();
20551 }
20552
20553 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20554 self.buffers_with_disabled_indent_guides
20555 .contains(&buffer_id)
20556 }
20557
20558 pub fn toggle_line_numbers(
20559 &mut self,
20560 _: &ToggleLineNumbers,
20561 _: &mut Window,
20562 cx: &mut Context<Self>,
20563 ) {
20564 let mut editor_settings = EditorSettings::get_global(cx).clone();
20565 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20566 EditorSettings::override_global(editor_settings, cx);
20567 }
20568
20569 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20570 if let Some(show_line_numbers) = self.show_line_numbers {
20571 return show_line_numbers;
20572 }
20573 EditorSettings::get_global(cx).gutter.line_numbers
20574 }
20575
20576 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20577 match (
20578 self.use_relative_line_numbers,
20579 EditorSettings::get_global(cx).relative_line_numbers,
20580 ) {
20581 (None, setting) => setting,
20582 (Some(false), _) => RelativeLineNumbers::Disabled,
20583 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20584 (Some(true), _) => RelativeLineNumbers::Enabled,
20585 }
20586 }
20587
20588 pub fn toggle_relative_line_numbers(
20589 &mut self,
20590 _: &ToggleRelativeLineNumbers,
20591 _: &mut Window,
20592 cx: &mut Context<Self>,
20593 ) {
20594 let is_relative = self.relative_line_numbers(cx);
20595 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20596 }
20597
20598 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20599 self.use_relative_line_numbers = is_relative;
20600 cx.notify();
20601 }
20602
20603 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20604 self.show_gutter = show_gutter;
20605 cx.notify();
20606 }
20607
20608 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20609 self.show_scrollbars = ScrollbarAxes {
20610 horizontal: show,
20611 vertical: show,
20612 };
20613 cx.notify();
20614 }
20615
20616 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20617 self.show_scrollbars.vertical = show;
20618 cx.notify();
20619 }
20620
20621 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20622 self.show_scrollbars.horizontal = show;
20623 cx.notify();
20624 }
20625
20626 pub fn set_minimap_visibility(
20627 &mut self,
20628 minimap_visibility: MinimapVisibility,
20629 window: &mut Window,
20630 cx: &mut Context<Self>,
20631 ) {
20632 if self.minimap_visibility != minimap_visibility {
20633 if minimap_visibility.visible() && self.minimap.is_none() {
20634 let minimap_settings = EditorSettings::get_global(cx).minimap;
20635 self.minimap =
20636 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20637 }
20638 self.minimap_visibility = minimap_visibility;
20639 cx.notify();
20640 }
20641 }
20642
20643 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20644 self.set_show_scrollbars(false, cx);
20645 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20646 }
20647
20648 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20649 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20650 }
20651
20652 /// Normally the text in full mode and auto height editors is padded on the
20653 /// left side by roughly half a character width for improved hit testing.
20654 ///
20655 /// Use this method to disable this for cases where this is not wanted (e.g.
20656 /// if you want to align the editor text with some other text above or below)
20657 /// or if you want to add this padding to single-line editors.
20658 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20659 self.offset_content = offset_content;
20660 cx.notify();
20661 }
20662
20663 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20664 self.show_line_numbers = Some(show_line_numbers);
20665 cx.notify();
20666 }
20667
20668 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20669 self.disable_expand_excerpt_buttons = true;
20670 cx.notify();
20671 }
20672
20673 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20674 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20675 cx.notify();
20676 }
20677
20678 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20679 self.show_code_actions = Some(show_code_actions);
20680 cx.notify();
20681 }
20682
20683 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20684 self.show_runnables = Some(show_runnables);
20685 cx.notify();
20686 }
20687
20688 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20689 self.show_breakpoints = Some(show_breakpoints);
20690 cx.notify();
20691 }
20692
20693 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20694 if self.display_map.read(cx).masked != masked {
20695 self.display_map.update(cx, |map, _| map.masked = masked);
20696 }
20697 cx.notify()
20698 }
20699
20700 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20701 self.show_wrap_guides = Some(show_wrap_guides);
20702 cx.notify();
20703 }
20704
20705 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20706 self.show_indent_guides = Some(show_indent_guides);
20707 cx.notify();
20708 }
20709
20710 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20711 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20712 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20713 && let Some(dir) = file.abs_path(cx).parent()
20714 {
20715 return Some(dir.to_owned());
20716 }
20717 }
20718
20719 None
20720 }
20721
20722 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20723 self.active_excerpt(cx)?
20724 .1
20725 .read(cx)
20726 .file()
20727 .and_then(|f| f.as_local())
20728 }
20729
20730 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20731 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20732 let buffer = buffer.read(cx);
20733 if let Some(project_path) = buffer.project_path(cx) {
20734 let project = self.project()?.read(cx);
20735 project.absolute_path(&project_path, cx)
20736 } else {
20737 buffer
20738 .file()
20739 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20740 }
20741 })
20742 }
20743
20744 pub fn reveal_in_finder(
20745 &mut self,
20746 _: &RevealInFileManager,
20747 _window: &mut Window,
20748 cx: &mut Context<Self>,
20749 ) {
20750 if let Some(target) = self.target_file(cx) {
20751 cx.reveal_path(&target.abs_path(cx));
20752 }
20753 }
20754
20755 pub fn copy_path(
20756 &mut self,
20757 _: &zed_actions::workspace::CopyPath,
20758 _window: &mut Window,
20759 cx: &mut Context<Self>,
20760 ) {
20761 if let Some(path) = self.target_file_abs_path(cx)
20762 && let Some(path) = path.to_str()
20763 {
20764 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20765 } else {
20766 cx.propagate();
20767 }
20768 }
20769
20770 pub fn copy_relative_path(
20771 &mut self,
20772 _: &zed_actions::workspace::CopyRelativePath,
20773 _window: &mut Window,
20774 cx: &mut Context<Self>,
20775 ) {
20776 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20777 let project = self.project()?.read(cx);
20778 let path = buffer.read(cx).file()?.path();
20779 let path = path.display(project.path_style(cx));
20780 Some(path)
20781 }) {
20782 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20783 } else {
20784 cx.propagate();
20785 }
20786 }
20787
20788 /// Returns the project path for the editor's buffer, if any buffer is
20789 /// opened in the editor.
20790 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20791 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20792 buffer.read(cx).project_path(cx)
20793 } else {
20794 None
20795 }
20796 }
20797
20798 // Returns true if the editor handled a go-to-line request
20799 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20800 maybe!({
20801 let breakpoint_store = self.breakpoint_store.as_ref()?;
20802
20803 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20804 else {
20805 self.clear_row_highlights::<ActiveDebugLine>();
20806 return None;
20807 };
20808
20809 let position = active_stack_frame.position;
20810 let buffer_id = position.buffer_id?;
20811 let snapshot = self
20812 .project
20813 .as_ref()?
20814 .read(cx)
20815 .buffer_for_id(buffer_id, cx)?
20816 .read(cx)
20817 .snapshot();
20818
20819 let mut handled = false;
20820 for (id, ExcerptRange { context, .. }) in
20821 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20822 {
20823 if context.start.cmp(&position, &snapshot).is_ge()
20824 || context.end.cmp(&position, &snapshot).is_lt()
20825 {
20826 continue;
20827 }
20828 let snapshot = self.buffer.read(cx).snapshot(cx);
20829 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20830
20831 handled = true;
20832 self.clear_row_highlights::<ActiveDebugLine>();
20833
20834 self.go_to_line::<ActiveDebugLine>(
20835 multibuffer_anchor,
20836 Some(cx.theme().colors().editor_debugger_active_line_background),
20837 window,
20838 cx,
20839 );
20840
20841 cx.notify();
20842 }
20843
20844 handled.then_some(())
20845 })
20846 .is_some()
20847 }
20848
20849 pub fn copy_file_name_without_extension(
20850 &mut self,
20851 _: &CopyFileNameWithoutExtension,
20852 _: &mut Window,
20853 cx: &mut Context<Self>,
20854 ) {
20855 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20856 let file = buffer.read(cx).file()?;
20857 file.path().file_stem()
20858 }) {
20859 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20860 }
20861 }
20862
20863 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20864 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20865 let file = buffer.read(cx).file()?;
20866 Some(file.file_name(cx))
20867 }) {
20868 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20869 }
20870 }
20871
20872 pub fn toggle_git_blame(
20873 &mut self,
20874 _: &::git::Blame,
20875 window: &mut Window,
20876 cx: &mut Context<Self>,
20877 ) {
20878 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20879
20880 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20881 self.start_git_blame(true, window, cx);
20882 }
20883
20884 cx.notify();
20885 }
20886
20887 pub fn toggle_git_blame_inline(
20888 &mut self,
20889 _: &ToggleGitBlameInline,
20890 window: &mut Window,
20891 cx: &mut Context<Self>,
20892 ) {
20893 self.toggle_git_blame_inline_internal(true, window, cx);
20894 cx.notify();
20895 }
20896
20897 pub fn open_git_blame_commit(
20898 &mut self,
20899 _: &OpenGitBlameCommit,
20900 window: &mut Window,
20901 cx: &mut Context<Self>,
20902 ) {
20903 self.open_git_blame_commit_internal(window, cx);
20904 }
20905
20906 fn open_git_blame_commit_internal(
20907 &mut self,
20908 window: &mut Window,
20909 cx: &mut Context<Self>,
20910 ) -> Option<()> {
20911 let blame = self.blame.as_ref()?;
20912 let snapshot = self.snapshot(window, cx);
20913 let cursor = self
20914 .selections
20915 .newest::<Point>(&snapshot.display_snapshot)
20916 .head();
20917 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20918 let (_, blame_entry) = blame
20919 .update(cx, |blame, cx| {
20920 blame
20921 .blame_for_rows(
20922 &[RowInfo {
20923 buffer_id: Some(buffer.remote_id()),
20924 buffer_row: Some(point.row),
20925 ..Default::default()
20926 }],
20927 cx,
20928 )
20929 .next()
20930 })
20931 .flatten()?;
20932 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20933 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20934 let workspace = self.workspace()?.downgrade();
20935 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20936 None
20937 }
20938
20939 pub fn git_blame_inline_enabled(&self) -> bool {
20940 self.git_blame_inline_enabled
20941 }
20942
20943 pub fn toggle_selection_menu(
20944 &mut self,
20945 _: &ToggleSelectionMenu,
20946 _: &mut Window,
20947 cx: &mut Context<Self>,
20948 ) {
20949 self.show_selection_menu = self
20950 .show_selection_menu
20951 .map(|show_selections_menu| !show_selections_menu)
20952 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20953
20954 cx.notify();
20955 }
20956
20957 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20958 self.show_selection_menu
20959 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20960 }
20961
20962 fn start_git_blame(
20963 &mut self,
20964 user_triggered: bool,
20965 window: &mut Window,
20966 cx: &mut Context<Self>,
20967 ) {
20968 if let Some(project) = self.project() {
20969 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20970 && buffer.read(cx).file().is_none()
20971 {
20972 return;
20973 }
20974
20975 let focused = self.focus_handle(cx).contains_focused(window, cx);
20976
20977 let project = project.clone();
20978 let blame = cx
20979 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20980 self.blame_subscription =
20981 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20982 self.blame = Some(blame);
20983 }
20984 }
20985
20986 fn toggle_git_blame_inline_internal(
20987 &mut self,
20988 user_triggered: bool,
20989 window: &mut Window,
20990 cx: &mut Context<Self>,
20991 ) {
20992 if self.git_blame_inline_enabled {
20993 self.git_blame_inline_enabled = false;
20994 self.show_git_blame_inline = false;
20995 self.show_git_blame_inline_delay_task.take();
20996 } else {
20997 self.git_blame_inline_enabled = true;
20998 self.start_git_blame_inline(user_triggered, window, cx);
20999 }
21000
21001 cx.notify();
21002 }
21003
21004 fn start_git_blame_inline(
21005 &mut self,
21006 user_triggered: bool,
21007 window: &mut Window,
21008 cx: &mut Context<Self>,
21009 ) {
21010 self.start_git_blame(user_triggered, window, cx);
21011
21012 if ProjectSettings::get_global(cx)
21013 .git
21014 .inline_blame_delay()
21015 .is_some()
21016 {
21017 self.start_inline_blame_timer(window, cx);
21018 } else {
21019 self.show_git_blame_inline = true
21020 }
21021 }
21022
21023 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
21024 self.blame.as_ref()
21025 }
21026
21027 pub fn show_git_blame_gutter(&self) -> bool {
21028 self.show_git_blame_gutter
21029 }
21030
21031 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
21032 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
21033 }
21034
21035 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
21036 self.show_git_blame_inline
21037 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
21038 && !self.newest_selection_head_on_empty_line(cx)
21039 && self.has_blame_entries(cx)
21040 }
21041
21042 fn has_blame_entries(&self, cx: &App) -> bool {
21043 self.blame()
21044 .is_some_and(|blame| blame.read(cx).has_generated_entries())
21045 }
21046
21047 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
21048 let cursor_anchor = self.selections.newest_anchor().head();
21049
21050 let snapshot = self.buffer.read(cx).snapshot(cx);
21051 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
21052
21053 snapshot.line_len(buffer_row) == 0
21054 }
21055
21056 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
21057 let buffer_and_selection = maybe!({
21058 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21059 let selection_range = selection.range();
21060
21061 let multi_buffer = self.buffer().read(cx);
21062 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21063 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21064
21065 let (buffer, range, _) = if selection.reversed {
21066 buffer_ranges.first()
21067 } else {
21068 buffer_ranges.last()
21069 }?;
21070
21071 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21072 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21073
21074 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21075 let selection = start_row_in_buffer..end_row_in_buffer;
21076
21077 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21078 };
21079
21080 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21081
21082 Some((
21083 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21084 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
21085 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
21086 ))
21087 });
21088
21089 let Some((buffer, selection)) = buffer_and_selection else {
21090 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21091 };
21092
21093 let Some(project) = self.project() else {
21094 return Task::ready(Err(anyhow!("editor does not have project")));
21095 };
21096
21097 project.update(cx, |project, cx| {
21098 project.get_permalink_to_line(&buffer, selection, cx)
21099 })
21100 }
21101
21102 pub fn copy_permalink_to_line(
21103 &mut self,
21104 _: &CopyPermalinkToLine,
21105 window: &mut Window,
21106 cx: &mut Context<Self>,
21107 ) {
21108 let permalink_task = self.get_permalink_to_line(cx);
21109 let workspace = self.workspace();
21110
21111 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21112 Ok(permalink) => {
21113 cx.update(|_, cx| {
21114 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21115 })
21116 .ok();
21117 }
21118 Err(err) => {
21119 let message = format!("Failed to copy permalink: {err}");
21120
21121 anyhow::Result::<()>::Err(err).log_err();
21122
21123 if let Some(workspace) = workspace {
21124 workspace
21125 .update_in(cx, |workspace, _, cx| {
21126 struct CopyPermalinkToLine;
21127
21128 workspace.show_toast(
21129 Toast::new(
21130 NotificationId::unique::<CopyPermalinkToLine>(),
21131 message,
21132 ),
21133 cx,
21134 )
21135 })
21136 .ok();
21137 }
21138 }
21139 })
21140 .detach();
21141 }
21142
21143 pub fn copy_file_location(
21144 &mut self,
21145 _: &CopyFileLocation,
21146 _: &mut Window,
21147 cx: &mut Context<Self>,
21148 ) {
21149 let selection = self
21150 .selections
21151 .newest::<Point>(&self.display_snapshot(cx))
21152 .start
21153 .row
21154 + 1;
21155 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21156 let project = self.project()?.read(cx);
21157 let file = buffer.read(cx).file()?;
21158 let path = file.path().display(project.path_style(cx));
21159
21160 Some(format!("{path}:{selection}"))
21161 }) {
21162 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21163 }
21164 }
21165
21166 pub fn open_permalink_to_line(
21167 &mut self,
21168 _: &OpenPermalinkToLine,
21169 window: &mut Window,
21170 cx: &mut Context<Self>,
21171 ) {
21172 let permalink_task = self.get_permalink_to_line(cx);
21173 let workspace = self.workspace();
21174
21175 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21176 Ok(permalink) => {
21177 cx.update(|_, cx| {
21178 cx.open_url(permalink.as_ref());
21179 })
21180 .ok();
21181 }
21182 Err(err) => {
21183 let message = format!("Failed to open permalink: {err}");
21184
21185 anyhow::Result::<()>::Err(err).log_err();
21186
21187 if let Some(workspace) = workspace {
21188 workspace
21189 .update(cx, |workspace, cx| {
21190 struct OpenPermalinkToLine;
21191
21192 workspace.show_toast(
21193 Toast::new(
21194 NotificationId::unique::<OpenPermalinkToLine>(),
21195 message,
21196 ),
21197 cx,
21198 )
21199 })
21200 .ok();
21201 }
21202 }
21203 })
21204 .detach();
21205 }
21206
21207 pub fn insert_uuid_v4(
21208 &mut self,
21209 _: &InsertUuidV4,
21210 window: &mut Window,
21211 cx: &mut Context<Self>,
21212 ) {
21213 self.insert_uuid(UuidVersion::V4, window, cx);
21214 }
21215
21216 pub fn insert_uuid_v7(
21217 &mut self,
21218 _: &InsertUuidV7,
21219 window: &mut Window,
21220 cx: &mut Context<Self>,
21221 ) {
21222 self.insert_uuid(UuidVersion::V7, window, cx);
21223 }
21224
21225 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21227 self.transact(window, cx, |this, window, cx| {
21228 let edits = this
21229 .selections
21230 .all::<Point>(&this.display_snapshot(cx))
21231 .into_iter()
21232 .map(|selection| {
21233 let uuid = match version {
21234 UuidVersion::V4 => uuid::Uuid::new_v4(),
21235 UuidVersion::V7 => uuid::Uuid::now_v7(),
21236 };
21237
21238 (selection.range(), uuid.to_string())
21239 });
21240 this.edit(edits, cx);
21241 this.refresh_edit_prediction(true, false, window, cx);
21242 });
21243 }
21244
21245 pub fn open_selections_in_multibuffer(
21246 &mut self,
21247 _: &OpenSelectionsInMultibuffer,
21248 window: &mut Window,
21249 cx: &mut Context<Self>,
21250 ) {
21251 let multibuffer = self.buffer.read(cx);
21252
21253 let Some(buffer) = multibuffer.as_singleton() else {
21254 return;
21255 };
21256
21257 let Some(workspace) = self.workspace() else {
21258 return;
21259 };
21260
21261 let title = multibuffer.title(cx).to_string();
21262
21263 let locations = self
21264 .selections
21265 .all_anchors(&self.display_snapshot(cx))
21266 .iter()
21267 .map(|selection| {
21268 (
21269 buffer.clone(),
21270 (selection.start.text_anchor..selection.end.text_anchor)
21271 .to_point(buffer.read(cx)),
21272 )
21273 })
21274 .into_group_map();
21275
21276 cx.spawn_in(window, async move |_, cx| {
21277 workspace.update_in(cx, |workspace, window, cx| {
21278 Self::open_locations_in_multibuffer(
21279 workspace,
21280 locations,
21281 format!("Selections for '{title}'"),
21282 false,
21283 false,
21284 MultibufferSelectionMode::All,
21285 window,
21286 cx,
21287 );
21288 })
21289 })
21290 .detach();
21291 }
21292
21293 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21294 /// last highlight added will be used.
21295 ///
21296 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21297 pub fn highlight_rows<T: 'static>(
21298 &mut self,
21299 range: Range<Anchor>,
21300 color: Hsla,
21301 options: RowHighlightOptions,
21302 cx: &mut Context<Self>,
21303 ) {
21304 let snapshot = self.buffer().read(cx).snapshot(cx);
21305 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21306 let ix = row_highlights.binary_search_by(|highlight| {
21307 Ordering::Equal
21308 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21309 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21310 });
21311
21312 if let Err(mut ix) = ix {
21313 let index = post_inc(&mut self.highlight_order);
21314
21315 // If this range intersects with the preceding highlight, then merge it with
21316 // the preceding highlight. Otherwise insert a new highlight.
21317 let mut merged = false;
21318 if ix > 0 {
21319 let prev_highlight = &mut row_highlights[ix - 1];
21320 if prev_highlight
21321 .range
21322 .end
21323 .cmp(&range.start, &snapshot)
21324 .is_ge()
21325 {
21326 ix -= 1;
21327 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21328 prev_highlight.range.end = range.end;
21329 }
21330 merged = true;
21331 prev_highlight.index = index;
21332 prev_highlight.color = color;
21333 prev_highlight.options = options;
21334 }
21335 }
21336
21337 if !merged {
21338 row_highlights.insert(
21339 ix,
21340 RowHighlight {
21341 range,
21342 index,
21343 color,
21344 options,
21345 type_id: TypeId::of::<T>(),
21346 },
21347 );
21348 }
21349
21350 // If any of the following highlights intersect with this one, merge them.
21351 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21352 let highlight = &row_highlights[ix];
21353 if next_highlight
21354 .range
21355 .start
21356 .cmp(&highlight.range.end, &snapshot)
21357 .is_le()
21358 {
21359 if next_highlight
21360 .range
21361 .end
21362 .cmp(&highlight.range.end, &snapshot)
21363 .is_gt()
21364 {
21365 row_highlights[ix].range.end = next_highlight.range.end;
21366 }
21367 row_highlights.remove(ix + 1);
21368 } else {
21369 break;
21370 }
21371 }
21372 }
21373 }
21374
21375 /// Remove any highlighted row ranges of the given type that intersect the
21376 /// given ranges.
21377 pub fn remove_highlighted_rows<T: 'static>(
21378 &mut self,
21379 ranges_to_remove: Vec<Range<Anchor>>,
21380 cx: &mut Context<Self>,
21381 ) {
21382 let snapshot = self.buffer().read(cx).snapshot(cx);
21383 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21384 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21385 row_highlights.retain(|highlight| {
21386 while let Some(range_to_remove) = ranges_to_remove.peek() {
21387 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21388 Ordering::Less | Ordering::Equal => {
21389 ranges_to_remove.next();
21390 }
21391 Ordering::Greater => {
21392 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21393 Ordering::Less | Ordering::Equal => {
21394 return false;
21395 }
21396 Ordering::Greater => break,
21397 }
21398 }
21399 }
21400 }
21401
21402 true
21403 })
21404 }
21405
21406 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21407 pub fn clear_row_highlights<T: 'static>(&mut self) {
21408 self.highlighted_rows.remove(&TypeId::of::<T>());
21409 }
21410
21411 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21412 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21413 self.highlighted_rows
21414 .get(&TypeId::of::<T>())
21415 .map_or(&[] as &[_], |vec| vec.as_slice())
21416 .iter()
21417 .map(|highlight| (highlight.range.clone(), highlight.color))
21418 }
21419
21420 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21421 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21422 /// Allows to ignore certain kinds of highlights.
21423 pub fn highlighted_display_rows(
21424 &self,
21425 window: &mut Window,
21426 cx: &mut App,
21427 ) -> BTreeMap<DisplayRow, LineHighlight> {
21428 let snapshot = self.snapshot(window, cx);
21429 let mut used_highlight_orders = HashMap::default();
21430 self.highlighted_rows
21431 .iter()
21432 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21433 .fold(
21434 BTreeMap::<DisplayRow, LineHighlight>::new(),
21435 |mut unique_rows, highlight| {
21436 let start = highlight.range.start.to_display_point(&snapshot);
21437 let end = highlight.range.end.to_display_point(&snapshot);
21438 let start_row = start.row().0;
21439 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21440 {
21441 end.row().0.saturating_sub(1)
21442 } else {
21443 end.row().0
21444 };
21445 for row in start_row..=end_row {
21446 let used_index =
21447 used_highlight_orders.entry(row).or_insert(highlight.index);
21448 if highlight.index >= *used_index {
21449 *used_index = highlight.index;
21450 unique_rows.insert(
21451 DisplayRow(row),
21452 LineHighlight {
21453 include_gutter: highlight.options.include_gutter,
21454 border: None,
21455 background: highlight.color.into(),
21456 type_id: Some(highlight.type_id),
21457 },
21458 );
21459 }
21460 }
21461 unique_rows
21462 },
21463 )
21464 }
21465
21466 pub fn highlighted_display_row_for_autoscroll(
21467 &self,
21468 snapshot: &DisplaySnapshot,
21469 ) -> Option<DisplayRow> {
21470 self.highlighted_rows
21471 .values()
21472 .flat_map(|highlighted_rows| highlighted_rows.iter())
21473 .filter_map(|highlight| {
21474 if highlight.options.autoscroll {
21475 Some(highlight.range.start.to_display_point(snapshot).row())
21476 } else {
21477 None
21478 }
21479 })
21480 .min()
21481 }
21482
21483 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21484 self.highlight_background::<SearchWithinRange>(
21485 ranges,
21486 |_, colors| colors.colors().editor_document_highlight_read_background,
21487 cx,
21488 )
21489 }
21490
21491 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21492 self.breadcrumb_header = Some(new_header);
21493 }
21494
21495 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21496 self.clear_background_highlights::<SearchWithinRange>(cx);
21497 }
21498
21499 pub fn highlight_background<T: 'static>(
21500 &mut self,
21501 ranges: &[Range<Anchor>],
21502 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21503 cx: &mut Context<Self>,
21504 ) {
21505 self.background_highlights.insert(
21506 HighlightKey::Type(TypeId::of::<T>()),
21507 (Arc::new(color_fetcher), Arc::from(ranges)),
21508 );
21509 self.scrollbar_marker_state.dirty = true;
21510 cx.notify();
21511 }
21512
21513 pub fn highlight_background_key<T: 'static>(
21514 &mut self,
21515 key: usize,
21516 ranges: &[Range<Anchor>],
21517 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21518 cx: &mut Context<Self>,
21519 ) {
21520 self.background_highlights.insert(
21521 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21522 (Arc::new(color_fetcher), Arc::from(ranges)),
21523 );
21524 self.scrollbar_marker_state.dirty = true;
21525 cx.notify();
21526 }
21527
21528 pub fn clear_background_highlights<T: 'static>(
21529 &mut self,
21530 cx: &mut Context<Self>,
21531 ) -> Option<BackgroundHighlight> {
21532 let text_highlights = self
21533 .background_highlights
21534 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21535 if !text_highlights.1.is_empty() {
21536 self.scrollbar_marker_state.dirty = true;
21537 cx.notify();
21538 }
21539 Some(text_highlights)
21540 }
21541
21542 pub fn highlight_gutter<T: 'static>(
21543 &mut self,
21544 ranges: impl Into<Vec<Range<Anchor>>>,
21545 color_fetcher: fn(&App) -> Hsla,
21546 cx: &mut Context<Self>,
21547 ) {
21548 self.gutter_highlights
21549 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21550 cx.notify();
21551 }
21552
21553 pub fn clear_gutter_highlights<T: 'static>(
21554 &mut self,
21555 cx: &mut Context<Self>,
21556 ) -> Option<GutterHighlight> {
21557 cx.notify();
21558 self.gutter_highlights.remove(&TypeId::of::<T>())
21559 }
21560
21561 pub fn insert_gutter_highlight<T: 'static>(
21562 &mut self,
21563 range: Range<Anchor>,
21564 color_fetcher: fn(&App) -> Hsla,
21565 cx: &mut Context<Self>,
21566 ) {
21567 let snapshot = self.buffer().read(cx).snapshot(cx);
21568 let mut highlights = self
21569 .gutter_highlights
21570 .remove(&TypeId::of::<T>())
21571 .map(|(_, highlights)| highlights)
21572 .unwrap_or_default();
21573 let ix = highlights.binary_search_by(|highlight| {
21574 Ordering::Equal
21575 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21576 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21577 });
21578 if let Err(ix) = ix {
21579 highlights.insert(ix, range);
21580 }
21581 self.gutter_highlights
21582 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21583 }
21584
21585 pub fn remove_gutter_highlights<T: 'static>(
21586 &mut self,
21587 ranges_to_remove: Vec<Range<Anchor>>,
21588 cx: &mut Context<Self>,
21589 ) {
21590 let snapshot = self.buffer().read(cx).snapshot(cx);
21591 let Some((color_fetcher, mut gutter_highlights)) =
21592 self.gutter_highlights.remove(&TypeId::of::<T>())
21593 else {
21594 return;
21595 };
21596 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21597 gutter_highlights.retain(|highlight| {
21598 while let Some(range_to_remove) = ranges_to_remove.peek() {
21599 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21600 Ordering::Less | Ordering::Equal => {
21601 ranges_to_remove.next();
21602 }
21603 Ordering::Greater => {
21604 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21605 Ordering::Less | Ordering::Equal => {
21606 return false;
21607 }
21608 Ordering::Greater => break,
21609 }
21610 }
21611 }
21612 }
21613
21614 true
21615 });
21616 self.gutter_highlights
21617 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21618 }
21619
21620 #[cfg(feature = "test-support")]
21621 pub fn all_text_highlights(
21622 &self,
21623 window: &mut Window,
21624 cx: &mut Context<Self>,
21625 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21626 let snapshot = self.snapshot(window, cx);
21627 self.display_map.update(cx, |display_map, _| {
21628 display_map
21629 .all_text_highlights()
21630 .map(|highlight| {
21631 let (style, ranges) = highlight.as_ref();
21632 (
21633 *style,
21634 ranges
21635 .iter()
21636 .map(|range| range.clone().to_display_points(&snapshot))
21637 .collect(),
21638 )
21639 })
21640 .collect()
21641 })
21642 }
21643
21644 #[cfg(feature = "test-support")]
21645 pub fn all_text_background_highlights(
21646 &self,
21647 window: &mut Window,
21648 cx: &mut Context<Self>,
21649 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21650 let snapshot = self.snapshot(window, cx);
21651 let buffer = &snapshot.buffer_snapshot();
21652 let start = buffer.anchor_before(MultiBufferOffset(0));
21653 let end = buffer.anchor_after(buffer.len());
21654 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21655 }
21656
21657 #[cfg(any(test, feature = "test-support"))]
21658 pub fn sorted_background_highlights_in_range(
21659 &self,
21660 search_range: Range<Anchor>,
21661 display_snapshot: &DisplaySnapshot,
21662 theme: &Theme,
21663 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21664 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21665 res.sort_by(|a, b| {
21666 a.0.start
21667 .cmp(&b.0.start)
21668 .then_with(|| a.0.end.cmp(&b.0.end))
21669 .then_with(|| a.1.cmp(&b.1))
21670 });
21671 res
21672 }
21673
21674 #[cfg(feature = "test-support")]
21675 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21676 let snapshot = self.buffer().read(cx).snapshot(cx);
21677
21678 let highlights = self
21679 .background_highlights
21680 .get(&HighlightKey::Type(TypeId::of::<
21681 items::BufferSearchHighlights,
21682 >()));
21683
21684 if let Some((_color, ranges)) = highlights {
21685 ranges
21686 .iter()
21687 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21688 .collect_vec()
21689 } else {
21690 vec![]
21691 }
21692 }
21693
21694 fn document_highlights_for_position<'a>(
21695 &'a self,
21696 position: Anchor,
21697 buffer: &'a MultiBufferSnapshot,
21698 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21699 let read_highlights = self
21700 .background_highlights
21701 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21702 .map(|h| &h.1);
21703 let write_highlights = self
21704 .background_highlights
21705 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21706 .map(|h| &h.1);
21707 let left_position = position.bias_left(buffer);
21708 let right_position = position.bias_right(buffer);
21709 read_highlights
21710 .into_iter()
21711 .chain(write_highlights)
21712 .flat_map(move |ranges| {
21713 let start_ix = match ranges.binary_search_by(|probe| {
21714 let cmp = probe.end.cmp(&left_position, buffer);
21715 if cmp.is_ge() {
21716 Ordering::Greater
21717 } else {
21718 Ordering::Less
21719 }
21720 }) {
21721 Ok(i) | Err(i) => i,
21722 };
21723
21724 ranges[start_ix..]
21725 .iter()
21726 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21727 })
21728 }
21729
21730 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21731 self.background_highlights
21732 .get(&HighlightKey::Type(TypeId::of::<T>()))
21733 .is_some_and(|(_, highlights)| !highlights.is_empty())
21734 }
21735
21736 /// Returns all background highlights for a given range.
21737 ///
21738 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21739 pub fn background_highlights_in_range(
21740 &self,
21741 search_range: Range<Anchor>,
21742 display_snapshot: &DisplaySnapshot,
21743 theme: &Theme,
21744 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21745 let mut results = Vec::new();
21746 for (color_fetcher, ranges) in self.background_highlights.values() {
21747 let start_ix = match ranges.binary_search_by(|probe| {
21748 let cmp = probe
21749 .end
21750 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21751 if cmp.is_gt() {
21752 Ordering::Greater
21753 } else {
21754 Ordering::Less
21755 }
21756 }) {
21757 Ok(i) | Err(i) => i,
21758 };
21759 for (index, range) in ranges[start_ix..].iter().enumerate() {
21760 if range
21761 .start
21762 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21763 .is_ge()
21764 {
21765 break;
21766 }
21767
21768 let color = color_fetcher(&(start_ix + index), theme);
21769 let start = range.start.to_display_point(display_snapshot);
21770 let end = range.end.to_display_point(display_snapshot);
21771 results.push((start..end, color))
21772 }
21773 }
21774 results
21775 }
21776
21777 pub fn gutter_highlights_in_range(
21778 &self,
21779 search_range: Range<Anchor>,
21780 display_snapshot: &DisplaySnapshot,
21781 cx: &App,
21782 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21783 let mut results = Vec::new();
21784 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21785 let color = color_fetcher(cx);
21786 let start_ix = match ranges.binary_search_by(|probe| {
21787 let cmp = probe
21788 .end
21789 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21790 if cmp.is_gt() {
21791 Ordering::Greater
21792 } else {
21793 Ordering::Less
21794 }
21795 }) {
21796 Ok(i) | Err(i) => i,
21797 };
21798 for range in &ranges[start_ix..] {
21799 if range
21800 .start
21801 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21802 .is_ge()
21803 {
21804 break;
21805 }
21806
21807 let start = range.start.to_display_point(display_snapshot);
21808 let end = range.end.to_display_point(display_snapshot);
21809 results.push((start..end, color))
21810 }
21811 }
21812 results
21813 }
21814
21815 /// Get the text ranges corresponding to the redaction query
21816 pub fn redacted_ranges(
21817 &self,
21818 search_range: Range<Anchor>,
21819 display_snapshot: &DisplaySnapshot,
21820 cx: &App,
21821 ) -> Vec<Range<DisplayPoint>> {
21822 display_snapshot
21823 .buffer_snapshot()
21824 .redacted_ranges(search_range, |file| {
21825 if let Some(file) = file {
21826 file.is_private()
21827 && EditorSettings::get(
21828 Some(SettingsLocation {
21829 worktree_id: file.worktree_id(cx),
21830 path: file.path().as_ref(),
21831 }),
21832 cx,
21833 )
21834 .redact_private_values
21835 } else {
21836 false
21837 }
21838 })
21839 .map(|range| {
21840 range.start.to_display_point(display_snapshot)
21841 ..range.end.to_display_point(display_snapshot)
21842 })
21843 .collect()
21844 }
21845
21846 pub fn highlight_text_key<T: 'static>(
21847 &mut self,
21848 key: usize,
21849 ranges: Vec<Range<Anchor>>,
21850 style: HighlightStyle,
21851 merge: bool,
21852 cx: &mut Context<Self>,
21853 ) {
21854 self.display_map.update(cx, |map, cx| {
21855 map.highlight_text(
21856 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21857 ranges,
21858 style,
21859 merge,
21860 cx,
21861 );
21862 });
21863 cx.notify();
21864 }
21865
21866 pub fn highlight_text<T: 'static>(
21867 &mut self,
21868 ranges: Vec<Range<Anchor>>,
21869 style: HighlightStyle,
21870 cx: &mut Context<Self>,
21871 ) {
21872 self.display_map.update(cx, |map, cx| {
21873 map.highlight_text(
21874 HighlightKey::Type(TypeId::of::<T>()),
21875 ranges,
21876 style,
21877 false,
21878 cx,
21879 )
21880 });
21881 cx.notify();
21882 }
21883
21884 pub fn text_highlights<'a, T: 'static>(
21885 &'a self,
21886 cx: &'a App,
21887 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21888 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21889 }
21890
21891 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21892 let cleared = self
21893 .display_map
21894 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21895 if cleared {
21896 cx.notify();
21897 }
21898 }
21899
21900 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21901 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21902 && self.focus_handle.is_focused(window)
21903 }
21904
21905 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21906 self.show_cursor_when_unfocused = is_enabled;
21907 cx.notify();
21908 }
21909
21910 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21911 cx.notify();
21912 }
21913
21914 fn on_debug_session_event(
21915 &mut self,
21916 _session: Entity<Session>,
21917 event: &SessionEvent,
21918 cx: &mut Context<Self>,
21919 ) {
21920 if let SessionEvent::InvalidateInlineValue = event {
21921 self.refresh_inline_values(cx);
21922 }
21923 }
21924
21925 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21926 let Some(project) = self.project.clone() else {
21927 return;
21928 };
21929
21930 if !self.inline_value_cache.enabled {
21931 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21932 self.splice_inlays(&inlays, Vec::new(), cx);
21933 return;
21934 }
21935
21936 let current_execution_position = self
21937 .highlighted_rows
21938 .get(&TypeId::of::<ActiveDebugLine>())
21939 .and_then(|lines| lines.last().map(|line| line.range.end));
21940
21941 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21942 let inline_values = editor
21943 .update(cx, |editor, cx| {
21944 let Some(current_execution_position) = current_execution_position else {
21945 return Some(Task::ready(Ok(Vec::new())));
21946 };
21947
21948 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21949 let snapshot = buffer.snapshot(cx);
21950
21951 let excerpt = snapshot.excerpt_containing(
21952 current_execution_position..current_execution_position,
21953 )?;
21954
21955 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21956 })?;
21957
21958 let range =
21959 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21960
21961 project.inline_values(buffer, range, cx)
21962 })
21963 .ok()
21964 .flatten()?
21965 .await
21966 .context("refreshing debugger inlays")
21967 .log_err()?;
21968
21969 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21970
21971 for (buffer_id, inline_value) in inline_values
21972 .into_iter()
21973 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21974 {
21975 buffer_inline_values
21976 .entry(buffer_id)
21977 .or_default()
21978 .push(inline_value);
21979 }
21980
21981 editor
21982 .update(cx, |editor, cx| {
21983 let snapshot = editor.buffer.read(cx).snapshot(cx);
21984 let mut new_inlays = Vec::default();
21985
21986 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21987 let buffer_id = buffer_snapshot.remote_id();
21988 buffer_inline_values
21989 .get(&buffer_id)
21990 .into_iter()
21991 .flatten()
21992 .for_each(|hint| {
21993 let inlay = Inlay::debugger(
21994 post_inc(&mut editor.next_inlay_id),
21995 Anchor::in_buffer(excerpt_id, hint.position),
21996 hint.text(),
21997 );
21998 if !inlay.text().chars().contains(&'\n') {
21999 new_inlays.push(inlay);
22000 }
22001 });
22002 }
22003
22004 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
22005 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
22006
22007 editor.splice_inlays(&inlay_ids, new_inlays, cx);
22008 })
22009 .ok()?;
22010 Some(())
22011 });
22012 }
22013
22014 fn on_buffer_event(
22015 &mut self,
22016 multibuffer: &Entity<MultiBuffer>,
22017 event: &multi_buffer::Event,
22018 window: &mut Window,
22019 cx: &mut Context<Self>,
22020 ) {
22021 match event {
22022 multi_buffer::Event::Edited { edited_buffer } => {
22023 self.scrollbar_marker_state.dirty = true;
22024 self.active_indent_guides_state.dirty = true;
22025 self.refresh_active_diagnostics(cx);
22026 self.refresh_code_actions(window, cx);
22027 self.refresh_single_line_folds(window, cx);
22028 self.refresh_matching_bracket_highlights(window, cx);
22029 if self.has_active_edit_prediction() {
22030 self.update_visible_edit_prediction(window, cx);
22031 }
22032
22033 if let Some(buffer) = edited_buffer {
22034 if buffer.read(cx).file().is_none() {
22035 cx.emit(EditorEvent::TitleChanged);
22036 }
22037
22038 if self.project.is_some() {
22039 let buffer_id = buffer.read(cx).remote_id();
22040 self.register_buffer(buffer_id, cx);
22041 self.update_lsp_data(Some(buffer_id), window, cx);
22042 self.refresh_inlay_hints(
22043 InlayHintRefreshReason::BufferEdited(buffer_id),
22044 cx,
22045 );
22046 }
22047 }
22048
22049 cx.emit(EditorEvent::BufferEdited);
22050 cx.emit(SearchEvent::MatchesInvalidated);
22051
22052 let Some(project) = &self.project else { return };
22053 let (telemetry, is_via_ssh) = {
22054 let project = project.read(cx);
22055 let telemetry = project.client().telemetry().clone();
22056 let is_via_ssh = project.is_via_remote_server();
22057 (telemetry, is_via_ssh)
22058 };
22059 telemetry.log_edit_event("editor", is_via_ssh);
22060 }
22061 multi_buffer::Event::ExcerptsAdded {
22062 buffer,
22063 predecessor,
22064 excerpts,
22065 } => {
22066 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22067 let buffer_id = buffer.read(cx).remote_id();
22068 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22069 && let Some(project) = &self.project
22070 {
22071 update_uncommitted_diff_for_buffer(
22072 cx.entity(),
22073 project,
22074 [buffer.clone()],
22075 self.buffer.clone(),
22076 cx,
22077 )
22078 .detach();
22079 }
22080 self.update_lsp_data(Some(buffer_id), window, cx);
22081 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22082 self.colorize_brackets(false, cx);
22083 cx.emit(EditorEvent::ExcerptsAdded {
22084 buffer: buffer.clone(),
22085 predecessor: *predecessor,
22086 excerpts: excerpts.clone(),
22087 });
22088 }
22089 multi_buffer::Event::ExcerptsRemoved {
22090 ids,
22091 removed_buffer_ids,
22092 } => {
22093 if let Some(inlay_hints) = &mut self.inlay_hints {
22094 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22095 }
22096 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22097 for buffer_id in removed_buffer_ids {
22098 self.registered_buffers.remove(buffer_id);
22099 }
22100 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22101 cx.emit(EditorEvent::ExcerptsRemoved {
22102 ids: ids.clone(),
22103 removed_buffer_ids: removed_buffer_ids.clone(),
22104 });
22105 }
22106 multi_buffer::Event::ExcerptsEdited {
22107 excerpt_ids,
22108 buffer_ids,
22109 } => {
22110 self.display_map.update(cx, |map, cx| {
22111 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22112 });
22113 cx.emit(EditorEvent::ExcerptsEdited {
22114 ids: excerpt_ids.clone(),
22115 });
22116 }
22117 multi_buffer::Event::ExcerptsExpanded { ids } => {
22118 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22119 self.refresh_document_highlights(cx);
22120 for id in ids {
22121 self.fetched_tree_sitter_chunks.remove(id);
22122 }
22123 self.colorize_brackets(false, cx);
22124 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22125 }
22126 multi_buffer::Event::Reparsed(buffer_id) => {
22127 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22128 self.refresh_selected_text_highlights(true, window, cx);
22129 self.colorize_brackets(true, cx);
22130 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22131
22132 cx.emit(EditorEvent::Reparsed(*buffer_id));
22133 }
22134 multi_buffer::Event::DiffHunksToggled => {
22135 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22136 }
22137 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22138 if !is_fresh_language {
22139 self.registered_buffers.remove(&buffer_id);
22140 }
22141 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22142 cx.emit(EditorEvent::Reparsed(*buffer_id));
22143 cx.notify();
22144 }
22145 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22146 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22147 multi_buffer::Event::FileHandleChanged
22148 | multi_buffer::Event::Reloaded
22149 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22150 multi_buffer::Event::DiagnosticsUpdated => {
22151 self.update_diagnostics_state(window, cx);
22152 }
22153 _ => {}
22154 };
22155 }
22156
22157 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22158 if !self.diagnostics_enabled() {
22159 return;
22160 }
22161 self.refresh_active_diagnostics(cx);
22162 self.refresh_inline_diagnostics(true, window, cx);
22163 self.scrollbar_marker_state.dirty = true;
22164 cx.notify();
22165 }
22166
22167 pub fn start_temporary_diff_override(&mut self) {
22168 self.load_diff_task.take();
22169 self.temporary_diff_override = true;
22170 }
22171
22172 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22173 self.temporary_diff_override = false;
22174 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22175 self.buffer.update(cx, |buffer, cx| {
22176 buffer.set_all_diff_hunks_collapsed(cx);
22177 });
22178
22179 if let Some(project) = self.project.clone() {
22180 self.load_diff_task = Some(
22181 update_uncommitted_diff_for_buffer(
22182 cx.entity(),
22183 &project,
22184 self.buffer.read(cx).all_buffers(),
22185 self.buffer.clone(),
22186 cx,
22187 )
22188 .shared(),
22189 );
22190 }
22191 }
22192
22193 fn on_display_map_changed(
22194 &mut self,
22195 _: Entity<DisplayMap>,
22196 _: &mut Window,
22197 cx: &mut Context<Self>,
22198 ) {
22199 cx.notify();
22200 }
22201
22202 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22203 if !self.mode.is_full() {
22204 return None;
22205 }
22206
22207 let theme_settings = theme::ThemeSettings::get_global(cx);
22208 let theme = cx.theme();
22209 let accent_colors = theme.accents().clone();
22210
22211 let accent_overrides = theme_settings
22212 .theme_overrides
22213 .get(theme.name.as_ref())
22214 .map(|theme_style| &theme_style.accents)
22215 .into_iter()
22216 .flatten()
22217 .chain(
22218 theme_settings
22219 .experimental_theme_overrides
22220 .as_ref()
22221 .map(|overrides| &overrides.accents)
22222 .into_iter()
22223 .flatten(),
22224 )
22225 .flat_map(|accent| accent.0.clone())
22226 .collect();
22227
22228 Some(AccentData {
22229 colors: accent_colors,
22230 overrides: accent_overrides,
22231 })
22232 }
22233
22234 fn fetch_applicable_language_settings(
22235 &self,
22236 cx: &App,
22237 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22238 if !self.mode.is_full() {
22239 return HashMap::default();
22240 }
22241
22242 self.buffer().read(cx).all_buffers().into_iter().fold(
22243 HashMap::default(),
22244 |mut acc, buffer| {
22245 let buffer = buffer.read(cx);
22246 let language = buffer.language().map(|language| language.name());
22247 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22248 let file = buffer.file();
22249 v.insert(language_settings(language, file, cx).into_owned());
22250 }
22251 acc
22252 },
22253 )
22254 }
22255
22256 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22257 let new_language_settings = self.fetch_applicable_language_settings(cx);
22258 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22259 self.applicable_language_settings = new_language_settings;
22260
22261 let new_accents = self.fetch_accent_data(cx);
22262 let accents_changed = new_accents != self.accent_data;
22263 self.accent_data = new_accents;
22264
22265 if self.diagnostics_enabled() {
22266 let new_severity = EditorSettings::get_global(cx)
22267 .diagnostics_max_severity
22268 .unwrap_or(DiagnosticSeverity::Hint);
22269 self.set_max_diagnostics_severity(new_severity, cx);
22270 }
22271 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22272 self.update_edit_prediction_settings(cx);
22273 self.refresh_edit_prediction(true, false, window, cx);
22274 self.refresh_inline_values(cx);
22275 self.refresh_inlay_hints(
22276 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22277 self.selections.newest_anchor().head(),
22278 &self.buffer.read(cx).snapshot(cx),
22279 cx,
22280 )),
22281 cx,
22282 );
22283
22284 let old_cursor_shape = self.cursor_shape;
22285 let old_show_breadcrumbs = self.show_breadcrumbs;
22286
22287 {
22288 let editor_settings = EditorSettings::get_global(cx);
22289 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22290 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22291 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22292 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22293 }
22294
22295 if old_cursor_shape != self.cursor_shape {
22296 cx.emit(EditorEvent::CursorShapeChanged);
22297 }
22298
22299 if old_show_breadcrumbs != self.show_breadcrumbs {
22300 cx.emit(EditorEvent::BreadcrumbsChanged);
22301 }
22302
22303 let project_settings = ProjectSettings::get_global(cx);
22304 self.buffer_serialization = self
22305 .should_serialize_buffer()
22306 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22307
22308 if self.mode.is_full() {
22309 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22310 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22311 if self.show_inline_diagnostics != show_inline_diagnostics {
22312 self.show_inline_diagnostics = show_inline_diagnostics;
22313 self.refresh_inline_diagnostics(false, window, cx);
22314 }
22315
22316 if self.git_blame_inline_enabled != inline_blame_enabled {
22317 self.toggle_git_blame_inline_internal(false, window, cx);
22318 }
22319
22320 let minimap_settings = EditorSettings::get_global(cx).minimap;
22321 if self.minimap_visibility != MinimapVisibility::Disabled {
22322 if self.minimap_visibility.settings_visibility()
22323 != minimap_settings.minimap_enabled()
22324 {
22325 self.set_minimap_visibility(
22326 MinimapVisibility::for_mode(self.mode(), cx),
22327 window,
22328 cx,
22329 );
22330 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22331 minimap_entity.update(cx, |minimap_editor, cx| {
22332 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22333 })
22334 }
22335 }
22336
22337 if language_settings_changed || accents_changed {
22338 self.colorize_brackets(true, cx);
22339 }
22340
22341 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22342 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22343 }) {
22344 if !inlay_splice.is_empty() {
22345 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22346 }
22347 self.refresh_colors_for_visible_range(None, window, cx);
22348 }
22349 }
22350
22351 cx.notify();
22352 }
22353
22354 pub fn set_searchable(&mut self, searchable: bool) {
22355 self.searchable = searchable;
22356 }
22357
22358 pub fn searchable(&self) -> bool {
22359 self.searchable
22360 }
22361
22362 pub fn open_excerpts_in_split(
22363 &mut self,
22364 _: &OpenExcerptsSplit,
22365 window: &mut Window,
22366 cx: &mut Context<Self>,
22367 ) {
22368 self.open_excerpts_common(None, true, window, cx)
22369 }
22370
22371 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22372 self.open_excerpts_common(None, false, window, cx)
22373 }
22374
22375 fn open_excerpts_common(
22376 &mut self,
22377 jump_data: Option<JumpData>,
22378 split: bool,
22379 window: &mut Window,
22380 cx: &mut Context<Self>,
22381 ) {
22382 let Some(workspace) = self.workspace() else {
22383 cx.propagate();
22384 return;
22385 };
22386
22387 if self.buffer.read(cx).is_singleton() {
22388 cx.propagate();
22389 return;
22390 }
22391
22392 let mut new_selections_by_buffer = HashMap::default();
22393 match &jump_data {
22394 Some(JumpData::MultiBufferPoint {
22395 excerpt_id,
22396 position,
22397 anchor,
22398 line_offset_from_top,
22399 }) => {
22400 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22401 if let Some(buffer) = multi_buffer_snapshot
22402 .buffer_id_for_excerpt(*excerpt_id)
22403 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22404 {
22405 let buffer_snapshot = buffer.read(cx).snapshot();
22406 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22407 language::ToPoint::to_point(anchor, &buffer_snapshot)
22408 } else {
22409 buffer_snapshot.clip_point(*position, Bias::Left)
22410 };
22411 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22412 new_selections_by_buffer.insert(
22413 buffer,
22414 (
22415 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22416 Some(*line_offset_from_top),
22417 ),
22418 );
22419 }
22420 }
22421 Some(JumpData::MultiBufferRow {
22422 row,
22423 line_offset_from_top,
22424 }) => {
22425 let point = MultiBufferPoint::new(row.0, 0);
22426 if let Some((buffer, buffer_point, _)) =
22427 self.buffer.read(cx).point_to_buffer_point(point, cx)
22428 {
22429 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22430 new_selections_by_buffer
22431 .entry(buffer)
22432 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22433 .0
22434 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22435 }
22436 }
22437 None => {
22438 let selections = self
22439 .selections
22440 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22441 let multi_buffer = self.buffer.read(cx);
22442 for selection in selections {
22443 for (snapshot, range, _, anchor) in multi_buffer
22444 .snapshot(cx)
22445 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22446 {
22447 if let Some(anchor) = anchor {
22448 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22449 else {
22450 continue;
22451 };
22452 let offset = text::ToOffset::to_offset(
22453 &anchor.text_anchor,
22454 &buffer_handle.read(cx).snapshot(),
22455 );
22456 let range = BufferOffset(offset)..BufferOffset(offset);
22457 new_selections_by_buffer
22458 .entry(buffer_handle)
22459 .or_insert((Vec::new(), None))
22460 .0
22461 .push(range)
22462 } else {
22463 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22464 else {
22465 continue;
22466 };
22467 new_selections_by_buffer
22468 .entry(buffer_handle)
22469 .or_insert((Vec::new(), None))
22470 .0
22471 .push(range)
22472 }
22473 }
22474 }
22475 }
22476 }
22477
22478 new_selections_by_buffer
22479 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22480
22481 if new_selections_by_buffer.is_empty() {
22482 return;
22483 }
22484
22485 // We defer the pane interaction because we ourselves are a workspace item
22486 // and activating a new item causes the pane to call a method on us reentrantly,
22487 // which panics if we're on the stack.
22488 window.defer(cx, move |window, cx| {
22489 workspace.update(cx, |workspace, cx| {
22490 let pane = if split {
22491 workspace.adjacent_pane(window, cx)
22492 } else {
22493 workspace.active_pane().clone()
22494 };
22495
22496 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22497 let buffer_read = buffer.read(cx);
22498 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22499 (true, project::File::from_dyn(Some(file)).is_some())
22500 } else {
22501 (false, false)
22502 };
22503
22504 // If project file is none workspace.open_project_item will fail to open the excerpt
22505 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22506 // so we check if there's a tab match in that case first
22507 let editor = (!has_file || !is_project_file)
22508 .then(|| {
22509 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22510 // so `workspace.open_project_item` will never find them, always opening a new editor.
22511 // Instead, we try to activate the existing editor in the pane first.
22512 let (editor, pane_item_index, pane_item_id) =
22513 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22514 let editor = item.downcast::<Editor>()?;
22515 let singleton_buffer =
22516 editor.read(cx).buffer().read(cx).as_singleton()?;
22517 if singleton_buffer == buffer {
22518 Some((editor, i, item.item_id()))
22519 } else {
22520 None
22521 }
22522 })?;
22523 pane.update(cx, |pane, cx| {
22524 pane.activate_item(pane_item_index, true, true, window, cx);
22525 if !PreviewTabsSettings::get_global(cx)
22526 .enable_preview_from_multibuffer
22527 {
22528 pane.unpreview_item_if_preview(pane_item_id);
22529 }
22530 });
22531 Some(editor)
22532 })
22533 .flatten()
22534 .unwrap_or_else(|| {
22535 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22536 .enable_keep_preview_on_code_navigation;
22537 let allow_new_preview =
22538 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22539 workspace.open_project_item::<Self>(
22540 pane.clone(),
22541 buffer,
22542 true,
22543 true,
22544 keep_old_preview,
22545 allow_new_preview,
22546 window,
22547 cx,
22548 )
22549 });
22550
22551 editor.update(cx, |editor, cx| {
22552 if has_file && !is_project_file {
22553 editor.set_read_only(true);
22554 }
22555 let autoscroll = match scroll_offset {
22556 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22557 None => Autoscroll::newest(),
22558 };
22559 let nav_history = editor.nav_history.take();
22560 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22561 let Some((&excerpt_id, _, buffer_snapshot)) =
22562 multibuffer_snapshot.as_singleton()
22563 else {
22564 return;
22565 };
22566 editor.change_selections(
22567 SelectionEffects::scroll(autoscroll),
22568 window,
22569 cx,
22570 |s| {
22571 s.select_ranges(ranges.into_iter().map(|range| {
22572 let range = buffer_snapshot.anchor_before(range.start)
22573 ..buffer_snapshot.anchor_after(range.end);
22574 multibuffer_snapshot
22575 .anchor_range_in_excerpt(excerpt_id, range)
22576 .unwrap()
22577 }));
22578 },
22579 );
22580 editor.nav_history = nav_history;
22581 });
22582 }
22583 })
22584 });
22585 }
22586
22587 // Allow opening excerpts for buffers that either belong to the current project
22588 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22589 // are also supported so tests and other in-memory views keep working.
22590 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22591 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22592 }
22593
22594 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22595 let snapshot = self.buffer.read(cx).read(cx);
22596 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22597 Some(
22598 ranges
22599 .iter()
22600 .map(move |range| {
22601 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22602 })
22603 .collect(),
22604 )
22605 }
22606
22607 fn selection_replacement_ranges(
22608 &self,
22609 range: Range<MultiBufferOffsetUtf16>,
22610 cx: &mut App,
22611 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22612 let selections = self
22613 .selections
22614 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22615 let newest_selection = selections
22616 .iter()
22617 .max_by_key(|selection| selection.id)
22618 .unwrap();
22619 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22620 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22621 let snapshot = self.buffer.read(cx).read(cx);
22622 selections
22623 .into_iter()
22624 .map(|mut selection| {
22625 selection.start.0.0 =
22626 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22627 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22628 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22629 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22630 })
22631 .collect()
22632 }
22633
22634 fn report_editor_event(
22635 &self,
22636 reported_event: ReportEditorEvent,
22637 file_extension: Option<String>,
22638 cx: &App,
22639 ) {
22640 if cfg!(any(test, feature = "test-support")) {
22641 return;
22642 }
22643
22644 let Some(project) = &self.project else { return };
22645
22646 // If None, we are in a file without an extension
22647 let file = self
22648 .buffer
22649 .read(cx)
22650 .as_singleton()
22651 .and_then(|b| b.read(cx).file());
22652 let file_extension = file_extension.or(file
22653 .as_ref()
22654 .and_then(|file| Path::new(file.file_name(cx)).extension())
22655 .and_then(|e| e.to_str())
22656 .map(|a| a.to_string()));
22657
22658 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22659 .map(|vim_mode| vim_mode.0)
22660 .unwrap_or(false);
22661
22662 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22663 let copilot_enabled = edit_predictions_provider
22664 == language::language_settings::EditPredictionProvider::Copilot;
22665 let copilot_enabled_for_language = self
22666 .buffer
22667 .read(cx)
22668 .language_settings(cx)
22669 .show_edit_predictions;
22670
22671 let project = project.read(cx);
22672 let event_type = reported_event.event_type();
22673
22674 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22675 telemetry::event!(
22676 event_type,
22677 type = if auto_saved {"autosave"} else {"manual"},
22678 file_extension,
22679 vim_mode,
22680 copilot_enabled,
22681 copilot_enabled_for_language,
22682 edit_predictions_provider,
22683 is_via_ssh = project.is_via_remote_server(),
22684 );
22685 } else {
22686 telemetry::event!(
22687 event_type,
22688 file_extension,
22689 vim_mode,
22690 copilot_enabled,
22691 copilot_enabled_for_language,
22692 edit_predictions_provider,
22693 is_via_ssh = project.is_via_remote_server(),
22694 );
22695 };
22696 }
22697
22698 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22699 /// with each line being an array of {text, highlight} objects.
22700 fn copy_highlight_json(
22701 &mut self,
22702 _: &CopyHighlightJson,
22703 window: &mut Window,
22704 cx: &mut Context<Self>,
22705 ) {
22706 #[derive(Serialize)]
22707 struct Chunk<'a> {
22708 text: String,
22709 highlight: Option<&'a str>,
22710 }
22711
22712 let snapshot = self.buffer.read(cx).snapshot(cx);
22713 let range = self
22714 .selected_text_range(false, window, cx)
22715 .and_then(|selection| {
22716 if selection.range.is_empty() {
22717 None
22718 } else {
22719 Some(
22720 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22721 selection.range.start,
22722 )))
22723 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22724 selection.range.end,
22725 ))),
22726 )
22727 }
22728 })
22729 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22730
22731 let chunks = snapshot.chunks(range, true);
22732 let mut lines = Vec::new();
22733 let mut line: VecDeque<Chunk> = VecDeque::new();
22734
22735 let Some(style) = self.style.as_ref() else {
22736 return;
22737 };
22738
22739 for chunk in chunks {
22740 let highlight = chunk
22741 .syntax_highlight_id
22742 .and_then(|id| id.name(&style.syntax));
22743 let mut chunk_lines = chunk.text.split('\n').peekable();
22744 while let Some(text) = chunk_lines.next() {
22745 let mut merged_with_last_token = false;
22746 if let Some(last_token) = line.back_mut()
22747 && last_token.highlight == highlight
22748 {
22749 last_token.text.push_str(text);
22750 merged_with_last_token = true;
22751 }
22752
22753 if !merged_with_last_token {
22754 line.push_back(Chunk {
22755 text: text.into(),
22756 highlight,
22757 });
22758 }
22759
22760 if chunk_lines.peek().is_some() {
22761 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22762 line.pop_front();
22763 }
22764 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22765 line.pop_back();
22766 }
22767
22768 lines.push(mem::take(&mut line));
22769 }
22770 }
22771 }
22772
22773 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22774 return;
22775 };
22776 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22777 }
22778
22779 pub fn open_context_menu(
22780 &mut self,
22781 _: &OpenContextMenu,
22782 window: &mut Window,
22783 cx: &mut Context<Self>,
22784 ) {
22785 self.request_autoscroll(Autoscroll::newest(), cx);
22786 let position = self
22787 .selections
22788 .newest_display(&self.display_snapshot(cx))
22789 .start;
22790 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22791 }
22792
22793 pub fn replay_insert_event(
22794 &mut self,
22795 text: &str,
22796 relative_utf16_range: Option<Range<isize>>,
22797 window: &mut Window,
22798 cx: &mut Context<Self>,
22799 ) {
22800 if !self.input_enabled {
22801 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22802 return;
22803 }
22804 if let Some(relative_utf16_range) = relative_utf16_range {
22805 let selections = self
22806 .selections
22807 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22808 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22809 let new_ranges = selections.into_iter().map(|range| {
22810 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22811 range
22812 .head()
22813 .0
22814 .0
22815 .saturating_add_signed(relative_utf16_range.start),
22816 ));
22817 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22818 range
22819 .head()
22820 .0
22821 .0
22822 .saturating_add_signed(relative_utf16_range.end),
22823 ));
22824 start..end
22825 });
22826 s.select_ranges(new_ranges);
22827 });
22828 }
22829
22830 self.handle_input(text, window, cx);
22831 }
22832
22833 pub fn is_focused(&self, window: &Window) -> bool {
22834 self.focus_handle.is_focused(window)
22835 }
22836
22837 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22838 cx.emit(EditorEvent::Focused);
22839
22840 if let Some(descendant) = self
22841 .last_focused_descendant
22842 .take()
22843 .and_then(|descendant| descendant.upgrade())
22844 {
22845 window.focus(&descendant);
22846 } else {
22847 if let Some(blame) = self.blame.as_ref() {
22848 blame.update(cx, GitBlame::focus)
22849 }
22850
22851 self.blink_manager.update(cx, BlinkManager::enable);
22852 self.show_cursor_names(window, cx);
22853 self.buffer.update(cx, |buffer, cx| {
22854 buffer.finalize_last_transaction(cx);
22855 if self.leader_id.is_none() {
22856 buffer.set_active_selections(
22857 &self.selections.disjoint_anchors_arc(),
22858 self.selections.line_mode(),
22859 self.cursor_shape,
22860 cx,
22861 );
22862 }
22863 });
22864
22865 if let Some(position_map) = self.last_position_map.clone() {
22866 EditorElement::mouse_moved(
22867 self,
22868 &MouseMoveEvent {
22869 position: window.mouse_position(),
22870 pressed_button: None,
22871 modifiers: window.modifiers(),
22872 },
22873 &position_map,
22874 window,
22875 cx,
22876 );
22877 }
22878 }
22879 }
22880
22881 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22882 cx.emit(EditorEvent::FocusedIn)
22883 }
22884
22885 fn handle_focus_out(
22886 &mut self,
22887 event: FocusOutEvent,
22888 _window: &mut Window,
22889 cx: &mut Context<Self>,
22890 ) {
22891 if event.blurred != self.focus_handle {
22892 self.last_focused_descendant = Some(event.blurred);
22893 }
22894 self.selection_drag_state = SelectionDragState::None;
22895 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22896 }
22897
22898 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22899 self.blink_manager.update(cx, BlinkManager::disable);
22900 self.buffer
22901 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22902
22903 if let Some(blame) = self.blame.as_ref() {
22904 blame.update(cx, GitBlame::blur)
22905 }
22906 if !self.hover_state.focused(window, cx) {
22907 hide_hover(self, cx);
22908 }
22909 if !self
22910 .context_menu
22911 .borrow()
22912 .as_ref()
22913 .is_some_and(|context_menu| context_menu.focused(window, cx))
22914 {
22915 self.hide_context_menu(window, cx);
22916 }
22917 self.take_active_edit_prediction(cx);
22918 cx.emit(EditorEvent::Blurred);
22919 cx.notify();
22920 }
22921
22922 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22923 let mut pending: String = window
22924 .pending_input_keystrokes()
22925 .into_iter()
22926 .flatten()
22927 .filter_map(|keystroke| keystroke.key_char.clone())
22928 .collect();
22929
22930 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22931 pending = "".to_string();
22932 }
22933
22934 let existing_pending = self
22935 .text_highlights::<PendingInput>(cx)
22936 .map(|(_, ranges)| ranges.to_vec());
22937 if existing_pending.is_none() && pending.is_empty() {
22938 return;
22939 }
22940 let transaction =
22941 self.transact(window, cx, |this, window, cx| {
22942 let selections = this
22943 .selections
22944 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22945 let edits = selections
22946 .iter()
22947 .map(|selection| (selection.end..selection.end, pending.clone()));
22948 this.edit(edits, cx);
22949 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22950 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22951 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22952 }));
22953 });
22954 if let Some(existing_ranges) = existing_pending {
22955 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22956 this.edit(edits, cx);
22957 }
22958 });
22959
22960 let snapshot = self.snapshot(window, cx);
22961 let ranges = self
22962 .selections
22963 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22964 .into_iter()
22965 .map(|selection| {
22966 snapshot.buffer_snapshot().anchor_after(selection.end)
22967 ..snapshot
22968 .buffer_snapshot()
22969 .anchor_before(selection.end + pending.len())
22970 })
22971 .collect();
22972
22973 if pending.is_empty() {
22974 self.clear_highlights::<PendingInput>(cx);
22975 } else {
22976 self.highlight_text::<PendingInput>(
22977 ranges,
22978 HighlightStyle {
22979 underline: Some(UnderlineStyle {
22980 thickness: px(1.),
22981 color: None,
22982 wavy: false,
22983 }),
22984 ..Default::default()
22985 },
22986 cx,
22987 );
22988 }
22989
22990 self.ime_transaction = self.ime_transaction.or(transaction);
22991 if let Some(transaction) = self.ime_transaction {
22992 self.buffer.update(cx, |buffer, cx| {
22993 buffer.group_until_transaction(transaction, cx);
22994 });
22995 }
22996
22997 if self.text_highlights::<PendingInput>(cx).is_none() {
22998 self.ime_transaction.take();
22999 }
23000 }
23001
23002 pub fn register_action_renderer(
23003 &mut self,
23004 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
23005 ) -> Subscription {
23006 let id = self.next_editor_action_id.post_inc();
23007 self.editor_actions
23008 .borrow_mut()
23009 .insert(id, Box::new(listener));
23010
23011 let editor_actions = self.editor_actions.clone();
23012 Subscription::new(move || {
23013 editor_actions.borrow_mut().remove(&id);
23014 })
23015 }
23016
23017 pub fn register_action<A: Action>(
23018 &mut self,
23019 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
23020 ) -> Subscription {
23021 let id = self.next_editor_action_id.post_inc();
23022 let listener = Arc::new(listener);
23023 self.editor_actions.borrow_mut().insert(
23024 id,
23025 Box::new(move |_, window, _| {
23026 let listener = listener.clone();
23027 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
23028 let action = action.downcast_ref().unwrap();
23029 if phase == DispatchPhase::Bubble {
23030 listener(action, window, cx)
23031 }
23032 })
23033 }),
23034 );
23035
23036 let editor_actions = self.editor_actions.clone();
23037 Subscription::new(move || {
23038 editor_actions.borrow_mut().remove(&id);
23039 })
23040 }
23041
23042 pub fn file_header_size(&self) -> u32 {
23043 FILE_HEADER_HEIGHT
23044 }
23045
23046 pub fn restore(
23047 &mut self,
23048 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
23049 window: &mut Window,
23050 cx: &mut Context<Self>,
23051 ) {
23052 self.buffer().update(cx, |multi_buffer, cx| {
23053 for (buffer_id, changes) in revert_changes {
23054 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
23055 buffer.update(cx, |buffer, cx| {
23056 buffer.edit(
23057 changes
23058 .into_iter()
23059 .map(|(range, text)| (range, text.to_string())),
23060 None,
23061 cx,
23062 );
23063 });
23064 }
23065 }
23066 });
23067 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23068 selections.refresh()
23069 });
23070 }
23071
23072 pub fn to_pixel_point(
23073 &mut self,
23074 source: multi_buffer::Anchor,
23075 editor_snapshot: &EditorSnapshot,
23076 window: &mut Window,
23077 cx: &App,
23078 ) -> Option<gpui::Point<Pixels>> {
23079 let source_point = source.to_display_point(editor_snapshot);
23080 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23081 }
23082
23083 pub fn display_to_pixel_point(
23084 &mut self,
23085 source: DisplayPoint,
23086 editor_snapshot: &EditorSnapshot,
23087 window: &mut Window,
23088 cx: &App,
23089 ) -> Option<gpui::Point<Pixels>> {
23090 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23091 let text_layout_details = self.text_layout_details(window);
23092 let scroll_top = text_layout_details
23093 .scroll_anchor
23094 .scroll_position(editor_snapshot)
23095 .y;
23096
23097 if source.row().as_f64() < scroll_top.floor() {
23098 return None;
23099 }
23100 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23101 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23102 Some(gpui::Point::new(source_x, source_y))
23103 }
23104
23105 pub fn has_visible_completions_menu(&self) -> bool {
23106 !self.edit_prediction_preview_is_active()
23107 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23108 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23109 })
23110 }
23111
23112 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23113 if self.mode.is_minimap() {
23114 return;
23115 }
23116 self.addons
23117 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23118 }
23119
23120 pub fn unregister_addon<T: Addon>(&mut self) {
23121 self.addons.remove(&std::any::TypeId::of::<T>());
23122 }
23123
23124 pub fn addon<T: Addon>(&self) -> Option<&T> {
23125 let type_id = std::any::TypeId::of::<T>();
23126 self.addons
23127 .get(&type_id)
23128 .and_then(|item| item.to_any().downcast_ref::<T>())
23129 }
23130
23131 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23132 let type_id = std::any::TypeId::of::<T>();
23133 self.addons
23134 .get_mut(&type_id)
23135 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23136 }
23137
23138 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23139 let text_layout_details = self.text_layout_details(window);
23140 let style = &text_layout_details.editor_style;
23141 let font_id = window.text_system().resolve_font(&style.text.font());
23142 let font_size = style.text.font_size.to_pixels(window.rem_size());
23143 let line_height = style.text.line_height_in_pixels(window.rem_size());
23144 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23145 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23146
23147 CharacterDimensions {
23148 em_width,
23149 em_advance,
23150 line_height,
23151 }
23152 }
23153
23154 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23155 self.load_diff_task.clone()
23156 }
23157
23158 fn read_metadata_from_db(
23159 &mut self,
23160 item_id: u64,
23161 workspace_id: WorkspaceId,
23162 window: &mut Window,
23163 cx: &mut Context<Editor>,
23164 ) {
23165 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23166 && !self.mode.is_minimap()
23167 && WorkspaceSettings::get(None, cx).restore_on_startup
23168 != RestoreOnStartupBehavior::EmptyTab
23169 {
23170 let buffer_snapshot = OnceCell::new();
23171
23172 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23173 && !folds.is_empty()
23174 {
23175 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23176 self.fold_ranges(
23177 folds
23178 .into_iter()
23179 .map(|(start, end)| {
23180 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23181 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23182 })
23183 .collect(),
23184 false,
23185 window,
23186 cx,
23187 );
23188 }
23189
23190 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23191 && !selections.is_empty()
23192 {
23193 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23194 // skip adding the initial selection to selection history
23195 self.selection_history.mode = SelectionHistoryMode::Skipping;
23196 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23197 s.select_ranges(selections.into_iter().map(|(start, end)| {
23198 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23199 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23200 }));
23201 });
23202 self.selection_history.mode = SelectionHistoryMode::Normal;
23203 };
23204 }
23205
23206 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23207 }
23208
23209 fn update_lsp_data(
23210 &mut self,
23211 for_buffer: Option<BufferId>,
23212 window: &mut Window,
23213 cx: &mut Context<'_, Self>,
23214 ) {
23215 self.pull_diagnostics(for_buffer, window, cx);
23216 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23217 }
23218
23219 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23220 if self.ignore_lsp_data() {
23221 return;
23222 }
23223 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23224 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23225 }
23226 }
23227
23228 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23229 if self.ignore_lsp_data() {
23230 return;
23231 }
23232
23233 if !self.registered_buffers.contains_key(&buffer_id)
23234 && let Some(project) = self.project.as_ref()
23235 {
23236 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23237 project.update(cx, |project, cx| {
23238 self.registered_buffers.insert(
23239 buffer_id,
23240 project.register_buffer_with_language_servers(&buffer, cx),
23241 );
23242 });
23243 } else {
23244 self.registered_buffers.remove(&buffer_id);
23245 }
23246 }
23247 }
23248
23249 fn ignore_lsp_data(&self) -> bool {
23250 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23251 // skip any LSP updates for it.
23252 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23253 }
23254
23255 fn create_style(&self, cx: &App) -> EditorStyle {
23256 let settings = ThemeSettings::get_global(cx);
23257
23258 let mut text_style = match self.mode {
23259 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23260 color: cx.theme().colors().editor_foreground,
23261 font_family: settings.ui_font.family.clone(),
23262 font_features: settings.ui_font.features.clone(),
23263 font_fallbacks: settings.ui_font.fallbacks.clone(),
23264 font_size: rems(0.875).into(),
23265 font_weight: settings.ui_font.weight,
23266 line_height: relative(settings.buffer_line_height.value()),
23267 ..Default::default()
23268 },
23269 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23270 color: cx.theme().colors().editor_foreground,
23271 font_family: settings.buffer_font.family.clone(),
23272 font_features: settings.buffer_font.features.clone(),
23273 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23274 font_size: settings.buffer_font_size(cx).into(),
23275 font_weight: settings.buffer_font.weight,
23276 line_height: relative(settings.buffer_line_height.value()),
23277 ..Default::default()
23278 },
23279 };
23280 if let Some(text_style_refinement) = &self.text_style_refinement {
23281 text_style.refine(text_style_refinement)
23282 }
23283
23284 let background = match self.mode {
23285 EditorMode::SingleLine => cx.theme().system().transparent,
23286 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23287 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23288 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23289 };
23290
23291 EditorStyle {
23292 background,
23293 border: cx.theme().colors().border,
23294 local_player: cx.theme().players().local(),
23295 text: text_style,
23296 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23297 syntax: cx.theme().syntax().clone(),
23298 status: cx.theme().status().clone(),
23299 inlay_hints_style: make_inlay_hints_style(cx),
23300 edit_prediction_styles: make_suggestion_styles(cx),
23301 unnecessary_code_fade: settings.unnecessary_code_fade,
23302 show_underlines: self.diagnostics_enabled(),
23303 }
23304 }
23305}
23306
23307fn edit_for_markdown_paste<'a>(
23308 buffer: &MultiBufferSnapshot,
23309 range: Range<MultiBufferOffset>,
23310 to_insert: &'a str,
23311 url: Option<url::Url>,
23312) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23313 if url.is_none() {
23314 return (range, Cow::Borrowed(to_insert));
23315 };
23316
23317 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23318
23319 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23320 Cow::Borrowed(to_insert)
23321 } else {
23322 Cow::Owned(format!("[{old_text}]({to_insert})"))
23323 };
23324 (range, new_text)
23325}
23326
23327fn process_completion_for_edit(
23328 completion: &Completion,
23329 intent: CompletionIntent,
23330 buffer: &Entity<Buffer>,
23331 cursor_position: &text::Anchor,
23332 cx: &mut Context<Editor>,
23333) -> CompletionEdit {
23334 let buffer = buffer.read(cx);
23335 let buffer_snapshot = buffer.snapshot();
23336 let (snippet, new_text) = if completion.is_snippet() {
23337 let mut snippet_source = completion.new_text.clone();
23338 // Workaround for typescript language server issues so that methods don't expand within
23339 // strings and functions with type expressions. The previous point is used because the query
23340 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23341 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23342 let previous_point = if previous_point.column > 0 {
23343 cursor_position.to_previous_offset(&buffer_snapshot)
23344 } else {
23345 cursor_position.to_offset(&buffer_snapshot)
23346 };
23347 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23348 && scope.prefers_label_for_snippet_in_completion()
23349 && let Some(label) = completion.label()
23350 && matches!(
23351 completion.kind(),
23352 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23353 )
23354 {
23355 snippet_source = label;
23356 }
23357 match Snippet::parse(&snippet_source).log_err() {
23358 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23359 None => (None, completion.new_text.clone()),
23360 }
23361 } else {
23362 (None, completion.new_text.clone())
23363 };
23364
23365 let mut range_to_replace = {
23366 let replace_range = &completion.replace_range;
23367 if let CompletionSource::Lsp {
23368 insert_range: Some(insert_range),
23369 ..
23370 } = &completion.source
23371 {
23372 debug_assert_eq!(
23373 insert_range.start, replace_range.start,
23374 "insert_range and replace_range should start at the same position"
23375 );
23376 debug_assert!(
23377 insert_range
23378 .start
23379 .cmp(cursor_position, &buffer_snapshot)
23380 .is_le(),
23381 "insert_range should start before or at cursor position"
23382 );
23383 debug_assert!(
23384 replace_range
23385 .start
23386 .cmp(cursor_position, &buffer_snapshot)
23387 .is_le(),
23388 "replace_range should start before or at cursor position"
23389 );
23390
23391 let should_replace = match intent {
23392 CompletionIntent::CompleteWithInsert => false,
23393 CompletionIntent::CompleteWithReplace => true,
23394 CompletionIntent::Complete | CompletionIntent::Compose => {
23395 let insert_mode =
23396 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23397 .completions
23398 .lsp_insert_mode;
23399 match insert_mode {
23400 LspInsertMode::Insert => false,
23401 LspInsertMode::Replace => true,
23402 LspInsertMode::ReplaceSubsequence => {
23403 let mut text_to_replace = buffer.chars_for_range(
23404 buffer.anchor_before(replace_range.start)
23405 ..buffer.anchor_after(replace_range.end),
23406 );
23407 let mut current_needle = text_to_replace.next();
23408 for haystack_ch in completion.label.text.chars() {
23409 if let Some(needle_ch) = current_needle
23410 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23411 {
23412 current_needle = text_to_replace.next();
23413 }
23414 }
23415 current_needle.is_none()
23416 }
23417 LspInsertMode::ReplaceSuffix => {
23418 if replace_range
23419 .end
23420 .cmp(cursor_position, &buffer_snapshot)
23421 .is_gt()
23422 {
23423 let range_after_cursor = *cursor_position..replace_range.end;
23424 let text_after_cursor = buffer
23425 .text_for_range(
23426 buffer.anchor_before(range_after_cursor.start)
23427 ..buffer.anchor_after(range_after_cursor.end),
23428 )
23429 .collect::<String>()
23430 .to_ascii_lowercase();
23431 completion
23432 .label
23433 .text
23434 .to_ascii_lowercase()
23435 .ends_with(&text_after_cursor)
23436 } else {
23437 true
23438 }
23439 }
23440 }
23441 }
23442 };
23443
23444 if should_replace {
23445 replace_range.clone()
23446 } else {
23447 insert_range.clone()
23448 }
23449 } else {
23450 replace_range.clone()
23451 }
23452 };
23453
23454 if range_to_replace
23455 .end
23456 .cmp(cursor_position, &buffer_snapshot)
23457 .is_lt()
23458 {
23459 range_to_replace.end = *cursor_position;
23460 }
23461
23462 let replace_range = range_to_replace.to_offset(buffer);
23463 CompletionEdit {
23464 new_text,
23465 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23466 snippet,
23467 }
23468}
23469
23470struct CompletionEdit {
23471 new_text: String,
23472 replace_range: Range<BufferOffset>,
23473 snippet: Option<Snippet>,
23474}
23475
23476fn insert_extra_newline_brackets(
23477 buffer: &MultiBufferSnapshot,
23478 range: Range<MultiBufferOffset>,
23479 language: &language::LanguageScope,
23480) -> bool {
23481 let leading_whitespace_len = buffer
23482 .reversed_chars_at(range.start)
23483 .take_while(|c| c.is_whitespace() && *c != '\n')
23484 .map(|c| c.len_utf8())
23485 .sum::<usize>();
23486 let trailing_whitespace_len = buffer
23487 .chars_at(range.end)
23488 .take_while(|c| c.is_whitespace() && *c != '\n')
23489 .map(|c| c.len_utf8())
23490 .sum::<usize>();
23491 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23492
23493 language.brackets().any(|(pair, enabled)| {
23494 let pair_start = pair.start.trim_end();
23495 let pair_end = pair.end.trim_start();
23496
23497 enabled
23498 && pair.newline
23499 && buffer.contains_str_at(range.end, pair_end)
23500 && buffer.contains_str_at(
23501 range.start.saturating_sub_usize(pair_start.len()),
23502 pair_start,
23503 )
23504 })
23505}
23506
23507fn insert_extra_newline_tree_sitter(
23508 buffer: &MultiBufferSnapshot,
23509 range: Range<MultiBufferOffset>,
23510) -> bool {
23511 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23512 [(buffer, range, _)] => (*buffer, range.clone()),
23513 _ => return false,
23514 };
23515 let pair = {
23516 let mut result: Option<BracketMatch<usize>> = None;
23517
23518 for pair in buffer
23519 .all_bracket_ranges(range.start.0..range.end.0)
23520 .filter(move |pair| {
23521 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23522 })
23523 {
23524 let len = pair.close_range.end - pair.open_range.start;
23525
23526 if let Some(existing) = &result {
23527 let existing_len = existing.close_range.end - existing.open_range.start;
23528 if len > existing_len {
23529 continue;
23530 }
23531 }
23532
23533 result = Some(pair);
23534 }
23535
23536 result
23537 };
23538 let Some(pair) = pair else {
23539 return false;
23540 };
23541 pair.newline_only
23542 && buffer
23543 .chars_for_range(pair.open_range.end..range.start.0)
23544 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23545 .all(|c| c.is_whitespace() && c != '\n')
23546}
23547
23548fn update_uncommitted_diff_for_buffer(
23549 editor: Entity<Editor>,
23550 project: &Entity<Project>,
23551 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23552 buffer: Entity<MultiBuffer>,
23553 cx: &mut App,
23554) -> Task<()> {
23555 let mut tasks = Vec::new();
23556 project.update(cx, |project, cx| {
23557 for buffer in buffers {
23558 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23559 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23560 }
23561 }
23562 });
23563 cx.spawn(async move |cx| {
23564 let diffs = future::join_all(tasks).await;
23565 if editor
23566 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23567 .unwrap_or(false)
23568 {
23569 return;
23570 }
23571
23572 buffer
23573 .update(cx, |buffer, cx| {
23574 for diff in diffs.into_iter().flatten() {
23575 buffer.add_diff(diff, cx);
23576 }
23577 })
23578 .ok();
23579 })
23580}
23581
23582fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23583 let tab_size = tab_size.get() as usize;
23584 let mut width = offset;
23585
23586 for ch in text.chars() {
23587 width += if ch == '\t' {
23588 tab_size - (width % tab_size)
23589 } else {
23590 1
23591 };
23592 }
23593
23594 width - offset
23595}
23596
23597#[cfg(test)]
23598mod tests {
23599 use super::*;
23600
23601 #[test]
23602 fn test_string_size_with_expanded_tabs() {
23603 let nz = |val| NonZeroU32::new(val).unwrap();
23604 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23605 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23606 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23607 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23608 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23609 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23610 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23611 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23612 }
23613}
23614
23615/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23616struct WordBreakingTokenizer<'a> {
23617 input: &'a str,
23618}
23619
23620impl<'a> WordBreakingTokenizer<'a> {
23621 fn new(input: &'a str) -> Self {
23622 Self { input }
23623 }
23624}
23625
23626fn is_char_ideographic(ch: char) -> bool {
23627 use unicode_script::Script::*;
23628 use unicode_script::UnicodeScript;
23629 matches!(ch.script(), Han | Tangut | Yi)
23630}
23631
23632fn is_grapheme_ideographic(text: &str) -> bool {
23633 text.chars().any(is_char_ideographic)
23634}
23635
23636fn is_grapheme_whitespace(text: &str) -> bool {
23637 text.chars().any(|x| x.is_whitespace())
23638}
23639
23640fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23641 text.chars()
23642 .next()
23643 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23644}
23645
23646#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23647enum WordBreakToken<'a> {
23648 Word { token: &'a str, grapheme_len: usize },
23649 InlineWhitespace { token: &'a str, grapheme_len: usize },
23650 Newline,
23651}
23652
23653impl<'a> Iterator for WordBreakingTokenizer<'a> {
23654 /// Yields a span, the count of graphemes in the token, and whether it was
23655 /// whitespace. Note that it also breaks at word boundaries.
23656 type Item = WordBreakToken<'a>;
23657
23658 fn next(&mut self) -> Option<Self::Item> {
23659 use unicode_segmentation::UnicodeSegmentation;
23660 if self.input.is_empty() {
23661 return None;
23662 }
23663
23664 let mut iter = self.input.graphemes(true).peekable();
23665 let mut offset = 0;
23666 let mut grapheme_len = 0;
23667 if let Some(first_grapheme) = iter.next() {
23668 let is_newline = first_grapheme == "\n";
23669 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23670 offset += first_grapheme.len();
23671 grapheme_len += 1;
23672 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23673 if let Some(grapheme) = iter.peek().copied()
23674 && should_stay_with_preceding_ideograph(grapheme)
23675 {
23676 offset += grapheme.len();
23677 grapheme_len += 1;
23678 }
23679 } else {
23680 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23681 let mut next_word_bound = words.peek().copied();
23682 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23683 next_word_bound = words.next();
23684 }
23685 while let Some(grapheme) = iter.peek().copied() {
23686 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23687 break;
23688 };
23689 if is_grapheme_whitespace(grapheme) != is_whitespace
23690 || (grapheme == "\n") != is_newline
23691 {
23692 break;
23693 };
23694 offset += grapheme.len();
23695 grapheme_len += 1;
23696 iter.next();
23697 }
23698 }
23699 let token = &self.input[..offset];
23700 self.input = &self.input[offset..];
23701 if token == "\n" {
23702 Some(WordBreakToken::Newline)
23703 } else if is_whitespace {
23704 Some(WordBreakToken::InlineWhitespace {
23705 token,
23706 grapheme_len,
23707 })
23708 } else {
23709 Some(WordBreakToken::Word {
23710 token,
23711 grapheme_len,
23712 })
23713 }
23714 } else {
23715 None
23716 }
23717 }
23718}
23719
23720#[test]
23721fn test_word_breaking_tokenizer() {
23722 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23723 ("", &[]),
23724 (" ", &[whitespace(" ", 2)]),
23725 ("Ʒ", &[word("Ʒ", 1)]),
23726 ("Ǽ", &[word("Ǽ", 1)]),
23727 ("⋑", &[word("⋑", 1)]),
23728 ("⋑⋑", &[word("⋑⋑", 2)]),
23729 (
23730 "原理,进而",
23731 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23732 ),
23733 (
23734 "hello world",
23735 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23736 ),
23737 (
23738 "hello, world",
23739 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23740 ),
23741 (
23742 " hello world",
23743 &[
23744 whitespace(" ", 2),
23745 word("hello", 5),
23746 whitespace(" ", 1),
23747 word("world", 5),
23748 ],
23749 ),
23750 (
23751 "这是什么 \n 钢笔",
23752 &[
23753 word("这", 1),
23754 word("是", 1),
23755 word("什", 1),
23756 word("么", 1),
23757 whitespace(" ", 1),
23758 newline(),
23759 whitespace(" ", 1),
23760 word("钢", 1),
23761 word("笔", 1),
23762 ],
23763 ),
23764 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23765 ];
23766
23767 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23768 WordBreakToken::Word {
23769 token,
23770 grapheme_len,
23771 }
23772 }
23773
23774 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23775 WordBreakToken::InlineWhitespace {
23776 token,
23777 grapheme_len,
23778 }
23779 }
23780
23781 fn newline() -> WordBreakToken<'static> {
23782 WordBreakToken::Newline
23783 }
23784
23785 for (input, result) in tests {
23786 assert_eq!(
23787 WordBreakingTokenizer::new(input)
23788 .collect::<Vec<_>>()
23789 .as_slice(),
23790 *result,
23791 );
23792 }
23793}
23794
23795fn wrap_with_prefix(
23796 first_line_prefix: String,
23797 subsequent_lines_prefix: String,
23798 unwrapped_text: String,
23799 wrap_column: usize,
23800 tab_size: NonZeroU32,
23801 preserve_existing_whitespace: bool,
23802) -> String {
23803 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23804 let subsequent_lines_prefix_len =
23805 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23806 let mut wrapped_text = String::new();
23807 let mut current_line = first_line_prefix;
23808 let mut is_first_line = true;
23809
23810 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23811 let mut current_line_len = first_line_prefix_len;
23812 let mut in_whitespace = false;
23813 for token in tokenizer {
23814 let have_preceding_whitespace = in_whitespace;
23815 match token {
23816 WordBreakToken::Word {
23817 token,
23818 grapheme_len,
23819 } => {
23820 in_whitespace = false;
23821 let current_prefix_len = if is_first_line {
23822 first_line_prefix_len
23823 } else {
23824 subsequent_lines_prefix_len
23825 };
23826 if current_line_len + grapheme_len > wrap_column
23827 && current_line_len != current_prefix_len
23828 {
23829 wrapped_text.push_str(current_line.trim_end());
23830 wrapped_text.push('\n');
23831 is_first_line = false;
23832 current_line = subsequent_lines_prefix.clone();
23833 current_line_len = subsequent_lines_prefix_len;
23834 }
23835 current_line.push_str(token);
23836 current_line_len += grapheme_len;
23837 }
23838 WordBreakToken::InlineWhitespace {
23839 mut token,
23840 mut grapheme_len,
23841 } => {
23842 in_whitespace = true;
23843 if have_preceding_whitespace && !preserve_existing_whitespace {
23844 continue;
23845 }
23846 if !preserve_existing_whitespace {
23847 // Keep a single whitespace grapheme as-is
23848 if let Some(first) =
23849 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23850 {
23851 token = first;
23852 } else {
23853 token = " ";
23854 }
23855 grapheme_len = 1;
23856 }
23857 let current_prefix_len = if is_first_line {
23858 first_line_prefix_len
23859 } else {
23860 subsequent_lines_prefix_len
23861 };
23862 if current_line_len + grapheme_len > wrap_column {
23863 wrapped_text.push_str(current_line.trim_end());
23864 wrapped_text.push('\n');
23865 is_first_line = false;
23866 current_line = subsequent_lines_prefix.clone();
23867 current_line_len = subsequent_lines_prefix_len;
23868 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23869 current_line.push_str(token);
23870 current_line_len += grapheme_len;
23871 }
23872 }
23873 WordBreakToken::Newline => {
23874 in_whitespace = true;
23875 let current_prefix_len = if is_first_line {
23876 first_line_prefix_len
23877 } else {
23878 subsequent_lines_prefix_len
23879 };
23880 if preserve_existing_whitespace {
23881 wrapped_text.push_str(current_line.trim_end());
23882 wrapped_text.push('\n');
23883 is_first_line = false;
23884 current_line = subsequent_lines_prefix.clone();
23885 current_line_len = subsequent_lines_prefix_len;
23886 } else if have_preceding_whitespace {
23887 continue;
23888 } else if current_line_len + 1 > wrap_column
23889 && current_line_len != current_prefix_len
23890 {
23891 wrapped_text.push_str(current_line.trim_end());
23892 wrapped_text.push('\n');
23893 is_first_line = false;
23894 current_line = subsequent_lines_prefix.clone();
23895 current_line_len = subsequent_lines_prefix_len;
23896 } else if current_line_len != current_prefix_len {
23897 current_line.push(' ');
23898 current_line_len += 1;
23899 }
23900 }
23901 }
23902 }
23903
23904 if !current_line.is_empty() {
23905 wrapped_text.push_str(¤t_line);
23906 }
23907 wrapped_text
23908}
23909
23910#[test]
23911fn test_wrap_with_prefix() {
23912 assert_eq!(
23913 wrap_with_prefix(
23914 "# ".to_string(),
23915 "# ".to_string(),
23916 "abcdefg".to_string(),
23917 4,
23918 NonZeroU32::new(4).unwrap(),
23919 false,
23920 ),
23921 "# abcdefg"
23922 );
23923 assert_eq!(
23924 wrap_with_prefix(
23925 "".to_string(),
23926 "".to_string(),
23927 "\thello world".to_string(),
23928 8,
23929 NonZeroU32::new(4).unwrap(),
23930 false,
23931 ),
23932 "hello\nworld"
23933 );
23934 assert_eq!(
23935 wrap_with_prefix(
23936 "// ".to_string(),
23937 "// ".to_string(),
23938 "xx \nyy zz aa bb cc".to_string(),
23939 12,
23940 NonZeroU32::new(4).unwrap(),
23941 false,
23942 ),
23943 "// xx yy zz\n// aa bb cc"
23944 );
23945 assert_eq!(
23946 wrap_with_prefix(
23947 String::new(),
23948 String::new(),
23949 "这是什么 \n 钢笔".to_string(),
23950 3,
23951 NonZeroU32::new(4).unwrap(),
23952 false,
23953 ),
23954 "这是什\n么 钢\n笔"
23955 );
23956 assert_eq!(
23957 wrap_with_prefix(
23958 String::new(),
23959 String::new(),
23960 format!("foo{}bar", '\u{2009}'), // thin space
23961 80,
23962 NonZeroU32::new(4).unwrap(),
23963 false,
23964 ),
23965 format!("foo{}bar", '\u{2009}')
23966 );
23967}
23968
23969pub trait CollaborationHub {
23970 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23971 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23972 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23973}
23974
23975impl CollaborationHub for Entity<Project> {
23976 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23977 self.read(cx).collaborators()
23978 }
23979
23980 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23981 self.read(cx).user_store().read(cx).participant_indices()
23982 }
23983
23984 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23985 let this = self.read(cx);
23986 let user_ids = this.collaborators().values().map(|c| c.user_id);
23987 this.user_store().read(cx).participant_names(user_ids, cx)
23988 }
23989}
23990
23991pub trait SemanticsProvider {
23992 fn hover(
23993 &self,
23994 buffer: &Entity<Buffer>,
23995 position: text::Anchor,
23996 cx: &mut App,
23997 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23998
23999 fn inline_values(
24000 &self,
24001 buffer_handle: Entity<Buffer>,
24002 range: Range<text::Anchor>,
24003 cx: &mut App,
24004 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24005
24006 fn applicable_inlay_chunks(
24007 &self,
24008 buffer: &Entity<Buffer>,
24009 ranges: &[Range<text::Anchor>],
24010 cx: &mut App,
24011 ) -> Vec<Range<BufferRow>>;
24012
24013 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24014
24015 fn inlay_hints(
24016 &self,
24017 invalidate: InvalidationStrategy,
24018 buffer: Entity<Buffer>,
24019 ranges: Vec<Range<text::Anchor>>,
24020 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24021 cx: &mut App,
24022 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24023
24024 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24025
24026 fn document_highlights(
24027 &self,
24028 buffer: &Entity<Buffer>,
24029 position: text::Anchor,
24030 cx: &mut App,
24031 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24032
24033 fn definitions(
24034 &self,
24035 buffer: &Entity<Buffer>,
24036 position: text::Anchor,
24037 kind: GotoDefinitionKind,
24038 cx: &mut App,
24039 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24040
24041 fn range_for_rename(
24042 &self,
24043 buffer: &Entity<Buffer>,
24044 position: text::Anchor,
24045 cx: &mut App,
24046 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24047
24048 fn perform_rename(
24049 &self,
24050 buffer: &Entity<Buffer>,
24051 position: text::Anchor,
24052 new_name: String,
24053 cx: &mut App,
24054 ) -> Option<Task<Result<ProjectTransaction>>>;
24055}
24056
24057pub trait CompletionProvider {
24058 fn completions(
24059 &self,
24060 excerpt_id: ExcerptId,
24061 buffer: &Entity<Buffer>,
24062 buffer_position: text::Anchor,
24063 trigger: CompletionContext,
24064 window: &mut Window,
24065 cx: &mut Context<Editor>,
24066 ) -> Task<Result<Vec<CompletionResponse>>>;
24067
24068 fn resolve_completions(
24069 &self,
24070 _buffer: Entity<Buffer>,
24071 _completion_indices: Vec<usize>,
24072 _completions: Rc<RefCell<Box<[Completion]>>>,
24073 _cx: &mut Context<Editor>,
24074 ) -> Task<Result<bool>> {
24075 Task::ready(Ok(false))
24076 }
24077
24078 fn apply_additional_edits_for_completion(
24079 &self,
24080 _buffer: Entity<Buffer>,
24081 _completions: Rc<RefCell<Box<[Completion]>>>,
24082 _completion_index: usize,
24083 _push_to_history: bool,
24084 _cx: &mut Context<Editor>,
24085 ) -> Task<Result<Option<language::Transaction>>> {
24086 Task::ready(Ok(None))
24087 }
24088
24089 fn is_completion_trigger(
24090 &self,
24091 buffer: &Entity<Buffer>,
24092 position: language::Anchor,
24093 text: &str,
24094 trigger_in_words: bool,
24095 cx: &mut Context<Editor>,
24096 ) -> bool;
24097
24098 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24099
24100 fn sort_completions(&self) -> bool {
24101 true
24102 }
24103
24104 fn filter_completions(&self) -> bool {
24105 true
24106 }
24107
24108 fn show_snippets(&self) -> bool {
24109 false
24110 }
24111}
24112
24113pub trait CodeActionProvider {
24114 fn id(&self) -> Arc<str>;
24115
24116 fn code_actions(
24117 &self,
24118 buffer: &Entity<Buffer>,
24119 range: Range<text::Anchor>,
24120 window: &mut Window,
24121 cx: &mut App,
24122 ) -> Task<Result<Vec<CodeAction>>>;
24123
24124 fn apply_code_action(
24125 &self,
24126 buffer_handle: Entity<Buffer>,
24127 action: CodeAction,
24128 excerpt_id: ExcerptId,
24129 push_to_history: bool,
24130 window: &mut Window,
24131 cx: &mut App,
24132 ) -> Task<Result<ProjectTransaction>>;
24133}
24134
24135impl CodeActionProvider for Entity<Project> {
24136 fn id(&self) -> Arc<str> {
24137 "project".into()
24138 }
24139
24140 fn code_actions(
24141 &self,
24142 buffer: &Entity<Buffer>,
24143 range: Range<text::Anchor>,
24144 _window: &mut Window,
24145 cx: &mut App,
24146 ) -> Task<Result<Vec<CodeAction>>> {
24147 self.update(cx, |project, cx| {
24148 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24149 let code_actions = project.code_actions(buffer, range, None, cx);
24150 cx.background_spawn(async move {
24151 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24152 Ok(code_lens_actions
24153 .context("code lens fetch")?
24154 .into_iter()
24155 .flatten()
24156 .chain(
24157 code_actions
24158 .context("code action fetch")?
24159 .into_iter()
24160 .flatten(),
24161 )
24162 .collect())
24163 })
24164 })
24165 }
24166
24167 fn apply_code_action(
24168 &self,
24169 buffer_handle: Entity<Buffer>,
24170 action: CodeAction,
24171 _excerpt_id: ExcerptId,
24172 push_to_history: bool,
24173 _window: &mut Window,
24174 cx: &mut App,
24175 ) -> Task<Result<ProjectTransaction>> {
24176 self.update(cx, |project, cx| {
24177 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24178 })
24179 }
24180}
24181
24182fn snippet_completions(
24183 project: &Project,
24184 buffer: &Entity<Buffer>,
24185 buffer_anchor: text::Anchor,
24186 classifier: CharClassifier,
24187 cx: &mut App,
24188) -> Task<Result<CompletionResponse>> {
24189 let languages = buffer.read(cx).languages_at(buffer_anchor);
24190 let snippet_store = project.snippets().read(cx);
24191
24192 let scopes: Vec<_> = languages
24193 .iter()
24194 .filter_map(|language| {
24195 let language_name = language.lsp_id();
24196 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24197
24198 if snippets.is_empty() {
24199 None
24200 } else {
24201 Some((language.default_scope(), snippets))
24202 }
24203 })
24204 .collect();
24205
24206 if scopes.is_empty() {
24207 return Task::ready(Ok(CompletionResponse {
24208 completions: vec![],
24209 display_options: CompletionDisplayOptions::default(),
24210 is_incomplete: false,
24211 }));
24212 }
24213
24214 let snapshot = buffer.read(cx).text_snapshot();
24215 let executor = cx.background_executor().clone();
24216
24217 cx.background_spawn(async move {
24218 let is_word_char = |c| classifier.is_word(c);
24219
24220 let mut is_incomplete = false;
24221 let mut completions: Vec<Completion> = Vec::new();
24222
24223 const MAX_PREFIX_LEN: usize = 128;
24224 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24225 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24226 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24227
24228 let max_buffer_window: String = snapshot
24229 .text_for_range(window_start..buffer_offset)
24230 .collect();
24231
24232 if max_buffer_window.is_empty() {
24233 return Ok(CompletionResponse {
24234 completions: vec![],
24235 display_options: CompletionDisplayOptions::default(),
24236 is_incomplete: true,
24237 });
24238 }
24239
24240 for (_scope, snippets) in scopes.into_iter() {
24241 // Sort snippets by word count to match longer snippet prefixes first.
24242 let mut sorted_snippet_candidates = snippets
24243 .iter()
24244 .enumerate()
24245 .flat_map(|(snippet_ix, snippet)| {
24246 snippet
24247 .prefix
24248 .iter()
24249 .enumerate()
24250 .map(move |(prefix_ix, prefix)| {
24251 let word_count =
24252 snippet_candidate_suffixes(prefix, is_word_char).count();
24253 ((snippet_ix, prefix_ix), prefix, word_count)
24254 })
24255 })
24256 .collect_vec();
24257 sorted_snippet_candidates
24258 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24259
24260 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24261
24262 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24263 .take(
24264 sorted_snippet_candidates
24265 .first()
24266 .map(|(_, _, word_count)| *word_count)
24267 .unwrap_or_default(),
24268 )
24269 .collect_vec();
24270
24271 const MAX_RESULTS: usize = 100;
24272 // Each match also remembers how many characters from the buffer it consumed
24273 let mut matches: Vec<(StringMatch, usize)> = vec![];
24274
24275 let mut snippet_list_cutoff_index = 0;
24276 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24277 let word_count = buffer_index + 1;
24278 // Increase `snippet_list_cutoff_index` until we have all of the
24279 // snippets with sufficiently many words.
24280 while sorted_snippet_candidates
24281 .get(snippet_list_cutoff_index)
24282 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24283 *snippet_word_count >= word_count
24284 })
24285 {
24286 snippet_list_cutoff_index += 1;
24287 }
24288
24289 // Take only the candidates with at least `word_count` many words
24290 let snippet_candidates_at_word_len =
24291 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24292
24293 let candidates = snippet_candidates_at_word_len
24294 .iter()
24295 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24296 .enumerate() // index in `sorted_snippet_candidates`
24297 // First char must match
24298 .filter(|(_ix, prefix)| {
24299 itertools::equal(
24300 prefix
24301 .chars()
24302 .next()
24303 .into_iter()
24304 .flat_map(|c| c.to_lowercase()),
24305 buffer_window
24306 .chars()
24307 .next()
24308 .into_iter()
24309 .flat_map(|c| c.to_lowercase()),
24310 )
24311 })
24312 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24313 .collect::<Vec<StringMatchCandidate>>();
24314
24315 matches.extend(
24316 fuzzy::match_strings(
24317 &candidates,
24318 &buffer_window,
24319 buffer_window.chars().any(|c| c.is_uppercase()),
24320 true,
24321 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24322 &Default::default(),
24323 executor.clone(),
24324 )
24325 .await
24326 .into_iter()
24327 .map(|string_match| (string_match, buffer_window.len())),
24328 );
24329
24330 if matches.len() >= MAX_RESULTS {
24331 break;
24332 }
24333 }
24334
24335 let to_lsp = |point: &text::Anchor| {
24336 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24337 point_to_lsp(end)
24338 };
24339 let lsp_end = to_lsp(&buffer_anchor);
24340
24341 if matches.len() >= MAX_RESULTS {
24342 is_incomplete = true;
24343 }
24344
24345 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24346 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24347 sorted_snippet_candidates[string_match.candidate_id];
24348 let snippet = &snippets[snippet_index];
24349 let start = buffer_offset - buffer_window_len;
24350 let start = snapshot.anchor_before(start);
24351 let range = start..buffer_anchor;
24352 let lsp_start = to_lsp(&start);
24353 let lsp_range = lsp::Range {
24354 start: lsp_start,
24355 end: lsp_end,
24356 };
24357 Completion {
24358 replace_range: range,
24359 new_text: snippet.body.clone(),
24360 source: CompletionSource::Lsp {
24361 insert_range: None,
24362 server_id: LanguageServerId(usize::MAX),
24363 resolved: true,
24364 lsp_completion: Box::new(lsp::CompletionItem {
24365 label: snippet.prefix.first().unwrap().clone(),
24366 kind: Some(CompletionItemKind::SNIPPET),
24367 label_details: snippet.description.as_ref().map(|description| {
24368 lsp::CompletionItemLabelDetails {
24369 detail: Some(description.clone()),
24370 description: None,
24371 }
24372 }),
24373 insert_text_format: Some(InsertTextFormat::SNIPPET),
24374 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24375 lsp::InsertReplaceEdit {
24376 new_text: snippet.body.clone(),
24377 insert: lsp_range,
24378 replace: lsp_range,
24379 },
24380 )),
24381 filter_text: Some(snippet.body.clone()),
24382 sort_text: Some(char::MAX.to_string()),
24383 ..lsp::CompletionItem::default()
24384 }),
24385 lsp_defaults: None,
24386 },
24387 label: CodeLabel {
24388 text: matching_prefix.clone(),
24389 runs: Vec::new(),
24390 filter_range: 0..matching_prefix.len(),
24391 },
24392 icon_path: None,
24393 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24394 single_line: snippet.name.clone().into(),
24395 plain_text: snippet
24396 .description
24397 .clone()
24398 .map(|description| description.into()),
24399 }),
24400 insert_text_mode: None,
24401 confirm: None,
24402 match_start: Some(start),
24403 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24404 }
24405 }));
24406 }
24407
24408 Ok(CompletionResponse {
24409 completions,
24410 display_options: CompletionDisplayOptions::default(),
24411 is_incomplete,
24412 })
24413 })
24414}
24415
24416impl CompletionProvider for Entity<Project> {
24417 fn completions(
24418 &self,
24419 _excerpt_id: ExcerptId,
24420 buffer: &Entity<Buffer>,
24421 buffer_position: text::Anchor,
24422 options: CompletionContext,
24423 _window: &mut Window,
24424 cx: &mut Context<Editor>,
24425 ) -> Task<Result<Vec<CompletionResponse>>> {
24426 self.update(cx, |project, cx| {
24427 let task = project.completions(buffer, buffer_position, options, cx);
24428 cx.background_spawn(task)
24429 })
24430 }
24431
24432 fn resolve_completions(
24433 &self,
24434 buffer: Entity<Buffer>,
24435 completion_indices: Vec<usize>,
24436 completions: Rc<RefCell<Box<[Completion]>>>,
24437 cx: &mut Context<Editor>,
24438 ) -> Task<Result<bool>> {
24439 self.update(cx, |project, cx| {
24440 project.lsp_store().update(cx, |lsp_store, cx| {
24441 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24442 })
24443 })
24444 }
24445
24446 fn apply_additional_edits_for_completion(
24447 &self,
24448 buffer: Entity<Buffer>,
24449 completions: Rc<RefCell<Box<[Completion]>>>,
24450 completion_index: usize,
24451 push_to_history: bool,
24452 cx: &mut Context<Editor>,
24453 ) -> Task<Result<Option<language::Transaction>>> {
24454 self.update(cx, |project, cx| {
24455 project.lsp_store().update(cx, |lsp_store, cx| {
24456 lsp_store.apply_additional_edits_for_completion(
24457 buffer,
24458 completions,
24459 completion_index,
24460 push_to_history,
24461 cx,
24462 )
24463 })
24464 })
24465 }
24466
24467 fn is_completion_trigger(
24468 &self,
24469 buffer: &Entity<Buffer>,
24470 position: language::Anchor,
24471 text: &str,
24472 trigger_in_words: bool,
24473 cx: &mut Context<Editor>,
24474 ) -> bool {
24475 let mut chars = text.chars();
24476 let char = if let Some(char) = chars.next() {
24477 char
24478 } else {
24479 return false;
24480 };
24481 if chars.next().is_some() {
24482 return false;
24483 }
24484
24485 let buffer = buffer.read(cx);
24486 let snapshot = buffer.snapshot();
24487 let classifier = snapshot
24488 .char_classifier_at(position)
24489 .scope_context(Some(CharScopeContext::Completion));
24490 if trigger_in_words && classifier.is_word(char) {
24491 return true;
24492 }
24493
24494 buffer.completion_triggers().contains(text)
24495 }
24496
24497 fn show_snippets(&self) -> bool {
24498 true
24499 }
24500}
24501
24502impl SemanticsProvider for Entity<Project> {
24503 fn hover(
24504 &self,
24505 buffer: &Entity<Buffer>,
24506 position: text::Anchor,
24507 cx: &mut App,
24508 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24509 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24510 }
24511
24512 fn document_highlights(
24513 &self,
24514 buffer: &Entity<Buffer>,
24515 position: text::Anchor,
24516 cx: &mut App,
24517 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24518 Some(self.update(cx, |project, cx| {
24519 project.document_highlights(buffer, position, cx)
24520 }))
24521 }
24522
24523 fn definitions(
24524 &self,
24525 buffer: &Entity<Buffer>,
24526 position: text::Anchor,
24527 kind: GotoDefinitionKind,
24528 cx: &mut App,
24529 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24530 Some(self.update(cx, |project, cx| match kind {
24531 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24532 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24533 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24534 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24535 }))
24536 }
24537
24538 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24539 self.update(cx, |project, cx| {
24540 if project
24541 .active_debug_session(cx)
24542 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24543 {
24544 return true;
24545 }
24546
24547 buffer.update(cx, |buffer, cx| {
24548 project.any_language_server_supports_inlay_hints(buffer, cx)
24549 })
24550 })
24551 }
24552
24553 fn inline_values(
24554 &self,
24555 buffer_handle: Entity<Buffer>,
24556 range: Range<text::Anchor>,
24557 cx: &mut App,
24558 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24559 self.update(cx, |project, cx| {
24560 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24561
24562 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24563 })
24564 }
24565
24566 fn applicable_inlay_chunks(
24567 &self,
24568 buffer: &Entity<Buffer>,
24569 ranges: &[Range<text::Anchor>],
24570 cx: &mut App,
24571 ) -> Vec<Range<BufferRow>> {
24572 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24573 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24574 })
24575 }
24576
24577 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24578 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24579 lsp_store.invalidate_inlay_hints(for_buffers)
24580 });
24581 }
24582
24583 fn inlay_hints(
24584 &self,
24585 invalidate: InvalidationStrategy,
24586 buffer: Entity<Buffer>,
24587 ranges: Vec<Range<text::Anchor>>,
24588 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24589 cx: &mut App,
24590 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24591 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24592 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24593 }))
24594 }
24595
24596 fn range_for_rename(
24597 &self,
24598 buffer: &Entity<Buffer>,
24599 position: text::Anchor,
24600 cx: &mut App,
24601 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24602 Some(self.update(cx, |project, cx| {
24603 let buffer = buffer.clone();
24604 let task = project.prepare_rename(buffer.clone(), position, cx);
24605 cx.spawn(async move |_, cx| {
24606 Ok(match task.await? {
24607 PrepareRenameResponse::Success(range) => Some(range),
24608 PrepareRenameResponse::InvalidPosition => None,
24609 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24610 // Fallback on using TreeSitter info to determine identifier range
24611 buffer.read_with(cx, |buffer, _| {
24612 let snapshot = buffer.snapshot();
24613 let (range, kind) = snapshot.surrounding_word(position, None);
24614 if kind != Some(CharKind::Word) {
24615 return None;
24616 }
24617 Some(
24618 snapshot.anchor_before(range.start)
24619 ..snapshot.anchor_after(range.end),
24620 )
24621 })?
24622 }
24623 })
24624 })
24625 }))
24626 }
24627
24628 fn perform_rename(
24629 &self,
24630 buffer: &Entity<Buffer>,
24631 position: text::Anchor,
24632 new_name: String,
24633 cx: &mut App,
24634 ) -> Option<Task<Result<ProjectTransaction>>> {
24635 Some(self.update(cx, |project, cx| {
24636 project.perform_rename(buffer.clone(), position, new_name, cx)
24637 }))
24638 }
24639}
24640
24641fn consume_contiguous_rows(
24642 contiguous_row_selections: &mut Vec<Selection<Point>>,
24643 selection: &Selection<Point>,
24644 display_map: &DisplaySnapshot,
24645 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24646) -> (MultiBufferRow, MultiBufferRow) {
24647 contiguous_row_selections.push(selection.clone());
24648 let start_row = starting_row(selection, display_map);
24649 let mut end_row = ending_row(selection, display_map);
24650
24651 while let Some(next_selection) = selections.peek() {
24652 if next_selection.start.row <= end_row.0 {
24653 end_row = ending_row(next_selection, display_map);
24654 contiguous_row_selections.push(selections.next().unwrap().clone());
24655 } else {
24656 break;
24657 }
24658 }
24659 (start_row, end_row)
24660}
24661
24662fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24663 if selection.start.column > 0 {
24664 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24665 } else {
24666 MultiBufferRow(selection.start.row)
24667 }
24668}
24669
24670fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24671 if next_selection.end.column > 0 || next_selection.is_empty() {
24672 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24673 } else {
24674 MultiBufferRow(next_selection.end.row)
24675 }
24676}
24677
24678impl EditorSnapshot {
24679 pub fn remote_selections_in_range<'a>(
24680 &'a self,
24681 range: &'a Range<Anchor>,
24682 collaboration_hub: &dyn CollaborationHub,
24683 cx: &'a App,
24684 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24685 let participant_names = collaboration_hub.user_names(cx);
24686 let participant_indices = collaboration_hub.user_participant_indices(cx);
24687 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24688 let collaborators_by_replica_id = collaborators_by_peer_id
24689 .values()
24690 .map(|collaborator| (collaborator.replica_id, collaborator))
24691 .collect::<HashMap<_, _>>();
24692 self.buffer_snapshot()
24693 .selections_in_range(range, false)
24694 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24695 if replica_id == ReplicaId::AGENT {
24696 Some(RemoteSelection {
24697 replica_id,
24698 selection,
24699 cursor_shape,
24700 line_mode,
24701 collaborator_id: CollaboratorId::Agent,
24702 user_name: Some("Agent".into()),
24703 color: cx.theme().players().agent(),
24704 })
24705 } else {
24706 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24707 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24708 let user_name = participant_names.get(&collaborator.user_id).cloned();
24709 Some(RemoteSelection {
24710 replica_id,
24711 selection,
24712 cursor_shape,
24713 line_mode,
24714 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24715 user_name,
24716 color: if let Some(index) = participant_index {
24717 cx.theme().players().color_for_participant(index.0)
24718 } else {
24719 cx.theme().players().absent()
24720 },
24721 })
24722 }
24723 })
24724 }
24725
24726 pub fn hunks_for_ranges(
24727 &self,
24728 ranges: impl IntoIterator<Item = Range<Point>>,
24729 ) -> Vec<MultiBufferDiffHunk> {
24730 let mut hunks = Vec::new();
24731 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24732 HashMap::default();
24733 for query_range in ranges {
24734 let query_rows =
24735 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24736 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24737 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24738 ) {
24739 // Include deleted hunks that are adjacent to the query range, because
24740 // otherwise they would be missed.
24741 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24742 if hunk.status().is_deleted() {
24743 intersects_range |= hunk.row_range.start == query_rows.end;
24744 intersects_range |= hunk.row_range.end == query_rows.start;
24745 }
24746 if intersects_range {
24747 if !processed_buffer_rows
24748 .entry(hunk.buffer_id)
24749 .or_default()
24750 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24751 {
24752 continue;
24753 }
24754 hunks.push(hunk);
24755 }
24756 }
24757 }
24758
24759 hunks
24760 }
24761
24762 fn display_diff_hunks_for_rows<'a>(
24763 &'a self,
24764 display_rows: Range<DisplayRow>,
24765 folded_buffers: &'a HashSet<BufferId>,
24766 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24767 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24768 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24769
24770 self.buffer_snapshot()
24771 .diff_hunks_in_range(buffer_start..buffer_end)
24772 .filter_map(|hunk| {
24773 if folded_buffers.contains(&hunk.buffer_id) {
24774 return None;
24775 }
24776
24777 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24778 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24779
24780 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24781 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24782
24783 let display_hunk = if hunk_display_start.column() != 0 {
24784 DisplayDiffHunk::Folded {
24785 display_row: hunk_display_start.row(),
24786 }
24787 } else {
24788 let mut end_row = hunk_display_end.row();
24789 if hunk_display_end.column() > 0 {
24790 end_row.0 += 1;
24791 }
24792 let is_created_file = hunk.is_created_file();
24793
24794 DisplayDiffHunk::Unfolded {
24795 status: hunk.status(),
24796 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24797 ..hunk.diff_base_byte_range.end.0,
24798 word_diffs: hunk.word_diffs,
24799 display_row_range: hunk_display_start.row()..end_row,
24800 multi_buffer_range: Anchor::range_in_buffer(
24801 hunk.excerpt_id,
24802 hunk.buffer_range,
24803 ),
24804 is_created_file,
24805 }
24806 };
24807
24808 Some(display_hunk)
24809 })
24810 }
24811
24812 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24813 self.display_snapshot
24814 .buffer_snapshot()
24815 .language_at(position)
24816 }
24817
24818 pub fn is_focused(&self) -> bool {
24819 self.is_focused
24820 }
24821
24822 pub fn placeholder_text(&self) -> Option<String> {
24823 self.placeholder_display_snapshot
24824 .as_ref()
24825 .map(|display_map| display_map.text())
24826 }
24827
24828 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24829 self.scroll_anchor.scroll_position(&self.display_snapshot)
24830 }
24831
24832 pub fn gutter_dimensions(
24833 &self,
24834 font_id: FontId,
24835 font_size: Pixels,
24836 style: &EditorStyle,
24837 window: &mut Window,
24838 cx: &App,
24839 ) -> GutterDimensions {
24840 if self.show_gutter
24841 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24842 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24843 {
24844 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24845 matches!(
24846 ProjectSettings::get_global(cx).git.git_gutter,
24847 GitGutterSetting::TrackedFiles
24848 )
24849 });
24850 let gutter_settings = EditorSettings::get_global(cx).gutter;
24851 let show_line_numbers = self
24852 .show_line_numbers
24853 .unwrap_or(gutter_settings.line_numbers);
24854 let line_gutter_width = if show_line_numbers {
24855 // Avoid flicker-like gutter resizes when the line number gains another digit by
24856 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24857 let min_width_for_number_on_gutter =
24858 ch_advance * gutter_settings.min_line_number_digits as f32;
24859 self.max_line_number_width(style, window)
24860 .max(min_width_for_number_on_gutter)
24861 } else {
24862 0.0.into()
24863 };
24864
24865 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24866 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24867
24868 let git_blame_entries_width =
24869 self.git_blame_gutter_max_author_length
24870 .map(|max_author_length| {
24871 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24872 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24873
24874 /// The number of characters to dedicate to gaps and margins.
24875 const SPACING_WIDTH: usize = 4;
24876
24877 let max_char_count = max_author_length.min(renderer.max_author_length())
24878 + ::git::SHORT_SHA_LENGTH
24879 + MAX_RELATIVE_TIMESTAMP.len()
24880 + SPACING_WIDTH;
24881
24882 ch_advance * max_char_count
24883 });
24884
24885 let is_singleton = self.buffer_snapshot().is_singleton();
24886
24887 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24888 left_padding += if !is_singleton {
24889 ch_width * 4.0
24890 } else if show_runnables || show_breakpoints {
24891 ch_width * 3.0
24892 } else if show_git_gutter && show_line_numbers {
24893 ch_width * 2.0
24894 } else if show_git_gutter || show_line_numbers {
24895 ch_width
24896 } else {
24897 px(0.)
24898 };
24899
24900 let shows_folds = is_singleton && gutter_settings.folds;
24901
24902 let right_padding = if shows_folds && show_line_numbers {
24903 ch_width * 4.0
24904 } else if shows_folds || (!is_singleton && show_line_numbers) {
24905 ch_width * 3.0
24906 } else if show_line_numbers {
24907 ch_width
24908 } else {
24909 px(0.)
24910 };
24911
24912 GutterDimensions {
24913 left_padding,
24914 right_padding,
24915 width: line_gutter_width + left_padding + right_padding,
24916 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24917 git_blame_entries_width,
24918 }
24919 } else if self.offset_content {
24920 GutterDimensions::default_with_margin(font_id, font_size, cx)
24921 } else {
24922 GutterDimensions::default()
24923 }
24924 }
24925
24926 pub fn render_crease_toggle(
24927 &self,
24928 buffer_row: MultiBufferRow,
24929 row_contains_cursor: bool,
24930 editor: Entity<Editor>,
24931 window: &mut Window,
24932 cx: &mut App,
24933 ) -> Option<AnyElement> {
24934 let folded = self.is_line_folded(buffer_row);
24935 let mut is_foldable = false;
24936
24937 if let Some(crease) = self
24938 .crease_snapshot
24939 .query_row(buffer_row, self.buffer_snapshot())
24940 {
24941 is_foldable = true;
24942 match crease {
24943 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24944 if let Some(render_toggle) = render_toggle {
24945 let toggle_callback =
24946 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24947 if folded {
24948 editor.update(cx, |editor, cx| {
24949 editor.fold_at(buffer_row, window, cx)
24950 });
24951 } else {
24952 editor.update(cx, |editor, cx| {
24953 editor.unfold_at(buffer_row, window, cx)
24954 });
24955 }
24956 });
24957 return Some((render_toggle)(
24958 buffer_row,
24959 folded,
24960 toggle_callback,
24961 window,
24962 cx,
24963 ));
24964 }
24965 }
24966 }
24967 }
24968
24969 is_foldable |= self.starts_indent(buffer_row);
24970
24971 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24972 Some(
24973 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24974 .toggle_state(folded)
24975 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24976 if folded {
24977 this.unfold_at(buffer_row, window, cx);
24978 } else {
24979 this.fold_at(buffer_row, window, cx);
24980 }
24981 }))
24982 .into_any_element(),
24983 )
24984 } else {
24985 None
24986 }
24987 }
24988
24989 pub fn render_crease_trailer(
24990 &self,
24991 buffer_row: MultiBufferRow,
24992 window: &mut Window,
24993 cx: &mut App,
24994 ) -> Option<AnyElement> {
24995 let folded = self.is_line_folded(buffer_row);
24996 if let Crease::Inline { render_trailer, .. } = self
24997 .crease_snapshot
24998 .query_row(buffer_row, self.buffer_snapshot())?
24999 {
25000 let render_trailer = render_trailer.as_ref()?;
25001 Some(render_trailer(buffer_row, folded, window, cx))
25002 } else {
25003 None
25004 }
25005 }
25006
25007 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25008 let digit_count = self.widest_line_number().ilog10() + 1;
25009 column_pixels(style, digit_count as usize, window)
25010 }
25011}
25012
25013pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25014 let font_size = style.text.font_size.to_pixels(window.rem_size());
25015 let layout = window.text_system().shape_line(
25016 SharedString::from(" ".repeat(column)),
25017 font_size,
25018 &[TextRun {
25019 len: column,
25020 font: style.text.font(),
25021 color: Hsla::default(),
25022 ..Default::default()
25023 }],
25024 None,
25025 );
25026
25027 layout.width
25028}
25029
25030impl Deref for EditorSnapshot {
25031 type Target = DisplaySnapshot;
25032
25033 fn deref(&self) -> &Self::Target {
25034 &self.display_snapshot
25035 }
25036}
25037
25038#[derive(Clone, Debug, PartialEq, Eq)]
25039pub enum EditorEvent {
25040 InputIgnored {
25041 text: Arc<str>,
25042 },
25043 InputHandled {
25044 utf16_range_to_replace: Option<Range<isize>>,
25045 text: Arc<str>,
25046 },
25047 ExcerptsAdded {
25048 buffer: Entity<Buffer>,
25049 predecessor: ExcerptId,
25050 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25051 },
25052 ExcerptsRemoved {
25053 ids: Vec<ExcerptId>,
25054 removed_buffer_ids: Vec<BufferId>,
25055 },
25056 BufferFoldToggled {
25057 ids: Vec<ExcerptId>,
25058 folded: bool,
25059 },
25060 ExcerptsEdited {
25061 ids: Vec<ExcerptId>,
25062 },
25063 ExcerptsExpanded {
25064 ids: Vec<ExcerptId>,
25065 },
25066 BufferEdited,
25067 Edited {
25068 transaction_id: clock::Lamport,
25069 },
25070 Reparsed(BufferId),
25071 Focused,
25072 FocusedIn,
25073 Blurred,
25074 DirtyChanged,
25075 Saved,
25076 TitleChanged,
25077 SelectionsChanged {
25078 local: bool,
25079 },
25080 ScrollPositionChanged {
25081 local: bool,
25082 autoscroll: bool,
25083 },
25084 TransactionUndone {
25085 transaction_id: clock::Lamport,
25086 },
25087 TransactionBegun {
25088 transaction_id: clock::Lamport,
25089 },
25090 CursorShapeChanged,
25091 BreadcrumbsChanged,
25092 PushedToNavHistory {
25093 anchor: Anchor,
25094 is_deactivate: bool,
25095 },
25096}
25097
25098impl EventEmitter<EditorEvent> for Editor {}
25099
25100impl Focusable for Editor {
25101 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25102 self.focus_handle.clone()
25103 }
25104}
25105
25106impl Render for Editor {
25107 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25108 EditorElement::new(&cx.entity(), self.create_style(cx))
25109 }
25110}
25111
25112impl EntityInputHandler for Editor {
25113 fn text_for_range(
25114 &mut self,
25115 range_utf16: Range<usize>,
25116 adjusted_range: &mut Option<Range<usize>>,
25117 _: &mut Window,
25118 cx: &mut Context<Self>,
25119 ) -> Option<String> {
25120 let snapshot = self.buffer.read(cx).read(cx);
25121 let start = snapshot.clip_offset_utf16(
25122 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25123 Bias::Left,
25124 );
25125 let end = snapshot.clip_offset_utf16(
25126 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25127 Bias::Right,
25128 );
25129 if (start.0.0..end.0.0) != range_utf16 {
25130 adjusted_range.replace(start.0.0..end.0.0);
25131 }
25132 Some(snapshot.text_for_range(start..end).collect())
25133 }
25134
25135 fn selected_text_range(
25136 &mut self,
25137 ignore_disabled_input: bool,
25138 _: &mut Window,
25139 cx: &mut Context<Self>,
25140 ) -> Option<UTF16Selection> {
25141 // Prevent the IME menu from appearing when holding down an alphabetic key
25142 // while input is disabled.
25143 if !ignore_disabled_input && !self.input_enabled {
25144 return None;
25145 }
25146
25147 let selection = self
25148 .selections
25149 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25150 let range = selection.range();
25151
25152 Some(UTF16Selection {
25153 range: range.start.0.0..range.end.0.0,
25154 reversed: selection.reversed,
25155 })
25156 }
25157
25158 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25159 let snapshot = self.buffer.read(cx).read(cx);
25160 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25161 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25162 }
25163
25164 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25165 self.clear_highlights::<InputComposition>(cx);
25166 self.ime_transaction.take();
25167 }
25168
25169 fn replace_text_in_range(
25170 &mut self,
25171 range_utf16: Option<Range<usize>>,
25172 text: &str,
25173 window: &mut Window,
25174 cx: &mut Context<Self>,
25175 ) {
25176 if !self.input_enabled {
25177 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25178 return;
25179 }
25180
25181 self.transact(window, cx, |this, window, cx| {
25182 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25183 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25184 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25185 Some(this.selection_replacement_ranges(range_utf16, cx))
25186 } else {
25187 this.marked_text_ranges(cx)
25188 };
25189
25190 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25191 let newest_selection_id = this.selections.newest_anchor().id;
25192 this.selections
25193 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25194 .iter()
25195 .zip(ranges_to_replace.iter())
25196 .find_map(|(selection, range)| {
25197 if selection.id == newest_selection_id {
25198 Some(
25199 (range.start.0.0 as isize - selection.head().0.0 as isize)
25200 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25201 )
25202 } else {
25203 None
25204 }
25205 })
25206 });
25207
25208 cx.emit(EditorEvent::InputHandled {
25209 utf16_range_to_replace: range_to_replace,
25210 text: text.into(),
25211 });
25212
25213 if let Some(new_selected_ranges) = new_selected_ranges {
25214 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25215 selections.select_ranges(new_selected_ranges)
25216 });
25217 this.backspace(&Default::default(), window, cx);
25218 }
25219
25220 this.handle_input(text, window, cx);
25221 });
25222
25223 if let Some(transaction) = self.ime_transaction {
25224 self.buffer.update(cx, |buffer, cx| {
25225 buffer.group_until_transaction(transaction, cx);
25226 });
25227 }
25228
25229 self.unmark_text(window, cx);
25230 }
25231
25232 fn replace_and_mark_text_in_range(
25233 &mut self,
25234 range_utf16: Option<Range<usize>>,
25235 text: &str,
25236 new_selected_range_utf16: Option<Range<usize>>,
25237 window: &mut Window,
25238 cx: &mut Context<Self>,
25239 ) {
25240 if !self.input_enabled {
25241 return;
25242 }
25243
25244 let transaction = self.transact(window, cx, |this, window, cx| {
25245 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25246 let snapshot = this.buffer.read(cx).read(cx);
25247 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25248 for marked_range in &mut marked_ranges {
25249 marked_range.end = marked_range.start + relative_range_utf16.end;
25250 marked_range.start += relative_range_utf16.start;
25251 marked_range.start =
25252 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25253 marked_range.end =
25254 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25255 }
25256 }
25257 Some(marked_ranges)
25258 } else if let Some(range_utf16) = range_utf16 {
25259 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25260 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25261 Some(this.selection_replacement_ranges(range_utf16, cx))
25262 } else {
25263 None
25264 };
25265
25266 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25267 let newest_selection_id = this.selections.newest_anchor().id;
25268 this.selections
25269 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25270 .iter()
25271 .zip(ranges_to_replace.iter())
25272 .find_map(|(selection, range)| {
25273 if selection.id == newest_selection_id {
25274 Some(
25275 (range.start.0.0 as isize - selection.head().0.0 as isize)
25276 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25277 )
25278 } else {
25279 None
25280 }
25281 })
25282 });
25283
25284 cx.emit(EditorEvent::InputHandled {
25285 utf16_range_to_replace: range_to_replace,
25286 text: text.into(),
25287 });
25288
25289 if let Some(ranges) = ranges_to_replace {
25290 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25291 s.select_ranges(ranges)
25292 });
25293 }
25294
25295 let marked_ranges = {
25296 let snapshot = this.buffer.read(cx).read(cx);
25297 this.selections
25298 .disjoint_anchors_arc()
25299 .iter()
25300 .map(|selection| {
25301 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25302 })
25303 .collect::<Vec<_>>()
25304 };
25305
25306 if text.is_empty() {
25307 this.unmark_text(window, cx);
25308 } else {
25309 this.highlight_text::<InputComposition>(
25310 marked_ranges.clone(),
25311 HighlightStyle {
25312 underline: Some(UnderlineStyle {
25313 thickness: px(1.),
25314 color: None,
25315 wavy: false,
25316 }),
25317 ..Default::default()
25318 },
25319 cx,
25320 );
25321 }
25322
25323 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25324 let use_autoclose = this.use_autoclose;
25325 let use_auto_surround = this.use_auto_surround;
25326 this.set_use_autoclose(false);
25327 this.set_use_auto_surround(false);
25328 this.handle_input(text, window, cx);
25329 this.set_use_autoclose(use_autoclose);
25330 this.set_use_auto_surround(use_auto_surround);
25331
25332 if let Some(new_selected_range) = new_selected_range_utf16 {
25333 let snapshot = this.buffer.read(cx).read(cx);
25334 let new_selected_ranges = marked_ranges
25335 .into_iter()
25336 .map(|marked_range| {
25337 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25338 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25339 insertion_start.0 + new_selected_range.start,
25340 ));
25341 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25342 insertion_start.0 + new_selected_range.end,
25343 ));
25344 snapshot.clip_offset_utf16(new_start, Bias::Left)
25345 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25346 })
25347 .collect::<Vec<_>>();
25348
25349 drop(snapshot);
25350 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25351 selections.select_ranges(new_selected_ranges)
25352 });
25353 }
25354 });
25355
25356 self.ime_transaction = self.ime_transaction.or(transaction);
25357 if let Some(transaction) = self.ime_transaction {
25358 self.buffer.update(cx, |buffer, cx| {
25359 buffer.group_until_transaction(transaction, cx);
25360 });
25361 }
25362
25363 if self.text_highlights::<InputComposition>(cx).is_none() {
25364 self.ime_transaction.take();
25365 }
25366 }
25367
25368 fn bounds_for_range(
25369 &mut self,
25370 range_utf16: Range<usize>,
25371 element_bounds: gpui::Bounds<Pixels>,
25372 window: &mut Window,
25373 cx: &mut Context<Self>,
25374 ) -> Option<gpui::Bounds<Pixels>> {
25375 let text_layout_details = self.text_layout_details(window);
25376 let CharacterDimensions {
25377 em_width,
25378 em_advance,
25379 line_height,
25380 } = self.character_dimensions(window);
25381
25382 let snapshot = self.snapshot(window, cx);
25383 let scroll_position = snapshot.scroll_position();
25384 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25385
25386 let start =
25387 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25388 let x = Pixels::from(
25389 ScrollOffset::from(
25390 snapshot.x_for_display_point(start, &text_layout_details)
25391 + self.gutter_dimensions.full_width(),
25392 ) - scroll_left,
25393 );
25394 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25395
25396 Some(Bounds {
25397 origin: element_bounds.origin + point(x, y),
25398 size: size(em_width, line_height),
25399 })
25400 }
25401
25402 fn character_index_for_point(
25403 &mut self,
25404 point: gpui::Point<Pixels>,
25405 _window: &mut Window,
25406 _cx: &mut Context<Self>,
25407 ) -> Option<usize> {
25408 let position_map = self.last_position_map.as_ref()?;
25409 if !position_map.text_hitbox.contains(&point) {
25410 return None;
25411 }
25412 let display_point = position_map.point_for_position(point).previous_valid;
25413 let anchor = position_map
25414 .snapshot
25415 .display_point_to_anchor(display_point, Bias::Left);
25416 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25417 Some(utf16_offset.0.0)
25418 }
25419
25420 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25421 self.input_enabled
25422 }
25423}
25424
25425trait SelectionExt {
25426 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25427 fn spanned_rows(
25428 &self,
25429 include_end_if_at_line_start: bool,
25430 map: &DisplaySnapshot,
25431 ) -> Range<MultiBufferRow>;
25432}
25433
25434impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25435 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25436 let start = self
25437 .start
25438 .to_point(map.buffer_snapshot())
25439 .to_display_point(map);
25440 let end = self
25441 .end
25442 .to_point(map.buffer_snapshot())
25443 .to_display_point(map);
25444 if self.reversed {
25445 end..start
25446 } else {
25447 start..end
25448 }
25449 }
25450
25451 fn spanned_rows(
25452 &self,
25453 include_end_if_at_line_start: bool,
25454 map: &DisplaySnapshot,
25455 ) -> Range<MultiBufferRow> {
25456 let start = self.start.to_point(map.buffer_snapshot());
25457 let mut end = self.end.to_point(map.buffer_snapshot());
25458 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25459 end.row -= 1;
25460 }
25461
25462 let buffer_start = map.prev_line_boundary(start).0;
25463 let buffer_end = map.next_line_boundary(end).0;
25464 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25465 }
25466}
25467
25468impl<T: InvalidationRegion> InvalidationStack<T> {
25469 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25470 where
25471 S: Clone + ToOffset,
25472 {
25473 while let Some(region) = self.last() {
25474 let all_selections_inside_invalidation_ranges =
25475 if selections.len() == region.ranges().len() {
25476 selections
25477 .iter()
25478 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25479 .all(|(selection, invalidation_range)| {
25480 let head = selection.head().to_offset(buffer);
25481 invalidation_range.start <= head && invalidation_range.end >= head
25482 })
25483 } else {
25484 false
25485 };
25486
25487 if all_selections_inside_invalidation_ranges {
25488 break;
25489 } else {
25490 self.pop();
25491 }
25492 }
25493 }
25494}
25495
25496impl<T> Default for InvalidationStack<T> {
25497 fn default() -> Self {
25498 Self(Default::default())
25499 }
25500}
25501
25502impl<T> Deref for InvalidationStack<T> {
25503 type Target = Vec<T>;
25504
25505 fn deref(&self) -> &Self::Target {
25506 &self.0
25507 }
25508}
25509
25510impl<T> DerefMut for InvalidationStack<T> {
25511 fn deref_mut(&mut self) -> &mut Self::Target {
25512 &mut self.0
25513 }
25514}
25515
25516impl InvalidationRegion for SnippetState {
25517 fn ranges(&self) -> &[Range<Anchor>] {
25518 &self.ranges[self.active_index]
25519 }
25520}
25521
25522fn edit_prediction_edit_text(
25523 current_snapshot: &BufferSnapshot,
25524 edits: &[(Range<Anchor>, impl AsRef<str>)],
25525 edit_preview: &EditPreview,
25526 include_deletions: bool,
25527 cx: &App,
25528) -> HighlightedText {
25529 let edits = edits
25530 .iter()
25531 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25532 .collect::<Vec<_>>();
25533
25534 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25535}
25536
25537fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25538 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25539 // Just show the raw edit text with basic styling
25540 let mut text = String::new();
25541 let mut highlights = Vec::new();
25542
25543 let insertion_highlight_style = HighlightStyle {
25544 color: Some(cx.theme().colors().text),
25545 ..Default::default()
25546 };
25547
25548 for (_, edit_text) in edits {
25549 let start_offset = text.len();
25550 text.push_str(edit_text);
25551 let end_offset = text.len();
25552
25553 if start_offset < end_offset {
25554 highlights.push((start_offset..end_offset, insertion_highlight_style));
25555 }
25556 }
25557
25558 HighlightedText {
25559 text: text.into(),
25560 highlights,
25561 }
25562}
25563
25564pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25565 match severity {
25566 lsp::DiagnosticSeverity::ERROR => colors.error,
25567 lsp::DiagnosticSeverity::WARNING => colors.warning,
25568 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25569 lsp::DiagnosticSeverity::HINT => colors.info,
25570 _ => colors.ignored,
25571 }
25572}
25573
25574pub fn styled_runs_for_code_label<'a>(
25575 label: &'a CodeLabel,
25576 syntax_theme: &'a theme::SyntaxTheme,
25577 local_player: &'a theme::PlayerColor,
25578) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25579 let fade_out = HighlightStyle {
25580 fade_out: Some(0.35),
25581 ..Default::default()
25582 };
25583
25584 let mut prev_end = label.filter_range.end;
25585 label
25586 .runs
25587 .iter()
25588 .enumerate()
25589 .flat_map(move |(ix, (range, highlight_id))| {
25590 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25591 HighlightStyle {
25592 color: Some(local_player.cursor),
25593 ..Default::default()
25594 }
25595 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25596 HighlightStyle {
25597 background_color: Some(local_player.selection),
25598 ..Default::default()
25599 }
25600 } else if let Some(style) = highlight_id.style(syntax_theme) {
25601 style
25602 } else {
25603 return Default::default();
25604 };
25605 let muted_style = style.highlight(fade_out);
25606
25607 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25608 if range.start >= label.filter_range.end {
25609 if range.start > prev_end {
25610 runs.push((prev_end..range.start, fade_out));
25611 }
25612 runs.push((range.clone(), muted_style));
25613 } else if range.end <= label.filter_range.end {
25614 runs.push((range.clone(), style));
25615 } else {
25616 runs.push((range.start..label.filter_range.end, style));
25617 runs.push((label.filter_range.end..range.end, muted_style));
25618 }
25619 prev_end = cmp::max(prev_end, range.end);
25620
25621 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25622 runs.push((prev_end..label.text.len(), fade_out));
25623 }
25624
25625 runs
25626 })
25627}
25628
25629pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25630 let mut prev_index = 0;
25631 let mut prev_codepoint: Option<char> = None;
25632 text.char_indices()
25633 .chain([(text.len(), '\0')])
25634 .filter_map(move |(index, codepoint)| {
25635 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25636 let is_boundary = index == text.len()
25637 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25638 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25639 if is_boundary {
25640 let chunk = &text[prev_index..index];
25641 prev_index = index;
25642 Some(chunk)
25643 } else {
25644 None
25645 }
25646 })
25647}
25648
25649/// Given a string of text immediately before the cursor, iterates over possible
25650/// strings a snippet could match to. More precisely: returns an iterator over
25651/// suffixes of `text` created by splitting at word boundaries (before & after
25652/// every non-word character).
25653///
25654/// Shorter suffixes are returned first.
25655pub(crate) fn snippet_candidate_suffixes(
25656 text: &str,
25657 is_word_char: impl Fn(char) -> bool,
25658) -> impl std::iter::Iterator<Item = &str> {
25659 let mut prev_index = text.len();
25660 let mut prev_codepoint = None;
25661 text.char_indices()
25662 .rev()
25663 .chain([(0, '\0')])
25664 .filter_map(move |(index, codepoint)| {
25665 let prev_index = std::mem::replace(&mut prev_index, index);
25666 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25667 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25668 None
25669 } else {
25670 let chunk = &text[prev_index..]; // go to end of string
25671 Some(chunk)
25672 }
25673 })
25674}
25675
25676pub trait RangeToAnchorExt: Sized {
25677 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25678
25679 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25680 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25681 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25682 }
25683}
25684
25685impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25686 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25687 let start_offset = self.start.to_offset(snapshot);
25688 let end_offset = self.end.to_offset(snapshot);
25689 if start_offset == end_offset {
25690 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25691 } else {
25692 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25693 }
25694 }
25695}
25696
25697pub trait RowExt {
25698 fn as_f64(&self) -> f64;
25699
25700 fn next_row(&self) -> Self;
25701
25702 fn previous_row(&self) -> Self;
25703
25704 fn minus(&self, other: Self) -> u32;
25705}
25706
25707impl RowExt for DisplayRow {
25708 fn as_f64(&self) -> f64 {
25709 self.0 as _
25710 }
25711
25712 fn next_row(&self) -> Self {
25713 Self(self.0 + 1)
25714 }
25715
25716 fn previous_row(&self) -> Self {
25717 Self(self.0.saturating_sub(1))
25718 }
25719
25720 fn minus(&self, other: Self) -> u32 {
25721 self.0 - other.0
25722 }
25723}
25724
25725impl RowExt for MultiBufferRow {
25726 fn as_f64(&self) -> f64 {
25727 self.0 as _
25728 }
25729
25730 fn next_row(&self) -> Self {
25731 Self(self.0 + 1)
25732 }
25733
25734 fn previous_row(&self) -> Self {
25735 Self(self.0.saturating_sub(1))
25736 }
25737
25738 fn minus(&self, other: Self) -> u32 {
25739 self.0 - other.0
25740 }
25741}
25742
25743trait RowRangeExt {
25744 type Row;
25745
25746 fn len(&self) -> usize;
25747
25748 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25749}
25750
25751impl RowRangeExt for Range<MultiBufferRow> {
25752 type Row = MultiBufferRow;
25753
25754 fn len(&self) -> usize {
25755 (self.end.0 - self.start.0) as usize
25756 }
25757
25758 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25759 (self.start.0..self.end.0).map(MultiBufferRow)
25760 }
25761}
25762
25763impl RowRangeExt for Range<DisplayRow> {
25764 type Row = DisplayRow;
25765
25766 fn len(&self) -> usize {
25767 (self.end.0 - self.start.0) as usize
25768 }
25769
25770 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25771 (self.start.0..self.end.0).map(DisplayRow)
25772 }
25773}
25774
25775/// If select range has more than one line, we
25776/// just point the cursor to range.start.
25777fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25778 if range.start.row == range.end.row {
25779 range
25780 } else {
25781 range.start..range.start
25782 }
25783}
25784pub struct KillRing(ClipboardItem);
25785impl Global for KillRing {}
25786
25787const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25788
25789enum BreakpointPromptEditAction {
25790 Log,
25791 Condition,
25792 HitCondition,
25793}
25794
25795struct BreakpointPromptEditor {
25796 pub(crate) prompt: Entity<Editor>,
25797 editor: WeakEntity<Editor>,
25798 breakpoint_anchor: Anchor,
25799 breakpoint: Breakpoint,
25800 edit_action: BreakpointPromptEditAction,
25801 block_ids: HashSet<CustomBlockId>,
25802 editor_margins: Arc<Mutex<EditorMargins>>,
25803 _subscriptions: Vec<Subscription>,
25804}
25805
25806impl BreakpointPromptEditor {
25807 const MAX_LINES: u8 = 4;
25808
25809 fn new(
25810 editor: WeakEntity<Editor>,
25811 breakpoint_anchor: Anchor,
25812 breakpoint: Breakpoint,
25813 edit_action: BreakpointPromptEditAction,
25814 window: &mut Window,
25815 cx: &mut Context<Self>,
25816 ) -> Self {
25817 let base_text = match edit_action {
25818 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25819 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25820 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25821 }
25822 .map(|msg| msg.to_string())
25823 .unwrap_or_default();
25824
25825 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25826 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25827
25828 let prompt = cx.new(|cx| {
25829 let mut prompt = Editor::new(
25830 EditorMode::AutoHeight {
25831 min_lines: 1,
25832 max_lines: Some(Self::MAX_LINES as usize),
25833 },
25834 buffer,
25835 None,
25836 window,
25837 cx,
25838 );
25839 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25840 prompt.set_show_cursor_when_unfocused(false, cx);
25841 prompt.set_placeholder_text(
25842 match edit_action {
25843 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25844 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25845 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25846 },
25847 window,
25848 cx,
25849 );
25850
25851 prompt
25852 });
25853
25854 Self {
25855 prompt,
25856 editor,
25857 breakpoint_anchor,
25858 breakpoint,
25859 edit_action,
25860 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25861 block_ids: Default::default(),
25862 _subscriptions: vec![],
25863 }
25864 }
25865
25866 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25867 self.block_ids.extend(block_ids)
25868 }
25869
25870 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25871 if let Some(editor) = self.editor.upgrade() {
25872 let message = self
25873 .prompt
25874 .read(cx)
25875 .buffer
25876 .read(cx)
25877 .as_singleton()
25878 .expect("A multi buffer in breakpoint prompt isn't possible")
25879 .read(cx)
25880 .as_rope()
25881 .to_string();
25882
25883 editor.update(cx, |editor, cx| {
25884 editor.edit_breakpoint_at_anchor(
25885 self.breakpoint_anchor,
25886 self.breakpoint.clone(),
25887 match self.edit_action {
25888 BreakpointPromptEditAction::Log => {
25889 BreakpointEditAction::EditLogMessage(message.into())
25890 }
25891 BreakpointPromptEditAction::Condition => {
25892 BreakpointEditAction::EditCondition(message.into())
25893 }
25894 BreakpointPromptEditAction::HitCondition => {
25895 BreakpointEditAction::EditHitCondition(message.into())
25896 }
25897 },
25898 cx,
25899 );
25900
25901 editor.remove_blocks(self.block_ids.clone(), None, cx);
25902 cx.focus_self(window);
25903 });
25904 }
25905 }
25906
25907 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25908 self.editor
25909 .update(cx, |editor, cx| {
25910 editor.remove_blocks(self.block_ids.clone(), None, cx);
25911 window.focus(&editor.focus_handle);
25912 })
25913 .log_err();
25914 }
25915
25916 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25917 let settings = ThemeSettings::get_global(cx);
25918 let text_style = TextStyle {
25919 color: if self.prompt.read(cx).read_only(cx) {
25920 cx.theme().colors().text_disabled
25921 } else {
25922 cx.theme().colors().text
25923 },
25924 font_family: settings.buffer_font.family.clone(),
25925 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25926 font_size: settings.buffer_font_size(cx).into(),
25927 font_weight: settings.buffer_font.weight,
25928 line_height: relative(settings.buffer_line_height.value()),
25929 ..Default::default()
25930 };
25931 EditorElement::new(
25932 &self.prompt,
25933 EditorStyle {
25934 background: cx.theme().colors().editor_background,
25935 local_player: cx.theme().players().local(),
25936 text: text_style,
25937 ..Default::default()
25938 },
25939 )
25940 }
25941}
25942
25943impl Render for BreakpointPromptEditor {
25944 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25945 let editor_margins = *self.editor_margins.lock();
25946 let gutter_dimensions = editor_margins.gutter;
25947 h_flex()
25948 .key_context("Editor")
25949 .bg(cx.theme().colors().editor_background)
25950 .border_y_1()
25951 .border_color(cx.theme().status().info_border)
25952 .size_full()
25953 .py(window.line_height() / 2.5)
25954 .on_action(cx.listener(Self::confirm))
25955 .on_action(cx.listener(Self::cancel))
25956 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25957 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25958 }
25959}
25960
25961impl Focusable for BreakpointPromptEditor {
25962 fn focus_handle(&self, cx: &App) -> FocusHandle {
25963 self.prompt.focus_handle(cx)
25964 }
25965}
25966
25967fn all_edits_insertions_or_deletions(
25968 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25969 snapshot: &MultiBufferSnapshot,
25970) -> bool {
25971 let mut all_insertions = true;
25972 let mut all_deletions = true;
25973
25974 for (range, new_text) in edits.iter() {
25975 let range_is_empty = range.to_offset(snapshot).is_empty();
25976 let text_is_empty = new_text.is_empty();
25977
25978 if range_is_empty != text_is_empty {
25979 if range_is_empty {
25980 all_deletions = false;
25981 } else {
25982 all_insertions = false;
25983 }
25984 } else {
25985 return false;
25986 }
25987
25988 if !all_insertions && !all_deletions {
25989 return false;
25990 }
25991 }
25992 all_insertions || all_deletions
25993}
25994
25995struct MissingEditPredictionKeybindingTooltip;
25996
25997impl Render for MissingEditPredictionKeybindingTooltip {
25998 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25999 ui::tooltip_container(cx, |container, cx| {
26000 container
26001 .flex_shrink_0()
26002 .max_w_80()
26003 .min_h(rems_from_px(124.))
26004 .justify_between()
26005 .child(
26006 v_flex()
26007 .flex_1()
26008 .text_ui_sm(cx)
26009 .child(Label::new("Conflict with Accept Keybinding"))
26010 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26011 )
26012 .child(
26013 h_flex()
26014 .pb_1()
26015 .gap_1()
26016 .items_end()
26017 .w_full()
26018 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26019 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26020 }))
26021 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26022 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26023 })),
26024 )
26025 })
26026 }
26027}
26028
26029#[derive(Debug, Clone, Copy, PartialEq)]
26030pub struct LineHighlight {
26031 pub background: Background,
26032 pub border: Option<gpui::Hsla>,
26033 pub include_gutter: bool,
26034 pub type_id: Option<TypeId>,
26035}
26036
26037struct LineManipulationResult {
26038 pub new_text: String,
26039 pub line_count_before: usize,
26040 pub line_count_after: usize,
26041}
26042
26043fn render_diff_hunk_controls(
26044 row: u32,
26045 status: &DiffHunkStatus,
26046 hunk_range: Range<Anchor>,
26047 is_created_file: bool,
26048 line_height: Pixels,
26049 editor: &Entity<Editor>,
26050 _window: &mut Window,
26051 cx: &mut App,
26052) -> AnyElement {
26053 h_flex()
26054 .h(line_height)
26055 .mr_1()
26056 .gap_1()
26057 .px_0p5()
26058 .pb_1()
26059 .border_x_1()
26060 .border_b_1()
26061 .border_color(cx.theme().colors().border_variant)
26062 .rounded_b_lg()
26063 .bg(cx.theme().colors().editor_background)
26064 .gap_1()
26065 .block_mouse_except_scroll()
26066 .shadow_md()
26067 .child(if status.has_secondary_hunk() {
26068 Button::new(("stage", row as u64), "Stage")
26069 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26070 .tooltip({
26071 let focus_handle = editor.focus_handle(cx);
26072 move |_window, cx| {
26073 Tooltip::for_action_in(
26074 "Stage Hunk",
26075 &::git::ToggleStaged,
26076 &focus_handle,
26077 cx,
26078 )
26079 }
26080 })
26081 .on_click({
26082 let editor = editor.clone();
26083 move |_event, _window, cx| {
26084 editor.update(cx, |editor, cx| {
26085 editor.stage_or_unstage_diff_hunks(
26086 true,
26087 vec![hunk_range.start..hunk_range.start],
26088 cx,
26089 );
26090 });
26091 }
26092 })
26093 } else {
26094 Button::new(("unstage", row as u64), "Unstage")
26095 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26096 .tooltip({
26097 let focus_handle = editor.focus_handle(cx);
26098 move |_window, cx| {
26099 Tooltip::for_action_in(
26100 "Unstage Hunk",
26101 &::git::ToggleStaged,
26102 &focus_handle,
26103 cx,
26104 )
26105 }
26106 })
26107 .on_click({
26108 let editor = editor.clone();
26109 move |_event, _window, cx| {
26110 editor.update(cx, |editor, cx| {
26111 editor.stage_or_unstage_diff_hunks(
26112 false,
26113 vec![hunk_range.start..hunk_range.start],
26114 cx,
26115 );
26116 });
26117 }
26118 })
26119 })
26120 .child(
26121 Button::new(("restore", row as u64), "Restore")
26122 .tooltip({
26123 let focus_handle = editor.focus_handle(cx);
26124 move |_window, cx| {
26125 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26126 }
26127 })
26128 .on_click({
26129 let editor = editor.clone();
26130 move |_event, window, cx| {
26131 editor.update(cx, |editor, cx| {
26132 let snapshot = editor.snapshot(window, cx);
26133 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26134 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26135 });
26136 }
26137 })
26138 .disabled(is_created_file),
26139 )
26140 .when(
26141 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26142 |el| {
26143 el.child(
26144 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26145 .shape(IconButtonShape::Square)
26146 .icon_size(IconSize::Small)
26147 // .disabled(!has_multiple_hunks)
26148 .tooltip({
26149 let focus_handle = editor.focus_handle(cx);
26150 move |_window, cx| {
26151 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26152 }
26153 })
26154 .on_click({
26155 let editor = editor.clone();
26156 move |_event, window, cx| {
26157 editor.update(cx, |editor, cx| {
26158 let snapshot = editor.snapshot(window, cx);
26159 let position =
26160 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26161 editor.go_to_hunk_before_or_after_position(
26162 &snapshot,
26163 position,
26164 Direction::Next,
26165 window,
26166 cx,
26167 );
26168 editor.expand_selected_diff_hunks(cx);
26169 });
26170 }
26171 }),
26172 )
26173 .child(
26174 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26175 .shape(IconButtonShape::Square)
26176 .icon_size(IconSize::Small)
26177 // .disabled(!has_multiple_hunks)
26178 .tooltip({
26179 let focus_handle = editor.focus_handle(cx);
26180 move |_window, cx| {
26181 Tooltip::for_action_in(
26182 "Previous Hunk",
26183 &GoToPreviousHunk,
26184 &focus_handle,
26185 cx,
26186 )
26187 }
26188 })
26189 .on_click({
26190 let editor = editor.clone();
26191 move |_event, window, cx| {
26192 editor.update(cx, |editor, cx| {
26193 let snapshot = editor.snapshot(window, cx);
26194 let point =
26195 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26196 editor.go_to_hunk_before_or_after_position(
26197 &snapshot,
26198 point,
26199 Direction::Prev,
26200 window,
26201 cx,
26202 );
26203 editor.expand_selected_diff_hunks(cx);
26204 });
26205 }
26206 }),
26207 )
26208 },
26209 )
26210 .into_any_element()
26211}
26212
26213pub fn multibuffer_context_lines(cx: &App) -> u32 {
26214 EditorSettings::try_get(cx)
26215 .map(|settings| settings.excerpt_context_lines)
26216 .unwrap_or(2)
26217 .min(32)
26218}