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::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};
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::{EditPredictionProvider, EditPredictionProviderHandle};
96use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
97use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
98use futures::{
99 FutureExt, StreamExt as _,
100 future::{self, Shared, join},
101 stream::FuturesUnordered,
102};
103use fuzzy::{StringMatch, StringMatchCandidate};
104use git::blame::{GitBlame, GlobalBlameRenderer};
105use gpui::{
106 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
107 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
108 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
109 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
110 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, Render,
111 ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle,
112 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
113 WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative, size,
114};
115use hover_links::{HoverLink, HoveredLinkState, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point,
125 Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
149 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
150 ProjectPath, ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::GitStoreEvent,
159 lsp_store::{
160 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
161 OpenLspBufferHandle,
162 },
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
169use serde::{Deserialize, Serialize};
170use settings::{
171 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
172 update_settings_file,
173};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::{Any, TypeId},
178 borrow::Cow,
179 cell::{OnceCell, RefCell},
180 cmp::{self, Ordering, Reverse},
181 collections::hash_map,
182 iter::{self, Peekable},
183 mem,
184 num::NonZeroU32,
185 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
186 path::{Path, PathBuf},
187 rc::Rc,
188 sync::Arc,
189 time::{Duration, Instant},
190};
191use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
192use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
193use theme::{
194 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
195 observe_buffer_font_size_adjustment,
196};
197use ui::{
198 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
199 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
200};
201use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
202use workspace::{
203 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
204 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
205 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
206 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
207 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
208 searchable::SearchEvent,
209};
210
211use crate::{
212 code_context_menus::CompletionsMenuSource,
213 editor_settings::MultiCursorModifier,
214 hover_links::{find_url, find_url_from_range},
215 inlays::{
216 InlineValueCache,
217 inlay_hints::{LspInlayHintData, inlay_hint_settings},
218 },
219 scroll::{ScrollOffset, ScrollPixelOffset},
220 selections_collection::resolve_selections_wrapping_blocks,
221 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
222};
223
224pub const FILE_HEADER_HEIGHT: u32 = 2;
225pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
226const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
227const MAX_LINE_LEN: usize = 1024;
228const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
229const MAX_SELECTION_HISTORY_LEN: usize = 1024;
230pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
231#[doc(hidden)]
232pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
233pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
234
235pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
236pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
237pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
238pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
239
240pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
241pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
242pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
243
244pub type RenderDiffHunkControlsFn = Arc<
245 dyn Fn(
246 u32,
247 &DiffHunkStatus,
248 Range<Anchor>,
249 bool,
250 Pixels,
251 &Entity<Editor>,
252 &mut Window,
253 &mut App,
254 ) -> AnyElement,
255>;
256
257enum ReportEditorEvent {
258 Saved { auto_saved: bool },
259 EditorOpened,
260 Closed,
261}
262
263impl ReportEditorEvent {
264 pub fn event_type(&self) -> &'static str {
265 match self {
266 Self::Saved { .. } => "Editor Saved",
267 Self::EditorOpened => "Editor Opened",
268 Self::Closed => "Editor Closed",
269 }
270 }
271}
272
273pub enum ActiveDebugLine {}
274pub enum DebugStackFrameLine {}
275enum DocumentHighlightRead {}
276enum DocumentHighlightWrite {}
277enum InputComposition {}
278pub enum PendingInput {}
279enum SelectedTextHighlight {}
280
281pub enum ConflictsOuter {}
282pub enum ConflictsOurs {}
283pub enum ConflictsTheirs {}
284pub enum ConflictsOursMarker {}
285pub enum ConflictsTheirsMarker {}
286
287pub struct HunkAddedColor;
288pub struct HunkRemovedColor;
289
290#[derive(Debug, Copy, Clone, PartialEq, Eq)]
291pub enum Navigated {
292 Yes,
293 No,
294}
295
296impl Navigated {
297 pub fn from_bool(yes: bool) -> Navigated {
298 if yes { Navigated::Yes } else { Navigated::No }
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303enum DisplayDiffHunk {
304 Folded {
305 display_row: DisplayRow,
306 },
307 Unfolded {
308 is_created_file: bool,
309 diff_base_byte_range: Range<usize>,
310 display_row_range: Range<DisplayRow>,
311 multi_buffer_range: Range<Anchor>,
312 status: DiffHunkStatus,
313 word_diffs: Vec<Range<MultiBufferOffset>>,
314 },
315}
316
317pub enum HideMouseCursorOrigin {
318 TypingAction,
319 MovementAction,
320}
321
322pub fn init(cx: &mut App) {
323 cx.set_global(GlobalBlameRenderer(Arc::new(())));
324
325 workspace::register_project_item::<Editor>(cx);
326 workspace::FollowableViewRegistry::register::<Editor>(cx);
327 workspace::register_serializable_item::<Editor>(cx);
328
329 cx.observe_new(
330 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
331 workspace.register_action(Editor::new_file);
332 workspace.register_action(Editor::new_file_split);
333 workspace.register_action(Editor::new_file_vertical);
334 workspace.register_action(Editor::new_file_horizontal);
335 workspace.register_action(Editor::cancel_language_server_work);
336 workspace.register_action(Editor::toggle_focus);
337 },
338 )
339 .detach();
340
341 cx.on_action(move |_: &workspace::NewFile, cx| {
342 let app_state = workspace::AppState::global(cx);
343 if let Some(app_state) = app_state.upgrade() {
344 workspace::open_new(
345 Default::default(),
346 app_state,
347 cx,
348 |workspace, window, cx| {
349 Editor::new_file(workspace, &Default::default(), window, cx)
350 },
351 )
352 .detach();
353 }
354 });
355 cx.on_action(move |_: &workspace::NewWindow, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 cx.activate(true);
364 Editor::new_file(workspace, &Default::default(), window, cx)
365 },
366 )
367 .detach();
368 }
369 });
370}
371
372pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
373 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
374}
375
376pub trait DiagnosticRenderer {
377 fn render_group(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 buffer_id: BufferId,
381 snapshot: EditorSnapshot,
382 editor: WeakEntity<Editor>,
383 language_registry: Option<Arc<LanguageRegistry>>,
384 cx: &mut App,
385 ) -> Vec<BlockProperties<Anchor>>;
386
387 fn render_hover(
388 &self,
389 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
390 range: Range<Point>,
391 buffer_id: BufferId,
392 language_registry: Option<Arc<LanguageRegistry>>,
393 cx: &mut App,
394 ) -> Option<Entity<markdown::Markdown>>;
395
396 fn open_link(
397 &self,
398 editor: &mut Editor,
399 link: SharedString,
400 window: &mut Window,
401 cx: &mut Context<Editor>,
402 );
403}
404
405pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
406
407impl GlobalDiagnosticRenderer {
408 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
409 cx.try_global::<Self>().map(|g| g.0.clone())
410 }
411}
412
413impl gpui::Global for GlobalDiagnosticRenderer {}
414pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
415 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
416}
417
418pub struct SearchWithinRange;
419
420trait InvalidationRegion {
421 fn ranges(&self) -> &[Range<Anchor>];
422}
423
424#[derive(Clone, Debug, PartialEq)]
425pub enum SelectPhase {
426 Begin {
427 position: DisplayPoint,
428 add: bool,
429 click_count: usize,
430 },
431 BeginColumnar {
432 position: DisplayPoint,
433 reset: bool,
434 mode: ColumnarMode,
435 goal_column: u32,
436 },
437 Extend {
438 position: DisplayPoint,
439 click_count: usize,
440 },
441 Update {
442 position: DisplayPoint,
443 goal_column: u32,
444 scroll_delta: gpui::Point<f32>,
445 },
446 End,
447}
448
449#[derive(Clone, Debug, PartialEq)]
450pub enum ColumnarMode {
451 FromMouse,
452 FromSelection,
453}
454
455#[derive(Clone, Debug)]
456pub enum SelectMode {
457 Character,
458 Word(Range<Anchor>),
459 Line(Range<Anchor>),
460 All,
461}
462
463#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
464pub enum SizingBehavior {
465 /// The editor will layout itself using `size_full` and will include the vertical
466 /// scroll margin as requested by user settings.
467 #[default]
468 Default,
469 /// The editor will layout itself using `size_full`, but will not have any
470 /// vertical overscroll.
471 ExcludeOverscrollMargin,
472 /// The editor will request a vertical size according to its content and will be
473 /// layouted without a vertical scroll margin.
474 SizeByContent,
475}
476
477#[derive(Clone, PartialEq, Eq, Debug)]
478pub enum EditorMode {
479 SingleLine,
480 AutoHeight {
481 min_lines: usize,
482 max_lines: Option<usize>,
483 },
484 Full {
485 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
486 scale_ui_elements_with_buffer_font_size: bool,
487 /// When set to `true`, the editor will render a background for the active line.
488 show_active_line_background: bool,
489 /// Determines the sizing behavior for this editor
490 sizing_behavior: SizingBehavior,
491 },
492 Minimap {
493 parent: WeakEntity<Editor>,
494 },
495}
496
497impl EditorMode {
498 pub fn full() -> Self {
499 Self::Full {
500 scale_ui_elements_with_buffer_font_size: true,
501 show_active_line_background: true,
502 sizing_behavior: SizingBehavior::Default,
503 }
504 }
505
506 #[inline]
507 pub fn is_full(&self) -> bool {
508 matches!(self, Self::Full { .. })
509 }
510
511 #[inline]
512 pub fn is_single_line(&self) -> bool {
513 matches!(self, Self::SingleLine { .. })
514 }
515
516 #[inline]
517 fn is_minimap(&self) -> bool {
518 matches!(self, Self::Minimap { .. })
519 }
520}
521
522#[derive(Copy, Clone, Debug)]
523pub enum SoftWrap {
524 /// Prefer not to wrap at all.
525 ///
526 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
527 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
528 GitDiff,
529 /// Prefer a single line generally, unless an overly long line is encountered.
530 None,
531 /// Soft wrap lines that exceed the editor width.
532 EditorWidth,
533 /// Soft wrap lines at the preferred line length.
534 Column(u32),
535 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
536 Bounded(u32),
537}
538
539#[derive(Clone)]
540pub struct EditorStyle {
541 pub background: Hsla,
542 pub border: Hsla,
543 pub local_player: PlayerColor,
544 pub text: TextStyle,
545 pub scrollbar_width: Pixels,
546 pub syntax: Arc<SyntaxTheme>,
547 pub status: StatusColors,
548 pub inlay_hints_style: HighlightStyle,
549 pub edit_prediction_styles: EditPredictionStyles,
550 pub unnecessary_code_fade: f32,
551 pub show_underlines: bool,
552}
553
554impl Default for EditorStyle {
555 fn default() -> Self {
556 Self {
557 background: Hsla::default(),
558 border: Hsla::default(),
559 local_player: PlayerColor::default(),
560 text: TextStyle::default(),
561 scrollbar_width: Pixels::default(),
562 syntax: Default::default(),
563 // HACK: Status colors don't have a real default.
564 // We should look into removing the status colors from the editor
565 // style and retrieve them directly from the theme.
566 status: StatusColors::dark(),
567 inlay_hints_style: HighlightStyle::default(),
568 edit_prediction_styles: EditPredictionStyles {
569 insertion: HighlightStyle::default(),
570 whitespace: HighlightStyle::default(),
571 },
572 unnecessary_code_fade: Default::default(),
573 show_underlines: true,
574 }
575 }
576}
577
578pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
579 let show_background = language_settings::language_settings(None, None, cx)
580 .inlay_hints
581 .show_background;
582
583 let mut style = cx.theme().syntax().get("hint");
584
585 if style.color.is_none() {
586 style.color = Some(cx.theme().status().hint);
587 }
588
589 if !show_background {
590 style.background_color = None;
591 return style;
592 }
593
594 if style.background_color.is_none() {
595 style.background_color = Some(cx.theme().status().hint_background);
596 }
597
598 style
599}
600
601pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
602 EditPredictionStyles {
603 insertion: HighlightStyle {
604 color: Some(cx.theme().status().predictive),
605 ..HighlightStyle::default()
606 },
607 whitespace: HighlightStyle {
608 background_color: Some(cx.theme().status().created_background),
609 ..HighlightStyle::default()
610 },
611 }
612}
613
614type CompletionId = usize;
615
616pub(crate) enum EditDisplayMode {
617 TabAccept,
618 DiffPopover,
619 Inline,
620}
621
622enum EditPrediction {
623 Edit {
624 edits: Vec<(Range<Anchor>, Arc<str>)>,
625 edit_preview: Option<EditPreview>,
626 display_mode: EditDisplayMode,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in the active editor
630 MoveWithin {
631 target: Anchor,
632 snapshot: BufferSnapshot,
633 },
634 /// Move to a specific location in a different editor (not the active one)
635 MoveOutside {
636 target: language::Anchor,
637 snapshot: BufferSnapshot,
638 },
639}
640
641struct EditPredictionState {
642 inlay_ids: Vec<InlayId>,
643 completion: EditPrediction,
644 completion_id: Option<SharedString>,
645 invalidation_range: Option<Range<Anchor>>,
646}
647
648enum EditPredictionSettings {
649 Disabled,
650 Enabled {
651 show_in_menu: bool,
652 preview_requires_modifier: bool,
653 },
654}
655
656enum EditPredictionHighlight {}
657
658#[derive(Debug, Clone)]
659struct InlineDiagnostic {
660 message: SharedString,
661 group_id: usize,
662 is_primary: bool,
663 start: Point,
664 severity: lsp::DiagnosticSeverity,
665}
666
667pub enum MenuEditPredictionsPolicy {
668 Never,
669 ByProvider,
670}
671
672pub enum EditPredictionPreview {
673 /// Modifier is not pressed
674 Inactive { released_too_fast: bool },
675 /// Modifier pressed
676 Active {
677 since: Instant,
678 previous_scroll_position: Option<ScrollAnchor>,
679 },
680}
681
682impl EditPredictionPreview {
683 pub fn released_too_fast(&self) -> bool {
684 match self {
685 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
686 EditPredictionPreview::Active { .. } => false,
687 }
688 }
689
690 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
691 if let EditPredictionPreview::Active {
692 previous_scroll_position,
693 ..
694 } = self
695 {
696 *previous_scroll_position = scroll_position;
697 }
698 }
699}
700
701pub struct ContextMenuOptions {
702 pub min_entries_visible: usize,
703 pub max_entries_visible: usize,
704 pub placement: Option<ContextMenuPlacement>,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum ContextMenuPlacement {
709 Above,
710 Below,
711}
712
713#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
714struct EditorActionId(usize);
715
716impl EditorActionId {
717 pub fn post_inc(&mut self) -> Self {
718 let answer = self.0;
719
720 *self = Self(answer + 1);
721
722 Self(answer)
723 }
724}
725
726// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
727// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
728
729type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
730type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
731
732#[derive(Default)]
733struct ScrollbarMarkerState {
734 scrollbar_size: Size<Pixels>,
735 dirty: bool,
736 markers: Arc<[PaintQuad]>,
737 pending_refresh: Option<Task<Result<()>>>,
738}
739
740impl ScrollbarMarkerState {
741 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
742 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
743 }
744}
745
746#[derive(Clone, Copy, PartialEq, Eq)]
747pub enum MinimapVisibility {
748 Disabled,
749 Enabled {
750 /// The configuration currently present in the users settings.
751 setting_configuration: bool,
752 /// Whether to override the currently set visibility from the users setting.
753 toggle_override: bool,
754 },
755}
756
757impl MinimapVisibility {
758 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
759 if mode.is_full() {
760 Self::Enabled {
761 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
762 toggle_override: false,
763 }
764 } else {
765 Self::Disabled
766 }
767 }
768
769 fn hidden(&self) -> Self {
770 match *self {
771 Self::Enabled {
772 setting_configuration,
773 ..
774 } => Self::Enabled {
775 setting_configuration,
776 toggle_override: setting_configuration,
777 },
778 Self::Disabled => Self::Disabled,
779 }
780 }
781
782 fn disabled(&self) -> bool {
783 matches!(*self, Self::Disabled)
784 }
785
786 fn settings_visibility(&self) -> bool {
787 match *self {
788 Self::Enabled {
789 setting_configuration,
790 ..
791 } => setting_configuration,
792 _ => false,
793 }
794 }
795
796 fn visible(&self) -> bool {
797 match *self {
798 Self::Enabled {
799 setting_configuration,
800 toggle_override,
801 } => setting_configuration ^ toggle_override,
802 _ => false,
803 }
804 }
805
806 fn toggle_visibility(&self) -> Self {
807 match *self {
808 Self::Enabled {
809 toggle_override,
810 setting_configuration,
811 } => Self::Enabled {
812 setting_configuration,
813 toggle_override: !toggle_override,
814 },
815 Self::Disabled => Self::Disabled,
816 }
817 }
818}
819
820#[derive(Debug, Clone, Copy, PartialEq, Eq)]
821pub enum BufferSerialization {
822 All,
823 NonDirtyBuffers,
824}
825
826impl BufferSerialization {
827 fn new(restore_unsaved_buffers: bool) -> Self {
828 if restore_unsaved_buffers {
829 Self::All
830 } else {
831 Self::NonDirtyBuffers
832 }
833 }
834}
835
836#[derive(Clone, Debug)]
837struct RunnableTasks {
838 templates: Vec<(TaskSourceKind, TaskTemplate)>,
839 offset: multi_buffer::Anchor,
840 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
841 column: u32,
842 // Values of all named captures, including those starting with '_'
843 extra_variables: HashMap<String, String>,
844 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
845 context_range: Range<BufferOffset>,
846}
847
848impl RunnableTasks {
849 fn resolve<'a>(
850 &'a self,
851 cx: &'a task::TaskContext,
852 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
853 self.templates.iter().filter_map(|(kind, template)| {
854 template
855 .resolve_task(&kind.to_id_base(), cx)
856 .map(|task| (kind.clone(), task))
857 })
858 }
859}
860
861#[derive(Clone)]
862pub struct ResolvedTasks {
863 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
864 position: Anchor,
865}
866
867/// Addons allow storing per-editor state in other crates (e.g. Vim)
868pub trait Addon: 'static {
869 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
870
871 fn render_buffer_header_controls(
872 &self,
873 _: &ExcerptInfo,
874 _: &Window,
875 _: &App,
876 ) -> Option<AnyElement> {
877 None
878 }
879
880 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
881 None
882 }
883
884 fn to_any(&self) -> &dyn std::any::Any;
885
886 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
887 None
888 }
889}
890
891struct ChangeLocation {
892 current: Option<Vec<Anchor>>,
893 original: Vec<Anchor>,
894}
895impl ChangeLocation {
896 fn locations(&self) -> &[Anchor] {
897 self.current.as_ref().unwrap_or(&self.original)
898 }
899}
900
901/// A set of caret positions, registered when the editor was edited.
902pub struct ChangeList {
903 changes: Vec<ChangeLocation>,
904 /// Currently "selected" change.
905 position: Option<usize>,
906}
907
908impl ChangeList {
909 pub fn new() -> Self {
910 Self {
911 changes: Vec::new(),
912 position: None,
913 }
914 }
915
916 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
917 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
918 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
919 if self.changes.is_empty() {
920 return None;
921 }
922
923 let prev = self.position.unwrap_or(self.changes.len());
924 let next = if direction == Direction::Prev {
925 prev.saturating_sub(count)
926 } else {
927 (prev + count).min(self.changes.len() - 1)
928 };
929 self.position = Some(next);
930 self.changes.get(next).map(|change| change.locations())
931 }
932
933 /// Adds a new change to the list, resetting the change list position.
934 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
935 self.position.take();
936 if let Some(last) = self.changes.last_mut()
937 && group
938 {
939 last.current = Some(new_positions)
940 } else {
941 self.changes.push(ChangeLocation {
942 original: new_positions,
943 current: None,
944 });
945 }
946 }
947
948 pub fn last(&self) -> Option<&[Anchor]> {
949 self.changes.last().map(|change| change.locations())
950 }
951
952 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
953 self.changes.last().map(|change| change.original.as_slice())
954 }
955
956 pub fn invert_last_group(&mut self) {
957 if let Some(last) = self.changes.last_mut()
958 && let Some(current) = last.current.as_mut()
959 {
960 mem::swap(&mut last.original, current);
961 }
962 }
963}
964
965#[derive(Clone)]
966struct InlineBlamePopoverState {
967 scroll_handle: ScrollHandle,
968 commit_message: Option<ParsedCommitMessage>,
969 markdown: Entity<Markdown>,
970}
971
972struct InlineBlamePopover {
973 position: gpui::Point<Pixels>,
974 hide_task: Option<Task<()>>,
975 popover_bounds: Option<Bounds<Pixels>>,
976 popover_state: InlineBlamePopoverState,
977 keyboard_grace: bool,
978}
979
980enum SelectionDragState {
981 /// State when no drag related activity is detected.
982 None,
983 /// State when the mouse is down on a selection that is about to be dragged.
984 ReadyToDrag {
985 selection: Selection<Anchor>,
986 click_position: gpui::Point<Pixels>,
987 mouse_down_time: Instant,
988 },
989 /// State when the mouse is dragging the selection in the editor.
990 Dragging {
991 selection: Selection<Anchor>,
992 drop_cursor: Selection<Anchor>,
993 hide_drop_cursor: bool,
994 },
995}
996
997enum ColumnarSelectionState {
998 FromMouse {
999 selection_tail: Anchor,
1000 display_point: Option<DisplayPoint>,
1001 },
1002 FromSelection {
1003 selection_tail: Anchor,
1004 },
1005}
1006
1007/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1008/// a breakpoint on them.
1009#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1010struct PhantomBreakpointIndicator {
1011 display_row: DisplayRow,
1012 /// There's a small debounce between hovering over the line and showing the indicator.
1013 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1014 is_active: bool,
1015 collides_with_existing_breakpoint: bool,
1016}
1017
1018/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1019///
1020/// See the [module level documentation](self) for more information.
1021pub struct Editor {
1022 focus_handle: FocusHandle,
1023 last_focused_descendant: Option<WeakFocusHandle>,
1024 /// The text buffer being edited
1025 buffer: Entity<MultiBuffer>,
1026 /// Map of how text in the buffer should be displayed.
1027 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1028 pub display_map: Entity<DisplayMap>,
1029 placeholder_display_map: Option<Entity<DisplayMap>>,
1030 pub selections: SelectionsCollection,
1031 pub scroll_manager: ScrollManager,
1032 /// When inline assist editors are linked, they all render cursors because
1033 /// typing enters text into each of them, even the ones that aren't focused.
1034 pub(crate) show_cursor_when_unfocused: bool,
1035 columnar_selection_state: Option<ColumnarSelectionState>,
1036 add_selections_state: Option<AddSelectionsState>,
1037 select_next_state: Option<SelectNextState>,
1038 select_prev_state: Option<SelectNextState>,
1039 selection_history: SelectionHistory,
1040 defer_selection_effects: bool,
1041 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1042 autoclose_regions: Vec<AutocloseRegion>,
1043 snippet_stack: InvalidationStack<SnippetState>,
1044 select_syntax_node_history: SelectSyntaxNodeHistory,
1045 ime_transaction: Option<TransactionId>,
1046 pub diagnostics_max_severity: DiagnosticSeverity,
1047 active_diagnostics: ActiveDiagnostic,
1048 show_inline_diagnostics: bool,
1049 inline_diagnostics_update: Task<()>,
1050 inline_diagnostics_enabled: bool,
1051 diagnostics_enabled: bool,
1052 word_completions_enabled: bool,
1053 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1054 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1055 hard_wrap: Option<usize>,
1056 project: Option<Entity<Project>>,
1057 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1058 completion_provider: Option<Rc<dyn CompletionProvider>>,
1059 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1060 blink_manager: Entity<BlinkManager>,
1061 show_cursor_names: bool,
1062 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1063 pub show_local_selections: bool,
1064 mode: EditorMode,
1065 show_breadcrumbs: bool,
1066 show_gutter: bool,
1067 show_scrollbars: ScrollbarAxes,
1068 minimap_visibility: MinimapVisibility,
1069 offset_content: bool,
1070 disable_expand_excerpt_buttons: bool,
1071 show_line_numbers: Option<bool>,
1072 use_relative_line_numbers: Option<bool>,
1073 show_git_diff_gutter: Option<bool>,
1074 show_code_actions: Option<bool>,
1075 show_runnables: Option<bool>,
1076 show_breakpoints: Option<bool>,
1077 show_wrap_guides: Option<bool>,
1078 show_indent_guides: Option<bool>,
1079 highlight_order: usize,
1080 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1081 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1082 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1083 scrollbar_marker_state: ScrollbarMarkerState,
1084 active_indent_guides_state: ActiveIndentGuidesState,
1085 nav_history: Option<ItemNavHistory>,
1086 context_menu: RefCell<Option<CodeContextMenu>>,
1087 context_menu_options: Option<ContextMenuOptions>,
1088 mouse_context_menu: Option<MouseContextMenu>,
1089 completion_tasks: Vec<(CompletionId, Task<()>)>,
1090 inline_blame_popover: Option<InlineBlamePopover>,
1091 inline_blame_popover_show_task: Option<Task<()>>,
1092 signature_help_state: SignatureHelpState,
1093 auto_signature_help: Option<bool>,
1094 find_all_references_task_sources: Vec<Anchor>,
1095 next_completion_id: CompletionId,
1096 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1097 code_actions_task: Option<Task<Result<()>>>,
1098 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1099 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1100 document_highlights_task: Option<Task<()>>,
1101 linked_editing_range_task: Option<Task<Option<()>>>,
1102 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1103 pending_rename: Option<RenameState>,
1104 searchable: bool,
1105 cursor_shape: CursorShape,
1106 current_line_highlight: Option<CurrentLineHighlight>,
1107 pub collapse_matches: bool,
1108 autoindent_mode: Option<AutoindentMode>,
1109 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1110 input_enabled: bool,
1111 use_modal_editing: bool,
1112 read_only: bool,
1113 leader_id: Option<CollaboratorId>,
1114 remote_id: Option<ViewId>,
1115 pub hover_state: HoverState,
1116 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1117 gutter_hovered: bool,
1118 hovered_link_state: Option<HoveredLinkState>,
1119 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1120 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1121 active_edit_prediction: Option<EditPredictionState>,
1122 /// Used to prevent flickering as the user types while the menu is open
1123 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1124 edit_prediction_settings: EditPredictionSettings,
1125 edit_predictions_hidden_for_vim_mode: bool,
1126 show_edit_predictions_override: Option<bool>,
1127 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1128 edit_prediction_preview: EditPredictionPreview,
1129 edit_prediction_indent_conflict: bool,
1130 edit_prediction_requires_modifier_in_indent_conflict: bool,
1131 next_inlay_id: usize,
1132 next_color_inlay_id: usize,
1133 _subscriptions: Vec<Subscription>,
1134 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1135 gutter_dimensions: GutterDimensions,
1136 style: Option<EditorStyle>,
1137 text_style_refinement: Option<TextStyleRefinement>,
1138 next_editor_action_id: EditorActionId,
1139 editor_actions: Rc<
1140 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1141 >,
1142 use_autoclose: bool,
1143 use_auto_surround: bool,
1144 auto_replace_emoji_shortcode: bool,
1145 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1146 show_git_blame_gutter: bool,
1147 show_git_blame_inline: bool,
1148 show_git_blame_inline_delay_task: Option<Task<()>>,
1149 git_blame_inline_enabled: bool,
1150 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1151 buffer_serialization: Option<BufferSerialization>,
1152 show_selection_menu: Option<bool>,
1153 blame: Option<Entity<GitBlame>>,
1154 blame_subscription: Option<Subscription>,
1155 custom_context_menu: Option<
1156 Box<
1157 dyn 'static
1158 + Fn(
1159 &mut Self,
1160 DisplayPoint,
1161 &mut Window,
1162 &mut Context<Self>,
1163 ) -> Option<Entity<ui::ContextMenu>>,
1164 >,
1165 >,
1166 last_bounds: Option<Bounds<Pixels>>,
1167 last_position_map: Option<Rc<PositionMap>>,
1168 expect_bounds_change: Option<Bounds<Pixels>>,
1169 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1170 tasks_update_task: Option<Task<()>>,
1171 breakpoint_store: Option<Entity<BreakpointStore>>,
1172 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1173 hovered_diff_hunk_row: Option<DisplayRow>,
1174 pull_diagnostics_task: Task<()>,
1175 in_project_search: bool,
1176 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1177 breadcrumb_header: Option<String>,
1178 focused_block: Option<FocusedBlock>,
1179 next_scroll_position: NextScrollCursorCenterTopBottom,
1180 addons: HashMap<TypeId, Box<dyn Addon>>,
1181 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1182 load_diff_task: Option<Shared<Task<()>>>,
1183 /// Whether we are temporarily displaying a diff other than git's
1184 temporary_diff_override: bool,
1185 selection_mark_mode: bool,
1186 toggle_fold_multiple_buffers: Task<()>,
1187 _scroll_cursor_center_top_bottom_task: Task<()>,
1188 serialize_selections: Task<()>,
1189 serialize_folds: Task<()>,
1190 mouse_cursor_hidden: bool,
1191 minimap: Option<Entity<Self>>,
1192 hide_mouse_mode: HideMouseMode,
1193 pub change_list: ChangeList,
1194 inline_value_cache: InlineValueCache,
1195
1196 selection_drag_state: SelectionDragState,
1197 colors: Option<LspColorData>,
1198 post_scroll_update: Task<()>,
1199 refresh_colors_task: Task<()>,
1200 inlay_hints: Option<LspInlayHintData>,
1201 folding_newlines: Task<()>,
1202 select_next_is_case_sensitive: Option<bool>,
1203 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1204 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1205 accent_overrides: Vec<SharedString>,
1206 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1207 use_base_text_line_numbers: bool,
1208}
1209
1210fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1211 if debounce_ms > 0 {
1212 Some(Duration::from_millis(debounce_ms))
1213 } else {
1214 None
1215 }
1216}
1217
1218#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1219enum NextScrollCursorCenterTopBottom {
1220 #[default]
1221 Center,
1222 Top,
1223 Bottom,
1224}
1225
1226impl NextScrollCursorCenterTopBottom {
1227 fn next(&self) -> Self {
1228 match self {
1229 Self::Center => Self::Top,
1230 Self::Top => Self::Bottom,
1231 Self::Bottom => Self::Center,
1232 }
1233 }
1234}
1235
1236#[derive(Clone)]
1237pub struct EditorSnapshot {
1238 pub mode: EditorMode,
1239 show_gutter: bool,
1240 show_line_numbers: Option<bool>,
1241 show_git_diff_gutter: Option<bool>,
1242 show_code_actions: Option<bool>,
1243 show_runnables: Option<bool>,
1244 show_breakpoints: Option<bool>,
1245 git_blame_gutter_max_author_length: Option<usize>,
1246 pub display_snapshot: DisplaySnapshot,
1247 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1248 is_focused: bool,
1249 scroll_anchor: ScrollAnchor,
1250 ongoing_scroll: OngoingScroll,
1251 current_line_highlight: CurrentLineHighlight,
1252 gutter_hovered: bool,
1253}
1254
1255#[derive(Default, Debug, Clone, Copy)]
1256pub struct GutterDimensions {
1257 pub left_padding: Pixels,
1258 pub right_padding: Pixels,
1259 pub width: Pixels,
1260 pub margin: Pixels,
1261 pub git_blame_entries_width: Option<Pixels>,
1262}
1263
1264impl GutterDimensions {
1265 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1266 Self {
1267 margin: Self::default_gutter_margin(font_id, font_size, cx),
1268 ..Default::default()
1269 }
1270 }
1271
1272 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1273 -cx.text_system().descent(font_id, font_size)
1274 }
1275 /// The full width of the space taken up by the gutter.
1276 pub fn full_width(&self) -> Pixels {
1277 self.margin + self.width
1278 }
1279
1280 /// The width of the space reserved for the fold indicators,
1281 /// use alongside 'justify_end' and `gutter_width` to
1282 /// right align content with the line numbers
1283 pub fn fold_area_width(&self) -> Pixels {
1284 self.margin + self.right_padding
1285 }
1286}
1287
1288struct CharacterDimensions {
1289 em_width: Pixels,
1290 em_advance: Pixels,
1291 line_height: Pixels,
1292}
1293
1294#[derive(Debug)]
1295pub struct RemoteSelection {
1296 pub replica_id: ReplicaId,
1297 pub selection: Selection<Anchor>,
1298 pub cursor_shape: CursorShape,
1299 pub collaborator_id: CollaboratorId,
1300 pub line_mode: bool,
1301 pub user_name: Option<SharedString>,
1302 pub color: PlayerColor,
1303}
1304
1305#[derive(Clone, Debug)]
1306struct SelectionHistoryEntry {
1307 selections: Arc<[Selection<Anchor>]>,
1308 select_next_state: Option<SelectNextState>,
1309 select_prev_state: Option<SelectNextState>,
1310 add_selections_state: Option<AddSelectionsState>,
1311}
1312
1313#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1314enum SelectionHistoryMode {
1315 #[default]
1316 Normal,
1317 Undoing,
1318 Redoing,
1319 Skipping,
1320}
1321
1322#[derive(Clone, PartialEq, Eq, Hash)]
1323struct HoveredCursor {
1324 replica_id: ReplicaId,
1325 selection_id: usize,
1326}
1327
1328#[derive(Debug)]
1329/// SelectionEffects controls the side-effects of updating the selection.
1330///
1331/// The default behaviour does "what you mostly want":
1332/// - it pushes to the nav history if the cursor moved by >10 lines
1333/// - it re-triggers completion requests
1334/// - it scrolls to fit
1335///
1336/// You might want to modify these behaviours. For example when doing a "jump"
1337/// like go to definition, we always want to add to nav history; but when scrolling
1338/// in vim mode we never do.
1339///
1340/// Similarly, you might want to disable scrolling if you don't want the viewport to
1341/// move.
1342#[derive(Clone)]
1343pub struct SelectionEffects {
1344 nav_history: Option<bool>,
1345 completions: bool,
1346 scroll: Option<Autoscroll>,
1347}
1348
1349impl Default for SelectionEffects {
1350 fn default() -> Self {
1351 Self {
1352 nav_history: None,
1353 completions: true,
1354 scroll: Some(Autoscroll::fit()),
1355 }
1356 }
1357}
1358impl SelectionEffects {
1359 pub fn scroll(scroll: Autoscroll) -> Self {
1360 Self {
1361 scroll: Some(scroll),
1362 ..Default::default()
1363 }
1364 }
1365
1366 pub fn no_scroll() -> Self {
1367 Self {
1368 scroll: None,
1369 ..Default::default()
1370 }
1371 }
1372
1373 pub fn completions(self, completions: bool) -> Self {
1374 Self {
1375 completions,
1376 ..self
1377 }
1378 }
1379
1380 pub fn nav_history(self, nav_history: bool) -> Self {
1381 Self {
1382 nav_history: Some(nav_history),
1383 ..self
1384 }
1385 }
1386}
1387
1388struct DeferredSelectionEffectsState {
1389 changed: bool,
1390 effects: SelectionEffects,
1391 old_cursor_position: Anchor,
1392 history_entry: SelectionHistoryEntry,
1393}
1394
1395#[derive(Default)]
1396struct SelectionHistory {
1397 #[allow(clippy::type_complexity)]
1398 selections_by_transaction:
1399 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1400 mode: SelectionHistoryMode,
1401 undo_stack: VecDeque<SelectionHistoryEntry>,
1402 redo_stack: VecDeque<SelectionHistoryEntry>,
1403}
1404
1405impl SelectionHistory {
1406 #[track_caller]
1407 fn insert_transaction(
1408 &mut self,
1409 transaction_id: TransactionId,
1410 selections: Arc<[Selection<Anchor>]>,
1411 ) {
1412 if selections.is_empty() {
1413 log::error!(
1414 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1415 std::panic::Location::caller()
1416 );
1417 return;
1418 }
1419 self.selections_by_transaction
1420 .insert(transaction_id, (selections, None));
1421 }
1422
1423 #[allow(clippy::type_complexity)]
1424 fn transaction(
1425 &self,
1426 transaction_id: TransactionId,
1427 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1428 self.selections_by_transaction.get(&transaction_id)
1429 }
1430
1431 #[allow(clippy::type_complexity)]
1432 fn transaction_mut(
1433 &mut self,
1434 transaction_id: TransactionId,
1435 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1436 self.selections_by_transaction.get_mut(&transaction_id)
1437 }
1438
1439 fn push(&mut self, entry: SelectionHistoryEntry) {
1440 if !entry.selections.is_empty() {
1441 match self.mode {
1442 SelectionHistoryMode::Normal => {
1443 self.push_undo(entry);
1444 self.redo_stack.clear();
1445 }
1446 SelectionHistoryMode::Undoing => self.push_redo(entry),
1447 SelectionHistoryMode::Redoing => self.push_undo(entry),
1448 SelectionHistoryMode::Skipping => {}
1449 }
1450 }
1451 }
1452
1453 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1454 if self
1455 .undo_stack
1456 .back()
1457 .is_none_or(|e| e.selections != entry.selections)
1458 {
1459 self.undo_stack.push_back(entry);
1460 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1461 self.undo_stack.pop_front();
1462 }
1463 }
1464 }
1465
1466 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1467 if self
1468 .redo_stack
1469 .back()
1470 .is_none_or(|e| e.selections != entry.selections)
1471 {
1472 self.redo_stack.push_back(entry);
1473 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1474 self.redo_stack.pop_front();
1475 }
1476 }
1477 }
1478}
1479
1480#[derive(Clone, Copy)]
1481pub struct RowHighlightOptions {
1482 pub autoscroll: bool,
1483 pub include_gutter: bool,
1484}
1485
1486impl Default for RowHighlightOptions {
1487 fn default() -> Self {
1488 Self {
1489 autoscroll: Default::default(),
1490 include_gutter: true,
1491 }
1492 }
1493}
1494
1495struct RowHighlight {
1496 index: usize,
1497 range: Range<Anchor>,
1498 color: Hsla,
1499 options: RowHighlightOptions,
1500 type_id: TypeId,
1501}
1502
1503#[derive(Clone, Debug)]
1504struct AddSelectionsState {
1505 groups: Vec<AddSelectionsGroup>,
1506}
1507
1508#[derive(Clone, Debug)]
1509struct AddSelectionsGroup {
1510 above: bool,
1511 stack: Vec<usize>,
1512}
1513
1514#[derive(Clone)]
1515struct SelectNextState {
1516 query: AhoCorasick,
1517 wordwise: bool,
1518 done: bool,
1519}
1520
1521impl std::fmt::Debug for SelectNextState {
1522 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1523 f.debug_struct(std::any::type_name::<Self>())
1524 .field("wordwise", &self.wordwise)
1525 .field("done", &self.done)
1526 .finish()
1527 }
1528}
1529
1530#[derive(Debug)]
1531struct AutocloseRegion {
1532 selection_id: usize,
1533 range: Range<Anchor>,
1534 pair: BracketPair,
1535}
1536
1537#[derive(Debug)]
1538struct SnippetState {
1539 ranges: Vec<Vec<Range<Anchor>>>,
1540 active_index: usize,
1541 choices: Vec<Option<Vec<String>>>,
1542}
1543
1544#[doc(hidden)]
1545pub struct RenameState {
1546 pub range: Range<Anchor>,
1547 pub old_name: Arc<str>,
1548 pub editor: Entity<Editor>,
1549 block_id: CustomBlockId,
1550}
1551
1552struct InvalidationStack<T>(Vec<T>);
1553
1554struct RegisteredEditPredictionProvider {
1555 provider: Arc<dyn EditPredictionProviderHandle>,
1556 _subscription: Subscription,
1557}
1558
1559#[derive(Debug, PartialEq, Eq)]
1560pub struct ActiveDiagnosticGroup {
1561 pub active_range: Range<Anchor>,
1562 pub active_message: String,
1563 pub group_id: usize,
1564 pub blocks: HashSet<CustomBlockId>,
1565}
1566
1567#[derive(Debug, PartialEq, Eq)]
1568
1569pub(crate) enum ActiveDiagnostic {
1570 None,
1571 All,
1572 Group(ActiveDiagnosticGroup),
1573}
1574
1575#[derive(Serialize, Deserialize, Clone, Debug)]
1576pub struct ClipboardSelection {
1577 /// The number of bytes in this selection.
1578 pub len: usize,
1579 /// Whether this was a full-line selection.
1580 pub is_entire_line: bool,
1581 /// The indentation of the first line when this content was originally copied.
1582 pub first_line_indent: u32,
1583}
1584
1585// selections, scroll behavior, was newest selection reversed
1586type SelectSyntaxNodeHistoryState = (
1587 Box<[Selection<MultiBufferOffset>]>,
1588 SelectSyntaxNodeScrollBehavior,
1589 bool,
1590);
1591
1592#[derive(Default)]
1593struct SelectSyntaxNodeHistory {
1594 stack: Vec<SelectSyntaxNodeHistoryState>,
1595 // disable temporarily to allow changing selections without losing the stack
1596 pub disable_clearing: bool,
1597}
1598
1599impl SelectSyntaxNodeHistory {
1600 pub fn try_clear(&mut self) {
1601 if !self.disable_clearing {
1602 self.stack.clear();
1603 }
1604 }
1605
1606 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1607 self.stack.push(selection);
1608 }
1609
1610 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1611 self.stack.pop()
1612 }
1613}
1614
1615enum SelectSyntaxNodeScrollBehavior {
1616 CursorTop,
1617 FitSelection,
1618 CursorBottom,
1619}
1620
1621#[derive(Debug)]
1622pub(crate) struct NavigationData {
1623 cursor_anchor: Anchor,
1624 cursor_position: Point,
1625 scroll_anchor: ScrollAnchor,
1626 scroll_top_row: u32,
1627}
1628
1629#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1630pub enum GotoDefinitionKind {
1631 Symbol,
1632 Declaration,
1633 Type,
1634 Implementation,
1635}
1636
1637pub enum FormatTarget {
1638 Buffers(HashSet<Entity<Buffer>>),
1639 Ranges(Vec<Range<MultiBufferPoint>>),
1640}
1641
1642pub(crate) struct FocusedBlock {
1643 id: BlockId,
1644 focus_handle: WeakFocusHandle,
1645}
1646
1647#[derive(Clone, Debug)]
1648enum JumpData {
1649 MultiBufferRow {
1650 row: MultiBufferRow,
1651 line_offset_from_top: u32,
1652 },
1653 MultiBufferPoint {
1654 excerpt_id: ExcerptId,
1655 position: Point,
1656 anchor: text::Anchor,
1657 line_offset_from_top: u32,
1658 },
1659}
1660
1661pub enum MultibufferSelectionMode {
1662 First,
1663 All,
1664}
1665
1666#[derive(Clone, Copy, Debug, Default)]
1667pub struct RewrapOptions {
1668 pub override_language_settings: bool,
1669 pub preserve_existing_whitespace: bool,
1670}
1671
1672impl Editor {
1673 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1674 let buffer = cx.new(|cx| Buffer::local("", cx));
1675 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1676 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1677 }
1678
1679 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1680 let buffer = cx.new(|cx| Buffer::local("", cx));
1681 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1682 Self::new(EditorMode::full(), buffer, None, window, cx)
1683 }
1684
1685 pub fn auto_height(
1686 min_lines: usize,
1687 max_lines: usize,
1688 window: &mut Window,
1689 cx: &mut Context<Self>,
1690 ) -> Self {
1691 let buffer = cx.new(|cx| Buffer::local("", cx));
1692 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1693 Self::new(
1694 EditorMode::AutoHeight {
1695 min_lines,
1696 max_lines: Some(max_lines),
1697 },
1698 buffer,
1699 None,
1700 window,
1701 cx,
1702 )
1703 }
1704
1705 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1706 /// The editor grows as tall as needed to fit its content.
1707 pub fn auto_height_unbounded(
1708 min_lines: usize,
1709 window: &mut Window,
1710 cx: &mut Context<Self>,
1711 ) -> Self {
1712 let buffer = cx.new(|cx| Buffer::local("", cx));
1713 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1714 Self::new(
1715 EditorMode::AutoHeight {
1716 min_lines,
1717 max_lines: None,
1718 },
1719 buffer,
1720 None,
1721 window,
1722 cx,
1723 )
1724 }
1725
1726 pub fn for_buffer(
1727 buffer: Entity<Buffer>,
1728 project: Option<Entity<Project>>,
1729 window: &mut Window,
1730 cx: &mut Context<Self>,
1731 ) -> Self {
1732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1733 Self::new(EditorMode::full(), buffer, project, window, cx)
1734 }
1735
1736 pub fn for_multibuffer(
1737 buffer: Entity<MultiBuffer>,
1738 project: Option<Entity<Project>>,
1739 window: &mut Window,
1740 cx: &mut Context<Self>,
1741 ) -> Self {
1742 Self::new(EditorMode::full(), buffer, project, window, cx)
1743 }
1744
1745 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1746 let mut clone = Self::new(
1747 self.mode.clone(),
1748 self.buffer.clone(),
1749 self.project.clone(),
1750 window,
1751 cx,
1752 );
1753 self.display_map.update(cx, |display_map, cx| {
1754 let snapshot = display_map.snapshot(cx);
1755 clone.display_map.update(cx, |display_map, cx| {
1756 display_map.set_state(&snapshot, cx);
1757 });
1758 });
1759 clone.folds_did_change(cx);
1760 clone.selections.clone_state(&self.selections);
1761 clone.scroll_manager.clone_state(&self.scroll_manager);
1762 clone.searchable = self.searchable;
1763 clone.read_only = self.read_only;
1764 clone
1765 }
1766
1767 pub fn new(
1768 mode: EditorMode,
1769 buffer: Entity<MultiBuffer>,
1770 project: Option<Entity<Project>>,
1771 window: &mut Window,
1772 cx: &mut Context<Self>,
1773 ) -> Self {
1774 Editor::new_internal(mode, buffer, project, None, window, cx)
1775 }
1776
1777 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1778 let multi_buffer = self.buffer().read(cx);
1779 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1780 let multi_buffer_visible_start = self
1781 .scroll_manager
1782 .anchor()
1783 .anchor
1784 .to_point(&multi_buffer_snapshot);
1785 let max_row = multi_buffer_snapshot.max_point().row;
1786
1787 let start_row = (multi_buffer_visible_start.row).min(max_row);
1788 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1789
1790 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1791 let outline_items = buffer
1792 .outline_items_containing(
1793 Point::new(start_row, 0)..Point::new(end_row, 0),
1794 true,
1795 self.style().map(|style| style.syntax.as_ref()),
1796 )
1797 .into_iter()
1798 .map(|outline_item| OutlineItem {
1799 depth: outline_item.depth,
1800 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1801 source_range_for_text: Anchor::range_in_buffer(
1802 *excerpt_id,
1803 outline_item.source_range_for_text,
1804 ),
1805 text: outline_item.text,
1806 highlight_ranges: outline_item.highlight_ranges,
1807 name_ranges: outline_item.name_ranges,
1808 body_range: outline_item
1809 .body_range
1810 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1811 annotation_range: outline_item
1812 .annotation_range
1813 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1814 });
1815 return Some(outline_items.collect());
1816 }
1817
1818 None
1819 }
1820
1821 fn new_internal(
1822 mode: EditorMode,
1823 multi_buffer: Entity<MultiBuffer>,
1824 project: Option<Entity<Project>>,
1825 display_map: Option<Entity<DisplayMap>>,
1826 window: &mut Window,
1827 cx: &mut Context<Self>,
1828 ) -> Self {
1829 debug_assert!(
1830 display_map.is_none() || mode.is_minimap(),
1831 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1832 );
1833
1834 let full_mode = mode.is_full();
1835 let is_minimap = mode.is_minimap();
1836 let diagnostics_max_severity = if full_mode {
1837 EditorSettings::get_global(cx)
1838 .diagnostics_max_severity
1839 .unwrap_or(DiagnosticSeverity::Hint)
1840 } else {
1841 DiagnosticSeverity::Off
1842 };
1843 let style = window.text_style();
1844 let font_size = style.font_size.to_pixels(window.rem_size());
1845 let editor = cx.entity().downgrade();
1846 let fold_placeholder = FoldPlaceholder {
1847 constrain_width: false,
1848 render: Arc::new(move |fold_id, fold_range, cx| {
1849 let editor = editor.clone();
1850 div()
1851 .id(fold_id)
1852 .bg(cx.theme().colors().ghost_element_background)
1853 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1854 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1855 .rounded_xs()
1856 .size_full()
1857 .cursor_pointer()
1858 .child("⋯")
1859 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1860 .on_click(move |_, _window, cx| {
1861 editor
1862 .update(cx, |editor, cx| {
1863 editor.unfold_ranges(
1864 &[fold_range.start..fold_range.end],
1865 true,
1866 false,
1867 cx,
1868 );
1869 cx.stop_propagation();
1870 })
1871 .ok();
1872 })
1873 .into_any()
1874 }),
1875 merge_adjacent: true,
1876 ..FoldPlaceholder::default()
1877 };
1878 let display_map = display_map.unwrap_or_else(|| {
1879 cx.new(|cx| {
1880 DisplayMap::new(
1881 multi_buffer.clone(),
1882 style.font(),
1883 font_size,
1884 None,
1885 FILE_HEADER_HEIGHT,
1886 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1887 fold_placeholder,
1888 diagnostics_max_severity,
1889 cx,
1890 )
1891 })
1892 });
1893
1894 let selections = SelectionsCollection::new();
1895
1896 let blink_manager = cx.new(|cx| {
1897 let mut blink_manager = BlinkManager::new(
1898 CURSOR_BLINK_INTERVAL,
1899 |cx| EditorSettings::get_global(cx).cursor_blink,
1900 cx,
1901 );
1902 if is_minimap {
1903 blink_manager.disable(cx);
1904 }
1905 blink_manager
1906 });
1907
1908 let soft_wrap_mode_override =
1909 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1910
1911 let mut project_subscriptions = Vec::new();
1912 if full_mode && let Some(project) = project.as_ref() {
1913 project_subscriptions.push(cx.subscribe_in(
1914 project,
1915 window,
1916 |editor, _, event, window, cx| match event {
1917 project::Event::RefreshCodeLens => {
1918 // we always query lens with actions, without storing them, always refreshing them
1919 }
1920 project::Event::RefreshInlayHints {
1921 server_id,
1922 request_id,
1923 } => {
1924 editor.refresh_inlay_hints(
1925 InlayHintRefreshReason::RefreshRequested {
1926 server_id: *server_id,
1927 request_id: *request_id,
1928 },
1929 cx,
1930 );
1931 }
1932 project::Event::LanguageServerRemoved(..) => {
1933 if editor.tasks_update_task.is_none() {
1934 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1935 }
1936 editor.registered_buffers.clear();
1937 editor.register_visible_buffers(cx);
1938 }
1939 project::Event::LanguageServerAdded(..) => {
1940 if editor.tasks_update_task.is_none() {
1941 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1942 }
1943 }
1944 project::Event::SnippetEdit(id, snippet_edits) => {
1945 // todo(lw): Non singletons
1946 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
1947 let snapshot = buffer.read(cx).snapshot();
1948 let focus_handle = editor.focus_handle(cx);
1949 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
1950 for (range, snippet) in snippet_edits {
1951 let buffer_range =
1952 language::range_from_lsp(*range).to_offset(&snapshot);
1953 editor
1954 .insert_snippet(
1955 &[MultiBufferOffset(buffer_range.start)
1956 ..MultiBufferOffset(buffer_range.end)],
1957 snippet.clone(),
1958 window,
1959 cx,
1960 )
1961 .ok();
1962 }
1963 }
1964 }
1965 }
1966 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1967 let buffer_id = *buffer_id;
1968 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1969 editor.register_buffer(buffer_id, cx);
1970 editor.update_lsp_data(Some(buffer_id), window, cx);
1971 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1972 refresh_linked_ranges(editor, window, cx);
1973 editor.refresh_code_actions(window, cx);
1974 editor.refresh_document_highlights(cx);
1975 }
1976 }
1977
1978 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1979 let Some(workspace) = editor.workspace() else {
1980 return;
1981 };
1982 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1983 else {
1984 return;
1985 };
1986
1987 if active_editor.entity_id() == cx.entity_id() {
1988 let entity_id = cx.entity_id();
1989 workspace.update(cx, |this, cx| {
1990 this.panes_mut()
1991 .iter_mut()
1992 .filter(|pane| pane.entity_id() != entity_id)
1993 .for_each(|p| {
1994 p.update(cx, |pane, _| {
1995 pane.nav_history_mut().rename_item(
1996 entity_id,
1997 project_path.clone(),
1998 abs_path.clone().into(),
1999 );
2000 })
2001 });
2002 });
2003 let edited_buffers_already_open = {
2004 let other_editors: Vec<Entity<Editor>> = workspace
2005 .read(cx)
2006 .panes()
2007 .iter()
2008 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2009 .filter(|editor| editor.entity_id() != cx.entity_id())
2010 .collect();
2011
2012 transaction.0.keys().all(|buffer| {
2013 other_editors.iter().any(|editor| {
2014 let multi_buffer = editor.read(cx).buffer();
2015 multi_buffer.read(cx).is_singleton()
2016 && multi_buffer.read(cx).as_singleton().map_or(
2017 false,
2018 |singleton| {
2019 singleton.entity_id() == buffer.entity_id()
2020 },
2021 )
2022 })
2023 })
2024 };
2025 if !edited_buffers_already_open {
2026 let workspace = workspace.downgrade();
2027 let transaction = transaction.clone();
2028 cx.defer_in(window, move |_, window, cx| {
2029 cx.spawn_in(window, async move |editor, cx| {
2030 Self::open_project_transaction(
2031 &editor,
2032 workspace,
2033 transaction,
2034 "Rename".to_string(),
2035 cx,
2036 )
2037 .await
2038 .ok()
2039 })
2040 .detach();
2041 });
2042 }
2043 }
2044 }
2045
2046 _ => {}
2047 },
2048 ));
2049 if let Some(task_inventory) = project
2050 .read(cx)
2051 .task_store()
2052 .read(cx)
2053 .task_inventory()
2054 .cloned()
2055 {
2056 project_subscriptions.push(cx.observe_in(
2057 &task_inventory,
2058 window,
2059 |editor, _, window, cx| {
2060 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2061 },
2062 ));
2063 };
2064
2065 project_subscriptions.push(cx.subscribe_in(
2066 &project.read(cx).breakpoint_store(),
2067 window,
2068 |editor, _, event, window, cx| match event {
2069 BreakpointStoreEvent::ClearDebugLines => {
2070 editor.clear_row_highlights::<ActiveDebugLine>();
2071 editor.refresh_inline_values(cx);
2072 }
2073 BreakpointStoreEvent::SetDebugLine => {
2074 if editor.go_to_active_debug_line(window, cx) {
2075 cx.stop_propagation();
2076 }
2077
2078 editor.refresh_inline_values(cx);
2079 }
2080 _ => {}
2081 },
2082 ));
2083 let git_store = project.read(cx).git_store().clone();
2084 let project = project.clone();
2085 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2086 if let GitStoreEvent::RepositoryAdded = event {
2087 this.load_diff_task = Some(
2088 update_uncommitted_diff_for_buffer(
2089 cx.entity(),
2090 &project,
2091 this.buffer.read(cx).all_buffers(),
2092 this.buffer.clone(),
2093 cx,
2094 )
2095 .shared(),
2096 );
2097 }
2098 }));
2099 }
2100
2101 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2102
2103 let inlay_hint_settings =
2104 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2105 let focus_handle = cx.focus_handle();
2106 if !is_minimap {
2107 cx.on_focus(&focus_handle, window, Self::handle_focus)
2108 .detach();
2109 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2110 .detach();
2111 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2112 .detach();
2113 cx.on_blur(&focus_handle, window, Self::handle_blur)
2114 .detach();
2115 cx.observe_pending_input(window, Self::observe_pending_input)
2116 .detach();
2117 }
2118
2119 let show_indent_guides =
2120 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2121 Some(false)
2122 } else {
2123 None
2124 };
2125
2126 let breakpoint_store = match (&mode, project.as_ref()) {
2127 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2128 _ => None,
2129 };
2130
2131 let mut code_action_providers = Vec::new();
2132 let mut load_uncommitted_diff = None;
2133 if let Some(project) = project.clone() {
2134 load_uncommitted_diff = Some(
2135 update_uncommitted_diff_for_buffer(
2136 cx.entity(),
2137 &project,
2138 multi_buffer.read(cx).all_buffers(),
2139 multi_buffer.clone(),
2140 cx,
2141 )
2142 .shared(),
2143 );
2144 code_action_providers.push(Rc::new(project) as Rc<_>);
2145 }
2146
2147 let mut editor = Self {
2148 focus_handle,
2149 show_cursor_when_unfocused: false,
2150 last_focused_descendant: None,
2151 buffer: multi_buffer.clone(),
2152 display_map: display_map.clone(),
2153 placeholder_display_map: None,
2154 selections,
2155 scroll_manager: ScrollManager::new(cx),
2156 columnar_selection_state: None,
2157 add_selections_state: None,
2158 select_next_state: None,
2159 select_prev_state: None,
2160 selection_history: SelectionHistory::default(),
2161 defer_selection_effects: false,
2162 deferred_selection_effects_state: None,
2163 autoclose_regions: Vec::new(),
2164 snippet_stack: InvalidationStack::default(),
2165 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2166 ime_transaction: None,
2167 active_diagnostics: ActiveDiagnostic::None,
2168 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2169 inline_diagnostics_update: Task::ready(()),
2170 inline_diagnostics: Vec::new(),
2171 soft_wrap_mode_override,
2172 diagnostics_max_severity,
2173 hard_wrap: None,
2174 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2175 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2176 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2177 project,
2178 blink_manager: blink_manager.clone(),
2179 show_local_selections: true,
2180 show_scrollbars: ScrollbarAxes {
2181 horizontal: full_mode,
2182 vertical: full_mode,
2183 },
2184 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2185 offset_content: !matches!(mode, EditorMode::SingleLine),
2186 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2187 show_gutter: full_mode,
2188 show_line_numbers: (!full_mode).then_some(false),
2189 use_relative_line_numbers: None,
2190 disable_expand_excerpt_buttons: !full_mode,
2191 show_git_diff_gutter: None,
2192 show_code_actions: None,
2193 show_runnables: None,
2194 show_breakpoints: None,
2195 show_wrap_guides: None,
2196 show_indent_guides,
2197 highlight_order: 0,
2198 highlighted_rows: HashMap::default(),
2199 background_highlights: HashMap::default(),
2200 gutter_highlights: HashMap::default(),
2201 scrollbar_marker_state: ScrollbarMarkerState::default(),
2202 active_indent_guides_state: ActiveIndentGuidesState::default(),
2203 nav_history: None,
2204 context_menu: RefCell::new(None),
2205 context_menu_options: None,
2206 mouse_context_menu: None,
2207 completion_tasks: Vec::new(),
2208 inline_blame_popover: None,
2209 inline_blame_popover_show_task: None,
2210 signature_help_state: SignatureHelpState::default(),
2211 auto_signature_help: None,
2212 find_all_references_task_sources: Vec::new(),
2213 next_completion_id: 0,
2214 next_inlay_id: 0,
2215 code_action_providers,
2216 available_code_actions: None,
2217 code_actions_task: None,
2218 quick_selection_highlight_task: None,
2219 debounced_selection_highlight_task: None,
2220 document_highlights_task: None,
2221 linked_editing_range_task: None,
2222 pending_rename: None,
2223 searchable: !is_minimap,
2224 cursor_shape: EditorSettings::get_global(cx)
2225 .cursor_shape
2226 .unwrap_or_default(),
2227 current_line_highlight: None,
2228 autoindent_mode: Some(AutoindentMode::EachLine),
2229 collapse_matches: false,
2230 workspace: None,
2231 input_enabled: !is_minimap,
2232 use_modal_editing: full_mode,
2233 read_only: is_minimap,
2234 use_autoclose: true,
2235 use_auto_surround: true,
2236 auto_replace_emoji_shortcode: false,
2237 jsx_tag_auto_close_enabled_in_any_buffer: false,
2238 leader_id: None,
2239 remote_id: None,
2240 hover_state: HoverState::default(),
2241 pending_mouse_down: None,
2242 hovered_link_state: None,
2243 edit_prediction_provider: None,
2244 active_edit_prediction: None,
2245 stale_edit_prediction_in_menu: None,
2246 edit_prediction_preview: EditPredictionPreview::Inactive {
2247 released_too_fast: false,
2248 },
2249 inline_diagnostics_enabled: full_mode,
2250 diagnostics_enabled: full_mode,
2251 word_completions_enabled: full_mode,
2252 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2253 gutter_hovered: false,
2254 pixel_position_of_newest_cursor: None,
2255 last_bounds: None,
2256 last_position_map: None,
2257 expect_bounds_change: None,
2258 gutter_dimensions: GutterDimensions::default(),
2259 style: None,
2260 show_cursor_names: false,
2261 hovered_cursors: HashMap::default(),
2262 next_editor_action_id: EditorActionId::default(),
2263 editor_actions: Rc::default(),
2264 edit_predictions_hidden_for_vim_mode: false,
2265 show_edit_predictions_override: None,
2266 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2267 edit_prediction_settings: EditPredictionSettings::Disabled,
2268 edit_prediction_indent_conflict: false,
2269 edit_prediction_requires_modifier_in_indent_conflict: true,
2270 custom_context_menu: None,
2271 show_git_blame_gutter: false,
2272 show_git_blame_inline: false,
2273 show_selection_menu: None,
2274 show_git_blame_inline_delay_task: None,
2275 git_blame_inline_enabled: full_mode
2276 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2277 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2278 buffer_serialization: is_minimap.not().then(|| {
2279 BufferSerialization::new(
2280 ProjectSettings::get_global(cx)
2281 .session
2282 .restore_unsaved_buffers,
2283 )
2284 }),
2285 blame: None,
2286 blame_subscription: None,
2287 tasks: BTreeMap::default(),
2288
2289 breakpoint_store,
2290 gutter_breakpoint_indicator: (None, None),
2291 hovered_diff_hunk_row: None,
2292 _subscriptions: (!is_minimap)
2293 .then(|| {
2294 vec![
2295 cx.observe(&multi_buffer, Self::on_buffer_changed),
2296 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2297 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2298 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2299 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2300 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2301 cx.observe_window_activation(window, |editor, window, cx| {
2302 let active = window.is_window_active();
2303 editor.blink_manager.update(cx, |blink_manager, cx| {
2304 if active {
2305 blink_manager.enable(cx);
2306 } else {
2307 blink_manager.disable(cx);
2308 }
2309 });
2310 if active {
2311 editor.show_mouse_cursor(cx);
2312 }
2313 }),
2314 ]
2315 })
2316 .unwrap_or_default(),
2317 tasks_update_task: None,
2318 pull_diagnostics_task: Task::ready(()),
2319 colors: None,
2320 refresh_colors_task: Task::ready(()),
2321 inlay_hints: None,
2322 next_color_inlay_id: 0,
2323 post_scroll_update: Task::ready(()),
2324 linked_edit_ranges: Default::default(),
2325 in_project_search: false,
2326 previous_search_ranges: None,
2327 breadcrumb_header: None,
2328 focused_block: None,
2329 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2330 addons: HashMap::default(),
2331 registered_buffers: HashMap::default(),
2332 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2333 selection_mark_mode: false,
2334 toggle_fold_multiple_buffers: Task::ready(()),
2335 serialize_selections: Task::ready(()),
2336 serialize_folds: Task::ready(()),
2337 text_style_refinement: None,
2338 load_diff_task: load_uncommitted_diff,
2339 temporary_diff_override: false,
2340 mouse_cursor_hidden: false,
2341 minimap: None,
2342 hide_mouse_mode: EditorSettings::get_global(cx)
2343 .hide_mouse
2344 .unwrap_or_default(),
2345 change_list: ChangeList::new(),
2346 mode,
2347 selection_drag_state: SelectionDragState::None,
2348 folding_newlines: Task::ready(()),
2349 lookup_key: None,
2350 select_next_is_case_sensitive: None,
2351 applicable_language_settings: HashMap::default(),
2352 accent_overrides: Vec::new(),
2353 fetched_tree_sitter_chunks: HashMap::default(),
2354 use_base_text_line_numbers: false,
2355 };
2356
2357 if is_minimap {
2358 return editor;
2359 }
2360
2361 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2362 editor.accent_overrides = editor.fetch_accent_overrides(cx);
2363
2364 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2365 editor
2366 ._subscriptions
2367 .push(cx.observe(breakpoints, |_, _, cx| {
2368 cx.notify();
2369 }));
2370 }
2371 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2372 editor._subscriptions.extend(project_subscriptions);
2373
2374 editor._subscriptions.push(cx.subscribe_in(
2375 &cx.entity(),
2376 window,
2377 |editor, _, e: &EditorEvent, window, cx| match e {
2378 EditorEvent::ScrollPositionChanged { local, .. } => {
2379 if *local {
2380 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2381 editor.inline_blame_popover.take();
2382 let new_anchor = editor.scroll_manager.anchor();
2383 let snapshot = editor.snapshot(window, cx);
2384 editor.update_restoration_data(cx, move |data| {
2385 data.scroll_position = (
2386 new_anchor.top_row(snapshot.buffer_snapshot()),
2387 new_anchor.offset,
2388 );
2389 });
2390
2391 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2392 cx.background_executor()
2393 .timer(Duration::from_millis(50))
2394 .await;
2395 editor
2396 .update_in(cx, |editor, window, cx| {
2397 editor.register_visible_buffers(cx);
2398 editor.refresh_colors_for_visible_range(None, window, cx);
2399 editor.refresh_inlay_hints(
2400 InlayHintRefreshReason::NewLinesShown,
2401 cx,
2402 );
2403 editor.colorize_brackets(false, cx);
2404 })
2405 .ok();
2406 });
2407 }
2408 }
2409 EditorEvent::Edited { .. } => {
2410 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2411 .map(|vim_mode| vim_mode.0)
2412 .unwrap_or(false);
2413 if !vim_mode {
2414 let display_map = editor.display_snapshot(cx);
2415 let selections = editor.selections.all_adjusted_display(&display_map);
2416 let pop_state = editor
2417 .change_list
2418 .last()
2419 .map(|previous| {
2420 previous.len() == selections.len()
2421 && previous.iter().enumerate().all(|(ix, p)| {
2422 p.to_display_point(&display_map).row()
2423 == selections[ix].head().row()
2424 })
2425 })
2426 .unwrap_or(false);
2427 let new_positions = selections
2428 .into_iter()
2429 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2430 .collect();
2431 editor
2432 .change_list
2433 .push_to_change_list(pop_state, new_positions);
2434 }
2435 }
2436 _ => (),
2437 },
2438 ));
2439
2440 if let Some(dap_store) = editor
2441 .project
2442 .as_ref()
2443 .map(|project| project.read(cx).dap_store())
2444 {
2445 let weak_editor = cx.weak_entity();
2446
2447 editor
2448 ._subscriptions
2449 .push(
2450 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2451 let session_entity = cx.entity();
2452 weak_editor
2453 .update(cx, |editor, cx| {
2454 editor._subscriptions.push(
2455 cx.subscribe(&session_entity, Self::on_debug_session_event),
2456 );
2457 })
2458 .ok();
2459 }),
2460 );
2461
2462 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2463 editor
2464 ._subscriptions
2465 .push(cx.subscribe(&session, Self::on_debug_session_event));
2466 }
2467 }
2468
2469 // skip adding the initial selection to selection history
2470 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2471 editor.end_selection(window, cx);
2472 editor.selection_history.mode = SelectionHistoryMode::Normal;
2473
2474 editor.scroll_manager.show_scrollbars(window, cx);
2475 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2476
2477 if full_mode {
2478 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2479 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2480
2481 if editor.git_blame_inline_enabled {
2482 editor.start_git_blame_inline(false, window, cx);
2483 }
2484
2485 editor.go_to_active_debug_line(window, cx);
2486
2487 editor.minimap =
2488 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2489 editor.colors = Some(LspColorData::new(cx));
2490 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2491
2492 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2493 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2494 }
2495 editor.update_lsp_data(None, window, cx);
2496 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2497 }
2498
2499 editor
2500 }
2501
2502 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2503 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2504 }
2505
2506 pub fn deploy_mouse_context_menu(
2507 &mut self,
2508 position: gpui::Point<Pixels>,
2509 context_menu: Entity<ContextMenu>,
2510 window: &mut Window,
2511 cx: &mut Context<Self>,
2512 ) {
2513 self.mouse_context_menu = Some(MouseContextMenu::new(
2514 self,
2515 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2516 context_menu,
2517 window,
2518 cx,
2519 ));
2520 }
2521
2522 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2523 self.mouse_context_menu
2524 .as_ref()
2525 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2526 }
2527
2528 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2529 if self
2530 .selections
2531 .pending_anchor()
2532 .is_some_and(|pending_selection| {
2533 let snapshot = self.buffer().read(cx).snapshot(cx);
2534 pending_selection.range().includes(range, &snapshot)
2535 })
2536 {
2537 return true;
2538 }
2539
2540 self.selections
2541 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2542 .into_iter()
2543 .any(|selection| {
2544 // This is needed to cover a corner case, if we just check for an existing
2545 // selection in the fold range, having a cursor at the start of the fold
2546 // marks it as selected. Non-empty selections don't cause this.
2547 let length = selection.end - selection.start;
2548 length > 0
2549 })
2550 }
2551
2552 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2553 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2554 }
2555
2556 fn key_context_internal(
2557 &self,
2558 has_active_edit_prediction: bool,
2559 window: &mut Window,
2560 cx: &mut App,
2561 ) -> KeyContext {
2562 let mut key_context = KeyContext::new_with_defaults();
2563 key_context.add("Editor");
2564 let mode = match self.mode {
2565 EditorMode::SingleLine => "single_line",
2566 EditorMode::AutoHeight { .. } => "auto_height",
2567 EditorMode::Minimap { .. } => "minimap",
2568 EditorMode::Full { .. } => "full",
2569 };
2570
2571 if EditorSettings::jupyter_enabled(cx) {
2572 key_context.add("jupyter");
2573 }
2574
2575 key_context.set("mode", mode);
2576 if self.pending_rename.is_some() {
2577 key_context.add("renaming");
2578 }
2579
2580 if let Some(snippet_stack) = self.snippet_stack.last() {
2581 key_context.add("in_snippet");
2582
2583 if snippet_stack.active_index > 0 {
2584 key_context.add("has_previous_tabstop");
2585 }
2586
2587 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2588 key_context.add("has_next_tabstop");
2589 }
2590 }
2591
2592 match self.context_menu.borrow().as_ref() {
2593 Some(CodeContextMenu::Completions(menu)) => {
2594 if menu.visible() {
2595 key_context.add("menu");
2596 key_context.add("showing_completions");
2597 }
2598 }
2599 Some(CodeContextMenu::CodeActions(menu)) => {
2600 if menu.visible() {
2601 key_context.add("menu");
2602 key_context.add("showing_code_actions")
2603 }
2604 }
2605 None => {}
2606 }
2607
2608 if self.signature_help_state.has_multiple_signatures() {
2609 key_context.add("showing_signature_help");
2610 }
2611
2612 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2613 if !self.focus_handle(cx).contains_focused(window, cx)
2614 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2615 {
2616 for addon in self.addons.values() {
2617 addon.extend_key_context(&mut key_context, cx)
2618 }
2619 }
2620
2621 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2622 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2623 Some(
2624 file.full_path(cx)
2625 .extension()?
2626 .to_string_lossy()
2627 .into_owned(),
2628 )
2629 }) {
2630 key_context.set("extension", extension);
2631 }
2632 } else {
2633 key_context.add("multibuffer");
2634 }
2635
2636 if has_active_edit_prediction {
2637 if self.edit_prediction_in_conflict() {
2638 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2639 } else {
2640 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2641 key_context.add("copilot_suggestion");
2642 }
2643 }
2644
2645 if self.selection_mark_mode {
2646 key_context.add("selection_mode");
2647 }
2648
2649 let disjoint = self.selections.disjoint_anchors();
2650 let snapshot = self.snapshot(window, cx);
2651 let snapshot = snapshot.buffer_snapshot();
2652 if self.mode == EditorMode::SingleLine
2653 && let [selection] = disjoint
2654 && selection.start == selection.end
2655 && selection.end.to_offset(snapshot) == snapshot.len()
2656 {
2657 key_context.add("end_of_input");
2658 }
2659
2660 if self.has_any_expanded_diff_hunks(cx) {
2661 key_context.add("diffs_expanded");
2662 }
2663
2664 key_context
2665 }
2666
2667 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2668 self.last_bounds.as_ref()
2669 }
2670
2671 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2672 if self.mouse_cursor_hidden {
2673 self.mouse_cursor_hidden = false;
2674 cx.notify();
2675 }
2676 }
2677
2678 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2679 let hide_mouse_cursor = match origin {
2680 HideMouseCursorOrigin::TypingAction => {
2681 matches!(
2682 self.hide_mouse_mode,
2683 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2684 )
2685 }
2686 HideMouseCursorOrigin::MovementAction => {
2687 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2688 }
2689 };
2690 if self.mouse_cursor_hidden != hide_mouse_cursor {
2691 self.mouse_cursor_hidden = hide_mouse_cursor;
2692 cx.notify();
2693 }
2694 }
2695
2696 pub fn edit_prediction_in_conflict(&self) -> bool {
2697 if !self.show_edit_predictions_in_menu() {
2698 return false;
2699 }
2700
2701 let showing_completions = self
2702 .context_menu
2703 .borrow()
2704 .as_ref()
2705 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2706
2707 showing_completions
2708 || self.edit_prediction_requires_modifier()
2709 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2710 // bindings to insert tab characters.
2711 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2712 }
2713
2714 pub fn accept_edit_prediction_keybind(
2715 &self,
2716 accept_partial: bool,
2717 window: &mut Window,
2718 cx: &mut App,
2719 ) -> AcceptEditPredictionBinding {
2720 let key_context = self.key_context_internal(true, window, cx);
2721 let in_conflict = self.edit_prediction_in_conflict();
2722
2723 let bindings = if accept_partial {
2724 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2725 } else {
2726 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2727 };
2728
2729 // TODO: if the binding contains multiple keystrokes, display all of them, not
2730 // just the first one.
2731 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2732 !in_conflict
2733 || binding
2734 .keystrokes()
2735 .first()
2736 .is_some_and(|keystroke| keystroke.modifiers().modified())
2737 }))
2738 }
2739
2740 pub fn new_file(
2741 workspace: &mut Workspace,
2742 _: &workspace::NewFile,
2743 window: &mut Window,
2744 cx: &mut Context<Workspace>,
2745 ) {
2746 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2747 "Failed to create buffer",
2748 window,
2749 cx,
2750 |e, _, _| match e.error_code() {
2751 ErrorCode::RemoteUpgradeRequired => Some(format!(
2752 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2753 e.error_tag("required").unwrap_or("the latest version")
2754 )),
2755 _ => None,
2756 },
2757 );
2758 }
2759
2760 pub fn new_in_workspace(
2761 workspace: &mut Workspace,
2762 window: &mut Window,
2763 cx: &mut Context<Workspace>,
2764 ) -> Task<Result<Entity<Editor>>> {
2765 let project = workspace.project().clone();
2766 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2767
2768 cx.spawn_in(window, async move |workspace, cx| {
2769 let buffer = create.await?;
2770 workspace.update_in(cx, |workspace, window, cx| {
2771 let editor =
2772 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2773 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2774 editor
2775 })
2776 })
2777 }
2778
2779 fn new_file_vertical(
2780 workspace: &mut Workspace,
2781 _: &workspace::NewFileSplitVertical,
2782 window: &mut Window,
2783 cx: &mut Context<Workspace>,
2784 ) {
2785 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2786 }
2787
2788 fn new_file_horizontal(
2789 workspace: &mut Workspace,
2790 _: &workspace::NewFileSplitHorizontal,
2791 window: &mut Window,
2792 cx: &mut Context<Workspace>,
2793 ) {
2794 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2795 }
2796
2797 fn new_file_split(
2798 workspace: &mut Workspace,
2799 action: &workspace::NewFileSplit,
2800 window: &mut Window,
2801 cx: &mut Context<Workspace>,
2802 ) {
2803 Self::new_file_in_direction(workspace, action.0, window, cx)
2804 }
2805
2806 fn new_file_in_direction(
2807 workspace: &mut Workspace,
2808 direction: SplitDirection,
2809 window: &mut Window,
2810 cx: &mut Context<Workspace>,
2811 ) {
2812 let project = workspace.project().clone();
2813 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2814
2815 cx.spawn_in(window, async move |workspace, cx| {
2816 let buffer = create.await?;
2817 workspace.update_in(cx, move |workspace, window, cx| {
2818 workspace.split_item(
2819 direction,
2820 Box::new(
2821 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2822 ),
2823 window,
2824 cx,
2825 )
2826 })?;
2827 anyhow::Ok(())
2828 })
2829 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2830 match e.error_code() {
2831 ErrorCode::RemoteUpgradeRequired => Some(format!(
2832 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2833 e.error_tag("required").unwrap_or("the latest version")
2834 )),
2835 _ => None,
2836 }
2837 });
2838 }
2839
2840 pub fn leader_id(&self) -> Option<CollaboratorId> {
2841 self.leader_id
2842 }
2843
2844 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2845 &self.buffer
2846 }
2847
2848 pub fn project(&self) -> Option<&Entity<Project>> {
2849 self.project.as_ref()
2850 }
2851
2852 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2853 self.workspace.as_ref()?.0.upgrade()
2854 }
2855
2856 /// Returns the workspace serialization ID if this editor should be serialized.
2857 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2858 self.workspace
2859 .as_ref()
2860 .filter(|_| self.should_serialize_buffer())
2861 .and_then(|workspace| workspace.1)
2862 }
2863
2864 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2865 self.buffer().read(cx).title(cx)
2866 }
2867
2868 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2869 let git_blame_gutter_max_author_length = self
2870 .render_git_blame_gutter(cx)
2871 .then(|| {
2872 if let Some(blame) = self.blame.as_ref() {
2873 let max_author_length =
2874 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2875 Some(max_author_length)
2876 } else {
2877 None
2878 }
2879 })
2880 .flatten();
2881
2882 EditorSnapshot {
2883 mode: self.mode.clone(),
2884 show_gutter: self.show_gutter,
2885 show_line_numbers: self.show_line_numbers,
2886 show_git_diff_gutter: self.show_git_diff_gutter,
2887 show_code_actions: self.show_code_actions,
2888 show_runnables: self.show_runnables,
2889 show_breakpoints: self.show_breakpoints,
2890 git_blame_gutter_max_author_length,
2891 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2892 placeholder_display_snapshot: self
2893 .placeholder_display_map
2894 .as_ref()
2895 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2896 scroll_anchor: self.scroll_manager.anchor(),
2897 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2898 is_focused: self.focus_handle.is_focused(window),
2899 current_line_highlight: self
2900 .current_line_highlight
2901 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2902 gutter_hovered: self.gutter_hovered,
2903 }
2904 }
2905
2906 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2907 self.buffer.read(cx).language_at(point, cx)
2908 }
2909
2910 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2911 self.buffer.read(cx).read(cx).file_at(point).cloned()
2912 }
2913
2914 pub fn active_excerpt(
2915 &self,
2916 cx: &App,
2917 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2918 self.buffer
2919 .read(cx)
2920 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2921 }
2922
2923 pub fn mode(&self) -> &EditorMode {
2924 &self.mode
2925 }
2926
2927 pub fn set_mode(&mut self, mode: EditorMode) {
2928 self.mode = mode;
2929 }
2930
2931 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2932 self.collaboration_hub.as_deref()
2933 }
2934
2935 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2936 self.collaboration_hub = Some(hub);
2937 }
2938
2939 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2940 self.in_project_search = in_project_search;
2941 }
2942
2943 pub fn set_custom_context_menu(
2944 &mut self,
2945 f: impl 'static
2946 + Fn(
2947 &mut Self,
2948 DisplayPoint,
2949 &mut Window,
2950 &mut Context<Self>,
2951 ) -> Option<Entity<ui::ContextMenu>>,
2952 ) {
2953 self.custom_context_menu = Some(Box::new(f))
2954 }
2955
2956 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2957 self.completion_provider = provider;
2958 }
2959
2960 #[cfg(any(test, feature = "test-support"))]
2961 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2962 self.completion_provider.clone()
2963 }
2964
2965 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2966 self.semantics_provider.clone()
2967 }
2968
2969 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2970 self.semantics_provider = provider;
2971 }
2972
2973 pub fn set_edit_prediction_provider<T>(
2974 &mut self,
2975 provider: Option<Entity<T>>,
2976 window: &mut Window,
2977 cx: &mut Context<Self>,
2978 ) where
2979 T: EditPredictionProvider,
2980 {
2981 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2982 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2983 if this.focus_handle.is_focused(window) {
2984 this.update_visible_edit_prediction(window, cx);
2985 }
2986 }),
2987 provider: Arc::new(provider),
2988 });
2989 self.update_edit_prediction_settings(cx);
2990 self.refresh_edit_prediction(false, false, window, cx);
2991 }
2992
2993 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2994 self.placeholder_display_map
2995 .as_ref()
2996 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2997 }
2998
2999 pub fn set_placeholder_text(
3000 &mut self,
3001 placeholder_text: &str,
3002 window: &mut Window,
3003 cx: &mut Context<Self>,
3004 ) {
3005 let multibuffer = cx
3006 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3007
3008 let style = window.text_style();
3009
3010 self.placeholder_display_map = Some(cx.new(|cx| {
3011 DisplayMap::new(
3012 multibuffer,
3013 style.font(),
3014 style.font_size.to_pixels(window.rem_size()),
3015 None,
3016 FILE_HEADER_HEIGHT,
3017 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3018 Default::default(),
3019 DiagnosticSeverity::Off,
3020 cx,
3021 )
3022 }));
3023 cx.notify();
3024 }
3025
3026 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3027 self.cursor_shape = cursor_shape;
3028
3029 // Disrupt blink for immediate user feedback that the cursor shape has changed
3030 self.blink_manager.update(cx, BlinkManager::show_cursor);
3031
3032 cx.notify();
3033 }
3034
3035 pub fn cursor_shape(&self) -> CursorShape {
3036 self.cursor_shape
3037 }
3038
3039 pub fn set_current_line_highlight(
3040 &mut self,
3041 current_line_highlight: Option<CurrentLineHighlight>,
3042 ) {
3043 self.current_line_highlight = current_line_highlight;
3044 }
3045
3046 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3047 self.collapse_matches = collapse_matches;
3048 }
3049
3050 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3051 if self.collapse_matches {
3052 return range.start..range.start;
3053 }
3054 range.clone()
3055 }
3056
3057 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3058 self.display_map.read(cx).clip_at_line_ends
3059 }
3060
3061 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3062 if self.display_map.read(cx).clip_at_line_ends != clip {
3063 self.display_map
3064 .update(cx, |map, _| map.clip_at_line_ends = clip);
3065 }
3066 }
3067
3068 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3069 self.input_enabled = input_enabled;
3070 }
3071
3072 pub fn set_edit_predictions_hidden_for_vim_mode(
3073 &mut self,
3074 hidden: bool,
3075 window: &mut Window,
3076 cx: &mut Context<Self>,
3077 ) {
3078 if hidden != self.edit_predictions_hidden_for_vim_mode {
3079 self.edit_predictions_hidden_for_vim_mode = hidden;
3080 if hidden {
3081 self.update_visible_edit_prediction(window, cx);
3082 } else {
3083 self.refresh_edit_prediction(true, false, window, cx);
3084 }
3085 }
3086 }
3087
3088 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3089 self.menu_edit_predictions_policy = value;
3090 }
3091
3092 pub fn set_autoindent(&mut self, autoindent: bool) {
3093 if autoindent {
3094 self.autoindent_mode = Some(AutoindentMode::EachLine);
3095 } else {
3096 self.autoindent_mode = None;
3097 }
3098 }
3099
3100 pub fn read_only(&self, cx: &App) -> bool {
3101 self.read_only || self.buffer.read(cx).read_only()
3102 }
3103
3104 pub fn set_read_only(&mut self, read_only: bool) {
3105 self.read_only = read_only;
3106 }
3107
3108 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3109 self.use_autoclose = autoclose;
3110 }
3111
3112 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3113 self.use_auto_surround = auto_surround;
3114 }
3115
3116 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3117 self.auto_replace_emoji_shortcode = auto_replace;
3118 }
3119
3120 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3121 self.buffer_serialization = should_serialize.then(|| {
3122 BufferSerialization::new(
3123 ProjectSettings::get_global(cx)
3124 .session
3125 .restore_unsaved_buffers,
3126 )
3127 })
3128 }
3129
3130 fn should_serialize_buffer(&self) -> bool {
3131 self.buffer_serialization.is_some()
3132 }
3133
3134 pub fn toggle_edit_predictions(
3135 &mut self,
3136 _: &ToggleEditPrediction,
3137 window: &mut Window,
3138 cx: &mut Context<Self>,
3139 ) {
3140 if self.show_edit_predictions_override.is_some() {
3141 self.set_show_edit_predictions(None, window, cx);
3142 } else {
3143 let show_edit_predictions = !self.edit_predictions_enabled();
3144 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3145 }
3146 }
3147
3148 pub fn set_show_edit_predictions(
3149 &mut self,
3150 show_edit_predictions: Option<bool>,
3151 window: &mut Window,
3152 cx: &mut Context<Self>,
3153 ) {
3154 self.show_edit_predictions_override = show_edit_predictions;
3155 self.update_edit_prediction_settings(cx);
3156
3157 if let Some(false) = show_edit_predictions {
3158 self.discard_edit_prediction(false, cx);
3159 } else {
3160 self.refresh_edit_prediction(false, true, window, cx);
3161 }
3162 }
3163
3164 fn edit_predictions_disabled_in_scope(
3165 &self,
3166 buffer: &Entity<Buffer>,
3167 buffer_position: language::Anchor,
3168 cx: &App,
3169 ) -> bool {
3170 let snapshot = buffer.read(cx).snapshot();
3171 let settings = snapshot.settings_at(buffer_position, cx);
3172
3173 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3174 return false;
3175 };
3176
3177 scope.override_name().is_some_and(|scope_name| {
3178 settings
3179 .edit_predictions_disabled_in
3180 .iter()
3181 .any(|s| s == scope_name)
3182 })
3183 }
3184
3185 pub fn set_use_modal_editing(&mut self, to: bool) {
3186 self.use_modal_editing = to;
3187 }
3188
3189 pub fn use_modal_editing(&self) -> bool {
3190 self.use_modal_editing
3191 }
3192
3193 fn selections_did_change(
3194 &mut self,
3195 local: bool,
3196 old_cursor_position: &Anchor,
3197 effects: SelectionEffects,
3198 window: &mut Window,
3199 cx: &mut Context<Self>,
3200 ) {
3201 window.invalidate_character_coordinates();
3202
3203 // Copy selections to primary selection buffer
3204 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3205 if local {
3206 let selections = self
3207 .selections
3208 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3209 let buffer_handle = self.buffer.read(cx).read(cx);
3210
3211 let mut text = String::new();
3212 for (index, selection) in selections.iter().enumerate() {
3213 let text_for_selection = buffer_handle
3214 .text_for_range(selection.start..selection.end)
3215 .collect::<String>();
3216
3217 text.push_str(&text_for_selection);
3218 if index != selections.len() - 1 {
3219 text.push('\n');
3220 }
3221 }
3222
3223 if !text.is_empty() {
3224 cx.write_to_primary(ClipboardItem::new_string(text));
3225 }
3226 }
3227
3228 let selection_anchors = self.selections.disjoint_anchors_arc();
3229
3230 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3231 self.buffer.update(cx, |buffer, cx| {
3232 buffer.set_active_selections(
3233 &selection_anchors,
3234 self.selections.line_mode(),
3235 self.cursor_shape,
3236 cx,
3237 )
3238 });
3239 }
3240 let display_map = self
3241 .display_map
3242 .update(cx, |display_map, cx| display_map.snapshot(cx));
3243 let buffer = display_map.buffer_snapshot();
3244 if self.selections.count() == 1 {
3245 self.add_selections_state = None;
3246 }
3247 self.select_next_state = None;
3248 self.select_prev_state = None;
3249 self.select_syntax_node_history.try_clear();
3250 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3251 self.snippet_stack.invalidate(&selection_anchors, buffer);
3252 self.take_rename(false, window, cx);
3253
3254 let newest_selection = self.selections.newest_anchor();
3255 let new_cursor_position = newest_selection.head();
3256 let selection_start = newest_selection.start;
3257
3258 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3259 self.push_to_nav_history(
3260 *old_cursor_position,
3261 Some(new_cursor_position.to_point(buffer)),
3262 false,
3263 effects.nav_history == Some(true),
3264 cx,
3265 );
3266 }
3267
3268 if local {
3269 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3270 self.register_buffer(buffer_id, cx);
3271 }
3272
3273 let mut context_menu = self.context_menu.borrow_mut();
3274 let completion_menu = match context_menu.as_ref() {
3275 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3276 Some(CodeContextMenu::CodeActions(_)) => {
3277 *context_menu = None;
3278 None
3279 }
3280 None => None,
3281 };
3282 let completion_position = completion_menu.map(|menu| menu.initial_position);
3283 drop(context_menu);
3284
3285 if effects.completions
3286 && let Some(completion_position) = completion_position
3287 {
3288 let start_offset = selection_start.to_offset(buffer);
3289 let position_matches = start_offset == completion_position.to_offset(buffer);
3290 let continue_showing = if position_matches {
3291 if self.snippet_stack.is_empty() {
3292 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3293 == Some(CharKind::Word)
3294 } else {
3295 // Snippet choices can be shown even when the cursor is in whitespace.
3296 // Dismissing the menu with actions like backspace is handled by
3297 // invalidation regions.
3298 true
3299 }
3300 } else {
3301 false
3302 };
3303
3304 if continue_showing {
3305 self.open_or_update_completions_menu(None, None, false, window, cx);
3306 } else {
3307 self.hide_context_menu(window, cx);
3308 }
3309 }
3310
3311 hide_hover(self, cx);
3312
3313 if old_cursor_position.to_display_point(&display_map).row()
3314 != new_cursor_position.to_display_point(&display_map).row()
3315 {
3316 self.available_code_actions.take();
3317 }
3318 self.refresh_code_actions(window, cx);
3319 self.refresh_document_highlights(cx);
3320 refresh_linked_ranges(self, window, cx);
3321
3322 self.refresh_selected_text_highlights(false, window, cx);
3323 self.refresh_matching_bracket_highlights(window, cx);
3324 self.update_visible_edit_prediction(window, cx);
3325 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3326 self.inline_blame_popover.take();
3327 if self.git_blame_inline_enabled {
3328 self.start_inline_blame_timer(window, cx);
3329 }
3330 }
3331
3332 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3333 cx.emit(EditorEvent::SelectionsChanged { local });
3334
3335 let selections = &self.selections.disjoint_anchors_arc();
3336 if selections.len() == 1 {
3337 cx.emit(SearchEvent::ActiveMatchChanged)
3338 }
3339 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3340 let inmemory_selections = selections
3341 .iter()
3342 .map(|s| {
3343 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3344 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3345 })
3346 .collect();
3347 self.update_restoration_data(cx, |data| {
3348 data.selections = inmemory_selections;
3349 });
3350
3351 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3352 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3353 {
3354 let snapshot = self.buffer().read(cx).snapshot(cx);
3355 let selections = selections.clone();
3356 let background_executor = cx.background_executor().clone();
3357 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3358 self.serialize_selections = cx.background_spawn(async move {
3359 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3360 let db_selections = selections
3361 .iter()
3362 .map(|selection| {
3363 (
3364 selection.start.to_offset(&snapshot).0,
3365 selection.end.to_offset(&snapshot).0,
3366 )
3367 })
3368 .collect();
3369
3370 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3371 .await
3372 .with_context(|| {
3373 format!(
3374 "persisting editor selections for editor {editor_id}, \
3375 workspace {workspace_id:?}"
3376 )
3377 })
3378 .log_err();
3379 });
3380 }
3381 }
3382
3383 cx.notify();
3384 }
3385
3386 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3387 use text::ToOffset as _;
3388 use text::ToPoint as _;
3389
3390 if self.mode.is_minimap()
3391 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3392 {
3393 return;
3394 }
3395
3396 if !self.buffer().read(cx).is_singleton() {
3397 return;
3398 }
3399
3400 let display_snapshot = self
3401 .display_map
3402 .update(cx, |display_map, cx| display_map.snapshot(cx));
3403 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3404 return;
3405 };
3406 let inmemory_folds = display_snapshot
3407 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3408 .map(|fold| {
3409 fold.range.start.text_anchor.to_point(&snapshot)
3410 ..fold.range.end.text_anchor.to_point(&snapshot)
3411 })
3412 .collect();
3413 self.update_restoration_data(cx, |data| {
3414 data.folds = inmemory_folds;
3415 });
3416
3417 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3418 return;
3419 };
3420 let background_executor = cx.background_executor().clone();
3421 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3422 let db_folds = display_snapshot
3423 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3424 .map(|fold| {
3425 (
3426 fold.range.start.text_anchor.to_offset(&snapshot),
3427 fold.range.end.text_anchor.to_offset(&snapshot),
3428 )
3429 })
3430 .collect();
3431 self.serialize_folds = cx.background_spawn(async move {
3432 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3433 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3434 .await
3435 .with_context(|| {
3436 format!(
3437 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3438 )
3439 })
3440 .log_err();
3441 });
3442 }
3443
3444 pub fn sync_selections(
3445 &mut self,
3446 other: Entity<Editor>,
3447 cx: &mut Context<Self>,
3448 ) -> gpui::Subscription {
3449 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3450 if !other_selections.is_empty() {
3451 self.selections
3452 .change_with(&self.display_snapshot(cx), |selections| {
3453 selections.select_anchors(other_selections);
3454 });
3455 }
3456
3457 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3458 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3459 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3460 if other_selections.is_empty() {
3461 return;
3462 }
3463 let snapshot = this.display_snapshot(cx);
3464 this.selections.change_with(&snapshot, |selections| {
3465 selections.select_anchors(other_selections);
3466 });
3467 }
3468 });
3469
3470 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3471 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3472 let these_selections = this.selections.disjoint_anchors().to_vec();
3473 if these_selections.is_empty() {
3474 return;
3475 }
3476 other.update(cx, |other_editor, cx| {
3477 let snapshot = other_editor.display_snapshot(cx);
3478 other_editor
3479 .selections
3480 .change_with(&snapshot, |selections| {
3481 selections.select_anchors(these_selections);
3482 })
3483 });
3484 }
3485 });
3486
3487 Subscription::join(other_subscription, this_subscription)
3488 }
3489
3490 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3491 if self.buffer().read(cx).is_singleton() {
3492 return;
3493 }
3494 let snapshot = self.buffer.read(cx).snapshot(cx);
3495 let buffer_ids: HashSet<BufferId> = self
3496 .selections
3497 .disjoint_anchor_ranges()
3498 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3499 .collect();
3500 for buffer_id in buffer_ids {
3501 self.unfold_buffer(buffer_id, cx);
3502 }
3503 }
3504
3505 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3506 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3507 /// effects of selection change occur at the end of the transaction.
3508 pub fn change_selections<R>(
3509 &mut self,
3510 effects: SelectionEffects,
3511 window: &mut Window,
3512 cx: &mut Context<Self>,
3513 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3514 ) -> R {
3515 let snapshot = self.display_snapshot(cx);
3516 if let Some(state) = &mut self.deferred_selection_effects_state {
3517 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3518 state.effects.completions = effects.completions;
3519 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3520 let (changed, result) = self.selections.change_with(&snapshot, change);
3521 state.changed |= changed;
3522 return result;
3523 }
3524 let mut state = DeferredSelectionEffectsState {
3525 changed: false,
3526 effects,
3527 old_cursor_position: self.selections.newest_anchor().head(),
3528 history_entry: SelectionHistoryEntry {
3529 selections: self.selections.disjoint_anchors_arc(),
3530 select_next_state: self.select_next_state.clone(),
3531 select_prev_state: self.select_prev_state.clone(),
3532 add_selections_state: self.add_selections_state.clone(),
3533 },
3534 };
3535 let (changed, result) = self.selections.change_with(&snapshot, change);
3536 state.changed = state.changed || changed;
3537 if self.defer_selection_effects {
3538 self.deferred_selection_effects_state = Some(state);
3539 } else {
3540 self.apply_selection_effects(state, window, cx);
3541 }
3542 result
3543 }
3544
3545 /// Defers the effects of selection change, so that the effects of multiple calls to
3546 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3547 /// to selection history and the state of popovers based on selection position aren't
3548 /// erroneously updated.
3549 pub fn with_selection_effects_deferred<R>(
3550 &mut self,
3551 window: &mut Window,
3552 cx: &mut Context<Self>,
3553 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3554 ) -> R {
3555 let already_deferred = self.defer_selection_effects;
3556 self.defer_selection_effects = true;
3557 let result = update(self, window, cx);
3558 if !already_deferred {
3559 self.defer_selection_effects = false;
3560 if let Some(state) = self.deferred_selection_effects_state.take() {
3561 self.apply_selection_effects(state, window, cx);
3562 }
3563 }
3564 result
3565 }
3566
3567 fn apply_selection_effects(
3568 &mut self,
3569 state: DeferredSelectionEffectsState,
3570 window: &mut Window,
3571 cx: &mut Context<Self>,
3572 ) {
3573 if state.changed {
3574 self.selection_history.push(state.history_entry);
3575
3576 if let Some(autoscroll) = state.effects.scroll {
3577 self.request_autoscroll(autoscroll, cx);
3578 }
3579
3580 let old_cursor_position = &state.old_cursor_position;
3581
3582 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3583
3584 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3585 self.show_signature_help(&ShowSignatureHelp, window, cx);
3586 }
3587 }
3588 }
3589
3590 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3591 where
3592 I: IntoIterator<Item = (Range<S>, T)>,
3593 S: ToOffset,
3594 T: Into<Arc<str>>,
3595 {
3596 if self.read_only(cx) {
3597 return;
3598 }
3599
3600 self.buffer
3601 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3602 }
3603
3604 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3605 where
3606 I: IntoIterator<Item = (Range<S>, T)>,
3607 S: ToOffset,
3608 T: Into<Arc<str>>,
3609 {
3610 if self.read_only(cx) {
3611 return;
3612 }
3613
3614 self.buffer.update(cx, |buffer, cx| {
3615 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3616 });
3617 }
3618
3619 pub fn edit_with_block_indent<I, S, T>(
3620 &mut self,
3621 edits: I,
3622 original_indent_columns: Vec<Option<u32>>,
3623 cx: &mut Context<Self>,
3624 ) where
3625 I: IntoIterator<Item = (Range<S>, T)>,
3626 S: ToOffset,
3627 T: Into<Arc<str>>,
3628 {
3629 if self.read_only(cx) {
3630 return;
3631 }
3632
3633 self.buffer.update(cx, |buffer, cx| {
3634 buffer.edit(
3635 edits,
3636 Some(AutoindentMode::Block {
3637 original_indent_columns,
3638 }),
3639 cx,
3640 )
3641 });
3642 }
3643
3644 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3645 self.hide_context_menu(window, cx);
3646
3647 match phase {
3648 SelectPhase::Begin {
3649 position,
3650 add,
3651 click_count,
3652 } => self.begin_selection(position, add, click_count, window, cx),
3653 SelectPhase::BeginColumnar {
3654 position,
3655 goal_column,
3656 reset,
3657 mode,
3658 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3659 SelectPhase::Extend {
3660 position,
3661 click_count,
3662 } => self.extend_selection(position, click_count, window, cx),
3663 SelectPhase::Update {
3664 position,
3665 goal_column,
3666 scroll_delta,
3667 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3668 SelectPhase::End => self.end_selection(window, cx),
3669 }
3670 }
3671
3672 fn extend_selection(
3673 &mut self,
3674 position: DisplayPoint,
3675 click_count: usize,
3676 window: &mut Window,
3677 cx: &mut Context<Self>,
3678 ) {
3679 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3680 let tail = self
3681 .selections
3682 .newest::<MultiBufferOffset>(&display_map)
3683 .tail();
3684 let click_count = click_count.max(match self.selections.select_mode() {
3685 SelectMode::Character => 1,
3686 SelectMode::Word(_) => 2,
3687 SelectMode::Line(_) => 3,
3688 SelectMode::All => 4,
3689 });
3690 self.begin_selection(position, false, click_count, window, cx);
3691
3692 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3693
3694 let current_selection = match self.selections.select_mode() {
3695 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3696 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3697 };
3698
3699 let mut pending_selection = self
3700 .selections
3701 .pending_anchor()
3702 .cloned()
3703 .expect("extend_selection not called with pending selection");
3704
3705 if pending_selection
3706 .start
3707 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3708 == Ordering::Greater
3709 {
3710 pending_selection.start = current_selection.start;
3711 }
3712 if pending_selection
3713 .end
3714 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3715 == Ordering::Less
3716 {
3717 pending_selection.end = current_selection.end;
3718 pending_selection.reversed = true;
3719 }
3720
3721 let mut pending_mode = self.selections.pending_mode().unwrap();
3722 match &mut pending_mode {
3723 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3724 _ => {}
3725 }
3726
3727 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3728 SelectionEffects::scroll(Autoscroll::fit())
3729 } else {
3730 SelectionEffects::no_scroll()
3731 };
3732
3733 self.change_selections(effects, window, cx, |s| {
3734 s.set_pending(pending_selection.clone(), pending_mode);
3735 s.set_is_extending(true);
3736 });
3737 }
3738
3739 fn begin_selection(
3740 &mut self,
3741 position: DisplayPoint,
3742 add: bool,
3743 click_count: usize,
3744 window: &mut Window,
3745 cx: &mut Context<Self>,
3746 ) {
3747 if !self.focus_handle.is_focused(window) {
3748 self.last_focused_descendant = None;
3749 window.focus(&self.focus_handle);
3750 }
3751
3752 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3753 let buffer = display_map.buffer_snapshot();
3754 let position = display_map.clip_point(position, Bias::Left);
3755
3756 let start;
3757 let end;
3758 let mode;
3759 let mut auto_scroll;
3760 match click_count {
3761 1 => {
3762 start = buffer.anchor_before(position.to_point(&display_map));
3763 end = start;
3764 mode = SelectMode::Character;
3765 auto_scroll = true;
3766 }
3767 2 => {
3768 let position = display_map
3769 .clip_point(position, Bias::Left)
3770 .to_offset(&display_map, Bias::Left);
3771 let (range, _) = buffer.surrounding_word(position, None);
3772 start = buffer.anchor_before(range.start);
3773 end = buffer.anchor_before(range.end);
3774 mode = SelectMode::Word(start..end);
3775 auto_scroll = true;
3776 }
3777 3 => {
3778 let position = display_map
3779 .clip_point(position, Bias::Left)
3780 .to_point(&display_map);
3781 let line_start = display_map.prev_line_boundary(position).0;
3782 let next_line_start = buffer.clip_point(
3783 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3784 Bias::Left,
3785 );
3786 start = buffer.anchor_before(line_start);
3787 end = buffer.anchor_before(next_line_start);
3788 mode = SelectMode::Line(start..end);
3789 auto_scroll = true;
3790 }
3791 _ => {
3792 start = buffer.anchor_before(MultiBufferOffset(0));
3793 end = buffer.anchor_before(buffer.len());
3794 mode = SelectMode::All;
3795 auto_scroll = false;
3796 }
3797 }
3798 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3799
3800 let point_to_delete: Option<usize> = {
3801 let selected_points: Vec<Selection<Point>> =
3802 self.selections.disjoint_in_range(start..end, &display_map);
3803
3804 if !add || click_count > 1 {
3805 None
3806 } else if !selected_points.is_empty() {
3807 Some(selected_points[0].id)
3808 } else {
3809 let clicked_point_already_selected =
3810 self.selections.disjoint_anchors().iter().find(|selection| {
3811 selection.start.to_point(buffer) == start.to_point(buffer)
3812 || selection.end.to_point(buffer) == end.to_point(buffer)
3813 });
3814
3815 clicked_point_already_selected.map(|selection| selection.id)
3816 }
3817 };
3818
3819 let selections_count = self.selections.count();
3820 let effects = if auto_scroll {
3821 SelectionEffects::default()
3822 } else {
3823 SelectionEffects::no_scroll()
3824 };
3825
3826 self.change_selections(effects, window, cx, |s| {
3827 if let Some(point_to_delete) = point_to_delete {
3828 s.delete(point_to_delete);
3829
3830 if selections_count == 1 {
3831 s.set_pending_anchor_range(start..end, mode);
3832 }
3833 } else {
3834 if !add {
3835 s.clear_disjoint();
3836 }
3837
3838 s.set_pending_anchor_range(start..end, mode);
3839 }
3840 });
3841 }
3842
3843 fn begin_columnar_selection(
3844 &mut self,
3845 position: DisplayPoint,
3846 goal_column: u32,
3847 reset: bool,
3848 mode: ColumnarMode,
3849 window: &mut Window,
3850 cx: &mut Context<Self>,
3851 ) {
3852 if !self.focus_handle.is_focused(window) {
3853 self.last_focused_descendant = None;
3854 window.focus(&self.focus_handle);
3855 }
3856
3857 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3858
3859 if reset {
3860 let pointer_position = display_map
3861 .buffer_snapshot()
3862 .anchor_before(position.to_point(&display_map));
3863
3864 self.change_selections(
3865 SelectionEffects::scroll(Autoscroll::newest()),
3866 window,
3867 cx,
3868 |s| {
3869 s.clear_disjoint();
3870 s.set_pending_anchor_range(
3871 pointer_position..pointer_position,
3872 SelectMode::Character,
3873 );
3874 },
3875 );
3876 };
3877
3878 let tail = self.selections.newest::<Point>(&display_map).tail();
3879 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3880 self.columnar_selection_state = match mode {
3881 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3882 selection_tail: selection_anchor,
3883 display_point: if reset {
3884 if position.column() != goal_column {
3885 Some(DisplayPoint::new(position.row(), goal_column))
3886 } else {
3887 None
3888 }
3889 } else {
3890 None
3891 },
3892 }),
3893 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3894 selection_tail: selection_anchor,
3895 }),
3896 };
3897
3898 if !reset {
3899 self.select_columns(position, goal_column, &display_map, window, cx);
3900 }
3901 }
3902
3903 fn update_selection(
3904 &mut self,
3905 position: DisplayPoint,
3906 goal_column: u32,
3907 scroll_delta: gpui::Point<f32>,
3908 window: &mut Window,
3909 cx: &mut Context<Self>,
3910 ) {
3911 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3912
3913 if self.columnar_selection_state.is_some() {
3914 self.select_columns(position, goal_column, &display_map, window, cx);
3915 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3916 let buffer = display_map.buffer_snapshot();
3917 let head;
3918 let tail;
3919 let mode = self.selections.pending_mode().unwrap();
3920 match &mode {
3921 SelectMode::Character => {
3922 head = position.to_point(&display_map);
3923 tail = pending.tail().to_point(buffer);
3924 }
3925 SelectMode::Word(original_range) => {
3926 let offset = display_map
3927 .clip_point(position, Bias::Left)
3928 .to_offset(&display_map, Bias::Left);
3929 let original_range = original_range.to_offset(buffer);
3930
3931 let head_offset = if buffer.is_inside_word(offset, None)
3932 || original_range.contains(&offset)
3933 {
3934 let (word_range, _) = buffer.surrounding_word(offset, None);
3935 if word_range.start < original_range.start {
3936 word_range.start
3937 } else {
3938 word_range.end
3939 }
3940 } else {
3941 offset
3942 };
3943
3944 head = head_offset.to_point(buffer);
3945 if head_offset <= original_range.start {
3946 tail = original_range.end.to_point(buffer);
3947 } else {
3948 tail = original_range.start.to_point(buffer);
3949 }
3950 }
3951 SelectMode::Line(original_range) => {
3952 let original_range = original_range.to_point(display_map.buffer_snapshot());
3953
3954 let position = display_map
3955 .clip_point(position, Bias::Left)
3956 .to_point(&display_map);
3957 let line_start = display_map.prev_line_boundary(position).0;
3958 let next_line_start = buffer.clip_point(
3959 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3960 Bias::Left,
3961 );
3962
3963 if line_start < original_range.start {
3964 head = line_start
3965 } else {
3966 head = next_line_start
3967 }
3968
3969 if head <= original_range.start {
3970 tail = original_range.end;
3971 } else {
3972 tail = original_range.start;
3973 }
3974 }
3975 SelectMode::All => {
3976 return;
3977 }
3978 };
3979
3980 if head < tail {
3981 pending.start = buffer.anchor_before(head);
3982 pending.end = buffer.anchor_before(tail);
3983 pending.reversed = true;
3984 } else {
3985 pending.start = buffer.anchor_before(tail);
3986 pending.end = buffer.anchor_before(head);
3987 pending.reversed = false;
3988 }
3989
3990 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3991 s.set_pending(pending.clone(), mode);
3992 });
3993 } else {
3994 log::error!("update_selection dispatched with no pending selection");
3995 return;
3996 }
3997
3998 self.apply_scroll_delta(scroll_delta, window, cx);
3999 cx.notify();
4000 }
4001
4002 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4003 self.columnar_selection_state.take();
4004 if let Some(pending_mode) = self.selections.pending_mode() {
4005 let selections = self
4006 .selections
4007 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4008 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4009 s.select(selections);
4010 s.clear_pending();
4011 if s.is_extending() {
4012 s.set_is_extending(false);
4013 } else {
4014 s.set_select_mode(pending_mode);
4015 }
4016 });
4017 }
4018 }
4019
4020 fn select_columns(
4021 &mut self,
4022 head: DisplayPoint,
4023 goal_column: u32,
4024 display_map: &DisplaySnapshot,
4025 window: &mut Window,
4026 cx: &mut Context<Self>,
4027 ) {
4028 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4029 return;
4030 };
4031
4032 let tail = match columnar_state {
4033 ColumnarSelectionState::FromMouse {
4034 selection_tail,
4035 display_point,
4036 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4037 ColumnarSelectionState::FromSelection { selection_tail } => {
4038 selection_tail.to_display_point(display_map)
4039 }
4040 };
4041
4042 let start_row = cmp::min(tail.row(), head.row());
4043 let end_row = cmp::max(tail.row(), head.row());
4044 let start_column = cmp::min(tail.column(), goal_column);
4045 let end_column = cmp::max(tail.column(), goal_column);
4046 let reversed = start_column < tail.column();
4047
4048 let selection_ranges = (start_row.0..=end_row.0)
4049 .map(DisplayRow)
4050 .filter_map(|row| {
4051 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4052 || start_column <= display_map.line_len(row))
4053 && !display_map.is_block_line(row)
4054 {
4055 let start = display_map
4056 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4057 .to_point(display_map);
4058 let end = display_map
4059 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4060 .to_point(display_map);
4061 if reversed {
4062 Some(end..start)
4063 } else {
4064 Some(start..end)
4065 }
4066 } else {
4067 None
4068 }
4069 })
4070 .collect::<Vec<_>>();
4071 if selection_ranges.is_empty() {
4072 return;
4073 }
4074
4075 let ranges = match columnar_state {
4076 ColumnarSelectionState::FromMouse { .. } => {
4077 let mut non_empty_ranges = selection_ranges
4078 .iter()
4079 .filter(|selection_range| selection_range.start != selection_range.end)
4080 .peekable();
4081 if non_empty_ranges.peek().is_some() {
4082 non_empty_ranges.cloned().collect()
4083 } else {
4084 selection_ranges
4085 }
4086 }
4087 _ => selection_ranges,
4088 };
4089
4090 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4091 s.select_ranges(ranges);
4092 });
4093 cx.notify();
4094 }
4095
4096 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4097 self.selections
4098 .all_adjusted(snapshot)
4099 .iter()
4100 .any(|selection| !selection.is_empty())
4101 }
4102
4103 pub fn has_pending_nonempty_selection(&self) -> bool {
4104 let pending_nonempty_selection = match self.selections.pending_anchor() {
4105 Some(Selection { start, end, .. }) => start != end,
4106 None => false,
4107 };
4108
4109 pending_nonempty_selection
4110 || (self.columnar_selection_state.is_some()
4111 && self.selections.disjoint_anchors().len() > 1)
4112 }
4113
4114 pub fn has_pending_selection(&self) -> bool {
4115 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4116 }
4117
4118 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4119 self.selection_mark_mode = false;
4120 self.selection_drag_state = SelectionDragState::None;
4121
4122 if self.dismiss_menus_and_popups(true, window, cx) {
4123 cx.notify();
4124 return;
4125 }
4126 if self.clear_expanded_diff_hunks(cx) {
4127 cx.notify();
4128 return;
4129 }
4130 if self.show_git_blame_gutter {
4131 self.show_git_blame_gutter = false;
4132 cx.notify();
4133 return;
4134 }
4135
4136 if self.mode.is_full()
4137 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4138 {
4139 cx.notify();
4140 return;
4141 }
4142
4143 cx.propagate();
4144 }
4145
4146 pub fn dismiss_menus_and_popups(
4147 &mut self,
4148 is_user_requested: bool,
4149 window: &mut Window,
4150 cx: &mut Context<Self>,
4151 ) -> bool {
4152 let mut dismissed = false;
4153
4154 dismissed |= self.take_rename(false, window, cx).is_some();
4155 dismissed |= self.hide_blame_popover(true, cx);
4156 dismissed |= hide_hover(self, cx);
4157 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4158 dismissed |= self.hide_context_menu(window, cx).is_some();
4159 dismissed |= self.mouse_context_menu.take().is_some();
4160 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4161 dismissed |= self.snippet_stack.pop().is_some();
4162
4163 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4164 self.dismiss_diagnostics(cx);
4165 dismissed = true;
4166 }
4167
4168 dismissed
4169 }
4170
4171 fn linked_editing_ranges_for(
4172 &self,
4173 selection: Range<text::Anchor>,
4174 cx: &App,
4175 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4176 if self.linked_edit_ranges.is_empty() {
4177 return None;
4178 }
4179 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4180 selection.end.buffer_id.and_then(|end_buffer_id| {
4181 if selection.start.buffer_id != Some(end_buffer_id) {
4182 return None;
4183 }
4184 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4185 let snapshot = buffer.read(cx).snapshot();
4186 self.linked_edit_ranges
4187 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4188 .map(|ranges| (ranges, snapshot, buffer))
4189 })?;
4190 use text::ToOffset as TO;
4191 // find offset from the start of current range to current cursor position
4192 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4193
4194 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4195 let start_difference = start_offset - start_byte_offset;
4196 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4197 let end_difference = end_offset - start_byte_offset;
4198 // Current range has associated linked ranges.
4199 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4200 for range in linked_ranges.iter() {
4201 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4202 let end_offset = start_offset + end_difference;
4203 let start_offset = start_offset + start_difference;
4204 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4205 continue;
4206 }
4207 if self.selections.disjoint_anchor_ranges().any(|s| {
4208 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4209 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4210 {
4211 return false;
4212 }
4213 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4214 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4215 }) {
4216 continue;
4217 }
4218 let start = buffer_snapshot.anchor_after(start_offset);
4219 let end = buffer_snapshot.anchor_after(end_offset);
4220 linked_edits
4221 .entry(buffer.clone())
4222 .or_default()
4223 .push(start..end);
4224 }
4225 Some(linked_edits)
4226 }
4227
4228 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4229 let text: Arc<str> = text.into();
4230
4231 if self.read_only(cx) {
4232 return;
4233 }
4234
4235 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4236
4237 self.unfold_buffers_with_selections(cx);
4238
4239 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4240 let mut bracket_inserted = false;
4241 let mut edits = Vec::new();
4242 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4243 let mut new_selections = Vec::with_capacity(selections.len());
4244 let mut new_autoclose_regions = Vec::new();
4245 let snapshot = self.buffer.read(cx).read(cx);
4246 let mut clear_linked_edit_ranges = false;
4247
4248 for (selection, autoclose_region) in
4249 self.selections_with_autoclose_regions(selections, &snapshot)
4250 {
4251 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4252 // Determine if the inserted text matches the opening or closing
4253 // bracket of any of this language's bracket pairs.
4254 let mut bracket_pair = None;
4255 let mut is_bracket_pair_start = false;
4256 let mut is_bracket_pair_end = false;
4257 if !text.is_empty() {
4258 let mut bracket_pair_matching_end = None;
4259 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4260 // and they are removing the character that triggered IME popup.
4261 for (pair, enabled) in scope.brackets() {
4262 if !pair.close && !pair.surround {
4263 continue;
4264 }
4265
4266 if enabled && pair.start.ends_with(text.as_ref()) {
4267 let prefix_len = pair.start.len() - text.len();
4268 let preceding_text_matches_prefix = prefix_len == 0
4269 || (selection.start.column >= (prefix_len as u32)
4270 && snapshot.contains_str_at(
4271 Point::new(
4272 selection.start.row,
4273 selection.start.column - (prefix_len as u32),
4274 ),
4275 &pair.start[..prefix_len],
4276 ));
4277 if preceding_text_matches_prefix {
4278 bracket_pair = Some(pair.clone());
4279 is_bracket_pair_start = true;
4280 break;
4281 }
4282 }
4283 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4284 {
4285 // take first bracket pair matching end, but don't break in case a later bracket
4286 // pair matches start
4287 bracket_pair_matching_end = Some(pair.clone());
4288 }
4289 }
4290 if let Some(end) = bracket_pair_matching_end
4291 && bracket_pair.is_none()
4292 {
4293 bracket_pair = Some(end);
4294 is_bracket_pair_end = true;
4295 }
4296 }
4297
4298 if let Some(bracket_pair) = bracket_pair {
4299 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4300 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4301 let auto_surround =
4302 self.use_auto_surround && snapshot_settings.use_auto_surround;
4303 if selection.is_empty() {
4304 if is_bracket_pair_start {
4305 // If the inserted text is a suffix of an opening bracket and the
4306 // selection is preceded by the rest of the opening bracket, then
4307 // insert the closing bracket.
4308 let following_text_allows_autoclose = snapshot
4309 .chars_at(selection.start)
4310 .next()
4311 .is_none_or(|c| scope.should_autoclose_before(c));
4312
4313 let preceding_text_allows_autoclose = selection.start.column == 0
4314 || snapshot
4315 .reversed_chars_at(selection.start)
4316 .next()
4317 .is_none_or(|c| {
4318 bracket_pair.start != bracket_pair.end
4319 || !snapshot
4320 .char_classifier_at(selection.start)
4321 .is_word(c)
4322 });
4323
4324 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4325 && bracket_pair.start.len() == 1
4326 {
4327 let target = bracket_pair.start.chars().next().unwrap();
4328 let current_line_count = snapshot
4329 .reversed_chars_at(selection.start)
4330 .take_while(|&c| c != '\n')
4331 .filter(|&c| c == target)
4332 .count();
4333 current_line_count % 2 == 1
4334 } else {
4335 false
4336 };
4337
4338 if autoclose
4339 && bracket_pair.close
4340 && following_text_allows_autoclose
4341 && preceding_text_allows_autoclose
4342 && !is_closing_quote
4343 {
4344 let anchor = snapshot.anchor_before(selection.end);
4345 new_selections.push((selection.map(|_| anchor), text.len()));
4346 new_autoclose_regions.push((
4347 anchor,
4348 text.len(),
4349 selection.id,
4350 bracket_pair.clone(),
4351 ));
4352 edits.push((
4353 selection.range(),
4354 format!("{}{}", text, bracket_pair.end).into(),
4355 ));
4356 bracket_inserted = true;
4357 continue;
4358 }
4359 }
4360
4361 if let Some(region) = autoclose_region {
4362 // If the selection is followed by an auto-inserted closing bracket,
4363 // then don't insert that closing bracket again; just move the selection
4364 // past the closing bracket.
4365 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4366 && text.as_ref() == region.pair.end.as_str()
4367 && snapshot.contains_str_at(region.range.end, text.as_ref());
4368 if should_skip {
4369 let anchor = snapshot.anchor_after(selection.end);
4370 new_selections
4371 .push((selection.map(|_| anchor), region.pair.end.len()));
4372 continue;
4373 }
4374 }
4375
4376 let always_treat_brackets_as_autoclosed = snapshot
4377 .language_settings_at(selection.start, cx)
4378 .always_treat_brackets_as_autoclosed;
4379 if always_treat_brackets_as_autoclosed
4380 && is_bracket_pair_end
4381 && snapshot.contains_str_at(selection.end, text.as_ref())
4382 {
4383 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4384 // and the inserted text is a closing bracket and the selection is followed
4385 // by the closing bracket then move the selection past the closing bracket.
4386 let anchor = snapshot.anchor_after(selection.end);
4387 new_selections.push((selection.map(|_| anchor), text.len()));
4388 continue;
4389 }
4390 }
4391 // If an opening bracket is 1 character long and is typed while
4392 // text is selected, then surround that text with the bracket pair.
4393 else if auto_surround
4394 && bracket_pair.surround
4395 && is_bracket_pair_start
4396 && bracket_pair.start.chars().count() == 1
4397 {
4398 edits.push((selection.start..selection.start, text.clone()));
4399 edits.push((
4400 selection.end..selection.end,
4401 bracket_pair.end.as_str().into(),
4402 ));
4403 bracket_inserted = true;
4404 new_selections.push((
4405 Selection {
4406 id: selection.id,
4407 start: snapshot.anchor_after(selection.start),
4408 end: snapshot.anchor_before(selection.end),
4409 reversed: selection.reversed,
4410 goal: selection.goal,
4411 },
4412 0,
4413 ));
4414 continue;
4415 }
4416 }
4417 }
4418
4419 if self.auto_replace_emoji_shortcode
4420 && selection.is_empty()
4421 && text.as_ref().ends_with(':')
4422 && let Some(possible_emoji_short_code) =
4423 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4424 && !possible_emoji_short_code.is_empty()
4425 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4426 {
4427 let emoji_shortcode_start = Point::new(
4428 selection.start.row,
4429 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4430 );
4431
4432 // Remove shortcode from buffer
4433 edits.push((
4434 emoji_shortcode_start..selection.start,
4435 "".to_string().into(),
4436 ));
4437 new_selections.push((
4438 Selection {
4439 id: selection.id,
4440 start: snapshot.anchor_after(emoji_shortcode_start),
4441 end: snapshot.anchor_before(selection.start),
4442 reversed: selection.reversed,
4443 goal: selection.goal,
4444 },
4445 0,
4446 ));
4447
4448 // Insert emoji
4449 let selection_start_anchor = snapshot.anchor_after(selection.start);
4450 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4451 edits.push((selection.start..selection.end, emoji.to_string().into()));
4452
4453 continue;
4454 }
4455
4456 // If not handling any auto-close operation, then just replace the selected
4457 // text with the given input and move the selection to the end of the
4458 // newly inserted text.
4459 let anchor = snapshot.anchor_after(selection.end);
4460 if !self.linked_edit_ranges.is_empty() {
4461 let start_anchor = snapshot.anchor_before(selection.start);
4462
4463 let is_word_char = text.chars().next().is_none_or(|char| {
4464 let classifier = snapshot
4465 .char_classifier_at(start_anchor.to_offset(&snapshot))
4466 .scope_context(Some(CharScopeContext::LinkedEdit));
4467 classifier.is_word(char)
4468 });
4469
4470 if is_word_char {
4471 if let Some(ranges) = self
4472 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4473 {
4474 for (buffer, edits) in ranges {
4475 linked_edits
4476 .entry(buffer.clone())
4477 .or_default()
4478 .extend(edits.into_iter().map(|range| (range, text.clone())));
4479 }
4480 }
4481 } else {
4482 clear_linked_edit_ranges = true;
4483 }
4484 }
4485
4486 new_selections.push((selection.map(|_| anchor), 0));
4487 edits.push((selection.start..selection.end, text.clone()));
4488 }
4489
4490 drop(snapshot);
4491
4492 self.transact(window, cx, |this, window, cx| {
4493 if clear_linked_edit_ranges {
4494 this.linked_edit_ranges.clear();
4495 }
4496 let initial_buffer_versions =
4497 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4498
4499 this.buffer.update(cx, |buffer, cx| {
4500 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4501 });
4502 for (buffer, edits) in linked_edits {
4503 buffer.update(cx, |buffer, cx| {
4504 let snapshot = buffer.snapshot();
4505 let edits = edits
4506 .into_iter()
4507 .map(|(range, text)| {
4508 use text::ToPoint as TP;
4509 let end_point = TP::to_point(&range.end, &snapshot);
4510 let start_point = TP::to_point(&range.start, &snapshot);
4511 (start_point..end_point, text)
4512 })
4513 .sorted_by_key(|(range, _)| range.start);
4514 buffer.edit(edits, None, cx);
4515 })
4516 }
4517 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4518 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4519 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4520 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4521 new_anchor_selections,
4522 &map,
4523 )
4524 .zip(new_selection_deltas)
4525 .map(|(selection, delta)| Selection {
4526 id: selection.id,
4527 start: selection.start + delta,
4528 end: selection.end + delta,
4529 reversed: selection.reversed,
4530 goal: SelectionGoal::None,
4531 })
4532 .collect::<Vec<_>>();
4533
4534 let mut i = 0;
4535 for (position, delta, selection_id, pair) in new_autoclose_regions {
4536 let position = position.to_offset(map.buffer_snapshot()) + delta;
4537 let start = map.buffer_snapshot().anchor_before(position);
4538 let end = map.buffer_snapshot().anchor_after(position);
4539 while let Some(existing_state) = this.autoclose_regions.get(i) {
4540 match existing_state
4541 .range
4542 .start
4543 .cmp(&start, map.buffer_snapshot())
4544 {
4545 Ordering::Less => i += 1,
4546 Ordering::Greater => break,
4547 Ordering::Equal => {
4548 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4549 Ordering::Less => i += 1,
4550 Ordering::Equal => break,
4551 Ordering::Greater => break,
4552 }
4553 }
4554 }
4555 }
4556 this.autoclose_regions.insert(
4557 i,
4558 AutocloseRegion {
4559 selection_id,
4560 range: start..end,
4561 pair,
4562 },
4563 );
4564 }
4565
4566 let had_active_edit_prediction = this.has_active_edit_prediction();
4567 this.change_selections(
4568 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4569 window,
4570 cx,
4571 |s| s.select(new_selections),
4572 );
4573
4574 if !bracket_inserted
4575 && let Some(on_type_format_task) =
4576 this.trigger_on_type_formatting(text.to_string(), window, cx)
4577 {
4578 on_type_format_task.detach_and_log_err(cx);
4579 }
4580
4581 let editor_settings = EditorSettings::get_global(cx);
4582 if bracket_inserted
4583 && (editor_settings.auto_signature_help
4584 || editor_settings.show_signature_help_after_edits)
4585 {
4586 this.show_signature_help(&ShowSignatureHelp, window, cx);
4587 }
4588
4589 let trigger_in_words =
4590 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4591 if this.hard_wrap.is_some() {
4592 let latest: Range<Point> = this.selections.newest(&map).range();
4593 if latest.is_empty()
4594 && this
4595 .buffer()
4596 .read(cx)
4597 .snapshot(cx)
4598 .line_len(MultiBufferRow(latest.start.row))
4599 == latest.start.column
4600 {
4601 this.rewrap_impl(
4602 RewrapOptions {
4603 override_language_settings: true,
4604 preserve_existing_whitespace: true,
4605 },
4606 cx,
4607 )
4608 }
4609 }
4610 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4611 refresh_linked_ranges(this, window, cx);
4612 this.refresh_edit_prediction(true, false, window, cx);
4613 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4614 });
4615 }
4616
4617 fn find_possible_emoji_shortcode_at_position(
4618 snapshot: &MultiBufferSnapshot,
4619 position: Point,
4620 ) -> Option<String> {
4621 let mut chars = Vec::new();
4622 let mut found_colon = false;
4623 for char in snapshot.reversed_chars_at(position).take(100) {
4624 // Found a possible emoji shortcode in the middle of the buffer
4625 if found_colon {
4626 if char.is_whitespace() {
4627 chars.reverse();
4628 return Some(chars.iter().collect());
4629 }
4630 // If the previous character is not a whitespace, we are in the middle of a word
4631 // and we only want to complete the shortcode if the word is made up of other emojis
4632 let mut containing_word = String::new();
4633 for ch in snapshot
4634 .reversed_chars_at(position)
4635 .skip(chars.len() + 1)
4636 .take(100)
4637 {
4638 if ch.is_whitespace() {
4639 break;
4640 }
4641 containing_word.push(ch);
4642 }
4643 let containing_word = containing_word.chars().rev().collect::<String>();
4644 if util::word_consists_of_emojis(containing_word.as_str()) {
4645 chars.reverse();
4646 return Some(chars.iter().collect());
4647 }
4648 }
4649
4650 if char.is_whitespace() || !char.is_ascii() {
4651 return None;
4652 }
4653 if char == ':' {
4654 found_colon = true;
4655 } else {
4656 chars.push(char);
4657 }
4658 }
4659 // Found a possible emoji shortcode at the beginning of the buffer
4660 chars.reverse();
4661 Some(chars.iter().collect())
4662 }
4663
4664 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4665 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4666 self.transact(window, cx, |this, window, cx| {
4667 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4668 let selections = this
4669 .selections
4670 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4671 let multi_buffer = this.buffer.read(cx);
4672 let buffer = multi_buffer.snapshot(cx);
4673 selections
4674 .iter()
4675 .map(|selection| {
4676 let start_point = selection.start.to_point(&buffer);
4677 let mut existing_indent =
4678 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4679 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4680 let start = selection.start;
4681 let end = selection.end;
4682 let selection_is_empty = start == end;
4683 let language_scope = buffer.language_scope_at(start);
4684 let (
4685 comment_delimiter,
4686 doc_delimiter,
4687 insert_extra_newline,
4688 indent_on_newline,
4689 indent_on_extra_newline,
4690 ) = if let Some(language) = &language_scope {
4691 let mut insert_extra_newline =
4692 insert_extra_newline_brackets(&buffer, start..end, language)
4693 || insert_extra_newline_tree_sitter(&buffer, start..end);
4694
4695 // Comment extension on newline is allowed only for cursor selections
4696 let comment_delimiter = maybe!({
4697 if !selection_is_empty {
4698 return None;
4699 }
4700
4701 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4702 return None;
4703 }
4704
4705 let delimiters = language.line_comment_prefixes();
4706 let max_len_of_delimiter =
4707 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4708 let (snapshot, range) =
4709 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4710
4711 let num_of_whitespaces = snapshot
4712 .chars_for_range(range.clone())
4713 .take_while(|c| c.is_whitespace())
4714 .count();
4715 let comment_candidate = snapshot
4716 .chars_for_range(range.clone())
4717 .skip(num_of_whitespaces)
4718 .take(max_len_of_delimiter)
4719 .collect::<String>();
4720 let (delimiter, trimmed_len) = delimiters
4721 .iter()
4722 .filter_map(|delimiter| {
4723 let prefix = delimiter.trim_end();
4724 if comment_candidate.starts_with(prefix) {
4725 Some((delimiter, prefix.len()))
4726 } else {
4727 None
4728 }
4729 })
4730 .max_by_key(|(_, len)| *len)?;
4731
4732 if let Some(BlockCommentConfig {
4733 start: block_start, ..
4734 }) = language.block_comment()
4735 {
4736 let block_start_trimmed = block_start.trim_end();
4737 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4738 let line_content = snapshot
4739 .chars_for_range(range)
4740 .skip(num_of_whitespaces)
4741 .take(block_start_trimmed.len())
4742 .collect::<String>();
4743
4744 if line_content.starts_with(block_start_trimmed) {
4745 return None;
4746 }
4747 }
4748 }
4749
4750 let cursor_is_placed_after_comment_marker =
4751 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4752 if cursor_is_placed_after_comment_marker {
4753 Some(delimiter.clone())
4754 } else {
4755 None
4756 }
4757 });
4758
4759 let mut indent_on_newline = IndentSize::spaces(0);
4760 let mut indent_on_extra_newline = IndentSize::spaces(0);
4761
4762 let doc_delimiter = maybe!({
4763 if !selection_is_empty {
4764 return None;
4765 }
4766
4767 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4768 return None;
4769 }
4770
4771 let BlockCommentConfig {
4772 start: start_tag,
4773 end: end_tag,
4774 prefix: delimiter,
4775 tab_size: len,
4776 } = language.documentation_comment()?;
4777 let is_within_block_comment = buffer
4778 .language_scope_at(start_point)
4779 .is_some_and(|scope| scope.override_name() == Some("comment"));
4780 if !is_within_block_comment {
4781 return None;
4782 }
4783
4784 let (snapshot, range) =
4785 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4786
4787 let num_of_whitespaces = snapshot
4788 .chars_for_range(range.clone())
4789 .take_while(|c| c.is_whitespace())
4790 .count();
4791
4792 // 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.
4793 let column = start_point.column;
4794 let cursor_is_after_start_tag = {
4795 let start_tag_len = start_tag.len();
4796 let start_tag_line = snapshot
4797 .chars_for_range(range.clone())
4798 .skip(num_of_whitespaces)
4799 .take(start_tag_len)
4800 .collect::<String>();
4801 if start_tag_line.starts_with(start_tag.as_ref()) {
4802 num_of_whitespaces + start_tag_len <= column as usize
4803 } else {
4804 false
4805 }
4806 };
4807
4808 let cursor_is_after_delimiter = {
4809 let delimiter_trim = delimiter.trim_end();
4810 let delimiter_line = snapshot
4811 .chars_for_range(range.clone())
4812 .skip(num_of_whitespaces)
4813 .take(delimiter_trim.len())
4814 .collect::<String>();
4815 if delimiter_line.starts_with(delimiter_trim) {
4816 num_of_whitespaces + delimiter_trim.len() <= column as usize
4817 } else {
4818 false
4819 }
4820 };
4821
4822 let cursor_is_before_end_tag_if_exists = {
4823 let mut char_position = 0u32;
4824 let mut end_tag_offset = None;
4825
4826 'outer: for chunk in snapshot.text_for_range(range) {
4827 if let Some(byte_pos) = chunk.find(&**end_tag) {
4828 let chars_before_match =
4829 chunk[..byte_pos].chars().count() as u32;
4830 end_tag_offset =
4831 Some(char_position + chars_before_match);
4832 break 'outer;
4833 }
4834 char_position += chunk.chars().count() as u32;
4835 }
4836
4837 if let Some(end_tag_offset) = end_tag_offset {
4838 let cursor_is_before_end_tag = column <= end_tag_offset;
4839 if cursor_is_after_start_tag {
4840 if cursor_is_before_end_tag {
4841 insert_extra_newline = true;
4842 }
4843 let cursor_is_at_start_of_end_tag =
4844 column == end_tag_offset;
4845 if cursor_is_at_start_of_end_tag {
4846 indent_on_extra_newline.len = *len;
4847 }
4848 }
4849 cursor_is_before_end_tag
4850 } else {
4851 true
4852 }
4853 };
4854
4855 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4856 && cursor_is_before_end_tag_if_exists
4857 {
4858 if cursor_is_after_start_tag {
4859 indent_on_newline.len = *len;
4860 }
4861 Some(delimiter.clone())
4862 } else {
4863 None
4864 }
4865 });
4866
4867 (
4868 comment_delimiter,
4869 doc_delimiter,
4870 insert_extra_newline,
4871 indent_on_newline,
4872 indent_on_extra_newline,
4873 )
4874 } else {
4875 (
4876 None,
4877 None,
4878 false,
4879 IndentSize::default(),
4880 IndentSize::default(),
4881 )
4882 };
4883
4884 let prevent_auto_indent = doc_delimiter.is_some();
4885 let delimiter = comment_delimiter.or(doc_delimiter);
4886
4887 let capacity_for_delimiter =
4888 delimiter.as_deref().map(str::len).unwrap_or_default();
4889 let mut new_text = String::with_capacity(
4890 1 + capacity_for_delimiter
4891 + existing_indent.len as usize
4892 + indent_on_newline.len as usize
4893 + indent_on_extra_newline.len as usize,
4894 );
4895 new_text.push('\n');
4896 new_text.extend(existing_indent.chars());
4897 new_text.extend(indent_on_newline.chars());
4898
4899 if let Some(delimiter) = &delimiter {
4900 new_text.push_str(delimiter);
4901 }
4902
4903 if insert_extra_newline {
4904 new_text.push('\n');
4905 new_text.extend(existing_indent.chars());
4906 new_text.extend(indent_on_extra_newline.chars());
4907 }
4908
4909 let anchor = buffer.anchor_after(end);
4910 let new_selection = selection.map(|_| anchor);
4911 (
4912 ((start..end, new_text), prevent_auto_indent),
4913 (insert_extra_newline, new_selection),
4914 )
4915 })
4916 .unzip()
4917 };
4918
4919 let mut auto_indent_edits = Vec::new();
4920 let mut edits = Vec::new();
4921 for (edit, prevent_auto_indent) in edits_with_flags {
4922 if prevent_auto_indent {
4923 edits.push(edit);
4924 } else {
4925 auto_indent_edits.push(edit);
4926 }
4927 }
4928 if !edits.is_empty() {
4929 this.edit(edits, cx);
4930 }
4931 if !auto_indent_edits.is_empty() {
4932 this.edit_with_autoindent(auto_indent_edits, cx);
4933 }
4934
4935 let buffer = this.buffer.read(cx).snapshot(cx);
4936 let new_selections = selection_info
4937 .into_iter()
4938 .map(|(extra_newline_inserted, new_selection)| {
4939 let mut cursor = new_selection.end.to_point(&buffer);
4940 if extra_newline_inserted {
4941 cursor.row -= 1;
4942 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4943 }
4944 new_selection.map(|_| cursor)
4945 })
4946 .collect();
4947
4948 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4949 this.refresh_edit_prediction(true, false, window, cx);
4950 });
4951 }
4952
4953 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4954 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4955
4956 let buffer = self.buffer.read(cx);
4957 let snapshot = buffer.snapshot(cx);
4958
4959 let mut edits = Vec::new();
4960 let mut rows = Vec::new();
4961
4962 for (rows_inserted, selection) in self
4963 .selections
4964 .all_adjusted(&self.display_snapshot(cx))
4965 .into_iter()
4966 .enumerate()
4967 {
4968 let cursor = selection.head();
4969 let row = cursor.row;
4970
4971 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4972
4973 let newline = "\n".to_string();
4974 edits.push((start_of_line..start_of_line, newline));
4975
4976 rows.push(row + rows_inserted as u32);
4977 }
4978
4979 self.transact(window, cx, |editor, window, cx| {
4980 editor.edit(edits, cx);
4981
4982 editor.change_selections(Default::default(), window, cx, |s| {
4983 let mut index = 0;
4984 s.move_cursors_with(|map, _, _| {
4985 let row = rows[index];
4986 index += 1;
4987
4988 let point = Point::new(row, 0);
4989 let boundary = map.next_line_boundary(point).1;
4990 let clipped = map.clip_point(boundary, Bias::Left);
4991
4992 (clipped, SelectionGoal::None)
4993 });
4994 });
4995
4996 let mut indent_edits = Vec::new();
4997 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4998 for row in rows {
4999 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5000 for (row, indent) in indents {
5001 if indent.len == 0 {
5002 continue;
5003 }
5004
5005 let text = match indent.kind {
5006 IndentKind::Space => " ".repeat(indent.len as usize),
5007 IndentKind::Tab => "\t".repeat(indent.len as usize),
5008 };
5009 let point = Point::new(row.0, 0);
5010 indent_edits.push((point..point, text));
5011 }
5012 }
5013 editor.edit(indent_edits, cx);
5014 });
5015 }
5016
5017 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5019
5020 let buffer = self.buffer.read(cx);
5021 let snapshot = buffer.snapshot(cx);
5022
5023 let mut edits = Vec::new();
5024 let mut rows = Vec::new();
5025 let mut rows_inserted = 0;
5026
5027 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5028 let cursor = selection.head();
5029 let row = cursor.row;
5030
5031 let point = Point::new(row + 1, 0);
5032 let start_of_line = snapshot.clip_point(point, Bias::Left);
5033
5034 let newline = "\n".to_string();
5035 edits.push((start_of_line..start_of_line, newline));
5036
5037 rows_inserted += 1;
5038 rows.push(row + rows_inserted);
5039 }
5040
5041 self.transact(window, cx, |editor, window, cx| {
5042 editor.edit(edits, cx);
5043
5044 editor.change_selections(Default::default(), window, cx, |s| {
5045 let mut index = 0;
5046 s.move_cursors_with(|map, _, _| {
5047 let row = rows[index];
5048 index += 1;
5049
5050 let point = Point::new(row, 0);
5051 let boundary = map.next_line_boundary(point).1;
5052 let clipped = map.clip_point(boundary, Bias::Left);
5053
5054 (clipped, SelectionGoal::None)
5055 });
5056 });
5057
5058 let mut indent_edits = Vec::new();
5059 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5060 for row in rows {
5061 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5062 for (row, indent) in indents {
5063 if indent.len == 0 {
5064 continue;
5065 }
5066
5067 let text = match indent.kind {
5068 IndentKind::Space => " ".repeat(indent.len as usize),
5069 IndentKind::Tab => "\t".repeat(indent.len as usize),
5070 };
5071 let point = Point::new(row.0, 0);
5072 indent_edits.push((point..point, text));
5073 }
5074 }
5075 editor.edit(indent_edits, cx);
5076 });
5077 }
5078
5079 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5080 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5081 original_indent_columns: Vec::new(),
5082 });
5083 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5084 }
5085
5086 fn insert_with_autoindent_mode(
5087 &mut self,
5088 text: &str,
5089 autoindent_mode: Option<AutoindentMode>,
5090 window: &mut Window,
5091 cx: &mut Context<Self>,
5092 ) {
5093 if self.read_only(cx) {
5094 return;
5095 }
5096
5097 let text: Arc<str> = text.into();
5098 self.transact(window, cx, |this, window, cx| {
5099 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5100 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5101 let anchors = {
5102 let snapshot = buffer.read(cx);
5103 old_selections
5104 .iter()
5105 .map(|s| {
5106 let anchor = snapshot.anchor_after(s.head());
5107 s.map(|_| anchor)
5108 })
5109 .collect::<Vec<_>>()
5110 };
5111 buffer.edit(
5112 old_selections
5113 .iter()
5114 .map(|s| (s.start..s.end, text.clone())),
5115 autoindent_mode,
5116 cx,
5117 );
5118 anchors
5119 });
5120
5121 this.change_selections(Default::default(), window, cx, |s| {
5122 s.select_anchors(selection_anchors);
5123 });
5124
5125 cx.notify();
5126 });
5127 }
5128
5129 fn trigger_completion_on_input(
5130 &mut self,
5131 text: &str,
5132 trigger_in_words: bool,
5133 window: &mut Window,
5134 cx: &mut Context<Self>,
5135 ) {
5136 let completions_source = self
5137 .context_menu
5138 .borrow()
5139 .as_ref()
5140 .and_then(|menu| match menu {
5141 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5142 CodeContextMenu::CodeActions(_) => None,
5143 });
5144
5145 match completions_source {
5146 Some(CompletionsMenuSource::Words { .. }) => {
5147 self.open_or_update_completions_menu(
5148 Some(CompletionsMenuSource::Words {
5149 ignore_threshold: false,
5150 }),
5151 None,
5152 trigger_in_words,
5153 window,
5154 cx,
5155 );
5156 }
5157 _ => self.open_or_update_completions_menu(
5158 None,
5159 Some(text.to_owned()).filter(|x| !x.is_empty()),
5160 true,
5161 window,
5162 cx,
5163 ),
5164 }
5165 }
5166
5167 /// If any empty selections is touching the start of its innermost containing autoclose
5168 /// region, expand it to select the brackets.
5169 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5170 let selections = self
5171 .selections
5172 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5173 let buffer = self.buffer.read(cx).read(cx);
5174 let new_selections = self
5175 .selections_with_autoclose_regions(selections, &buffer)
5176 .map(|(mut selection, region)| {
5177 if !selection.is_empty() {
5178 return selection;
5179 }
5180
5181 if let Some(region) = region {
5182 let mut range = region.range.to_offset(&buffer);
5183 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5184 range.start -= region.pair.start.len();
5185 if buffer.contains_str_at(range.start, ®ion.pair.start)
5186 && buffer.contains_str_at(range.end, ®ion.pair.end)
5187 {
5188 range.end += region.pair.end.len();
5189 selection.start = range.start;
5190 selection.end = range.end;
5191
5192 return selection;
5193 }
5194 }
5195 }
5196
5197 let always_treat_brackets_as_autoclosed = buffer
5198 .language_settings_at(selection.start, cx)
5199 .always_treat_brackets_as_autoclosed;
5200
5201 if !always_treat_brackets_as_autoclosed {
5202 return selection;
5203 }
5204
5205 if let Some(scope) = buffer.language_scope_at(selection.start) {
5206 for (pair, enabled) in scope.brackets() {
5207 if !enabled || !pair.close {
5208 continue;
5209 }
5210
5211 if buffer.contains_str_at(selection.start, &pair.end) {
5212 let pair_start_len = pair.start.len();
5213 if buffer.contains_str_at(
5214 selection.start.saturating_sub_usize(pair_start_len),
5215 &pair.start,
5216 ) {
5217 selection.start -= pair_start_len;
5218 selection.end += pair.end.len();
5219
5220 return selection;
5221 }
5222 }
5223 }
5224 }
5225
5226 selection
5227 })
5228 .collect();
5229
5230 drop(buffer);
5231 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5232 selections.select(new_selections)
5233 });
5234 }
5235
5236 /// Iterate the given selections, and for each one, find the smallest surrounding
5237 /// autoclose region. This uses the ordering of the selections and the autoclose
5238 /// regions to avoid repeated comparisons.
5239 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5240 &'a self,
5241 selections: impl IntoIterator<Item = Selection<D>>,
5242 buffer: &'a MultiBufferSnapshot,
5243 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5244 let mut i = 0;
5245 let mut regions = self.autoclose_regions.as_slice();
5246 selections.into_iter().map(move |selection| {
5247 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5248
5249 let mut enclosing = None;
5250 while let Some(pair_state) = regions.get(i) {
5251 if pair_state.range.end.to_offset(buffer) < range.start {
5252 regions = ®ions[i + 1..];
5253 i = 0;
5254 } else if pair_state.range.start.to_offset(buffer) > range.end {
5255 break;
5256 } else {
5257 if pair_state.selection_id == selection.id {
5258 enclosing = Some(pair_state);
5259 }
5260 i += 1;
5261 }
5262 }
5263
5264 (selection, enclosing)
5265 })
5266 }
5267
5268 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5269 fn invalidate_autoclose_regions(
5270 &mut self,
5271 mut selections: &[Selection<Anchor>],
5272 buffer: &MultiBufferSnapshot,
5273 ) {
5274 self.autoclose_regions.retain(|state| {
5275 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5276 return false;
5277 }
5278
5279 let mut i = 0;
5280 while let Some(selection) = selections.get(i) {
5281 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5282 selections = &selections[1..];
5283 continue;
5284 }
5285 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5286 break;
5287 }
5288 if selection.id == state.selection_id {
5289 return true;
5290 } else {
5291 i += 1;
5292 }
5293 }
5294 false
5295 });
5296 }
5297
5298 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5299 let offset = position.to_offset(buffer);
5300 let (word_range, kind) =
5301 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5302 if offset > word_range.start && kind == Some(CharKind::Word) {
5303 Some(
5304 buffer
5305 .text_for_range(word_range.start..offset)
5306 .collect::<String>(),
5307 )
5308 } else {
5309 None
5310 }
5311 }
5312
5313 pub fn visible_excerpts(
5314 &self,
5315 lsp_related_only: bool,
5316 cx: &mut Context<Editor>,
5317 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5318 let project = self.project().cloned();
5319 let multi_buffer = self.buffer().read(cx);
5320 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5321 let multi_buffer_visible_start = self
5322 .scroll_manager
5323 .anchor()
5324 .anchor
5325 .to_point(&multi_buffer_snapshot);
5326 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5327 multi_buffer_visible_start
5328 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5329 Bias::Left,
5330 );
5331 multi_buffer_snapshot
5332 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5333 .into_iter()
5334 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5335 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5336 if !lsp_related_only {
5337 return Some((
5338 excerpt_id,
5339 (
5340 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5341 buffer.version().clone(),
5342 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5343 ),
5344 ));
5345 }
5346
5347 let project = project.as_ref()?.read(cx);
5348 let buffer_file = project::File::from_dyn(buffer.file())?;
5349 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5350 let worktree_entry = buffer_worktree
5351 .read(cx)
5352 .entry_for_id(buffer_file.project_entry_id()?)?;
5353 if worktree_entry.is_ignored {
5354 None
5355 } else {
5356 Some((
5357 excerpt_id,
5358 (
5359 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5360 buffer.version().clone(),
5361 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5362 ),
5363 ))
5364 }
5365 })
5366 .collect()
5367 }
5368
5369 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5370 TextLayoutDetails {
5371 text_system: window.text_system().clone(),
5372 editor_style: self.style.clone().unwrap(),
5373 rem_size: window.rem_size(),
5374 scroll_anchor: self.scroll_manager.anchor(),
5375 visible_rows: self.visible_line_count(),
5376 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5377 }
5378 }
5379
5380 fn trigger_on_type_formatting(
5381 &self,
5382 input: String,
5383 window: &mut Window,
5384 cx: &mut Context<Self>,
5385 ) -> Option<Task<Result<()>>> {
5386 if input.len() != 1 {
5387 return None;
5388 }
5389
5390 let project = self.project()?;
5391 let position = self.selections.newest_anchor().head();
5392 let (buffer, buffer_position) = self
5393 .buffer
5394 .read(cx)
5395 .text_anchor_for_position(position, cx)?;
5396
5397 let settings = language_settings::language_settings(
5398 buffer
5399 .read(cx)
5400 .language_at(buffer_position)
5401 .map(|l| l.name()),
5402 buffer.read(cx).file(),
5403 cx,
5404 );
5405 if !settings.use_on_type_format {
5406 return None;
5407 }
5408
5409 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5410 // hence we do LSP request & edit on host side only — add formats to host's history.
5411 let push_to_lsp_host_history = true;
5412 // If this is not the host, append its history with new edits.
5413 let push_to_client_history = project.read(cx).is_via_collab();
5414
5415 let on_type_formatting = project.update(cx, |project, cx| {
5416 project.on_type_format(
5417 buffer.clone(),
5418 buffer_position,
5419 input,
5420 push_to_lsp_host_history,
5421 cx,
5422 )
5423 });
5424 Some(cx.spawn_in(window, async move |editor, cx| {
5425 if let Some(transaction) = on_type_formatting.await? {
5426 if push_to_client_history {
5427 buffer
5428 .update(cx, |buffer, _| {
5429 buffer.push_transaction(transaction, Instant::now());
5430 buffer.finalize_last_transaction();
5431 })
5432 .ok();
5433 }
5434 editor.update(cx, |editor, cx| {
5435 editor.refresh_document_highlights(cx);
5436 })?;
5437 }
5438 Ok(())
5439 }))
5440 }
5441
5442 pub fn show_word_completions(
5443 &mut self,
5444 _: &ShowWordCompletions,
5445 window: &mut Window,
5446 cx: &mut Context<Self>,
5447 ) {
5448 self.open_or_update_completions_menu(
5449 Some(CompletionsMenuSource::Words {
5450 ignore_threshold: true,
5451 }),
5452 None,
5453 false,
5454 window,
5455 cx,
5456 );
5457 }
5458
5459 pub fn show_completions(
5460 &mut self,
5461 _: &ShowCompletions,
5462 window: &mut Window,
5463 cx: &mut Context<Self>,
5464 ) {
5465 self.open_or_update_completions_menu(None, None, false, window, cx);
5466 }
5467
5468 fn open_or_update_completions_menu(
5469 &mut self,
5470 requested_source: Option<CompletionsMenuSource>,
5471 trigger: Option<String>,
5472 trigger_in_words: bool,
5473 window: &mut Window,
5474 cx: &mut Context<Self>,
5475 ) {
5476 if self.pending_rename.is_some() {
5477 return;
5478 }
5479
5480 let completions_source = self
5481 .context_menu
5482 .borrow()
5483 .as_ref()
5484 .and_then(|menu| match menu {
5485 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5486 CodeContextMenu::CodeActions(_) => None,
5487 });
5488
5489 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5490
5491 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5492 // inserted and selected. To handle that case, the start of the selection is used so that
5493 // the menu starts with all choices.
5494 let position = self
5495 .selections
5496 .newest_anchor()
5497 .start
5498 .bias_right(&multibuffer_snapshot);
5499 if position.diff_base_anchor.is_some() {
5500 return;
5501 }
5502 let buffer_position = multibuffer_snapshot.anchor_before(position);
5503 let Some(buffer) = buffer_position
5504 .text_anchor
5505 .buffer_id
5506 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5507 else {
5508 return;
5509 };
5510 let buffer_snapshot = buffer.read(cx).snapshot();
5511
5512 let query: Option<Arc<String>> =
5513 Self::completion_query(&multibuffer_snapshot, buffer_position)
5514 .map(|query| query.into());
5515
5516 drop(multibuffer_snapshot);
5517
5518 // Hide the current completions menu when query is empty. Without this, cached
5519 // completions from before the trigger char may be reused (#32774).
5520 if query.is_none() {
5521 let menu_is_open = matches!(
5522 self.context_menu.borrow().as_ref(),
5523 Some(CodeContextMenu::Completions(_))
5524 );
5525 if menu_is_open {
5526 self.hide_context_menu(window, cx);
5527 }
5528 }
5529
5530 let mut ignore_word_threshold = false;
5531 let provider = match requested_source {
5532 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5533 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5534 ignore_word_threshold = ignore_threshold;
5535 None
5536 }
5537 Some(CompletionsMenuSource::SnippetChoices)
5538 | Some(CompletionsMenuSource::SnippetsOnly) => {
5539 log::error!("bug: SnippetChoices requested_source is not handled");
5540 None
5541 }
5542 };
5543
5544 let sort_completions = provider
5545 .as_ref()
5546 .is_some_and(|provider| provider.sort_completions());
5547
5548 let filter_completions = provider
5549 .as_ref()
5550 .is_none_or(|provider| provider.filter_completions());
5551
5552 let was_snippets_only = matches!(
5553 completions_source,
5554 Some(CompletionsMenuSource::SnippetsOnly)
5555 );
5556
5557 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5558 if filter_completions {
5559 menu.filter(
5560 query.clone().unwrap_or_default(),
5561 buffer_position.text_anchor,
5562 &buffer,
5563 provider.clone(),
5564 window,
5565 cx,
5566 );
5567 }
5568 // When `is_incomplete` is false, no need to re-query completions when the current query
5569 // is a suffix of the initial query.
5570 let was_complete = !menu.is_incomplete;
5571 if was_complete && !was_snippets_only {
5572 // If the new query is a suffix of the old query (typing more characters) and
5573 // the previous result was complete, the existing completions can be filtered.
5574 //
5575 // Note that snippet completions are always complete.
5576 let query_matches = match (&menu.initial_query, &query) {
5577 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5578 (None, _) => true,
5579 _ => false,
5580 };
5581 if query_matches {
5582 let position_matches = if menu.initial_position == position {
5583 true
5584 } else {
5585 let snapshot = self.buffer.read(cx).read(cx);
5586 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5587 };
5588 if position_matches {
5589 return;
5590 }
5591 }
5592 }
5593 };
5594
5595 let Anchor {
5596 excerpt_id: buffer_excerpt_id,
5597 text_anchor: buffer_position,
5598 ..
5599 } = buffer_position;
5600
5601 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5602 buffer_snapshot.surrounding_word(buffer_position, None)
5603 {
5604 let word_to_exclude = buffer_snapshot
5605 .text_for_range(word_range.clone())
5606 .collect::<String>();
5607 (
5608 buffer_snapshot.anchor_before(word_range.start)
5609 ..buffer_snapshot.anchor_after(buffer_position),
5610 Some(word_to_exclude),
5611 )
5612 } else {
5613 (buffer_position..buffer_position, None)
5614 };
5615
5616 let language = buffer_snapshot
5617 .language_at(buffer_position)
5618 .map(|language| language.name());
5619
5620 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5621 .completions
5622 .clone();
5623
5624 let show_completion_documentation = buffer_snapshot
5625 .settings_at(buffer_position, cx)
5626 .show_completion_documentation;
5627
5628 // The document can be large, so stay in reasonable bounds when searching for words,
5629 // otherwise completion pop-up might be slow to appear.
5630 const WORD_LOOKUP_ROWS: u32 = 5_000;
5631 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5632 let min_word_search = buffer_snapshot.clip_point(
5633 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5634 Bias::Left,
5635 );
5636 let max_word_search = buffer_snapshot.clip_point(
5637 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5638 Bias::Right,
5639 );
5640 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5641 ..buffer_snapshot.point_to_offset(max_word_search);
5642
5643 let skip_digits = query
5644 .as_ref()
5645 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5646
5647 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5648 trigger.as_ref().is_none_or(|trigger| {
5649 provider.is_completion_trigger(
5650 &buffer,
5651 position.text_anchor,
5652 trigger,
5653 trigger_in_words,
5654 completions_source.is_some(),
5655 cx,
5656 )
5657 })
5658 });
5659
5660 let provider_responses = if let Some(provider) = &provider
5661 && load_provider_completions
5662 {
5663 let trigger_character =
5664 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5665 let completion_context = CompletionContext {
5666 trigger_kind: match &trigger_character {
5667 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5668 None => CompletionTriggerKind::INVOKED,
5669 },
5670 trigger_character,
5671 };
5672
5673 provider.completions(
5674 buffer_excerpt_id,
5675 &buffer,
5676 buffer_position,
5677 completion_context,
5678 window,
5679 cx,
5680 )
5681 } else {
5682 Task::ready(Ok(Vec::new()))
5683 };
5684
5685 let load_word_completions = if !self.word_completions_enabled {
5686 false
5687 } else if requested_source
5688 == Some(CompletionsMenuSource::Words {
5689 ignore_threshold: true,
5690 })
5691 {
5692 true
5693 } else {
5694 load_provider_completions
5695 && completion_settings.words != WordsCompletionMode::Disabled
5696 && (ignore_word_threshold || {
5697 let words_min_length = completion_settings.words_min_length;
5698 // check whether word has at least `words_min_length` characters
5699 let query_chars = query.iter().flat_map(|q| q.chars());
5700 query_chars.take(words_min_length).count() == words_min_length
5701 })
5702 };
5703
5704 let mut words = if load_word_completions {
5705 cx.background_spawn({
5706 let buffer_snapshot = buffer_snapshot.clone();
5707 async move {
5708 buffer_snapshot.words_in_range(WordsQuery {
5709 fuzzy_contents: None,
5710 range: word_search_range,
5711 skip_digits,
5712 })
5713 }
5714 })
5715 } else {
5716 Task::ready(BTreeMap::default())
5717 };
5718
5719 let snippets = if let Some(provider) = &provider
5720 && provider.show_snippets()
5721 && let Some(project) = self.project()
5722 {
5723 let char_classifier = buffer_snapshot
5724 .char_classifier_at(buffer_position)
5725 .scope_context(Some(CharScopeContext::Completion));
5726 project.update(cx, |project, cx| {
5727 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5728 })
5729 } else {
5730 Task::ready(Ok(CompletionResponse {
5731 completions: Vec::new(),
5732 display_options: Default::default(),
5733 is_incomplete: false,
5734 }))
5735 };
5736
5737 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5738
5739 let id = post_inc(&mut self.next_completion_id);
5740 let task = cx.spawn_in(window, async move |editor, cx| {
5741 let Ok(()) = editor.update(cx, |this, _| {
5742 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5743 }) else {
5744 return;
5745 };
5746
5747 // TODO: Ideally completions from different sources would be selectively re-queried, so
5748 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5749 let mut completions = Vec::new();
5750 let mut is_incomplete = false;
5751 let mut display_options: Option<CompletionDisplayOptions> = None;
5752 if let Some(provider_responses) = provider_responses.await.log_err()
5753 && !provider_responses.is_empty()
5754 {
5755 for response in provider_responses {
5756 completions.extend(response.completions);
5757 is_incomplete = is_incomplete || response.is_incomplete;
5758 match display_options.as_mut() {
5759 None => {
5760 display_options = Some(response.display_options);
5761 }
5762 Some(options) => options.merge(&response.display_options),
5763 }
5764 }
5765 if completion_settings.words == WordsCompletionMode::Fallback {
5766 words = Task::ready(BTreeMap::default());
5767 }
5768 }
5769 let display_options = display_options.unwrap_or_default();
5770
5771 let mut words = words.await;
5772 if let Some(word_to_exclude) = &word_to_exclude {
5773 words.remove(word_to_exclude);
5774 }
5775 for lsp_completion in &completions {
5776 words.remove(&lsp_completion.new_text);
5777 }
5778 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5779 replace_range: word_replace_range.clone(),
5780 new_text: word.clone(),
5781 label: CodeLabel::plain(word, None),
5782 match_start: None,
5783 snippet_deduplication_key: None,
5784 icon_path: None,
5785 documentation: None,
5786 source: CompletionSource::BufferWord {
5787 word_range,
5788 resolved: false,
5789 },
5790 insert_text_mode: Some(InsertTextMode::AS_IS),
5791 confirm: None,
5792 }));
5793
5794 completions.extend(
5795 snippets
5796 .await
5797 .into_iter()
5798 .flat_map(|response| response.completions),
5799 );
5800
5801 let menu = if completions.is_empty() {
5802 None
5803 } else {
5804 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5805 let languages = editor
5806 .workspace
5807 .as_ref()
5808 .and_then(|(workspace, _)| workspace.upgrade())
5809 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5810 let menu = CompletionsMenu::new(
5811 id,
5812 requested_source.unwrap_or(if load_provider_completions {
5813 CompletionsMenuSource::Normal
5814 } else {
5815 CompletionsMenuSource::SnippetsOnly
5816 }),
5817 sort_completions,
5818 show_completion_documentation,
5819 position,
5820 query.clone(),
5821 is_incomplete,
5822 buffer.clone(),
5823 completions.into(),
5824 display_options,
5825 snippet_sort_order,
5826 languages,
5827 language,
5828 cx,
5829 );
5830
5831 let query = if filter_completions { query } else { None };
5832 let matches_task = menu.do_async_filtering(
5833 query.unwrap_or_default(),
5834 buffer_position,
5835 &buffer,
5836 cx,
5837 );
5838 (menu, matches_task)
5839 }) else {
5840 return;
5841 };
5842
5843 let matches = matches_task.await;
5844
5845 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5846 // Newer menu already set, so exit.
5847 if let Some(CodeContextMenu::Completions(prev_menu)) =
5848 editor.context_menu.borrow().as_ref()
5849 && prev_menu.id > id
5850 {
5851 return;
5852 };
5853
5854 // Only valid to take prev_menu because either the new menu is immediately set
5855 // below, or the menu is hidden.
5856 if let Some(CodeContextMenu::Completions(prev_menu)) =
5857 editor.context_menu.borrow_mut().take()
5858 {
5859 let position_matches =
5860 if prev_menu.initial_position == menu.initial_position {
5861 true
5862 } else {
5863 let snapshot = editor.buffer.read(cx).read(cx);
5864 prev_menu.initial_position.to_offset(&snapshot)
5865 == menu.initial_position.to_offset(&snapshot)
5866 };
5867 if position_matches {
5868 // Preserve markdown cache before `set_filter_results` because it will
5869 // try to populate the documentation cache.
5870 menu.preserve_markdown_cache(prev_menu);
5871 }
5872 };
5873
5874 menu.set_filter_results(matches, provider, window, cx);
5875 }) else {
5876 return;
5877 };
5878
5879 menu.visible().then_some(menu)
5880 };
5881
5882 editor
5883 .update_in(cx, |editor, window, cx| {
5884 if editor.focus_handle.is_focused(window)
5885 && let Some(menu) = menu
5886 {
5887 *editor.context_menu.borrow_mut() =
5888 Some(CodeContextMenu::Completions(menu));
5889
5890 crate::hover_popover::hide_hover(editor, cx);
5891 if editor.show_edit_predictions_in_menu() {
5892 editor.update_visible_edit_prediction(window, cx);
5893 } else {
5894 editor.discard_edit_prediction(false, cx);
5895 }
5896
5897 cx.notify();
5898 return;
5899 }
5900
5901 if editor.completion_tasks.len() <= 1 {
5902 // If there are no more completion tasks and the last menu was empty, we should hide it.
5903 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5904 // If it was already hidden and we don't show edit predictions in the menu,
5905 // we should also show the edit prediction when available.
5906 if was_hidden && editor.show_edit_predictions_in_menu() {
5907 editor.update_visible_edit_prediction(window, cx);
5908 }
5909 }
5910 })
5911 .ok();
5912 });
5913
5914 self.completion_tasks.push((id, task));
5915 }
5916
5917 #[cfg(feature = "test-support")]
5918 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5919 let menu = self.context_menu.borrow();
5920 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5921 let completions = menu.completions.borrow();
5922 Some(completions.to_vec())
5923 } else {
5924 None
5925 }
5926 }
5927
5928 pub fn with_completions_menu_matching_id<R>(
5929 &self,
5930 id: CompletionId,
5931 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5932 ) -> R {
5933 let mut context_menu = self.context_menu.borrow_mut();
5934 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5935 return f(None);
5936 };
5937 if completions_menu.id != id {
5938 return f(None);
5939 }
5940 f(Some(completions_menu))
5941 }
5942
5943 pub fn confirm_completion(
5944 &mut self,
5945 action: &ConfirmCompletion,
5946 window: &mut Window,
5947 cx: &mut Context<Self>,
5948 ) -> Option<Task<Result<()>>> {
5949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5950 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5951 }
5952
5953 pub fn confirm_completion_insert(
5954 &mut self,
5955 _: &ConfirmCompletionInsert,
5956 window: &mut Window,
5957 cx: &mut Context<Self>,
5958 ) -> Option<Task<Result<()>>> {
5959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5960 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5961 }
5962
5963 pub fn confirm_completion_replace(
5964 &mut self,
5965 _: &ConfirmCompletionReplace,
5966 window: &mut Window,
5967 cx: &mut Context<Self>,
5968 ) -> Option<Task<Result<()>>> {
5969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5970 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5971 }
5972
5973 pub fn compose_completion(
5974 &mut self,
5975 action: &ComposeCompletion,
5976 window: &mut Window,
5977 cx: &mut Context<Self>,
5978 ) -> Option<Task<Result<()>>> {
5979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5980 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5981 }
5982
5983 fn do_completion(
5984 &mut self,
5985 item_ix: Option<usize>,
5986 intent: CompletionIntent,
5987 window: &mut Window,
5988 cx: &mut Context<Editor>,
5989 ) -> Option<Task<Result<()>>> {
5990 use language::ToOffset as _;
5991
5992 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5993 else {
5994 return None;
5995 };
5996
5997 let candidate_id = {
5998 let entries = completions_menu.entries.borrow();
5999 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6000 if self.show_edit_predictions_in_menu() {
6001 self.discard_edit_prediction(true, cx);
6002 }
6003 mat.candidate_id
6004 };
6005
6006 let completion = completions_menu
6007 .completions
6008 .borrow()
6009 .get(candidate_id)?
6010 .clone();
6011 cx.stop_propagation();
6012
6013 let buffer_handle = completions_menu.buffer.clone();
6014
6015 let CompletionEdit {
6016 new_text,
6017 snippet,
6018 replace_range,
6019 } = process_completion_for_edit(
6020 &completion,
6021 intent,
6022 &buffer_handle,
6023 &completions_menu.initial_position.text_anchor,
6024 cx,
6025 );
6026
6027 let buffer = buffer_handle.read(cx);
6028 let snapshot = self.buffer.read(cx).snapshot(cx);
6029 let newest_anchor = self.selections.newest_anchor();
6030 let replace_range_multibuffer = {
6031 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6032 excerpt.map_range_from_buffer(replace_range.clone())
6033 };
6034 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6035 return None;
6036 }
6037
6038 let old_text = buffer
6039 .text_for_range(replace_range.clone())
6040 .collect::<String>();
6041 let lookbehind = newest_anchor
6042 .start
6043 .text_anchor
6044 .to_offset(buffer)
6045 .saturating_sub(replace_range.start.0);
6046 let lookahead = replace_range
6047 .end
6048 .0
6049 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6050 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6051 let suffix = &old_text[lookbehind.min(old_text.len())..];
6052
6053 let selections = self
6054 .selections
6055 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6056 let mut ranges = Vec::new();
6057 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6058
6059 for selection in &selections {
6060 let range = if selection.id == newest_anchor.id {
6061 replace_range_multibuffer.clone()
6062 } else {
6063 let mut range = selection.range();
6064
6065 // if prefix is present, don't duplicate it
6066 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6067 range.start = range.start.saturating_sub_usize(lookbehind);
6068
6069 // if suffix is also present, mimic the newest cursor and replace it
6070 if selection.id != newest_anchor.id
6071 && snapshot.contains_str_at(range.end, suffix)
6072 {
6073 range.end += lookahead;
6074 }
6075 }
6076 range
6077 };
6078
6079 ranges.push(range.clone());
6080
6081 if !self.linked_edit_ranges.is_empty() {
6082 let start_anchor = snapshot.anchor_before(range.start);
6083 let end_anchor = snapshot.anchor_after(range.end);
6084 if let Some(ranges) = self
6085 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6086 {
6087 for (buffer, edits) in ranges {
6088 linked_edits
6089 .entry(buffer.clone())
6090 .or_default()
6091 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6092 }
6093 }
6094 }
6095 }
6096
6097 let common_prefix_len = old_text
6098 .chars()
6099 .zip(new_text.chars())
6100 .take_while(|(a, b)| a == b)
6101 .map(|(a, _)| a.len_utf8())
6102 .sum::<usize>();
6103
6104 cx.emit(EditorEvent::InputHandled {
6105 utf16_range_to_replace: None,
6106 text: new_text[common_prefix_len..].into(),
6107 });
6108
6109 self.transact(window, cx, |editor, window, cx| {
6110 if let Some(mut snippet) = snippet {
6111 snippet.text = new_text.to_string();
6112 editor
6113 .insert_snippet(&ranges, snippet, window, cx)
6114 .log_err();
6115 } else {
6116 editor.buffer.update(cx, |multi_buffer, cx| {
6117 let auto_indent = match completion.insert_text_mode {
6118 Some(InsertTextMode::AS_IS) => None,
6119 _ => editor.autoindent_mode.clone(),
6120 };
6121 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6122 multi_buffer.edit(edits, auto_indent, cx);
6123 });
6124 }
6125 for (buffer, edits) in linked_edits {
6126 buffer.update(cx, |buffer, cx| {
6127 let snapshot = buffer.snapshot();
6128 let edits = edits
6129 .into_iter()
6130 .map(|(range, text)| {
6131 use text::ToPoint as TP;
6132 let end_point = TP::to_point(&range.end, &snapshot);
6133 let start_point = TP::to_point(&range.start, &snapshot);
6134 (start_point..end_point, text)
6135 })
6136 .sorted_by_key(|(range, _)| range.start);
6137 buffer.edit(edits, None, cx);
6138 })
6139 }
6140
6141 editor.refresh_edit_prediction(true, false, window, cx);
6142 });
6143 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6144
6145 let show_new_completions_on_confirm = completion
6146 .confirm
6147 .as_ref()
6148 .is_some_and(|confirm| confirm(intent, window, cx));
6149 if show_new_completions_on_confirm {
6150 self.open_or_update_completions_menu(None, None, false, window, cx);
6151 }
6152
6153 let provider = self.completion_provider.as_ref()?;
6154 drop(completion);
6155 let apply_edits = provider.apply_additional_edits_for_completion(
6156 buffer_handle,
6157 completions_menu.completions.clone(),
6158 candidate_id,
6159 true,
6160 cx,
6161 );
6162
6163 let editor_settings = EditorSettings::get_global(cx);
6164 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6165 // After the code completion is finished, users often want to know what signatures are needed.
6166 // so we should automatically call signature_help
6167 self.show_signature_help(&ShowSignatureHelp, window, cx);
6168 }
6169
6170 Some(cx.foreground_executor().spawn(async move {
6171 apply_edits.await?;
6172 Ok(())
6173 }))
6174 }
6175
6176 pub fn toggle_code_actions(
6177 &mut self,
6178 action: &ToggleCodeActions,
6179 window: &mut Window,
6180 cx: &mut Context<Self>,
6181 ) {
6182 let quick_launch = action.quick_launch;
6183 let mut context_menu = self.context_menu.borrow_mut();
6184 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6185 if code_actions.deployed_from == action.deployed_from {
6186 // Toggle if we're selecting the same one
6187 *context_menu = None;
6188 cx.notify();
6189 return;
6190 } else {
6191 // Otherwise, clear it and start a new one
6192 *context_menu = None;
6193 cx.notify();
6194 }
6195 }
6196 drop(context_menu);
6197 let snapshot = self.snapshot(window, cx);
6198 let deployed_from = action.deployed_from.clone();
6199 let action = action.clone();
6200 self.completion_tasks.clear();
6201 self.discard_edit_prediction(false, cx);
6202
6203 let multibuffer_point = match &action.deployed_from {
6204 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6205 DisplayPoint::new(*row, 0).to_point(&snapshot)
6206 }
6207 _ => self
6208 .selections
6209 .newest::<Point>(&snapshot.display_snapshot)
6210 .head(),
6211 };
6212 let Some((buffer, buffer_row)) = snapshot
6213 .buffer_snapshot()
6214 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6215 .and_then(|(buffer_snapshot, range)| {
6216 self.buffer()
6217 .read(cx)
6218 .buffer(buffer_snapshot.remote_id())
6219 .map(|buffer| (buffer, range.start.row))
6220 })
6221 else {
6222 return;
6223 };
6224 let buffer_id = buffer.read(cx).remote_id();
6225 let tasks = self
6226 .tasks
6227 .get(&(buffer_id, buffer_row))
6228 .map(|t| Arc::new(t.to_owned()));
6229
6230 if !self.focus_handle.is_focused(window) {
6231 return;
6232 }
6233 let project = self.project.clone();
6234
6235 let code_actions_task = match deployed_from {
6236 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6237 _ => self.code_actions(buffer_row, window, cx),
6238 };
6239
6240 let runnable_task = match deployed_from {
6241 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6242 _ => {
6243 let mut task_context_task = Task::ready(None);
6244 if let Some(tasks) = &tasks
6245 && let Some(project) = project
6246 {
6247 task_context_task =
6248 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6249 }
6250
6251 cx.spawn_in(window, {
6252 let buffer = buffer.clone();
6253 async move |editor, cx| {
6254 let task_context = task_context_task.await;
6255
6256 let resolved_tasks =
6257 tasks
6258 .zip(task_context.clone())
6259 .map(|(tasks, task_context)| ResolvedTasks {
6260 templates: tasks.resolve(&task_context).collect(),
6261 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6262 multibuffer_point.row,
6263 tasks.column,
6264 )),
6265 });
6266 let debug_scenarios = editor
6267 .update(cx, |editor, cx| {
6268 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6269 })?
6270 .await;
6271 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6272 }
6273 })
6274 }
6275 };
6276
6277 cx.spawn_in(window, async move |editor, cx| {
6278 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6279 let code_actions = code_actions_task.await;
6280 let spawn_straight_away = quick_launch
6281 && resolved_tasks
6282 .as_ref()
6283 .is_some_and(|tasks| tasks.templates.len() == 1)
6284 && code_actions
6285 .as_ref()
6286 .is_none_or(|actions| actions.is_empty())
6287 && debug_scenarios.is_empty();
6288
6289 editor.update_in(cx, |editor, window, cx| {
6290 crate::hover_popover::hide_hover(editor, cx);
6291 let actions = CodeActionContents::new(
6292 resolved_tasks,
6293 code_actions,
6294 debug_scenarios,
6295 task_context.unwrap_or_default(),
6296 );
6297
6298 // Don't show the menu if there are no actions available
6299 if actions.is_empty() {
6300 cx.notify();
6301 return Task::ready(Ok(()));
6302 }
6303
6304 *editor.context_menu.borrow_mut() =
6305 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6306 buffer,
6307 actions,
6308 selected_item: Default::default(),
6309 scroll_handle: UniformListScrollHandle::default(),
6310 deployed_from,
6311 }));
6312 cx.notify();
6313 if spawn_straight_away
6314 && let Some(task) = editor.confirm_code_action(
6315 &ConfirmCodeAction { item_ix: Some(0) },
6316 window,
6317 cx,
6318 )
6319 {
6320 return task;
6321 }
6322
6323 Task::ready(Ok(()))
6324 })
6325 })
6326 .detach_and_log_err(cx);
6327 }
6328
6329 fn debug_scenarios(
6330 &mut self,
6331 resolved_tasks: &Option<ResolvedTasks>,
6332 buffer: &Entity<Buffer>,
6333 cx: &mut App,
6334 ) -> Task<Vec<task::DebugScenario>> {
6335 maybe!({
6336 let project = self.project()?;
6337 let dap_store = project.read(cx).dap_store();
6338 let mut scenarios = vec![];
6339 let resolved_tasks = resolved_tasks.as_ref()?;
6340 let buffer = buffer.read(cx);
6341 let language = buffer.language()?;
6342 let file = buffer.file();
6343 let debug_adapter = language_settings(language.name().into(), file, cx)
6344 .debuggers
6345 .first()
6346 .map(SharedString::from)
6347 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6348
6349 dap_store.update(cx, |dap_store, cx| {
6350 for (_, task) in &resolved_tasks.templates {
6351 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6352 task.original_task().clone(),
6353 debug_adapter.clone().into(),
6354 task.display_label().to_owned().into(),
6355 cx,
6356 );
6357 scenarios.push(maybe_scenario);
6358 }
6359 });
6360 Some(cx.background_spawn(async move {
6361 futures::future::join_all(scenarios)
6362 .await
6363 .into_iter()
6364 .flatten()
6365 .collect::<Vec<_>>()
6366 }))
6367 })
6368 .unwrap_or_else(|| Task::ready(vec![]))
6369 }
6370
6371 fn code_actions(
6372 &mut self,
6373 buffer_row: u32,
6374 window: &mut Window,
6375 cx: &mut Context<Self>,
6376 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6377 let mut task = self.code_actions_task.take();
6378 cx.spawn_in(window, async move |editor, cx| {
6379 while let Some(prev_task) = task {
6380 prev_task.await.log_err();
6381 task = editor
6382 .update(cx, |this, _| this.code_actions_task.take())
6383 .ok()?;
6384 }
6385
6386 editor
6387 .update(cx, |editor, cx| {
6388 editor
6389 .available_code_actions
6390 .clone()
6391 .and_then(|(location, code_actions)| {
6392 let snapshot = location.buffer.read(cx).snapshot();
6393 let point_range = location.range.to_point(&snapshot);
6394 let point_range = point_range.start.row..=point_range.end.row;
6395 if point_range.contains(&buffer_row) {
6396 Some(code_actions)
6397 } else {
6398 None
6399 }
6400 })
6401 })
6402 .ok()
6403 .flatten()
6404 })
6405 }
6406
6407 pub fn confirm_code_action(
6408 &mut self,
6409 action: &ConfirmCodeAction,
6410 window: &mut Window,
6411 cx: &mut Context<Self>,
6412 ) -> Option<Task<Result<()>>> {
6413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6414
6415 let actions_menu =
6416 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6417 menu
6418 } else {
6419 return None;
6420 };
6421
6422 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6423 let action = actions_menu.actions.get(action_ix)?;
6424 let title = action.label();
6425 let buffer = actions_menu.buffer;
6426 let workspace = self.workspace()?;
6427
6428 match action {
6429 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6430 workspace.update(cx, |workspace, cx| {
6431 workspace.schedule_resolved_task(
6432 task_source_kind,
6433 resolved_task,
6434 false,
6435 window,
6436 cx,
6437 );
6438
6439 Some(Task::ready(Ok(())))
6440 })
6441 }
6442 CodeActionsItem::CodeAction {
6443 excerpt_id,
6444 action,
6445 provider,
6446 } => {
6447 let apply_code_action =
6448 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6449 let workspace = workspace.downgrade();
6450 Some(cx.spawn_in(window, async move |editor, cx| {
6451 let project_transaction = apply_code_action.await?;
6452 Self::open_project_transaction(
6453 &editor,
6454 workspace,
6455 project_transaction,
6456 title,
6457 cx,
6458 )
6459 .await
6460 }))
6461 }
6462 CodeActionsItem::DebugScenario(scenario) => {
6463 let context = actions_menu.actions.context;
6464
6465 workspace.update(cx, |workspace, cx| {
6466 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6467 workspace.start_debug_session(
6468 scenario,
6469 context,
6470 Some(buffer),
6471 None,
6472 window,
6473 cx,
6474 );
6475 });
6476 Some(Task::ready(Ok(())))
6477 }
6478 }
6479 }
6480
6481 pub async fn open_project_transaction(
6482 editor: &WeakEntity<Editor>,
6483 workspace: WeakEntity<Workspace>,
6484 transaction: ProjectTransaction,
6485 title: String,
6486 cx: &mut AsyncWindowContext,
6487 ) -> Result<()> {
6488 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6489 cx.update(|_, cx| {
6490 entries.sort_unstable_by_key(|(buffer, _)| {
6491 buffer.read(cx).file().map(|f| f.path().clone())
6492 });
6493 })?;
6494 if entries.is_empty() {
6495 return Ok(());
6496 }
6497
6498 // If the project transaction's edits are all contained within this editor, then
6499 // avoid opening a new editor to display them.
6500
6501 if let [(buffer, transaction)] = &*entries {
6502 let excerpt = editor.update(cx, |editor, cx| {
6503 editor
6504 .buffer()
6505 .read(cx)
6506 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6507 })?;
6508 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6509 && excerpted_buffer == *buffer
6510 {
6511 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6512 let excerpt_range = excerpt_range.to_offset(buffer);
6513 buffer
6514 .edited_ranges_for_transaction::<usize>(transaction)
6515 .all(|range| {
6516 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6517 })
6518 })?;
6519
6520 if all_edits_within_excerpt {
6521 return Ok(());
6522 }
6523 }
6524 }
6525
6526 let mut ranges_to_highlight = Vec::new();
6527 let excerpt_buffer = cx.new(|cx| {
6528 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6529 for (buffer_handle, transaction) in &entries {
6530 let edited_ranges = buffer_handle
6531 .read(cx)
6532 .edited_ranges_for_transaction::<Point>(transaction)
6533 .collect::<Vec<_>>();
6534 let (ranges, _) = multibuffer.set_excerpts_for_path(
6535 PathKey::for_buffer(buffer_handle, cx),
6536 buffer_handle.clone(),
6537 edited_ranges,
6538 multibuffer_context_lines(cx),
6539 cx,
6540 );
6541
6542 ranges_to_highlight.extend(ranges);
6543 }
6544 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6545 multibuffer
6546 })?;
6547
6548 workspace.update_in(cx, |workspace, window, cx| {
6549 let project = workspace.project().clone();
6550 let editor =
6551 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6552 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6553 editor.update(cx, |editor, cx| {
6554 editor.highlight_background::<Self>(
6555 &ranges_to_highlight,
6556 |theme| theme.colors().editor_highlighted_line_background,
6557 cx,
6558 );
6559 });
6560 })?;
6561
6562 Ok(())
6563 }
6564
6565 pub fn clear_code_action_providers(&mut self) {
6566 self.code_action_providers.clear();
6567 self.available_code_actions.take();
6568 }
6569
6570 pub fn add_code_action_provider(
6571 &mut self,
6572 provider: Rc<dyn CodeActionProvider>,
6573 window: &mut Window,
6574 cx: &mut Context<Self>,
6575 ) {
6576 if self
6577 .code_action_providers
6578 .iter()
6579 .any(|existing_provider| existing_provider.id() == provider.id())
6580 {
6581 return;
6582 }
6583
6584 self.code_action_providers.push(provider);
6585 self.refresh_code_actions(window, cx);
6586 }
6587
6588 pub fn remove_code_action_provider(
6589 &mut self,
6590 id: Arc<str>,
6591 window: &mut Window,
6592 cx: &mut Context<Self>,
6593 ) {
6594 self.code_action_providers
6595 .retain(|provider| provider.id() != id);
6596 self.refresh_code_actions(window, cx);
6597 }
6598
6599 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6600 !self.code_action_providers.is_empty()
6601 && EditorSettings::get_global(cx).toolbar.code_actions
6602 }
6603
6604 pub fn has_available_code_actions(&self) -> bool {
6605 self.available_code_actions
6606 .as_ref()
6607 .is_some_and(|(_, actions)| !actions.is_empty())
6608 }
6609
6610 fn render_inline_code_actions(
6611 &self,
6612 icon_size: ui::IconSize,
6613 display_row: DisplayRow,
6614 is_active: bool,
6615 cx: &mut Context<Self>,
6616 ) -> AnyElement {
6617 let show_tooltip = !self.context_menu_visible();
6618 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6619 .icon_size(icon_size)
6620 .shape(ui::IconButtonShape::Square)
6621 .icon_color(ui::Color::Hidden)
6622 .toggle_state(is_active)
6623 .when(show_tooltip, |this| {
6624 this.tooltip({
6625 let focus_handle = self.focus_handle.clone();
6626 move |_window, cx| {
6627 Tooltip::for_action_in(
6628 "Toggle Code Actions",
6629 &ToggleCodeActions {
6630 deployed_from: None,
6631 quick_launch: false,
6632 },
6633 &focus_handle,
6634 cx,
6635 )
6636 }
6637 })
6638 })
6639 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6640 window.focus(&editor.focus_handle(cx));
6641 editor.toggle_code_actions(
6642 &crate::actions::ToggleCodeActions {
6643 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6644 display_row,
6645 )),
6646 quick_launch: false,
6647 },
6648 window,
6649 cx,
6650 );
6651 }))
6652 .into_any_element()
6653 }
6654
6655 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6656 &self.context_menu
6657 }
6658
6659 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6660 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6661 cx.background_executor()
6662 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6663 .await;
6664
6665 let (start_buffer, start, _, end, newest_selection) = this
6666 .update(cx, |this, cx| {
6667 let newest_selection = this.selections.newest_anchor().clone();
6668 if newest_selection.head().diff_base_anchor.is_some() {
6669 return None;
6670 }
6671 let display_snapshot = this.display_snapshot(cx);
6672 let newest_selection_adjusted =
6673 this.selections.newest_adjusted(&display_snapshot);
6674 let buffer = this.buffer.read(cx);
6675
6676 let (start_buffer, start) =
6677 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6678 let (end_buffer, end) =
6679 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6680
6681 Some((start_buffer, start, end_buffer, end, newest_selection))
6682 })?
6683 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6684 .context(
6685 "Expected selection to lie in a single buffer when refreshing code actions",
6686 )?;
6687 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6688 let providers = this.code_action_providers.clone();
6689 let tasks = this
6690 .code_action_providers
6691 .iter()
6692 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6693 .collect::<Vec<_>>();
6694 (providers, tasks)
6695 })?;
6696
6697 let mut actions = Vec::new();
6698 for (provider, provider_actions) in
6699 providers.into_iter().zip(future::join_all(tasks).await)
6700 {
6701 if let Some(provider_actions) = provider_actions.log_err() {
6702 actions.extend(provider_actions.into_iter().map(|action| {
6703 AvailableCodeAction {
6704 excerpt_id: newest_selection.start.excerpt_id,
6705 action,
6706 provider: provider.clone(),
6707 }
6708 }));
6709 }
6710 }
6711
6712 this.update(cx, |this, cx| {
6713 this.available_code_actions = if actions.is_empty() {
6714 None
6715 } else {
6716 Some((
6717 Location {
6718 buffer: start_buffer,
6719 range: start..end,
6720 },
6721 actions.into(),
6722 ))
6723 };
6724 cx.notify();
6725 })
6726 }));
6727 }
6728
6729 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6730 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6731 self.show_git_blame_inline = false;
6732
6733 self.show_git_blame_inline_delay_task =
6734 Some(cx.spawn_in(window, async move |this, cx| {
6735 cx.background_executor().timer(delay).await;
6736
6737 this.update(cx, |this, cx| {
6738 this.show_git_blame_inline = true;
6739 cx.notify();
6740 })
6741 .log_err();
6742 }));
6743 }
6744 }
6745
6746 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6747 let snapshot = self.snapshot(window, cx);
6748 let cursor = self
6749 .selections
6750 .newest::<Point>(&snapshot.display_snapshot)
6751 .head();
6752 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6753 else {
6754 return;
6755 };
6756
6757 let Some(blame) = self.blame.as_ref() else {
6758 return;
6759 };
6760
6761 let row_info = RowInfo {
6762 buffer_id: Some(buffer.remote_id()),
6763 buffer_row: Some(point.row),
6764 ..Default::default()
6765 };
6766 let Some((buffer, blame_entry)) = blame
6767 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6768 .flatten()
6769 else {
6770 return;
6771 };
6772
6773 let anchor = self.selections.newest_anchor().head();
6774 let position = self.to_pixel_point(anchor, &snapshot, window);
6775 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6776 self.show_blame_popover(
6777 buffer,
6778 &blame_entry,
6779 position + last_bounds.origin,
6780 true,
6781 cx,
6782 );
6783 };
6784 }
6785
6786 fn show_blame_popover(
6787 &mut self,
6788 buffer: BufferId,
6789 blame_entry: &BlameEntry,
6790 position: gpui::Point<Pixels>,
6791 ignore_timeout: bool,
6792 cx: &mut Context<Self>,
6793 ) {
6794 if let Some(state) = &mut self.inline_blame_popover {
6795 state.hide_task.take();
6796 } else {
6797 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6798 let blame_entry = blame_entry.clone();
6799 let show_task = cx.spawn(async move |editor, cx| {
6800 if !ignore_timeout {
6801 cx.background_executor()
6802 .timer(std::time::Duration::from_millis(blame_popover_delay))
6803 .await;
6804 }
6805 editor
6806 .update(cx, |editor, cx| {
6807 editor.inline_blame_popover_show_task.take();
6808 let Some(blame) = editor.blame.as_ref() else {
6809 return;
6810 };
6811 let blame = blame.read(cx);
6812 let details = blame.details_for_entry(buffer, &blame_entry);
6813 let markdown = cx.new(|cx| {
6814 Markdown::new(
6815 details
6816 .as_ref()
6817 .map(|message| message.message.clone())
6818 .unwrap_or_default(),
6819 None,
6820 None,
6821 cx,
6822 )
6823 });
6824 editor.inline_blame_popover = Some(InlineBlamePopover {
6825 position,
6826 hide_task: None,
6827 popover_bounds: None,
6828 popover_state: InlineBlamePopoverState {
6829 scroll_handle: ScrollHandle::new(),
6830 commit_message: details,
6831 markdown,
6832 },
6833 keyboard_grace: ignore_timeout,
6834 });
6835 cx.notify();
6836 })
6837 .ok();
6838 });
6839 self.inline_blame_popover_show_task = Some(show_task);
6840 }
6841 }
6842
6843 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6844 self.inline_blame_popover_show_task.take();
6845 if let Some(state) = &mut self.inline_blame_popover {
6846 let hide_task = cx.spawn(async move |editor, cx| {
6847 if !ignore_timeout {
6848 cx.background_executor()
6849 .timer(std::time::Duration::from_millis(100))
6850 .await;
6851 }
6852 editor
6853 .update(cx, |editor, cx| {
6854 editor.inline_blame_popover.take();
6855 cx.notify();
6856 })
6857 .ok();
6858 });
6859 state.hide_task = Some(hide_task);
6860 true
6861 } else {
6862 false
6863 }
6864 }
6865
6866 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6867 if self.pending_rename.is_some() {
6868 return None;
6869 }
6870
6871 let provider = self.semantics_provider.clone()?;
6872 let buffer = self.buffer.read(cx);
6873 let newest_selection = self.selections.newest_anchor().clone();
6874 let cursor_position = newest_selection.head();
6875 let (cursor_buffer, cursor_buffer_position) =
6876 buffer.text_anchor_for_position(cursor_position, cx)?;
6877 let (tail_buffer, tail_buffer_position) =
6878 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6879 if cursor_buffer != tail_buffer {
6880 return None;
6881 }
6882
6883 let snapshot = cursor_buffer.read(cx).snapshot();
6884 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6885 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6886 if start_word_range != end_word_range {
6887 self.document_highlights_task.take();
6888 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6889 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6890 return None;
6891 }
6892
6893 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6894 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6895 cx.background_executor()
6896 .timer(Duration::from_millis(debounce))
6897 .await;
6898
6899 let highlights = if let Some(highlights) = cx
6900 .update(|cx| {
6901 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6902 })
6903 .ok()
6904 .flatten()
6905 {
6906 highlights.await.log_err()
6907 } else {
6908 None
6909 };
6910
6911 if let Some(highlights) = highlights {
6912 this.update(cx, |this, cx| {
6913 if this.pending_rename.is_some() {
6914 return;
6915 }
6916
6917 let buffer = this.buffer.read(cx);
6918 if buffer
6919 .text_anchor_for_position(cursor_position, cx)
6920 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6921 {
6922 return;
6923 }
6924
6925 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6926 let mut write_ranges = Vec::new();
6927 let mut read_ranges = Vec::new();
6928 for highlight in highlights {
6929 let buffer_id = cursor_buffer.read(cx).remote_id();
6930 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6931 {
6932 let start = highlight
6933 .range
6934 .start
6935 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6936 let end = highlight
6937 .range
6938 .end
6939 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6940 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6941 continue;
6942 }
6943
6944 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
6945 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6946 write_ranges.push(range);
6947 } else {
6948 read_ranges.push(range);
6949 }
6950 }
6951 }
6952
6953 this.highlight_background::<DocumentHighlightRead>(
6954 &read_ranges,
6955 |theme| theme.colors().editor_document_highlight_read_background,
6956 cx,
6957 );
6958 this.highlight_background::<DocumentHighlightWrite>(
6959 &write_ranges,
6960 |theme| theme.colors().editor_document_highlight_write_background,
6961 cx,
6962 );
6963 cx.notify();
6964 })
6965 .log_err();
6966 }
6967 }));
6968 None
6969 }
6970
6971 fn prepare_highlight_query_from_selection(
6972 &mut self,
6973 window: &Window,
6974 cx: &mut Context<Editor>,
6975 ) -> Option<(String, Range<Anchor>)> {
6976 if matches!(self.mode, EditorMode::SingleLine) {
6977 return None;
6978 }
6979 if !EditorSettings::get_global(cx).selection_highlight {
6980 return None;
6981 }
6982 if self.selections.count() != 1 || self.selections.line_mode() {
6983 return None;
6984 }
6985 let snapshot = self.snapshot(window, cx);
6986 let selection = self.selections.newest::<Point>(&snapshot);
6987 // If the selection spans multiple rows OR it is empty
6988 if selection.start.row != selection.end.row
6989 || selection.start.column == selection.end.column
6990 {
6991 return None;
6992 }
6993 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6994 let query = snapshot
6995 .buffer_snapshot()
6996 .text_for_range(selection_anchor_range.clone())
6997 .collect::<String>();
6998 if query.trim().is_empty() {
6999 return None;
7000 }
7001 Some((query, selection_anchor_range))
7002 }
7003
7004 fn update_selection_occurrence_highlights(
7005 &mut self,
7006 query_text: String,
7007 query_range: Range<Anchor>,
7008 multi_buffer_range_to_query: Range<Point>,
7009 use_debounce: bool,
7010 window: &mut Window,
7011 cx: &mut Context<Editor>,
7012 ) -> Task<()> {
7013 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7014 cx.spawn_in(window, async move |editor, cx| {
7015 if use_debounce {
7016 cx.background_executor()
7017 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7018 .await;
7019 }
7020 let match_task = cx.background_spawn(async move {
7021 let buffer_ranges = multi_buffer_snapshot
7022 .range_to_buffer_ranges(multi_buffer_range_to_query)
7023 .into_iter()
7024 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7025 let mut match_ranges = Vec::new();
7026 let Ok(regex) = project::search::SearchQuery::text(
7027 query_text.clone(),
7028 false,
7029 false,
7030 false,
7031 Default::default(),
7032 Default::default(),
7033 false,
7034 None,
7035 ) else {
7036 return Vec::default();
7037 };
7038 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7039 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7040 match_ranges.extend(
7041 regex
7042 .search(
7043 buffer_snapshot,
7044 Some(search_range.start.0..search_range.end.0),
7045 )
7046 .await
7047 .into_iter()
7048 .filter_map(|match_range| {
7049 let match_start = buffer_snapshot
7050 .anchor_after(search_range.start + match_range.start);
7051 let match_end = buffer_snapshot
7052 .anchor_before(search_range.start + match_range.end);
7053 let match_anchor_range =
7054 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7055 (match_anchor_range != query_range).then_some(match_anchor_range)
7056 }),
7057 );
7058 }
7059 match_ranges
7060 });
7061 let match_ranges = match_task.await;
7062 editor
7063 .update_in(cx, |editor, _, cx| {
7064 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7065 if !match_ranges.is_empty() {
7066 editor.highlight_background::<SelectedTextHighlight>(
7067 &match_ranges,
7068 |theme| theme.colors().editor_document_highlight_bracket_background,
7069 cx,
7070 )
7071 }
7072 })
7073 .log_err();
7074 })
7075 }
7076
7077 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7078 struct NewlineFold;
7079 let type_id = std::any::TypeId::of::<NewlineFold>();
7080 if !self.mode.is_single_line() {
7081 return;
7082 }
7083 let snapshot = self.snapshot(window, cx);
7084 if snapshot.buffer_snapshot().max_point().row == 0 {
7085 return;
7086 }
7087 let task = cx.background_spawn(async move {
7088 let new_newlines = snapshot
7089 .buffer_chars_at(MultiBufferOffset(0))
7090 .filter_map(|(c, i)| {
7091 if c == '\n' {
7092 Some(
7093 snapshot.buffer_snapshot().anchor_after(i)
7094 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7095 )
7096 } else {
7097 None
7098 }
7099 })
7100 .collect::<Vec<_>>();
7101 let existing_newlines = snapshot
7102 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7103 .filter_map(|fold| {
7104 if fold.placeholder.type_tag == Some(type_id) {
7105 Some(fold.range.start..fold.range.end)
7106 } else {
7107 None
7108 }
7109 })
7110 .collect::<Vec<_>>();
7111
7112 (new_newlines, existing_newlines)
7113 });
7114 self.folding_newlines = cx.spawn(async move |this, cx| {
7115 let (new_newlines, existing_newlines) = task.await;
7116 if new_newlines == existing_newlines {
7117 return;
7118 }
7119 let placeholder = FoldPlaceholder {
7120 render: Arc::new(move |_, _, cx| {
7121 div()
7122 .bg(cx.theme().status().hint_background)
7123 .border_b_1()
7124 .size_full()
7125 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7126 .border_color(cx.theme().status().hint)
7127 .child("\\n")
7128 .into_any()
7129 }),
7130 constrain_width: false,
7131 merge_adjacent: false,
7132 type_tag: Some(type_id),
7133 };
7134 let creases = new_newlines
7135 .into_iter()
7136 .map(|range| Crease::simple(range, placeholder.clone()))
7137 .collect();
7138 this.update(cx, |this, cx| {
7139 this.display_map.update(cx, |display_map, cx| {
7140 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7141 display_map.fold(creases, cx);
7142 });
7143 })
7144 .ok();
7145 });
7146 }
7147
7148 fn refresh_selected_text_highlights(
7149 &mut self,
7150 on_buffer_edit: bool,
7151 window: &mut Window,
7152 cx: &mut Context<Editor>,
7153 ) {
7154 let Some((query_text, query_range)) =
7155 self.prepare_highlight_query_from_selection(window, cx)
7156 else {
7157 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7158 self.quick_selection_highlight_task.take();
7159 self.debounced_selection_highlight_task.take();
7160 return;
7161 };
7162 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7163 if on_buffer_edit
7164 || self
7165 .quick_selection_highlight_task
7166 .as_ref()
7167 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7168 {
7169 let multi_buffer_visible_start = self
7170 .scroll_manager
7171 .anchor()
7172 .anchor
7173 .to_point(&multi_buffer_snapshot);
7174 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7175 multi_buffer_visible_start
7176 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7177 Bias::Left,
7178 );
7179 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7180 self.quick_selection_highlight_task = Some((
7181 query_range.clone(),
7182 self.update_selection_occurrence_highlights(
7183 query_text.clone(),
7184 query_range.clone(),
7185 multi_buffer_visible_range,
7186 false,
7187 window,
7188 cx,
7189 ),
7190 ));
7191 }
7192 if on_buffer_edit
7193 || self
7194 .debounced_selection_highlight_task
7195 .as_ref()
7196 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7197 {
7198 let multi_buffer_start = multi_buffer_snapshot
7199 .anchor_before(MultiBufferOffset(0))
7200 .to_point(&multi_buffer_snapshot);
7201 let multi_buffer_end = multi_buffer_snapshot
7202 .anchor_after(multi_buffer_snapshot.len())
7203 .to_point(&multi_buffer_snapshot);
7204 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7205 self.debounced_selection_highlight_task = Some((
7206 query_range.clone(),
7207 self.update_selection_occurrence_highlights(
7208 query_text,
7209 query_range,
7210 multi_buffer_full_range,
7211 true,
7212 window,
7213 cx,
7214 ),
7215 ));
7216 }
7217 }
7218
7219 pub fn refresh_edit_prediction(
7220 &mut self,
7221 debounce: bool,
7222 user_requested: bool,
7223 window: &mut Window,
7224 cx: &mut Context<Self>,
7225 ) -> Option<()> {
7226 if DisableAiSettings::get_global(cx).disable_ai {
7227 return None;
7228 }
7229
7230 let provider = self.edit_prediction_provider()?;
7231 let cursor = self.selections.newest_anchor().head();
7232 let (buffer, cursor_buffer_position) =
7233 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7234
7235 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7236 self.discard_edit_prediction(false, cx);
7237 return None;
7238 }
7239
7240 self.update_visible_edit_prediction(window, cx);
7241
7242 if !user_requested
7243 && (!self.should_show_edit_predictions()
7244 || !self.is_focused(window)
7245 || buffer.read(cx).is_empty())
7246 {
7247 self.discard_edit_prediction(false, cx);
7248 return None;
7249 }
7250
7251 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7252 Some(())
7253 }
7254
7255 fn show_edit_predictions_in_menu(&self) -> bool {
7256 match self.edit_prediction_settings {
7257 EditPredictionSettings::Disabled => false,
7258 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7259 }
7260 }
7261
7262 pub fn edit_predictions_enabled(&self) -> bool {
7263 match self.edit_prediction_settings {
7264 EditPredictionSettings::Disabled => false,
7265 EditPredictionSettings::Enabled { .. } => true,
7266 }
7267 }
7268
7269 fn edit_prediction_requires_modifier(&self) -> bool {
7270 match self.edit_prediction_settings {
7271 EditPredictionSettings::Disabled => false,
7272 EditPredictionSettings::Enabled {
7273 preview_requires_modifier,
7274 ..
7275 } => preview_requires_modifier,
7276 }
7277 }
7278
7279 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7280 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7281 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7282 self.discard_edit_prediction(false, cx);
7283 } else {
7284 let selection = self.selections.newest_anchor();
7285 let cursor = selection.head();
7286
7287 if let Some((buffer, cursor_buffer_position)) =
7288 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7289 {
7290 self.edit_prediction_settings =
7291 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7292 }
7293 }
7294 }
7295
7296 fn edit_prediction_settings_at_position(
7297 &self,
7298 buffer: &Entity<Buffer>,
7299 buffer_position: language::Anchor,
7300 cx: &App,
7301 ) -> EditPredictionSettings {
7302 if !self.mode.is_full()
7303 || !self.show_edit_predictions_override.unwrap_or(true)
7304 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7305 {
7306 return EditPredictionSettings::Disabled;
7307 }
7308
7309 let buffer = buffer.read(cx);
7310
7311 let file = buffer.file();
7312
7313 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7314 return EditPredictionSettings::Disabled;
7315 };
7316
7317 let by_provider = matches!(
7318 self.menu_edit_predictions_policy,
7319 MenuEditPredictionsPolicy::ByProvider
7320 );
7321
7322 let show_in_menu = by_provider
7323 && self
7324 .edit_prediction_provider
7325 .as_ref()
7326 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7327
7328 let preview_requires_modifier =
7329 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7330
7331 EditPredictionSettings::Enabled {
7332 show_in_menu,
7333 preview_requires_modifier,
7334 }
7335 }
7336
7337 fn should_show_edit_predictions(&self) -> bool {
7338 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7339 }
7340
7341 pub fn edit_prediction_preview_is_active(&self) -> bool {
7342 matches!(
7343 self.edit_prediction_preview,
7344 EditPredictionPreview::Active { .. }
7345 )
7346 }
7347
7348 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7349 let cursor = self.selections.newest_anchor().head();
7350 if let Some((buffer, cursor_position)) =
7351 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7352 {
7353 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7354 } else {
7355 false
7356 }
7357 }
7358
7359 pub fn supports_minimap(&self, cx: &App) -> bool {
7360 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7361 }
7362
7363 fn edit_predictions_enabled_in_buffer(
7364 &self,
7365 buffer: &Entity<Buffer>,
7366 buffer_position: language::Anchor,
7367 cx: &App,
7368 ) -> bool {
7369 maybe!({
7370 if self.read_only(cx) {
7371 return Some(false);
7372 }
7373 let provider = self.edit_prediction_provider()?;
7374 if !provider.is_enabled(buffer, buffer_position, cx) {
7375 return Some(false);
7376 }
7377 let buffer = buffer.read(cx);
7378 let Some(file) = buffer.file() else {
7379 return Some(true);
7380 };
7381 let settings = all_language_settings(Some(file), cx);
7382 Some(settings.edit_predictions_enabled_for_file(file, cx))
7383 })
7384 .unwrap_or(false)
7385 }
7386
7387 fn cycle_edit_prediction(
7388 &mut self,
7389 direction: Direction,
7390 window: &mut Window,
7391 cx: &mut Context<Self>,
7392 ) -> Option<()> {
7393 let provider = self.edit_prediction_provider()?;
7394 let cursor = self.selections.newest_anchor().head();
7395 let (buffer, cursor_buffer_position) =
7396 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7397 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7398 return None;
7399 }
7400
7401 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7402 self.update_visible_edit_prediction(window, cx);
7403
7404 Some(())
7405 }
7406
7407 pub fn show_edit_prediction(
7408 &mut self,
7409 _: &ShowEditPrediction,
7410 window: &mut Window,
7411 cx: &mut Context<Self>,
7412 ) {
7413 if !self.has_active_edit_prediction() {
7414 self.refresh_edit_prediction(false, true, window, cx);
7415 return;
7416 }
7417
7418 self.update_visible_edit_prediction(window, cx);
7419 }
7420
7421 pub fn display_cursor_names(
7422 &mut self,
7423 _: &DisplayCursorNames,
7424 window: &mut Window,
7425 cx: &mut Context<Self>,
7426 ) {
7427 self.show_cursor_names(window, cx);
7428 }
7429
7430 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7431 self.show_cursor_names = true;
7432 cx.notify();
7433 cx.spawn_in(window, async move |this, cx| {
7434 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7435 this.update(cx, |this, cx| {
7436 this.show_cursor_names = false;
7437 cx.notify()
7438 })
7439 .ok()
7440 })
7441 .detach();
7442 }
7443
7444 pub fn next_edit_prediction(
7445 &mut self,
7446 _: &NextEditPrediction,
7447 window: &mut Window,
7448 cx: &mut Context<Self>,
7449 ) {
7450 if self.has_active_edit_prediction() {
7451 self.cycle_edit_prediction(Direction::Next, window, cx);
7452 } else {
7453 let is_copilot_disabled = self
7454 .refresh_edit_prediction(false, true, window, cx)
7455 .is_none();
7456 if is_copilot_disabled {
7457 cx.propagate();
7458 }
7459 }
7460 }
7461
7462 pub fn previous_edit_prediction(
7463 &mut self,
7464 _: &PreviousEditPrediction,
7465 window: &mut Window,
7466 cx: &mut Context<Self>,
7467 ) {
7468 if self.has_active_edit_prediction() {
7469 self.cycle_edit_prediction(Direction::Prev, window, cx);
7470 } else {
7471 let is_copilot_disabled = self
7472 .refresh_edit_prediction(false, true, window, cx)
7473 .is_none();
7474 if is_copilot_disabled {
7475 cx.propagate();
7476 }
7477 }
7478 }
7479
7480 pub fn accept_edit_prediction(
7481 &mut self,
7482 _: &AcceptEditPrediction,
7483 window: &mut Window,
7484 cx: &mut Context<Self>,
7485 ) {
7486 if self.show_edit_predictions_in_menu() {
7487 self.hide_context_menu(window, cx);
7488 }
7489
7490 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7491 return;
7492 };
7493
7494 match &active_edit_prediction.completion {
7495 EditPrediction::MoveWithin { target, .. } => {
7496 let target = *target;
7497
7498 if let Some(position_map) = &self.last_position_map {
7499 if position_map
7500 .visible_row_range
7501 .contains(&target.to_display_point(&position_map.snapshot).row())
7502 || !self.edit_prediction_requires_modifier()
7503 {
7504 self.unfold_ranges(&[target..target], true, false, cx);
7505 // Note that this is also done in vim's handler of the Tab action.
7506 self.change_selections(
7507 SelectionEffects::scroll(Autoscroll::newest()),
7508 window,
7509 cx,
7510 |selections| {
7511 selections.select_anchor_ranges([target..target]);
7512 },
7513 );
7514 self.clear_row_highlights::<EditPredictionPreview>();
7515
7516 self.edit_prediction_preview
7517 .set_previous_scroll_position(None);
7518 } else {
7519 self.edit_prediction_preview
7520 .set_previous_scroll_position(Some(
7521 position_map.snapshot.scroll_anchor,
7522 ));
7523
7524 self.highlight_rows::<EditPredictionPreview>(
7525 target..target,
7526 cx.theme().colors().editor_highlighted_line_background,
7527 RowHighlightOptions {
7528 autoscroll: true,
7529 ..Default::default()
7530 },
7531 cx,
7532 );
7533 self.request_autoscroll(Autoscroll::fit(), cx);
7534 }
7535 }
7536 }
7537 EditPrediction::MoveOutside { snapshot, target } => {
7538 if let Some(workspace) = self.workspace() {
7539 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7540 .detach_and_log_err(cx);
7541 }
7542 }
7543 EditPrediction::Edit { edits, .. } => {
7544 self.report_edit_prediction_event(
7545 active_edit_prediction.completion_id.clone(),
7546 true,
7547 cx,
7548 );
7549
7550 if let Some(provider) = self.edit_prediction_provider() {
7551 provider.accept(cx);
7552 }
7553
7554 // Store the transaction ID and selections before applying the edit
7555 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7556
7557 let snapshot = self.buffer.read(cx).snapshot(cx);
7558 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7559
7560 self.buffer.update(cx, |buffer, cx| {
7561 buffer.edit(edits.iter().cloned(), None, cx)
7562 });
7563
7564 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7565 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7566 });
7567
7568 let selections = self.selections.disjoint_anchors_arc();
7569 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7570 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7571 if has_new_transaction {
7572 self.selection_history
7573 .insert_transaction(transaction_id_now, selections);
7574 }
7575 }
7576
7577 self.update_visible_edit_prediction(window, cx);
7578 if self.active_edit_prediction.is_none() {
7579 self.refresh_edit_prediction(true, true, window, cx);
7580 }
7581
7582 cx.notify();
7583 }
7584 }
7585
7586 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7587 }
7588
7589 pub fn accept_partial_edit_prediction(
7590 &mut self,
7591 _: &AcceptPartialEditPrediction,
7592 window: &mut Window,
7593 cx: &mut Context<Self>,
7594 ) {
7595 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7596 return;
7597 };
7598 if self.selections.count() != 1 {
7599 return;
7600 }
7601
7602 match &active_edit_prediction.completion {
7603 EditPrediction::MoveWithin { target, .. } => {
7604 let target = *target;
7605 self.change_selections(
7606 SelectionEffects::scroll(Autoscroll::newest()),
7607 window,
7608 cx,
7609 |selections| {
7610 selections.select_anchor_ranges([target..target]);
7611 },
7612 );
7613 }
7614 EditPrediction::MoveOutside { snapshot, target } => {
7615 if let Some(workspace) = self.workspace() {
7616 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7617 .detach_and_log_err(cx);
7618 }
7619 }
7620 EditPrediction::Edit { edits, .. } => {
7621 self.report_edit_prediction_event(
7622 active_edit_prediction.completion_id.clone(),
7623 true,
7624 cx,
7625 );
7626
7627 // Find an insertion that starts at the cursor position.
7628 let snapshot = self.buffer.read(cx).snapshot(cx);
7629 let cursor_offset = self
7630 .selections
7631 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7632 .head();
7633 let insertion = edits.iter().find_map(|(range, text)| {
7634 let range = range.to_offset(&snapshot);
7635 if range.is_empty() && range.start == cursor_offset {
7636 Some(text)
7637 } else {
7638 None
7639 }
7640 });
7641
7642 if let Some(text) = insertion {
7643 let mut partial_completion = text
7644 .chars()
7645 .by_ref()
7646 .take_while(|c| c.is_alphabetic())
7647 .collect::<String>();
7648 if partial_completion.is_empty() {
7649 partial_completion = text
7650 .chars()
7651 .by_ref()
7652 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7653 .collect::<String>();
7654 }
7655
7656 cx.emit(EditorEvent::InputHandled {
7657 utf16_range_to_replace: None,
7658 text: partial_completion.clone().into(),
7659 });
7660
7661 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7662
7663 self.refresh_edit_prediction(true, true, window, cx);
7664 cx.notify();
7665 } else {
7666 self.accept_edit_prediction(&Default::default(), window, cx);
7667 }
7668 }
7669 }
7670 }
7671
7672 fn discard_edit_prediction(
7673 &mut self,
7674 should_report_edit_prediction_event: bool,
7675 cx: &mut Context<Self>,
7676 ) -> bool {
7677 if should_report_edit_prediction_event {
7678 let completion_id = self
7679 .active_edit_prediction
7680 .as_ref()
7681 .and_then(|active_completion| active_completion.completion_id.clone());
7682
7683 self.report_edit_prediction_event(completion_id, false, cx);
7684 }
7685
7686 if let Some(provider) = self.edit_prediction_provider() {
7687 provider.discard(cx);
7688 }
7689
7690 self.take_active_edit_prediction(cx)
7691 }
7692
7693 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7694 let Some(provider) = self.edit_prediction_provider() else {
7695 return;
7696 };
7697
7698 let Some((_, buffer, _)) = self
7699 .buffer
7700 .read(cx)
7701 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7702 else {
7703 return;
7704 };
7705
7706 let extension = buffer
7707 .read(cx)
7708 .file()
7709 .and_then(|file| Some(file.path().extension()?.to_string()));
7710
7711 let event_type = match accepted {
7712 true => "Edit Prediction Accepted",
7713 false => "Edit Prediction Discarded",
7714 };
7715 telemetry::event!(
7716 event_type,
7717 provider = provider.name(),
7718 prediction_id = id,
7719 suggestion_accepted = accepted,
7720 file_extension = extension,
7721 );
7722 }
7723
7724 fn open_editor_at_anchor(
7725 snapshot: &language::BufferSnapshot,
7726 target: language::Anchor,
7727 workspace: &Entity<Workspace>,
7728 window: &mut Window,
7729 cx: &mut App,
7730 ) -> Task<Result<()>> {
7731 workspace.update(cx, |workspace, cx| {
7732 let path = snapshot.file().map(|file| file.full_path(cx));
7733 let Some(path) =
7734 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7735 else {
7736 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7737 };
7738 let target = text::ToPoint::to_point(&target, snapshot);
7739 let item = workspace.open_path(path, None, true, window, cx);
7740 window.spawn(cx, async move |cx| {
7741 let Some(editor) = item.await?.downcast::<Editor>() else {
7742 return Ok(());
7743 };
7744 editor
7745 .update_in(cx, |editor, window, cx| {
7746 editor.go_to_singleton_buffer_point(target, window, cx);
7747 })
7748 .ok();
7749 anyhow::Ok(())
7750 })
7751 })
7752 }
7753
7754 pub fn has_active_edit_prediction(&self) -> bool {
7755 self.active_edit_prediction.is_some()
7756 }
7757
7758 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7759 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7760 return false;
7761 };
7762
7763 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7764 self.clear_highlights::<EditPredictionHighlight>(cx);
7765 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7766 true
7767 }
7768
7769 /// Returns true when we're displaying the edit prediction popover below the cursor
7770 /// like we are not previewing and the LSP autocomplete menu is visible
7771 /// or we are in `when_holding_modifier` mode.
7772 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7773 if self.edit_prediction_preview_is_active()
7774 || !self.show_edit_predictions_in_menu()
7775 || !self.edit_predictions_enabled()
7776 {
7777 return false;
7778 }
7779
7780 if self.has_visible_completions_menu() {
7781 return true;
7782 }
7783
7784 has_completion && self.edit_prediction_requires_modifier()
7785 }
7786
7787 fn handle_modifiers_changed(
7788 &mut self,
7789 modifiers: Modifiers,
7790 position_map: &PositionMap,
7791 window: &mut Window,
7792 cx: &mut Context<Self>,
7793 ) {
7794 // Ensure that the edit prediction preview is updated, even when not
7795 // enabled, if there's an active edit prediction preview.
7796 if self.show_edit_predictions_in_menu()
7797 || matches!(
7798 self.edit_prediction_preview,
7799 EditPredictionPreview::Active { .. }
7800 )
7801 {
7802 self.update_edit_prediction_preview(&modifiers, window, cx);
7803 }
7804
7805 self.update_selection_mode(&modifiers, position_map, window, cx);
7806
7807 let mouse_position = window.mouse_position();
7808 if !position_map.text_hitbox.is_hovered(window) {
7809 return;
7810 }
7811
7812 self.update_hovered_link(
7813 position_map.point_for_position(mouse_position),
7814 &position_map.snapshot,
7815 modifiers,
7816 window,
7817 cx,
7818 )
7819 }
7820
7821 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7822 match EditorSettings::get_global(cx).multi_cursor_modifier {
7823 MultiCursorModifier::Alt => modifiers.secondary(),
7824 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7825 }
7826 }
7827
7828 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7829 match EditorSettings::get_global(cx).multi_cursor_modifier {
7830 MultiCursorModifier::Alt => modifiers.alt,
7831 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7832 }
7833 }
7834
7835 fn columnar_selection_mode(
7836 modifiers: &Modifiers,
7837 cx: &mut Context<Self>,
7838 ) -> Option<ColumnarMode> {
7839 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7840 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7841 Some(ColumnarMode::FromMouse)
7842 } else if Self::is_alt_pressed(modifiers, cx) {
7843 Some(ColumnarMode::FromSelection)
7844 } else {
7845 None
7846 }
7847 } else {
7848 None
7849 }
7850 }
7851
7852 fn update_selection_mode(
7853 &mut self,
7854 modifiers: &Modifiers,
7855 position_map: &PositionMap,
7856 window: &mut Window,
7857 cx: &mut Context<Self>,
7858 ) {
7859 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7860 return;
7861 };
7862 if self.selections.pending_anchor().is_none() {
7863 return;
7864 }
7865
7866 let mouse_position = window.mouse_position();
7867 let point_for_position = position_map.point_for_position(mouse_position);
7868 let position = point_for_position.previous_valid;
7869
7870 self.select(
7871 SelectPhase::BeginColumnar {
7872 position,
7873 reset: false,
7874 mode,
7875 goal_column: point_for_position.exact_unclipped.column(),
7876 },
7877 window,
7878 cx,
7879 );
7880 }
7881
7882 fn update_edit_prediction_preview(
7883 &mut self,
7884 modifiers: &Modifiers,
7885 window: &mut Window,
7886 cx: &mut Context<Self>,
7887 ) {
7888 let mut modifiers_held = false;
7889 if let Some(accept_keystroke) = self
7890 .accept_edit_prediction_keybind(false, window, cx)
7891 .keystroke()
7892 {
7893 modifiers_held = modifiers_held
7894 || (accept_keystroke.modifiers() == modifiers
7895 && accept_keystroke.modifiers().modified());
7896 };
7897 if let Some(accept_partial_keystroke) = self
7898 .accept_edit_prediction_keybind(true, window, cx)
7899 .keystroke()
7900 {
7901 modifiers_held = modifiers_held
7902 || (accept_partial_keystroke.modifiers() == modifiers
7903 && accept_partial_keystroke.modifiers().modified());
7904 }
7905
7906 if modifiers_held {
7907 if matches!(
7908 self.edit_prediction_preview,
7909 EditPredictionPreview::Inactive { .. }
7910 ) {
7911 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7912 provider.provider.did_show(cx)
7913 }
7914
7915 self.edit_prediction_preview = EditPredictionPreview::Active {
7916 previous_scroll_position: None,
7917 since: Instant::now(),
7918 };
7919
7920 self.update_visible_edit_prediction(window, cx);
7921 cx.notify();
7922 }
7923 } else if let EditPredictionPreview::Active {
7924 previous_scroll_position,
7925 since,
7926 } = self.edit_prediction_preview
7927 {
7928 if let (Some(previous_scroll_position), Some(position_map)) =
7929 (previous_scroll_position, self.last_position_map.as_ref())
7930 {
7931 self.set_scroll_position(
7932 previous_scroll_position
7933 .scroll_position(&position_map.snapshot.display_snapshot),
7934 window,
7935 cx,
7936 );
7937 }
7938
7939 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7940 released_too_fast: since.elapsed() < Duration::from_millis(200),
7941 };
7942 self.clear_row_highlights::<EditPredictionPreview>();
7943 self.update_visible_edit_prediction(window, cx);
7944 cx.notify();
7945 }
7946 }
7947
7948 fn update_visible_edit_prediction(
7949 &mut self,
7950 _window: &mut Window,
7951 cx: &mut Context<Self>,
7952 ) -> Option<()> {
7953 if DisableAiSettings::get_global(cx).disable_ai {
7954 return None;
7955 }
7956
7957 if self.ime_transaction.is_some() {
7958 self.discard_edit_prediction(false, cx);
7959 return None;
7960 }
7961
7962 let selection = self.selections.newest_anchor();
7963 let cursor = selection.head();
7964 let multibuffer = self.buffer.read(cx).snapshot(cx);
7965 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7966 let excerpt_id = cursor.excerpt_id;
7967
7968 let show_in_menu = self.show_edit_predictions_in_menu();
7969 let completions_menu_has_precedence = !show_in_menu
7970 && (self.context_menu.borrow().is_some()
7971 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7972
7973 if completions_menu_has_precedence
7974 || !offset_selection.is_empty()
7975 || self
7976 .active_edit_prediction
7977 .as_ref()
7978 .is_some_and(|completion| {
7979 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7980 return false;
7981 };
7982 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7983 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7984 !invalidation_range.contains(&offset_selection.head())
7985 })
7986 {
7987 self.discard_edit_prediction(false, cx);
7988 return None;
7989 }
7990
7991 self.take_active_edit_prediction(cx);
7992 let Some(provider) = self.edit_prediction_provider() else {
7993 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7994 return None;
7995 };
7996
7997 let (buffer, cursor_buffer_position) =
7998 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7999
8000 self.edit_prediction_settings =
8001 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8002
8003 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8004
8005 if self.edit_prediction_indent_conflict {
8006 let cursor_point = cursor.to_point(&multibuffer);
8007
8008 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
8009
8010 if let Some((_, indent)) = indents.iter().next()
8011 && indent.len == cursor_point.column
8012 {
8013 self.edit_prediction_indent_conflict = false;
8014 }
8015 }
8016
8017 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8018
8019 let (completion_id, edits, edit_preview) = match edit_prediction {
8020 edit_prediction::EditPrediction::Local {
8021 id,
8022 edits,
8023 edit_preview,
8024 } => (id, edits, edit_preview),
8025 edit_prediction::EditPrediction::Jump {
8026 id,
8027 snapshot,
8028 target,
8029 } => {
8030 self.stale_edit_prediction_in_menu = None;
8031 self.active_edit_prediction = Some(EditPredictionState {
8032 inlay_ids: vec![],
8033 completion: EditPrediction::MoveOutside { snapshot, target },
8034 completion_id: id,
8035 invalidation_range: None,
8036 });
8037 cx.notify();
8038 return Some(());
8039 }
8040 };
8041
8042 let edits = edits
8043 .into_iter()
8044 .flat_map(|(range, new_text)| {
8045 Some((
8046 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8047 new_text,
8048 ))
8049 })
8050 .collect::<Vec<_>>();
8051 if edits.is_empty() {
8052 return None;
8053 }
8054
8055 let first_edit_start = edits.first().unwrap().0.start;
8056 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8057 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8058
8059 let last_edit_end = edits.last().unwrap().0.end;
8060 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8061 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8062
8063 let cursor_row = cursor.to_point(&multibuffer).row;
8064
8065 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8066
8067 let mut inlay_ids = Vec::new();
8068 let invalidation_row_range;
8069 let move_invalidation_row_range = if cursor_row < edit_start_row {
8070 Some(cursor_row..edit_end_row)
8071 } else if cursor_row > edit_end_row {
8072 Some(edit_start_row..cursor_row)
8073 } else {
8074 None
8075 };
8076 let supports_jump = self
8077 .edit_prediction_provider
8078 .as_ref()
8079 .map(|provider| provider.provider.supports_jump_to_edit())
8080 .unwrap_or(true);
8081
8082 let is_move = supports_jump
8083 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8084 let completion = if is_move {
8085 invalidation_row_range =
8086 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8087 let target = first_edit_start;
8088 EditPrediction::MoveWithin { target, snapshot }
8089 } else {
8090 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8091 && !self.edit_predictions_hidden_for_vim_mode;
8092
8093 if show_completions_in_buffer {
8094 if let Some(provider) = &self.edit_prediction_provider {
8095 provider.provider.did_show(cx);
8096 }
8097 if edits
8098 .iter()
8099 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8100 {
8101 let mut inlays = Vec::new();
8102 for (range, new_text) in &edits {
8103 let inlay = Inlay::edit_prediction(
8104 post_inc(&mut self.next_inlay_id),
8105 range.start,
8106 new_text.as_ref(),
8107 );
8108 inlay_ids.push(inlay.id);
8109 inlays.push(inlay);
8110 }
8111
8112 self.splice_inlays(&[], inlays, cx);
8113 } else {
8114 let background_color = cx.theme().status().deleted_background;
8115 self.highlight_text::<EditPredictionHighlight>(
8116 edits.iter().map(|(range, _)| range.clone()).collect(),
8117 HighlightStyle {
8118 background_color: Some(background_color),
8119 ..Default::default()
8120 },
8121 cx,
8122 );
8123 }
8124 }
8125
8126 invalidation_row_range = edit_start_row..edit_end_row;
8127
8128 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8129 if provider.show_tab_accept_marker() {
8130 EditDisplayMode::TabAccept
8131 } else {
8132 EditDisplayMode::Inline
8133 }
8134 } else {
8135 EditDisplayMode::DiffPopover
8136 };
8137
8138 EditPrediction::Edit {
8139 edits,
8140 edit_preview,
8141 display_mode,
8142 snapshot,
8143 }
8144 };
8145
8146 let invalidation_range = multibuffer
8147 .anchor_before(Point::new(invalidation_row_range.start, 0))
8148 ..multibuffer.anchor_after(Point::new(
8149 invalidation_row_range.end,
8150 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8151 ));
8152
8153 self.stale_edit_prediction_in_menu = None;
8154 self.active_edit_prediction = Some(EditPredictionState {
8155 inlay_ids,
8156 completion,
8157 completion_id,
8158 invalidation_range: Some(invalidation_range),
8159 });
8160
8161 cx.notify();
8162
8163 Some(())
8164 }
8165
8166 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8167 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8168 }
8169
8170 fn clear_tasks(&mut self) {
8171 self.tasks.clear()
8172 }
8173
8174 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8175 if self.tasks.insert(key, value).is_some() {
8176 // This case should hopefully be rare, but just in case...
8177 log::error!(
8178 "multiple different run targets found on a single line, only the last target will be rendered"
8179 )
8180 }
8181 }
8182
8183 /// Get all display points of breakpoints that will be rendered within editor
8184 ///
8185 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8186 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8187 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8188 fn active_breakpoints(
8189 &self,
8190 range: Range<DisplayRow>,
8191 window: &mut Window,
8192 cx: &mut Context<Self>,
8193 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8194 let mut breakpoint_display_points = HashMap::default();
8195
8196 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8197 return breakpoint_display_points;
8198 };
8199
8200 let snapshot = self.snapshot(window, cx);
8201
8202 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8203 let Some(project) = self.project() else {
8204 return breakpoint_display_points;
8205 };
8206
8207 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8208 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8209
8210 for (buffer_snapshot, range, excerpt_id) in
8211 multi_buffer_snapshot.range_to_buffer_ranges(range)
8212 {
8213 let Some(buffer) = project
8214 .read(cx)
8215 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8216 else {
8217 continue;
8218 };
8219 let breakpoints = breakpoint_store.read(cx).breakpoints(
8220 &buffer,
8221 Some(
8222 buffer_snapshot.anchor_before(range.start)
8223 ..buffer_snapshot.anchor_after(range.end),
8224 ),
8225 buffer_snapshot,
8226 cx,
8227 );
8228 for (breakpoint, state) in breakpoints {
8229 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8230 let position = multi_buffer_anchor
8231 .to_point(&multi_buffer_snapshot)
8232 .to_display_point(&snapshot);
8233
8234 breakpoint_display_points.insert(
8235 position.row(),
8236 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8237 );
8238 }
8239 }
8240
8241 breakpoint_display_points
8242 }
8243
8244 fn breakpoint_context_menu(
8245 &self,
8246 anchor: Anchor,
8247 window: &mut Window,
8248 cx: &mut Context<Self>,
8249 ) -> Entity<ui::ContextMenu> {
8250 let weak_editor = cx.weak_entity();
8251 let focus_handle = self.focus_handle(cx);
8252
8253 let row = self
8254 .buffer
8255 .read(cx)
8256 .snapshot(cx)
8257 .summary_for_anchor::<Point>(&anchor)
8258 .row;
8259
8260 let breakpoint = self
8261 .breakpoint_at_row(row, window, cx)
8262 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8263
8264 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8265 "Edit Log Breakpoint"
8266 } else {
8267 "Set Log Breakpoint"
8268 };
8269
8270 let condition_breakpoint_msg = if breakpoint
8271 .as_ref()
8272 .is_some_and(|bp| bp.1.condition.is_some())
8273 {
8274 "Edit Condition Breakpoint"
8275 } else {
8276 "Set Condition Breakpoint"
8277 };
8278
8279 let hit_condition_breakpoint_msg = if breakpoint
8280 .as_ref()
8281 .is_some_and(|bp| bp.1.hit_condition.is_some())
8282 {
8283 "Edit Hit Condition Breakpoint"
8284 } else {
8285 "Set Hit Condition Breakpoint"
8286 };
8287
8288 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8289 "Unset Breakpoint"
8290 } else {
8291 "Set Breakpoint"
8292 };
8293
8294 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8295
8296 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8297 BreakpointState::Enabled => Some("Disable"),
8298 BreakpointState::Disabled => Some("Enable"),
8299 });
8300
8301 let (anchor, breakpoint) =
8302 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8303
8304 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8305 menu.on_blur_subscription(Subscription::new(|| {}))
8306 .context(focus_handle)
8307 .when(run_to_cursor, |this| {
8308 let weak_editor = weak_editor.clone();
8309 this.entry("Run to cursor", None, move |window, cx| {
8310 weak_editor
8311 .update(cx, |editor, cx| {
8312 editor.change_selections(
8313 SelectionEffects::no_scroll(),
8314 window,
8315 cx,
8316 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8317 );
8318 })
8319 .ok();
8320
8321 window.dispatch_action(Box::new(RunToCursor), cx);
8322 })
8323 .separator()
8324 })
8325 .when_some(toggle_state_msg, |this, msg| {
8326 this.entry(msg, None, {
8327 let weak_editor = weak_editor.clone();
8328 let breakpoint = breakpoint.clone();
8329 move |_window, cx| {
8330 weak_editor
8331 .update(cx, |this, cx| {
8332 this.edit_breakpoint_at_anchor(
8333 anchor,
8334 breakpoint.as_ref().clone(),
8335 BreakpointEditAction::InvertState,
8336 cx,
8337 );
8338 })
8339 .log_err();
8340 }
8341 })
8342 })
8343 .entry(set_breakpoint_msg, None, {
8344 let weak_editor = weak_editor.clone();
8345 let breakpoint = breakpoint.clone();
8346 move |_window, cx| {
8347 weak_editor
8348 .update(cx, |this, cx| {
8349 this.edit_breakpoint_at_anchor(
8350 anchor,
8351 breakpoint.as_ref().clone(),
8352 BreakpointEditAction::Toggle,
8353 cx,
8354 );
8355 })
8356 .log_err();
8357 }
8358 })
8359 .entry(log_breakpoint_msg, None, {
8360 let breakpoint = breakpoint.clone();
8361 let weak_editor = weak_editor.clone();
8362 move |window, cx| {
8363 weak_editor
8364 .update(cx, |this, cx| {
8365 this.add_edit_breakpoint_block(
8366 anchor,
8367 breakpoint.as_ref(),
8368 BreakpointPromptEditAction::Log,
8369 window,
8370 cx,
8371 );
8372 })
8373 .log_err();
8374 }
8375 })
8376 .entry(condition_breakpoint_msg, None, {
8377 let breakpoint = breakpoint.clone();
8378 let weak_editor = weak_editor.clone();
8379 move |window, cx| {
8380 weak_editor
8381 .update(cx, |this, cx| {
8382 this.add_edit_breakpoint_block(
8383 anchor,
8384 breakpoint.as_ref(),
8385 BreakpointPromptEditAction::Condition,
8386 window,
8387 cx,
8388 );
8389 })
8390 .log_err();
8391 }
8392 })
8393 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8394 weak_editor
8395 .update(cx, |this, cx| {
8396 this.add_edit_breakpoint_block(
8397 anchor,
8398 breakpoint.as_ref(),
8399 BreakpointPromptEditAction::HitCondition,
8400 window,
8401 cx,
8402 );
8403 })
8404 .log_err();
8405 })
8406 })
8407 }
8408
8409 fn render_breakpoint(
8410 &self,
8411 position: Anchor,
8412 row: DisplayRow,
8413 breakpoint: &Breakpoint,
8414 state: Option<BreakpointSessionState>,
8415 cx: &mut Context<Self>,
8416 ) -> IconButton {
8417 let is_rejected = state.is_some_and(|s| !s.verified);
8418 // Is it a breakpoint that shows up when hovering over gutter?
8419 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8420 (false, false),
8421 |PhantomBreakpointIndicator {
8422 is_active,
8423 display_row,
8424 collides_with_existing_breakpoint,
8425 }| {
8426 (
8427 is_active && display_row == row,
8428 collides_with_existing_breakpoint,
8429 )
8430 },
8431 );
8432
8433 let (color, icon) = {
8434 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8435 (false, false) => ui::IconName::DebugBreakpoint,
8436 (true, false) => ui::IconName::DebugLogBreakpoint,
8437 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8438 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8439 };
8440
8441 let color = cx.theme().colors();
8442
8443 let color = if is_phantom {
8444 if collides_with_existing {
8445 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8446 } else {
8447 Color::Hint
8448 }
8449 } else if is_rejected {
8450 Color::Disabled
8451 } else {
8452 Color::Debugger
8453 };
8454
8455 (color, icon)
8456 };
8457
8458 let breakpoint = Arc::from(breakpoint.clone());
8459
8460 let alt_as_text = gpui::Keystroke {
8461 modifiers: Modifiers::secondary_key(),
8462 ..Default::default()
8463 };
8464 let primary_action_text = if breakpoint.is_disabled() {
8465 "Enable breakpoint"
8466 } else if is_phantom && !collides_with_existing {
8467 "Set breakpoint"
8468 } else {
8469 "Unset breakpoint"
8470 };
8471 let focus_handle = self.focus_handle.clone();
8472
8473 let meta = if is_rejected {
8474 SharedString::from("No executable code is associated with this line.")
8475 } else if collides_with_existing && !breakpoint.is_disabled() {
8476 SharedString::from(format!(
8477 "{alt_as_text}-click to disable,\nright-click for more options."
8478 ))
8479 } else {
8480 SharedString::from("Right-click for more options.")
8481 };
8482 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8483 .icon_size(IconSize::XSmall)
8484 .size(ui::ButtonSize::None)
8485 .when(is_rejected, |this| {
8486 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8487 })
8488 .icon_color(color)
8489 .style(ButtonStyle::Transparent)
8490 .on_click(cx.listener({
8491 move |editor, event: &ClickEvent, window, cx| {
8492 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8493 BreakpointEditAction::InvertState
8494 } else {
8495 BreakpointEditAction::Toggle
8496 };
8497
8498 window.focus(&editor.focus_handle(cx));
8499 editor.edit_breakpoint_at_anchor(
8500 position,
8501 breakpoint.as_ref().clone(),
8502 edit_action,
8503 cx,
8504 );
8505 }
8506 }))
8507 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8508 editor.set_breakpoint_context_menu(
8509 row,
8510 Some(position),
8511 event.position(),
8512 window,
8513 cx,
8514 );
8515 }))
8516 .tooltip(move |_window, cx| {
8517 Tooltip::with_meta_in(
8518 primary_action_text,
8519 Some(&ToggleBreakpoint),
8520 meta.clone(),
8521 &focus_handle,
8522 cx,
8523 )
8524 })
8525 }
8526
8527 fn build_tasks_context(
8528 project: &Entity<Project>,
8529 buffer: &Entity<Buffer>,
8530 buffer_row: u32,
8531 tasks: &Arc<RunnableTasks>,
8532 cx: &mut Context<Self>,
8533 ) -> Task<Option<task::TaskContext>> {
8534 let position = Point::new(buffer_row, tasks.column);
8535 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8536 let location = Location {
8537 buffer: buffer.clone(),
8538 range: range_start..range_start,
8539 };
8540 // Fill in the environmental variables from the tree-sitter captures
8541 let mut captured_task_variables = TaskVariables::default();
8542 for (capture_name, value) in tasks.extra_variables.clone() {
8543 captured_task_variables.insert(
8544 task::VariableName::Custom(capture_name.into()),
8545 value.clone(),
8546 );
8547 }
8548 project.update(cx, |project, cx| {
8549 project.task_store().update(cx, |task_store, cx| {
8550 task_store.task_context_for_location(captured_task_variables, location, cx)
8551 })
8552 })
8553 }
8554
8555 pub fn spawn_nearest_task(
8556 &mut self,
8557 action: &SpawnNearestTask,
8558 window: &mut Window,
8559 cx: &mut Context<Self>,
8560 ) {
8561 let Some((workspace, _)) = self.workspace.clone() else {
8562 return;
8563 };
8564 let Some(project) = self.project.clone() else {
8565 return;
8566 };
8567
8568 // Try to find a closest, enclosing node using tree-sitter that has a task
8569 let Some((buffer, buffer_row, tasks)) = self
8570 .find_enclosing_node_task(cx)
8571 // Or find the task that's closest in row-distance.
8572 .or_else(|| self.find_closest_task(cx))
8573 else {
8574 return;
8575 };
8576
8577 let reveal_strategy = action.reveal;
8578 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8579 cx.spawn_in(window, async move |_, cx| {
8580 let context = task_context.await?;
8581 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8582
8583 let resolved = &mut resolved_task.resolved;
8584 resolved.reveal = reveal_strategy;
8585
8586 workspace
8587 .update_in(cx, |workspace, window, cx| {
8588 workspace.schedule_resolved_task(
8589 task_source_kind,
8590 resolved_task,
8591 false,
8592 window,
8593 cx,
8594 );
8595 })
8596 .ok()
8597 })
8598 .detach();
8599 }
8600
8601 fn find_closest_task(
8602 &mut self,
8603 cx: &mut Context<Self>,
8604 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8605 let cursor_row = self
8606 .selections
8607 .newest_adjusted(&self.display_snapshot(cx))
8608 .head()
8609 .row;
8610
8611 let ((buffer_id, row), tasks) = self
8612 .tasks
8613 .iter()
8614 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8615
8616 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8617 let tasks = Arc::new(tasks.to_owned());
8618 Some((buffer, *row, tasks))
8619 }
8620
8621 fn find_enclosing_node_task(
8622 &mut self,
8623 cx: &mut Context<Self>,
8624 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8625 let snapshot = self.buffer.read(cx).snapshot(cx);
8626 let offset = self
8627 .selections
8628 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8629 .head();
8630 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8631 let offset = excerpt.map_offset_to_buffer(offset);
8632 let buffer_id = excerpt.buffer().remote_id();
8633
8634 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8635 let mut cursor = layer.node().walk();
8636
8637 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8638 if cursor.node().end_byte() == offset.0 {
8639 cursor.goto_next_sibling();
8640 }
8641 }
8642
8643 // Ascend to the smallest ancestor that contains the range and has a task.
8644 loop {
8645 let node = cursor.node();
8646 let node_range = node.byte_range();
8647 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8648
8649 // Check if this node contains our offset
8650 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8651 // If it contains offset, check for task
8652 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8653 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8654 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8655 }
8656 }
8657
8658 if !cursor.goto_parent() {
8659 break;
8660 }
8661 }
8662 None
8663 }
8664
8665 fn render_run_indicator(
8666 &self,
8667 _style: &EditorStyle,
8668 is_active: bool,
8669 row: DisplayRow,
8670 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8671 cx: &mut Context<Self>,
8672 ) -> IconButton {
8673 let color = Color::Muted;
8674 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8675
8676 IconButton::new(
8677 ("run_indicator", row.0 as usize),
8678 ui::IconName::PlayOutlined,
8679 )
8680 .shape(ui::IconButtonShape::Square)
8681 .icon_size(IconSize::XSmall)
8682 .icon_color(color)
8683 .toggle_state(is_active)
8684 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8685 let quick_launch = match e {
8686 ClickEvent::Keyboard(_) => true,
8687 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8688 };
8689
8690 window.focus(&editor.focus_handle(cx));
8691 editor.toggle_code_actions(
8692 &ToggleCodeActions {
8693 deployed_from: Some(CodeActionSource::RunMenu(row)),
8694 quick_launch,
8695 },
8696 window,
8697 cx,
8698 );
8699 }))
8700 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8701 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8702 }))
8703 }
8704
8705 pub fn context_menu_visible(&self) -> bool {
8706 !self.edit_prediction_preview_is_active()
8707 && self
8708 .context_menu
8709 .borrow()
8710 .as_ref()
8711 .is_some_and(|menu| menu.visible())
8712 }
8713
8714 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8715 self.context_menu
8716 .borrow()
8717 .as_ref()
8718 .map(|menu| menu.origin())
8719 }
8720
8721 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8722 self.context_menu_options = Some(options);
8723 }
8724
8725 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8726 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8727
8728 fn render_edit_prediction_popover(
8729 &mut self,
8730 text_bounds: &Bounds<Pixels>,
8731 content_origin: gpui::Point<Pixels>,
8732 right_margin: Pixels,
8733 editor_snapshot: &EditorSnapshot,
8734 visible_row_range: Range<DisplayRow>,
8735 scroll_top: ScrollOffset,
8736 scroll_bottom: ScrollOffset,
8737 line_layouts: &[LineWithInvisibles],
8738 line_height: Pixels,
8739 scroll_position: gpui::Point<ScrollOffset>,
8740 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8741 newest_selection_head: Option<DisplayPoint>,
8742 editor_width: Pixels,
8743 style: &EditorStyle,
8744 window: &mut Window,
8745 cx: &mut App,
8746 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8747 if self.mode().is_minimap() {
8748 return None;
8749 }
8750 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8751
8752 if self.edit_prediction_visible_in_cursor_popover(true) {
8753 return None;
8754 }
8755
8756 match &active_edit_prediction.completion {
8757 EditPrediction::MoveWithin { target, .. } => {
8758 let target_display_point = target.to_display_point(editor_snapshot);
8759
8760 if self.edit_prediction_requires_modifier() {
8761 if !self.edit_prediction_preview_is_active() {
8762 return None;
8763 }
8764
8765 self.render_edit_prediction_modifier_jump_popover(
8766 text_bounds,
8767 content_origin,
8768 visible_row_range,
8769 line_layouts,
8770 line_height,
8771 scroll_pixel_position,
8772 newest_selection_head,
8773 target_display_point,
8774 window,
8775 cx,
8776 )
8777 } else {
8778 self.render_edit_prediction_eager_jump_popover(
8779 text_bounds,
8780 content_origin,
8781 editor_snapshot,
8782 visible_row_range,
8783 scroll_top,
8784 scroll_bottom,
8785 line_height,
8786 scroll_pixel_position,
8787 target_display_point,
8788 editor_width,
8789 window,
8790 cx,
8791 )
8792 }
8793 }
8794 EditPrediction::Edit {
8795 display_mode: EditDisplayMode::Inline,
8796 ..
8797 } => None,
8798 EditPrediction::Edit {
8799 display_mode: EditDisplayMode::TabAccept,
8800 edits,
8801 ..
8802 } => {
8803 let range = &edits.first()?.0;
8804 let target_display_point = range.end.to_display_point(editor_snapshot);
8805
8806 self.render_edit_prediction_end_of_line_popover(
8807 "Accept",
8808 editor_snapshot,
8809 visible_row_range,
8810 target_display_point,
8811 line_height,
8812 scroll_pixel_position,
8813 content_origin,
8814 editor_width,
8815 window,
8816 cx,
8817 )
8818 }
8819 EditPrediction::Edit {
8820 edits,
8821 edit_preview,
8822 display_mode: EditDisplayMode::DiffPopover,
8823 snapshot,
8824 } => self.render_edit_prediction_diff_popover(
8825 text_bounds,
8826 content_origin,
8827 right_margin,
8828 editor_snapshot,
8829 visible_row_range,
8830 line_layouts,
8831 line_height,
8832 scroll_position,
8833 scroll_pixel_position,
8834 newest_selection_head,
8835 editor_width,
8836 style,
8837 edits,
8838 edit_preview,
8839 snapshot,
8840 window,
8841 cx,
8842 ),
8843 EditPrediction::MoveOutside { snapshot, .. } => {
8844 let mut element = self
8845 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8846 .into_any();
8847
8848 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8849 let origin_x = text_bounds.size.width - size.width - px(30.);
8850 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8851 element.prepaint_at(origin, window, cx);
8852
8853 Some((element, origin))
8854 }
8855 }
8856 }
8857
8858 fn render_edit_prediction_modifier_jump_popover(
8859 &mut self,
8860 text_bounds: &Bounds<Pixels>,
8861 content_origin: gpui::Point<Pixels>,
8862 visible_row_range: Range<DisplayRow>,
8863 line_layouts: &[LineWithInvisibles],
8864 line_height: Pixels,
8865 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8866 newest_selection_head: Option<DisplayPoint>,
8867 target_display_point: DisplayPoint,
8868 window: &mut Window,
8869 cx: &mut App,
8870 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8871 let scrolled_content_origin =
8872 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8873
8874 const SCROLL_PADDING_Y: Pixels = px(12.);
8875
8876 if target_display_point.row() < visible_row_range.start {
8877 return self.render_edit_prediction_scroll_popover(
8878 |_| SCROLL_PADDING_Y,
8879 IconName::ArrowUp,
8880 visible_row_range,
8881 line_layouts,
8882 newest_selection_head,
8883 scrolled_content_origin,
8884 window,
8885 cx,
8886 );
8887 } else if target_display_point.row() >= visible_row_range.end {
8888 return self.render_edit_prediction_scroll_popover(
8889 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8890 IconName::ArrowDown,
8891 visible_row_range,
8892 line_layouts,
8893 newest_selection_head,
8894 scrolled_content_origin,
8895 window,
8896 cx,
8897 );
8898 }
8899
8900 const POLE_WIDTH: Pixels = px(2.);
8901
8902 let line_layout =
8903 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8904 let target_column = target_display_point.column() as usize;
8905
8906 let target_x = line_layout.x_for_index(target_column);
8907 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8908 - scroll_pixel_position.y;
8909
8910 let flag_on_right = target_x < text_bounds.size.width / 2.;
8911
8912 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8913 border_color.l += 0.001;
8914
8915 let mut element = v_flex()
8916 .items_end()
8917 .when(flag_on_right, |el| el.items_start())
8918 .child(if flag_on_right {
8919 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8920 .rounded_bl(px(0.))
8921 .rounded_tl(px(0.))
8922 .border_l_2()
8923 .border_color(border_color)
8924 } else {
8925 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8926 .rounded_br(px(0.))
8927 .rounded_tr(px(0.))
8928 .border_r_2()
8929 .border_color(border_color)
8930 })
8931 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8932 .into_any();
8933
8934 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8935
8936 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8937 - point(
8938 if flag_on_right {
8939 POLE_WIDTH
8940 } else {
8941 size.width - POLE_WIDTH
8942 },
8943 size.height - line_height,
8944 );
8945
8946 origin.x = origin.x.max(content_origin.x);
8947
8948 element.prepaint_at(origin, window, cx);
8949
8950 Some((element, origin))
8951 }
8952
8953 fn render_edit_prediction_scroll_popover(
8954 &mut self,
8955 to_y: impl Fn(Size<Pixels>) -> Pixels,
8956 scroll_icon: IconName,
8957 visible_row_range: Range<DisplayRow>,
8958 line_layouts: &[LineWithInvisibles],
8959 newest_selection_head: Option<DisplayPoint>,
8960 scrolled_content_origin: gpui::Point<Pixels>,
8961 window: &mut Window,
8962 cx: &mut App,
8963 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8964 let mut element = self
8965 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8966 .into_any();
8967
8968 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8969
8970 let cursor = newest_selection_head?;
8971 let cursor_row_layout =
8972 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8973 let cursor_column = cursor.column() as usize;
8974
8975 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8976
8977 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8978
8979 element.prepaint_at(origin, window, cx);
8980 Some((element, origin))
8981 }
8982
8983 fn render_edit_prediction_eager_jump_popover(
8984 &mut self,
8985 text_bounds: &Bounds<Pixels>,
8986 content_origin: gpui::Point<Pixels>,
8987 editor_snapshot: &EditorSnapshot,
8988 visible_row_range: Range<DisplayRow>,
8989 scroll_top: ScrollOffset,
8990 scroll_bottom: ScrollOffset,
8991 line_height: Pixels,
8992 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8993 target_display_point: DisplayPoint,
8994 editor_width: Pixels,
8995 window: &mut Window,
8996 cx: &mut App,
8997 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8998 if target_display_point.row().as_f64() < scroll_top {
8999 let mut element = self
9000 .render_edit_prediction_line_popover(
9001 "Jump to Edit",
9002 Some(IconName::ArrowUp),
9003 window,
9004 cx,
9005 )
9006 .into_any();
9007
9008 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9009 let offset = point(
9010 (text_bounds.size.width - size.width) / 2.,
9011 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9012 );
9013
9014 let origin = text_bounds.origin + offset;
9015 element.prepaint_at(origin, window, cx);
9016 Some((element, origin))
9017 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9018 let mut element = self
9019 .render_edit_prediction_line_popover(
9020 "Jump to Edit",
9021 Some(IconName::ArrowDown),
9022 window,
9023 cx,
9024 )
9025 .into_any();
9026
9027 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9028 let offset = point(
9029 (text_bounds.size.width - size.width) / 2.,
9030 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9031 );
9032
9033 let origin = text_bounds.origin + offset;
9034 element.prepaint_at(origin, window, cx);
9035 Some((element, origin))
9036 } else {
9037 self.render_edit_prediction_end_of_line_popover(
9038 "Jump to Edit",
9039 editor_snapshot,
9040 visible_row_range,
9041 target_display_point,
9042 line_height,
9043 scroll_pixel_position,
9044 content_origin,
9045 editor_width,
9046 window,
9047 cx,
9048 )
9049 }
9050 }
9051
9052 fn render_edit_prediction_end_of_line_popover(
9053 self: &mut Editor,
9054 label: &'static str,
9055 editor_snapshot: &EditorSnapshot,
9056 visible_row_range: Range<DisplayRow>,
9057 target_display_point: DisplayPoint,
9058 line_height: Pixels,
9059 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9060 content_origin: gpui::Point<Pixels>,
9061 editor_width: Pixels,
9062 window: &mut Window,
9063 cx: &mut App,
9064 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9065 let target_line_end = DisplayPoint::new(
9066 target_display_point.row(),
9067 editor_snapshot.line_len(target_display_point.row()),
9068 );
9069
9070 let mut element = self
9071 .render_edit_prediction_line_popover(label, None, window, cx)
9072 .into_any();
9073
9074 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9075
9076 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9077
9078 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9079 let mut origin = start_point
9080 + line_origin
9081 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9082 origin.x = origin.x.max(content_origin.x);
9083
9084 let max_x = content_origin.x + editor_width - size.width;
9085
9086 if origin.x > max_x {
9087 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9088
9089 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9090 origin.y += offset;
9091 IconName::ArrowUp
9092 } else {
9093 origin.y -= offset;
9094 IconName::ArrowDown
9095 };
9096
9097 element = self
9098 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9099 .into_any();
9100
9101 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9102
9103 origin.x = content_origin.x + editor_width - size.width - px(2.);
9104 }
9105
9106 element.prepaint_at(origin, window, cx);
9107 Some((element, origin))
9108 }
9109
9110 fn render_edit_prediction_diff_popover(
9111 self: &Editor,
9112 text_bounds: &Bounds<Pixels>,
9113 content_origin: gpui::Point<Pixels>,
9114 right_margin: Pixels,
9115 editor_snapshot: &EditorSnapshot,
9116 visible_row_range: Range<DisplayRow>,
9117 line_layouts: &[LineWithInvisibles],
9118 line_height: Pixels,
9119 scroll_position: gpui::Point<ScrollOffset>,
9120 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9121 newest_selection_head: Option<DisplayPoint>,
9122 editor_width: Pixels,
9123 style: &EditorStyle,
9124 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9125 edit_preview: &Option<language::EditPreview>,
9126 snapshot: &language::BufferSnapshot,
9127 window: &mut Window,
9128 cx: &mut App,
9129 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9130 let edit_start = edits
9131 .first()
9132 .unwrap()
9133 .0
9134 .start
9135 .to_display_point(editor_snapshot);
9136 let edit_end = edits
9137 .last()
9138 .unwrap()
9139 .0
9140 .end
9141 .to_display_point(editor_snapshot);
9142
9143 let is_visible = visible_row_range.contains(&edit_start.row())
9144 || visible_row_range.contains(&edit_end.row());
9145 if !is_visible {
9146 return None;
9147 }
9148
9149 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9150 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9151 } else {
9152 // Fallback for providers without edit_preview
9153 crate::edit_prediction_fallback_text(edits, cx)
9154 };
9155
9156 let styled_text = highlighted_edits.to_styled_text(&style.text);
9157 let line_count = highlighted_edits.text.lines().count();
9158
9159 const BORDER_WIDTH: Pixels = px(1.);
9160
9161 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9162 let has_keybind = keybind.is_some();
9163
9164 let mut element = h_flex()
9165 .items_start()
9166 .child(
9167 h_flex()
9168 .bg(cx.theme().colors().editor_background)
9169 .border(BORDER_WIDTH)
9170 .shadow_xs()
9171 .border_color(cx.theme().colors().border)
9172 .rounded_l_lg()
9173 .when(line_count > 1, |el| el.rounded_br_lg())
9174 .pr_1()
9175 .child(styled_text),
9176 )
9177 .child(
9178 h_flex()
9179 .h(line_height + BORDER_WIDTH * 2.)
9180 .px_1p5()
9181 .gap_1()
9182 // Workaround: For some reason, there's a gap if we don't do this
9183 .ml(-BORDER_WIDTH)
9184 .shadow(vec![gpui::BoxShadow {
9185 color: gpui::black().opacity(0.05),
9186 offset: point(px(1.), px(1.)),
9187 blur_radius: px(2.),
9188 spread_radius: px(0.),
9189 }])
9190 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9191 .border(BORDER_WIDTH)
9192 .border_color(cx.theme().colors().border)
9193 .rounded_r_lg()
9194 .id("edit_prediction_diff_popover_keybind")
9195 .when(!has_keybind, |el| {
9196 let status_colors = cx.theme().status();
9197
9198 el.bg(status_colors.error_background)
9199 .border_color(status_colors.error.opacity(0.6))
9200 .child(Icon::new(IconName::Info).color(Color::Error))
9201 .cursor_default()
9202 .hoverable_tooltip(move |_window, cx| {
9203 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9204 })
9205 })
9206 .children(keybind),
9207 )
9208 .into_any();
9209
9210 let longest_row =
9211 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9212 let longest_line_width = if visible_row_range.contains(&longest_row) {
9213 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9214 } else {
9215 layout_line(
9216 longest_row,
9217 editor_snapshot,
9218 style,
9219 editor_width,
9220 |_| false,
9221 window,
9222 cx,
9223 )
9224 .width
9225 };
9226
9227 let viewport_bounds =
9228 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9229 right: -right_margin,
9230 ..Default::default()
9231 });
9232
9233 let x_after_longest = Pixels::from(
9234 ScrollPixelOffset::from(
9235 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9236 ) - scroll_pixel_position.x,
9237 );
9238
9239 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9240
9241 // Fully visible if it can be displayed within the window (allow overlapping other
9242 // panes). However, this is only allowed if the popover starts within text_bounds.
9243 let can_position_to_the_right = x_after_longest < text_bounds.right()
9244 && x_after_longest + element_bounds.width < viewport_bounds.right();
9245
9246 let mut origin = if can_position_to_the_right {
9247 point(
9248 x_after_longest,
9249 text_bounds.origin.y
9250 + Pixels::from(
9251 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9252 - scroll_pixel_position.y,
9253 ),
9254 )
9255 } else {
9256 let cursor_row = newest_selection_head.map(|head| head.row());
9257 let above_edit = edit_start
9258 .row()
9259 .0
9260 .checked_sub(line_count as u32)
9261 .map(DisplayRow);
9262 let below_edit = Some(edit_end.row() + 1);
9263 let above_cursor =
9264 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9265 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9266
9267 // Place the edit popover adjacent to the edit if there is a location
9268 // available that is onscreen and does not obscure the cursor. Otherwise,
9269 // place it adjacent to the cursor.
9270 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9271 .into_iter()
9272 .flatten()
9273 .find(|&start_row| {
9274 let end_row = start_row + line_count as u32;
9275 visible_row_range.contains(&start_row)
9276 && visible_row_range.contains(&end_row)
9277 && cursor_row
9278 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9279 })?;
9280
9281 content_origin
9282 + point(
9283 Pixels::from(-scroll_pixel_position.x),
9284 Pixels::from(
9285 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9286 ),
9287 )
9288 };
9289
9290 origin.x -= BORDER_WIDTH;
9291
9292 window.defer_draw(element, origin, 1);
9293
9294 // Do not return an element, since it will already be drawn due to defer_draw.
9295 None
9296 }
9297
9298 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9299 px(30.)
9300 }
9301
9302 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9303 if self.read_only(cx) {
9304 cx.theme().players().read_only()
9305 } else {
9306 self.style.as_ref().unwrap().local_player
9307 }
9308 }
9309
9310 fn render_edit_prediction_accept_keybind(
9311 &self,
9312 window: &mut Window,
9313 cx: &mut App,
9314 ) -> Option<AnyElement> {
9315 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9316 let accept_keystroke = accept_binding.keystroke()?;
9317
9318 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9319
9320 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9321 Color::Accent
9322 } else {
9323 Color::Muted
9324 };
9325
9326 h_flex()
9327 .px_0p5()
9328 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9329 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9330 .text_size(TextSize::XSmall.rems(cx))
9331 .child(h_flex().children(ui::render_modifiers(
9332 accept_keystroke.modifiers(),
9333 PlatformStyle::platform(),
9334 Some(modifiers_color),
9335 Some(IconSize::XSmall.rems().into()),
9336 true,
9337 )))
9338 .when(is_platform_style_mac, |parent| {
9339 parent.child(accept_keystroke.key().to_string())
9340 })
9341 .when(!is_platform_style_mac, |parent| {
9342 parent.child(
9343 Key::new(
9344 util::capitalize(accept_keystroke.key()),
9345 Some(Color::Default),
9346 )
9347 .size(Some(IconSize::XSmall.rems().into())),
9348 )
9349 })
9350 .into_any()
9351 .into()
9352 }
9353
9354 fn render_edit_prediction_line_popover(
9355 &self,
9356 label: impl Into<SharedString>,
9357 icon: Option<IconName>,
9358 window: &mut Window,
9359 cx: &mut App,
9360 ) -> Stateful<Div> {
9361 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9362
9363 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9364 let has_keybind = keybind.is_some();
9365
9366 h_flex()
9367 .id("ep-line-popover")
9368 .py_0p5()
9369 .pl_1()
9370 .pr(padding_right)
9371 .gap_1()
9372 .rounded_md()
9373 .border_1()
9374 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9375 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9376 .shadow_xs()
9377 .when(!has_keybind, |el| {
9378 let status_colors = cx.theme().status();
9379
9380 el.bg(status_colors.error_background)
9381 .border_color(status_colors.error.opacity(0.6))
9382 .pl_2()
9383 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9384 .cursor_default()
9385 .hoverable_tooltip(move |_window, cx| {
9386 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9387 })
9388 })
9389 .children(keybind)
9390 .child(
9391 Label::new(label)
9392 .size(LabelSize::Small)
9393 .when(!has_keybind, |el| {
9394 el.color(cx.theme().status().error.into()).strikethrough()
9395 }),
9396 )
9397 .when(!has_keybind, |el| {
9398 el.child(
9399 h_flex().ml_1().child(
9400 Icon::new(IconName::Info)
9401 .size(IconSize::Small)
9402 .color(cx.theme().status().error.into()),
9403 ),
9404 )
9405 })
9406 .when_some(icon, |element, icon| {
9407 element.child(
9408 div()
9409 .mt(px(1.5))
9410 .child(Icon::new(icon).size(IconSize::Small)),
9411 )
9412 })
9413 }
9414
9415 fn render_edit_prediction_jump_outside_popover(
9416 &self,
9417 snapshot: &BufferSnapshot,
9418 window: &mut Window,
9419 cx: &mut App,
9420 ) -> Stateful<Div> {
9421 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9422 let has_keybind = keybind.is_some();
9423
9424 let file_name = snapshot
9425 .file()
9426 .map(|file| SharedString::new(file.file_name(cx)))
9427 .unwrap_or(SharedString::new_static("untitled"));
9428
9429 h_flex()
9430 .id("ep-jump-outside-popover")
9431 .py_1()
9432 .px_2()
9433 .gap_1()
9434 .rounded_md()
9435 .border_1()
9436 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9437 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9438 .shadow_xs()
9439 .when(!has_keybind, |el| {
9440 let status_colors = cx.theme().status();
9441
9442 el.bg(status_colors.error_background)
9443 .border_color(status_colors.error.opacity(0.6))
9444 .pl_2()
9445 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9446 .cursor_default()
9447 .hoverable_tooltip(move |_window, cx| {
9448 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9449 })
9450 })
9451 .children(keybind)
9452 .child(
9453 Label::new(file_name)
9454 .size(LabelSize::Small)
9455 .buffer_font(cx)
9456 .when(!has_keybind, |el| {
9457 el.color(cx.theme().status().error.into()).strikethrough()
9458 }),
9459 )
9460 .when(!has_keybind, |el| {
9461 el.child(
9462 h_flex().ml_1().child(
9463 Icon::new(IconName::Info)
9464 .size(IconSize::Small)
9465 .color(cx.theme().status().error.into()),
9466 ),
9467 )
9468 })
9469 .child(
9470 div()
9471 .mt(px(1.5))
9472 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9473 )
9474 }
9475
9476 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9477 let accent_color = cx.theme().colors().text_accent;
9478 let editor_bg_color = cx.theme().colors().editor_background;
9479 editor_bg_color.blend(accent_color.opacity(0.1))
9480 }
9481
9482 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9483 let accent_color = cx.theme().colors().text_accent;
9484 let editor_bg_color = cx.theme().colors().editor_background;
9485 editor_bg_color.blend(accent_color.opacity(0.6))
9486 }
9487 fn get_prediction_provider_icon_name(
9488 provider: &Option<RegisteredEditPredictionProvider>,
9489 ) -> IconName {
9490 match provider {
9491 Some(provider) => match provider.provider.name() {
9492 "copilot" => IconName::Copilot,
9493 "supermaven" => IconName::Supermaven,
9494 _ => IconName::ZedPredict,
9495 },
9496 None => IconName::ZedPredict,
9497 }
9498 }
9499
9500 fn render_edit_prediction_cursor_popover(
9501 &self,
9502 min_width: Pixels,
9503 max_width: Pixels,
9504 cursor_point: Point,
9505 style: &EditorStyle,
9506 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9507 _window: &Window,
9508 cx: &mut Context<Editor>,
9509 ) -> Option<AnyElement> {
9510 let provider = self.edit_prediction_provider.as_ref()?;
9511 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9512
9513 let is_refreshing = provider.provider.is_refreshing(cx);
9514
9515 fn pending_completion_container(icon: IconName) -> Div {
9516 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9517 }
9518
9519 let completion = match &self.active_edit_prediction {
9520 Some(prediction) => {
9521 if !self.has_visible_completions_menu() {
9522 const RADIUS: Pixels = px(6.);
9523 const BORDER_WIDTH: Pixels = px(1.);
9524
9525 return Some(
9526 h_flex()
9527 .elevation_2(cx)
9528 .border(BORDER_WIDTH)
9529 .border_color(cx.theme().colors().border)
9530 .when(accept_keystroke.is_none(), |el| {
9531 el.border_color(cx.theme().status().error)
9532 })
9533 .rounded(RADIUS)
9534 .rounded_tl(px(0.))
9535 .overflow_hidden()
9536 .child(div().px_1p5().child(match &prediction.completion {
9537 EditPrediction::MoveWithin { target, snapshot } => {
9538 use text::ToPoint as _;
9539 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9540 {
9541 Icon::new(IconName::ZedPredictDown)
9542 } else {
9543 Icon::new(IconName::ZedPredictUp)
9544 }
9545 }
9546 EditPrediction::MoveOutside { .. } => {
9547 // TODO [zeta2] custom icon for external jump?
9548 Icon::new(provider_icon)
9549 }
9550 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9551 }))
9552 .child(
9553 h_flex()
9554 .gap_1()
9555 .py_1()
9556 .px_2()
9557 .rounded_r(RADIUS - BORDER_WIDTH)
9558 .border_l_1()
9559 .border_color(cx.theme().colors().border)
9560 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9561 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9562 el.child(
9563 Label::new("Hold")
9564 .size(LabelSize::Small)
9565 .when(accept_keystroke.is_none(), |el| {
9566 el.strikethrough()
9567 })
9568 .line_height_style(LineHeightStyle::UiLabel),
9569 )
9570 })
9571 .id("edit_prediction_cursor_popover_keybind")
9572 .when(accept_keystroke.is_none(), |el| {
9573 let status_colors = cx.theme().status();
9574
9575 el.bg(status_colors.error_background)
9576 .border_color(status_colors.error.opacity(0.6))
9577 .child(Icon::new(IconName::Info).color(Color::Error))
9578 .cursor_default()
9579 .hoverable_tooltip(move |_window, cx| {
9580 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9581 .into()
9582 })
9583 })
9584 .when_some(
9585 accept_keystroke.as_ref(),
9586 |el, accept_keystroke| {
9587 el.child(h_flex().children(ui::render_modifiers(
9588 accept_keystroke.modifiers(),
9589 PlatformStyle::platform(),
9590 Some(Color::Default),
9591 Some(IconSize::XSmall.rems().into()),
9592 false,
9593 )))
9594 },
9595 ),
9596 )
9597 .into_any(),
9598 );
9599 }
9600
9601 self.render_edit_prediction_cursor_popover_preview(
9602 prediction,
9603 cursor_point,
9604 style,
9605 cx,
9606 )?
9607 }
9608
9609 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9610 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9611 stale_completion,
9612 cursor_point,
9613 style,
9614 cx,
9615 )?,
9616
9617 None => pending_completion_container(provider_icon)
9618 .child(Label::new("...").size(LabelSize::Small)),
9619 },
9620
9621 None => pending_completion_container(provider_icon)
9622 .child(Label::new("...").size(LabelSize::Small)),
9623 };
9624
9625 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9626 completion
9627 .with_animation(
9628 "loading-completion",
9629 Animation::new(Duration::from_secs(2))
9630 .repeat()
9631 .with_easing(pulsating_between(0.4, 0.8)),
9632 |label, delta| label.opacity(delta),
9633 )
9634 .into_any_element()
9635 } else {
9636 completion.into_any_element()
9637 };
9638
9639 let has_completion = self.active_edit_prediction.is_some();
9640
9641 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9642 Some(
9643 h_flex()
9644 .min_w(min_width)
9645 .max_w(max_width)
9646 .flex_1()
9647 .elevation_2(cx)
9648 .border_color(cx.theme().colors().border)
9649 .child(
9650 div()
9651 .flex_1()
9652 .py_1()
9653 .px_2()
9654 .overflow_hidden()
9655 .child(completion),
9656 )
9657 .when_some(accept_keystroke, |el, accept_keystroke| {
9658 if !accept_keystroke.modifiers().modified() {
9659 return el;
9660 }
9661
9662 el.child(
9663 h_flex()
9664 .h_full()
9665 .border_l_1()
9666 .rounded_r_lg()
9667 .border_color(cx.theme().colors().border)
9668 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9669 .gap_1()
9670 .py_1()
9671 .px_2()
9672 .child(
9673 h_flex()
9674 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9675 .when(is_platform_style_mac, |parent| parent.gap_1())
9676 .child(h_flex().children(ui::render_modifiers(
9677 accept_keystroke.modifiers(),
9678 PlatformStyle::platform(),
9679 Some(if !has_completion {
9680 Color::Muted
9681 } else {
9682 Color::Default
9683 }),
9684 None,
9685 false,
9686 ))),
9687 )
9688 .child(Label::new("Preview").into_any_element())
9689 .opacity(if has_completion { 1.0 } else { 0.4 }),
9690 )
9691 })
9692 .into_any(),
9693 )
9694 }
9695
9696 fn render_edit_prediction_cursor_popover_preview(
9697 &self,
9698 completion: &EditPredictionState,
9699 cursor_point: Point,
9700 style: &EditorStyle,
9701 cx: &mut Context<Editor>,
9702 ) -> Option<Div> {
9703 use text::ToPoint as _;
9704
9705 fn render_relative_row_jump(
9706 prefix: impl Into<String>,
9707 current_row: u32,
9708 target_row: u32,
9709 ) -> Div {
9710 let (row_diff, arrow) = if target_row < current_row {
9711 (current_row - target_row, IconName::ArrowUp)
9712 } else {
9713 (target_row - current_row, IconName::ArrowDown)
9714 };
9715
9716 h_flex()
9717 .child(
9718 Label::new(format!("{}{}", prefix.into(), row_diff))
9719 .color(Color::Muted)
9720 .size(LabelSize::Small),
9721 )
9722 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9723 }
9724
9725 let supports_jump = self
9726 .edit_prediction_provider
9727 .as_ref()
9728 .map(|provider| provider.provider.supports_jump_to_edit())
9729 .unwrap_or(true);
9730
9731 match &completion.completion {
9732 EditPrediction::MoveWithin {
9733 target, snapshot, ..
9734 } => {
9735 if !supports_jump {
9736 return None;
9737 }
9738
9739 Some(
9740 h_flex()
9741 .px_2()
9742 .gap_2()
9743 .flex_1()
9744 .child(
9745 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9746 Icon::new(IconName::ZedPredictDown)
9747 } else {
9748 Icon::new(IconName::ZedPredictUp)
9749 },
9750 )
9751 .child(Label::new("Jump to Edit")),
9752 )
9753 }
9754 EditPrediction::MoveOutside { snapshot, .. } => {
9755 let file_name = snapshot
9756 .file()
9757 .map(|file| file.file_name(cx))
9758 .unwrap_or("untitled");
9759 Some(
9760 h_flex()
9761 .px_2()
9762 .gap_2()
9763 .flex_1()
9764 .child(Icon::new(IconName::ZedPredict))
9765 .child(Label::new(format!("Jump to {file_name}"))),
9766 )
9767 }
9768 EditPrediction::Edit {
9769 edits,
9770 edit_preview,
9771 snapshot,
9772 display_mode: _,
9773 } => {
9774 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9775
9776 let (highlighted_edits, has_more_lines) =
9777 if let Some(edit_preview) = edit_preview.as_ref() {
9778 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9779 .first_line_preview()
9780 } else {
9781 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9782 };
9783
9784 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9785 .with_default_highlights(&style.text, highlighted_edits.highlights);
9786
9787 let preview = h_flex()
9788 .gap_1()
9789 .min_w_16()
9790 .child(styled_text)
9791 .when(has_more_lines, |parent| parent.child("…"));
9792
9793 let left = if supports_jump && first_edit_row != cursor_point.row {
9794 render_relative_row_jump("", cursor_point.row, first_edit_row)
9795 .into_any_element()
9796 } else {
9797 let icon_name =
9798 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9799 Icon::new(icon_name).into_any_element()
9800 };
9801
9802 Some(
9803 h_flex()
9804 .h_full()
9805 .flex_1()
9806 .gap_2()
9807 .pr_1()
9808 .overflow_x_hidden()
9809 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9810 .child(left)
9811 .child(preview),
9812 )
9813 }
9814 }
9815 }
9816
9817 pub fn render_context_menu(
9818 &self,
9819 style: &EditorStyle,
9820 max_height_in_lines: u32,
9821 window: &mut Window,
9822 cx: &mut Context<Editor>,
9823 ) -> Option<AnyElement> {
9824 let menu = self.context_menu.borrow();
9825 let menu = menu.as_ref()?;
9826 if !menu.visible() {
9827 return None;
9828 };
9829 Some(menu.render(style, max_height_in_lines, window, cx))
9830 }
9831
9832 fn render_context_menu_aside(
9833 &mut self,
9834 max_size: Size<Pixels>,
9835 window: &mut Window,
9836 cx: &mut Context<Editor>,
9837 ) -> Option<AnyElement> {
9838 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9839 if menu.visible() {
9840 menu.render_aside(max_size, window, cx)
9841 } else {
9842 None
9843 }
9844 })
9845 }
9846
9847 fn hide_context_menu(
9848 &mut self,
9849 window: &mut Window,
9850 cx: &mut Context<Self>,
9851 ) -> Option<CodeContextMenu> {
9852 cx.notify();
9853 self.completion_tasks.clear();
9854 let context_menu = self.context_menu.borrow_mut().take();
9855 self.stale_edit_prediction_in_menu.take();
9856 self.update_visible_edit_prediction(window, cx);
9857 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9858 && let Some(completion_provider) = &self.completion_provider
9859 {
9860 completion_provider.selection_changed(None, window, cx);
9861 }
9862 context_menu
9863 }
9864
9865 fn show_snippet_choices(
9866 &mut self,
9867 choices: &Vec<String>,
9868 selection: Range<Anchor>,
9869 cx: &mut Context<Self>,
9870 ) {
9871 let Some((_, buffer, _)) = self
9872 .buffer()
9873 .read(cx)
9874 .excerpt_containing(selection.start, cx)
9875 else {
9876 return;
9877 };
9878 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9879 else {
9880 return;
9881 };
9882 if buffer != end_buffer {
9883 log::error!("expected anchor range to have matching buffer IDs");
9884 return;
9885 }
9886
9887 let id = post_inc(&mut self.next_completion_id);
9888 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9889 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9890 CompletionsMenu::new_snippet_choices(
9891 id,
9892 true,
9893 choices,
9894 selection,
9895 buffer,
9896 snippet_sort_order,
9897 ),
9898 ));
9899 }
9900
9901 pub fn insert_snippet(
9902 &mut self,
9903 insertion_ranges: &[Range<MultiBufferOffset>],
9904 snippet: Snippet,
9905 window: &mut Window,
9906 cx: &mut Context<Self>,
9907 ) -> Result<()> {
9908 struct Tabstop<T> {
9909 is_end_tabstop: bool,
9910 ranges: Vec<Range<T>>,
9911 choices: Option<Vec<String>>,
9912 }
9913
9914 let tabstops = self.buffer.update(cx, |buffer, cx| {
9915 let snippet_text: Arc<str> = snippet.text.clone().into();
9916 let edits = insertion_ranges
9917 .iter()
9918 .cloned()
9919 .map(|range| (range, snippet_text.clone()));
9920 let autoindent_mode = AutoindentMode::Block {
9921 original_indent_columns: Vec::new(),
9922 };
9923 buffer.edit(edits, Some(autoindent_mode), cx);
9924
9925 let snapshot = &*buffer.read(cx);
9926 let snippet = &snippet;
9927 snippet
9928 .tabstops
9929 .iter()
9930 .map(|tabstop| {
9931 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9932 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9933 });
9934 let mut tabstop_ranges = tabstop
9935 .ranges
9936 .iter()
9937 .flat_map(|tabstop_range| {
9938 let mut delta = 0_isize;
9939 insertion_ranges.iter().map(move |insertion_range| {
9940 let insertion_start = insertion_range.start + delta;
9941 delta += snippet.text.len() as isize
9942 - (insertion_range.end - insertion_range.start) as isize;
9943
9944 let start =
9945 (insertion_start + tabstop_range.start).min(snapshot.len());
9946 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
9947 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9948 })
9949 })
9950 .collect::<Vec<_>>();
9951 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9952
9953 Tabstop {
9954 is_end_tabstop,
9955 ranges: tabstop_ranges,
9956 choices: tabstop.choices.clone(),
9957 }
9958 })
9959 .collect::<Vec<_>>()
9960 });
9961 if let Some(tabstop) = tabstops.first() {
9962 self.change_selections(Default::default(), window, cx, |s| {
9963 // Reverse order so that the first range is the newest created selection.
9964 // Completions will use it and autoscroll will prioritize it.
9965 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9966 });
9967
9968 if let Some(choices) = &tabstop.choices
9969 && let Some(selection) = tabstop.ranges.first()
9970 {
9971 self.show_snippet_choices(choices, selection.clone(), cx)
9972 }
9973
9974 // If we're already at the last tabstop and it's at the end of the snippet,
9975 // we're done, we don't need to keep the state around.
9976 if !tabstop.is_end_tabstop {
9977 let choices = tabstops
9978 .iter()
9979 .map(|tabstop| tabstop.choices.clone())
9980 .collect();
9981
9982 let ranges = tabstops
9983 .into_iter()
9984 .map(|tabstop| tabstop.ranges)
9985 .collect::<Vec<_>>();
9986
9987 self.snippet_stack.push(SnippetState {
9988 active_index: 0,
9989 ranges,
9990 choices,
9991 });
9992 }
9993
9994 // Check whether the just-entered snippet ends with an auto-closable bracket.
9995 if self.autoclose_regions.is_empty() {
9996 let snapshot = self.buffer.read(cx).snapshot(cx);
9997 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9998 let selection_head = selection.head();
9999 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10000 continue;
10001 };
10002
10003 let mut bracket_pair = None;
10004 let max_lookup_length = scope
10005 .brackets()
10006 .map(|(pair, _)| {
10007 pair.start
10008 .as_str()
10009 .chars()
10010 .count()
10011 .max(pair.end.as_str().chars().count())
10012 })
10013 .max();
10014 if let Some(max_lookup_length) = max_lookup_length {
10015 let next_text = snapshot
10016 .chars_at(selection_head)
10017 .take(max_lookup_length)
10018 .collect::<String>();
10019 let prev_text = snapshot
10020 .reversed_chars_at(selection_head)
10021 .take(max_lookup_length)
10022 .collect::<String>();
10023
10024 for (pair, enabled) in scope.brackets() {
10025 if enabled
10026 && pair.close
10027 && prev_text.starts_with(pair.start.as_str())
10028 && next_text.starts_with(pair.end.as_str())
10029 {
10030 bracket_pair = Some(pair.clone());
10031 break;
10032 }
10033 }
10034 }
10035
10036 if let Some(pair) = bracket_pair {
10037 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10038 let autoclose_enabled =
10039 self.use_autoclose && snapshot_settings.use_autoclose;
10040 if autoclose_enabled {
10041 let start = snapshot.anchor_after(selection_head);
10042 let end = snapshot.anchor_after(selection_head);
10043 self.autoclose_regions.push(AutocloseRegion {
10044 selection_id: selection.id,
10045 range: start..end,
10046 pair,
10047 });
10048 }
10049 }
10050 }
10051 }
10052 }
10053 Ok(())
10054 }
10055
10056 pub fn move_to_next_snippet_tabstop(
10057 &mut self,
10058 window: &mut Window,
10059 cx: &mut Context<Self>,
10060 ) -> bool {
10061 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10062 }
10063
10064 pub fn move_to_prev_snippet_tabstop(
10065 &mut self,
10066 window: &mut Window,
10067 cx: &mut Context<Self>,
10068 ) -> bool {
10069 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10070 }
10071
10072 pub fn move_to_snippet_tabstop(
10073 &mut self,
10074 bias: Bias,
10075 window: &mut Window,
10076 cx: &mut Context<Self>,
10077 ) -> bool {
10078 if let Some(mut snippet) = self.snippet_stack.pop() {
10079 match bias {
10080 Bias::Left => {
10081 if snippet.active_index > 0 {
10082 snippet.active_index -= 1;
10083 } else {
10084 self.snippet_stack.push(snippet);
10085 return false;
10086 }
10087 }
10088 Bias::Right => {
10089 if snippet.active_index + 1 < snippet.ranges.len() {
10090 snippet.active_index += 1;
10091 } else {
10092 self.snippet_stack.push(snippet);
10093 return false;
10094 }
10095 }
10096 }
10097 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10098 self.change_selections(Default::default(), window, cx, |s| {
10099 // Reverse order so that the first range is the newest created selection.
10100 // Completions will use it and autoscroll will prioritize it.
10101 s.select_ranges(current_ranges.iter().rev().cloned())
10102 });
10103
10104 if let Some(choices) = &snippet.choices[snippet.active_index]
10105 && let Some(selection) = current_ranges.first()
10106 {
10107 self.show_snippet_choices(choices, selection.clone(), cx);
10108 }
10109
10110 // If snippet state is not at the last tabstop, push it back on the stack
10111 if snippet.active_index + 1 < snippet.ranges.len() {
10112 self.snippet_stack.push(snippet);
10113 }
10114 return true;
10115 }
10116 }
10117
10118 false
10119 }
10120
10121 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10122 self.transact(window, cx, |this, window, cx| {
10123 this.select_all(&SelectAll, window, cx);
10124 this.insert("", window, cx);
10125 });
10126 }
10127
10128 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10129 if self.read_only(cx) {
10130 return;
10131 }
10132 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10133 self.transact(window, cx, |this, window, cx| {
10134 this.select_autoclose_pair(window, cx);
10135
10136 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10137
10138 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10139 if !this.linked_edit_ranges.is_empty() {
10140 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10141 let snapshot = this.buffer.read(cx).snapshot(cx);
10142
10143 for selection in selections.iter() {
10144 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10145 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10146 if selection_start.buffer_id != selection_end.buffer_id {
10147 continue;
10148 }
10149 if let Some(ranges) =
10150 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10151 {
10152 for (buffer, entries) in ranges {
10153 linked_ranges.entry(buffer).or_default().extend(entries);
10154 }
10155 }
10156 }
10157 }
10158
10159 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10160 for selection in &mut selections {
10161 if selection.is_empty() {
10162 let old_head = selection.head();
10163 let mut new_head =
10164 movement::left(&display_map, old_head.to_display_point(&display_map))
10165 .to_point(&display_map);
10166 if let Some((buffer, line_buffer_range)) = display_map
10167 .buffer_snapshot()
10168 .buffer_line_for_row(MultiBufferRow(old_head.row))
10169 {
10170 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10171 let indent_len = match indent_size.kind {
10172 IndentKind::Space => {
10173 buffer.settings_at(line_buffer_range.start, cx).tab_size
10174 }
10175 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10176 };
10177 if old_head.column <= indent_size.len && old_head.column > 0 {
10178 let indent_len = indent_len.get();
10179 new_head = cmp::min(
10180 new_head,
10181 MultiBufferPoint::new(
10182 old_head.row,
10183 ((old_head.column - 1) / indent_len) * indent_len,
10184 ),
10185 );
10186 }
10187 }
10188
10189 selection.set_head(new_head, SelectionGoal::None);
10190 }
10191 }
10192
10193 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10194 this.insert("", window, cx);
10195 let empty_str: Arc<str> = Arc::from("");
10196 for (buffer, edits) in linked_ranges {
10197 let snapshot = buffer.read(cx).snapshot();
10198 use text::ToPoint as TP;
10199
10200 let edits = edits
10201 .into_iter()
10202 .map(|range| {
10203 let end_point = TP::to_point(&range.end, &snapshot);
10204 let mut start_point = TP::to_point(&range.start, &snapshot);
10205
10206 if end_point == start_point {
10207 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10208 .saturating_sub(1);
10209 start_point =
10210 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10211 };
10212
10213 (start_point..end_point, empty_str.clone())
10214 })
10215 .sorted_by_key(|(range, _)| range.start)
10216 .collect::<Vec<_>>();
10217 buffer.update(cx, |this, cx| {
10218 this.edit(edits, None, cx);
10219 })
10220 }
10221 this.refresh_edit_prediction(true, false, window, cx);
10222 refresh_linked_ranges(this, window, cx);
10223 });
10224 }
10225
10226 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10227 if self.read_only(cx) {
10228 return;
10229 }
10230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10231 self.transact(window, cx, |this, window, cx| {
10232 this.change_selections(Default::default(), window, cx, |s| {
10233 s.move_with(|map, selection| {
10234 if selection.is_empty() {
10235 let cursor = movement::right(map, selection.head());
10236 selection.end = cursor;
10237 selection.reversed = true;
10238 selection.goal = SelectionGoal::None;
10239 }
10240 })
10241 });
10242 this.insert("", window, cx);
10243 this.refresh_edit_prediction(true, false, window, cx);
10244 });
10245 }
10246
10247 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10248 if self.mode.is_single_line() {
10249 cx.propagate();
10250 return;
10251 }
10252
10253 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10254 if self.move_to_prev_snippet_tabstop(window, cx) {
10255 return;
10256 }
10257 self.outdent(&Outdent, window, cx);
10258 }
10259
10260 pub fn next_snippet_tabstop(
10261 &mut self,
10262 _: &NextSnippetTabstop,
10263 window: &mut Window,
10264 cx: &mut Context<Self>,
10265 ) {
10266 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10267 cx.propagate();
10268 return;
10269 }
10270
10271 if self.move_to_next_snippet_tabstop(window, cx) {
10272 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10273 return;
10274 }
10275 cx.propagate();
10276 }
10277
10278 pub fn previous_snippet_tabstop(
10279 &mut self,
10280 _: &PreviousSnippetTabstop,
10281 window: &mut Window,
10282 cx: &mut Context<Self>,
10283 ) {
10284 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10285 cx.propagate();
10286 return;
10287 }
10288
10289 if self.move_to_prev_snippet_tabstop(window, cx) {
10290 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10291 return;
10292 }
10293 cx.propagate();
10294 }
10295
10296 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10297 if self.mode.is_single_line() {
10298 cx.propagate();
10299 return;
10300 }
10301
10302 if self.move_to_next_snippet_tabstop(window, cx) {
10303 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10304 return;
10305 }
10306 if self.read_only(cx) {
10307 return;
10308 }
10309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10310 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10311 let buffer = self.buffer.read(cx);
10312 let snapshot = buffer.snapshot(cx);
10313 let rows_iter = selections.iter().map(|s| s.head().row);
10314 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10315
10316 let has_some_cursor_in_whitespace = selections
10317 .iter()
10318 .filter(|selection| selection.is_empty())
10319 .any(|selection| {
10320 let cursor = selection.head();
10321 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10322 cursor.column < current_indent.len
10323 });
10324
10325 let mut edits = Vec::new();
10326 let mut prev_edited_row = 0;
10327 let mut row_delta = 0;
10328 for selection in &mut selections {
10329 if selection.start.row != prev_edited_row {
10330 row_delta = 0;
10331 }
10332 prev_edited_row = selection.end.row;
10333
10334 // If the selection is non-empty, then increase the indentation of the selected lines.
10335 if !selection.is_empty() {
10336 row_delta =
10337 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10338 continue;
10339 }
10340
10341 let cursor = selection.head();
10342 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10343 if let Some(suggested_indent) =
10344 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10345 {
10346 // Don't do anything if already at suggested indent
10347 // and there is any other cursor which is not
10348 if has_some_cursor_in_whitespace
10349 && cursor.column == current_indent.len
10350 && current_indent.len == suggested_indent.len
10351 {
10352 continue;
10353 }
10354
10355 // Adjust line and move cursor to suggested indent
10356 // if cursor is not at suggested indent
10357 if cursor.column < suggested_indent.len
10358 && cursor.column <= current_indent.len
10359 && current_indent.len <= suggested_indent.len
10360 {
10361 selection.start = Point::new(cursor.row, suggested_indent.len);
10362 selection.end = selection.start;
10363 if row_delta == 0 {
10364 edits.extend(Buffer::edit_for_indent_size_adjustment(
10365 cursor.row,
10366 current_indent,
10367 suggested_indent,
10368 ));
10369 row_delta = suggested_indent.len - current_indent.len;
10370 }
10371 continue;
10372 }
10373
10374 // If current indent is more than suggested indent
10375 // only move cursor to current indent and skip indent
10376 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10377 selection.start = Point::new(cursor.row, current_indent.len);
10378 selection.end = selection.start;
10379 continue;
10380 }
10381 }
10382
10383 // Otherwise, insert a hard or soft tab.
10384 let settings = buffer.language_settings_at(cursor, cx);
10385 let tab_size = if settings.hard_tabs {
10386 IndentSize::tab()
10387 } else {
10388 let tab_size = settings.tab_size.get();
10389 let indent_remainder = snapshot
10390 .text_for_range(Point::new(cursor.row, 0)..cursor)
10391 .flat_map(str::chars)
10392 .fold(row_delta % tab_size, |counter: u32, c| {
10393 if c == '\t' {
10394 0
10395 } else {
10396 (counter + 1) % tab_size
10397 }
10398 });
10399
10400 let chars_to_next_tab_stop = tab_size - indent_remainder;
10401 IndentSize::spaces(chars_to_next_tab_stop)
10402 };
10403 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10404 selection.end = selection.start;
10405 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10406 row_delta += tab_size.len;
10407 }
10408
10409 self.transact(window, cx, |this, window, cx| {
10410 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10411 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10412 this.refresh_edit_prediction(true, false, window, cx);
10413 });
10414 }
10415
10416 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10417 if self.read_only(cx) {
10418 return;
10419 }
10420 if self.mode.is_single_line() {
10421 cx.propagate();
10422 return;
10423 }
10424
10425 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10426 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10427 let mut prev_edited_row = 0;
10428 let mut row_delta = 0;
10429 let mut edits = Vec::new();
10430 let buffer = self.buffer.read(cx);
10431 let snapshot = buffer.snapshot(cx);
10432 for selection in &mut selections {
10433 if selection.start.row != prev_edited_row {
10434 row_delta = 0;
10435 }
10436 prev_edited_row = selection.end.row;
10437
10438 row_delta =
10439 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10440 }
10441
10442 self.transact(window, cx, |this, window, cx| {
10443 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10444 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10445 });
10446 }
10447
10448 fn indent_selection(
10449 buffer: &MultiBuffer,
10450 snapshot: &MultiBufferSnapshot,
10451 selection: &mut Selection<Point>,
10452 edits: &mut Vec<(Range<Point>, String)>,
10453 delta_for_start_row: u32,
10454 cx: &App,
10455 ) -> u32 {
10456 let settings = buffer.language_settings_at(selection.start, cx);
10457 let tab_size = settings.tab_size.get();
10458 let indent_kind = if settings.hard_tabs {
10459 IndentKind::Tab
10460 } else {
10461 IndentKind::Space
10462 };
10463 let mut start_row = selection.start.row;
10464 let mut end_row = selection.end.row + 1;
10465
10466 // If a selection ends at the beginning of a line, don't indent
10467 // that last line.
10468 if selection.end.column == 0 && selection.end.row > selection.start.row {
10469 end_row -= 1;
10470 }
10471
10472 // Avoid re-indenting a row that has already been indented by a
10473 // previous selection, but still update this selection's column
10474 // to reflect that indentation.
10475 if delta_for_start_row > 0 {
10476 start_row += 1;
10477 selection.start.column += delta_for_start_row;
10478 if selection.end.row == selection.start.row {
10479 selection.end.column += delta_for_start_row;
10480 }
10481 }
10482
10483 let mut delta_for_end_row = 0;
10484 let has_multiple_rows = start_row + 1 != end_row;
10485 for row in start_row..end_row {
10486 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10487 let indent_delta = match (current_indent.kind, indent_kind) {
10488 (IndentKind::Space, IndentKind::Space) => {
10489 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10490 IndentSize::spaces(columns_to_next_tab_stop)
10491 }
10492 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10493 (_, IndentKind::Tab) => IndentSize::tab(),
10494 };
10495
10496 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10497 0
10498 } else {
10499 selection.start.column
10500 };
10501 let row_start = Point::new(row, start);
10502 edits.push((
10503 row_start..row_start,
10504 indent_delta.chars().collect::<String>(),
10505 ));
10506
10507 // Update this selection's endpoints to reflect the indentation.
10508 if row == selection.start.row {
10509 selection.start.column += indent_delta.len;
10510 }
10511 if row == selection.end.row {
10512 selection.end.column += indent_delta.len;
10513 delta_for_end_row = indent_delta.len;
10514 }
10515 }
10516
10517 if selection.start.row == selection.end.row {
10518 delta_for_start_row + delta_for_end_row
10519 } else {
10520 delta_for_end_row
10521 }
10522 }
10523
10524 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10525 if self.read_only(cx) {
10526 return;
10527 }
10528 if self.mode.is_single_line() {
10529 cx.propagate();
10530 return;
10531 }
10532
10533 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10534 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10535 let selections = self.selections.all::<Point>(&display_map);
10536 let mut deletion_ranges = Vec::new();
10537 let mut last_outdent = None;
10538 {
10539 let buffer = self.buffer.read(cx);
10540 let snapshot = buffer.snapshot(cx);
10541 for selection in &selections {
10542 let settings = buffer.language_settings_at(selection.start, cx);
10543 let tab_size = settings.tab_size.get();
10544 let mut rows = selection.spanned_rows(false, &display_map);
10545
10546 // Avoid re-outdenting a row that has already been outdented by a
10547 // previous selection.
10548 if let Some(last_row) = last_outdent
10549 && last_row == rows.start
10550 {
10551 rows.start = rows.start.next_row();
10552 }
10553 let has_multiple_rows = rows.len() > 1;
10554 for row in rows.iter_rows() {
10555 let indent_size = snapshot.indent_size_for_line(row);
10556 if indent_size.len > 0 {
10557 let deletion_len = match indent_size.kind {
10558 IndentKind::Space => {
10559 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10560 if columns_to_prev_tab_stop == 0 {
10561 tab_size
10562 } else {
10563 columns_to_prev_tab_stop
10564 }
10565 }
10566 IndentKind::Tab => 1,
10567 };
10568 let start = if has_multiple_rows
10569 || deletion_len > selection.start.column
10570 || indent_size.len < selection.start.column
10571 {
10572 0
10573 } else {
10574 selection.start.column - deletion_len
10575 };
10576 deletion_ranges.push(
10577 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10578 );
10579 last_outdent = Some(row);
10580 }
10581 }
10582 }
10583 }
10584
10585 self.transact(window, cx, |this, window, cx| {
10586 this.buffer.update(cx, |buffer, cx| {
10587 let empty_str: Arc<str> = Arc::default();
10588 buffer.edit(
10589 deletion_ranges
10590 .into_iter()
10591 .map(|range| (range, empty_str.clone())),
10592 None,
10593 cx,
10594 );
10595 });
10596 let selections = this
10597 .selections
10598 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10599 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10600 });
10601 }
10602
10603 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10604 if self.read_only(cx) {
10605 return;
10606 }
10607 if self.mode.is_single_line() {
10608 cx.propagate();
10609 return;
10610 }
10611
10612 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10613 let selections = self
10614 .selections
10615 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10616 .into_iter()
10617 .map(|s| s.range());
10618
10619 self.transact(window, cx, |this, window, cx| {
10620 this.buffer.update(cx, |buffer, cx| {
10621 buffer.autoindent_ranges(selections, cx);
10622 });
10623 let selections = this
10624 .selections
10625 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10626 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10627 });
10628 }
10629
10630 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10631 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10632 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10633 let selections = self.selections.all::<Point>(&display_map);
10634
10635 let mut new_cursors = Vec::new();
10636 let mut edit_ranges = Vec::new();
10637 let mut selections = selections.iter().peekable();
10638 while let Some(selection) = selections.next() {
10639 let mut rows = selection.spanned_rows(false, &display_map);
10640
10641 // Accumulate contiguous regions of rows that we want to delete.
10642 while let Some(next_selection) = selections.peek() {
10643 let next_rows = next_selection.spanned_rows(false, &display_map);
10644 if next_rows.start <= rows.end {
10645 rows.end = next_rows.end;
10646 selections.next().unwrap();
10647 } else {
10648 break;
10649 }
10650 }
10651
10652 let buffer = display_map.buffer_snapshot();
10653 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10654 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10655 // If there's a line after the range, delete the \n from the end of the row range
10656 (
10657 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10658 rows.end,
10659 )
10660 } else {
10661 // If there isn't a line after the range, delete the \n from the line before the
10662 // start of the row range
10663 edit_start = edit_start.saturating_sub_usize(1);
10664 (buffer.len(), rows.start.previous_row())
10665 };
10666
10667 let text_layout_details = self.text_layout_details(window);
10668 let x = display_map.x_for_display_point(
10669 selection.head().to_display_point(&display_map),
10670 &text_layout_details,
10671 );
10672 let row = Point::new(target_row.0, 0)
10673 .to_display_point(&display_map)
10674 .row();
10675 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10676
10677 new_cursors.push((
10678 selection.id,
10679 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10680 SelectionGoal::None,
10681 ));
10682 edit_ranges.push(edit_start..edit_end);
10683 }
10684
10685 self.transact(window, cx, |this, window, cx| {
10686 let buffer = this.buffer.update(cx, |buffer, cx| {
10687 let empty_str: Arc<str> = Arc::default();
10688 buffer.edit(
10689 edit_ranges
10690 .into_iter()
10691 .map(|range| (range, empty_str.clone())),
10692 None,
10693 cx,
10694 );
10695 buffer.snapshot(cx)
10696 });
10697 let new_selections = new_cursors
10698 .into_iter()
10699 .map(|(id, cursor, goal)| {
10700 let cursor = cursor.to_point(&buffer);
10701 Selection {
10702 id,
10703 start: cursor,
10704 end: cursor,
10705 reversed: false,
10706 goal,
10707 }
10708 })
10709 .collect();
10710
10711 this.change_selections(Default::default(), window, cx, |s| {
10712 s.select(new_selections);
10713 });
10714 });
10715 }
10716
10717 pub fn join_lines_impl(
10718 &mut self,
10719 insert_whitespace: bool,
10720 window: &mut Window,
10721 cx: &mut Context<Self>,
10722 ) {
10723 if self.read_only(cx) {
10724 return;
10725 }
10726 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10727 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10728 let start = MultiBufferRow(selection.start.row);
10729 // Treat single line selections as if they include the next line. Otherwise this action
10730 // would do nothing for single line selections individual cursors.
10731 let end = if selection.start.row == selection.end.row {
10732 MultiBufferRow(selection.start.row + 1)
10733 } else {
10734 MultiBufferRow(selection.end.row)
10735 };
10736
10737 if let Some(last_row_range) = row_ranges.last_mut()
10738 && start <= last_row_range.end
10739 {
10740 last_row_range.end = end;
10741 continue;
10742 }
10743 row_ranges.push(start..end);
10744 }
10745
10746 let snapshot = self.buffer.read(cx).snapshot(cx);
10747 let mut cursor_positions = Vec::new();
10748 for row_range in &row_ranges {
10749 let anchor = snapshot.anchor_before(Point::new(
10750 row_range.end.previous_row().0,
10751 snapshot.line_len(row_range.end.previous_row()),
10752 ));
10753 cursor_positions.push(anchor..anchor);
10754 }
10755
10756 self.transact(window, cx, |this, window, cx| {
10757 for row_range in row_ranges.into_iter().rev() {
10758 for row in row_range.iter_rows().rev() {
10759 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10760 let next_line_row = row.next_row();
10761 let indent = snapshot.indent_size_for_line(next_line_row);
10762 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10763
10764 let replace =
10765 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10766 " "
10767 } else {
10768 ""
10769 };
10770
10771 this.buffer.update(cx, |buffer, cx| {
10772 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10773 });
10774 }
10775 }
10776
10777 this.change_selections(Default::default(), window, cx, |s| {
10778 s.select_anchor_ranges(cursor_positions)
10779 });
10780 });
10781 }
10782
10783 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10784 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10785 self.join_lines_impl(true, window, cx);
10786 }
10787
10788 pub fn sort_lines_case_sensitive(
10789 &mut self,
10790 _: &SortLinesCaseSensitive,
10791 window: &mut Window,
10792 cx: &mut Context<Self>,
10793 ) {
10794 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10795 }
10796
10797 pub fn sort_lines_by_length(
10798 &mut self,
10799 _: &SortLinesByLength,
10800 window: &mut Window,
10801 cx: &mut Context<Self>,
10802 ) {
10803 self.manipulate_immutable_lines(window, cx, |lines| {
10804 lines.sort_by_key(|&line| line.chars().count())
10805 })
10806 }
10807
10808 pub fn sort_lines_case_insensitive(
10809 &mut self,
10810 _: &SortLinesCaseInsensitive,
10811 window: &mut Window,
10812 cx: &mut Context<Self>,
10813 ) {
10814 self.manipulate_immutable_lines(window, cx, |lines| {
10815 lines.sort_by_key(|line| line.to_lowercase())
10816 })
10817 }
10818
10819 pub fn unique_lines_case_insensitive(
10820 &mut self,
10821 _: &UniqueLinesCaseInsensitive,
10822 window: &mut Window,
10823 cx: &mut Context<Self>,
10824 ) {
10825 self.manipulate_immutable_lines(window, cx, |lines| {
10826 let mut seen = HashSet::default();
10827 lines.retain(|line| seen.insert(line.to_lowercase()));
10828 })
10829 }
10830
10831 pub fn unique_lines_case_sensitive(
10832 &mut self,
10833 _: &UniqueLinesCaseSensitive,
10834 window: &mut Window,
10835 cx: &mut Context<Self>,
10836 ) {
10837 self.manipulate_immutable_lines(window, cx, |lines| {
10838 let mut seen = HashSet::default();
10839 lines.retain(|line| seen.insert(*line));
10840 })
10841 }
10842
10843 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10844 let snapshot = self.buffer.read(cx).snapshot(cx);
10845 for selection in self.selections.disjoint_anchors_arc().iter() {
10846 if snapshot
10847 .language_at(selection.start)
10848 .and_then(|lang| lang.config().wrap_characters.as_ref())
10849 .is_some()
10850 {
10851 return true;
10852 }
10853 }
10854 false
10855 }
10856
10857 fn wrap_selections_in_tag(
10858 &mut self,
10859 _: &WrapSelectionsInTag,
10860 window: &mut Window,
10861 cx: &mut Context<Self>,
10862 ) {
10863 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10864
10865 let snapshot = self.buffer.read(cx).snapshot(cx);
10866
10867 let mut edits = Vec::new();
10868 let mut boundaries = Vec::new();
10869
10870 for selection in self
10871 .selections
10872 .all_adjusted(&self.display_snapshot(cx))
10873 .iter()
10874 {
10875 let Some(wrap_config) = snapshot
10876 .language_at(selection.start)
10877 .and_then(|lang| lang.config().wrap_characters.clone())
10878 else {
10879 continue;
10880 };
10881
10882 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10883 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10884
10885 let start_before = snapshot.anchor_before(selection.start);
10886 let end_after = snapshot.anchor_after(selection.end);
10887
10888 edits.push((start_before..start_before, open_tag));
10889 edits.push((end_after..end_after, close_tag));
10890
10891 boundaries.push((
10892 start_before,
10893 end_after,
10894 wrap_config.start_prefix.len(),
10895 wrap_config.end_suffix.len(),
10896 ));
10897 }
10898
10899 if edits.is_empty() {
10900 return;
10901 }
10902
10903 self.transact(window, cx, |this, window, cx| {
10904 let buffer = this.buffer.update(cx, |buffer, cx| {
10905 buffer.edit(edits, None, cx);
10906 buffer.snapshot(cx)
10907 });
10908
10909 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10910 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10911 boundaries.into_iter()
10912 {
10913 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10914 let close_offset = end_after
10915 .to_offset(&buffer)
10916 .saturating_sub_usize(end_suffix_len);
10917 new_selections.push(open_offset..open_offset);
10918 new_selections.push(close_offset..close_offset);
10919 }
10920
10921 this.change_selections(Default::default(), window, cx, |s| {
10922 s.select_ranges(new_selections);
10923 });
10924
10925 this.request_autoscroll(Autoscroll::fit(), cx);
10926 });
10927 }
10928
10929 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10930 let Some(project) = self.project.clone() else {
10931 return;
10932 };
10933 self.reload(project, window, cx)
10934 .detach_and_notify_err(window, cx);
10935 }
10936
10937 pub fn restore_file(
10938 &mut self,
10939 _: &::git::RestoreFile,
10940 window: &mut Window,
10941 cx: &mut Context<Self>,
10942 ) {
10943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10944 let mut buffer_ids = HashSet::default();
10945 let snapshot = self.buffer().read(cx).snapshot(cx);
10946 for selection in self
10947 .selections
10948 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10949 {
10950 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10951 }
10952
10953 let buffer = self.buffer().read(cx);
10954 let ranges = buffer_ids
10955 .into_iter()
10956 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10957 .collect::<Vec<_>>();
10958
10959 self.restore_hunks_in_ranges(ranges, window, cx);
10960 }
10961
10962 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10963 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10964 let selections = self
10965 .selections
10966 .all(&self.display_snapshot(cx))
10967 .into_iter()
10968 .map(|s| s.range())
10969 .collect();
10970 self.restore_hunks_in_ranges(selections, window, cx);
10971 }
10972
10973 pub fn restore_hunks_in_ranges(
10974 &mut self,
10975 ranges: Vec<Range<Point>>,
10976 window: &mut Window,
10977 cx: &mut Context<Editor>,
10978 ) {
10979 let mut revert_changes = HashMap::default();
10980 let chunk_by = self
10981 .snapshot(window, cx)
10982 .hunks_for_ranges(ranges)
10983 .into_iter()
10984 .chunk_by(|hunk| hunk.buffer_id);
10985 for (buffer_id, hunks) in &chunk_by {
10986 let hunks = hunks.collect::<Vec<_>>();
10987 for hunk in &hunks {
10988 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10989 }
10990 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10991 }
10992 drop(chunk_by);
10993 if !revert_changes.is_empty() {
10994 self.transact(window, cx, |editor, window, cx| {
10995 editor.restore(revert_changes, window, cx);
10996 });
10997 }
10998 }
10999
11000 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11001 if let Some(status) = self
11002 .addons
11003 .iter()
11004 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11005 {
11006 return Some(status);
11007 }
11008 self.project
11009 .as_ref()?
11010 .read(cx)
11011 .status_for_buffer_id(buffer_id, cx)
11012 }
11013
11014 pub fn open_active_item_in_terminal(
11015 &mut self,
11016 _: &OpenInTerminal,
11017 window: &mut Window,
11018 cx: &mut Context<Self>,
11019 ) {
11020 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11021 let project_path = buffer.read(cx).project_path(cx)?;
11022 let project = self.project()?.read(cx);
11023 let entry = project.entry_for_path(&project_path, cx)?;
11024 let parent = match &entry.canonical_path {
11025 Some(canonical_path) => canonical_path.to_path_buf(),
11026 None => project.absolute_path(&project_path, cx)?,
11027 }
11028 .parent()?
11029 .to_path_buf();
11030 Some(parent)
11031 }) {
11032 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11033 }
11034 }
11035
11036 fn set_breakpoint_context_menu(
11037 &mut self,
11038 display_row: DisplayRow,
11039 position: Option<Anchor>,
11040 clicked_point: gpui::Point<Pixels>,
11041 window: &mut Window,
11042 cx: &mut Context<Self>,
11043 ) {
11044 let source = self
11045 .buffer
11046 .read(cx)
11047 .snapshot(cx)
11048 .anchor_before(Point::new(display_row.0, 0u32));
11049
11050 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11051
11052 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11053 self,
11054 source,
11055 clicked_point,
11056 context_menu,
11057 window,
11058 cx,
11059 );
11060 }
11061
11062 fn add_edit_breakpoint_block(
11063 &mut self,
11064 anchor: Anchor,
11065 breakpoint: &Breakpoint,
11066 edit_action: BreakpointPromptEditAction,
11067 window: &mut Window,
11068 cx: &mut Context<Self>,
11069 ) {
11070 let weak_editor = cx.weak_entity();
11071 let bp_prompt = cx.new(|cx| {
11072 BreakpointPromptEditor::new(
11073 weak_editor,
11074 anchor,
11075 breakpoint.clone(),
11076 edit_action,
11077 window,
11078 cx,
11079 )
11080 });
11081
11082 let height = bp_prompt.update(cx, |this, cx| {
11083 this.prompt
11084 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11085 });
11086 let cloned_prompt = bp_prompt.clone();
11087 let blocks = vec![BlockProperties {
11088 style: BlockStyle::Sticky,
11089 placement: BlockPlacement::Above(anchor),
11090 height: Some(height),
11091 render: Arc::new(move |cx| {
11092 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11093 cloned_prompt.clone().into_any_element()
11094 }),
11095 priority: 0,
11096 }];
11097
11098 let focus_handle = bp_prompt.focus_handle(cx);
11099 window.focus(&focus_handle);
11100
11101 let block_ids = self.insert_blocks(blocks, None, cx);
11102 bp_prompt.update(cx, |prompt, _| {
11103 prompt.add_block_ids(block_ids);
11104 });
11105 }
11106
11107 pub(crate) fn breakpoint_at_row(
11108 &self,
11109 row: u32,
11110 window: &mut Window,
11111 cx: &mut Context<Self>,
11112 ) -> Option<(Anchor, Breakpoint)> {
11113 let snapshot = self.snapshot(window, cx);
11114 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11115
11116 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11117 }
11118
11119 pub(crate) fn breakpoint_at_anchor(
11120 &self,
11121 breakpoint_position: Anchor,
11122 snapshot: &EditorSnapshot,
11123 cx: &mut Context<Self>,
11124 ) -> Option<(Anchor, Breakpoint)> {
11125 let buffer = self
11126 .buffer
11127 .read(cx)
11128 .buffer_for_anchor(breakpoint_position, cx)?;
11129
11130 let enclosing_excerpt = breakpoint_position.excerpt_id;
11131 let buffer_snapshot = buffer.read(cx).snapshot();
11132
11133 let row = buffer_snapshot
11134 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11135 .row;
11136
11137 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11138 let anchor_end = snapshot
11139 .buffer_snapshot()
11140 .anchor_after(Point::new(row, line_len));
11141
11142 self.breakpoint_store
11143 .as_ref()?
11144 .read_with(cx, |breakpoint_store, cx| {
11145 breakpoint_store
11146 .breakpoints(
11147 &buffer,
11148 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11149 &buffer_snapshot,
11150 cx,
11151 )
11152 .next()
11153 .and_then(|(bp, _)| {
11154 let breakpoint_row = buffer_snapshot
11155 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11156 .row;
11157
11158 if breakpoint_row == row {
11159 snapshot
11160 .buffer_snapshot()
11161 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11162 .map(|position| (position, bp.bp.clone()))
11163 } else {
11164 None
11165 }
11166 })
11167 })
11168 }
11169
11170 pub fn edit_log_breakpoint(
11171 &mut self,
11172 _: &EditLogBreakpoint,
11173 window: &mut Window,
11174 cx: &mut Context<Self>,
11175 ) {
11176 if self.breakpoint_store.is_none() {
11177 return;
11178 }
11179
11180 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11181 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11182 message: None,
11183 state: BreakpointState::Enabled,
11184 condition: None,
11185 hit_condition: None,
11186 });
11187
11188 self.add_edit_breakpoint_block(
11189 anchor,
11190 &breakpoint,
11191 BreakpointPromptEditAction::Log,
11192 window,
11193 cx,
11194 );
11195 }
11196 }
11197
11198 fn breakpoints_at_cursors(
11199 &self,
11200 window: &mut Window,
11201 cx: &mut Context<Self>,
11202 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11203 let snapshot = self.snapshot(window, cx);
11204 let cursors = self
11205 .selections
11206 .disjoint_anchors_arc()
11207 .iter()
11208 .map(|selection| {
11209 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11210
11211 let breakpoint_position = self
11212 .breakpoint_at_row(cursor_position.row, window, cx)
11213 .map(|bp| bp.0)
11214 .unwrap_or_else(|| {
11215 snapshot
11216 .display_snapshot
11217 .buffer_snapshot()
11218 .anchor_after(Point::new(cursor_position.row, 0))
11219 });
11220
11221 let breakpoint = self
11222 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11223 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11224
11225 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11226 })
11227 // 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.
11228 .collect::<HashMap<Anchor, _>>();
11229
11230 cursors.into_iter().collect()
11231 }
11232
11233 pub fn enable_breakpoint(
11234 &mut self,
11235 _: &crate::actions::EnableBreakpoint,
11236 window: &mut Window,
11237 cx: &mut Context<Self>,
11238 ) {
11239 if self.breakpoint_store.is_none() {
11240 return;
11241 }
11242
11243 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11244 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11245 continue;
11246 };
11247 self.edit_breakpoint_at_anchor(
11248 anchor,
11249 breakpoint,
11250 BreakpointEditAction::InvertState,
11251 cx,
11252 );
11253 }
11254 }
11255
11256 pub fn disable_breakpoint(
11257 &mut self,
11258 _: &crate::actions::DisableBreakpoint,
11259 window: &mut Window,
11260 cx: &mut Context<Self>,
11261 ) {
11262 if self.breakpoint_store.is_none() {
11263 return;
11264 }
11265
11266 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11267 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11268 continue;
11269 };
11270 self.edit_breakpoint_at_anchor(
11271 anchor,
11272 breakpoint,
11273 BreakpointEditAction::InvertState,
11274 cx,
11275 );
11276 }
11277 }
11278
11279 pub fn toggle_breakpoint(
11280 &mut self,
11281 _: &crate::actions::ToggleBreakpoint,
11282 window: &mut Window,
11283 cx: &mut Context<Self>,
11284 ) {
11285 if self.breakpoint_store.is_none() {
11286 return;
11287 }
11288
11289 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11290 if let Some(breakpoint) = breakpoint {
11291 self.edit_breakpoint_at_anchor(
11292 anchor,
11293 breakpoint,
11294 BreakpointEditAction::Toggle,
11295 cx,
11296 );
11297 } else {
11298 self.edit_breakpoint_at_anchor(
11299 anchor,
11300 Breakpoint::new_standard(),
11301 BreakpointEditAction::Toggle,
11302 cx,
11303 );
11304 }
11305 }
11306 }
11307
11308 pub fn edit_breakpoint_at_anchor(
11309 &mut self,
11310 breakpoint_position: Anchor,
11311 breakpoint: Breakpoint,
11312 edit_action: BreakpointEditAction,
11313 cx: &mut Context<Self>,
11314 ) {
11315 let Some(breakpoint_store) = &self.breakpoint_store else {
11316 return;
11317 };
11318
11319 let Some(buffer) = self
11320 .buffer
11321 .read(cx)
11322 .buffer_for_anchor(breakpoint_position, cx)
11323 else {
11324 return;
11325 };
11326
11327 breakpoint_store.update(cx, |breakpoint_store, cx| {
11328 breakpoint_store.toggle_breakpoint(
11329 buffer,
11330 BreakpointWithPosition {
11331 position: breakpoint_position.text_anchor,
11332 bp: breakpoint,
11333 },
11334 edit_action,
11335 cx,
11336 );
11337 });
11338
11339 cx.notify();
11340 }
11341
11342 #[cfg(any(test, feature = "test-support"))]
11343 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11344 self.breakpoint_store.clone()
11345 }
11346
11347 pub fn prepare_restore_change(
11348 &self,
11349 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11350 hunk: &MultiBufferDiffHunk,
11351 cx: &mut App,
11352 ) -> Option<()> {
11353 if hunk.is_created_file() {
11354 return None;
11355 }
11356 let buffer = self.buffer.read(cx);
11357 let diff = buffer.diff_for(hunk.buffer_id)?;
11358 let buffer = buffer.buffer(hunk.buffer_id)?;
11359 let buffer = buffer.read(cx);
11360 let original_text = diff
11361 .read(cx)
11362 .base_text()
11363 .as_rope()
11364 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11365 let buffer_snapshot = buffer.snapshot();
11366 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11367 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11368 probe
11369 .0
11370 .start
11371 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11372 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11373 }) {
11374 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11375 Some(())
11376 } else {
11377 None
11378 }
11379 }
11380
11381 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11382 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11383 }
11384
11385 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11386 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11387 }
11388
11389 fn manipulate_lines<M>(
11390 &mut self,
11391 window: &mut Window,
11392 cx: &mut Context<Self>,
11393 mut manipulate: M,
11394 ) where
11395 M: FnMut(&str) -> LineManipulationResult,
11396 {
11397 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11398
11399 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11400 let buffer = self.buffer.read(cx).snapshot(cx);
11401
11402 let mut edits = Vec::new();
11403
11404 let selections = self.selections.all::<Point>(&display_map);
11405 let mut selections = selections.iter().peekable();
11406 let mut contiguous_row_selections = Vec::new();
11407 let mut new_selections = Vec::new();
11408 let mut added_lines = 0;
11409 let mut removed_lines = 0;
11410
11411 while let Some(selection) = selections.next() {
11412 let (start_row, end_row) = consume_contiguous_rows(
11413 &mut contiguous_row_selections,
11414 selection,
11415 &display_map,
11416 &mut selections,
11417 );
11418
11419 let start_point = Point::new(start_row.0, 0);
11420 let end_point = Point::new(
11421 end_row.previous_row().0,
11422 buffer.line_len(end_row.previous_row()),
11423 );
11424 let text = buffer
11425 .text_for_range(start_point..end_point)
11426 .collect::<String>();
11427
11428 let LineManipulationResult {
11429 new_text,
11430 line_count_before,
11431 line_count_after,
11432 } = manipulate(&text);
11433
11434 edits.push((start_point..end_point, new_text));
11435
11436 // Selections must change based on added and removed line count
11437 let start_row =
11438 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11439 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11440 new_selections.push(Selection {
11441 id: selection.id,
11442 start: start_row,
11443 end: end_row,
11444 goal: SelectionGoal::None,
11445 reversed: selection.reversed,
11446 });
11447
11448 if line_count_after > line_count_before {
11449 added_lines += line_count_after - line_count_before;
11450 } else if line_count_before > line_count_after {
11451 removed_lines += line_count_before - line_count_after;
11452 }
11453 }
11454
11455 self.transact(window, cx, |this, window, cx| {
11456 let buffer = this.buffer.update(cx, |buffer, cx| {
11457 buffer.edit(edits, None, cx);
11458 buffer.snapshot(cx)
11459 });
11460
11461 // Recalculate offsets on newly edited buffer
11462 let new_selections = new_selections
11463 .iter()
11464 .map(|s| {
11465 let start_point = Point::new(s.start.0, 0);
11466 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11467 Selection {
11468 id: s.id,
11469 start: buffer.point_to_offset(start_point),
11470 end: buffer.point_to_offset(end_point),
11471 goal: s.goal,
11472 reversed: s.reversed,
11473 }
11474 })
11475 .collect();
11476
11477 this.change_selections(Default::default(), window, cx, |s| {
11478 s.select(new_selections);
11479 });
11480
11481 this.request_autoscroll(Autoscroll::fit(), cx);
11482 });
11483 }
11484
11485 fn manipulate_immutable_lines<Fn>(
11486 &mut self,
11487 window: &mut Window,
11488 cx: &mut Context<Self>,
11489 mut callback: Fn,
11490 ) where
11491 Fn: FnMut(&mut Vec<&str>),
11492 {
11493 self.manipulate_lines(window, cx, |text| {
11494 let mut lines: Vec<&str> = text.split('\n').collect();
11495 let line_count_before = lines.len();
11496
11497 callback(&mut lines);
11498
11499 LineManipulationResult {
11500 new_text: lines.join("\n"),
11501 line_count_before,
11502 line_count_after: lines.len(),
11503 }
11504 });
11505 }
11506
11507 fn manipulate_mutable_lines<Fn>(
11508 &mut self,
11509 window: &mut Window,
11510 cx: &mut Context<Self>,
11511 mut callback: Fn,
11512 ) where
11513 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11514 {
11515 self.manipulate_lines(window, cx, |text| {
11516 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11517 let line_count_before = lines.len();
11518
11519 callback(&mut lines);
11520
11521 LineManipulationResult {
11522 new_text: lines.join("\n"),
11523 line_count_before,
11524 line_count_after: lines.len(),
11525 }
11526 });
11527 }
11528
11529 pub fn convert_indentation_to_spaces(
11530 &mut self,
11531 _: &ConvertIndentationToSpaces,
11532 window: &mut Window,
11533 cx: &mut Context<Self>,
11534 ) {
11535 let settings = self.buffer.read(cx).language_settings(cx);
11536 let tab_size = settings.tab_size.get() as usize;
11537
11538 self.manipulate_mutable_lines(window, cx, |lines| {
11539 // Allocates a reasonably sized scratch buffer once for the whole loop
11540 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11541 // Avoids recomputing spaces that could be inserted many times
11542 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11543 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11544 .collect();
11545
11546 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11547 let mut chars = line.as_ref().chars();
11548 let mut col = 0;
11549 let mut changed = false;
11550
11551 for ch in chars.by_ref() {
11552 match ch {
11553 ' ' => {
11554 reindented_line.push(' ');
11555 col += 1;
11556 }
11557 '\t' => {
11558 // \t are converted to spaces depending on the current column
11559 let spaces_len = tab_size - (col % tab_size);
11560 reindented_line.extend(&space_cache[spaces_len - 1]);
11561 col += spaces_len;
11562 changed = true;
11563 }
11564 _ => {
11565 // If we dont append before break, the character is consumed
11566 reindented_line.push(ch);
11567 break;
11568 }
11569 }
11570 }
11571
11572 if !changed {
11573 reindented_line.clear();
11574 continue;
11575 }
11576 // Append the rest of the line and replace old reference with new one
11577 reindented_line.extend(chars);
11578 *line = Cow::Owned(reindented_line.clone());
11579 reindented_line.clear();
11580 }
11581 });
11582 }
11583
11584 pub fn convert_indentation_to_tabs(
11585 &mut self,
11586 _: &ConvertIndentationToTabs,
11587 window: &mut Window,
11588 cx: &mut Context<Self>,
11589 ) {
11590 let settings = self.buffer.read(cx).language_settings(cx);
11591 let tab_size = settings.tab_size.get() as usize;
11592
11593 self.manipulate_mutable_lines(window, cx, |lines| {
11594 // Allocates a reasonably sized buffer once for the whole loop
11595 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11596 // Avoids recomputing spaces that could be inserted many times
11597 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11598 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11599 .collect();
11600
11601 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11602 let mut chars = line.chars();
11603 let mut spaces_count = 0;
11604 let mut first_non_indent_char = None;
11605 let mut changed = false;
11606
11607 for ch in chars.by_ref() {
11608 match ch {
11609 ' ' => {
11610 // Keep track of spaces. Append \t when we reach tab_size
11611 spaces_count += 1;
11612 changed = true;
11613 if spaces_count == tab_size {
11614 reindented_line.push('\t');
11615 spaces_count = 0;
11616 }
11617 }
11618 '\t' => {
11619 reindented_line.push('\t');
11620 spaces_count = 0;
11621 }
11622 _ => {
11623 // Dont append it yet, we might have remaining spaces
11624 first_non_indent_char = Some(ch);
11625 break;
11626 }
11627 }
11628 }
11629
11630 if !changed {
11631 reindented_line.clear();
11632 continue;
11633 }
11634 // Remaining spaces that didn't make a full tab stop
11635 if spaces_count > 0 {
11636 reindented_line.extend(&space_cache[spaces_count - 1]);
11637 }
11638 // If we consume an extra character that was not indentation, add it back
11639 if let Some(extra_char) = first_non_indent_char {
11640 reindented_line.push(extra_char);
11641 }
11642 // Append the rest of the line and replace old reference with new one
11643 reindented_line.extend(chars);
11644 *line = Cow::Owned(reindented_line.clone());
11645 reindented_line.clear();
11646 }
11647 });
11648 }
11649
11650 pub fn convert_to_upper_case(
11651 &mut self,
11652 _: &ConvertToUpperCase,
11653 window: &mut Window,
11654 cx: &mut Context<Self>,
11655 ) {
11656 self.manipulate_text(window, cx, |text| text.to_uppercase())
11657 }
11658
11659 pub fn convert_to_lower_case(
11660 &mut self,
11661 _: &ConvertToLowerCase,
11662 window: &mut Window,
11663 cx: &mut Context<Self>,
11664 ) {
11665 self.manipulate_text(window, cx, |text| text.to_lowercase())
11666 }
11667
11668 pub fn convert_to_title_case(
11669 &mut self,
11670 _: &ConvertToTitleCase,
11671 window: &mut Window,
11672 cx: &mut Context<Self>,
11673 ) {
11674 self.manipulate_text(window, cx, |text| {
11675 text.split('\n')
11676 .map(|line| line.to_case(Case::Title))
11677 .join("\n")
11678 })
11679 }
11680
11681 pub fn convert_to_snake_case(
11682 &mut self,
11683 _: &ConvertToSnakeCase,
11684 window: &mut Window,
11685 cx: &mut Context<Self>,
11686 ) {
11687 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11688 }
11689
11690 pub fn convert_to_kebab_case(
11691 &mut self,
11692 _: &ConvertToKebabCase,
11693 window: &mut Window,
11694 cx: &mut Context<Self>,
11695 ) {
11696 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11697 }
11698
11699 pub fn convert_to_upper_camel_case(
11700 &mut self,
11701 _: &ConvertToUpperCamelCase,
11702 window: &mut Window,
11703 cx: &mut Context<Self>,
11704 ) {
11705 self.manipulate_text(window, cx, |text| {
11706 text.split('\n')
11707 .map(|line| line.to_case(Case::UpperCamel))
11708 .join("\n")
11709 })
11710 }
11711
11712 pub fn convert_to_lower_camel_case(
11713 &mut self,
11714 _: &ConvertToLowerCamelCase,
11715 window: &mut Window,
11716 cx: &mut Context<Self>,
11717 ) {
11718 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11719 }
11720
11721 pub fn convert_to_opposite_case(
11722 &mut self,
11723 _: &ConvertToOppositeCase,
11724 window: &mut Window,
11725 cx: &mut Context<Self>,
11726 ) {
11727 self.manipulate_text(window, cx, |text| {
11728 text.chars()
11729 .fold(String::with_capacity(text.len()), |mut t, c| {
11730 if c.is_uppercase() {
11731 t.extend(c.to_lowercase());
11732 } else {
11733 t.extend(c.to_uppercase());
11734 }
11735 t
11736 })
11737 })
11738 }
11739
11740 pub fn convert_to_sentence_case(
11741 &mut self,
11742 _: &ConvertToSentenceCase,
11743 window: &mut Window,
11744 cx: &mut Context<Self>,
11745 ) {
11746 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11747 }
11748
11749 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11750 self.manipulate_text(window, cx, |text| {
11751 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11752 if has_upper_case_characters {
11753 text.to_lowercase()
11754 } else {
11755 text.to_uppercase()
11756 }
11757 })
11758 }
11759
11760 pub fn convert_to_rot13(
11761 &mut self,
11762 _: &ConvertToRot13,
11763 window: &mut Window,
11764 cx: &mut Context<Self>,
11765 ) {
11766 self.manipulate_text(window, cx, |text| {
11767 text.chars()
11768 .map(|c| match c {
11769 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11770 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11771 _ => c,
11772 })
11773 .collect()
11774 })
11775 }
11776
11777 pub fn convert_to_rot47(
11778 &mut self,
11779 _: &ConvertToRot47,
11780 window: &mut Window,
11781 cx: &mut Context<Self>,
11782 ) {
11783 self.manipulate_text(window, cx, |text| {
11784 text.chars()
11785 .map(|c| {
11786 let code_point = c as u32;
11787 if code_point >= 33 && code_point <= 126 {
11788 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11789 }
11790 c
11791 })
11792 .collect()
11793 })
11794 }
11795
11796 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11797 where
11798 Fn: FnMut(&str) -> String,
11799 {
11800 let buffer = self.buffer.read(cx).snapshot(cx);
11801
11802 let mut new_selections = Vec::new();
11803 let mut edits = Vec::new();
11804 let mut selection_adjustment = 0isize;
11805
11806 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11807 let selection_is_empty = selection.is_empty();
11808
11809 let (start, end) = if selection_is_empty {
11810 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11811 (word_range.start, word_range.end)
11812 } else {
11813 (
11814 buffer.point_to_offset(selection.start),
11815 buffer.point_to_offset(selection.end),
11816 )
11817 };
11818
11819 let text = buffer.text_for_range(start..end).collect::<String>();
11820 let old_length = text.len() as isize;
11821 let text = callback(&text);
11822
11823 new_selections.push(Selection {
11824 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11825 end: MultiBufferOffset(
11826 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11827 ),
11828 goal: SelectionGoal::None,
11829 id: selection.id,
11830 reversed: selection.reversed,
11831 });
11832
11833 selection_adjustment += old_length - text.len() as isize;
11834
11835 edits.push((start..end, text));
11836 }
11837
11838 self.transact(window, cx, |this, window, cx| {
11839 this.buffer.update(cx, |buffer, cx| {
11840 buffer.edit(edits, None, cx);
11841 });
11842
11843 this.change_selections(Default::default(), window, cx, |s| {
11844 s.select(new_selections);
11845 });
11846
11847 this.request_autoscroll(Autoscroll::fit(), cx);
11848 });
11849 }
11850
11851 pub fn move_selection_on_drop(
11852 &mut self,
11853 selection: &Selection<Anchor>,
11854 target: DisplayPoint,
11855 is_cut: bool,
11856 window: &mut Window,
11857 cx: &mut Context<Self>,
11858 ) {
11859 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11860 let buffer = display_map.buffer_snapshot();
11861 let mut edits = Vec::new();
11862 let insert_point = display_map
11863 .clip_point(target, Bias::Left)
11864 .to_point(&display_map);
11865 let text = buffer
11866 .text_for_range(selection.start..selection.end)
11867 .collect::<String>();
11868 if is_cut {
11869 edits.push(((selection.start..selection.end), String::new()));
11870 }
11871 let insert_anchor = buffer.anchor_before(insert_point);
11872 edits.push(((insert_anchor..insert_anchor), text));
11873 let last_edit_start = insert_anchor.bias_left(buffer);
11874 let last_edit_end = insert_anchor.bias_right(buffer);
11875 self.transact(window, cx, |this, window, cx| {
11876 this.buffer.update(cx, |buffer, cx| {
11877 buffer.edit(edits, None, cx);
11878 });
11879 this.change_selections(Default::default(), window, cx, |s| {
11880 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11881 });
11882 });
11883 }
11884
11885 pub fn clear_selection_drag_state(&mut self) {
11886 self.selection_drag_state = SelectionDragState::None;
11887 }
11888
11889 pub fn duplicate(
11890 &mut self,
11891 upwards: bool,
11892 whole_lines: bool,
11893 window: &mut Window,
11894 cx: &mut Context<Self>,
11895 ) {
11896 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11897
11898 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11899 let buffer = display_map.buffer_snapshot();
11900 let selections = self.selections.all::<Point>(&display_map);
11901
11902 let mut edits = Vec::new();
11903 let mut selections_iter = selections.iter().peekable();
11904 while let Some(selection) = selections_iter.next() {
11905 let mut rows = selection.spanned_rows(false, &display_map);
11906 // duplicate line-wise
11907 if whole_lines || selection.start == selection.end {
11908 // Avoid duplicating the same lines twice.
11909 while let Some(next_selection) = selections_iter.peek() {
11910 let next_rows = next_selection.spanned_rows(false, &display_map);
11911 if next_rows.start < rows.end {
11912 rows.end = next_rows.end;
11913 selections_iter.next().unwrap();
11914 } else {
11915 break;
11916 }
11917 }
11918
11919 // Copy the text from the selected row region and splice it either at the start
11920 // or end of the region.
11921 let start = Point::new(rows.start.0, 0);
11922 let end = Point::new(
11923 rows.end.previous_row().0,
11924 buffer.line_len(rows.end.previous_row()),
11925 );
11926
11927 let mut text = buffer.text_for_range(start..end).collect::<String>();
11928
11929 let insert_location = if upwards {
11930 // When duplicating upward, we need to insert before the current line.
11931 // If we're on the last line and it doesn't end with a newline,
11932 // we need to add a newline before the duplicated content.
11933 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11934 && buffer.max_point().column > 0
11935 && !text.ends_with('\n');
11936
11937 if needs_leading_newline {
11938 text.insert(0, '\n');
11939 end
11940 } else {
11941 text.push('\n');
11942 Point::new(rows.start.0, 0)
11943 }
11944 } else {
11945 text.push('\n');
11946 start
11947 };
11948 edits.push((insert_location..insert_location, text));
11949 } else {
11950 // duplicate character-wise
11951 let start = selection.start;
11952 let end = selection.end;
11953 let text = buffer.text_for_range(start..end).collect::<String>();
11954 edits.push((selection.end..selection.end, text));
11955 }
11956 }
11957
11958 self.transact(window, cx, |this, window, cx| {
11959 this.buffer.update(cx, |buffer, cx| {
11960 buffer.edit(edits, None, cx);
11961 });
11962
11963 // When duplicating upward with whole lines, move the cursor to the duplicated line
11964 if upwards && whole_lines {
11965 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11966
11967 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11968 let mut new_ranges = Vec::new();
11969 let selections = s.all::<Point>(&display_map);
11970 let mut selections_iter = selections.iter().peekable();
11971
11972 while let Some(first_selection) = selections_iter.next() {
11973 // Group contiguous selections together to find the total row span
11974 let mut group_selections = vec![first_selection];
11975 let mut rows = first_selection.spanned_rows(false, &display_map);
11976
11977 while let Some(next_selection) = selections_iter.peek() {
11978 let next_rows = next_selection.spanned_rows(false, &display_map);
11979 if next_rows.start < rows.end {
11980 rows.end = next_rows.end;
11981 group_selections.push(selections_iter.next().unwrap());
11982 } else {
11983 break;
11984 }
11985 }
11986
11987 let row_count = rows.end.0 - rows.start.0;
11988
11989 // Move all selections in this group up by the total number of duplicated rows
11990 for selection in group_selections {
11991 let new_start = Point::new(
11992 selection.start.row.saturating_sub(row_count),
11993 selection.start.column,
11994 );
11995
11996 let new_end = Point::new(
11997 selection.end.row.saturating_sub(row_count),
11998 selection.end.column,
11999 );
12000
12001 new_ranges.push(new_start..new_end);
12002 }
12003 }
12004
12005 s.select_ranges(new_ranges);
12006 });
12007 }
12008
12009 this.request_autoscroll(Autoscroll::fit(), cx);
12010 });
12011 }
12012
12013 pub fn duplicate_line_up(
12014 &mut self,
12015 _: &DuplicateLineUp,
12016 window: &mut Window,
12017 cx: &mut Context<Self>,
12018 ) {
12019 self.duplicate(true, true, window, cx);
12020 }
12021
12022 pub fn duplicate_line_down(
12023 &mut self,
12024 _: &DuplicateLineDown,
12025 window: &mut Window,
12026 cx: &mut Context<Self>,
12027 ) {
12028 self.duplicate(false, true, window, cx);
12029 }
12030
12031 pub fn duplicate_selection(
12032 &mut self,
12033 _: &DuplicateSelection,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 self.duplicate(false, false, window, cx);
12038 }
12039
12040 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12042 if self.mode.is_single_line() {
12043 cx.propagate();
12044 return;
12045 }
12046
12047 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12048 let buffer = self.buffer.read(cx).snapshot(cx);
12049
12050 let mut edits = Vec::new();
12051 let mut unfold_ranges = Vec::new();
12052 let mut refold_creases = Vec::new();
12053
12054 let selections = self.selections.all::<Point>(&display_map);
12055 let mut selections = selections.iter().peekable();
12056 let mut contiguous_row_selections = Vec::new();
12057 let mut new_selections = Vec::new();
12058
12059 while let Some(selection) = selections.next() {
12060 // Find all the selections that span a contiguous row range
12061 let (start_row, end_row) = consume_contiguous_rows(
12062 &mut contiguous_row_selections,
12063 selection,
12064 &display_map,
12065 &mut selections,
12066 );
12067
12068 // Move the text spanned by the row range to be before the line preceding the row range
12069 if start_row.0 > 0 {
12070 let range_to_move = Point::new(
12071 start_row.previous_row().0,
12072 buffer.line_len(start_row.previous_row()),
12073 )
12074 ..Point::new(
12075 end_row.previous_row().0,
12076 buffer.line_len(end_row.previous_row()),
12077 );
12078 let insertion_point = display_map
12079 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12080 .0;
12081
12082 // Don't move lines across excerpts
12083 if buffer
12084 .excerpt_containing(insertion_point..range_to_move.end)
12085 .is_some()
12086 {
12087 let text = buffer
12088 .text_for_range(range_to_move.clone())
12089 .flat_map(|s| s.chars())
12090 .skip(1)
12091 .chain(['\n'])
12092 .collect::<String>();
12093
12094 edits.push((
12095 buffer.anchor_after(range_to_move.start)
12096 ..buffer.anchor_before(range_to_move.end),
12097 String::new(),
12098 ));
12099 let insertion_anchor = buffer.anchor_after(insertion_point);
12100 edits.push((insertion_anchor..insertion_anchor, text));
12101
12102 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12103
12104 // Move selections up
12105 new_selections.extend(contiguous_row_selections.drain(..).map(
12106 |mut selection| {
12107 selection.start.row -= row_delta;
12108 selection.end.row -= row_delta;
12109 selection
12110 },
12111 ));
12112
12113 // Move folds up
12114 unfold_ranges.push(range_to_move.clone());
12115 for fold in display_map.folds_in_range(
12116 buffer.anchor_before(range_to_move.start)
12117 ..buffer.anchor_after(range_to_move.end),
12118 ) {
12119 let mut start = fold.range.start.to_point(&buffer);
12120 let mut end = fold.range.end.to_point(&buffer);
12121 start.row -= row_delta;
12122 end.row -= row_delta;
12123 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12124 }
12125 }
12126 }
12127
12128 // If we didn't move line(s), preserve the existing selections
12129 new_selections.append(&mut contiguous_row_selections);
12130 }
12131
12132 self.transact(window, cx, |this, window, cx| {
12133 this.unfold_ranges(&unfold_ranges, true, true, cx);
12134 this.buffer.update(cx, |buffer, cx| {
12135 for (range, text) in edits {
12136 buffer.edit([(range, text)], None, cx);
12137 }
12138 });
12139 this.fold_creases(refold_creases, true, window, cx);
12140 this.change_selections(Default::default(), window, cx, |s| {
12141 s.select(new_selections);
12142 })
12143 });
12144 }
12145
12146 pub fn move_line_down(
12147 &mut self,
12148 _: &MoveLineDown,
12149 window: &mut Window,
12150 cx: &mut Context<Self>,
12151 ) {
12152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12153 if self.mode.is_single_line() {
12154 cx.propagate();
12155 return;
12156 }
12157
12158 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12159 let buffer = self.buffer.read(cx).snapshot(cx);
12160
12161 let mut edits = Vec::new();
12162 let mut unfold_ranges = Vec::new();
12163 let mut refold_creases = Vec::new();
12164
12165 let selections = self.selections.all::<Point>(&display_map);
12166 let mut selections = selections.iter().peekable();
12167 let mut contiguous_row_selections = Vec::new();
12168 let mut new_selections = Vec::new();
12169
12170 while let Some(selection) = selections.next() {
12171 // Find all the selections that span a contiguous row range
12172 let (start_row, end_row) = consume_contiguous_rows(
12173 &mut contiguous_row_selections,
12174 selection,
12175 &display_map,
12176 &mut selections,
12177 );
12178
12179 // Move the text spanned by the row range to be after the last line of the row range
12180 if end_row.0 <= buffer.max_point().row {
12181 let range_to_move =
12182 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12183 let insertion_point = display_map
12184 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12185 .0;
12186
12187 // Don't move lines across excerpt boundaries
12188 if buffer
12189 .excerpt_containing(range_to_move.start..insertion_point)
12190 .is_some()
12191 {
12192 let mut text = String::from("\n");
12193 text.extend(buffer.text_for_range(range_to_move.clone()));
12194 text.pop(); // Drop trailing newline
12195 edits.push((
12196 buffer.anchor_after(range_to_move.start)
12197 ..buffer.anchor_before(range_to_move.end),
12198 String::new(),
12199 ));
12200 let insertion_anchor = buffer.anchor_after(insertion_point);
12201 edits.push((insertion_anchor..insertion_anchor, text));
12202
12203 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12204
12205 // Move selections down
12206 new_selections.extend(contiguous_row_selections.drain(..).map(
12207 |mut selection| {
12208 selection.start.row += row_delta;
12209 selection.end.row += row_delta;
12210 selection
12211 },
12212 ));
12213
12214 // Move folds down
12215 unfold_ranges.push(range_to_move.clone());
12216 for fold in display_map.folds_in_range(
12217 buffer.anchor_before(range_to_move.start)
12218 ..buffer.anchor_after(range_to_move.end),
12219 ) {
12220 let mut start = fold.range.start.to_point(&buffer);
12221 let mut end = fold.range.end.to_point(&buffer);
12222 start.row += row_delta;
12223 end.row += row_delta;
12224 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12225 }
12226 }
12227 }
12228
12229 // If we didn't move line(s), preserve the existing selections
12230 new_selections.append(&mut contiguous_row_selections);
12231 }
12232
12233 self.transact(window, cx, |this, window, cx| {
12234 this.unfold_ranges(&unfold_ranges, true, true, cx);
12235 this.buffer.update(cx, |buffer, cx| {
12236 for (range, text) in edits {
12237 buffer.edit([(range, text)], None, cx);
12238 }
12239 });
12240 this.fold_creases(refold_creases, true, window, cx);
12241 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12242 });
12243 }
12244
12245 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12246 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12247 let text_layout_details = &self.text_layout_details(window);
12248 self.transact(window, cx, |this, window, cx| {
12249 let edits = this.change_selections(Default::default(), window, cx, |s| {
12250 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12251 s.move_with(|display_map, selection| {
12252 if !selection.is_empty() {
12253 return;
12254 }
12255
12256 let mut head = selection.head();
12257 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12258 if head.column() == display_map.line_len(head.row()) {
12259 transpose_offset = display_map
12260 .buffer_snapshot()
12261 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12262 }
12263
12264 if transpose_offset == MultiBufferOffset(0) {
12265 return;
12266 }
12267
12268 *head.column_mut() += 1;
12269 head = display_map.clip_point(head, Bias::Right);
12270 let goal = SelectionGoal::HorizontalPosition(
12271 display_map
12272 .x_for_display_point(head, text_layout_details)
12273 .into(),
12274 );
12275 selection.collapse_to(head, goal);
12276
12277 let transpose_start = display_map
12278 .buffer_snapshot()
12279 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12280 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12281 let transpose_end = display_map
12282 .buffer_snapshot()
12283 .clip_offset(transpose_offset + 1usize, Bias::Right);
12284 if let Some(ch) = display_map
12285 .buffer_snapshot()
12286 .chars_at(transpose_start)
12287 .next()
12288 {
12289 edits.push((transpose_start..transpose_offset, String::new()));
12290 edits.push((transpose_end..transpose_end, ch.to_string()));
12291 }
12292 }
12293 });
12294 edits
12295 });
12296 this.buffer
12297 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12298 let selections = this
12299 .selections
12300 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12301 this.change_selections(Default::default(), window, cx, |s| {
12302 s.select(selections);
12303 });
12304 });
12305 }
12306
12307 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12308 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12309 if self.mode.is_single_line() {
12310 cx.propagate();
12311 return;
12312 }
12313
12314 self.rewrap_impl(RewrapOptions::default(), cx)
12315 }
12316
12317 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12318 let buffer = self.buffer.read(cx).snapshot(cx);
12319 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12320
12321 #[derive(Clone, Debug, PartialEq)]
12322 enum CommentFormat {
12323 /// single line comment, with prefix for line
12324 Line(String),
12325 /// single line within a block comment, with prefix for line
12326 BlockLine(String),
12327 /// a single line of a block comment that includes the initial delimiter
12328 BlockCommentWithStart(BlockCommentConfig),
12329 /// a single line of a block comment that includes the ending delimiter
12330 BlockCommentWithEnd(BlockCommentConfig),
12331 }
12332
12333 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12334 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12335 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12336 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12337 .peekable();
12338
12339 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12340 row
12341 } else {
12342 return Vec::new();
12343 };
12344
12345 let language_settings = buffer.language_settings_at(selection.head(), cx);
12346 let language_scope = buffer.language_scope_at(selection.head());
12347
12348 let indent_and_prefix_for_row =
12349 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12350 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12351 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12352 &language_scope
12353 {
12354 let indent_end = Point::new(row, indent.len);
12355 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12356 let line_text_after_indent = buffer
12357 .text_for_range(indent_end..line_end)
12358 .collect::<String>();
12359
12360 let is_within_comment_override = buffer
12361 .language_scope_at(indent_end)
12362 .is_some_and(|scope| scope.override_name() == Some("comment"));
12363 let comment_delimiters = if is_within_comment_override {
12364 // we are within a comment syntax node, but we don't
12365 // yet know what kind of comment: block, doc or line
12366 match (
12367 language_scope.documentation_comment(),
12368 language_scope.block_comment(),
12369 ) {
12370 (Some(config), _) | (_, Some(config))
12371 if buffer.contains_str_at(indent_end, &config.start) =>
12372 {
12373 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12374 }
12375 (Some(config), _) | (_, Some(config))
12376 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12377 {
12378 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12379 }
12380 (Some(config), _) | (_, Some(config))
12381 if buffer.contains_str_at(indent_end, &config.prefix) =>
12382 {
12383 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12384 }
12385 (_, _) => language_scope
12386 .line_comment_prefixes()
12387 .iter()
12388 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12389 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12390 }
12391 } else {
12392 // we not in an overridden comment node, but we may
12393 // be within a non-overridden line comment node
12394 language_scope
12395 .line_comment_prefixes()
12396 .iter()
12397 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12398 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12399 };
12400
12401 let rewrap_prefix = language_scope
12402 .rewrap_prefixes()
12403 .iter()
12404 .find_map(|prefix_regex| {
12405 prefix_regex.find(&line_text_after_indent).map(|mat| {
12406 if mat.start() == 0 {
12407 Some(mat.as_str().to_string())
12408 } else {
12409 None
12410 }
12411 })
12412 })
12413 .flatten();
12414 (comment_delimiters, rewrap_prefix)
12415 } else {
12416 (None, None)
12417 };
12418 (indent, comment_prefix, rewrap_prefix)
12419 };
12420
12421 let mut ranges = Vec::new();
12422 let from_empty_selection = selection.is_empty();
12423
12424 let mut current_range_start = first_row;
12425 let mut prev_row = first_row;
12426 let (
12427 mut current_range_indent,
12428 mut current_range_comment_delimiters,
12429 mut current_range_rewrap_prefix,
12430 ) = indent_and_prefix_for_row(first_row);
12431
12432 for row in non_blank_rows_iter.skip(1) {
12433 let has_paragraph_break = row > prev_row + 1;
12434
12435 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12436 indent_and_prefix_for_row(row);
12437
12438 let has_indent_change = row_indent != current_range_indent;
12439 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12440
12441 let has_boundary_change = has_comment_change
12442 || row_rewrap_prefix.is_some()
12443 || (has_indent_change && current_range_comment_delimiters.is_some());
12444
12445 if has_paragraph_break || has_boundary_change {
12446 ranges.push((
12447 language_settings.clone(),
12448 Point::new(current_range_start, 0)
12449 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12450 current_range_indent,
12451 current_range_comment_delimiters.clone(),
12452 current_range_rewrap_prefix.clone(),
12453 from_empty_selection,
12454 ));
12455 current_range_start = row;
12456 current_range_indent = row_indent;
12457 current_range_comment_delimiters = row_comment_delimiters;
12458 current_range_rewrap_prefix = row_rewrap_prefix;
12459 }
12460 prev_row = row;
12461 }
12462
12463 ranges.push((
12464 language_settings.clone(),
12465 Point::new(current_range_start, 0)
12466 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12467 current_range_indent,
12468 current_range_comment_delimiters,
12469 current_range_rewrap_prefix,
12470 from_empty_selection,
12471 ));
12472
12473 ranges
12474 });
12475
12476 let mut edits = Vec::new();
12477 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12478
12479 for (
12480 language_settings,
12481 wrap_range,
12482 mut indent_size,
12483 comment_prefix,
12484 rewrap_prefix,
12485 from_empty_selection,
12486 ) in wrap_ranges
12487 {
12488 let mut start_row = wrap_range.start.row;
12489 let mut end_row = wrap_range.end.row;
12490
12491 // Skip selections that overlap with a range that has already been rewrapped.
12492 let selection_range = start_row..end_row;
12493 if rewrapped_row_ranges
12494 .iter()
12495 .any(|range| range.overlaps(&selection_range))
12496 {
12497 continue;
12498 }
12499
12500 let tab_size = language_settings.tab_size;
12501
12502 let (line_prefix, inside_comment) = match &comment_prefix {
12503 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12504 (Some(prefix.as_str()), true)
12505 }
12506 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12507 (Some(prefix.as_ref()), true)
12508 }
12509 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12510 start: _,
12511 end: _,
12512 prefix,
12513 tab_size,
12514 })) => {
12515 indent_size.len += tab_size;
12516 (Some(prefix.as_ref()), true)
12517 }
12518 None => (None, false),
12519 };
12520 let indent_prefix = indent_size.chars().collect::<String>();
12521 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12522
12523 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12524 RewrapBehavior::InComments => inside_comment,
12525 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12526 RewrapBehavior::Anywhere => true,
12527 };
12528
12529 let should_rewrap = options.override_language_settings
12530 || allow_rewrap_based_on_language
12531 || self.hard_wrap.is_some();
12532 if !should_rewrap {
12533 continue;
12534 }
12535
12536 if from_empty_selection {
12537 'expand_upwards: while start_row > 0 {
12538 let prev_row = start_row - 1;
12539 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12540 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12541 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12542 {
12543 start_row = prev_row;
12544 } else {
12545 break 'expand_upwards;
12546 }
12547 }
12548
12549 'expand_downwards: while end_row < buffer.max_point().row {
12550 let next_row = end_row + 1;
12551 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12552 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12553 && !buffer.is_line_blank(MultiBufferRow(next_row))
12554 {
12555 end_row = next_row;
12556 } else {
12557 break 'expand_downwards;
12558 }
12559 }
12560 }
12561
12562 let start = Point::new(start_row, 0);
12563 let start_offset = ToOffset::to_offset(&start, &buffer);
12564 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12565 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12566 let mut first_line_delimiter = None;
12567 let mut last_line_delimiter = None;
12568 let Some(lines_without_prefixes) = selection_text
12569 .lines()
12570 .enumerate()
12571 .map(|(ix, line)| {
12572 let line_trimmed = line.trim_start();
12573 if rewrap_prefix.is_some() && ix > 0 {
12574 Ok(line_trimmed)
12575 } else if let Some(
12576 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12577 start,
12578 prefix,
12579 end,
12580 tab_size,
12581 })
12582 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12583 start,
12584 prefix,
12585 end,
12586 tab_size,
12587 }),
12588 ) = &comment_prefix
12589 {
12590 let line_trimmed = line_trimmed
12591 .strip_prefix(start.as_ref())
12592 .map(|s| {
12593 let mut indent_size = indent_size;
12594 indent_size.len -= tab_size;
12595 let indent_prefix: String = indent_size.chars().collect();
12596 first_line_delimiter = Some((indent_prefix, start));
12597 s.trim_start()
12598 })
12599 .unwrap_or(line_trimmed);
12600 let line_trimmed = line_trimmed
12601 .strip_suffix(end.as_ref())
12602 .map(|s| {
12603 last_line_delimiter = Some(end);
12604 s.trim_end()
12605 })
12606 .unwrap_or(line_trimmed);
12607 let line_trimmed = line_trimmed
12608 .strip_prefix(prefix.as_ref())
12609 .unwrap_or(line_trimmed);
12610 Ok(line_trimmed)
12611 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12612 line_trimmed.strip_prefix(prefix).with_context(|| {
12613 format!("line did not start with prefix {prefix:?}: {line:?}")
12614 })
12615 } else {
12616 line_trimmed
12617 .strip_prefix(&line_prefix.trim_start())
12618 .with_context(|| {
12619 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12620 })
12621 }
12622 })
12623 .collect::<Result<Vec<_>, _>>()
12624 .log_err()
12625 else {
12626 continue;
12627 };
12628
12629 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12630 buffer
12631 .language_settings_at(Point::new(start_row, 0), cx)
12632 .preferred_line_length as usize
12633 });
12634
12635 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12636 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12637 } else {
12638 line_prefix.clone()
12639 };
12640
12641 let wrapped_text = {
12642 let mut wrapped_text = wrap_with_prefix(
12643 line_prefix,
12644 subsequent_lines_prefix,
12645 lines_without_prefixes.join("\n"),
12646 wrap_column,
12647 tab_size,
12648 options.preserve_existing_whitespace,
12649 );
12650
12651 if let Some((indent, delimiter)) = first_line_delimiter {
12652 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12653 }
12654 if let Some(last_line) = last_line_delimiter {
12655 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12656 }
12657
12658 wrapped_text
12659 };
12660
12661 // TODO: should always use char-based diff while still supporting cursor behavior that
12662 // matches vim.
12663 let mut diff_options = DiffOptions::default();
12664 if options.override_language_settings {
12665 diff_options.max_word_diff_len = 0;
12666 diff_options.max_word_diff_line_count = 0;
12667 } else {
12668 diff_options.max_word_diff_len = usize::MAX;
12669 diff_options.max_word_diff_line_count = usize::MAX;
12670 }
12671
12672 for (old_range, new_text) in
12673 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12674 {
12675 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12676 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12677 edits.push((edit_start..edit_end, new_text));
12678 }
12679
12680 rewrapped_row_ranges.push(start_row..=end_row);
12681 }
12682
12683 self.buffer
12684 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12685 }
12686
12687 pub fn cut_common(
12688 &mut self,
12689 cut_no_selection_line: bool,
12690 window: &mut Window,
12691 cx: &mut Context<Self>,
12692 ) -> ClipboardItem {
12693 let mut text = String::new();
12694 let buffer = self.buffer.read(cx).snapshot(cx);
12695 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12696 let mut clipboard_selections = Vec::with_capacity(selections.len());
12697 {
12698 let max_point = buffer.max_point();
12699 let mut is_first = true;
12700 let mut prev_selection_was_entire_line = false;
12701 for selection in &mut selections {
12702 let is_entire_line =
12703 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12704 if is_entire_line {
12705 selection.start = Point::new(selection.start.row, 0);
12706 if !selection.is_empty() && selection.end.column == 0 {
12707 selection.end = cmp::min(max_point, selection.end);
12708 } else {
12709 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12710 }
12711 selection.goal = SelectionGoal::None;
12712 }
12713 if is_first {
12714 is_first = false;
12715 } else if !prev_selection_was_entire_line {
12716 text += "\n";
12717 }
12718 prev_selection_was_entire_line = is_entire_line;
12719 let mut len = 0;
12720 for chunk in buffer.text_for_range(selection.start..selection.end) {
12721 text.push_str(chunk);
12722 len += chunk.len();
12723 }
12724 clipboard_selections.push(ClipboardSelection {
12725 len,
12726 is_entire_line,
12727 first_line_indent: buffer
12728 .indent_size_for_line(MultiBufferRow(selection.start.row))
12729 .len,
12730 });
12731 }
12732 }
12733
12734 self.transact(window, cx, |this, window, cx| {
12735 this.change_selections(Default::default(), window, cx, |s| {
12736 s.select(selections);
12737 });
12738 this.insert("", window, cx);
12739 });
12740 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12741 }
12742
12743 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12744 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12745 let item = self.cut_common(true, window, cx);
12746 cx.write_to_clipboard(item);
12747 }
12748
12749 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12750 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12751 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12752 s.move_with(|snapshot, sel| {
12753 if sel.is_empty() {
12754 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12755 }
12756 if sel.is_empty() {
12757 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12758 }
12759 });
12760 });
12761 let item = self.cut_common(false, window, cx);
12762 cx.set_global(KillRing(item))
12763 }
12764
12765 pub fn kill_ring_yank(
12766 &mut self,
12767 _: &KillRingYank,
12768 window: &mut Window,
12769 cx: &mut Context<Self>,
12770 ) {
12771 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12772 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12773 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12774 (kill_ring.text().to_string(), kill_ring.metadata_json())
12775 } else {
12776 return;
12777 }
12778 } else {
12779 return;
12780 };
12781 self.do_paste(&text, metadata, false, window, cx);
12782 }
12783
12784 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12785 self.do_copy(true, cx);
12786 }
12787
12788 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12789 self.do_copy(false, cx);
12790 }
12791
12792 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12793 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12794 let buffer = self.buffer.read(cx).read(cx);
12795 let mut text = String::new();
12796
12797 let mut clipboard_selections = Vec::with_capacity(selections.len());
12798 {
12799 let max_point = buffer.max_point();
12800 let mut is_first = true;
12801 let mut prev_selection_was_entire_line = false;
12802 for selection in &selections {
12803 let mut start = selection.start;
12804 let mut end = selection.end;
12805 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12806 let mut add_trailing_newline = false;
12807 if is_entire_line {
12808 start = Point::new(start.row, 0);
12809 let next_line_start = Point::new(end.row + 1, 0);
12810 if next_line_start <= max_point {
12811 end = next_line_start;
12812 } else {
12813 // We're on the last line without a trailing newline.
12814 // Copy to the end of the line and add a newline afterwards.
12815 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12816 add_trailing_newline = true;
12817 }
12818 }
12819
12820 let mut trimmed_selections = Vec::new();
12821 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12822 let row = MultiBufferRow(start.row);
12823 let first_indent = buffer.indent_size_for_line(row);
12824 if first_indent.len == 0 || start.column > first_indent.len {
12825 trimmed_selections.push(start..end);
12826 } else {
12827 trimmed_selections.push(
12828 Point::new(row.0, first_indent.len)
12829 ..Point::new(row.0, buffer.line_len(row)),
12830 );
12831 for row in start.row + 1..=end.row {
12832 let mut line_len = buffer.line_len(MultiBufferRow(row));
12833 if row == end.row {
12834 line_len = end.column;
12835 }
12836 if line_len == 0 {
12837 trimmed_selections
12838 .push(Point::new(row, 0)..Point::new(row, line_len));
12839 continue;
12840 }
12841 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12842 if row_indent_size.len >= first_indent.len {
12843 trimmed_selections.push(
12844 Point::new(row, first_indent.len)..Point::new(row, line_len),
12845 );
12846 } else {
12847 trimmed_selections.clear();
12848 trimmed_selections.push(start..end);
12849 break;
12850 }
12851 }
12852 }
12853 } else {
12854 trimmed_selections.push(start..end);
12855 }
12856
12857 for trimmed_range in trimmed_selections {
12858 if is_first {
12859 is_first = false;
12860 } else if !prev_selection_was_entire_line {
12861 text += "\n";
12862 }
12863 prev_selection_was_entire_line = is_entire_line;
12864 let mut len = 0;
12865 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12866 text.push_str(chunk);
12867 len += chunk.len();
12868 }
12869 if add_trailing_newline {
12870 text.push('\n');
12871 len += 1;
12872 }
12873 clipboard_selections.push(ClipboardSelection {
12874 len,
12875 is_entire_line,
12876 first_line_indent: buffer
12877 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12878 .len,
12879 });
12880 }
12881 }
12882 }
12883
12884 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12885 text,
12886 clipboard_selections,
12887 ));
12888 }
12889
12890 pub fn do_paste(
12891 &mut self,
12892 text: &String,
12893 clipboard_selections: Option<Vec<ClipboardSelection>>,
12894 handle_entire_lines: bool,
12895 window: &mut Window,
12896 cx: &mut Context<Self>,
12897 ) {
12898 if self.read_only(cx) {
12899 return;
12900 }
12901
12902 let clipboard_text = Cow::Borrowed(text.as_str());
12903
12904 self.transact(window, cx, |this, window, cx| {
12905 let had_active_edit_prediction = this.has_active_edit_prediction();
12906 let display_map = this.display_snapshot(cx);
12907 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12908 let cursor_offset = this
12909 .selections
12910 .last::<MultiBufferOffset>(&display_map)
12911 .head();
12912
12913 if let Some(mut clipboard_selections) = clipboard_selections {
12914 let all_selections_were_entire_line =
12915 clipboard_selections.iter().all(|s| s.is_entire_line);
12916 let first_selection_indent_column =
12917 clipboard_selections.first().map(|s| s.first_line_indent);
12918 if clipboard_selections.len() != old_selections.len() {
12919 clipboard_selections.drain(..);
12920 }
12921 let mut auto_indent_on_paste = true;
12922
12923 this.buffer.update(cx, |buffer, cx| {
12924 let snapshot = buffer.read(cx);
12925 auto_indent_on_paste = snapshot
12926 .language_settings_at(cursor_offset, cx)
12927 .auto_indent_on_paste;
12928
12929 let mut start_offset = 0;
12930 let mut edits = Vec::new();
12931 let mut original_indent_columns = Vec::new();
12932 for (ix, selection) in old_selections.iter().enumerate() {
12933 let to_insert;
12934 let entire_line;
12935 let original_indent_column;
12936 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12937 let end_offset = start_offset + clipboard_selection.len;
12938 to_insert = &clipboard_text[start_offset..end_offset];
12939 entire_line = clipboard_selection.is_entire_line;
12940 start_offset = if entire_line {
12941 end_offset
12942 } else {
12943 end_offset + 1
12944 };
12945 original_indent_column = Some(clipboard_selection.first_line_indent);
12946 } else {
12947 to_insert = &*clipboard_text;
12948 entire_line = all_selections_were_entire_line;
12949 original_indent_column = first_selection_indent_column
12950 }
12951
12952 let (range, to_insert) =
12953 if selection.is_empty() && handle_entire_lines && entire_line {
12954 // If the corresponding selection was empty when this slice of the
12955 // clipboard text was written, then the entire line containing the
12956 // selection was copied. If this selection is also currently empty,
12957 // then paste the line before the current line of the buffer.
12958 let column = selection.start.to_point(&snapshot).column as usize;
12959 let line_start = selection.start - column;
12960 (line_start..line_start, Cow::Borrowed(to_insert))
12961 } else {
12962 let language = snapshot.language_at(selection.head());
12963 let range = selection.range();
12964 if let Some(language) = language
12965 && language.name() == "Markdown".into()
12966 {
12967 edit_for_markdown_paste(
12968 &snapshot,
12969 range,
12970 to_insert,
12971 url::Url::parse(to_insert).ok(),
12972 )
12973 } else {
12974 (range, Cow::Borrowed(to_insert))
12975 }
12976 };
12977
12978 edits.push((range, to_insert));
12979 original_indent_columns.push(original_indent_column);
12980 }
12981 drop(snapshot);
12982
12983 buffer.edit(
12984 edits,
12985 if auto_indent_on_paste {
12986 Some(AutoindentMode::Block {
12987 original_indent_columns,
12988 })
12989 } else {
12990 None
12991 },
12992 cx,
12993 );
12994 });
12995
12996 let selections = this
12997 .selections
12998 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12999 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13000 } else {
13001 let url = url::Url::parse(&clipboard_text).ok();
13002
13003 let auto_indent_mode = if !clipboard_text.is_empty() {
13004 Some(AutoindentMode::Block {
13005 original_indent_columns: Vec::new(),
13006 })
13007 } else {
13008 None
13009 };
13010
13011 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13012 let snapshot = buffer.snapshot(cx);
13013
13014 let anchors = old_selections
13015 .iter()
13016 .map(|s| {
13017 let anchor = snapshot.anchor_after(s.head());
13018 s.map(|_| anchor)
13019 })
13020 .collect::<Vec<_>>();
13021
13022 let mut edits = Vec::new();
13023
13024 for selection in old_selections.iter() {
13025 let language = snapshot.language_at(selection.head());
13026 let range = selection.range();
13027
13028 let (edit_range, edit_text) = if let Some(language) = language
13029 && language.name() == "Markdown".into()
13030 {
13031 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13032 } else {
13033 (range, clipboard_text.clone())
13034 };
13035
13036 edits.push((edit_range, edit_text));
13037 }
13038
13039 drop(snapshot);
13040 buffer.edit(edits, auto_indent_mode, cx);
13041
13042 anchors
13043 });
13044
13045 this.change_selections(Default::default(), window, cx, |s| {
13046 s.select_anchors(selection_anchors);
13047 });
13048 }
13049
13050 // 🤔 | .. | show_in_menu |
13051 // | .. | true true
13052 // | had_edit_prediction | false true
13053
13054 let trigger_in_words =
13055 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13056
13057 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13058 });
13059 }
13060
13061 pub fn diff_clipboard_with_selection(
13062 &mut self,
13063 _: &DiffClipboardWithSelection,
13064 window: &mut Window,
13065 cx: &mut Context<Self>,
13066 ) {
13067 let selections = self
13068 .selections
13069 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13070
13071 if selections.is_empty() {
13072 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13073 return;
13074 };
13075
13076 let clipboard_text = match cx.read_from_clipboard() {
13077 Some(item) => match item.entries().first() {
13078 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13079 _ => None,
13080 },
13081 None => None,
13082 };
13083
13084 let Some(clipboard_text) = clipboard_text else {
13085 log::warn!("Clipboard doesn't contain text.");
13086 return;
13087 };
13088
13089 window.dispatch_action(
13090 Box::new(DiffClipboardWithSelectionData {
13091 clipboard_text,
13092 editor: cx.entity(),
13093 }),
13094 cx,
13095 );
13096 }
13097
13098 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13099 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13100 if let Some(item) = cx.read_from_clipboard() {
13101 let entries = item.entries();
13102
13103 match entries.first() {
13104 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13105 // of all the pasted entries.
13106 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13107 .do_paste(
13108 clipboard_string.text(),
13109 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13110 true,
13111 window,
13112 cx,
13113 ),
13114 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13115 }
13116 }
13117 }
13118
13119 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13120 if self.read_only(cx) {
13121 return;
13122 }
13123
13124 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13125
13126 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13127 if let Some((selections, _)) =
13128 self.selection_history.transaction(transaction_id).cloned()
13129 {
13130 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13131 s.select_anchors(selections.to_vec());
13132 });
13133 } else {
13134 log::error!(
13135 "No entry in selection_history found for undo. \
13136 This may correspond to a bug where undo does not update the selection. \
13137 If this is occurring, please add details to \
13138 https://github.com/zed-industries/zed/issues/22692"
13139 );
13140 }
13141 self.request_autoscroll(Autoscroll::fit(), cx);
13142 self.unmark_text(window, cx);
13143 self.refresh_edit_prediction(true, false, window, cx);
13144 cx.emit(EditorEvent::Edited { transaction_id });
13145 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13146 }
13147 }
13148
13149 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13150 if self.read_only(cx) {
13151 return;
13152 }
13153
13154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13155
13156 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13157 if let Some((_, Some(selections))) =
13158 self.selection_history.transaction(transaction_id).cloned()
13159 {
13160 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13161 s.select_anchors(selections.to_vec());
13162 });
13163 } else {
13164 log::error!(
13165 "No entry in selection_history found for redo. \
13166 This may correspond to a bug where undo does not update the selection. \
13167 If this is occurring, please add details to \
13168 https://github.com/zed-industries/zed/issues/22692"
13169 );
13170 }
13171 self.request_autoscroll(Autoscroll::fit(), cx);
13172 self.unmark_text(window, cx);
13173 self.refresh_edit_prediction(true, false, window, cx);
13174 cx.emit(EditorEvent::Edited { transaction_id });
13175 }
13176 }
13177
13178 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13179 self.buffer
13180 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13181 }
13182
13183 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13184 self.buffer
13185 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13186 }
13187
13188 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13190 self.change_selections(Default::default(), window, cx, |s| {
13191 s.move_with(|map, selection| {
13192 let cursor = if selection.is_empty() {
13193 movement::left(map, selection.start)
13194 } else {
13195 selection.start
13196 };
13197 selection.collapse_to(cursor, SelectionGoal::None);
13198 });
13199 })
13200 }
13201
13202 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13204 self.change_selections(Default::default(), window, cx, |s| {
13205 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13206 })
13207 }
13208
13209 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13210 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13211 self.change_selections(Default::default(), window, cx, |s| {
13212 s.move_with(|map, selection| {
13213 let cursor = if selection.is_empty() {
13214 movement::right(map, selection.end)
13215 } else {
13216 selection.end
13217 };
13218 selection.collapse_to(cursor, SelectionGoal::None)
13219 });
13220 })
13221 }
13222
13223 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13224 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13225 self.change_selections(Default::default(), window, cx, |s| {
13226 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13227 });
13228 }
13229
13230 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13231 if self.take_rename(true, window, cx).is_some() {
13232 return;
13233 }
13234
13235 if self.mode.is_single_line() {
13236 cx.propagate();
13237 return;
13238 }
13239
13240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13241
13242 let text_layout_details = &self.text_layout_details(window);
13243 let selection_count = self.selections.count();
13244 let first_selection = self.selections.first_anchor();
13245
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_with(|map, selection| {
13248 if !selection.is_empty() {
13249 selection.goal = SelectionGoal::None;
13250 }
13251 let (cursor, goal) = movement::up(
13252 map,
13253 selection.start,
13254 selection.goal,
13255 false,
13256 text_layout_details,
13257 );
13258 selection.collapse_to(cursor, goal);
13259 });
13260 });
13261
13262 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13263 {
13264 cx.propagate();
13265 }
13266 }
13267
13268 pub fn move_up_by_lines(
13269 &mut self,
13270 action: &MoveUpByLines,
13271 window: &mut Window,
13272 cx: &mut Context<Self>,
13273 ) {
13274 if self.take_rename(true, window, cx).is_some() {
13275 return;
13276 }
13277
13278 if self.mode.is_single_line() {
13279 cx.propagate();
13280 return;
13281 }
13282
13283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13284
13285 let text_layout_details = &self.text_layout_details(window);
13286
13287 self.change_selections(Default::default(), window, cx, |s| {
13288 s.move_with(|map, selection| {
13289 if !selection.is_empty() {
13290 selection.goal = SelectionGoal::None;
13291 }
13292 let (cursor, goal) = movement::up_by_rows(
13293 map,
13294 selection.start,
13295 action.lines,
13296 selection.goal,
13297 false,
13298 text_layout_details,
13299 );
13300 selection.collapse_to(cursor, goal);
13301 });
13302 })
13303 }
13304
13305 pub fn move_down_by_lines(
13306 &mut self,
13307 action: &MoveDownByLines,
13308 window: &mut Window,
13309 cx: &mut Context<Self>,
13310 ) {
13311 if self.take_rename(true, window, cx).is_some() {
13312 return;
13313 }
13314
13315 if self.mode.is_single_line() {
13316 cx.propagate();
13317 return;
13318 }
13319
13320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13321
13322 let text_layout_details = &self.text_layout_details(window);
13323
13324 self.change_selections(Default::default(), window, cx, |s| {
13325 s.move_with(|map, selection| {
13326 if !selection.is_empty() {
13327 selection.goal = SelectionGoal::None;
13328 }
13329 let (cursor, goal) = movement::down_by_rows(
13330 map,
13331 selection.start,
13332 action.lines,
13333 selection.goal,
13334 false,
13335 text_layout_details,
13336 );
13337 selection.collapse_to(cursor, goal);
13338 });
13339 })
13340 }
13341
13342 pub fn select_down_by_lines(
13343 &mut self,
13344 action: &SelectDownByLines,
13345 window: &mut Window,
13346 cx: &mut Context<Self>,
13347 ) {
13348 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13349 let text_layout_details = &self.text_layout_details(window);
13350 self.change_selections(Default::default(), window, cx, |s| {
13351 s.move_heads_with(|map, head, goal| {
13352 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13353 })
13354 })
13355 }
13356
13357 pub fn select_up_by_lines(
13358 &mut self,
13359 action: &SelectUpByLines,
13360 window: &mut Window,
13361 cx: &mut Context<Self>,
13362 ) {
13363 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13364 let text_layout_details = &self.text_layout_details(window);
13365 self.change_selections(Default::default(), window, cx, |s| {
13366 s.move_heads_with(|map, head, goal| {
13367 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13368 })
13369 })
13370 }
13371
13372 pub fn select_page_up(
13373 &mut self,
13374 _: &SelectPageUp,
13375 window: &mut Window,
13376 cx: &mut Context<Self>,
13377 ) {
13378 let Some(row_count) = self.visible_row_count() else {
13379 return;
13380 };
13381
13382 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13383
13384 let text_layout_details = &self.text_layout_details(window);
13385
13386 self.change_selections(Default::default(), window, cx, |s| {
13387 s.move_heads_with(|map, head, goal| {
13388 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13389 })
13390 })
13391 }
13392
13393 pub fn move_page_up(
13394 &mut self,
13395 action: &MovePageUp,
13396 window: &mut Window,
13397 cx: &mut Context<Self>,
13398 ) {
13399 if self.take_rename(true, window, cx).is_some() {
13400 return;
13401 }
13402
13403 if self
13404 .context_menu
13405 .borrow_mut()
13406 .as_mut()
13407 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13408 .unwrap_or(false)
13409 {
13410 return;
13411 }
13412
13413 if matches!(self.mode, EditorMode::SingleLine) {
13414 cx.propagate();
13415 return;
13416 }
13417
13418 let Some(row_count) = self.visible_row_count() else {
13419 return;
13420 };
13421
13422 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13423
13424 let effects = if action.center_cursor {
13425 SelectionEffects::scroll(Autoscroll::center())
13426 } else {
13427 SelectionEffects::default()
13428 };
13429
13430 let text_layout_details = &self.text_layout_details(window);
13431
13432 self.change_selections(effects, window, cx, |s| {
13433 s.move_with(|map, selection| {
13434 if !selection.is_empty() {
13435 selection.goal = SelectionGoal::None;
13436 }
13437 let (cursor, goal) = movement::up_by_rows(
13438 map,
13439 selection.end,
13440 row_count,
13441 selection.goal,
13442 false,
13443 text_layout_details,
13444 );
13445 selection.collapse_to(cursor, goal);
13446 });
13447 });
13448 }
13449
13450 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13451 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13452 let text_layout_details = &self.text_layout_details(window);
13453 self.change_selections(Default::default(), window, cx, |s| {
13454 s.move_heads_with(|map, head, goal| {
13455 movement::up(map, head, goal, false, text_layout_details)
13456 })
13457 })
13458 }
13459
13460 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13461 self.take_rename(true, window, cx);
13462
13463 if self.mode.is_single_line() {
13464 cx.propagate();
13465 return;
13466 }
13467
13468 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13469
13470 let text_layout_details = &self.text_layout_details(window);
13471 let selection_count = self.selections.count();
13472 let first_selection = self.selections.first_anchor();
13473
13474 self.change_selections(Default::default(), window, cx, |s| {
13475 s.move_with(|map, selection| {
13476 if !selection.is_empty() {
13477 selection.goal = SelectionGoal::None;
13478 }
13479 let (cursor, goal) = movement::down(
13480 map,
13481 selection.end,
13482 selection.goal,
13483 false,
13484 text_layout_details,
13485 );
13486 selection.collapse_to(cursor, goal);
13487 });
13488 });
13489
13490 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13491 {
13492 cx.propagate();
13493 }
13494 }
13495
13496 pub fn select_page_down(
13497 &mut self,
13498 _: &SelectPageDown,
13499 window: &mut Window,
13500 cx: &mut Context<Self>,
13501 ) {
13502 let Some(row_count) = self.visible_row_count() else {
13503 return;
13504 };
13505
13506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13507
13508 let text_layout_details = &self.text_layout_details(window);
13509
13510 self.change_selections(Default::default(), window, cx, |s| {
13511 s.move_heads_with(|map, head, goal| {
13512 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13513 })
13514 })
13515 }
13516
13517 pub fn move_page_down(
13518 &mut self,
13519 action: &MovePageDown,
13520 window: &mut Window,
13521 cx: &mut Context<Self>,
13522 ) {
13523 if self.take_rename(true, window, cx).is_some() {
13524 return;
13525 }
13526
13527 if self
13528 .context_menu
13529 .borrow_mut()
13530 .as_mut()
13531 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13532 .unwrap_or(false)
13533 {
13534 return;
13535 }
13536
13537 if matches!(self.mode, EditorMode::SingleLine) {
13538 cx.propagate();
13539 return;
13540 }
13541
13542 let Some(row_count) = self.visible_row_count() else {
13543 return;
13544 };
13545
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13547
13548 let effects = if action.center_cursor {
13549 SelectionEffects::scroll(Autoscroll::center())
13550 } else {
13551 SelectionEffects::default()
13552 };
13553
13554 let text_layout_details = &self.text_layout_details(window);
13555 self.change_selections(effects, window, cx, |s| {
13556 s.move_with(|map, selection| {
13557 if !selection.is_empty() {
13558 selection.goal = SelectionGoal::None;
13559 }
13560 let (cursor, goal) = movement::down_by_rows(
13561 map,
13562 selection.end,
13563 row_count,
13564 selection.goal,
13565 false,
13566 text_layout_details,
13567 );
13568 selection.collapse_to(cursor, goal);
13569 });
13570 });
13571 }
13572
13573 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13575 let text_layout_details = &self.text_layout_details(window);
13576 self.change_selections(Default::default(), window, cx, |s| {
13577 s.move_heads_with(|map, head, goal| {
13578 movement::down(map, head, goal, false, text_layout_details)
13579 })
13580 });
13581 }
13582
13583 pub fn context_menu_first(
13584 &mut self,
13585 _: &ContextMenuFirst,
13586 window: &mut Window,
13587 cx: &mut Context<Self>,
13588 ) {
13589 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13590 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13591 }
13592 }
13593
13594 pub fn context_menu_prev(
13595 &mut self,
13596 _: &ContextMenuPrevious,
13597 window: &mut Window,
13598 cx: &mut Context<Self>,
13599 ) {
13600 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13601 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13602 }
13603 }
13604
13605 pub fn context_menu_next(
13606 &mut self,
13607 _: &ContextMenuNext,
13608 window: &mut Window,
13609 cx: &mut Context<Self>,
13610 ) {
13611 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13612 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13613 }
13614 }
13615
13616 pub fn context_menu_last(
13617 &mut self,
13618 _: &ContextMenuLast,
13619 window: &mut Window,
13620 cx: &mut Context<Self>,
13621 ) {
13622 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13623 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13624 }
13625 }
13626
13627 pub fn signature_help_prev(
13628 &mut self,
13629 _: &SignatureHelpPrevious,
13630 _: &mut Window,
13631 cx: &mut Context<Self>,
13632 ) {
13633 if let Some(popover) = self.signature_help_state.popover_mut() {
13634 if popover.current_signature == 0 {
13635 popover.current_signature = popover.signatures.len() - 1;
13636 } else {
13637 popover.current_signature -= 1;
13638 }
13639 cx.notify();
13640 }
13641 }
13642
13643 pub fn signature_help_next(
13644 &mut self,
13645 _: &SignatureHelpNext,
13646 _: &mut Window,
13647 cx: &mut Context<Self>,
13648 ) {
13649 if let Some(popover) = self.signature_help_state.popover_mut() {
13650 if popover.current_signature + 1 == popover.signatures.len() {
13651 popover.current_signature = 0;
13652 } else {
13653 popover.current_signature += 1;
13654 }
13655 cx.notify();
13656 }
13657 }
13658
13659 pub fn move_to_previous_word_start(
13660 &mut self,
13661 _: &MoveToPreviousWordStart,
13662 window: &mut Window,
13663 cx: &mut Context<Self>,
13664 ) {
13665 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13666 self.change_selections(Default::default(), window, cx, |s| {
13667 s.move_cursors_with(|map, head, _| {
13668 (
13669 movement::previous_word_start(map, head),
13670 SelectionGoal::None,
13671 )
13672 });
13673 })
13674 }
13675
13676 pub fn move_to_previous_subword_start(
13677 &mut self,
13678 _: &MoveToPreviousSubwordStart,
13679 window: &mut Window,
13680 cx: &mut Context<Self>,
13681 ) {
13682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13683 self.change_selections(Default::default(), window, cx, |s| {
13684 s.move_cursors_with(|map, head, _| {
13685 (
13686 movement::previous_subword_start(map, head),
13687 SelectionGoal::None,
13688 )
13689 });
13690 })
13691 }
13692
13693 pub fn select_to_previous_word_start(
13694 &mut self,
13695 _: &SelectToPreviousWordStart,
13696 window: &mut Window,
13697 cx: &mut Context<Self>,
13698 ) {
13699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13700 self.change_selections(Default::default(), window, cx, |s| {
13701 s.move_heads_with(|map, head, _| {
13702 (
13703 movement::previous_word_start(map, head),
13704 SelectionGoal::None,
13705 )
13706 });
13707 })
13708 }
13709
13710 pub fn select_to_previous_subword_start(
13711 &mut self,
13712 _: &SelectToPreviousSubwordStart,
13713 window: &mut Window,
13714 cx: &mut Context<Self>,
13715 ) {
13716 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13717 self.change_selections(Default::default(), window, cx, |s| {
13718 s.move_heads_with(|map, head, _| {
13719 (
13720 movement::previous_subword_start(map, head),
13721 SelectionGoal::None,
13722 )
13723 });
13724 })
13725 }
13726
13727 pub fn delete_to_previous_word_start(
13728 &mut self,
13729 action: &DeleteToPreviousWordStart,
13730 window: &mut Window,
13731 cx: &mut Context<Self>,
13732 ) {
13733 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13734 self.transact(window, cx, |this, window, cx| {
13735 this.select_autoclose_pair(window, cx);
13736 this.change_selections(Default::default(), window, cx, |s| {
13737 s.move_with(|map, selection| {
13738 if selection.is_empty() {
13739 let mut cursor = if action.ignore_newlines {
13740 movement::previous_word_start(map, selection.head())
13741 } else {
13742 movement::previous_word_start_or_newline(map, selection.head())
13743 };
13744 cursor = movement::adjust_greedy_deletion(
13745 map,
13746 selection.head(),
13747 cursor,
13748 action.ignore_brackets,
13749 );
13750 selection.set_head(cursor, SelectionGoal::None);
13751 }
13752 });
13753 });
13754 this.insert("", window, cx);
13755 });
13756 }
13757
13758 pub fn delete_to_previous_subword_start(
13759 &mut self,
13760 _: &DeleteToPreviousSubwordStart,
13761 window: &mut Window,
13762 cx: &mut Context<Self>,
13763 ) {
13764 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13765 self.transact(window, cx, |this, window, cx| {
13766 this.select_autoclose_pair(window, cx);
13767 this.change_selections(Default::default(), window, cx, |s| {
13768 s.move_with(|map, selection| {
13769 if selection.is_empty() {
13770 let mut cursor = movement::previous_subword_start(map, selection.head());
13771 cursor =
13772 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13773 selection.set_head(cursor, SelectionGoal::None);
13774 }
13775 });
13776 });
13777 this.insert("", window, cx);
13778 });
13779 }
13780
13781 pub fn move_to_next_word_end(
13782 &mut self,
13783 _: &MoveToNextWordEnd,
13784 window: &mut Window,
13785 cx: &mut Context<Self>,
13786 ) {
13787 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13788 self.change_selections(Default::default(), window, cx, |s| {
13789 s.move_cursors_with(|map, head, _| {
13790 (movement::next_word_end(map, head), SelectionGoal::None)
13791 });
13792 })
13793 }
13794
13795 pub fn move_to_next_subword_end(
13796 &mut self,
13797 _: &MoveToNextSubwordEnd,
13798 window: &mut Window,
13799 cx: &mut Context<Self>,
13800 ) {
13801 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13802 self.change_selections(Default::default(), window, cx, |s| {
13803 s.move_cursors_with(|map, head, _| {
13804 (movement::next_subword_end(map, head), SelectionGoal::None)
13805 });
13806 })
13807 }
13808
13809 pub fn select_to_next_word_end(
13810 &mut self,
13811 _: &SelectToNextWordEnd,
13812 window: &mut Window,
13813 cx: &mut Context<Self>,
13814 ) {
13815 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13816 self.change_selections(Default::default(), window, cx, |s| {
13817 s.move_heads_with(|map, head, _| {
13818 (movement::next_word_end(map, head), SelectionGoal::None)
13819 });
13820 })
13821 }
13822
13823 pub fn select_to_next_subword_end(
13824 &mut self,
13825 _: &SelectToNextSubwordEnd,
13826 window: &mut Window,
13827 cx: &mut Context<Self>,
13828 ) {
13829 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13830 self.change_selections(Default::default(), window, cx, |s| {
13831 s.move_heads_with(|map, head, _| {
13832 (movement::next_subword_end(map, head), SelectionGoal::None)
13833 });
13834 })
13835 }
13836
13837 pub fn delete_to_next_word_end(
13838 &mut self,
13839 action: &DeleteToNextWordEnd,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) {
13843 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13844 self.transact(window, cx, |this, window, cx| {
13845 this.change_selections(Default::default(), window, cx, |s| {
13846 s.move_with(|map, selection| {
13847 if selection.is_empty() {
13848 let mut cursor = if action.ignore_newlines {
13849 movement::next_word_end(map, selection.head())
13850 } else {
13851 movement::next_word_end_or_newline(map, selection.head())
13852 };
13853 cursor = movement::adjust_greedy_deletion(
13854 map,
13855 selection.head(),
13856 cursor,
13857 action.ignore_brackets,
13858 );
13859 selection.set_head(cursor, SelectionGoal::None);
13860 }
13861 });
13862 });
13863 this.insert("", window, cx);
13864 });
13865 }
13866
13867 pub fn delete_to_next_subword_end(
13868 &mut self,
13869 _: &DeleteToNextSubwordEnd,
13870 window: &mut Window,
13871 cx: &mut Context<Self>,
13872 ) {
13873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13874 self.transact(window, cx, |this, window, cx| {
13875 this.change_selections(Default::default(), window, cx, |s| {
13876 s.move_with(|map, selection| {
13877 if selection.is_empty() {
13878 let mut cursor = movement::next_subword_end(map, selection.head());
13879 cursor =
13880 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13881 selection.set_head(cursor, SelectionGoal::None);
13882 }
13883 });
13884 });
13885 this.insert("", window, cx);
13886 });
13887 }
13888
13889 pub fn move_to_beginning_of_line(
13890 &mut self,
13891 action: &MoveToBeginningOfLine,
13892 window: &mut Window,
13893 cx: &mut Context<Self>,
13894 ) {
13895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13896 self.change_selections(Default::default(), window, cx, |s| {
13897 s.move_cursors_with(|map, head, _| {
13898 (
13899 movement::indented_line_beginning(
13900 map,
13901 head,
13902 action.stop_at_soft_wraps,
13903 action.stop_at_indent,
13904 ),
13905 SelectionGoal::None,
13906 )
13907 });
13908 })
13909 }
13910
13911 pub fn select_to_beginning_of_line(
13912 &mut self,
13913 action: &SelectToBeginningOfLine,
13914 window: &mut Window,
13915 cx: &mut Context<Self>,
13916 ) {
13917 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13918 self.change_selections(Default::default(), window, cx, |s| {
13919 s.move_heads_with(|map, head, _| {
13920 (
13921 movement::indented_line_beginning(
13922 map,
13923 head,
13924 action.stop_at_soft_wraps,
13925 action.stop_at_indent,
13926 ),
13927 SelectionGoal::None,
13928 )
13929 });
13930 });
13931 }
13932
13933 pub fn delete_to_beginning_of_line(
13934 &mut self,
13935 action: &DeleteToBeginningOfLine,
13936 window: &mut Window,
13937 cx: &mut Context<Self>,
13938 ) {
13939 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13940 self.transact(window, cx, |this, window, cx| {
13941 this.change_selections(Default::default(), window, cx, |s| {
13942 s.move_with(|_, selection| {
13943 selection.reversed = true;
13944 });
13945 });
13946
13947 this.select_to_beginning_of_line(
13948 &SelectToBeginningOfLine {
13949 stop_at_soft_wraps: false,
13950 stop_at_indent: action.stop_at_indent,
13951 },
13952 window,
13953 cx,
13954 );
13955 this.backspace(&Backspace, window, cx);
13956 });
13957 }
13958
13959 pub fn move_to_end_of_line(
13960 &mut self,
13961 action: &MoveToEndOfLine,
13962 window: &mut Window,
13963 cx: &mut Context<Self>,
13964 ) {
13965 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13966 self.change_selections(Default::default(), window, cx, |s| {
13967 s.move_cursors_with(|map, head, _| {
13968 (
13969 movement::line_end(map, head, action.stop_at_soft_wraps),
13970 SelectionGoal::None,
13971 )
13972 });
13973 })
13974 }
13975
13976 pub fn select_to_end_of_line(
13977 &mut self,
13978 action: &SelectToEndOfLine,
13979 window: &mut Window,
13980 cx: &mut Context<Self>,
13981 ) {
13982 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13983 self.change_selections(Default::default(), window, cx, |s| {
13984 s.move_heads_with(|map, head, _| {
13985 (
13986 movement::line_end(map, head, action.stop_at_soft_wraps),
13987 SelectionGoal::None,
13988 )
13989 });
13990 })
13991 }
13992
13993 pub fn delete_to_end_of_line(
13994 &mut self,
13995 _: &DeleteToEndOfLine,
13996 window: &mut Window,
13997 cx: &mut Context<Self>,
13998 ) {
13999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14000 self.transact(window, cx, |this, window, cx| {
14001 this.select_to_end_of_line(
14002 &SelectToEndOfLine {
14003 stop_at_soft_wraps: false,
14004 },
14005 window,
14006 cx,
14007 );
14008 this.delete(&Delete, window, cx);
14009 });
14010 }
14011
14012 pub fn cut_to_end_of_line(
14013 &mut self,
14014 action: &CutToEndOfLine,
14015 window: &mut Window,
14016 cx: &mut Context<Self>,
14017 ) {
14018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14019 self.transact(window, cx, |this, window, cx| {
14020 this.select_to_end_of_line(
14021 &SelectToEndOfLine {
14022 stop_at_soft_wraps: false,
14023 },
14024 window,
14025 cx,
14026 );
14027 if !action.stop_at_newlines {
14028 this.change_selections(Default::default(), window, cx, |s| {
14029 s.move_with(|_, sel| {
14030 if sel.is_empty() {
14031 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14032 }
14033 });
14034 });
14035 }
14036 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14037 let item = this.cut_common(false, window, cx);
14038 cx.write_to_clipboard(item);
14039 });
14040 }
14041
14042 pub fn move_to_start_of_paragraph(
14043 &mut self,
14044 _: &MoveToStartOfParagraph,
14045 window: &mut Window,
14046 cx: &mut Context<Self>,
14047 ) {
14048 if matches!(self.mode, EditorMode::SingleLine) {
14049 cx.propagate();
14050 return;
14051 }
14052 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14053 self.change_selections(Default::default(), window, cx, |s| {
14054 s.move_with(|map, selection| {
14055 selection.collapse_to(
14056 movement::start_of_paragraph(map, selection.head(), 1),
14057 SelectionGoal::None,
14058 )
14059 });
14060 })
14061 }
14062
14063 pub fn move_to_end_of_paragraph(
14064 &mut self,
14065 _: &MoveToEndOfParagraph,
14066 window: &mut Window,
14067 cx: &mut Context<Self>,
14068 ) {
14069 if matches!(self.mode, EditorMode::SingleLine) {
14070 cx.propagate();
14071 return;
14072 }
14073 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14074 self.change_selections(Default::default(), window, cx, |s| {
14075 s.move_with(|map, selection| {
14076 selection.collapse_to(
14077 movement::end_of_paragraph(map, selection.head(), 1),
14078 SelectionGoal::None,
14079 )
14080 });
14081 })
14082 }
14083
14084 pub fn select_to_start_of_paragraph(
14085 &mut self,
14086 _: &SelectToStartOfParagraph,
14087 window: &mut Window,
14088 cx: &mut Context<Self>,
14089 ) {
14090 if matches!(self.mode, EditorMode::SingleLine) {
14091 cx.propagate();
14092 return;
14093 }
14094 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14095 self.change_selections(Default::default(), window, cx, |s| {
14096 s.move_heads_with(|map, head, _| {
14097 (
14098 movement::start_of_paragraph(map, head, 1),
14099 SelectionGoal::None,
14100 )
14101 });
14102 })
14103 }
14104
14105 pub fn select_to_end_of_paragraph(
14106 &mut self,
14107 _: &SelectToEndOfParagraph,
14108 window: &mut Window,
14109 cx: &mut Context<Self>,
14110 ) {
14111 if matches!(self.mode, EditorMode::SingleLine) {
14112 cx.propagate();
14113 return;
14114 }
14115 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14116 self.change_selections(Default::default(), window, cx, |s| {
14117 s.move_heads_with(|map, head, _| {
14118 (
14119 movement::end_of_paragraph(map, head, 1),
14120 SelectionGoal::None,
14121 )
14122 });
14123 })
14124 }
14125
14126 pub fn move_to_start_of_excerpt(
14127 &mut self,
14128 _: &MoveToStartOfExcerpt,
14129 window: &mut Window,
14130 cx: &mut Context<Self>,
14131 ) {
14132 if matches!(self.mode, EditorMode::SingleLine) {
14133 cx.propagate();
14134 return;
14135 }
14136 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14137 self.change_selections(Default::default(), window, cx, |s| {
14138 s.move_with(|map, selection| {
14139 selection.collapse_to(
14140 movement::start_of_excerpt(
14141 map,
14142 selection.head(),
14143 workspace::searchable::Direction::Prev,
14144 ),
14145 SelectionGoal::None,
14146 )
14147 });
14148 })
14149 }
14150
14151 pub fn move_to_start_of_next_excerpt(
14152 &mut self,
14153 _: &MoveToStartOfNextExcerpt,
14154 window: &mut Window,
14155 cx: &mut Context<Self>,
14156 ) {
14157 if matches!(self.mode, EditorMode::SingleLine) {
14158 cx.propagate();
14159 return;
14160 }
14161
14162 self.change_selections(Default::default(), window, cx, |s| {
14163 s.move_with(|map, selection| {
14164 selection.collapse_to(
14165 movement::start_of_excerpt(
14166 map,
14167 selection.head(),
14168 workspace::searchable::Direction::Next,
14169 ),
14170 SelectionGoal::None,
14171 )
14172 });
14173 })
14174 }
14175
14176 pub fn move_to_end_of_excerpt(
14177 &mut self,
14178 _: &MoveToEndOfExcerpt,
14179 window: &mut Window,
14180 cx: &mut Context<Self>,
14181 ) {
14182 if matches!(self.mode, EditorMode::SingleLine) {
14183 cx.propagate();
14184 return;
14185 }
14186 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14187 self.change_selections(Default::default(), window, cx, |s| {
14188 s.move_with(|map, selection| {
14189 selection.collapse_to(
14190 movement::end_of_excerpt(
14191 map,
14192 selection.head(),
14193 workspace::searchable::Direction::Next,
14194 ),
14195 SelectionGoal::None,
14196 )
14197 });
14198 })
14199 }
14200
14201 pub fn move_to_end_of_previous_excerpt(
14202 &mut self,
14203 _: &MoveToEndOfPreviousExcerpt,
14204 window: &mut Window,
14205 cx: &mut Context<Self>,
14206 ) {
14207 if matches!(self.mode, EditorMode::SingleLine) {
14208 cx.propagate();
14209 return;
14210 }
14211 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14212 self.change_selections(Default::default(), window, cx, |s| {
14213 s.move_with(|map, selection| {
14214 selection.collapse_to(
14215 movement::end_of_excerpt(
14216 map,
14217 selection.head(),
14218 workspace::searchable::Direction::Prev,
14219 ),
14220 SelectionGoal::None,
14221 )
14222 });
14223 })
14224 }
14225
14226 pub fn select_to_start_of_excerpt(
14227 &mut self,
14228 _: &SelectToStartOfExcerpt,
14229 window: &mut Window,
14230 cx: &mut Context<Self>,
14231 ) {
14232 if matches!(self.mode, EditorMode::SingleLine) {
14233 cx.propagate();
14234 return;
14235 }
14236 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14237 self.change_selections(Default::default(), window, cx, |s| {
14238 s.move_heads_with(|map, head, _| {
14239 (
14240 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14241 SelectionGoal::None,
14242 )
14243 });
14244 })
14245 }
14246
14247 pub fn select_to_start_of_next_excerpt(
14248 &mut self,
14249 _: &SelectToStartOfNextExcerpt,
14250 window: &mut Window,
14251 cx: &mut Context<Self>,
14252 ) {
14253 if matches!(self.mode, EditorMode::SingleLine) {
14254 cx.propagate();
14255 return;
14256 }
14257 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14258 self.change_selections(Default::default(), window, cx, |s| {
14259 s.move_heads_with(|map, head, _| {
14260 (
14261 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14262 SelectionGoal::None,
14263 )
14264 });
14265 })
14266 }
14267
14268 pub fn select_to_end_of_excerpt(
14269 &mut self,
14270 _: &SelectToEndOfExcerpt,
14271 window: &mut Window,
14272 cx: &mut Context<Self>,
14273 ) {
14274 if matches!(self.mode, EditorMode::SingleLine) {
14275 cx.propagate();
14276 return;
14277 }
14278 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14279 self.change_selections(Default::default(), window, cx, |s| {
14280 s.move_heads_with(|map, head, _| {
14281 (
14282 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14283 SelectionGoal::None,
14284 )
14285 });
14286 })
14287 }
14288
14289 pub fn select_to_end_of_previous_excerpt(
14290 &mut self,
14291 _: &SelectToEndOfPreviousExcerpt,
14292 window: &mut Window,
14293 cx: &mut Context<Self>,
14294 ) {
14295 if matches!(self.mode, EditorMode::SingleLine) {
14296 cx.propagate();
14297 return;
14298 }
14299 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14300 self.change_selections(Default::default(), window, cx, |s| {
14301 s.move_heads_with(|map, head, _| {
14302 (
14303 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14304 SelectionGoal::None,
14305 )
14306 });
14307 })
14308 }
14309
14310 pub fn move_to_beginning(
14311 &mut self,
14312 _: &MoveToBeginning,
14313 window: &mut Window,
14314 cx: &mut Context<Self>,
14315 ) {
14316 if matches!(self.mode, EditorMode::SingleLine) {
14317 cx.propagate();
14318 return;
14319 }
14320 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14321 self.change_selections(Default::default(), window, cx, |s| {
14322 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14323 });
14324 }
14325
14326 pub fn select_to_beginning(
14327 &mut self,
14328 _: &SelectToBeginning,
14329 window: &mut Window,
14330 cx: &mut Context<Self>,
14331 ) {
14332 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14333 selection.set_head(Point::zero(), SelectionGoal::None);
14334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14335 self.change_selections(Default::default(), window, cx, |s| {
14336 s.select(vec![selection]);
14337 });
14338 }
14339
14340 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14341 if matches!(self.mode, EditorMode::SingleLine) {
14342 cx.propagate();
14343 return;
14344 }
14345 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14346 let cursor = self.buffer.read(cx).read(cx).len();
14347 self.change_selections(Default::default(), window, cx, |s| {
14348 s.select_ranges(vec![cursor..cursor])
14349 });
14350 }
14351
14352 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14353 self.nav_history = nav_history;
14354 }
14355
14356 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14357 self.nav_history.as_ref()
14358 }
14359
14360 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14361 self.push_to_nav_history(
14362 self.selections.newest_anchor().head(),
14363 None,
14364 false,
14365 true,
14366 cx,
14367 );
14368 }
14369
14370 fn push_to_nav_history(
14371 &mut self,
14372 cursor_anchor: Anchor,
14373 new_position: Option<Point>,
14374 is_deactivate: bool,
14375 always: bool,
14376 cx: &mut Context<Self>,
14377 ) {
14378 if let Some(nav_history) = self.nav_history.as_mut() {
14379 let buffer = self.buffer.read(cx).read(cx);
14380 let cursor_position = cursor_anchor.to_point(&buffer);
14381 let scroll_state = self.scroll_manager.anchor();
14382 let scroll_top_row = scroll_state.top_row(&buffer);
14383 drop(buffer);
14384
14385 if let Some(new_position) = new_position {
14386 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14387 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14388 return;
14389 }
14390 }
14391
14392 nav_history.push(
14393 Some(NavigationData {
14394 cursor_anchor,
14395 cursor_position,
14396 scroll_anchor: scroll_state,
14397 scroll_top_row,
14398 }),
14399 cx,
14400 );
14401 cx.emit(EditorEvent::PushedToNavHistory {
14402 anchor: cursor_anchor,
14403 is_deactivate,
14404 })
14405 }
14406 }
14407
14408 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14409 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14410 let buffer = self.buffer.read(cx).snapshot(cx);
14411 let mut selection = self
14412 .selections
14413 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14414 selection.set_head(buffer.len(), SelectionGoal::None);
14415 self.change_selections(Default::default(), window, cx, |s| {
14416 s.select(vec![selection]);
14417 });
14418 }
14419
14420 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14422 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14423 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14424 });
14425 }
14426
14427 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14429 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14430 let mut selections = self.selections.all::<Point>(&display_map);
14431 let max_point = display_map.buffer_snapshot().max_point();
14432 for selection in &mut selections {
14433 let rows = selection.spanned_rows(true, &display_map);
14434 selection.start = Point::new(rows.start.0, 0);
14435 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14436 selection.reversed = false;
14437 }
14438 self.change_selections(Default::default(), window, cx, |s| {
14439 s.select(selections);
14440 });
14441 }
14442
14443 pub fn split_selection_into_lines(
14444 &mut self,
14445 action: &SplitSelectionIntoLines,
14446 window: &mut Window,
14447 cx: &mut Context<Self>,
14448 ) {
14449 let selections = self
14450 .selections
14451 .all::<Point>(&self.display_snapshot(cx))
14452 .into_iter()
14453 .map(|selection| selection.start..selection.end)
14454 .collect::<Vec<_>>();
14455 self.unfold_ranges(&selections, true, true, cx);
14456
14457 let mut new_selection_ranges = Vec::new();
14458 {
14459 let buffer = self.buffer.read(cx).read(cx);
14460 for selection in selections {
14461 for row in selection.start.row..selection.end.row {
14462 let line_start = Point::new(row, 0);
14463 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14464
14465 if action.keep_selections {
14466 // Keep the selection range for each line
14467 let selection_start = if row == selection.start.row {
14468 selection.start
14469 } else {
14470 line_start
14471 };
14472 new_selection_ranges.push(selection_start..line_end);
14473 } else {
14474 // Collapse to cursor at end of line
14475 new_selection_ranges.push(line_end..line_end);
14476 }
14477 }
14478
14479 let is_multiline_selection = selection.start.row != selection.end.row;
14480 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14481 // so this action feels more ergonomic when paired with other selection operations
14482 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14483 if !should_skip_last {
14484 if action.keep_selections {
14485 if is_multiline_selection {
14486 let line_start = Point::new(selection.end.row, 0);
14487 new_selection_ranges.push(line_start..selection.end);
14488 } else {
14489 new_selection_ranges.push(selection.start..selection.end);
14490 }
14491 } else {
14492 new_selection_ranges.push(selection.end..selection.end);
14493 }
14494 }
14495 }
14496 }
14497 self.change_selections(Default::default(), window, cx, |s| {
14498 s.select_ranges(new_selection_ranges);
14499 });
14500 }
14501
14502 pub fn add_selection_above(
14503 &mut self,
14504 action: &AddSelectionAbove,
14505 window: &mut Window,
14506 cx: &mut Context<Self>,
14507 ) {
14508 self.add_selection(true, action.skip_soft_wrap, window, cx);
14509 }
14510
14511 pub fn add_selection_below(
14512 &mut self,
14513 action: &AddSelectionBelow,
14514 window: &mut Window,
14515 cx: &mut Context<Self>,
14516 ) {
14517 self.add_selection(false, action.skip_soft_wrap, window, cx);
14518 }
14519
14520 fn add_selection(
14521 &mut self,
14522 above: bool,
14523 skip_soft_wrap: bool,
14524 window: &mut Window,
14525 cx: &mut Context<Self>,
14526 ) {
14527 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14528
14529 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14530 let all_selections = self.selections.all::<Point>(&display_map);
14531 let text_layout_details = self.text_layout_details(window);
14532
14533 let (mut columnar_selections, new_selections_to_columnarize) = {
14534 if let Some(state) = self.add_selections_state.as_ref() {
14535 let columnar_selection_ids: HashSet<_> = state
14536 .groups
14537 .iter()
14538 .flat_map(|group| group.stack.iter())
14539 .copied()
14540 .collect();
14541
14542 all_selections
14543 .into_iter()
14544 .partition(|s| columnar_selection_ids.contains(&s.id))
14545 } else {
14546 (Vec::new(), all_selections)
14547 }
14548 };
14549
14550 let mut state = self
14551 .add_selections_state
14552 .take()
14553 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14554
14555 for selection in new_selections_to_columnarize {
14556 let range = selection.display_range(&display_map).sorted();
14557 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14558 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14559 let positions = start_x.min(end_x)..start_x.max(end_x);
14560 let mut stack = Vec::new();
14561 for row in range.start.row().0..=range.end.row().0 {
14562 if let Some(selection) = self.selections.build_columnar_selection(
14563 &display_map,
14564 DisplayRow(row),
14565 &positions,
14566 selection.reversed,
14567 &text_layout_details,
14568 ) {
14569 stack.push(selection.id);
14570 columnar_selections.push(selection);
14571 }
14572 }
14573 if !stack.is_empty() {
14574 if above {
14575 stack.reverse();
14576 }
14577 state.groups.push(AddSelectionsGroup { above, stack });
14578 }
14579 }
14580
14581 let mut final_selections = Vec::new();
14582 let end_row = if above {
14583 DisplayRow(0)
14584 } else {
14585 display_map.max_point().row()
14586 };
14587
14588 let mut last_added_item_per_group = HashMap::default();
14589 for group in state.groups.iter_mut() {
14590 if let Some(last_id) = group.stack.last() {
14591 last_added_item_per_group.insert(*last_id, group);
14592 }
14593 }
14594
14595 for selection in columnar_selections {
14596 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14597 if above == group.above {
14598 let range = selection.display_range(&display_map).sorted();
14599 debug_assert_eq!(range.start.row(), range.end.row());
14600 let mut row = range.start.row();
14601 let positions =
14602 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14603 Pixels::from(start)..Pixels::from(end)
14604 } else {
14605 let start_x =
14606 display_map.x_for_display_point(range.start, &text_layout_details);
14607 let end_x =
14608 display_map.x_for_display_point(range.end, &text_layout_details);
14609 start_x.min(end_x)..start_x.max(end_x)
14610 };
14611
14612 let mut maybe_new_selection = None;
14613 let direction = if above { -1 } else { 1 };
14614
14615 while row != end_row {
14616 if skip_soft_wrap {
14617 row = display_map
14618 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14619 .row();
14620 } else if above {
14621 row.0 -= 1;
14622 } else {
14623 row.0 += 1;
14624 }
14625
14626 if let Some(new_selection) = self.selections.build_columnar_selection(
14627 &display_map,
14628 row,
14629 &positions,
14630 selection.reversed,
14631 &text_layout_details,
14632 ) {
14633 maybe_new_selection = Some(new_selection);
14634 break;
14635 }
14636 }
14637
14638 if let Some(new_selection) = maybe_new_selection {
14639 group.stack.push(new_selection.id);
14640 if above {
14641 final_selections.push(new_selection);
14642 final_selections.push(selection);
14643 } else {
14644 final_selections.push(selection);
14645 final_selections.push(new_selection);
14646 }
14647 } else {
14648 final_selections.push(selection);
14649 }
14650 } else {
14651 group.stack.pop();
14652 }
14653 } else {
14654 final_selections.push(selection);
14655 }
14656 }
14657
14658 self.change_selections(Default::default(), window, cx, |s| {
14659 s.select(final_selections);
14660 });
14661
14662 let final_selection_ids: HashSet<_> = self
14663 .selections
14664 .all::<Point>(&display_map)
14665 .iter()
14666 .map(|s| s.id)
14667 .collect();
14668 state.groups.retain_mut(|group| {
14669 // selections might get merged above so we remove invalid items from stacks
14670 group.stack.retain(|id| final_selection_ids.contains(id));
14671
14672 // single selection in stack can be treated as initial state
14673 group.stack.len() > 1
14674 });
14675
14676 if !state.groups.is_empty() {
14677 self.add_selections_state = Some(state);
14678 }
14679 }
14680
14681 fn select_match_ranges(
14682 &mut self,
14683 range: Range<MultiBufferOffset>,
14684 reversed: bool,
14685 replace_newest: bool,
14686 auto_scroll: Option<Autoscroll>,
14687 window: &mut Window,
14688 cx: &mut Context<Editor>,
14689 ) {
14690 self.unfold_ranges(
14691 std::slice::from_ref(&range),
14692 false,
14693 auto_scroll.is_some(),
14694 cx,
14695 );
14696 let effects = if let Some(scroll) = auto_scroll {
14697 SelectionEffects::scroll(scroll)
14698 } else {
14699 SelectionEffects::no_scroll()
14700 };
14701 self.change_selections(effects, window, cx, |s| {
14702 if replace_newest {
14703 s.delete(s.newest_anchor().id);
14704 }
14705 if reversed {
14706 s.insert_range(range.end..range.start);
14707 } else {
14708 s.insert_range(range);
14709 }
14710 });
14711 }
14712
14713 pub fn select_next_match_internal(
14714 &mut self,
14715 display_map: &DisplaySnapshot,
14716 replace_newest: bool,
14717 autoscroll: Option<Autoscroll>,
14718 window: &mut Window,
14719 cx: &mut Context<Self>,
14720 ) -> Result<()> {
14721 let buffer = display_map.buffer_snapshot();
14722 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14723 if let Some(mut select_next_state) = self.select_next_state.take() {
14724 let query = &select_next_state.query;
14725 if !select_next_state.done {
14726 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14727 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14728 let mut next_selected_range = None;
14729
14730 let bytes_after_last_selection =
14731 buffer.bytes_in_range(last_selection.end..buffer.len());
14732 let bytes_before_first_selection =
14733 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14734 let query_matches = query
14735 .stream_find_iter(bytes_after_last_selection)
14736 .map(|result| (last_selection.end, result))
14737 .chain(
14738 query
14739 .stream_find_iter(bytes_before_first_selection)
14740 .map(|result| (MultiBufferOffset(0), result)),
14741 );
14742
14743 for (start_offset, query_match) in query_matches {
14744 let query_match = query_match.unwrap(); // can only fail due to I/O
14745 let offset_range =
14746 start_offset + query_match.start()..start_offset + query_match.end();
14747
14748 if !select_next_state.wordwise
14749 || (!buffer.is_inside_word(offset_range.start, None)
14750 && !buffer.is_inside_word(offset_range.end, None))
14751 {
14752 let idx = selections
14753 .partition_point(|selection| selection.end <= offset_range.start);
14754 let overlaps = selections
14755 .get(idx)
14756 .map_or(false, |selection| selection.start < offset_range.end);
14757
14758 if !overlaps {
14759 next_selected_range = Some(offset_range);
14760 break;
14761 }
14762 }
14763 }
14764
14765 if let Some(next_selected_range) = next_selected_range {
14766 self.select_match_ranges(
14767 next_selected_range,
14768 last_selection.reversed,
14769 replace_newest,
14770 autoscroll,
14771 window,
14772 cx,
14773 );
14774 } else {
14775 select_next_state.done = true;
14776 }
14777 }
14778
14779 self.select_next_state = Some(select_next_state);
14780 } else {
14781 let mut only_carets = true;
14782 let mut same_text_selected = true;
14783 let mut selected_text = None;
14784
14785 let mut selections_iter = selections.iter().peekable();
14786 while let Some(selection) = selections_iter.next() {
14787 if selection.start != selection.end {
14788 only_carets = false;
14789 }
14790
14791 if same_text_selected {
14792 if selected_text.is_none() {
14793 selected_text =
14794 Some(buffer.text_for_range(selection.range()).collect::<String>());
14795 }
14796
14797 if let Some(next_selection) = selections_iter.peek() {
14798 if next_selection.len() == selection.len() {
14799 let next_selected_text = buffer
14800 .text_for_range(next_selection.range())
14801 .collect::<String>();
14802 if Some(next_selected_text) != selected_text {
14803 same_text_selected = false;
14804 selected_text = None;
14805 }
14806 } else {
14807 same_text_selected = false;
14808 selected_text = None;
14809 }
14810 }
14811 }
14812 }
14813
14814 if only_carets {
14815 for selection in &mut selections {
14816 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14817 selection.start = word_range.start;
14818 selection.end = word_range.end;
14819 selection.goal = SelectionGoal::None;
14820 selection.reversed = false;
14821 self.select_match_ranges(
14822 selection.start..selection.end,
14823 selection.reversed,
14824 replace_newest,
14825 autoscroll,
14826 window,
14827 cx,
14828 );
14829 }
14830
14831 if selections.len() == 1 {
14832 let selection = selections
14833 .last()
14834 .expect("ensured that there's only one selection");
14835 let query = buffer
14836 .text_for_range(selection.start..selection.end)
14837 .collect::<String>();
14838 let is_empty = query.is_empty();
14839 let select_state = SelectNextState {
14840 query: self.build_query(&[query], cx)?,
14841 wordwise: true,
14842 done: is_empty,
14843 };
14844 self.select_next_state = Some(select_state);
14845 } else {
14846 self.select_next_state = None;
14847 }
14848 } else if let Some(selected_text) = selected_text {
14849 self.select_next_state = Some(SelectNextState {
14850 query: self.build_query(&[selected_text], cx)?,
14851 wordwise: false,
14852 done: false,
14853 });
14854 self.select_next_match_internal(
14855 display_map,
14856 replace_newest,
14857 autoscroll,
14858 window,
14859 cx,
14860 )?;
14861 }
14862 }
14863 Ok(())
14864 }
14865
14866 pub fn select_all_matches(
14867 &mut self,
14868 _action: &SelectAllMatches,
14869 window: &mut Window,
14870 cx: &mut Context<Self>,
14871 ) -> Result<()> {
14872 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14873
14874 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14875
14876 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14877 let Some(select_next_state) = self.select_next_state.as_mut() else {
14878 return Ok(());
14879 };
14880 if select_next_state.done {
14881 return Ok(());
14882 }
14883
14884 let mut new_selections = Vec::new();
14885
14886 let reversed = self
14887 .selections
14888 .oldest::<MultiBufferOffset>(&display_map)
14889 .reversed;
14890 let buffer = display_map.buffer_snapshot();
14891 let query_matches = select_next_state
14892 .query
14893 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14894
14895 for query_match in query_matches.into_iter() {
14896 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14897 let offset_range = if reversed {
14898 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14899 } else {
14900 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14901 };
14902
14903 if !select_next_state.wordwise
14904 || (!buffer.is_inside_word(offset_range.start, None)
14905 && !buffer.is_inside_word(offset_range.end, None))
14906 {
14907 new_selections.push(offset_range.start..offset_range.end);
14908 }
14909 }
14910
14911 select_next_state.done = true;
14912
14913 if new_selections.is_empty() {
14914 log::error!("bug: new_selections is empty in select_all_matches");
14915 return Ok(());
14916 }
14917
14918 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14919 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14920 selections.select_ranges(new_selections)
14921 });
14922
14923 Ok(())
14924 }
14925
14926 pub fn select_next(
14927 &mut self,
14928 action: &SelectNext,
14929 window: &mut Window,
14930 cx: &mut Context<Self>,
14931 ) -> Result<()> {
14932 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14933 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14934 self.select_next_match_internal(
14935 &display_map,
14936 action.replace_newest,
14937 Some(Autoscroll::newest()),
14938 window,
14939 cx,
14940 )?;
14941 Ok(())
14942 }
14943
14944 pub fn select_previous(
14945 &mut self,
14946 action: &SelectPrevious,
14947 window: &mut Window,
14948 cx: &mut Context<Self>,
14949 ) -> Result<()> {
14950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14951 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14952 let buffer = display_map.buffer_snapshot();
14953 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14954 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14955 let query = &select_prev_state.query;
14956 if !select_prev_state.done {
14957 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14958 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14959 let mut next_selected_range = None;
14960 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14961 let bytes_before_last_selection =
14962 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
14963 let bytes_after_first_selection =
14964 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14965 let query_matches = query
14966 .stream_find_iter(bytes_before_last_selection)
14967 .map(|result| (last_selection.start, result))
14968 .chain(
14969 query
14970 .stream_find_iter(bytes_after_first_selection)
14971 .map(|result| (buffer.len(), result)),
14972 );
14973 for (end_offset, query_match) in query_matches {
14974 let query_match = query_match.unwrap(); // can only fail due to I/O
14975 let offset_range =
14976 end_offset - query_match.end()..end_offset - query_match.start();
14977
14978 if !select_prev_state.wordwise
14979 || (!buffer.is_inside_word(offset_range.start, None)
14980 && !buffer.is_inside_word(offset_range.end, None))
14981 {
14982 next_selected_range = Some(offset_range);
14983 break;
14984 }
14985 }
14986
14987 if let Some(next_selected_range) = next_selected_range {
14988 self.select_match_ranges(
14989 next_selected_range,
14990 last_selection.reversed,
14991 action.replace_newest,
14992 Some(Autoscroll::newest()),
14993 window,
14994 cx,
14995 );
14996 } else {
14997 select_prev_state.done = true;
14998 }
14999 }
15000
15001 self.select_prev_state = Some(select_prev_state);
15002 } else {
15003 let mut only_carets = true;
15004 let mut same_text_selected = true;
15005 let mut selected_text = None;
15006
15007 let mut selections_iter = selections.iter().peekable();
15008 while let Some(selection) = selections_iter.next() {
15009 if selection.start != selection.end {
15010 only_carets = false;
15011 }
15012
15013 if same_text_selected {
15014 if selected_text.is_none() {
15015 selected_text =
15016 Some(buffer.text_for_range(selection.range()).collect::<String>());
15017 }
15018
15019 if let Some(next_selection) = selections_iter.peek() {
15020 if next_selection.len() == selection.len() {
15021 let next_selected_text = buffer
15022 .text_for_range(next_selection.range())
15023 .collect::<String>();
15024 if Some(next_selected_text) != selected_text {
15025 same_text_selected = false;
15026 selected_text = None;
15027 }
15028 } else {
15029 same_text_selected = false;
15030 selected_text = None;
15031 }
15032 }
15033 }
15034 }
15035
15036 if only_carets {
15037 for selection in &mut selections {
15038 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15039 selection.start = word_range.start;
15040 selection.end = word_range.end;
15041 selection.goal = SelectionGoal::None;
15042 selection.reversed = false;
15043 self.select_match_ranges(
15044 selection.start..selection.end,
15045 selection.reversed,
15046 action.replace_newest,
15047 Some(Autoscroll::newest()),
15048 window,
15049 cx,
15050 );
15051 }
15052 if selections.len() == 1 {
15053 let selection = selections
15054 .last()
15055 .expect("ensured that there's only one selection");
15056 let query = buffer
15057 .text_for_range(selection.start..selection.end)
15058 .collect::<String>();
15059 let is_empty = query.is_empty();
15060 let select_state = SelectNextState {
15061 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15062 wordwise: true,
15063 done: is_empty,
15064 };
15065 self.select_prev_state = Some(select_state);
15066 } else {
15067 self.select_prev_state = None;
15068 }
15069 } else if let Some(selected_text) = selected_text {
15070 self.select_prev_state = Some(SelectNextState {
15071 query: self
15072 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15073 wordwise: false,
15074 done: false,
15075 });
15076 self.select_previous(action, window, cx)?;
15077 }
15078 }
15079 Ok(())
15080 }
15081
15082 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15083 /// setting the case sensitivity based on the global
15084 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15085 /// editor's settings.
15086 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15087 where
15088 I: IntoIterator<Item = P>,
15089 P: AsRef<[u8]>,
15090 {
15091 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15092 || EditorSettings::get_global(cx).search.case_sensitive,
15093 |value| value,
15094 );
15095
15096 let mut builder = AhoCorasickBuilder::new();
15097 builder.ascii_case_insensitive(!case_sensitive);
15098 builder.build(patterns)
15099 }
15100
15101 pub fn find_next_match(
15102 &mut self,
15103 _: &FindNextMatch,
15104 window: &mut Window,
15105 cx: &mut Context<Self>,
15106 ) -> Result<()> {
15107 let selections = self.selections.disjoint_anchors_arc();
15108 match selections.first() {
15109 Some(first) if selections.len() >= 2 => {
15110 self.change_selections(Default::default(), window, cx, |s| {
15111 s.select_ranges([first.range()]);
15112 });
15113 }
15114 _ => self.select_next(
15115 &SelectNext {
15116 replace_newest: true,
15117 },
15118 window,
15119 cx,
15120 )?,
15121 }
15122 Ok(())
15123 }
15124
15125 pub fn find_previous_match(
15126 &mut self,
15127 _: &FindPreviousMatch,
15128 window: &mut Window,
15129 cx: &mut Context<Self>,
15130 ) -> Result<()> {
15131 let selections = self.selections.disjoint_anchors_arc();
15132 match selections.last() {
15133 Some(last) if selections.len() >= 2 => {
15134 self.change_selections(Default::default(), window, cx, |s| {
15135 s.select_ranges([last.range()]);
15136 });
15137 }
15138 _ => self.select_previous(
15139 &SelectPrevious {
15140 replace_newest: true,
15141 },
15142 window,
15143 cx,
15144 )?,
15145 }
15146 Ok(())
15147 }
15148
15149 pub fn toggle_comments(
15150 &mut self,
15151 action: &ToggleComments,
15152 window: &mut Window,
15153 cx: &mut Context<Self>,
15154 ) {
15155 if self.read_only(cx) {
15156 return;
15157 }
15158 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15159 let text_layout_details = &self.text_layout_details(window);
15160 self.transact(window, cx, |this, window, cx| {
15161 let mut selections = this
15162 .selections
15163 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15164 let mut edits = Vec::new();
15165 let mut selection_edit_ranges = Vec::new();
15166 let mut last_toggled_row = None;
15167 let snapshot = this.buffer.read(cx).read(cx);
15168 let empty_str: Arc<str> = Arc::default();
15169 let mut suffixes_inserted = Vec::new();
15170 let ignore_indent = action.ignore_indent;
15171
15172 fn comment_prefix_range(
15173 snapshot: &MultiBufferSnapshot,
15174 row: MultiBufferRow,
15175 comment_prefix: &str,
15176 comment_prefix_whitespace: &str,
15177 ignore_indent: bool,
15178 ) -> Range<Point> {
15179 let indent_size = if ignore_indent {
15180 0
15181 } else {
15182 snapshot.indent_size_for_line(row).len
15183 };
15184
15185 let start = Point::new(row.0, indent_size);
15186
15187 let mut line_bytes = snapshot
15188 .bytes_in_range(start..snapshot.max_point())
15189 .flatten()
15190 .copied();
15191
15192 // If this line currently begins with the line comment prefix, then record
15193 // the range containing the prefix.
15194 if line_bytes
15195 .by_ref()
15196 .take(comment_prefix.len())
15197 .eq(comment_prefix.bytes())
15198 {
15199 // Include any whitespace that matches the comment prefix.
15200 let matching_whitespace_len = line_bytes
15201 .zip(comment_prefix_whitespace.bytes())
15202 .take_while(|(a, b)| a == b)
15203 .count() as u32;
15204 let end = Point::new(
15205 start.row,
15206 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15207 );
15208 start..end
15209 } else {
15210 start..start
15211 }
15212 }
15213
15214 fn comment_suffix_range(
15215 snapshot: &MultiBufferSnapshot,
15216 row: MultiBufferRow,
15217 comment_suffix: &str,
15218 comment_suffix_has_leading_space: bool,
15219 ) -> Range<Point> {
15220 let end = Point::new(row.0, snapshot.line_len(row));
15221 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15222
15223 let mut line_end_bytes = snapshot
15224 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15225 .flatten()
15226 .copied();
15227
15228 let leading_space_len = if suffix_start_column > 0
15229 && line_end_bytes.next() == Some(b' ')
15230 && comment_suffix_has_leading_space
15231 {
15232 1
15233 } else {
15234 0
15235 };
15236
15237 // If this line currently begins with the line comment prefix, then record
15238 // the range containing the prefix.
15239 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15240 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15241 start..end
15242 } else {
15243 end..end
15244 }
15245 }
15246
15247 // TODO: Handle selections that cross excerpts
15248 for selection in &mut selections {
15249 let start_column = snapshot
15250 .indent_size_for_line(MultiBufferRow(selection.start.row))
15251 .len;
15252 let language = if let Some(language) =
15253 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15254 {
15255 language
15256 } else {
15257 continue;
15258 };
15259
15260 selection_edit_ranges.clear();
15261
15262 // If multiple selections contain a given row, avoid processing that
15263 // row more than once.
15264 let mut start_row = MultiBufferRow(selection.start.row);
15265 if last_toggled_row == Some(start_row) {
15266 start_row = start_row.next_row();
15267 }
15268 let end_row =
15269 if selection.end.row > selection.start.row && selection.end.column == 0 {
15270 MultiBufferRow(selection.end.row - 1)
15271 } else {
15272 MultiBufferRow(selection.end.row)
15273 };
15274 last_toggled_row = Some(end_row);
15275
15276 if start_row > end_row {
15277 continue;
15278 }
15279
15280 // If the language has line comments, toggle those.
15281 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15282
15283 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15284 if ignore_indent {
15285 full_comment_prefixes = full_comment_prefixes
15286 .into_iter()
15287 .map(|s| Arc::from(s.trim_end()))
15288 .collect();
15289 }
15290
15291 if !full_comment_prefixes.is_empty() {
15292 let first_prefix = full_comment_prefixes
15293 .first()
15294 .expect("prefixes is non-empty");
15295 let prefix_trimmed_lengths = full_comment_prefixes
15296 .iter()
15297 .map(|p| p.trim_end_matches(' ').len())
15298 .collect::<SmallVec<[usize; 4]>>();
15299
15300 let mut all_selection_lines_are_comments = true;
15301
15302 for row in start_row.0..=end_row.0 {
15303 let row = MultiBufferRow(row);
15304 if start_row < end_row && snapshot.is_line_blank(row) {
15305 continue;
15306 }
15307
15308 let prefix_range = full_comment_prefixes
15309 .iter()
15310 .zip(prefix_trimmed_lengths.iter().copied())
15311 .map(|(prefix, trimmed_prefix_len)| {
15312 comment_prefix_range(
15313 snapshot.deref(),
15314 row,
15315 &prefix[..trimmed_prefix_len],
15316 &prefix[trimmed_prefix_len..],
15317 ignore_indent,
15318 )
15319 })
15320 .max_by_key(|range| range.end.column - range.start.column)
15321 .expect("prefixes is non-empty");
15322
15323 if prefix_range.is_empty() {
15324 all_selection_lines_are_comments = false;
15325 }
15326
15327 selection_edit_ranges.push(prefix_range);
15328 }
15329
15330 if all_selection_lines_are_comments {
15331 edits.extend(
15332 selection_edit_ranges
15333 .iter()
15334 .cloned()
15335 .map(|range| (range, empty_str.clone())),
15336 );
15337 } else {
15338 let min_column = selection_edit_ranges
15339 .iter()
15340 .map(|range| range.start.column)
15341 .min()
15342 .unwrap_or(0);
15343 edits.extend(selection_edit_ranges.iter().map(|range| {
15344 let position = Point::new(range.start.row, min_column);
15345 (position..position, first_prefix.clone())
15346 }));
15347 }
15348 } else if let Some(BlockCommentConfig {
15349 start: full_comment_prefix,
15350 end: comment_suffix,
15351 ..
15352 }) = language.block_comment()
15353 {
15354 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15355 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15356 let prefix_range = comment_prefix_range(
15357 snapshot.deref(),
15358 start_row,
15359 comment_prefix,
15360 comment_prefix_whitespace,
15361 ignore_indent,
15362 );
15363 let suffix_range = comment_suffix_range(
15364 snapshot.deref(),
15365 end_row,
15366 comment_suffix.trim_start_matches(' '),
15367 comment_suffix.starts_with(' '),
15368 );
15369
15370 if prefix_range.is_empty() || suffix_range.is_empty() {
15371 edits.push((
15372 prefix_range.start..prefix_range.start,
15373 full_comment_prefix.clone(),
15374 ));
15375 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15376 suffixes_inserted.push((end_row, comment_suffix.len()));
15377 } else {
15378 edits.push((prefix_range, empty_str.clone()));
15379 edits.push((suffix_range, empty_str.clone()));
15380 }
15381 } else {
15382 continue;
15383 }
15384 }
15385
15386 drop(snapshot);
15387 this.buffer.update(cx, |buffer, cx| {
15388 buffer.edit(edits, None, cx);
15389 });
15390
15391 // Adjust selections so that they end before any comment suffixes that
15392 // were inserted.
15393 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15394 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15395 let snapshot = this.buffer.read(cx).read(cx);
15396 for selection in &mut selections {
15397 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15398 match row.cmp(&MultiBufferRow(selection.end.row)) {
15399 Ordering::Less => {
15400 suffixes_inserted.next();
15401 continue;
15402 }
15403 Ordering::Greater => break,
15404 Ordering::Equal => {
15405 if selection.end.column == snapshot.line_len(row) {
15406 if selection.is_empty() {
15407 selection.start.column -= suffix_len as u32;
15408 }
15409 selection.end.column -= suffix_len as u32;
15410 }
15411 break;
15412 }
15413 }
15414 }
15415 }
15416
15417 drop(snapshot);
15418 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15419
15420 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15421 let selections_on_single_row = selections.windows(2).all(|selections| {
15422 selections[0].start.row == selections[1].start.row
15423 && selections[0].end.row == selections[1].end.row
15424 && selections[0].start.row == selections[0].end.row
15425 });
15426 let selections_selecting = selections
15427 .iter()
15428 .any(|selection| selection.start != selection.end);
15429 let advance_downwards = action.advance_downwards
15430 && selections_on_single_row
15431 && !selections_selecting
15432 && !matches!(this.mode, EditorMode::SingleLine);
15433
15434 if advance_downwards {
15435 let snapshot = this.buffer.read(cx).snapshot(cx);
15436
15437 this.change_selections(Default::default(), window, cx, |s| {
15438 s.move_cursors_with(|display_snapshot, display_point, _| {
15439 let mut point = display_point.to_point(display_snapshot);
15440 point.row += 1;
15441 point = snapshot.clip_point(point, Bias::Left);
15442 let display_point = point.to_display_point(display_snapshot);
15443 let goal = SelectionGoal::HorizontalPosition(
15444 display_snapshot
15445 .x_for_display_point(display_point, text_layout_details)
15446 .into(),
15447 );
15448 (display_point, goal)
15449 })
15450 });
15451 }
15452 });
15453 }
15454
15455 pub fn select_enclosing_symbol(
15456 &mut self,
15457 _: &SelectEnclosingSymbol,
15458 window: &mut Window,
15459 cx: &mut Context<Self>,
15460 ) {
15461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15462
15463 let buffer = self.buffer.read(cx).snapshot(cx);
15464 let old_selections = self
15465 .selections
15466 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15467 .into_boxed_slice();
15468
15469 fn update_selection(
15470 selection: &Selection<MultiBufferOffset>,
15471 buffer_snap: &MultiBufferSnapshot,
15472 ) -> Option<Selection<MultiBufferOffset>> {
15473 let cursor = selection.head();
15474 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15475 for symbol in symbols.iter().rev() {
15476 let start = symbol.range.start.to_offset(buffer_snap);
15477 let end = symbol.range.end.to_offset(buffer_snap);
15478 let new_range = start..end;
15479 if start < selection.start || end > selection.end {
15480 return Some(Selection {
15481 id: selection.id,
15482 start: new_range.start,
15483 end: new_range.end,
15484 goal: SelectionGoal::None,
15485 reversed: selection.reversed,
15486 });
15487 }
15488 }
15489 None
15490 }
15491
15492 let mut selected_larger_symbol = false;
15493 let new_selections = old_selections
15494 .iter()
15495 .map(|selection| match update_selection(selection, &buffer) {
15496 Some(new_selection) => {
15497 if new_selection.range() != selection.range() {
15498 selected_larger_symbol = true;
15499 }
15500 new_selection
15501 }
15502 None => selection.clone(),
15503 })
15504 .collect::<Vec<_>>();
15505
15506 if selected_larger_symbol {
15507 self.change_selections(Default::default(), window, cx, |s| {
15508 s.select(new_selections);
15509 });
15510 }
15511 }
15512
15513 pub fn select_larger_syntax_node(
15514 &mut self,
15515 _: &SelectLargerSyntaxNode,
15516 window: &mut Window,
15517 cx: &mut Context<Self>,
15518 ) {
15519 let Some(visible_row_count) = self.visible_row_count() else {
15520 return;
15521 };
15522 let old_selections: Box<[_]> = self
15523 .selections
15524 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15525 .into();
15526 if old_selections.is_empty() {
15527 return;
15528 }
15529
15530 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15531
15532 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15533 let buffer = self.buffer.read(cx).snapshot(cx);
15534
15535 let mut selected_larger_node = false;
15536 let mut new_selections = old_selections
15537 .iter()
15538 .map(|selection| {
15539 let old_range = selection.start..selection.end;
15540
15541 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15542 // manually select word at selection
15543 if ["string_content", "inline"].contains(&node.kind()) {
15544 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15545 // ignore if word is already selected
15546 if !word_range.is_empty() && old_range != word_range {
15547 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15548 // only select word if start and end point belongs to same word
15549 if word_range == last_word_range {
15550 selected_larger_node = true;
15551 return Selection {
15552 id: selection.id,
15553 start: word_range.start,
15554 end: word_range.end,
15555 goal: SelectionGoal::None,
15556 reversed: selection.reversed,
15557 };
15558 }
15559 }
15560 }
15561 }
15562
15563 let mut new_range = old_range.clone();
15564 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15565 new_range = range;
15566 if !node.is_named() {
15567 continue;
15568 }
15569 if !display_map.intersects_fold(new_range.start)
15570 && !display_map.intersects_fold(new_range.end)
15571 {
15572 break;
15573 }
15574 }
15575
15576 selected_larger_node |= new_range != old_range;
15577 Selection {
15578 id: selection.id,
15579 start: new_range.start,
15580 end: new_range.end,
15581 goal: SelectionGoal::None,
15582 reversed: selection.reversed,
15583 }
15584 })
15585 .collect::<Vec<_>>();
15586
15587 if !selected_larger_node {
15588 return; // don't put this call in the history
15589 }
15590
15591 // scroll based on transformation done to the last selection created by the user
15592 let (last_old, last_new) = old_selections
15593 .last()
15594 .zip(new_selections.last().cloned())
15595 .expect("old_selections isn't empty");
15596
15597 // revert selection
15598 let is_selection_reversed = {
15599 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15600 new_selections.last_mut().expect("checked above").reversed =
15601 should_newest_selection_be_reversed;
15602 should_newest_selection_be_reversed
15603 };
15604
15605 if selected_larger_node {
15606 self.select_syntax_node_history.disable_clearing = true;
15607 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15608 s.select(new_selections.clone());
15609 });
15610 self.select_syntax_node_history.disable_clearing = false;
15611 }
15612
15613 let start_row = last_new.start.to_display_point(&display_map).row().0;
15614 let end_row = last_new.end.to_display_point(&display_map).row().0;
15615 let selection_height = end_row - start_row + 1;
15616 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15617
15618 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15619 let scroll_behavior = if fits_on_the_screen {
15620 self.request_autoscroll(Autoscroll::fit(), cx);
15621 SelectSyntaxNodeScrollBehavior::FitSelection
15622 } else if is_selection_reversed {
15623 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15624 SelectSyntaxNodeScrollBehavior::CursorTop
15625 } else {
15626 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15627 SelectSyntaxNodeScrollBehavior::CursorBottom
15628 };
15629
15630 self.select_syntax_node_history.push((
15631 old_selections,
15632 scroll_behavior,
15633 is_selection_reversed,
15634 ));
15635 }
15636
15637 pub fn select_smaller_syntax_node(
15638 &mut self,
15639 _: &SelectSmallerSyntaxNode,
15640 window: &mut Window,
15641 cx: &mut Context<Self>,
15642 ) {
15643 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15644
15645 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15646 self.select_syntax_node_history.pop()
15647 {
15648 if let Some(selection) = selections.last_mut() {
15649 selection.reversed = is_selection_reversed;
15650 }
15651
15652 self.select_syntax_node_history.disable_clearing = true;
15653 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15654 s.select(selections.to_vec());
15655 });
15656 self.select_syntax_node_history.disable_clearing = false;
15657
15658 match scroll_behavior {
15659 SelectSyntaxNodeScrollBehavior::CursorTop => {
15660 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15661 }
15662 SelectSyntaxNodeScrollBehavior::FitSelection => {
15663 self.request_autoscroll(Autoscroll::fit(), cx);
15664 }
15665 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15666 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15667 }
15668 }
15669 }
15670 }
15671
15672 pub fn unwrap_syntax_node(
15673 &mut self,
15674 _: &UnwrapSyntaxNode,
15675 window: &mut Window,
15676 cx: &mut Context<Self>,
15677 ) {
15678 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15679
15680 let buffer = self.buffer.read(cx).snapshot(cx);
15681 let selections = self
15682 .selections
15683 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15684 .into_iter()
15685 // subtracting the offset requires sorting
15686 .sorted_by_key(|i| i.start);
15687
15688 let full_edits = selections
15689 .into_iter()
15690 .filter_map(|selection| {
15691 let child = if selection.is_empty()
15692 && let Some((_, ancestor_range)) =
15693 buffer.syntax_ancestor(selection.start..selection.end)
15694 {
15695 ancestor_range
15696 } else {
15697 selection.range()
15698 };
15699
15700 let mut parent = child.clone();
15701 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15702 parent = ancestor_range;
15703 if parent.start < child.start || parent.end > child.end {
15704 break;
15705 }
15706 }
15707
15708 if parent == child {
15709 return None;
15710 }
15711 let text = buffer.text_for_range(child).collect::<String>();
15712 Some((selection.id, parent, text))
15713 })
15714 .collect::<Vec<_>>();
15715 if full_edits.is_empty() {
15716 return;
15717 }
15718
15719 self.transact(window, cx, |this, window, cx| {
15720 this.buffer.update(cx, |buffer, cx| {
15721 buffer.edit(
15722 full_edits
15723 .iter()
15724 .map(|(_, p, t)| (p.clone(), t.clone()))
15725 .collect::<Vec<_>>(),
15726 None,
15727 cx,
15728 );
15729 });
15730 this.change_selections(Default::default(), window, cx, |s| {
15731 let mut offset = 0;
15732 let mut selections = vec![];
15733 for (id, parent, text) in full_edits {
15734 let start = parent.start - offset;
15735 offset += (parent.end - parent.start) - text.len();
15736 selections.push(Selection {
15737 id,
15738 start,
15739 end: start + text.len(),
15740 reversed: false,
15741 goal: Default::default(),
15742 });
15743 }
15744 s.select(selections);
15745 });
15746 });
15747 }
15748
15749 pub fn select_next_syntax_node(
15750 &mut self,
15751 _: &SelectNextSyntaxNode,
15752 window: &mut Window,
15753 cx: &mut Context<Self>,
15754 ) {
15755 let old_selections: Box<[_]> = self
15756 .selections
15757 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15758 .into();
15759 if old_selections.is_empty() {
15760 return;
15761 }
15762
15763 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15764
15765 let buffer = self.buffer.read(cx).snapshot(cx);
15766 let mut selected_sibling = false;
15767
15768 let new_selections = old_selections
15769 .iter()
15770 .map(|selection| {
15771 let old_range = selection.start..selection.end;
15772
15773 let old_range =
15774 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15775 let excerpt = buffer.excerpt_containing(old_range.clone());
15776
15777 if let Some(mut excerpt) = excerpt
15778 && let Some(node) = excerpt
15779 .buffer()
15780 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15781 {
15782 let new_range = excerpt.map_range_from_buffer(
15783 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15784 );
15785 selected_sibling = true;
15786 Selection {
15787 id: selection.id,
15788 start: new_range.start,
15789 end: new_range.end,
15790 goal: SelectionGoal::None,
15791 reversed: selection.reversed,
15792 }
15793 } else {
15794 selection.clone()
15795 }
15796 })
15797 .collect::<Vec<_>>();
15798
15799 if selected_sibling {
15800 self.change_selections(
15801 SelectionEffects::scroll(Autoscroll::fit()),
15802 window,
15803 cx,
15804 |s| {
15805 s.select(new_selections);
15806 },
15807 );
15808 }
15809 }
15810
15811 pub fn select_prev_syntax_node(
15812 &mut self,
15813 _: &SelectPreviousSyntaxNode,
15814 window: &mut Window,
15815 cx: &mut Context<Self>,
15816 ) {
15817 let old_selections: Box<[_]> = self
15818 .selections
15819 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15820 .into();
15821 if old_selections.is_empty() {
15822 return;
15823 }
15824
15825 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15826
15827 let buffer = self.buffer.read(cx).snapshot(cx);
15828 let mut selected_sibling = false;
15829
15830 let new_selections = old_selections
15831 .iter()
15832 .map(|selection| {
15833 let old_range = selection.start..selection.end;
15834 let old_range =
15835 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15836 let excerpt = buffer.excerpt_containing(old_range.clone());
15837
15838 if let Some(mut excerpt) = excerpt
15839 && let Some(node) = excerpt
15840 .buffer()
15841 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15842 {
15843 let new_range = excerpt.map_range_from_buffer(
15844 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15845 );
15846 selected_sibling = true;
15847 Selection {
15848 id: selection.id,
15849 start: new_range.start,
15850 end: new_range.end,
15851 goal: SelectionGoal::None,
15852 reversed: selection.reversed,
15853 }
15854 } else {
15855 selection.clone()
15856 }
15857 })
15858 .collect::<Vec<_>>();
15859
15860 if selected_sibling {
15861 self.change_selections(
15862 SelectionEffects::scroll(Autoscroll::fit()),
15863 window,
15864 cx,
15865 |s| {
15866 s.select(new_selections);
15867 },
15868 );
15869 }
15870 }
15871
15872 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15873 if !EditorSettings::get_global(cx).gutter.runnables {
15874 self.clear_tasks();
15875 return Task::ready(());
15876 }
15877 let project = self.project().map(Entity::downgrade);
15878 let task_sources = self.lsp_task_sources(cx);
15879 let multi_buffer = self.buffer.downgrade();
15880 cx.spawn_in(window, async move |editor, cx| {
15881 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15882 let Some(project) = project.and_then(|p| p.upgrade()) else {
15883 return;
15884 };
15885 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15886 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15887 }) else {
15888 return;
15889 };
15890
15891 let hide_runnables = project
15892 .update(cx, |project, _| project.is_via_collab())
15893 .unwrap_or(true);
15894 if hide_runnables {
15895 return;
15896 }
15897 let new_rows =
15898 cx.background_spawn({
15899 let snapshot = display_snapshot.clone();
15900 async move {
15901 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15902 }
15903 })
15904 .await;
15905 let Ok(lsp_tasks) =
15906 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15907 else {
15908 return;
15909 };
15910 let lsp_tasks = lsp_tasks.await;
15911
15912 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15913 lsp_tasks
15914 .into_iter()
15915 .flat_map(|(kind, tasks)| {
15916 tasks.into_iter().filter_map(move |(location, task)| {
15917 Some((kind.clone(), location?, task))
15918 })
15919 })
15920 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15921 let buffer = location.target.buffer;
15922 let buffer_snapshot = buffer.read(cx).snapshot();
15923 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15924 |(excerpt_id, snapshot, _)| {
15925 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15926 display_snapshot
15927 .buffer_snapshot()
15928 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15929 } else {
15930 None
15931 }
15932 },
15933 );
15934 if let Some(offset) = offset {
15935 let task_buffer_range =
15936 location.target.range.to_point(&buffer_snapshot);
15937 let context_buffer_range =
15938 task_buffer_range.to_offset(&buffer_snapshot);
15939 let context_range = BufferOffset(context_buffer_range.start)
15940 ..BufferOffset(context_buffer_range.end);
15941
15942 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15943 .or_insert_with(|| RunnableTasks {
15944 templates: Vec::new(),
15945 offset,
15946 column: task_buffer_range.start.column,
15947 extra_variables: HashMap::default(),
15948 context_range,
15949 })
15950 .templates
15951 .push((kind, task.original_task().clone()));
15952 }
15953
15954 acc
15955 })
15956 }) else {
15957 return;
15958 };
15959
15960 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15961 buffer.language_settings(cx).tasks.prefer_lsp
15962 }) else {
15963 return;
15964 };
15965
15966 let rows = Self::runnable_rows(
15967 project,
15968 display_snapshot,
15969 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15970 new_rows,
15971 cx.clone(),
15972 )
15973 .await;
15974 editor
15975 .update(cx, |editor, _| {
15976 editor.clear_tasks();
15977 for (key, mut value) in rows {
15978 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15979 value.templates.extend(lsp_tasks.templates);
15980 }
15981
15982 editor.insert_tasks(key, value);
15983 }
15984 for (key, value) in lsp_tasks_by_rows {
15985 editor.insert_tasks(key, value);
15986 }
15987 })
15988 .ok();
15989 })
15990 }
15991 fn fetch_runnable_ranges(
15992 snapshot: &DisplaySnapshot,
15993 range: Range<Anchor>,
15994 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
15995 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15996 }
15997
15998 fn runnable_rows(
15999 project: Entity<Project>,
16000 snapshot: DisplaySnapshot,
16001 prefer_lsp: bool,
16002 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16003 cx: AsyncWindowContext,
16004 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16005 cx.spawn(async move |cx| {
16006 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16007 for (run_range, mut runnable) in runnable_ranges {
16008 let Some(tasks) = cx
16009 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16010 .ok()
16011 else {
16012 continue;
16013 };
16014 let mut tasks = tasks.await;
16015
16016 if prefer_lsp {
16017 tasks.retain(|(task_kind, _)| {
16018 !matches!(task_kind, TaskSourceKind::Language { .. })
16019 });
16020 }
16021 if tasks.is_empty() {
16022 continue;
16023 }
16024
16025 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16026 let Some(row) = snapshot
16027 .buffer_snapshot()
16028 .buffer_line_for_row(MultiBufferRow(point.row))
16029 .map(|(_, range)| range.start.row)
16030 else {
16031 continue;
16032 };
16033
16034 let context_range =
16035 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16036 runnable_rows.push((
16037 (runnable.buffer_id, row),
16038 RunnableTasks {
16039 templates: tasks,
16040 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16041 context_range,
16042 column: point.column,
16043 extra_variables: runnable.extra_captures,
16044 },
16045 ));
16046 }
16047 runnable_rows
16048 })
16049 }
16050
16051 fn templates_with_tags(
16052 project: &Entity<Project>,
16053 runnable: &mut Runnable,
16054 cx: &mut App,
16055 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16056 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16057 let (worktree_id, file) = project
16058 .buffer_for_id(runnable.buffer, cx)
16059 .and_then(|buffer| buffer.read(cx).file())
16060 .map(|file| (file.worktree_id(cx), file.clone()))
16061 .unzip();
16062
16063 (
16064 project.task_store().read(cx).task_inventory().cloned(),
16065 worktree_id,
16066 file,
16067 )
16068 });
16069
16070 let tags = mem::take(&mut runnable.tags);
16071 let language = runnable.language.clone();
16072 cx.spawn(async move |cx| {
16073 let mut templates_with_tags = Vec::new();
16074 if let Some(inventory) = inventory {
16075 for RunnableTag(tag) in tags {
16076 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16077 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16078 }) else {
16079 return templates_with_tags;
16080 };
16081 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16082 move |(_, template)| {
16083 template.tags.iter().any(|source_tag| source_tag == &tag)
16084 },
16085 ));
16086 }
16087 }
16088 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16089
16090 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16091 // Strongest source wins; if we have worktree tag binding, prefer that to
16092 // global and language bindings;
16093 // if we have a global binding, prefer that to language binding.
16094 let first_mismatch = templates_with_tags
16095 .iter()
16096 .position(|(tag_source, _)| tag_source != leading_tag_source);
16097 if let Some(index) = first_mismatch {
16098 templates_with_tags.truncate(index);
16099 }
16100 }
16101
16102 templates_with_tags
16103 })
16104 }
16105
16106 pub fn move_to_enclosing_bracket(
16107 &mut self,
16108 _: &MoveToEnclosingBracket,
16109 window: &mut Window,
16110 cx: &mut Context<Self>,
16111 ) {
16112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16113 self.change_selections(Default::default(), window, cx, |s| {
16114 s.move_offsets_with(|snapshot, selection| {
16115 let Some(enclosing_bracket_ranges) =
16116 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16117 else {
16118 return;
16119 };
16120
16121 let mut best_length = usize::MAX;
16122 let mut best_inside = false;
16123 let mut best_in_bracket_range = false;
16124 let mut best_destination = None;
16125 for (open, close) in enclosing_bracket_ranges {
16126 let close = close.to_inclusive();
16127 let length = *close.end() - open.start;
16128 let inside = selection.start >= open.end && selection.end <= *close.start();
16129 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16130 || close.contains(&selection.head());
16131
16132 // If best is next to a bracket and current isn't, skip
16133 if !in_bracket_range && best_in_bracket_range {
16134 continue;
16135 }
16136
16137 // Prefer smaller lengths unless best is inside and current isn't
16138 if length > best_length && (best_inside || !inside) {
16139 continue;
16140 }
16141
16142 best_length = length;
16143 best_inside = inside;
16144 best_in_bracket_range = in_bracket_range;
16145 best_destination = Some(
16146 if close.contains(&selection.start) && close.contains(&selection.end) {
16147 if inside { open.end } else { open.start }
16148 } else if inside {
16149 *close.start()
16150 } else {
16151 *close.end()
16152 },
16153 );
16154 }
16155
16156 if let Some(destination) = best_destination {
16157 selection.collapse_to(destination, SelectionGoal::None);
16158 }
16159 })
16160 });
16161 }
16162
16163 pub fn undo_selection(
16164 &mut self,
16165 _: &UndoSelection,
16166 window: &mut Window,
16167 cx: &mut Context<Self>,
16168 ) {
16169 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16170 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16171 self.selection_history.mode = SelectionHistoryMode::Undoing;
16172 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16173 this.end_selection(window, cx);
16174 this.change_selections(
16175 SelectionEffects::scroll(Autoscroll::newest()),
16176 window,
16177 cx,
16178 |s| s.select_anchors(entry.selections.to_vec()),
16179 );
16180 });
16181 self.selection_history.mode = SelectionHistoryMode::Normal;
16182
16183 self.select_next_state = entry.select_next_state;
16184 self.select_prev_state = entry.select_prev_state;
16185 self.add_selections_state = entry.add_selections_state;
16186 }
16187 }
16188
16189 pub fn redo_selection(
16190 &mut self,
16191 _: &RedoSelection,
16192 window: &mut Window,
16193 cx: &mut Context<Self>,
16194 ) {
16195 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16196 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16197 self.selection_history.mode = SelectionHistoryMode::Redoing;
16198 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16199 this.end_selection(window, cx);
16200 this.change_selections(
16201 SelectionEffects::scroll(Autoscroll::newest()),
16202 window,
16203 cx,
16204 |s| s.select_anchors(entry.selections.to_vec()),
16205 );
16206 });
16207 self.selection_history.mode = SelectionHistoryMode::Normal;
16208
16209 self.select_next_state = entry.select_next_state;
16210 self.select_prev_state = entry.select_prev_state;
16211 self.add_selections_state = entry.add_selections_state;
16212 }
16213 }
16214
16215 pub fn expand_excerpts(
16216 &mut self,
16217 action: &ExpandExcerpts,
16218 _: &mut Window,
16219 cx: &mut Context<Self>,
16220 ) {
16221 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16222 }
16223
16224 pub fn expand_excerpts_down(
16225 &mut self,
16226 action: &ExpandExcerptsDown,
16227 _: &mut Window,
16228 cx: &mut Context<Self>,
16229 ) {
16230 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16231 }
16232
16233 pub fn expand_excerpts_up(
16234 &mut self,
16235 action: &ExpandExcerptsUp,
16236 _: &mut Window,
16237 cx: &mut Context<Self>,
16238 ) {
16239 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16240 }
16241
16242 pub fn expand_excerpts_for_direction(
16243 &mut self,
16244 lines: u32,
16245 direction: ExpandExcerptDirection,
16246
16247 cx: &mut Context<Self>,
16248 ) {
16249 let selections = self.selections.disjoint_anchors_arc();
16250
16251 let lines = if lines == 0 {
16252 EditorSettings::get_global(cx).expand_excerpt_lines
16253 } else {
16254 lines
16255 };
16256
16257 self.buffer.update(cx, |buffer, cx| {
16258 let snapshot = buffer.snapshot(cx);
16259 let mut excerpt_ids = selections
16260 .iter()
16261 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16262 .collect::<Vec<_>>();
16263 excerpt_ids.sort();
16264 excerpt_ids.dedup();
16265 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16266 })
16267 }
16268
16269 pub fn expand_excerpt(
16270 &mut self,
16271 excerpt: ExcerptId,
16272 direction: ExpandExcerptDirection,
16273 window: &mut Window,
16274 cx: &mut Context<Self>,
16275 ) {
16276 let current_scroll_position = self.scroll_position(cx);
16277 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16278 let mut scroll = None;
16279
16280 if direction == ExpandExcerptDirection::Down {
16281 let multi_buffer = self.buffer.read(cx);
16282 let snapshot = multi_buffer.snapshot(cx);
16283 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16284 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16285 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16286 {
16287 let buffer_snapshot = buffer.read(cx).snapshot();
16288 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16289 let last_row = buffer_snapshot.max_point().row;
16290 let lines_below = last_row.saturating_sub(excerpt_end_row);
16291 if lines_below >= lines_to_expand {
16292 scroll = Some(
16293 current_scroll_position
16294 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16295 );
16296 }
16297 }
16298 }
16299 if direction == ExpandExcerptDirection::Up
16300 && self
16301 .buffer
16302 .read(cx)
16303 .snapshot(cx)
16304 .excerpt_before(excerpt)
16305 .is_none()
16306 {
16307 scroll = Some(current_scroll_position);
16308 }
16309
16310 self.buffer.update(cx, |buffer, cx| {
16311 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16312 });
16313
16314 if let Some(new_scroll_position) = scroll {
16315 self.set_scroll_position(new_scroll_position, window, cx);
16316 }
16317 }
16318
16319 pub fn go_to_singleton_buffer_point(
16320 &mut self,
16321 point: Point,
16322 window: &mut Window,
16323 cx: &mut Context<Self>,
16324 ) {
16325 self.go_to_singleton_buffer_range(point..point, window, cx);
16326 }
16327
16328 pub fn go_to_singleton_buffer_range(
16329 &mut self,
16330 range: Range<Point>,
16331 window: &mut Window,
16332 cx: &mut Context<Self>,
16333 ) {
16334 let multibuffer = self.buffer().read(cx);
16335 let Some(buffer) = multibuffer.as_singleton() else {
16336 return;
16337 };
16338 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16339 return;
16340 };
16341 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16342 return;
16343 };
16344 self.change_selections(
16345 SelectionEffects::default().nav_history(true),
16346 window,
16347 cx,
16348 |s| s.select_anchor_ranges([start..end]),
16349 );
16350 }
16351
16352 pub fn go_to_diagnostic(
16353 &mut self,
16354 action: &GoToDiagnostic,
16355 window: &mut Window,
16356 cx: &mut Context<Self>,
16357 ) {
16358 if !self.diagnostics_enabled() {
16359 return;
16360 }
16361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16362 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16363 }
16364
16365 pub fn go_to_prev_diagnostic(
16366 &mut self,
16367 action: &GoToPreviousDiagnostic,
16368 window: &mut Window,
16369 cx: &mut Context<Self>,
16370 ) {
16371 if !self.diagnostics_enabled() {
16372 return;
16373 }
16374 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16375 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16376 }
16377
16378 pub fn go_to_diagnostic_impl(
16379 &mut self,
16380 direction: Direction,
16381 severity: GoToDiagnosticSeverityFilter,
16382 window: &mut Window,
16383 cx: &mut Context<Self>,
16384 ) {
16385 let buffer = self.buffer.read(cx).snapshot(cx);
16386 let selection = self
16387 .selections
16388 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16389
16390 let mut active_group_id = None;
16391 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16392 && active_group.active_range.start.to_offset(&buffer) == selection.start
16393 {
16394 active_group_id = Some(active_group.group_id);
16395 }
16396
16397 fn filtered<'a>(
16398 severity: GoToDiagnosticSeverityFilter,
16399 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16400 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16401 diagnostics
16402 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16403 .filter(|entry| entry.range.start != entry.range.end)
16404 .filter(|entry| !entry.diagnostic.is_unnecessary)
16405 }
16406
16407 let before = filtered(
16408 severity,
16409 buffer
16410 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16411 .filter(|entry| entry.range.start <= selection.start),
16412 );
16413 let after = filtered(
16414 severity,
16415 buffer
16416 .diagnostics_in_range(selection.start..buffer.len())
16417 .filter(|entry| entry.range.start >= selection.start),
16418 );
16419
16420 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16421 if direction == Direction::Prev {
16422 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16423 {
16424 for diagnostic in prev_diagnostics.into_iter().rev() {
16425 if diagnostic.range.start != selection.start
16426 || active_group_id
16427 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16428 {
16429 found = Some(diagnostic);
16430 break 'outer;
16431 }
16432 }
16433 }
16434 } else {
16435 for diagnostic in after.chain(before) {
16436 if diagnostic.range.start != selection.start
16437 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16438 {
16439 found = Some(diagnostic);
16440 break;
16441 }
16442 }
16443 }
16444 let Some(next_diagnostic) = found else {
16445 return;
16446 };
16447
16448 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16449 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16450 return;
16451 };
16452 let snapshot = self.snapshot(window, cx);
16453 if snapshot.intersects_fold(next_diagnostic.range.start) {
16454 self.unfold_ranges(
16455 std::slice::from_ref(&next_diagnostic.range),
16456 true,
16457 false,
16458 cx,
16459 );
16460 }
16461 self.change_selections(Default::default(), window, cx, |s| {
16462 s.select_ranges(vec![
16463 next_diagnostic.range.start..next_diagnostic.range.start,
16464 ])
16465 });
16466 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16467 self.refresh_edit_prediction(false, true, window, cx);
16468 }
16469
16470 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16471 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16472 let snapshot = self.snapshot(window, cx);
16473 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16474 self.go_to_hunk_before_or_after_position(
16475 &snapshot,
16476 selection.head(),
16477 Direction::Next,
16478 window,
16479 cx,
16480 );
16481 }
16482
16483 pub fn go_to_hunk_before_or_after_position(
16484 &mut self,
16485 snapshot: &EditorSnapshot,
16486 position: Point,
16487 direction: Direction,
16488 window: &mut Window,
16489 cx: &mut Context<Editor>,
16490 ) {
16491 let row = if direction == Direction::Next {
16492 self.hunk_after_position(snapshot, position)
16493 .map(|hunk| hunk.row_range.start)
16494 } else {
16495 self.hunk_before_position(snapshot, position)
16496 };
16497
16498 if let Some(row) = row {
16499 let destination = Point::new(row.0, 0);
16500 let autoscroll = Autoscroll::center();
16501
16502 self.unfold_ranges(&[destination..destination], false, false, cx);
16503 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16504 s.select_ranges([destination..destination]);
16505 });
16506 }
16507 }
16508
16509 fn hunk_after_position(
16510 &mut self,
16511 snapshot: &EditorSnapshot,
16512 position: Point,
16513 ) -> Option<MultiBufferDiffHunk> {
16514 snapshot
16515 .buffer_snapshot()
16516 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16517 .find(|hunk| hunk.row_range.start.0 > position.row)
16518 .or_else(|| {
16519 snapshot
16520 .buffer_snapshot()
16521 .diff_hunks_in_range(Point::zero()..position)
16522 .find(|hunk| hunk.row_range.end.0 < position.row)
16523 })
16524 }
16525
16526 fn go_to_prev_hunk(
16527 &mut self,
16528 _: &GoToPreviousHunk,
16529 window: &mut Window,
16530 cx: &mut Context<Self>,
16531 ) {
16532 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16533 let snapshot = self.snapshot(window, cx);
16534 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16535 self.go_to_hunk_before_or_after_position(
16536 &snapshot,
16537 selection.head(),
16538 Direction::Prev,
16539 window,
16540 cx,
16541 );
16542 }
16543
16544 fn hunk_before_position(
16545 &mut self,
16546 snapshot: &EditorSnapshot,
16547 position: Point,
16548 ) -> Option<MultiBufferRow> {
16549 snapshot
16550 .buffer_snapshot()
16551 .diff_hunk_before(position)
16552 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16553 }
16554
16555 fn go_to_next_change(
16556 &mut self,
16557 _: &GoToNextChange,
16558 window: &mut Window,
16559 cx: &mut Context<Self>,
16560 ) {
16561 if let Some(selections) = self
16562 .change_list
16563 .next_change(1, Direction::Next)
16564 .map(|s| s.to_vec())
16565 {
16566 self.change_selections(Default::default(), window, cx, |s| {
16567 let map = s.display_snapshot();
16568 s.select_display_ranges(selections.iter().map(|a| {
16569 let point = a.to_display_point(&map);
16570 point..point
16571 }))
16572 })
16573 }
16574 }
16575
16576 fn go_to_previous_change(
16577 &mut self,
16578 _: &GoToPreviousChange,
16579 window: &mut Window,
16580 cx: &mut Context<Self>,
16581 ) {
16582 if let Some(selections) = self
16583 .change_list
16584 .next_change(1, Direction::Prev)
16585 .map(|s| s.to_vec())
16586 {
16587 self.change_selections(Default::default(), window, cx, |s| {
16588 let map = s.display_snapshot();
16589 s.select_display_ranges(selections.iter().map(|a| {
16590 let point = a.to_display_point(&map);
16591 point..point
16592 }))
16593 })
16594 }
16595 }
16596
16597 pub fn go_to_next_document_highlight(
16598 &mut self,
16599 _: &GoToNextDocumentHighlight,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16604 }
16605
16606 pub fn go_to_prev_document_highlight(
16607 &mut self,
16608 _: &GoToPreviousDocumentHighlight,
16609 window: &mut Window,
16610 cx: &mut Context<Self>,
16611 ) {
16612 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16613 }
16614
16615 pub fn go_to_document_highlight_before_or_after_position(
16616 &mut self,
16617 direction: Direction,
16618 window: &mut Window,
16619 cx: &mut Context<Editor>,
16620 ) {
16621 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16622 let snapshot = self.snapshot(window, cx);
16623 let buffer = &snapshot.buffer_snapshot();
16624 let position = self
16625 .selections
16626 .newest::<Point>(&snapshot.display_snapshot)
16627 .head();
16628 let anchor_position = buffer.anchor_after(position);
16629
16630 // Get all document highlights (both read and write)
16631 let mut all_highlights = Vec::new();
16632
16633 if let Some((_, read_highlights)) = self
16634 .background_highlights
16635 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16636 {
16637 all_highlights.extend(read_highlights.iter());
16638 }
16639
16640 if let Some((_, write_highlights)) = self
16641 .background_highlights
16642 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16643 {
16644 all_highlights.extend(write_highlights.iter());
16645 }
16646
16647 if all_highlights.is_empty() {
16648 return;
16649 }
16650
16651 // Sort highlights by position
16652 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16653
16654 let target_highlight = match direction {
16655 Direction::Next => {
16656 // Find the first highlight after the current position
16657 all_highlights
16658 .iter()
16659 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16660 }
16661 Direction::Prev => {
16662 // Find the last highlight before the current position
16663 all_highlights
16664 .iter()
16665 .rev()
16666 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16667 }
16668 };
16669
16670 if let Some(highlight) = target_highlight {
16671 let destination = highlight.start.to_point(buffer);
16672 let autoscroll = Autoscroll::center();
16673
16674 self.unfold_ranges(&[destination..destination], false, false, cx);
16675 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16676 s.select_ranges([destination..destination]);
16677 });
16678 }
16679 }
16680
16681 fn go_to_line<T: 'static>(
16682 &mut self,
16683 position: Anchor,
16684 highlight_color: Option<Hsla>,
16685 window: &mut Window,
16686 cx: &mut Context<Self>,
16687 ) {
16688 let snapshot = self.snapshot(window, cx).display_snapshot;
16689 let position = position.to_point(&snapshot.buffer_snapshot());
16690 let start = snapshot
16691 .buffer_snapshot()
16692 .clip_point(Point::new(position.row, 0), Bias::Left);
16693 let end = start + Point::new(1, 0);
16694 let start = snapshot.buffer_snapshot().anchor_before(start);
16695 let end = snapshot.buffer_snapshot().anchor_before(end);
16696
16697 self.highlight_rows::<T>(
16698 start..end,
16699 highlight_color
16700 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16701 Default::default(),
16702 cx,
16703 );
16704
16705 if self.buffer.read(cx).is_singleton() {
16706 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16707 }
16708 }
16709
16710 pub fn go_to_definition(
16711 &mut self,
16712 _: &GoToDefinition,
16713 window: &mut Window,
16714 cx: &mut Context<Self>,
16715 ) -> Task<Result<Navigated>> {
16716 let definition =
16717 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16718 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16719 cx.spawn_in(window, async move |editor, cx| {
16720 if definition.await? == Navigated::Yes {
16721 return Ok(Navigated::Yes);
16722 }
16723 match fallback_strategy {
16724 GoToDefinitionFallback::None => Ok(Navigated::No),
16725 GoToDefinitionFallback::FindAllReferences => {
16726 match editor.update_in(cx, |editor, window, cx| {
16727 editor.find_all_references(&FindAllReferences, window, cx)
16728 })? {
16729 Some(references) => references.await,
16730 None => Ok(Navigated::No),
16731 }
16732 }
16733 }
16734 })
16735 }
16736
16737 pub fn go_to_declaration(
16738 &mut self,
16739 _: &GoToDeclaration,
16740 window: &mut Window,
16741 cx: &mut Context<Self>,
16742 ) -> Task<Result<Navigated>> {
16743 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16744 }
16745
16746 pub fn go_to_declaration_split(
16747 &mut self,
16748 _: &GoToDeclaration,
16749 window: &mut Window,
16750 cx: &mut Context<Self>,
16751 ) -> Task<Result<Navigated>> {
16752 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16753 }
16754
16755 pub fn go_to_implementation(
16756 &mut self,
16757 _: &GoToImplementation,
16758 window: &mut Window,
16759 cx: &mut Context<Self>,
16760 ) -> Task<Result<Navigated>> {
16761 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16762 }
16763
16764 pub fn go_to_implementation_split(
16765 &mut self,
16766 _: &GoToImplementationSplit,
16767 window: &mut Window,
16768 cx: &mut Context<Self>,
16769 ) -> Task<Result<Navigated>> {
16770 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16771 }
16772
16773 pub fn go_to_type_definition(
16774 &mut self,
16775 _: &GoToTypeDefinition,
16776 window: &mut Window,
16777 cx: &mut Context<Self>,
16778 ) -> Task<Result<Navigated>> {
16779 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16780 }
16781
16782 pub fn go_to_definition_split(
16783 &mut self,
16784 _: &GoToDefinitionSplit,
16785 window: &mut Window,
16786 cx: &mut Context<Self>,
16787 ) -> Task<Result<Navigated>> {
16788 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16789 }
16790
16791 pub fn go_to_type_definition_split(
16792 &mut self,
16793 _: &GoToTypeDefinitionSplit,
16794 window: &mut Window,
16795 cx: &mut Context<Self>,
16796 ) -> Task<Result<Navigated>> {
16797 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16798 }
16799
16800 fn go_to_definition_of_kind(
16801 &mut self,
16802 kind: GotoDefinitionKind,
16803 split: bool,
16804 window: &mut Window,
16805 cx: &mut Context<Self>,
16806 ) -> Task<Result<Navigated>> {
16807 let Some(provider) = self.semantics_provider.clone() else {
16808 return Task::ready(Ok(Navigated::No));
16809 };
16810 let head = self
16811 .selections
16812 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16813 .head();
16814 let buffer = self.buffer.read(cx);
16815 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16816 return Task::ready(Ok(Navigated::No));
16817 };
16818 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16819 return Task::ready(Ok(Navigated::No));
16820 };
16821
16822 cx.spawn_in(window, async move |editor, cx| {
16823 let Some(definitions) = definitions.await? else {
16824 return Ok(Navigated::No);
16825 };
16826 let navigated = editor
16827 .update_in(cx, |editor, window, cx| {
16828 editor.navigate_to_hover_links(
16829 Some(kind),
16830 definitions
16831 .into_iter()
16832 .filter(|location| {
16833 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16834 })
16835 .map(HoverLink::Text)
16836 .collect::<Vec<_>>(),
16837 split,
16838 window,
16839 cx,
16840 )
16841 })?
16842 .await?;
16843 anyhow::Ok(navigated)
16844 })
16845 }
16846
16847 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16848 let selection = self.selections.newest_anchor();
16849 let head = selection.head();
16850 let tail = selection.tail();
16851
16852 let Some((buffer, start_position)) =
16853 self.buffer.read(cx).text_anchor_for_position(head, cx)
16854 else {
16855 return;
16856 };
16857
16858 let end_position = if head != tail {
16859 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16860 return;
16861 };
16862 Some(pos)
16863 } else {
16864 None
16865 };
16866
16867 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16868 let url = if let Some(end_pos) = end_position {
16869 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16870 } else {
16871 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16872 };
16873
16874 if let Some(url) = url {
16875 cx.update(|window, cx| {
16876 if parse_zed_link(&url, cx).is_some() {
16877 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16878 } else {
16879 cx.open_url(&url);
16880 }
16881 })?;
16882 }
16883
16884 anyhow::Ok(())
16885 });
16886
16887 url_finder.detach();
16888 }
16889
16890 pub fn open_selected_filename(
16891 &mut self,
16892 _: &OpenSelectedFilename,
16893 window: &mut Window,
16894 cx: &mut Context<Self>,
16895 ) {
16896 let Some(workspace) = self.workspace() else {
16897 return;
16898 };
16899
16900 let position = self.selections.newest_anchor().head();
16901
16902 let Some((buffer, buffer_position)) =
16903 self.buffer.read(cx).text_anchor_for_position(position, cx)
16904 else {
16905 return;
16906 };
16907
16908 let project = self.project.clone();
16909
16910 cx.spawn_in(window, async move |_, cx| {
16911 let result = find_file(&buffer, project, buffer_position, cx).await;
16912
16913 if let Some((_, path)) = result {
16914 workspace
16915 .update_in(cx, |workspace, window, cx| {
16916 workspace.open_resolved_path(path, window, cx)
16917 })?
16918 .await?;
16919 }
16920 anyhow::Ok(())
16921 })
16922 .detach();
16923 }
16924
16925 pub(crate) fn navigate_to_hover_links(
16926 &mut self,
16927 kind: Option<GotoDefinitionKind>,
16928 definitions: Vec<HoverLink>,
16929 split: bool,
16930 window: &mut Window,
16931 cx: &mut Context<Editor>,
16932 ) -> Task<Result<Navigated>> {
16933 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16934 let mut first_url_or_file = None;
16935 let definitions: Vec<_> = definitions
16936 .into_iter()
16937 .filter_map(|def| match def {
16938 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16939 HoverLink::InlayHint(lsp_location, server_id) => {
16940 let computation =
16941 self.compute_target_location(lsp_location, server_id, window, cx);
16942 Some(cx.background_spawn(computation))
16943 }
16944 HoverLink::Url(url) => {
16945 first_url_or_file = Some(Either::Left(url));
16946 None
16947 }
16948 HoverLink::File(path) => {
16949 first_url_or_file = Some(Either::Right(path));
16950 None
16951 }
16952 })
16953 .collect();
16954
16955 let workspace = self.workspace();
16956
16957 cx.spawn_in(window, async move |editor, cx| {
16958 let locations: Vec<Location> = future::join_all(definitions)
16959 .await
16960 .into_iter()
16961 .filter_map(|location| location.transpose())
16962 .collect::<Result<_>>()
16963 .context("location tasks")?;
16964 let mut locations = cx.update(|_, cx| {
16965 locations
16966 .into_iter()
16967 .map(|location| {
16968 let buffer = location.buffer.read(cx);
16969 (location.buffer, location.range.to_point(buffer))
16970 })
16971 .into_group_map()
16972 })?;
16973 let mut num_locations = 0;
16974 for ranges in locations.values_mut() {
16975 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16976 ranges.dedup();
16977 num_locations += ranges.len();
16978 }
16979
16980 if num_locations > 1 {
16981 let Some(workspace) = workspace else {
16982 return Ok(Navigated::No);
16983 };
16984
16985 let tab_kind = match kind {
16986 Some(GotoDefinitionKind::Implementation) => "Implementations",
16987 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16988 Some(GotoDefinitionKind::Declaration) => "Declarations",
16989 Some(GotoDefinitionKind::Type) => "Types",
16990 };
16991 let title = editor
16992 .update_in(cx, |_, _, cx| {
16993 let target = locations
16994 .iter()
16995 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16996 .map(|(buffer, location)| {
16997 buffer
16998 .read(cx)
16999 .text_for_range(location.clone())
17000 .collect::<String>()
17001 })
17002 .filter(|text| !text.contains('\n'))
17003 .unique()
17004 .take(3)
17005 .join(", ");
17006 if target.is_empty() {
17007 tab_kind.to_owned()
17008 } else {
17009 format!("{tab_kind} for {target}")
17010 }
17011 })
17012 .context("buffer title")?;
17013
17014 let opened = workspace
17015 .update_in(cx, |workspace, window, cx| {
17016 Self::open_locations_in_multibuffer(
17017 workspace,
17018 locations,
17019 title,
17020 split,
17021 MultibufferSelectionMode::First,
17022 window,
17023 cx,
17024 )
17025 })
17026 .is_ok();
17027
17028 anyhow::Ok(Navigated::from_bool(opened))
17029 } else if num_locations == 0 {
17030 // If there is one url or file, open it directly
17031 match first_url_or_file {
17032 Some(Either::Left(url)) => {
17033 cx.update(|_, cx| cx.open_url(&url))?;
17034 Ok(Navigated::Yes)
17035 }
17036 Some(Either::Right(path)) => {
17037 let Some(workspace) = workspace else {
17038 return Ok(Navigated::No);
17039 };
17040
17041 workspace
17042 .update_in(cx, |workspace, window, cx| {
17043 workspace.open_resolved_path(path, window, cx)
17044 })?
17045 .await?;
17046 Ok(Navigated::Yes)
17047 }
17048 None => Ok(Navigated::No),
17049 }
17050 } else {
17051 let Some(workspace) = workspace else {
17052 return Ok(Navigated::No);
17053 };
17054
17055 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17056 let target_range = target_ranges.first().unwrap().clone();
17057
17058 editor.update_in(cx, |editor, window, cx| {
17059 let range = target_range.to_point(target_buffer.read(cx));
17060 let range = editor.range_for_match(&range);
17061 let range = collapse_multiline_range(range);
17062
17063 if !split
17064 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17065 {
17066 editor.go_to_singleton_buffer_range(range, window, cx);
17067 } else {
17068 let pane = workspace.read(cx).active_pane().clone();
17069 window.defer(cx, move |window, cx| {
17070 let target_editor: Entity<Self> =
17071 workspace.update(cx, |workspace, cx| {
17072 let pane = if split {
17073 workspace.adjacent_pane(window, cx)
17074 } else {
17075 workspace.active_pane().clone()
17076 };
17077
17078 workspace.open_project_item(
17079 pane,
17080 target_buffer.clone(),
17081 true,
17082 true,
17083 window,
17084 cx,
17085 )
17086 });
17087 target_editor.update(cx, |target_editor, cx| {
17088 // When selecting a definition in a different buffer, disable the nav history
17089 // to avoid creating a history entry at the previous cursor location.
17090 pane.update(cx, |pane, _| pane.disable_history());
17091 target_editor.go_to_singleton_buffer_range(range, window, cx);
17092 pane.update(cx, |pane, _| pane.enable_history());
17093 });
17094 });
17095 }
17096 Navigated::Yes
17097 })
17098 }
17099 })
17100 }
17101
17102 fn compute_target_location(
17103 &self,
17104 lsp_location: lsp::Location,
17105 server_id: LanguageServerId,
17106 window: &mut Window,
17107 cx: &mut Context<Self>,
17108 ) -> Task<anyhow::Result<Option<Location>>> {
17109 let Some(project) = self.project.clone() else {
17110 return Task::ready(Ok(None));
17111 };
17112
17113 cx.spawn_in(window, async move |editor, cx| {
17114 let location_task = editor.update(cx, |_, cx| {
17115 project.update(cx, |project, cx| {
17116 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17117 })
17118 })?;
17119 let location = Some({
17120 let target_buffer_handle = location_task.await.context("open local buffer")?;
17121 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17122 let target_start = target_buffer
17123 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17124 let target_end = target_buffer
17125 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17126 target_buffer.anchor_after(target_start)
17127 ..target_buffer.anchor_before(target_end)
17128 })?;
17129 Location {
17130 buffer: target_buffer_handle,
17131 range,
17132 }
17133 });
17134 Ok(location)
17135 })
17136 }
17137
17138 fn go_to_next_reference(
17139 &mut self,
17140 _: &GoToNextReference,
17141 window: &mut Window,
17142 cx: &mut Context<Self>,
17143 ) {
17144 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17145 if let Some(task) = task {
17146 task.detach();
17147 };
17148 }
17149
17150 fn go_to_prev_reference(
17151 &mut self,
17152 _: &GoToPreviousReference,
17153 window: &mut Window,
17154 cx: &mut Context<Self>,
17155 ) {
17156 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17157 if let Some(task) = task {
17158 task.detach();
17159 };
17160 }
17161
17162 pub fn go_to_reference_before_or_after_position(
17163 &mut self,
17164 direction: Direction,
17165 count: usize,
17166 window: &mut Window,
17167 cx: &mut Context<Self>,
17168 ) -> Option<Task<Result<()>>> {
17169 let selection = self.selections.newest_anchor();
17170 let head = selection.head();
17171
17172 let multi_buffer = self.buffer.read(cx);
17173
17174 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17175 let workspace = self.workspace()?;
17176 let project = workspace.read(cx).project().clone();
17177 let references =
17178 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17179 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17180 let Some(locations) = references.await? else {
17181 return Ok(());
17182 };
17183
17184 if locations.is_empty() {
17185 // totally normal - the cursor may be on something which is not
17186 // a symbol (e.g. a keyword)
17187 log::info!("no references found under cursor");
17188 return Ok(());
17189 }
17190
17191 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17192
17193 let (locations, current_location_index) =
17194 multi_buffer.update(cx, |multi_buffer, cx| {
17195 let mut locations = locations
17196 .into_iter()
17197 .filter_map(|loc| {
17198 let start = multi_buffer.buffer_anchor_to_anchor(
17199 &loc.buffer,
17200 loc.range.start,
17201 cx,
17202 )?;
17203 let end = multi_buffer.buffer_anchor_to_anchor(
17204 &loc.buffer,
17205 loc.range.end,
17206 cx,
17207 )?;
17208 Some(start..end)
17209 })
17210 .collect::<Vec<_>>();
17211
17212 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17213 // There is an O(n) implementation, but given this list will be
17214 // small (usually <100 items), the extra O(log(n)) factor isn't
17215 // worth the (surprisingly large amount of) extra complexity.
17216 locations
17217 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17218
17219 let head_offset = head.to_offset(&multi_buffer_snapshot);
17220
17221 let current_location_index = locations.iter().position(|loc| {
17222 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17223 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17224 });
17225
17226 (locations, current_location_index)
17227 })?;
17228
17229 let Some(current_location_index) = current_location_index else {
17230 // This indicates something has gone wrong, because we already
17231 // handle the "no references" case above
17232 log::error!(
17233 "failed to find current reference under cursor. Total references: {}",
17234 locations.len()
17235 );
17236 return Ok(());
17237 };
17238
17239 let destination_location_index = match direction {
17240 Direction::Next => (current_location_index + count) % locations.len(),
17241 Direction::Prev => {
17242 (current_location_index + locations.len() - count % locations.len())
17243 % locations.len()
17244 }
17245 };
17246
17247 // TODO(cameron): is this needed?
17248 // the thinking is to avoid "jumping to the current location" (avoid
17249 // polluting "jumplist" in vim terms)
17250 if current_location_index == destination_location_index {
17251 return Ok(());
17252 }
17253
17254 let Range { start, end } = locations[destination_location_index];
17255
17256 editor.update_in(cx, |editor, window, cx| {
17257 let effects = SelectionEffects::default();
17258
17259 editor.unfold_ranges(&[start..end], false, false, cx);
17260 editor.change_selections(effects, window, cx, |s| {
17261 s.select_ranges([start..start]);
17262 });
17263 })?;
17264
17265 Ok(())
17266 }))
17267 }
17268
17269 pub fn find_all_references(
17270 &mut self,
17271 _: &FindAllReferences,
17272 window: &mut Window,
17273 cx: &mut Context<Self>,
17274 ) -> Option<Task<Result<Navigated>>> {
17275 let selection = self
17276 .selections
17277 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17278 let multi_buffer = self.buffer.read(cx);
17279 let head = selection.head();
17280
17281 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17282 let head_anchor = multi_buffer_snapshot.anchor_at(
17283 head,
17284 if head < selection.tail() {
17285 Bias::Right
17286 } else {
17287 Bias::Left
17288 },
17289 );
17290
17291 match self
17292 .find_all_references_task_sources
17293 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17294 {
17295 Ok(_) => {
17296 log::info!(
17297 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17298 );
17299 return None;
17300 }
17301 Err(i) => {
17302 self.find_all_references_task_sources.insert(i, head_anchor);
17303 }
17304 }
17305
17306 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17307 let workspace = self.workspace()?;
17308 let project = workspace.read(cx).project().clone();
17309 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17310 Some(cx.spawn_in(window, async move |editor, cx| {
17311 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17312 if let Ok(i) = editor
17313 .find_all_references_task_sources
17314 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17315 {
17316 editor.find_all_references_task_sources.remove(i);
17317 }
17318 });
17319
17320 let Some(locations) = references.await? else {
17321 return anyhow::Ok(Navigated::No);
17322 };
17323 let mut locations = cx.update(|_, cx| {
17324 locations
17325 .into_iter()
17326 .map(|location| {
17327 let buffer = location.buffer.read(cx);
17328 (location.buffer, location.range.to_point(buffer))
17329 })
17330 .into_group_map()
17331 })?;
17332 if locations.is_empty() {
17333 return anyhow::Ok(Navigated::No);
17334 }
17335 for ranges in locations.values_mut() {
17336 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17337 ranges.dedup();
17338 }
17339
17340 workspace.update_in(cx, |workspace, window, cx| {
17341 let target = locations
17342 .iter()
17343 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17344 .map(|(buffer, location)| {
17345 buffer
17346 .read(cx)
17347 .text_for_range(location.clone())
17348 .collect::<String>()
17349 })
17350 .filter(|text| !text.contains('\n'))
17351 .unique()
17352 .take(3)
17353 .join(", ");
17354 let title = if target.is_empty() {
17355 "References".to_owned()
17356 } else {
17357 format!("References to {target}")
17358 };
17359 Self::open_locations_in_multibuffer(
17360 workspace,
17361 locations,
17362 title,
17363 false,
17364 MultibufferSelectionMode::First,
17365 window,
17366 cx,
17367 );
17368 Navigated::Yes
17369 })
17370 }))
17371 }
17372
17373 /// Opens a multibuffer with the given project locations in it
17374 pub fn open_locations_in_multibuffer(
17375 workspace: &mut Workspace,
17376 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17377 title: String,
17378 split: bool,
17379 multibuffer_selection_mode: MultibufferSelectionMode,
17380 window: &mut Window,
17381 cx: &mut Context<Workspace>,
17382 ) {
17383 if locations.is_empty() {
17384 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17385 return;
17386 }
17387
17388 let capability = workspace.project().read(cx).capability();
17389 let mut ranges = <Vec<Range<Anchor>>>::new();
17390
17391 // a key to find existing multibuffer editors with the same set of locations
17392 // to prevent us from opening more and more multibuffer tabs for searches and the like
17393 let mut key = (title.clone(), vec![]);
17394 let excerpt_buffer = cx.new(|cx| {
17395 let key = &mut key.1;
17396 let mut multibuffer = MultiBuffer::new(capability);
17397 for (buffer, mut ranges_for_buffer) in locations {
17398 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17399 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17400 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17401 PathKey::for_buffer(&buffer, cx),
17402 buffer.clone(),
17403 ranges_for_buffer,
17404 multibuffer_context_lines(cx),
17405 cx,
17406 );
17407 ranges.extend(new_ranges)
17408 }
17409
17410 multibuffer.with_title(title)
17411 });
17412 let existing = workspace.active_pane().update(cx, |pane, cx| {
17413 pane.items()
17414 .filter_map(|item| item.downcast::<Editor>())
17415 .find(|editor| {
17416 editor
17417 .read(cx)
17418 .lookup_key
17419 .as_ref()
17420 .and_then(|it| {
17421 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17422 })
17423 .is_some_and(|it| *it == key)
17424 })
17425 });
17426 let editor = existing.unwrap_or_else(|| {
17427 cx.new(|cx| {
17428 let mut editor = Editor::for_multibuffer(
17429 excerpt_buffer,
17430 Some(workspace.project().clone()),
17431 window,
17432 cx,
17433 );
17434 editor.lookup_key = Some(Box::new(key));
17435 editor
17436 })
17437 });
17438 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17439 MultibufferSelectionMode::First => {
17440 if let Some(first_range) = ranges.first() {
17441 editor.change_selections(
17442 SelectionEffects::no_scroll(),
17443 window,
17444 cx,
17445 |selections| {
17446 selections.clear_disjoint();
17447 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17448 },
17449 );
17450 }
17451 editor.highlight_background::<Self>(
17452 &ranges,
17453 |theme| theme.colors().editor_highlighted_line_background,
17454 cx,
17455 );
17456 }
17457 MultibufferSelectionMode::All => {
17458 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17459 selections.clear_disjoint();
17460 selections.select_anchor_ranges(ranges);
17461 });
17462 }
17463 });
17464
17465 let item = Box::new(editor);
17466 let item_id = item.item_id();
17467
17468 if split {
17469 let pane = workspace.adjacent_pane(window, cx);
17470 workspace.add_item(pane, item, None, true, true, window, cx);
17471 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17472 let (preview_item_id, preview_item_idx) =
17473 workspace.active_pane().read_with(cx, |pane, _| {
17474 (pane.preview_item_id(), pane.preview_item_idx())
17475 });
17476
17477 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17478
17479 if let Some(preview_item_id) = preview_item_id {
17480 workspace.active_pane().update(cx, |pane, cx| {
17481 pane.remove_item(preview_item_id, false, false, window, cx);
17482 });
17483 }
17484 } else {
17485 workspace.add_item_to_active_pane(item, None, true, window, cx);
17486 }
17487 workspace.active_pane().update(cx, |pane, cx| {
17488 pane.set_preview_item_id(Some(item_id), cx);
17489 });
17490 }
17491
17492 pub fn rename(
17493 &mut self,
17494 _: &Rename,
17495 window: &mut Window,
17496 cx: &mut Context<Self>,
17497 ) -> Option<Task<Result<()>>> {
17498 use language::ToOffset as _;
17499
17500 let provider = self.semantics_provider.clone()?;
17501 let selection = self.selections.newest_anchor().clone();
17502 let (cursor_buffer, cursor_buffer_position) = self
17503 .buffer
17504 .read(cx)
17505 .text_anchor_for_position(selection.head(), cx)?;
17506 let (tail_buffer, cursor_buffer_position_end) = self
17507 .buffer
17508 .read(cx)
17509 .text_anchor_for_position(selection.tail(), cx)?;
17510 if tail_buffer != cursor_buffer {
17511 return None;
17512 }
17513
17514 let snapshot = cursor_buffer.read(cx).snapshot();
17515 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17516 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17517 let prepare_rename = provider
17518 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17519 .unwrap_or_else(|| Task::ready(Ok(None)));
17520 drop(snapshot);
17521
17522 Some(cx.spawn_in(window, async move |this, cx| {
17523 let rename_range = if let Some(range) = prepare_rename.await? {
17524 Some(range)
17525 } else {
17526 this.update(cx, |this, cx| {
17527 let buffer = this.buffer.read(cx).snapshot(cx);
17528 let mut buffer_highlights = this
17529 .document_highlights_for_position(selection.head(), &buffer)
17530 .filter(|highlight| {
17531 highlight.start.excerpt_id == selection.head().excerpt_id
17532 && highlight.end.excerpt_id == selection.head().excerpt_id
17533 });
17534 buffer_highlights
17535 .next()
17536 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17537 })?
17538 };
17539 if let Some(rename_range) = rename_range {
17540 this.update_in(cx, |this, window, cx| {
17541 let snapshot = cursor_buffer.read(cx).snapshot();
17542 let rename_buffer_range = rename_range.to_offset(&snapshot);
17543 let cursor_offset_in_rename_range =
17544 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17545 let cursor_offset_in_rename_range_end =
17546 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17547
17548 this.take_rename(false, window, cx);
17549 let buffer = this.buffer.read(cx).read(cx);
17550 let cursor_offset = selection.head().to_offset(&buffer);
17551 let rename_start =
17552 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17553 let rename_end = rename_start + rename_buffer_range.len();
17554 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17555 let mut old_highlight_id = None;
17556 let old_name: Arc<str> = buffer
17557 .chunks(rename_start..rename_end, true)
17558 .map(|chunk| {
17559 if old_highlight_id.is_none() {
17560 old_highlight_id = chunk.syntax_highlight_id;
17561 }
17562 chunk.text
17563 })
17564 .collect::<String>()
17565 .into();
17566
17567 drop(buffer);
17568
17569 // Position the selection in the rename editor so that it matches the current selection.
17570 this.show_local_selections = false;
17571 let rename_editor = cx.new(|cx| {
17572 let mut editor = Editor::single_line(window, cx);
17573 editor.buffer.update(cx, |buffer, cx| {
17574 buffer.edit(
17575 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17576 None,
17577 cx,
17578 )
17579 });
17580 let cursor_offset_in_rename_range =
17581 MultiBufferOffset(cursor_offset_in_rename_range);
17582 let cursor_offset_in_rename_range_end =
17583 MultiBufferOffset(cursor_offset_in_rename_range_end);
17584 let rename_selection_range = match cursor_offset_in_rename_range
17585 .cmp(&cursor_offset_in_rename_range_end)
17586 {
17587 Ordering::Equal => {
17588 editor.select_all(&SelectAll, window, cx);
17589 return editor;
17590 }
17591 Ordering::Less => {
17592 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17593 }
17594 Ordering::Greater => {
17595 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17596 }
17597 };
17598 if rename_selection_range.end.0 > old_name.len() {
17599 editor.select_all(&SelectAll, window, cx);
17600 } else {
17601 editor.change_selections(Default::default(), window, cx, |s| {
17602 s.select_ranges([rename_selection_range]);
17603 });
17604 }
17605 editor
17606 });
17607 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17608 if e == &EditorEvent::Focused {
17609 cx.emit(EditorEvent::FocusedIn)
17610 }
17611 })
17612 .detach();
17613
17614 let write_highlights =
17615 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17616 let read_highlights =
17617 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17618 let ranges = write_highlights
17619 .iter()
17620 .flat_map(|(_, ranges)| ranges.iter())
17621 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17622 .cloned()
17623 .collect();
17624
17625 this.highlight_text::<Rename>(
17626 ranges,
17627 HighlightStyle {
17628 fade_out: Some(0.6),
17629 ..Default::default()
17630 },
17631 cx,
17632 );
17633 let rename_focus_handle = rename_editor.focus_handle(cx);
17634 window.focus(&rename_focus_handle);
17635 let block_id = this.insert_blocks(
17636 [BlockProperties {
17637 style: BlockStyle::Flex,
17638 placement: BlockPlacement::Below(range.start),
17639 height: Some(1),
17640 render: Arc::new({
17641 let rename_editor = rename_editor.clone();
17642 move |cx: &mut BlockContext| {
17643 let mut text_style = cx.editor_style.text.clone();
17644 if let Some(highlight_style) = old_highlight_id
17645 .and_then(|h| h.style(&cx.editor_style.syntax))
17646 {
17647 text_style = text_style.highlight(highlight_style);
17648 }
17649 div()
17650 .block_mouse_except_scroll()
17651 .pl(cx.anchor_x)
17652 .child(EditorElement::new(
17653 &rename_editor,
17654 EditorStyle {
17655 background: cx.theme().system().transparent,
17656 local_player: cx.editor_style.local_player,
17657 text: text_style,
17658 scrollbar_width: cx.editor_style.scrollbar_width,
17659 syntax: cx.editor_style.syntax.clone(),
17660 status: cx.editor_style.status.clone(),
17661 inlay_hints_style: HighlightStyle {
17662 font_weight: Some(FontWeight::BOLD),
17663 ..make_inlay_hints_style(cx.app)
17664 },
17665 edit_prediction_styles: make_suggestion_styles(
17666 cx.app,
17667 ),
17668 ..EditorStyle::default()
17669 },
17670 ))
17671 .into_any_element()
17672 }
17673 }),
17674 priority: 0,
17675 }],
17676 Some(Autoscroll::fit()),
17677 cx,
17678 )[0];
17679 this.pending_rename = Some(RenameState {
17680 range,
17681 old_name,
17682 editor: rename_editor,
17683 block_id,
17684 });
17685 })?;
17686 }
17687
17688 Ok(())
17689 }))
17690 }
17691
17692 pub fn confirm_rename(
17693 &mut self,
17694 _: &ConfirmRename,
17695 window: &mut Window,
17696 cx: &mut Context<Self>,
17697 ) -> Option<Task<Result<()>>> {
17698 let rename = self.take_rename(false, window, cx)?;
17699 let workspace = self.workspace()?.downgrade();
17700 let (buffer, start) = self
17701 .buffer
17702 .read(cx)
17703 .text_anchor_for_position(rename.range.start, cx)?;
17704 let (end_buffer, _) = self
17705 .buffer
17706 .read(cx)
17707 .text_anchor_for_position(rename.range.end, cx)?;
17708 if buffer != end_buffer {
17709 return None;
17710 }
17711
17712 let old_name = rename.old_name;
17713 let new_name = rename.editor.read(cx).text(cx);
17714
17715 let rename = self.semantics_provider.as_ref()?.perform_rename(
17716 &buffer,
17717 start,
17718 new_name.clone(),
17719 cx,
17720 )?;
17721
17722 Some(cx.spawn_in(window, async move |editor, cx| {
17723 let project_transaction = rename.await?;
17724 Self::open_project_transaction(
17725 &editor,
17726 workspace,
17727 project_transaction,
17728 format!("Rename: {} → {}", old_name, new_name),
17729 cx,
17730 )
17731 .await?;
17732
17733 editor.update(cx, |editor, cx| {
17734 editor.refresh_document_highlights(cx);
17735 })?;
17736 Ok(())
17737 }))
17738 }
17739
17740 fn take_rename(
17741 &mut self,
17742 moving_cursor: bool,
17743 window: &mut Window,
17744 cx: &mut Context<Self>,
17745 ) -> Option<RenameState> {
17746 let rename = self.pending_rename.take()?;
17747 if rename.editor.focus_handle(cx).is_focused(window) {
17748 window.focus(&self.focus_handle);
17749 }
17750
17751 self.remove_blocks(
17752 [rename.block_id].into_iter().collect(),
17753 Some(Autoscroll::fit()),
17754 cx,
17755 );
17756 self.clear_highlights::<Rename>(cx);
17757 self.show_local_selections = true;
17758
17759 if moving_cursor {
17760 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17761 editor
17762 .selections
17763 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17764 .head()
17765 });
17766
17767 // Update the selection to match the position of the selection inside
17768 // the rename editor.
17769 let snapshot = self.buffer.read(cx).read(cx);
17770 let rename_range = rename.range.to_offset(&snapshot);
17771 let cursor_in_editor = snapshot
17772 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17773 .min(rename_range.end);
17774 drop(snapshot);
17775
17776 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17777 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17778 });
17779 } else {
17780 self.refresh_document_highlights(cx);
17781 }
17782
17783 Some(rename)
17784 }
17785
17786 pub fn pending_rename(&self) -> Option<&RenameState> {
17787 self.pending_rename.as_ref()
17788 }
17789
17790 fn format(
17791 &mut self,
17792 _: &Format,
17793 window: &mut Window,
17794 cx: &mut Context<Self>,
17795 ) -> Option<Task<Result<()>>> {
17796 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17797
17798 let project = match &self.project {
17799 Some(project) => project.clone(),
17800 None => return None,
17801 };
17802
17803 Some(self.perform_format(
17804 project,
17805 FormatTrigger::Manual,
17806 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17807 window,
17808 cx,
17809 ))
17810 }
17811
17812 fn format_selections(
17813 &mut self,
17814 _: &FormatSelections,
17815 window: &mut Window,
17816 cx: &mut Context<Self>,
17817 ) -> Option<Task<Result<()>>> {
17818 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17819
17820 let project = match &self.project {
17821 Some(project) => project.clone(),
17822 None => return None,
17823 };
17824
17825 let ranges = self
17826 .selections
17827 .all_adjusted(&self.display_snapshot(cx))
17828 .into_iter()
17829 .map(|selection| selection.range())
17830 .collect_vec();
17831
17832 Some(self.perform_format(
17833 project,
17834 FormatTrigger::Manual,
17835 FormatTarget::Ranges(ranges),
17836 window,
17837 cx,
17838 ))
17839 }
17840
17841 fn perform_format(
17842 &mut self,
17843 project: Entity<Project>,
17844 trigger: FormatTrigger,
17845 target: FormatTarget,
17846 window: &mut Window,
17847 cx: &mut Context<Self>,
17848 ) -> Task<Result<()>> {
17849 let buffer = self.buffer.clone();
17850 let (buffers, target) = match target {
17851 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17852 FormatTarget::Ranges(selection_ranges) => {
17853 let multi_buffer = buffer.read(cx);
17854 let snapshot = multi_buffer.read(cx);
17855 let mut buffers = HashSet::default();
17856 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17857 BTreeMap::new();
17858 for selection_range in selection_ranges {
17859 for (buffer, buffer_range, _) in
17860 snapshot.range_to_buffer_ranges(selection_range)
17861 {
17862 let buffer_id = buffer.remote_id();
17863 let start = buffer.anchor_before(buffer_range.start);
17864 let end = buffer.anchor_after(buffer_range.end);
17865 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17866 buffer_id_to_ranges
17867 .entry(buffer_id)
17868 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17869 .or_insert_with(|| vec![start..end]);
17870 }
17871 }
17872 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17873 }
17874 };
17875
17876 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17877 let selections_prev = transaction_id_prev
17878 .and_then(|transaction_id_prev| {
17879 // default to selections as they were after the last edit, if we have them,
17880 // instead of how they are now.
17881 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17882 // will take you back to where you made the last edit, instead of staying where you scrolled
17883 self.selection_history
17884 .transaction(transaction_id_prev)
17885 .map(|t| t.0.clone())
17886 })
17887 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17888
17889 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17890 let format = project.update(cx, |project, cx| {
17891 project.format(buffers, target, true, trigger, cx)
17892 });
17893
17894 cx.spawn_in(window, async move |editor, cx| {
17895 let transaction = futures::select_biased! {
17896 transaction = format.log_err().fuse() => transaction,
17897 () = timeout => {
17898 log::warn!("timed out waiting for formatting");
17899 None
17900 }
17901 };
17902
17903 buffer
17904 .update(cx, |buffer, cx| {
17905 if let Some(transaction) = transaction
17906 && !buffer.is_singleton()
17907 {
17908 buffer.push_transaction(&transaction.0, cx);
17909 }
17910 cx.notify();
17911 })
17912 .ok();
17913
17914 if let Some(transaction_id_now) =
17915 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17916 {
17917 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17918 if has_new_transaction {
17919 _ = editor.update(cx, |editor, _| {
17920 editor
17921 .selection_history
17922 .insert_transaction(transaction_id_now, selections_prev);
17923 });
17924 }
17925 }
17926
17927 Ok(())
17928 })
17929 }
17930
17931 fn organize_imports(
17932 &mut self,
17933 _: &OrganizeImports,
17934 window: &mut Window,
17935 cx: &mut Context<Self>,
17936 ) -> Option<Task<Result<()>>> {
17937 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17938 let project = match &self.project {
17939 Some(project) => project.clone(),
17940 None => return None,
17941 };
17942 Some(self.perform_code_action_kind(
17943 project,
17944 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17945 window,
17946 cx,
17947 ))
17948 }
17949
17950 fn perform_code_action_kind(
17951 &mut self,
17952 project: Entity<Project>,
17953 kind: CodeActionKind,
17954 window: &mut Window,
17955 cx: &mut Context<Self>,
17956 ) -> Task<Result<()>> {
17957 let buffer = self.buffer.clone();
17958 let buffers = buffer.read(cx).all_buffers();
17959 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17960 let apply_action = project.update(cx, |project, cx| {
17961 project.apply_code_action_kind(buffers, kind, true, cx)
17962 });
17963 cx.spawn_in(window, async move |_, cx| {
17964 let transaction = futures::select_biased! {
17965 () = timeout => {
17966 log::warn!("timed out waiting for executing code action");
17967 None
17968 }
17969 transaction = apply_action.log_err().fuse() => transaction,
17970 };
17971 buffer
17972 .update(cx, |buffer, cx| {
17973 // check if we need this
17974 if let Some(transaction) = transaction
17975 && !buffer.is_singleton()
17976 {
17977 buffer.push_transaction(&transaction.0, cx);
17978 }
17979 cx.notify();
17980 })
17981 .ok();
17982 Ok(())
17983 })
17984 }
17985
17986 pub fn restart_language_server(
17987 &mut self,
17988 _: &RestartLanguageServer,
17989 _: &mut Window,
17990 cx: &mut Context<Self>,
17991 ) {
17992 if let Some(project) = self.project.clone() {
17993 self.buffer.update(cx, |multi_buffer, cx| {
17994 project.update(cx, |project, cx| {
17995 project.restart_language_servers_for_buffers(
17996 multi_buffer.all_buffers().into_iter().collect(),
17997 HashSet::default(),
17998 cx,
17999 );
18000 });
18001 })
18002 }
18003 }
18004
18005 pub fn stop_language_server(
18006 &mut self,
18007 _: &StopLanguageServer,
18008 _: &mut Window,
18009 cx: &mut Context<Self>,
18010 ) {
18011 if let Some(project) = self.project.clone() {
18012 self.buffer.update(cx, |multi_buffer, cx| {
18013 project.update(cx, |project, cx| {
18014 project.stop_language_servers_for_buffers(
18015 multi_buffer.all_buffers().into_iter().collect(),
18016 HashSet::default(),
18017 cx,
18018 );
18019 });
18020 });
18021 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18022 }
18023 }
18024
18025 fn cancel_language_server_work(
18026 workspace: &mut Workspace,
18027 _: &actions::CancelLanguageServerWork,
18028 _: &mut Window,
18029 cx: &mut Context<Workspace>,
18030 ) {
18031 let project = workspace.project();
18032 let buffers = workspace
18033 .active_item(cx)
18034 .and_then(|item| item.act_as::<Editor>(cx))
18035 .map_or(HashSet::default(), |editor| {
18036 editor.read(cx).buffer.read(cx).all_buffers()
18037 });
18038 project.update(cx, |project, cx| {
18039 project.cancel_language_server_work_for_buffers(buffers, cx);
18040 });
18041 }
18042
18043 fn show_character_palette(
18044 &mut self,
18045 _: &ShowCharacterPalette,
18046 window: &mut Window,
18047 _: &mut Context<Self>,
18048 ) {
18049 window.show_character_palette();
18050 }
18051
18052 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18053 if !self.diagnostics_enabled() {
18054 return;
18055 }
18056
18057 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18058 let buffer = self.buffer.read(cx).snapshot(cx);
18059 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18060 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18061 let is_valid = buffer
18062 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18063 .any(|entry| {
18064 entry.diagnostic.is_primary
18065 && !entry.range.is_empty()
18066 && entry.range.start == primary_range_start
18067 && entry.diagnostic.message == active_diagnostics.active_message
18068 });
18069
18070 if !is_valid {
18071 self.dismiss_diagnostics(cx);
18072 }
18073 }
18074 }
18075
18076 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18077 match &self.active_diagnostics {
18078 ActiveDiagnostic::Group(group) => Some(group),
18079 _ => None,
18080 }
18081 }
18082
18083 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18084 if !self.diagnostics_enabled() {
18085 return;
18086 }
18087 self.dismiss_diagnostics(cx);
18088 self.active_diagnostics = ActiveDiagnostic::All;
18089 }
18090
18091 fn activate_diagnostics(
18092 &mut self,
18093 buffer_id: BufferId,
18094 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18095 window: &mut Window,
18096 cx: &mut Context<Self>,
18097 ) {
18098 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18099 return;
18100 }
18101 self.dismiss_diagnostics(cx);
18102 let snapshot = self.snapshot(window, cx);
18103 let buffer = self.buffer.read(cx).snapshot(cx);
18104 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18105 return;
18106 };
18107
18108 let diagnostic_group = buffer
18109 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18110 .collect::<Vec<_>>();
18111
18112 let language_registry = self
18113 .project()
18114 .map(|project| project.read(cx).languages().clone());
18115
18116 let blocks = renderer.render_group(
18117 diagnostic_group,
18118 buffer_id,
18119 snapshot,
18120 cx.weak_entity(),
18121 language_registry,
18122 cx,
18123 );
18124
18125 let blocks = self.display_map.update(cx, |display_map, cx| {
18126 display_map.insert_blocks(blocks, cx).into_iter().collect()
18127 });
18128 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18129 active_range: buffer.anchor_before(diagnostic.range.start)
18130 ..buffer.anchor_after(diagnostic.range.end),
18131 active_message: diagnostic.diagnostic.message.clone(),
18132 group_id: diagnostic.diagnostic.group_id,
18133 blocks,
18134 });
18135 cx.notify();
18136 }
18137
18138 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18139 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18140 return;
18141 };
18142
18143 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18144 if let ActiveDiagnostic::Group(group) = prev {
18145 self.display_map.update(cx, |display_map, cx| {
18146 display_map.remove_blocks(group.blocks, cx);
18147 });
18148 cx.notify();
18149 }
18150 }
18151
18152 /// Disable inline diagnostics rendering for this editor.
18153 pub fn disable_inline_diagnostics(&mut self) {
18154 self.inline_diagnostics_enabled = false;
18155 self.inline_diagnostics_update = Task::ready(());
18156 self.inline_diagnostics.clear();
18157 }
18158
18159 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18160 self.diagnostics_enabled = false;
18161 self.dismiss_diagnostics(cx);
18162 self.inline_diagnostics_update = Task::ready(());
18163 self.inline_diagnostics.clear();
18164 }
18165
18166 pub fn disable_word_completions(&mut self) {
18167 self.word_completions_enabled = false;
18168 }
18169
18170 pub fn diagnostics_enabled(&self) -> bool {
18171 self.diagnostics_enabled && self.mode.is_full()
18172 }
18173
18174 pub fn inline_diagnostics_enabled(&self) -> bool {
18175 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18176 }
18177
18178 pub fn show_inline_diagnostics(&self) -> bool {
18179 self.show_inline_diagnostics
18180 }
18181
18182 pub fn toggle_inline_diagnostics(
18183 &mut self,
18184 _: &ToggleInlineDiagnostics,
18185 window: &mut Window,
18186 cx: &mut Context<Editor>,
18187 ) {
18188 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18189 self.refresh_inline_diagnostics(false, window, cx);
18190 }
18191
18192 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18193 self.diagnostics_max_severity = severity;
18194 self.display_map.update(cx, |display_map, _| {
18195 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18196 });
18197 }
18198
18199 pub fn toggle_diagnostics(
18200 &mut self,
18201 _: &ToggleDiagnostics,
18202 window: &mut Window,
18203 cx: &mut Context<Editor>,
18204 ) {
18205 if !self.diagnostics_enabled() {
18206 return;
18207 }
18208
18209 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18210 EditorSettings::get_global(cx)
18211 .diagnostics_max_severity
18212 .filter(|severity| severity != &DiagnosticSeverity::Off)
18213 .unwrap_or(DiagnosticSeverity::Hint)
18214 } else {
18215 DiagnosticSeverity::Off
18216 };
18217 self.set_max_diagnostics_severity(new_severity, cx);
18218 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18219 self.active_diagnostics = ActiveDiagnostic::None;
18220 self.inline_diagnostics_update = Task::ready(());
18221 self.inline_diagnostics.clear();
18222 } else {
18223 self.refresh_inline_diagnostics(false, window, cx);
18224 }
18225
18226 cx.notify();
18227 }
18228
18229 pub fn toggle_minimap(
18230 &mut self,
18231 _: &ToggleMinimap,
18232 window: &mut Window,
18233 cx: &mut Context<Editor>,
18234 ) {
18235 if self.supports_minimap(cx) {
18236 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18237 }
18238 }
18239
18240 fn refresh_inline_diagnostics(
18241 &mut self,
18242 debounce: bool,
18243 window: &mut Window,
18244 cx: &mut Context<Self>,
18245 ) {
18246 let max_severity = ProjectSettings::get_global(cx)
18247 .diagnostics
18248 .inline
18249 .max_severity
18250 .unwrap_or(self.diagnostics_max_severity);
18251
18252 if !self.inline_diagnostics_enabled()
18253 || !self.diagnostics_enabled()
18254 || !self.show_inline_diagnostics
18255 || max_severity == DiagnosticSeverity::Off
18256 {
18257 self.inline_diagnostics_update = Task::ready(());
18258 self.inline_diagnostics.clear();
18259 return;
18260 }
18261
18262 let debounce_ms = ProjectSettings::get_global(cx)
18263 .diagnostics
18264 .inline
18265 .update_debounce_ms;
18266 let debounce = if debounce && debounce_ms > 0 {
18267 Some(Duration::from_millis(debounce_ms))
18268 } else {
18269 None
18270 };
18271 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18272 if let Some(debounce) = debounce {
18273 cx.background_executor().timer(debounce).await;
18274 }
18275 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18276 editor
18277 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18278 .ok()
18279 }) else {
18280 return;
18281 };
18282
18283 let new_inline_diagnostics = cx
18284 .background_spawn(async move {
18285 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18286 for diagnostic_entry in
18287 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18288 {
18289 let message = diagnostic_entry
18290 .diagnostic
18291 .message
18292 .split_once('\n')
18293 .map(|(line, _)| line)
18294 .map(SharedString::new)
18295 .unwrap_or_else(|| {
18296 SharedString::new(&*diagnostic_entry.diagnostic.message)
18297 });
18298 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18299 let (Ok(i) | Err(i)) = inline_diagnostics
18300 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18301 inline_diagnostics.insert(
18302 i,
18303 (
18304 start_anchor,
18305 InlineDiagnostic {
18306 message,
18307 group_id: diagnostic_entry.diagnostic.group_id,
18308 start: diagnostic_entry.range.start.to_point(&snapshot),
18309 is_primary: diagnostic_entry.diagnostic.is_primary,
18310 severity: diagnostic_entry.diagnostic.severity,
18311 },
18312 ),
18313 );
18314 }
18315 inline_diagnostics
18316 })
18317 .await;
18318
18319 editor
18320 .update(cx, |editor, cx| {
18321 editor.inline_diagnostics = new_inline_diagnostics;
18322 cx.notify();
18323 })
18324 .ok();
18325 });
18326 }
18327
18328 fn pull_diagnostics(
18329 &mut self,
18330 buffer_id: Option<BufferId>,
18331 window: &Window,
18332 cx: &mut Context<Self>,
18333 ) -> Option<()> {
18334 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18335 return None;
18336 }
18337 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18338 .diagnostics
18339 .lsp_pull_diagnostics;
18340 if !pull_diagnostics_settings.enabled {
18341 return None;
18342 }
18343 let project = self.project()?.downgrade();
18344 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18345 let mut buffers = self.buffer.read(cx).all_buffers();
18346 buffers.retain(|buffer| {
18347 let buffer_id_to_retain = buffer.read(cx).remote_id();
18348 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18349 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18350 });
18351 if buffers.is_empty() {
18352 self.pull_diagnostics_task = Task::ready(());
18353 return None;
18354 }
18355
18356 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18357 cx.background_executor().timer(debounce).await;
18358
18359 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18360 buffers
18361 .into_iter()
18362 .filter_map(|buffer| {
18363 project
18364 .update(cx, |project, cx| {
18365 project.lsp_store().update(cx, |lsp_store, cx| {
18366 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18367 })
18368 })
18369 .ok()
18370 })
18371 .collect::<FuturesUnordered<_>>()
18372 }) else {
18373 return;
18374 };
18375
18376 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18377 match pull_task {
18378 Ok(()) => {
18379 if editor
18380 .update_in(cx, |editor, window, cx| {
18381 editor.update_diagnostics_state(window, cx);
18382 })
18383 .is_err()
18384 {
18385 return;
18386 }
18387 }
18388 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18389 }
18390 }
18391 });
18392
18393 Some(())
18394 }
18395
18396 pub fn set_selections_from_remote(
18397 &mut self,
18398 selections: Vec<Selection<Anchor>>,
18399 pending_selection: Option<Selection<Anchor>>,
18400 window: &mut Window,
18401 cx: &mut Context<Self>,
18402 ) {
18403 let old_cursor_position = self.selections.newest_anchor().head();
18404 self.selections
18405 .change_with(&self.display_snapshot(cx), |s| {
18406 s.select_anchors(selections);
18407 if let Some(pending_selection) = pending_selection {
18408 s.set_pending(pending_selection, SelectMode::Character);
18409 } else {
18410 s.clear_pending();
18411 }
18412 });
18413 self.selections_did_change(
18414 false,
18415 &old_cursor_position,
18416 SelectionEffects::default(),
18417 window,
18418 cx,
18419 );
18420 }
18421
18422 pub fn transact(
18423 &mut self,
18424 window: &mut Window,
18425 cx: &mut Context<Self>,
18426 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18427 ) -> Option<TransactionId> {
18428 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18429 this.start_transaction_at(Instant::now(), window, cx);
18430 update(this, window, cx);
18431 this.end_transaction_at(Instant::now(), cx)
18432 })
18433 }
18434
18435 pub fn start_transaction_at(
18436 &mut self,
18437 now: Instant,
18438 window: &mut Window,
18439 cx: &mut Context<Self>,
18440 ) -> Option<TransactionId> {
18441 self.end_selection(window, cx);
18442 if let Some(tx_id) = self
18443 .buffer
18444 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18445 {
18446 self.selection_history
18447 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18448 cx.emit(EditorEvent::TransactionBegun {
18449 transaction_id: tx_id,
18450 });
18451 Some(tx_id)
18452 } else {
18453 None
18454 }
18455 }
18456
18457 pub fn end_transaction_at(
18458 &mut self,
18459 now: Instant,
18460 cx: &mut Context<Self>,
18461 ) -> Option<TransactionId> {
18462 if let Some(transaction_id) = self
18463 .buffer
18464 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18465 {
18466 if let Some((_, end_selections)) =
18467 self.selection_history.transaction_mut(transaction_id)
18468 {
18469 *end_selections = Some(self.selections.disjoint_anchors_arc());
18470 } else {
18471 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18472 }
18473
18474 cx.emit(EditorEvent::Edited { transaction_id });
18475 Some(transaction_id)
18476 } else {
18477 None
18478 }
18479 }
18480
18481 pub fn modify_transaction_selection_history(
18482 &mut self,
18483 transaction_id: TransactionId,
18484 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18485 ) -> bool {
18486 self.selection_history
18487 .transaction_mut(transaction_id)
18488 .map(modify)
18489 .is_some()
18490 }
18491
18492 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18493 if self.selection_mark_mode {
18494 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18495 s.move_with(|_, sel| {
18496 sel.collapse_to(sel.head(), SelectionGoal::None);
18497 });
18498 })
18499 }
18500 self.selection_mark_mode = true;
18501 cx.notify();
18502 }
18503
18504 pub fn swap_selection_ends(
18505 &mut self,
18506 _: &actions::SwapSelectionEnds,
18507 window: &mut Window,
18508 cx: &mut Context<Self>,
18509 ) {
18510 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18511 s.move_with(|_, sel| {
18512 if sel.start != sel.end {
18513 sel.reversed = !sel.reversed
18514 }
18515 });
18516 });
18517 self.request_autoscroll(Autoscroll::newest(), cx);
18518 cx.notify();
18519 }
18520
18521 pub fn toggle_focus(
18522 workspace: &mut Workspace,
18523 _: &actions::ToggleFocus,
18524 window: &mut Window,
18525 cx: &mut Context<Workspace>,
18526 ) {
18527 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18528 return;
18529 };
18530 workspace.activate_item(&item, true, true, window, cx);
18531 }
18532
18533 pub fn toggle_fold(
18534 &mut self,
18535 _: &actions::ToggleFold,
18536 window: &mut Window,
18537 cx: &mut Context<Self>,
18538 ) {
18539 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18540 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18541 let selection = self.selections.newest::<Point>(&display_map);
18542
18543 let range = if selection.is_empty() {
18544 let point = selection.head().to_display_point(&display_map);
18545 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18546 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18547 .to_point(&display_map);
18548 start..end
18549 } else {
18550 selection.range()
18551 };
18552 if display_map.folds_in_range(range).next().is_some() {
18553 self.unfold_lines(&Default::default(), window, cx)
18554 } else {
18555 self.fold(&Default::default(), window, cx)
18556 }
18557 } else {
18558 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18559 let buffer_ids: HashSet<_> = self
18560 .selections
18561 .disjoint_anchor_ranges()
18562 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18563 .collect();
18564
18565 let should_unfold = buffer_ids
18566 .iter()
18567 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18568
18569 for buffer_id in buffer_ids {
18570 if should_unfold {
18571 self.unfold_buffer(buffer_id, cx);
18572 } else {
18573 self.fold_buffer(buffer_id, cx);
18574 }
18575 }
18576 }
18577 }
18578
18579 pub fn toggle_fold_recursive(
18580 &mut self,
18581 _: &actions::ToggleFoldRecursive,
18582 window: &mut Window,
18583 cx: &mut Context<Self>,
18584 ) {
18585 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18586
18587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18588 let range = if selection.is_empty() {
18589 let point = selection.head().to_display_point(&display_map);
18590 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18591 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18592 .to_point(&display_map);
18593 start..end
18594 } else {
18595 selection.range()
18596 };
18597 if display_map.folds_in_range(range).next().is_some() {
18598 self.unfold_recursive(&Default::default(), window, cx)
18599 } else {
18600 self.fold_recursive(&Default::default(), window, cx)
18601 }
18602 }
18603
18604 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18605 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18606 let mut to_fold = Vec::new();
18607 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18608 let selections = self.selections.all_adjusted(&display_map);
18609
18610 for selection in selections {
18611 let range = selection.range().sorted();
18612 let buffer_start_row = range.start.row;
18613
18614 if range.start.row != range.end.row {
18615 let mut found = false;
18616 let mut row = range.start.row;
18617 while row <= range.end.row {
18618 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18619 {
18620 found = true;
18621 row = crease.range().end.row + 1;
18622 to_fold.push(crease);
18623 } else {
18624 row += 1
18625 }
18626 }
18627 if found {
18628 continue;
18629 }
18630 }
18631
18632 for row in (0..=range.start.row).rev() {
18633 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18634 && crease.range().end.row >= buffer_start_row
18635 {
18636 to_fold.push(crease);
18637 if row <= range.start.row {
18638 break;
18639 }
18640 }
18641 }
18642 }
18643
18644 self.fold_creases(to_fold, true, window, cx);
18645 } else {
18646 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18647 let buffer_ids = self
18648 .selections
18649 .disjoint_anchor_ranges()
18650 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18651 .collect::<HashSet<_>>();
18652 for buffer_id in buffer_ids {
18653 self.fold_buffer(buffer_id, cx);
18654 }
18655 }
18656 }
18657
18658 pub fn toggle_fold_all(
18659 &mut self,
18660 _: &actions::ToggleFoldAll,
18661 window: &mut Window,
18662 cx: &mut Context<Self>,
18663 ) {
18664 if self.buffer.read(cx).is_singleton() {
18665 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18666 let has_folds = display_map
18667 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18668 .next()
18669 .is_some();
18670
18671 if has_folds {
18672 self.unfold_all(&actions::UnfoldAll, window, cx);
18673 } else {
18674 self.fold_all(&actions::FoldAll, window, cx);
18675 }
18676 } else {
18677 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18678 let should_unfold = buffer_ids
18679 .iter()
18680 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18681
18682 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18683 editor
18684 .update_in(cx, |editor, _, cx| {
18685 for buffer_id in buffer_ids {
18686 if should_unfold {
18687 editor.unfold_buffer(buffer_id, cx);
18688 } else {
18689 editor.fold_buffer(buffer_id, cx);
18690 }
18691 }
18692 })
18693 .ok();
18694 });
18695 }
18696 }
18697
18698 fn fold_at_level(
18699 &mut self,
18700 fold_at: &FoldAtLevel,
18701 window: &mut Window,
18702 cx: &mut Context<Self>,
18703 ) {
18704 if !self.buffer.read(cx).is_singleton() {
18705 return;
18706 }
18707
18708 let fold_at_level = fold_at.0;
18709 let snapshot = self.buffer.read(cx).snapshot(cx);
18710 let mut to_fold = Vec::new();
18711 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18712
18713 let row_ranges_to_keep: Vec<Range<u32>> = self
18714 .selections
18715 .all::<Point>(&self.display_snapshot(cx))
18716 .into_iter()
18717 .map(|sel| sel.start.row..sel.end.row)
18718 .collect();
18719
18720 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18721 while start_row < end_row {
18722 match self
18723 .snapshot(window, cx)
18724 .crease_for_buffer_row(MultiBufferRow(start_row))
18725 {
18726 Some(crease) => {
18727 let nested_start_row = crease.range().start.row + 1;
18728 let nested_end_row = crease.range().end.row;
18729
18730 if current_level < fold_at_level {
18731 stack.push((nested_start_row, nested_end_row, current_level + 1));
18732 } else if current_level == fold_at_level {
18733 // Fold iff there is no selection completely contained within the fold region
18734 if !row_ranges_to_keep.iter().any(|selection| {
18735 selection.end >= nested_start_row
18736 && selection.start <= nested_end_row
18737 }) {
18738 to_fold.push(crease);
18739 }
18740 }
18741
18742 start_row = nested_end_row + 1;
18743 }
18744 None => start_row += 1,
18745 }
18746 }
18747 }
18748
18749 self.fold_creases(to_fold, true, window, cx);
18750 }
18751
18752 pub fn fold_at_level_1(
18753 &mut self,
18754 _: &actions::FoldAtLevel1,
18755 window: &mut Window,
18756 cx: &mut Context<Self>,
18757 ) {
18758 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18759 }
18760
18761 pub fn fold_at_level_2(
18762 &mut self,
18763 _: &actions::FoldAtLevel2,
18764 window: &mut Window,
18765 cx: &mut Context<Self>,
18766 ) {
18767 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18768 }
18769
18770 pub fn fold_at_level_3(
18771 &mut self,
18772 _: &actions::FoldAtLevel3,
18773 window: &mut Window,
18774 cx: &mut Context<Self>,
18775 ) {
18776 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18777 }
18778
18779 pub fn fold_at_level_4(
18780 &mut self,
18781 _: &actions::FoldAtLevel4,
18782 window: &mut Window,
18783 cx: &mut Context<Self>,
18784 ) {
18785 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18786 }
18787
18788 pub fn fold_at_level_5(
18789 &mut self,
18790 _: &actions::FoldAtLevel5,
18791 window: &mut Window,
18792 cx: &mut Context<Self>,
18793 ) {
18794 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18795 }
18796
18797 pub fn fold_at_level_6(
18798 &mut self,
18799 _: &actions::FoldAtLevel6,
18800 window: &mut Window,
18801 cx: &mut Context<Self>,
18802 ) {
18803 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18804 }
18805
18806 pub fn fold_at_level_7(
18807 &mut self,
18808 _: &actions::FoldAtLevel7,
18809 window: &mut Window,
18810 cx: &mut Context<Self>,
18811 ) {
18812 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18813 }
18814
18815 pub fn fold_at_level_8(
18816 &mut self,
18817 _: &actions::FoldAtLevel8,
18818 window: &mut Window,
18819 cx: &mut Context<Self>,
18820 ) {
18821 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18822 }
18823
18824 pub fn fold_at_level_9(
18825 &mut self,
18826 _: &actions::FoldAtLevel9,
18827 window: &mut Window,
18828 cx: &mut Context<Self>,
18829 ) {
18830 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18831 }
18832
18833 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18834 if self.buffer.read(cx).is_singleton() {
18835 let mut fold_ranges = Vec::new();
18836 let snapshot = self.buffer.read(cx).snapshot(cx);
18837
18838 for row in 0..snapshot.max_row().0 {
18839 if let Some(foldable_range) = self
18840 .snapshot(window, cx)
18841 .crease_for_buffer_row(MultiBufferRow(row))
18842 {
18843 fold_ranges.push(foldable_range);
18844 }
18845 }
18846
18847 self.fold_creases(fold_ranges, true, window, cx);
18848 } else {
18849 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18850 editor
18851 .update_in(cx, |editor, _, cx| {
18852 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18853 editor.fold_buffer(buffer_id, cx);
18854 }
18855 })
18856 .ok();
18857 });
18858 }
18859 }
18860
18861 pub fn fold_function_bodies(
18862 &mut self,
18863 _: &actions::FoldFunctionBodies,
18864 window: &mut Window,
18865 cx: &mut Context<Self>,
18866 ) {
18867 let snapshot = self.buffer.read(cx).snapshot(cx);
18868
18869 let ranges = snapshot
18870 .text_object_ranges(
18871 MultiBufferOffset(0)..snapshot.len(),
18872 TreeSitterOptions::default(),
18873 )
18874 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18875 .collect::<Vec<_>>();
18876
18877 let creases = ranges
18878 .into_iter()
18879 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18880 .collect();
18881
18882 self.fold_creases(creases, true, window, cx);
18883 }
18884
18885 pub fn fold_recursive(
18886 &mut self,
18887 _: &actions::FoldRecursive,
18888 window: &mut Window,
18889 cx: &mut Context<Self>,
18890 ) {
18891 let mut to_fold = Vec::new();
18892 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18893 let selections = self.selections.all_adjusted(&display_map);
18894
18895 for selection in selections {
18896 let range = selection.range().sorted();
18897 let buffer_start_row = range.start.row;
18898
18899 if range.start.row != range.end.row {
18900 let mut found = false;
18901 for row in range.start.row..=range.end.row {
18902 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18903 found = true;
18904 to_fold.push(crease);
18905 }
18906 }
18907 if found {
18908 continue;
18909 }
18910 }
18911
18912 for row in (0..=range.start.row).rev() {
18913 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18914 if crease.range().end.row >= buffer_start_row {
18915 to_fold.push(crease);
18916 } else {
18917 break;
18918 }
18919 }
18920 }
18921 }
18922
18923 self.fold_creases(to_fold, true, window, cx);
18924 }
18925
18926 pub fn fold_at(
18927 &mut self,
18928 buffer_row: MultiBufferRow,
18929 window: &mut Window,
18930 cx: &mut Context<Self>,
18931 ) {
18932 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18933
18934 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18935 let autoscroll = self
18936 .selections
18937 .all::<Point>(&display_map)
18938 .iter()
18939 .any(|selection| crease.range().overlaps(&selection.range()));
18940
18941 self.fold_creases(vec![crease], autoscroll, window, cx);
18942 }
18943 }
18944
18945 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18946 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18947 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18948 let buffer = display_map.buffer_snapshot();
18949 let selections = self.selections.all::<Point>(&display_map);
18950 let ranges = selections
18951 .iter()
18952 .map(|s| {
18953 let range = s.display_range(&display_map).sorted();
18954 let mut start = range.start.to_point(&display_map);
18955 let mut end = range.end.to_point(&display_map);
18956 start.column = 0;
18957 end.column = buffer.line_len(MultiBufferRow(end.row));
18958 start..end
18959 })
18960 .collect::<Vec<_>>();
18961
18962 self.unfold_ranges(&ranges, true, true, cx);
18963 } else {
18964 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18965 let buffer_ids = self
18966 .selections
18967 .disjoint_anchor_ranges()
18968 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18969 .collect::<HashSet<_>>();
18970 for buffer_id in buffer_ids {
18971 self.unfold_buffer(buffer_id, cx);
18972 }
18973 }
18974 }
18975
18976 pub fn unfold_recursive(
18977 &mut self,
18978 _: &UnfoldRecursive,
18979 _window: &mut Window,
18980 cx: &mut Context<Self>,
18981 ) {
18982 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18983 let selections = self.selections.all::<Point>(&display_map);
18984 let ranges = selections
18985 .iter()
18986 .map(|s| {
18987 let mut range = s.display_range(&display_map).sorted();
18988 *range.start.column_mut() = 0;
18989 *range.end.column_mut() = display_map.line_len(range.end.row());
18990 let start = range.start.to_point(&display_map);
18991 let end = range.end.to_point(&display_map);
18992 start..end
18993 })
18994 .collect::<Vec<_>>();
18995
18996 self.unfold_ranges(&ranges, true, true, cx);
18997 }
18998
18999 pub fn unfold_at(
19000 &mut self,
19001 buffer_row: MultiBufferRow,
19002 _window: &mut Window,
19003 cx: &mut Context<Self>,
19004 ) {
19005 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19006
19007 let intersection_range = Point::new(buffer_row.0, 0)
19008 ..Point::new(
19009 buffer_row.0,
19010 display_map.buffer_snapshot().line_len(buffer_row),
19011 );
19012
19013 let autoscroll = self
19014 .selections
19015 .all::<Point>(&display_map)
19016 .iter()
19017 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19018
19019 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19020 }
19021
19022 pub fn unfold_all(
19023 &mut self,
19024 _: &actions::UnfoldAll,
19025 _window: &mut Window,
19026 cx: &mut Context<Self>,
19027 ) {
19028 if self.buffer.read(cx).is_singleton() {
19029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19030 self.unfold_ranges(
19031 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19032 true,
19033 true,
19034 cx,
19035 );
19036 } else {
19037 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19038 editor
19039 .update(cx, |editor, cx| {
19040 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19041 editor.unfold_buffer(buffer_id, cx);
19042 }
19043 })
19044 .ok();
19045 });
19046 }
19047 }
19048
19049 pub fn fold_selected_ranges(
19050 &mut self,
19051 _: &FoldSelectedRanges,
19052 window: &mut Window,
19053 cx: &mut Context<Self>,
19054 ) {
19055 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19056 let selections = self.selections.all_adjusted(&display_map);
19057 let ranges = selections
19058 .into_iter()
19059 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19060 .collect::<Vec<_>>();
19061 self.fold_creases(ranges, true, window, cx);
19062 }
19063
19064 pub fn fold_ranges<T: ToOffset + Clone>(
19065 &mut self,
19066 ranges: Vec<Range<T>>,
19067 auto_scroll: bool,
19068 window: &mut Window,
19069 cx: &mut Context<Self>,
19070 ) {
19071 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19072 let ranges = ranges
19073 .into_iter()
19074 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19075 .collect::<Vec<_>>();
19076 self.fold_creases(ranges, auto_scroll, window, cx);
19077 }
19078
19079 pub fn fold_creases<T: ToOffset + Clone>(
19080 &mut self,
19081 creases: Vec<Crease<T>>,
19082 auto_scroll: bool,
19083 _window: &mut Window,
19084 cx: &mut Context<Self>,
19085 ) {
19086 if creases.is_empty() {
19087 return;
19088 }
19089
19090 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19091
19092 if auto_scroll {
19093 self.request_autoscroll(Autoscroll::fit(), cx);
19094 }
19095
19096 cx.notify();
19097
19098 self.scrollbar_marker_state.dirty = true;
19099 self.folds_did_change(cx);
19100 }
19101
19102 /// Removes any folds whose ranges intersect any of the given ranges.
19103 pub fn unfold_ranges<T: ToOffset + Clone>(
19104 &mut self,
19105 ranges: &[Range<T>],
19106 inclusive: bool,
19107 auto_scroll: bool,
19108 cx: &mut Context<Self>,
19109 ) {
19110 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19111 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19112 });
19113 self.folds_did_change(cx);
19114 }
19115
19116 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19117 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19118 return;
19119 }
19120
19121 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19122 self.display_map.update(cx, |display_map, cx| {
19123 display_map.fold_buffers([buffer_id], cx)
19124 });
19125
19126 let snapshot = self.display_snapshot(cx);
19127 self.selections.change_with(&snapshot, |selections| {
19128 selections.remove_selections_from_buffer(buffer_id);
19129 });
19130
19131 cx.emit(EditorEvent::BufferFoldToggled {
19132 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19133 folded: true,
19134 });
19135 cx.notify();
19136 }
19137
19138 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19139 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19140 return;
19141 }
19142 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19143 self.display_map.update(cx, |display_map, cx| {
19144 display_map.unfold_buffers([buffer_id], cx);
19145 });
19146 cx.emit(EditorEvent::BufferFoldToggled {
19147 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19148 folded: false,
19149 });
19150 cx.notify();
19151 }
19152
19153 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19154 self.display_map.read(cx).is_buffer_folded(buffer)
19155 }
19156
19157 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19158 self.display_map.read(cx).folded_buffers()
19159 }
19160
19161 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19162 self.display_map.update(cx, |display_map, cx| {
19163 display_map.disable_header_for_buffer(buffer_id, cx);
19164 });
19165 cx.notify();
19166 }
19167
19168 /// Removes any folds with the given ranges.
19169 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19170 &mut self,
19171 ranges: &[Range<T>],
19172 type_id: TypeId,
19173 auto_scroll: bool,
19174 cx: &mut Context<Self>,
19175 ) {
19176 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19177 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19178 });
19179 self.folds_did_change(cx);
19180 }
19181
19182 fn remove_folds_with<T: ToOffset + Clone>(
19183 &mut self,
19184 ranges: &[Range<T>],
19185 auto_scroll: bool,
19186 cx: &mut Context<Self>,
19187 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19188 ) {
19189 if ranges.is_empty() {
19190 return;
19191 }
19192
19193 let mut buffers_affected = HashSet::default();
19194 let multi_buffer = self.buffer().read(cx);
19195 for range in ranges {
19196 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19197 buffers_affected.insert(buffer.read(cx).remote_id());
19198 };
19199 }
19200
19201 self.display_map.update(cx, update);
19202
19203 if auto_scroll {
19204 self.request_autoscroll(Autoscroll::fit(), cx);
19205 }
19206
19207 cx.notify();
19208 self.scrollbar_marker_state.dirty = true;
19209 self.active_indent_guides_state.dirty = true;
19210 }
19211
19212 pub fn update_renderer_widths(
19213 &mut self,
19214 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19215 cx: &mut Context<Self>,
19216 ) -> bool {
19217 self.display_map
19218 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19219 }
19220
19221 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19222 self.display_map.read(cx).fold_placeholder.clone()
19223 }
19224
19225 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19226 self.use_base_text_line_numbers = show;
19227 }
19228
19229 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19230 self.buffer.update(cx, |buffer, cx| {
19231 buffer.set_all_diff_hunks_expanded(cx);
19232 });
19233 }
19234
19235 pub fn expand_all_diff_hunks(
19236 &mut self,
19237 _: &ExpandAllDiffHunks,
19238 _window: &mut Window,
19239 cx: &mut Context<Self>,
19240 ) {
19241 self.buffer.update(cx, |buffer, cx| {
19242 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19243 });
19244 }
19245
19246 pub fn collapse_all_diff_hunks(
19247 &mut self,
19248 _: &CollapseAllDiffHunks,
19249 _window: &mut Window,
19250 cx: &mut Context<Self>,
19251 ) {
19252 self.buffer.update(cx, |buffer, cx| {
19253 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19254 });
19255 }
19256
19257 pub fn toggle_selected_diff_hunks(
19258 &mut self,
19259 _: &ToggleSelectedDiffHunks,
19260 _window: &mut Window,
19261 cx: &mut Context<Self>,
19262 ) {
19263 let ranges: Vec<_> = self
19264 .selections
19265 .disjoint_anchors()
19266 .iter()
19267 .map(|s| s.range())
19268 .collect();
19269 self.toggle_diff_hunks_in_ranges(ranges, cx);
19270 }
19271
19272 pub fn diff_hunks_in_ranges<'a>(
19273 &'a self,
19274 ranges: &'a [Range<Anchor>],
19275 buffer: &'a MultiBufferSnapshot,
19276 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19277 ranges.iter().flat_map(move |range| {
19278 let end_excerpt_id = range.end.excerpt_id;
19279 let range = range.to_point(buffer);
19280 let mut peek_end = range.end;
19281 if range.end.row < buffer.max_row().0 {
19282 peek_end = Point::new(range.end.row + 1, 0);
19283 }
19284 buffer
19285 .diff_hunks_in_range(range.start..peek_end)
19286 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19287 })
19288 }
19289
19290 pub fn has_stageable_diff_hunks_in_ranges(
19291 &self,
19292 ranges: &[Range<Anchor>],
19293 snapshot: &MultiBufferSnapshot,
19294 ) -> bool {
19295 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19296 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19297 }
19298
19299 pub fn toggle_staged_selected_diff_hunks(
19300 &mut self,
19301 _: &::git::ToggleStaged,
19302 _: &mut Window,
19303 cx: &mut Context<Self>,
19304 ) {
19305 let snapshot = self.buffer.read(cx).snapshot(cx);
19306 let ranges: Vec<_> = self
19307 .selections
19308 .disjoint_anchors()
19309 .iter()
19310 .map(|s| s.range())
19311 .collect();
19312 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19313 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19314 }
19315
19316 pub fn set_render_diff_hunk_controls(
19317 &mut self,
19318 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19319 cx: &mut Context<Self>,
19320 ) {
19321 self.render_diff_hunk_controls = render_diff_hunk_controls;
19322 cx.notify();
19323 }
19324
19325 pub fn stage_and_next(
19326 &mut self,
19327 _: &::git::StageAndNext,
19328 window: &mut Window,
19329 cx: &mut Context<Self>,
19330 ) {
19331 self.do_stage_or_unstage_and_next(true, window, cx);
19332 }
19333
19334 pub fn unstage_and_next(
19335 &mut self,
19336 _: &::git::UnstageAndNext,
19337 window: &mut Window,
19338 cx: &mut Context<Self>,
19339 ) {
19340 self.do_stage_or_unstage_and_next(false, window, cx);
19341 }
19342
19343 pub fn stage_or_unstage_diff_hunks(
19344 &mut self,
19345 stage: bool,
19346 ranges: Vec<Range<Anchor>>,
19347 cx: &mut Context<Self>,
19348 ) {
19349 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19350 cx.spawn(async move |this, cx| {
19351 task.await?;
19352 this.update(cx, |this, cx| {
19353 let snapshot = this.buffer.read(cx).snapshot(cx);
19354 let chunk_by = this
19355 .diff_hunks_in_ranges(&ranges, &snapshot)
19356 .chunk_by(|hunk| hunk.buffer_id);
19357 for (buffer_id, hunks) in &chunk_by {
19358 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19359 }
19360 })
19361 })
19362 .detach_and_log_err(cx);
19363 }
19364
19365 fn save_buffers_for_ranges_if_needed(
19366 &mut self,
19367 ranges: &[Range<Anchor>],
19368 cx: &mut Context<Editor>,
19369 ) -> Task<Result<()>> {
19370 let multibuffer = self.buffer.read(cx);
19371 let snapshot = multibuffer.read(cx);
19372 let buffer_ids: HashSet<_> = ranges
19373 .iter()
19374 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19375 .collect();
19376 drop(snapshot);
19377
19378 let mut buffers = HashSet::default();
19379 for buffer_id in buffer_ids {
19380 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19381 let buffer = buffer_entity.read(cx);
19382 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19383 {
19384 buffers.insert(buffer_entity);
19385 }
19386 }
19387 }
19388
19389 if let Some(project) = &self.project {
19390 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19391 } else {
19392 Task::ready(Ok(()))
19393 }
19394 }
19395
19396 fn do_stage_or_unstage_and_next(
19397 &mut self,
19398 stage: bool,
19399 window: &mut Window,
19400 cx: &mut Context<Self>,
19401 ) {
19402 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19403
19404 if ranges.iter().any(|range| range.start != range.end) {
19405 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19406 return;
19407 }
19408
19409 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19410 let snapshot = self.snapshot(window, cx);
19411 let position = self
19412 .selections
19413 .newest::<Point>(&snapshot.display_snapshot)
19414 .head();
19415 let mut row = snapshot
19416 .buffer_snapshot()
19417 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19418 .find(|hunk| hunk.row_range.start.0 > position.row)
19419 .map(|hunk| hunk.row_range.start);
19420
19421 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19422 // Outside of the project diff editor, wrap around to the beginning.
19423 if !all_diff_hunks_expanded {
19424 row = row.or_else(|| {
19425 snapshot
19426 .buffer_snapshot()
19427 .diff_hunks_in_range(Point::zero()..position)
19428 .find(|hunk| hunk.row_range.end.0 < position.row)
19429 .map(|hunk| hunk.row_range.start)
19430 });
19431 }
19432
19433 if let Some(row) = row {
19434 let destination = Point::new(row.0, 0);
19435 let autoscroll = Autoscroll::center();
19436
19437 self.unfold_ranges(&[destination..destination], false, false, cx);
19438 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19439 s.select_ranges([destination..destination]);
19440 });
19441 }
19442 }
19443
19444 fn do_stage_or_unstage(
19445 &self,
19446 stage: bool,
19447 buffer_id: BufferId,
19448 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19449 cx: &mut App,
19450 ) -> Option<()> {
19451 let project = self.project()?;
19452 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19453 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19454 let buffer_snapshot = buffer.read(cx).snapshot();
19455 let file_exists = buffer_snapshot
19456 .file()
19457 .is_some_and(|file| file.disk_state().exists());
19458 diff.update(cx, |diff, cx| {
19459 diff.stage_or_unstage_hunks(
19460 stage,
19461 &hunks
19462 .map(|hunk| buffer_diff::DiffHunk {
19463 buffer_range: hunk.buffer_range,
19464 // We don't need to pass in word diffs here because they're only used for rendering and
19465 // this function changes internal state
19466 base_word_diffs: Vec::default(),
19467 buffer_word_diffs: Vec::default(),
19468 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19469 ..hunk.diff_base_byte_range.end.0,
19470 secondary_status: hunk.secondary_status,
19471 range: Point::zero()..Point::zero(), // unused
19472 })
19473 .collect::<Vec<_>>(),
19474 &buffer_snapshot,
19475 file_exists,
19476 cx,
19477 )
19478 });
19479 None
19480 }
19481
19482 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19483 let ranges: Vec<_> = self
19484 .selections
19485 .disjoint_anchors()
19486 .iter()
19487 .map(|s| s.range())
19488 .collect();
19489 self.buffer
19490 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19491 }
19492
19493 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19494 self.buffer.update(cx, |buffer, cx| {
19495 let ranges = vec![Anchor::min()..Anchor::max()];
19496 if !buffer.all_diff_hunks_expanded()
19497 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19498 {
19499 buffer.collapse_diff_hunks(ranges, cx);
19500 true
19501 } else {
19502 false
19503 }
19504 })
19505 }
19506
19507 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19508 if self.buffer.read(cx).all_diff_hunks_expanded() {
19509 return true;
19510 }
19511 let ranges = vec![Anchor::min()..Anchor::max()];
19512 self.buffer
19513 .read(cx)
19514 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19515 }
19516
19517 fn toggle_diff_hunks_in_ranges(
19518 &mut self,
19519 ranges: Vec<Range<Anchor>>,
19520 cx: &mut Context<Editor>,
19521 ) {
19522 self.buffer.update(cx, |buffer, cx| {
19523 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19524 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19525 })
19526 }
19527
19528 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19529 self.buffer.update(cx, |buffer, cx| {
19530 let snapshot = buffer.snapshot(cx);
19531 let excerpt_id = range.end.excerpt_id;
19532 let point_range = range.to_point(&snapshot);
19533 let expand = !buffer.single_hunk_is_expanded(range, cx);
19534 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19535 })
19536 }
19537
19538 pub(crate) fn apply_all_diff_hunks(
19539 &mut self,
19540 _: &ApplyAllDiffHunks,
19541 window: &mut Window,
19542 cx: &mut Context<Self>,
19543 ) {
19544 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19545
19546 let buffers = self.buffer.read(cx).all_buffers();
19547 for branch_buffer in buffers {
19548 branch_buffer.update(cx, |branch_buffer, cx| {
19549 branch_buffer.merge_into_base(Vec::new(), cx);
19550 });
19551 }
19552
19553 if let Some(project) = self.project.clone() {
19554 self.save(
19555 SaveOptions {
19556 format: true,
19557 autosave: false,
19558 },
19559 project,
19560 window,
19561 cx,
19562 )
19563 .detach_and_log_err(cx);
19564 }
19565 }
19566
19567 pub(crate) fn apply_selected_diff_hunks(
19568 &mut self,
19569 _: &ApplyDiffHunk,
19570 window: &mut Window,
19571 cx: &mut Context<Self>,
19572 ) {
19573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19574 let snapshot = self.snapshot(window, cx);
19575 let hunks = snapshot.hunks_for_ranges(
19576 self.selections
19577 .all(&snapshot.display_snapshot)
19578 .into_iter()
19579 .map(|selection| selection.range()),
19580 );
19581 let mut ranges_by_buffer = HashMap::default();
19582 self.transact(window, cx, |editor, _window, cx| {
19583 for hunk in hunks {
19584 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19585 ranges_by_buffer
19586 .entry(buffer.clone())
19587 .or_insert_with(Vec::new)
19588 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19589 }
19590 }
19591
19592 for (buffer, ranges) in ranges_by_buffer {
19593 buffer.update(cx, |buffer, cx| {
19594 buffer.merge_into_base(ranges, cx);
19595 });
19596 }
19597 });
19598
19599 if let Some(project) = self.project.clone() {
19600 self.save(
19601 SaveOptions {
19602 format: true,
19603 autosave: false,
19604 },
19605 project,
19606 window,
19607 cx,
19608 )
19609 .detach_and_log_err(cx);
19610 }
19611 }
19612
19613 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19614 if hovered != self.gutter_hovered {
19615 self.gutter_hovered = hovered;
19616 cx.notify();
19617 }
19618 }
19619
19620 pub fn insert_blocks(
19621 &mut self,
19622 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19623 autoscroll: Option<Autoscroll>,
19624 cx: &mut Context<Self>,
19625 ) -> Vec<CustomBlockId> {
19626 let blocks = self
19627 .display_map
19628 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19629 if let Some(autoscroll) = autoscroll {
19630 self.request_autoscroll(autoscroll, cx);
19631 }
19632 cx.notify();
19633 blocks
19634 }
19635
19636 pub fn resize_blocks(
19637 &mut self,
19638 heights: HashMap<CustomBlockId, u32>,
19639 autoscroll: Option<Autoscroll>,
19640 cx: &mut Context<Self>,
19641 ) {
19642 self.display_map
19643 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19644 if let Some(autoscroll) = autoscroll {
19645 self.request_autoscroll(autoscroll, cx);
19646 }
19647 cx.notify();
19648 }
19649
19650 pub fn replace_blocks(
19651 &mut self,
19652 renderers: HashMap<CustomBlockId, RenderBlock>,
19653 autoscroll: Option<Autoscroll>,
19654 cx: &mut Context<Self>,
19655 ) {
19656 self.display_map
19657 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19658 if let Some(autoscroll) = autoscroll {
19659 self.request_autoscroll(autoscroll, cx);
19660 }
19661 cx.notify();
19662 }
19663
19664 pub fn remove_blocks(
19665 &mut self,
19666 block_ids: HashSet<CustomBlockId>,
19667 autoscroll: Option<Autoscroll>,
19668 cx: &mut Context<Self>,
19669 ) {
19670 self.display_map.update(cx, |display_map, cx| {
19671 display_map.remove_blocks(block_ids, cx)
19672 });
19673 if let Some(autoscroll) = autoscroll {
19674 self.request_autoscroll(autoscroll, cx);
19675 }
19676 cx.notify();
19677 }
19678
19679 pub fn row_for_block(
19680 &self,
19681 block_id: CustomBlockId,
19682 cx: &mut Context<Self>,
19683 ) -> Option<DisplayRow> {
19684 self.display_map
19685 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19686 }
19687
19688 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19689 self.focused_block = Some(focused_block);
19690 }
19691
19692 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19693 self.focused_block.take()
19694 }
19695
19696 pub fn insert_creases(
19697 &mut self,
19698 creases: impl IntoIterator<Item = Crease<Anchor>>,
19699 cx: &mut Context<Self>,
19700 ) -> Vec<CreaseId> {
19701 self.display_map
19702 .update(cx, |map, cx| map.insert_creases(creases, cx))
19703 }
19704
19705 pub fn remove_creases(
19706 &mut self,
19707 ids: impl IntoIterator<Item = CreaseId>,
19708 cx: &mut Context<Self>,
19709 ) -> Vec<(CreaseId, Range<Anchor>)> {
19710 self.display_map
19711 .update(cx, |map, cx| map.remove_creases(ids, cx))
19712 }
19713
19714 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19715 self.display_map
19716 .update(cx, |map, cx| map.snapshot(cx))
19717 .longest_row()
19718 }
19719
19720 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19721 self.display_map
19722 .update(cx, |map, cx| map.snapshot(cx))
19723 .max_point()
19724 }
19725
19726 pub fn text(&self, cx: &App) -> String {
19727 self.buffer.read(cx).read(cx).text()
19728 }
19729
19730 pub fn is_empty(&self, cx: &App) -> bool {
19731 self.buffer.read(cx).read(cx).is_empty()
19732 }
19733
19734 pub fn text_option(&self, cx: &App) -> Option<String> {
19735 let text = self.text(cx);
19736 let text = text.trim();
19737
19738 if text.is_empty() {
19739 return None;
19740 }
19741
19742 Some(text.to_string())
19743 }
19744
19745 pub fn set_text(
19746 &mut self,
19747 text: impl Into<Arc<str>>,
19748 window: &mut Window,
19749 cx: &mut Context<Self>,
19750 ) {
19751 self.transact(window, cx, |this, _, cx| {
19752 this.buffer
19753 .read(cx)
19754 .as_singleton()
19755 .expect("you can only call set_text on editors for singleton buffers")
19756 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19757 });
19758 }
19759
19760 pub fn display_text(&self, cx: &mut App) -> String {
19761 self.display_map
19762 .update(cx, |map, cx| map.snapshot(cx))
19763 .text()
19764 }
19765
19766 fn create_minimap(
19767 &self,
19768 minimap_settings: MinimapSettings,
19769 window: &mut Window,
19770 cx: &mut Context<Self>,
19771 ) -> Option<Entity<Self>> {
19772 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19773 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19774 }
19775
19776 fn initialize_new_minimap(
19777 &self,
19778 minimap_settings: MinimapSettings,
19779 window: &mut Window,
19780 cx: &mut Context<Self>,
19781 ) -> Entity<Self> {
19782 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19783
19784 let mut minimap = Editor::new_internal(
19785 EditorMode::Minimap {
19786 parent: cx.weak_entity(),
19787 },
19788 self.buffer.clone(),
19789 None,
19790 Some(self.display_map.clone()),
19791 window,
19792 cx,
19793 );
19794 minimap.scroll_manager.clone_state(&self.scroll_manager);
19795 minimap.set_text_style_refinement(TextStyleRefinement {
19796 font_size: Some(MINIMAP_FONT_SIZE),
19797 font_weight: Some(MINIMAP_FONT_WEIGHT),
19798 ..Default::default()
19799 });
19800 minimap.update_minimap_configuration(minimap_settings, cx);
19801 cx.new(|_| minimap)
19802 }
19803
19804 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19805 let current_line_highlight = minimap_settings
19806 .current_line_highlight
19807 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19808 self.set_current_line_highlight(Some(current_line_highlight));
19809 }
19810
19811 pub fn minimap(&self) -> Option<&Entity<Self>> {
19812 self.minimap
19813 .as_ref()
19814 .filter(|_| self.minimap_visibility.visible())
19815 }
19816
19817 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19818 let mut wrap_guides = smallvec![];
19819
19820 if self.show_wrap_guides == Some(false) {
19821 return wrap_guides;
19822 }
19823
19824 let settings = self.buffer.read(cx).language_settings(cx);
19825 if settings.show_wrap_guides {
19826 match self.soft_wrap_mode(cx) {
19827 SoftWrap::Column(soft_wrap) => {
19828 wrap_guides.push((soft_wrap as usize, true));
19829 }
19830 SoftWrap::Bounded(soft_wrap) => {
19831 wrap_guides.push((soft_wrap as usize, true));
19832 }
19833 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19834 }
19835 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19836 }
19837
19838 wrap_guides
19839 }
19840
19841 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19842 let settings = self.buffer.read(cx).language_settings(cx);
19843 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19844 match mode {
19845 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19846 SoftWrap::None
19847 }
19848 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19849 language_settings::SoftWrap::PreferredLineLength => {
19850 SoftWrap::Column(settings.preferred_line_length)
19851 }
19852 language_settings::SoftWrap::Bounded => {
19853 SoftWrap::Bounded(settings.preferred_line_length)
19854 }
19855 }
19856 }
19857
19858 pub fn set_soft_wrap_mode(
19859 &mut self,
19860 mode: language_settings::SoftWrap,
19861
19862 cx: &mut Context<Self>,
19863 ) {
19864 self.soft_wrap_mode_override = Some(mode);
19865 cx.notify();
19866 }
19867
19868 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19869 self.hard_wrap = hard_wrap;
19870 cx.notify();
19871 }
19872
19873 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19874 self.text_style_refinement = Some(style);
19875 }
19876
19877 /// called by the Element so we know what style we were most recently rendered with.
19878 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19879 // We intentionally do not inform the display map about the minimap style
19880 // so that wrapping is not recalculated and stays consistent for the editor
19881 // and its linked minimap.
19882 if !self.mode.is_minimap() {
19883 let font = style.text.font();
19884 let font_size = style.text.font_size.to_pixels(window.rem_size());
19885 let display_map = self
19886 .placeholder_display_map
19887 .as_ref()
19888 .filter(|_| self.is_empty(cx))
19889 .unwrap_or(&self.display_map);
19890
19891 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19892 }
19893 self.style = Some(style);
19894 }
19895
19896 pub fn style(&self) -> Option<&EditorStyle> {
19897 self.style.as_ref()
19898 }
19899
19900 // Called by the element. This method is not designed to be called outside of the editor
19901 // element's layout code because it does not notify when rewrapping is computed synchronously.
19902 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19903 if self.is_empty(cx) {
19904 self.placeholder_display_map
19905 .as_ref()
19906 .map_or(false, |display_map| {
19907 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19908 })
19909 } else {
19910 self.display_map
19911 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19912 }
19913 }
19914
19915 pub fn set_soft_wrap(&mut self) {
19916 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19917 }
19918
19919 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19920 if self.soft_wrap_mode_override.is_some() {
19921 self.soft_wrap_mode_override.take();
19922 } else {
19923 let soft_wrap = match self.soft_wrap_mode(cx) {
19924 SoftWrap::GitDiff => return,
19925 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19926 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19927 language_settings::SoftWrap::None
19928 }
19929 };
19930 self.soft_wrap_mode_override = Some(soft_wrap);
19931 }
19932 cx.notify();
19933 }
19934
19935 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19936 let Some(workspace) = self.workspace() else {
19937 return;
19938 };
19939 let fs = workspace.read(cx).app_state().fs.clone();
19940 let current_show = TabBarSettings::get_global(cx).show;
19941 update_settings_file(fs, cx, move |setting, _| {
19942 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19943 });
19944 }
19945
19946 pub fn toggle_indent_guides(
19947 &mut self,
19948 _: &ToggleIndentGuides,
19949 _: &mut Window,
19950 cx: &mut Context<Self>,
19951 ) {
19952 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19953 self.buffer
19954 .read(cx)
19955 .language_settings(cx)
19956 .indent_guides
19957 .enabled
19958 });
19959 self.show_indent_guides = Some(!currently_enabled);
19960 cx.notify();
19961 }
19962
19963 fn should_show_indent_guides(&self) -> Option<bool> {
19964 self.show_indent_guides
19965 }
19966
19967 pub fn toggle_line_numbers(
19968 &mut self,
19969 _: &ToggleLineNumbers,
19970 _: &mut Window,
19971 cx: &mut Context<Self>,
19972 ) {
19973 let mut editor_settings = EditorSettings::get_global(cx).clone();
19974 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19975 EditorSettings::override_global(editor_settings, cx);
19976 }
19977
19978 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19979 if let Some(show_line_numbers) = self.show_line_numbers {
19980 return show_line_numbers;
19981 }
19982 EditorSettings::get_global(cx).gutter.line_numbers
19983 }
19984
19985 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19986 match (
19987 self.use_relative_line_numbers,
19988 EditorSettings::get_global(cx).relative_line_numbers,
19989 ) {
19990 (None, setting) => setting,
19991 (Some(false), _) => RelativeLineNumbers::Disabled,
19992 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19993 (Some(true), _) => RelativeLineNumbers::Enabled,
19994 }
19995 }
19996
19997 pub fn toggle_relative_line_numbers(
19998 &mut self,
19999 _: &ToggleRelativeLineNumbers,
20000 _: &mut Window,
20001 cx: &mut Context<Self>,
20002 ) {
20003 let is_relative = self.relative_line_numbers(cx);
20004 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20005 }
20006
20007 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20008 self.use_relative_line_numbers = is_relative;
20009 cx.notify();
20010 }
20011
20012 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20013 self.show_gutter = show_gutter;
20014 cx.notify();
20015 }
20016
20017 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20018 self.show_scrollbars = ScrollbarAxes {
20019 horizontal: show,
20020 vertical: show,
20021 };
20022 cx.notify();
20023 }
20024
20025 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20026 self.show_scrollbars.vertical = show;
20027 cx.notify();
20028 }
20029
20030 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20031 self.show_scrollbars.horizontal = show;
20032 cx.notify();
20033 }
20034
20035 pub fn set_minimap_visibility(
20036 &mut self,
20037 minimap_visibility: MinimapVisibility,
20038 window: &mut Window,
20039 cx: &mut Context<Self>,
20040 ) {
20041 if self.minimap_visibility != minimap_visibility {
20042 if minimap_visibility.visible() && self.minimap.is_none() {
20043 let minimap_settings = EditorSettings::get_global(cx).minimap;
20044 self.minimap =
20045 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20046 }
20047 self.minimap_visibility = minimap_visibility;
20048 cx.notify();
20049 }
20050 }
20051
20052 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20053 self.set_show_scrollbars(false, cx);
20054 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20055 }
20056
20057 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20058 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20059 }
20060
20061 /// Normally the text in full mode and auto height editors is padded on the
20062 /// left side by roughly half a character width for improved hit testing.
20063 ///
20064 /// Use this method to disable this for cases where this is not wanted (e.g.
20065 /// if you want to align the editor text with some other text above or below)
20066 /// or if you want to add this padding to single-line editors.
20067 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20068 self.offset_content = offset_content;
20069 cx.notify();
20070 }
20071
20072 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20073 self.show_line_numbers = Some(show_line_numbers);
20074 cx.notify();
20075 }
20076
20077 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20078 self.disable_expand_excerpt_buttons = true;
20079 cx.notify();
20080 }
20081
20082 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20083 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20084 cx.notify();
20085 }
20086
20087 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20088 self.show_code_actions = Some(show_code_actions);
20089 cx.notify();
20090 }
20091
20092 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20093 self.show_runnables = Some(show_runnables);
20094 cx.notify();
20095 }
20096
20097 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20098 self.show_breakpoints = Some(show_breakpoints);
20099 cx.notify();
20100 }
20101
20102 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20103 if self.display_map.read(cx).masked != masked {
20104 self.display_map.update(cx, |map, _| map.masked = masked);
20105 }
20106 cx.notify()
20107 }
20108
20109 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20110 self.show_wrap_guides = Some(show_wrap_guides);
20111 cx.notify();
20112 }
20113
20114 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20115 self.show_indent_guides = Some(show_indent_guides);
20116 cx.notify();
20117 }
20118
20119 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20120 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20121 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20122 && let Some(dir) = file.abs_path(cx).parent()
20123 {
20124 return Some(dir.to_owned());
20125 }
20126 }
20127
20128 None
20129 }
20130
20131 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20132 self.active_excerpt(cx)?
20133 .1
20134 .read(cx)
20135 .file()
20136 .and_then(|f| f.as_local())
20137 }
20138
20139 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20140 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20141 let buffer = buffer.read(cx);
20142 if let Some(project_path) = buffer.project_path(cx) {
20143 let project = self.project()?.read(cx);
20144 project.absolute_path(&project_path, cx)
20145 } else {
20146 buffer
20147 .file()
20148 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20149 }
20150 })
20151 }
20152
20153 pub fn reveal_in_finder(
20154 &mut self,
20155 _: &RevealInFileManager,
20156 _window: &mut Window,
20157 cx: &mut Context<Self>,
20158 ) {
20159 if let Some(target) = self.target_file(cx) {
20160 cx.reveal_path(&target.abs_path(cx));
20161 }
20162 }
20163
20164 pub fn copy_path(
20165 &mut self,
20166 _: &zed_actions::workspace::CopyPath,
20167 _window: &mut Window,
20168 cx: &mut Context<Self>,
20169 ) {
20170 if let Some(path) = self.target_file_abs_path(cx)
20171 && let Some(path) = path.to_str()
20172 {
20173 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20174 } else {
20175 cx.propagate();
20176 }
20177 }
20178
20179 pub fn copy_relative_path(
20180 &mut self,
20181 _: &zed_actions::workspace::CopyRelativePath,
20182 _window: &mut Window,
20183 cx: &mut Context<Self>,
20184 ) {
20185 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20186 let project = self.project()?.read(cx);
20187 let path = buffer.read(cx).file()?.path();
20188 let path = path.display(project.path_style(cx));
20189 Some(path)
20190 }) {
20191 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20192 } else {
20193 cx.propagate();
20194 }
20195 }
20196
20197 /// Returns the project path for the editor's buffer, if any buffer is
20198 /// opened in the editor.
20199 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20200 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20201 buffer.read(cx).project_path(cx)
20202 } else {
20203 None
20204 }
20205 }
20206
20207 // Returns true if the editor handled a go-to-line request
20208 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20209 maybe!({
20210 let breakpoint_store = self.breakpoint_store.as_ref()?;
20211
20212 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20213 else {
20214 self.clear_row_highlights::<ActiveDebugLine>();
20215 return None;
20216 };
20217
20218 let position = active_stack_frame.position;
20219 let buffer_id = position.buffer_id?;
20220 let snapshot = self
20221 .project
20222 .as_ref()?
20223 .read(cx)
20224 .buffer_for_id(buffer_id, cx)?
20225 .read(cx)
20226 .snapshot();
20227
20228 let mut handled = false;
20229 for (id, ExcerptRange { context, .. }) in
20230 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20231 {
20232 if context.start.cmp(&position, &snapshot).is_ge()
20233 || context.end.cmp(&position, &snapshot).is_lt()
20234 {
20235 continue;
20236 }
20237 let snapshot = self.buffer.read(cx).snapshot(cx);
20238 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20239
20240 handled = true;
20241 self.clear_row_highlights::<ActiveDebugLine>();
20242
20243 self.go_to_line::<ActiveDebugLine>(
20244 multibuffer_anchor,
20245 Some(cx.theme().colors().editor_debugger_active_line_background),
20246 window,
20247 cx,
20248 );
20249
20250 cx.notify();
20251 }
20252
20253 handled.then_some(())
20254 })
20255 .is_some()
20256 }
20257
20258 pub fn copy_file_name_without_extension(
20259 &mut self,
20260 _: &CopyFileNameWithoutExtension,
20261 _: &mut Window,
20262 cx: &mut Context<Self>,
20263 ) {
20264 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20265 let file = buffer.read(cx).file()?;
20266 file.path().file_stem()
20267 }) {
20268 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20269 }
20270 }
20271
20272 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20273 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20274 let file = buffer.read(cx).file()?;
20275 Some(file.file_name(cx))
20276 }) {
20277 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20278 }
20279 }
20280
20281 pub fn toggle_git_blame(
20282 &mut self,
20283 _: &::git::Blame,
20284 window: &mut Window,
20285 cx: &mut Context<Self>,
20286 ) {
20287 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20288
20289 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20290 self.start_git_blame(true, window, cx);
20291 }
20292
20293 cx.notify();
20294 }
20295
20296 pub fn toggle_git_blame_inline(
20297 &mut self,
20298 _: &ToggleGitBlameInline,
20299 window: &mut Window,
20300 cx: &mut Context<Self>,
20301 ) {
20302 self.toggle_git_blame_inline_internal(true, window, cx);
20303 cx.notify();
20304 }
20305
20306 pub fn open_git_blame_commit(
20307 &mut self,
20308 _: &OpenGitBlameCommit,
20309 window: &mut Window,
20310 cx: &mut Context<Self>,
20311 ) {
20312 self.open_git_blame_commit_internal(window, cx);
20313 }
20314
20315 fn open_git_blame_commit_internal(
20316 &mut self,
20317 window: &mut Window,
20318 cx: &mut Context<Self>,
20319 ) -> Option<()> {
20320 let blame = self.blame.as_ref()?;
20321 let snapshot = self.snapshot(window, cx);
20322 let cursor = self
20323 .selections
20324 .newest::<Point>(&snapshot.display_snapshot)
20325 .head();
20326 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20327 let (_, blame_entry) = blame
20328 .update(cx, |blame, cx| {
20329 blame
20330 .blame_for_rows(
20331 &[RowInfo {
20332 buffer_id: Some(buffer.remote_id()),
20333 buffer_row: Some(point.row),
20334 ..Default::default()
20335 }],
20336 cx,
20337 )
20338 .next()
20339 })
20340 .flatten()?;
20341 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20342 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20343 let workspace = self.workspace()?.downgrade();
20344 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20345 None
20346 }
20347
20348 pub fn git_blame_inline_enabled(&self) -> bool {
20349 self.git_blame_inline_enabled
20350 }
20351
20352 pub fn toggle_selection_menu(
20353 &mut self,
20354 _: &ToggleSelectionMenu,
20355 _: &mut Window,
20356 cx: &mut Context<Self>,
20357 ) {
20358 self.show_selection_menu = self
20359 .show_selection_menu
20360 .map(|show_selections_menu| !show_selections_menu)
20361 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20362
20363 cx.notify();
20364 }
20365
20366 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20367 self.show_selection_menu
20368 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20369 }
20370
20371 fn start_git_blame(
20372 &mut self,
20373 user_triggered: bool,
20374 window: &mut Window,
20375 cx: &mut Context<Self>,
20376 ) {
20377 if let Some(project) = self.project() {
20378 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20379 && buffer.read(cx).file().is_none()
20380 {
20381 return;
20382 }
20383
20384 let focused = self.focus_handle(cx).contains_focused(window, cx);
20385
20386 let project = project.clone();
20387 let blame = cx
20388 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20389 self.blame_subscription =
20390 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20391 self.blame = Some(blame);
20392 }
20393 }
20394
20395 fn toggle_git_blame_inline_internal(
20396 &mut self,
20397 user_triggered: bool,
20398 window: &mut Window,
20399 cx: &mut Context<Self>,
20400 ) {
20401 if self.git_blame_inline_enabled {
20402 self.git_blame_inline_enabled = false;
20403 self.show_git_blame_inline = false;
20404 self.show_git_blame_inline_delay_task.take();
20405 } else {
20406 self.git_blame_inline_enabled = true;
20407 self.start_git_blame_inline(user_triggered, window, cx);
20408 }
20409
20410 cx.notify();
20411 }
20412
20413 fn start_git_blame_inline(
20414 &mut self,
20415 user_triggered: bool,
20416 window: &mut Window,
20417 cx: &mut Context<Self>,
20418 ) {
20419 self.start_git_blame(user_triggered, window, cx);
20420
20421 if ProjectSettings::get_global(cx)
20422 .git
20423 .inline_blame_delay()
20424 .is_some()
20425 {
20426 self.start_inline_blame_timer(window, cx);
20427 } else {
20428 self.show_git_blame_inline = true
20429 }
20430 }
20431
20432 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20433 self.blame.as_ref()
20434 }
20435
20436 pub fn show_git_blame_gutter(&self) -> bool {
20437 self.show_git_blame_gutter
20438 }
20439
20440 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20441 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20442 }
20443
20444 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20445 self.show_git_blame_inline
20446 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20447 && !self.newest_selection_head_on_empty_line(cx)
20448 && self.has_blame_entries(cx)
20449 }
20450
20451 fn has_blame_entries(&self, cx: &App) -> bool {
20452 self.blame()
20453 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20454 }
20455
20456 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20457 let cursor_anchor = self.selections.newest_anchor().head();
20458
20459 let snapshot = self.buffer.read(cx).snapshot(cx);
20460 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20461
20462 snapshot.line_len(buffer_row) == 0
20463 }
20464
20465 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20466 let buffer_and_selection = maybe!({
20467 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20468 let selection_range = selection.range();
20469
20470 let multi_buffer = self.buffer().read(cx);
20471 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20472 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20473
20474 let (buffer, range, _) = if selection.reversed {
20475 buffer_ranges.first()
20476 } else {
20477 buffer_ranges.last()
20478 }?;
20479
20480 let selection = text::ToPoint::to_point(&range.start, buffer).row
20481 ..text::ToPoint::to_point(&range.end, buffer).row;
20482 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20483 });
20484
20485 let Some((buffer, selection)) = buffer_and_selection else {
20486 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20487 };
20488
20489 let Some(project) = self.project() else {
20490 return Task::ready(Err(anyhow!("editor does not have project")));
20491 };
20492
20493 project.update(cx, |project, cx| {
20494 project.get_permalink_to_line(&buffer, selection, cx)
20495 })
20496 }
20497
20498 pub fn copy_permalink_to_line(
20499 &mut self,
20500 _: &CopyPermalinkToLine,
20501 window: &mut Window,
20502 cx: &mut Context<Self>,
20503 ) {
20504 let permalink_task = self.get_permalink_to_line(cx);
20505 let workspace = self.workspace();
20506
20507 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20508 Ok(permalink) => {
20509 cx.update(|_, cx| {
20510 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20511 })
20512 .ok();
20513 }
20514 Err(err) => {
20515 let message = format!("Failed to copy permalink: {err}");
20516
20517 anyhow::Result::<()>::Err(err).log_err();
20518
20519 if let Some(workspace) = workspace {
20520 workspace
20521 .update_in(cx, |workspace, _, cx| {
20522 struct CopyPermalinkToLine;
20523
20524 workspace.show_toast(
20525 Toast::new(
20526 NotificationId::unique::<CopyPermalinkToLine>(),
20527 message,
20528 ),
20529 cx,
20530 )
20531 })
20532 .ok();
20533 }
20534 }
20535 })
20536 .detach();
20537 }
20538
20539 pub fn copy_file_location(
20540 &mut self,
20541 _: &CopyFileLocation,
20542 _: &mut Window,
20543 cx: &mut Context<Self>,
20544 ) {
20545 let selection = self
20546 .selections
20547 .newest::<Point>(&self.display_snapshot(cx))
20548 .start
20549 .row
20550 + 1;
20551 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20552 let project = self.project()?.read(cx);
20553 let file = buffer.read(cx).file()?;
20554 let path = file.path().display(project.path_style(cx));
20555
20556 Some(format!("{path}:{selection}"))
20557 }) {
20558 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20559 }
20560 }
20561
20562 pub fn open_permalink_to_line(
20563 &mut self,
20564 _: &OpenPermalinkToLine,
20565 window: &mut Window,
20566 cx: &mut Context<Self>,
20567 ) {
20568 let permalink_task = self.get_permalink_to_line(cx);
20569 let workspace = self.workspace();
20570
20571 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20572 Ok(permalink) => {
20573 cx.update(|_, cx| {
20574 cx.open_url(permalink.as_ref());
20575 })
20576 .ok();
20577 }
20578 Err(err) => {
20579 let message = format!("Failed to open permalink: {err}");
20580
20581 anyhow::Result::<()>::Err(err).log_err();
20582
20583 if let Some(workspace) = workspace {
20584 workspace
20585 .update(cx, |workspace, cx| {
20586 struct OpenPermalinkToLine;
20587
20588 workspace.show_toast(
20589 Toast::new(
20590 NotificationId::unique::<OpenPermalinkToLine>(),
20591 message,
20592 ),
20593 cx,
20594 )
20595 })
20596 .ok();
20597 }
20598 }
20599 })
20600 .detach();
20601 }
20602
20603 pub fn insert_uuid_v4(
20604 &mut self,
20605 _: &InsertUuidV4,
20606 window: &mut Window,
20607 cx: &mut Context<Self>,
20608 ) {
20609 self.insert_uuid(UuidVersion::V4, window, cx);
20610 }
20611
20612 pub fn insert_uuid_v7(
20613 &mut self,
20614 _: &InsertUuidV7,
20615 window: &mut Window,
20616 cx: &mut Context<Self>,
20617 ) {
20618 self.insert_uuid(UuidVersion::V7, window, cx);
20619 }
20620
20621 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20622 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20623 self.transact(window, cx, |this, window, cx| {
20624 let edits = this
20625 .selections
20626 .all::<Point>(&this.display_snapshot(cx))
20627 .into_iter()
20628 .map(|selection| {
20629 let uuid = match version {
20630 UuidVersion::V4 => uuid::Uuid::new_v4(),
20631 UuidVersion::V7 => uuid::Uuid::now_v7(),
20632 };
20633
20634 (selection.range(), uuid.to_string())
20635 });
20636 this.edit(edits, cx);
20637 this.refresh_edit_prediction(true, false, window, cx);
20638 });
20639 }
20640
20641 pub fn open_selections_in_multibuffer(
20642 &mut self,
20643 _: &OpenSelectionsInMultibuffer,
20644 window: &mut Window,
20645 cx: &mut Context<Self>,
20646 ) {
20647 let multibuffer = self.buffer.read(cx);
20648
20649 let Some(buffer) = multibuffer.as_singleton() else {
20650 return;
20651 };
20652
20653 let Some(workspace) = self.workspace() else {
20654 return;
20655 };
20656
20657 let title = multibuffer.title(cx).to_string();
20658
20659 let locations = self
20660 .selections
20661 .all_anchors(&self.display_snapshot(cx))
20662 .iter()
20663 .map(|selection| {
20664 (
20665 buffer.clone(),
20666 (selection.start.text_anchor..selection.end.text_anchor)
20667 .to_point(buffer.read(cx)),
20668 )
20669 })
20670 .into_group_map();
20671
20672 cx.spawn_in(window, async move |_, cx| {
20673 workspace.update_in(cx, |workspace, window, cx| {
20674 Self::open_locations_in_multibuffer(
20675 workspace,
20676 locations,
20677 format!("Selections for '{title}'"),
20678 false,
20679 MultibufferSelectionMode::All,
20680 window,
20681 cx,
20682 );
20683 })
20684 })
20685 .detach();
20686 }
20687
20688 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20689 /// last highlight added will be used.
20690 ///
20691 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20692 pub fn highlight_rows<T: 'static>(
20693 &mut self,
20694 range: Range<Anchor>,
20695 color: Hsla,
20696 options: RowHighlightOptions,
20697 cx: &mut Context<Self>,
20698 ) {
20699 let snapshot = self.buffer().read(cx).snapshot(cx);
20700 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20701 let ix = row_highlights.binary_search_by(|highlight| {
20702 Ordering::Equal
20703 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20704 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20705 });
20706
20707 if let Err(mut ix) = ix {
20708 let index = post_inc(&mut self.highlight_order);
20709
20710 // If this range intersects with the preceding highlight, then merge it with
20711 // the preceding highlight. Otherwise insert a new highlight.
20712 let mut merged = false;
20713 if ix > 0 {
20714 let prev_highlight = &mut row_highlights[ix - 1];
20715 if prev_highlight
20716 .range
20717 .end
20718 .cmp(&range.start, &snapshot)
20719 .is_ge()
20720 {
20721 ix -= 1;
20722 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20723 prev_highlight.range.end = range.end;
20724 }
20725 merged = true;
20726 prev_highlight.index = index;
20727 prev_highlight.color = color;
20728 prev_highlight.options = options;
20729 }
20730 }
20731
20732 if !merged {
20733 row_highlights.insert(
20734 ix,
20735 RowHighlight {
20736 range,
20737 index,
20738 color,
20739 options,
20740 type_id: TypeId::of::<T>(),
20741 },
20742 );
20743 }
20744
20745 // If any of the following highlights intersect with this one, merge them.
20746 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20747 let highlight = &row_highlights[ix];
20748 if next_highlight
20749 .range
20750 .start
20751 .cmp(&highlight.range.end, &snapshot)
20752 .is_le()
20753 {
20754 if next_highlight
20755 .range
20756 .end
20757 .cmp(&highlight.range.end, &snapshot)
20758 .is_gt()
20759 {
20760 row_highlights[ix].range.end = next_highlight.range.end;
20761 }
20762 row_highlights.remove(ix + 1);
20763 } else {
20764 break;
20765 }
20766 }
20767 }
20768 }
20769
20770 /// Remove any highlighted row ranges of the given type that intersect the
20771 /// given ranges.
20772 pub fn remove_highlighted_rows<T: 'static>(
20773 &mut self,
20774 ranges_to_remove: Vec<Range<Anchor>>,
20775 cx: &mut Context<Self>,
20776 ) {
20777 let snapshot = self.buffer().read(cx).snapshot(cx);
20778 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20779 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20780 row_highlights.retain(|highlight| {
20781 while let Some(range_to_remove) = ranges_to_remove.peek() {
20782 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20783 Ordering::Less | Ordering::Equal => {
20784 ranges_to_remove.next();
20785 }
20786 Ordering::Greater => {
20787 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20788 Ordering::Less | Ordering::Equal => {
20789 return false;
20790 }
20791 Ordering::Greater => break,
20792 }
20793 }
20794 }
20795 }
20796
20797 true
20798 })
20799 }
20800
20801 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20802 pub fn clear_row_highlights<T: 'static>(&mut self) {
20803 self.highlighted_rows.remove(&TypeId::of::<T>());
20804 }
20805
20806 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20807 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20808 self.highlighted_rows
20809 .get(&TypeId::of::<T>())
20810 .map_or(&[] as &[_], |vec| vec.as_slice())
20811 .iter()
20812 .map(|highlight| (highlight.range.clone(), highlight.color))
20813 }
20814
20815 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20816 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20817 /// Allows to ignore certain kinds of highlights.
20818 pub fn highlighted_display_rows(
20819 &self,
20820 window: &mut Window,
20821 cx: &mut App,
20822 ) -> BTreeMap<DisplayRow, LineHighlight> {
20823 let snapshot = self.snapshot(window, cx);
20824 let mut used_highlight_orders = HashMap::default();
20825 self.highlighted_rows
20826 .iter()
20827 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20828 .fold(
20829 BTreeMap::<DisplayRow, LineHighlight>::new(),
20830 |mut unique_rows, highlight| {
20831 let start = highlight.range.start.to_display_point(&snapshot);
20832 let end = highlight.range.end.to_display_point(&snapshot);
20833 let start_row = start.row().0;
20834 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20835 {
20836 end.row().0.saturating_sub(1)
20837 } else {
20838 end.row().0
20839 };
20840 for row in start_row..=end_row {
20841 let used_index =
20842 used_highlight_orders.entry(row).or_insert(highlight.index);
20843 if highlight.index >= *used_index {
20844 *used_index = highlight.index;
20845 unique_rows.insert(
20846 DisplayRow(row),
20847 LineHighlight {
20848 include_gutter: highlight.options.include_gutter,
20849 border: None,
20850 background: highlight.color.into(),
20851 type_id: Some(highlight.type_id),
20852 },
20853 );
20854 }
20855 }
20856 unique_rows
20857 },
20858 )
20859 }
20860
20861 pub fn highlighted_display_row_for_autoscroll(
20862 &self,
20863 snapshot: &DisplaySnapshot,
20864 ) -> Option<DisplayRow> {
20865 self.highlighted_rows
20866 .values()
20867 .flat_map(|highlighted_rows| highlighted_rows.iter())
20868 .filter_map(|highlight| {
20869 if highlight.options.autoscroll {
20870 Some(highlight.range.start.to_display_point(snapshot).row())
20871 } else {
20872 None
20873 }
20874 })
20875 .min()
20876 }
20877
20878 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20879 self.highlight_background::<SearchWithinRange>(
20880 ranges,
20881 |colors| colors.colors().editor_document_highlight_read_background,
20882 cx,
20883 )
20884 }
20885
20886 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20887 self.breadcrumb_header = Some(new_header);
20888 }
20889
20890 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20891 self.clear_background_highlights::<SearchWithinRange>(cx);
20892 }
20893
20894 pub fn highlight_background<T: 'static>(
20895 &mut self,
20896 ranges: &[Range<Anchor>],
20897 color_fetcher: fn(&Theme) -> Hsla,
20898 cx: &mut Context<Self>,
20899 ) {
20900 self.background_highlights.insert(
20901 HighlightKey::Type(TypeId::of::<T>()),
20902 (color_fetcher, Arc::from(ranges)),
20903 );
20904 self.scrollbar_marker_state.dirty = true;
20905 cx.notify();
20906 }
20907
20908 pub fn highlight_background_key<T: 'static>(
20909 &mut self,
20910 key: usize,
20911 ranges: &[Range<Anchor>],
20912 color_fetcher: fn(&Theme) -> Hsla,
20913 cx: &mut Context<Self>,
20914 ) {
20915 self.background_highlights.insert(
20916 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20917 (color_fetcher, Arc::from(ranges)),
20918 );
20919 self.scrollbar_marker_state.dirty = true;
20920 cx.notify();
20921 }
20922
20923 pub fn clear_background_highlights<T: 'static>(
20924 &mut self,
20925 cx: &mut Context<Self>,
20926 ) -> Option<BackgroundHighlight> {
20927 let text_highlights = self
20928 .background_highlights
20929 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20930 if !text_highlights.1.is_empty() {
20931 self.scrollbar_marker_state.dirty = true;
20932 cx.notify();
20933 }
20934 Some(text_highlights)
20935 }
20936
20937 pub fn highlight_gutter<T: 'static>(
20938 &mut self,
20939 ranges: impl Into<Vec<Range<Anchor>>>,
20940 color_fetcher: fn(&App) -> Hsla,
20941 cx: &mut Context<Self>,
20942 ) {
20943 self.gutter_highlights
20944 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20945 cx.notify();
20946 }
20947
20948 pub fn clear_gutter_highlights<T: 'static>(
20949 &mut self,
20950 cx: &mut Context<Self>,
20951 ) -> Option<GutterHighlight> {
20952 cx.notify();
20953 self.gutter_highlights.remove(&TypeId::of::<T>())
20954 }
20955
20956 pub fn insert_gutter_highlight<T: 'static>(
20957 &mut self,
20958 range: Range<Anchor>,
20959 color_fetcher: fn(&App) -> Hsla,
20960 cx: &mut Context<Self>,
20961 ) {
20962 let snapshot = self.buffer().read(cx).snapshot(cx);
20963 let mut highlights = self
20964 .gutter_highlights
20965 .remove(&TypeId::of::<T>())
20966 .map(|(_, highlights)| highlights)
20967 .unwrap_or_default();
20968 let ix = highlights.binary_search_by(|highlight| {
20969 Ordering::Equal
20970 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20971 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20972 });
20973 if let Err(ix) = ix {
20974 highlights.insert(ix, range);
20975 }
20976 self.gutter_highlights
20977 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20978 }
20979
20980 pub fn remove_gutter_highlights<T: 'static>(
20981 &mut self,
20982 ranges_to_remove: Vec<Range<Anchor>>,
20983 cx: &mut Context<Self>,
20984 ) {
20985 let snapshot = self.buffer().read(cx).snapshot(cx);
20986 let Some((color_fetcher, mut gutter_highlights)) =
20987 self.gutter_highlights.remove(&TypeId::of::<T>())
20988 else {
20989 return;
20990 };
20991 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20992 gutter_highlights.retain(|highlight| {
20993 while let Some(range_to_remove) = ranges_to_remove.peek() {
20994 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20995 Ordering::Less | Ordering::Equal => {
20996 ranges_to_remove.next();
20997 }
20998 Ordering::Greater => {
20999 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21000 Ordering::Less | Ordering::Equal => {
21001 return false;
21002 }
21003 Ordering::Greater => break,
21004 }
21005 }
21006 }
21007 }
21008
21009 true
21010 });
21011 self.gutter_highlights
21012 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21013 }
21014
21015 #[cfg(feature = "test-support")]
21016 pub fn all_text_highlights(
21017 &self,
21018 window: &mut Window,
21019 cx: &mut Context<Self>,
21020 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21021 let snapshot = self.snapshot(window, cx);
21022 self.display_map.update(cx, |display_map, _| {
21023 display_map
21024 .all_text_highlights()
21025 .map(|highlight| {
21026 let (style, ranges) = highlight.as_ref();
21027 (
21028 *style,
21029 ranges
21030 .iter()
21031 .map(|range| range.clone().to_display_points(&snapshot))
21032 .collect(),
21033 )
21034 })
21035 .collect()
21036 })
21037 }
21038
21039 #[cfg(feature = "test-support")]
21040 pub fn all_text_background_highlights(
21041 &self,
21042 window: &mut Window,
21043 cx: &mut Context<Self>,
21044 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21045 let snapshot = self.snapshot(window, cx);
21046 let buffer = &snapshot.buffer_snapshot();
21047 let start = buffer.anchor_before(MultiBufferOffset(0));
21048 let end = buffer.anchor_after(buffer.len());
21049 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21050 }
21051
21052 #[cfg(any(test, feature = "test-support"))]
21053 pub fn sorted_background_highlights_in_range(
21054 &self,
21055 search_range: Range<Anchor>,
21056 display_snapshot: &DisplaySnapshot,
21057 theme: &Theme,
21058 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21059 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21060 res.sort_by(|a, b| {
21061 a.0.start
21062 .cmp(&b.0.start)
21063 .then_with(|| a.0.end.cmp(&b.0.end))
21064 .then_with(|| a.1.cmp(&b.1))
21065 });
21066 res
21067 }
21068
21069 #[cfg(feature = "test-support")]
21070 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21071 let snapshot = self.buffer().read(cx).snapshot(cx);
21072
21073 let highlights = self
21074 .background_highlights
21075 .get(&HighlightKey::Type(TypeId::of::<
21076 items::BufferSearchHighlights,
21077 >()));
21078
21079 if let Some((_color, ranges)) = highlights {
21080 ranges
21081 .iter()
21082 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21083 .collect_vec()
21084 } else {
21085 vec![]
21086 }
21087 }
21088
21089 fn document_highlights_for_position<'a>(
21090 &'a self,
21091 position: Anchor,
21092 buffer: &'a MultiBufferSnapshot,
21093 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21094 let read_highlights = self
21095 .background_highlights
21096 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21097 .map(|h| &h.1);
21098 let write_highlights = self
21099 .background_highlights
21100 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21101 .map(|h| &h.1);
21102 let left_position = position.bias_left(buffer);
21103 let right_position = position.bias_right(buffer);
21104 read_highlights
21105 .into_iter()
21106 .chain(write_highlights)
21107 .flat_map(move |ranges| {
21108 let start_ix = match ranges.binary_search_by(|probe| {
21109 let cmp = probe.end.cmp(&left_position, buffer);
21110 if cmp.is_ge() {
21111 Ordering::Greater
21112 } else {
21113 Ordering::Less
21114 }
21115 }) {
21116 Ok(i) | Err(i) => i,
21117 };
21118
21119 ranges[start_ix..]
21120 .iter()
21121 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21122 })
21123 }
21124
21125 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21126 self.background_highlights
21127 .get(&HighlightKey::Type(TypeId::of::<T>()))
21128 .is_some_and(|(_, highlights)| !highlights.is_empty())
21129 }
21130
21131 /// Returns all background highlights for a given range.
21132 ///
21133 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21134 pub fn background_highlights_in_range(
21135 &self,
21136 search_range: Range<Anchor>,
21137 display_snapshot: &DisplaySnapshot,
21138 theme: &Theme,
21139 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21140 let mut results = Vec::new();
21141 for (color_fetcher, ranges) in self.background_highlights.values() {
21142 let color = color_fetcher(theme);
21143 let start_ix = match ranges.binary_search_by(|probe| {
21144 let cmp = probe
21145 .end
21146 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21147 if cmp.is_gt() {
21148 Ordering::Greater
21149 } else {
21150 Ordering::Less
21151 }
21152 }) {
21153 Ok(i) | Err(i) => i,
21154 };
21155 for range in &ranges[start_ix..] {
21156 if range
21157 .start
21158 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21159 .is_ge()
21160 {
21161 break;
21162 }
21163
21164 let start = range.start.to_display_point(display_snapshot);
21165 let end = range.end.to_display_point(display_snapshot);
21166 results.push((start..end, color))
21167 }
21168 }
21169 results
21170 }
21171
21172 pub fn gutter_highlights_in_range(
21173 &self,
21174 search_range: Range<Anchor>,
21175 display_snapshot: &DisplaySnapshot,
21176 cx: &App,
21177 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21178 let mut results = Vec::new();
21179 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21180 let color = color_fetcher(cx);
21181 let start_ix = match ranges.binary_search_by(|probe| {
21182 let cmp = probe
21183 .end
21184 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21185 if cmp.is_gt() {
21186 Ordering::Greater
21187 } else {
21188 Ordering::Less
21189 }
21190 }) {
21191 Ok(i) | Err(i) => i,
21192 };
21193 for range in &ranges[start_ix..] {
21194 if range
21195 .start
21196 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21197 .is_ge()
21198 {
21199 break;
21200 }
21201
21202 let start = range.start.to_display_point(display_snapshot);
21203 let end = range.end.to_display_point(display_snapshot);
21204 results.push((start..end, color))
21205 }
21206 }
21207 results
21208 }
21209
21210 /// Get the text ranges corresponding to the redaction query
21211 pub fn redacted_ranges(
21212 &self,
21213 search_range: Range<Anchor>,
21214 display_snapshot: &DisplaySnapshot,
21215 cx: &App,
21216 ) -> Vec<Range<DisplayPoint>> {
21217 display_snapshot
21218 .buffer_snapshot()
21219 .redacted_ranges(search_range, |file| {
21220 if let Some(file) = file {
21221 file.is_private()
21222 && EditorSettings::get(
21223 Some(SettingsLocation {
21224 worktree_id: file.worktree_id(cx),
21225 path: file.path().as_ref(),
21226 }),
21227 cx,
21228 )
21229 .redact_private_values
21230 } else {
21231 false
21232 }
21233 })
21234 .map(|range| {
21235 range.start.to_display_point(display_snapshot)
21236 ..range.end.to_display_point(display_snapshot)
21237 })
21238 .collect()
21239 }
21240
21241 pub fn highlight_text_key<T: 'static>(
21242 &mut self,
21243 key: usize,
21244 ranges: Vec<Range<Anchor>>,
21245 style: HighlightStyle,
21246 merge: bool,
21247 cx: &mut Context<Self>,
21248 ) {
21249 self.display_map.update(cx, |map, cx| {
21250 map.highlight_text(
21251 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21252 ranges,
21253 style,
21254 merge,
21255 cx,
21256 );
21257 });
21258 cx.notify();
21259 }
21260
21261 pub fn highlight_text<T: 'static>(
21262 &mut self,
21263 ranges: Vec<Range<Anchor>>,
21264 style: HighlightStyle,
21265 cx: &mut Context<Self>,
21266 ) {
21267 self.display_map.update(cx, |map, cx| {
21268 map.highlight_text(
21269 HighlightKey::Type(TypeId::of::<T>()),
21270 ranges,
21271 style,
21272 false,
21273 cx,
21274 )
21275 });
21276 cx.notify();
21277 }
21278
21279 pub fn text_highlights<'a, T: 'static>(
21280 &'a self,
21281 cx: &'a App,
21282 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21283 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21284 }
21285
21286 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21287 let cleared = self
21288 .display_map
21289 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21290 if cleared {
21291 cx.notify();
21292 }
21293 }
21294
21295 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21296 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21297 && self.focus_handle.is_focused(window)
21298 }
21299
21300 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21301 self.show_cursor_when_unfocused = is_enabled;
21302 cx.notify();
21303 }
21304
21305 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21306 cx.notify();
21307 }
21308
21309 fn on_debug_session_event(
21310 &mut self,
21311 _session: Entity<Session>,
21312 event: &SessionEvent,
21313 cx: &mut Context<Self>,
21314 ) {
21315 if let SessionEvent::InvalidateInlineValue = event {
21316 self.refresh_inline_values(cx);
21317 }
21318 }
21319
21320 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21321 let Some(project) = self.project.clone() else {
21322 return;
21323 };
21324
21325 if !self.inline_value_cache.enabled {
21326 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21327 self.splice_inlays(&inlays, Vec::new(), cx);
21328 return;
21329 }
21330
21331 let current_execution_position = self
21332 .highlighted_rows
21333 .get(&TypeId::of::<ActiveDebugLine>())
21334 .and_then(|lines| lines.last().map(|line| line.range.end));
21335
21336 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21337 let inline_values = editor
21338 .update(cx, |editor, cx| {
21339 let Some(current_execution_position) = current_execution_position else {
21340 return Some(Task::ready(Ok(Vec::new())));
21341 };
21342
21343 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21344 let snapshot = buffer.snapshot(cx);
21345
21346 let excerpt = snapshot.excerpt_containing(
21347 current_execution_position..current_execution_position,
21348 )?;
21349
21350 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21351 })?;
21352
21353 let range =
21354 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21355
21356 project.inline_values(buffer, range, cx)
21357 })
21358 .ok()
21359 .flatten()?
21360 .await
21361 .context("refreshing debugger inlays")
21362 .log_err()?;
21363
21364 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21365
21366 for (buffer_id, inline_value) in inline_values
21367 .into_iter()
21368 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21369 {
21370 buffer_inline_values
21371 .entry(buffer_id)
21372 .or_default()
21373 .push(inline_value);
21374 }
21375
21376 editor
21377 .update(cx, |editor, cx| {
21378 let snapshot = editor.buffer.read(cx).snapshot(cx);
21379 let mut new_inlays = Vec::default();
21380
21381 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21382 let buffer_id = buffer_snapshot.remote_id();
21383 buffer_inline_values
21384 .get(&buffer_id)
21385 .into_iter()
21386 .flatten()
21387 .for_each(|hint| {
21388 let inlay = Inlay::debugger(
21389 post_inc(&mut editor.next_inlay_id),
21390 Anchor::in_buffer(excerpt_id, hint.position),
21391 hint.text(),
21392 );
21393 if !inlay.text().chars().contains(&'\n') {
21394 new_inlays.push(inlay);
21395 }
21396 });
21397 }
21398
21399 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21400 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21401
21402 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21403 })
21404 .ok()?;
21405 Some(())
21406 });
21407 }
21408
21409 fn on_buffer_event(
21410 &mut self,
21411 multibuffer: &Entity<MultiBuffer>,
21412 event: &multi_buffer::Event,
21413 window: &mut Window,
21414 cx: &mut Context<Self>,
21415 ) {
21416 match event {
21417 multi_buffer::Event::Edited { edited_buffer } => {
21418 self.scrollbar_marker_state.dirty = true;
21419 self.active_indent_guides_state.dirty = true;
21420 self.refresh_active_diagnostics(cx);
21421 self.refresh_code_actions(window, cx);
21422 self.refresh_single_line_folds(window, cx);
21423 self.refresh_matching_bracket_highlights(window, cx);
21424 if self.has_active_edit_prediction() {
21425 self.update_visible_edit_prediction(window, cx);
21426 }
21427
21428 if let Some(buffer) = edited_buffer {
21429 if buffer.read(cx).file().is_none() {
21430 cx.emit(EditorEvent::TitleChanged);
21431 }
21432
21433 if self.project.is_some() {
21434 let buffer_id = buffer.read(cx).remote_id();
21435 self.register_buffer(buffer_id, cx);
21436 self.update_lsp_data(Some(buffer_id), window, cx);
21437 self.refresh_inlay_hints(
21438 InlayHintRefreshReason::BufferEdited(buffer_id),
21439 cx,
21440 );
21441 }
21442 }
21443
21444 cx.emit(EditorEvent::BufferEdited);
21445 cx.emit(SearchEvent::MatchesInvalidated);
21446
21447 let Some(project) = &self.project else { return };
21448 let (telemetry, is_via_ssh) = {
21449 let project = project.read(cx);
21450 let telemetry = project.client().telemetry().clone();
21451 let is_via_ssh = project.is_via_remote_server();
21452 (telemetry, is_via_ssh)
21453 };
21454 telemetry.log_edit_event("editor", is_via_ssh);
21455 }
21456 multi_buffer::Event::ExcerptsAdded {
21457 buffer,
21458 predecessor,
21459 excerpts,
21460 } => {
21461 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21462 let buffer_id = buffer.read(cx).remote_id();
21463 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21464 && let Some(project) = &self.project
21465 {
21466 update_uncommitted_diff_for_buffer(
21467 cx.entity(),
21468 project,
21469 [buffer.clone()],
21470 self.buffer.clone(),
21471 cx,
21472 )
21473 .detach();
21474 }
21475 self.update_lsp_data(Some(buffer_id), window, cx);
21476 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21477 self.colorize_brackets(false, cx);
21478 cx.emit(EditorEvent::ExcerptsAdded {
21479 buffer: buffer.clone(),
21480 predecessor: *predecessor,
21481 excerpts: excerpts.clone(),
21482 });
21483 }
21484 multi_buffer::Event::ExcerptsRemoved {
21485 ids,
21486 removed_buffer_ids,
21487 } => {
21488 if let Some(inlay_hints) = &mut self.inlay_hints {
21489 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21490 }
21491 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21492 for buffer_id in removed_buffer_ids {
21493 self.registered_buffers.remove(buffer_id);
21494 }
21495 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21496 cx.emit(EditorEvent::ExcerptsRemoved {
21497 ids: ids.clone(),
21498 removed_buffer_ids: removed_buffer_ids.clone(),
21499 });
21500 }
21501 multi_buffer::Event::ExcerptsEdited {
21502 excerpt_ids,
21503 buffer_ids,
21504 } => {
21505 self.display_map.update(cx, |map, cx| {
21506 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21507 });
21508 cx.emit(EditorEvent::ExcerptsEdited {
21509 ids: excerpt_ids.clone(),
21510 });
21511 }
21512 multi_buffer::Event::ExcerptsExpanded { ids } => {
21513 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21514 self.refresh_document_highlights(cx);
21515 for id in ids {
21516 self.fetched_tree_sitter_chunks.remove(id);
21517 }
21518 self.colorize_brackets(false, cx);
21519 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21520 }
21521 multi_buffer::Event::Reparsed(buffer_id) => {
21522 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21523 self.refresh_selected_text_highlights(true, window, cx);
21524 self.colorize_brackets(true, cx);
21525 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21526
21527 cx.emit(EditorEvent::Reparsed(*buffer_id));
21528 }
21529 multi_buffer::Event::DiffHunksToggled => {
21530 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21531 }
21532 multi_buffer::Event::LanguageChanged(buffer_id) => {
21533 self.registered_buffers.remove(&buffer_id);
21534 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21535 cx.emit(EditorEvent::Reparsed(*buffer_id));
21536 cx.notify();
21537 }
21538 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21539 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21540 multi_buffer::Event::FileHandleChanged
21541 | multi_buffer::Event::Reloaded
21542 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21543 multi_buffer::Event::DiagnosticsUpdated => {
21544 self.update_diagnostics_state(window, cx);
21545 }
21546 _ => {}
21547 };
21548 }
21549
21550 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21551 if !self.diagnostics_enabled() {
21552 return;
21553 }
21554 self.refresh_active_diagnostics(cx);
21555 self.refresh_inline_diagnostics(true, window, cx);
21556 self.scrollbar_marker_state.dirty = true;
21557 cx.notify();
21558 }
21559
21560 pub fn start_temporary_diff_override(&mut self) {
21561 self.load_diff_task.take();
21562 self.temporary_diff_override = true;
21563 }
21564
21565 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21566 self.temporary_diff_override = false;
21567 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21568 self.buffer.update(cx, |buffer, cx| {
21569 buffer.set_all_diff_hunks_collapsed(cx);
21570 });
21571
21572 if let Some(project) = self.project.clone() {
21573 self.load_diff_task = Some(
21574 update_uncommitted_diff_for_buffer(
21575 cx.entity(),
21576 &project,
21577 self.buffer.read(cx).all_buffers(),
21578 self.buffer.clone(),
21579 cx,
21580 )
21581 .shared(),
21582 );
21583 }
21584 }
21585
21586 fn on_display_map_changed(
21587 &mut self,
21588 _: Entity<DisplayMap>,
21589 _: &mut Window,
21590 cx: &mut Context<Self>,
21591 ) {
21592 cx.notify();
21593 }
21594
21595 fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
21596 if !self.mode.is_full() {
21597 return Vec::new();
21598 }
21599
21600 let theme_settings = theme::ThemeSettings::get_global(cx);
21601
21602 theme_settings
21603 .theme_overrides
21604 .get(cx.theme().name.as_ref())
21605 .map(|theme_style| &theme_style.accents)
21606 .into_iter()
21607 .flatten()
21608 .chain(
21609 theme_settings
21610 .experimental_theme_overrides
21611 .as_ref()
21612 .map(|overrides| &overrides.accents)
21613 .into_iter()
21614 .flatten(),
21615 )
21616 .flat_map(|accent| accent.0.clone())
21617 .collect()
21618 }
21619
21620 fn fetch_applicable_language_settings(
21621 &self,
21622 cx: &App,
21623 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21624 if !self.mode.is_full() {
21625 return HashMap::default();
21626 }
21627
21628 self.buffer().read(cx).all_buffers().into_iter().fold(
21629 HashMap::default(),
21630 |mut acc, buffer| {
21631 let buffer = buffer.read(cx);
21632 let language = buffer.language().map(|language| language.name());
21633 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21634 let file = buffer.file();
21635 v.insert(language_settings(language, file, cx).into_owned());
21636 }
21637 acc
21638 },
21639 )
21640 }
21641
21642 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21643 let new_language_settings = self.fetch_applicable_language_settings(cx);
21644 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21645 self.applicable_language_settings = new_language_settings;
21646
21647 let new_accent_overrides = self.fetch_accent_overrides(cx);
21648 let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
21649 self.accent_overrides = new_accent_overrides;
21650
21651 if self.diagnostics_enabled() {
21652 let new_severity = EditorSettings::get_global(cx)
21653 .diagnostics_max_severity
21654 .unwrap_or(DiagnosticSeverity::Hint);
21655 self.set_max_diagnostics_severity(new_severity, cx);
21656 }
21657 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21658 self.update_edit_prediction_settings(cx);
21659 self.refresh_edit_prediction(true, false, window, cx);
21660 self.refresh_inline_values(cx);
21661 self.refresh_inlay_hints(
21662 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21663 self.selections.newest_anchor().head(),
21664 &self.buffer.read(cx).snapshot(cx),
21665 cx,
21666 )),
21667 cx,
21668 );
21669
21670 let old_cursor_shape = self.cursor_shape;
21671 let old_show_breadcrumbs = self.show_breadcrumbs;
21672
21673 {
21674 let editor_settings = EditorSettings::get_global(cx);
21675 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21676 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21677 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21678 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21679 }
21680
21681 if old_cursor_shape != self.cursor_shape {
21682 cx.emit(EditorEvent::CursorShapeChanged);
21683 }
21684
21685 if old_show_breadcrumbs != self.show_breadcrumbs {
21686 cx.emit(EditorEvent::BreadcrumbsChanged);
21687 }
21688
21689 let project_settings = ProjectSettings::get_global(cx);
21690 self.buffer_serialization = self
21691 .should_serialize_buffer()
21692 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21693
21694 if self.mode.is_full() {
21695 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21696 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21697 if self.show_inline_diagnostics != show_inline_diagnostics {
21698 self.show_inline_diagnostics = show_inline_diagnostics;
21699 self.refresh_inline_diagnostics(false, window, cx);
21700 }
21701
21702 if self.git_blame_inline_enabled != inline_blame_enabled {
21703 self.toggle_git_blame_inline_internal(false, window, cx);
21704 }
21705
21706 let minimap_settings = EditorSettings::get_global(cx).minimap;
21707 if self.minimap_visibility != MinimapVisibility::Disabled {
21708 if self.minimap_visibility.settings_visibility()
21709 != minimap_settings.minimap_enabled()
21710 {
21711 self.set_minimap_visibility(
21712 MinimapVisibility::for_mode(self.mode(), cx),
21713 window,
21714 cx,
21715 );
21716 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21717 minimap_entity.update(cx, |minimap_editor, cx| {
21718 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21719 })
21720 }
21721 }
21722
21723 if language_settings_changed || accent_overrides_changed {
21724 self.colorize_brackets(true, cx);
21725 }
21726
21727 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21728 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21729 }) {
21730 if !inlay_splice.is_empty() {
21731 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21732 }
21733 self.refresh_colors_for_visible_range(None, window, cx);
21734 }
21735 }
21736
21737 cx.notify();
21738 }
21739
21740 pub fn set_searchable(&mut self, searchable: bool) {
21741 self.searchable = searchable;
21742 }
21743
21744 pub fn searchable(&self) -> bool {
21745 self.searchable
21746 }
21747
21748 pub fn open_excerpts_in_split(
21749 &mut self,
21750 _: &OpenExcerptsSplit,
21751 window: &mut Window,
21752 cx: &mut Context<Self>,
21753 ) {
21754 self.open_excerpts_common(None, true, window, cx)
21755 }
21756
21757 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21758 self.open_excerpts_common(None, false, window, cx)
21759 }
21760
21761 fn open_excerpts_common(
21762 &mut self,
21763 jump_data: Option<JumpData>,
21764 split: bool,
21765 window: &mut Window,
21766 cx: &mut Context<Self>,
21767 ) {
21768 let Some(workspace) = self.workspace() else {
21769 cx.propagate();
21770 return;
21771 };
21772
21773 if self.buffer.read(cx).is_singleton() {
21774 cx.propagate();
21775 return;
21776 }
21777
21778 let mut new_selections_by_buffer = HashMap::default();
21779 match &jump_data {
21780 Some(JumpData::MultiBufferPoint {
21781 excerpt_id,
21782 position,
21783 anchor,
21784 line_offset_from_top,
21785 }) => {
21786 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21787 if let Some(buffer) = multi_buffer_snapshot
21788 .buffer_id_for_excerpt(*excerpt_id)
21789 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21790 {
21791 let buffer_snapshot = buffer.read(cx).snapshot();
21792 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21793 language::ToPoint::to_point(anchor, &buffer_snapshot)
21794 } else {
21795 buffer_snapshot.clip_point(*position, Bias::Left)
21796 };
21797 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21798 new_selections_by_buffer.insert(
21799 buffer,
21800 (
21801 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21802 Some(*line_offset_from_top),
21803 ),
21804 );
21805 }
21806 }
21807 Some(JumpData::MultiBufferRow {
21808 row,
21809 line_offset_from_top,
21810 }) => {
21811 let point = MultiBufferPoint::new(row.0, 0);
21812 if let Some((buffer, buffer_point, _)) =
21813 self.buffer.read(cx).point_to_buffer_point(point, cx)
21814 {
21815 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21816 new_selections_by_buffer
21817 .entry(buffer)
21818 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21819 .0
21820 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21821 }
21822 }
21823 None => {
21824 let selections = self
21825 .selections
21826 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21827 let multi_buffer = self.buffer.read(cx);
21828 for selection in selections {
21829 for (snapshot, range, _, anchor) in multi_buffer
21830 .snapshot(cx)
21831 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21832 {
21833 if let Some(anchor) = anchor {
21834 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21835 else {
21836 continue;
21837 };
21838 let offset = text::ToOffset::to_offset(
21839 &anchor.text_anchor,
21840 &buffer_handle.read(cx).snapshot(),
21841 );
21842 let range = BufferOffset(offset)..BufferOffset(offset);
21843 new_selections_by_buffer
21844 .entry(buffer_handle)
21845 .or_insert((Vec::new(), None))
21846 .0
21847 .push(range)
21848 } else {
21849 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21850 else {
21851 continue;
21852 };
21853 new_selections_by_buffer
21854 .entry(buffer_handle)
21855 .or_insert((Vec::new(), None))
21856 .0
21857 .push(range)
21858 }
21859 }
21860 }
21861 }
21862 }
21863
21864 new_selections_by_buffer
21865 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21866
21867 if new_selections_by_buffer.is_empty() {
21868 return;
21869 }
21870
21871 // We defer the pane interaction because we ourselves are a workspace item
21872 // and activating a new item causes the pane to call a method on us reentrantly,
21873 // which panics if we're on the stack.
21874 window.defer(cx, move |window, cx| {
21875 workspace.update(cx, |workspace, cx| {
21876 let pane = if split {
21877 workspace.adjacent_pane(window, cx)
21878 } else {
21879 workspace.active_pane().clone()
21880 };
21881
21882 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21883 let editor = buffer
21884 .read(cx)
21885 .file()
21886 .is_none()
21887 .then(|| {
21888 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21889 // so `workspace.open_project_item` will never find them, always opening a new editor.
21890 // Instead, we try to activate the existing editor in the pane first.
21891 let (editor, pane_item_index) =
21892 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21893 let editor = item.downcast::<Editor>()?;
21894 let singleton_buffer =
21895 editor.read(cx).buffer().read(cx).as_singleton()?;
21896 if singleton_buffer == buffer {
21897 Some((editor, i))
21898 } else {
21899 None
21900 }
21901 })?;
21902 pane.update(cx, |pane, cx| {
21903 pane.activate_item(pane_item_index, true, true, window, cx)
21904 });
21905 Some(editor)
21906 })
21907 .flatten()
21908 .unwrap_or_else(|| {
21909 workspace.open_project_item::<Self>(
21910 pane.clone(),
21911 buffer,
21912 true,
21913 true,
21914 window,
21915 cx,
21916 )
21917 });
21918
21919 editor.update(cx, |editor, cx| {
21920 let autoscroll = match scroll_offset {
21921 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21922 None => Autoscroll::newest(),
21923 };
21924 let nav_history = editor.nav_history.take();
21925 editor.change_selections(
21926 SelectionEffects::scroll(autoscroll),
21927 window,
21928 cx,
21929 |s| {
21930 s.select_ranges(ranges.into_iter().map(|range| {
21931 // we checked that the editor is a singleton editor so the offsets are valid
21932 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21933 }));
21934 },
21935 );
21936 editor.nav_history = nav_history;
21937 });
21938 }
21939 })
21940 });
21941 }
21942
21943 // For now, don't allow opening excerpts in buffers that aren't backed by
21944 // regular project files.
21945 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21946 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21947 }
21948
21949 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
21950 let snapshot = self.buffer.read(cx).read(cx);
21951 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21952 Some(
21953 ranges
21954 .iter()
21955 .map(move |range| {
21956 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21957 })
21958 .collect(),
21959 )
21960 }
21961
21962 fn selection_replacement_ranges(
21963 &self,
21964 range: Range<MultiBufferOffsetUtf16>,
21965 cx: &mut App,
21966 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
21967 let selections = self
21968 .selections
21969 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
21970 let newest_selection = selections
21971 .iter()
21972 .max_by_key(|selection| selection.id)
21973 .unwrap();
21974 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
21975 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
21976 let snapshot = self.buffer.read(cx).read(cx);
21977 selections
21978 .into_iter()
21979 .map(|mut selection| {
21980 selection.start.0.0 =
21981 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
21982 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
21983 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21984 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21985 })
21986 .collect()
21987 }
21988
21989 fn report_editor_event(
21990 &self,
21991 reported_event: ReportEditorEvent,
21992 file_extension: Option<String>,
21993 cx: &App,
21994 ) {
21995 if cfg!(any(test, feature = "test-support")) {
21996 return;
21997 }
21998
21999 let Some(project) = &self.project else { return };
22000
22001 // If None, we are in a file without an extension
22002 let file = self
22003 .buffer
22004 .read(cx)
22005 .as_singleton()
22006 .and_then(|b| b.read(cx).file());
22007 let file_extension = file_extension.or(file
22008 .as_ref()
22009 .and_then(|file| Path::new(file.file_name(cx)).extension())
22010 .and_then(|e| e.to_str())
22011 .map(|a| a.to_string()));
22012
22013 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22014 .map(|vim_mode| vim_mode.0)
22015 .unwrap_or(false);
22016
22017 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22018 let copilot_enabled = edit_predictions_provider
22019 == language::language_settings::EditPredictionProvider::Copilot;
22020 let copilot_enabled_for_language = self
22021 .buffer
22022 .read(cx)
22023 .language_settings(cx)
22024 .show_edit_predictions;
22025
22026 let project = project.read(cx);
22027 let event_type = reported_event.event_type();
22028
22029 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22030 telemetry::event!(
22031 event_type,
22032 type = if auto_saved {"autosave"} else {"manual"},
22033 file_extension,
22034 vim_mode,
22035 copilot_enabled,
22036 copilot_enabled_for_language,
22037 edit_predictions_provider,
22038 is_via_ssh = project.is_via_remote_server(),
22039 );
22040 } else {
22041 telemetry::event!(
22042 event_type,
22043 file_extension,
22044 vim_mode,
22045 copilot_enabled,
22046 copilot_enabled_for_language,
22047 edit_predictions_provider,
22048 is_via_ssh = project.is_via_remote_server(),
22049 );
22050 };
22051 }
22052
22053 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22054 /// with each line being an array of {text, highlight} objects.
22055 fn copy_highlight_json(
22056 &mut self,
22057 _: &CopyHighlightJson,
22058 window: &mut Window,
22059 cx: &mut Context<Self>,
22060 ) {
22061 #[derive(Serialize)]
22062 struct Chunk<'a> {
22063 text: String,
22064 highlight: Option<&'a str>,
22065 }
22066
22067 let snapshot = self.buffer.read(cx).snapshot(cx);
22068 let range = self
22069 .selected_text_range(false, window, cx)
22070 .and_then(|selection| {
22071 if selection.range.is_empty() {
22072 None
22073 } else {
22074 Some(
22075 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22076 selection.range.start,
22077 )))
22078 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22079 selection.range.end,
22080 ))),
22081 )
22082 }
22083 })
22084 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22085
22086 let chunks = snapshot.chunks(range, true);
22087 let mut lines = Vec::new();
22088 let mut line: VecDeque<Chunk> = VecDeque::new();
22089
22090 let Some(style) = self.style.as_ref() else {
22091 return;
22092 };
22093
22094 for chunk in chunks {
22095 let highlight = chunk
22096 .syntax_highlight_id
22097 .and_then(|id| id.name(&style.syntax));
22098 let mut chunk_lines = chunk.text.split('\n').peekable();
22099 while let Some(text) = chunk_lines.next() {
22100 let mut merged_with_last_token = false;
22101 if let Some(last_token) = line.back_mut()
22102 && last_token.highlight == highlight
22103 {
22104 last_token.text.push_str(text);
22105 merged_with_last_token = true;
22106 }
22107
22108 if !merged_with_last_token {
22109 line.push_back(Chunk {
22110 text: text.into(),
22111 highlight,
22112 });
22113 }
22114
22115 if chunk_lines.peek().is_some() {
22116 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22117 line.pop_front();
22118 }
22119 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22120 line.pop_back();
22121 }
22122
22123 lines.push(mem::take(&mut line));
22124 }
22125 }
22126 }
22127
22128 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22129 return;
22130 };
22131 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22132 }
22133
22134 pub fn open_context_menu(
22135 &mut self,
22136 _: &OpenContextMenu,
22137 window: &mut Window,
22138 cx: &mut Context<Self>,
22139 ) {
22140 self.request_autoscroll(Autoscroll::newest(), cx);
22141 let position = self
22142 .selections
22143 .newest_display(&self.display_snapshot(cx))
22144 .start;
22145 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22146 }
22147
22148 pub fn replay_insert_event(
22149 &mut self,
22150 text: &str,
22151 relative_utf16_range: Option<Range<isize>>,
22152 window: &mut Window,
22153 cx: &mut Context<Self>,
22154 ) {
22155 if !self.input_enabled {
22156 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22157 return;
22158 }
22159 if let Some(relative_utf16_range) = relative_utf16_range {
22160 let selections = self
22161 .selections
22162 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22163 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22164 let new_ranges = selections.into_iter().map(|range| {
22165 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22166 range
22167 .head()
22168 .0
22169 .0
22170 .saturating_add_signed(relative_utf16_range.start),
22171 ));
22172 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22173 range
22174 .head()
22175 .0
22176 .0
22177 .saturating_add_signed(relative_utf16_range.end),
22178 ));
22179 start..end
22180 });
22181 s.select_ranges(new_ranges);
22182 });
22183 }
22184
22185 self.handle_input(text, window, cx);
22186 }
22187
22188 pub fn is_focused(&self, window: &Window) -> bool {
22189 self.focus_handle.is_focused(window)
22190 }
22191
22192 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22193 cx.emit(EditorEvent::Focused);
22194
22195 if let Some(descendant) = self
22196 .last_focused_descendant
22197 .take()
22198 .and_then(|descendant| descendant.upgrade())
22199 {
22200 window.focus(&descendant);
22201 } else {
22202 if let Some(blame) = self.blame.as_ref() {
22203 blame.update(cx, GitBlame::focus)
22204 }
22205
22206 self.blink_manager.update(cx, BlinkManager::enable);
22207 self.show_cursor_names(window, cx);
22208 self.buffer.update(cx, |buffer, cx| {
22209 buffer.finalize_last_transaction(cx);
22210 if self.leader_id.is_none() {
22211 buffer.set_active_selections(
22212 &self.selections.disjoint_anchors_arc(),
22213 self.selections.line_mode(),
22214 self.cursor_shape,
22215 cx,
22216 );
22217 }
22218 });
22219
22220 if let Some(position_map) = self.last_position_map.clone() {
22221 EditorElement::mouse_moved(
22222 self,
22223 &MouseMoveEvent {
22224 position: window.mouse_position(),
22225 pressed_button: None,
22226 modifiers: window.modifiers(),
22227 },
22228 &position_map,
22229 window,
22230 cx,
22231 );
22232 }
22233 }
22234 }
22235
22236 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22237 cx.emit(EditorEvent::FocusedIn)
22238 }
22239
22240 fn handle_focus_out(
22241 &mut self,
22242 event: FocusOutEvent,
22243 _window: &mut Window,
22244 cx: &mut Context<Self>,
22245 ) {
22246 if event.blurred != self.focus_handle {
22247 self.last_focused_descendant = Some(event.blurred);
22248 }
22249 self.selection_drag_state = SelectionDragState::None;
22250 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22251 }
22252
22253 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22254 self.blink_manager.update(cx, BlinkManager::disable);
22255 self.buffer
22256 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22257
22258 if let Some(blame) = self.blame.as_ref() {
22259 blame.update(cx, GitBlame::blur)
22260 }
22261 if !self.hover_state.focused(window, cx) {
22262 hide_hover(self, cx);
22263 }
22264 if !self
22265 .context_menu
22266 .borrow()
22267 .as_ref()
22268 .is_some_and(|context_menu| context_menu.focused(window, cx))
22269 {
22270 self.hide_context_menu(window, cx);
22271 }
22272 self.take_active_edit_prediction(cx);
22273 cx.emit(EditorEvent::Blurred);
22274 cx.notify();
22275 }
22276
22277 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22278 let mut pending: String = window
22279 .pending_input_keystrokes()
22280 .into_iter()
22281 .flatten()
22282 .filter_map(|keystroke| keystroke.key_char.clone())
22283 .collect();
22284
22285 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22286 pending = "".to_string();
22287 }
22288
22289 let existing_pending = self
22290 .text_highlights::<PendingInput>(cx)
22291 .map(|(_, ranges)| ranges.to_vec());
22292 if existing_pending.is_none() && pending.is_empty() {
22293 return;
22294 }
22295 let transaction =
22296 self.transact(window, cx, |this, window, cx| {
22297 let selections = this
22298 .selections
22299 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22300 let edits = selections
22301 .iter()
22302 .map(|selection| (selection.end..selection.end, pending.clone()));
22303 this.edit(edits, cx);
22304 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22305 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22306 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22307 }));
22308 });
22309 if let Some(existing_ranges) = existing_pending {
22310 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22311 this.edit(edits, cx);
22312 }
22313 });
22314
22315 let snapshot = self.snapshot(window, cx);
22316 let ranges = self
22317 .selections
22318 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22319 .into_iter()
22320 .map(|selection| {
22321 snapshot.buffer_snapshot().anchor_after(selection.end)
22322 ..snapshot
22323 .buffer_snapshot()
22324 .anchor_before(selection.end + pending.len())
22325 })
22326 .collect();
22327
22328 if pending.is_empty() {
22329 self.clear_highlights::<PendingInput>(cx);
22330 } else {
22331 self.highlight_text::<PendingInput>(
22332 ranges,
22333 HighlightStyle {
22334 underline: Some(UnderlineStyle {
22335 thickness: px(1.),
22336 color: None,
22337 wavy: false,
22338 }),
22339 ..Default::default()
22340 },
22341 cx,
22342 );
22343 }
22344
22345 self.ime_transaction = self.ime_transaction.or(transaction);
22346 if let Some(transaction) = self.ime_transaction {
22347 self.buffer.update(cx, |buffer, cx| {
22348 buffer.group_until_transaction(transaction, cx);
22349 });
22350 }
22351
22352 if self.text_highlights::<PendingInput>(cx).is_none() {
22353 self.ime_transaction.take();
22354 }
22355 }
22356
22357 pub fn register_action_renderer(
22358 &mut self,
22359 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22360 ) -> Subscription {
22361 let id = self.next_editor_action_id.post_inc();
22362 self.editor_actions
22363 .borrow_mut()
22364 .insert(id, Box::new(listener));
22365
22366 let editor_actions = self.editor_actions.clone();
22367 Subscription::new(move || {
22368 editor_actions.borrow_mut().remove(&id);
22369 })
22370 }
22371
22372 pub fn register_action<A: Action>(
22373 &mut self,
22374 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22375 ) -> Subscription {
22376 let id = self.next_editor_action_id.post_inc();
22377 let listener = Arc::new(listener);
22378 self.editor_actions.borrow_mut().insert(
22379 id,
22380 Box::new(move |_, window, _| {
22381 let listener = listener.clone();
22382 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22383 let action = action.downcast_ref().unwrap();
22384 if phase == DispatchPhase::Bubble {
22385 listener(action, window, cx)
22386 }
22387 })
22388 }),
22389 );
22390
22391 let editor_actions = self.editor_actions.clone();
22392 Subscription::new(move || {
22393 editor_actions.borrow_mut().remove(&id);
22394 })
22395 }
22396
22397 pub fn file_header_size(&self) -> u32 {
22398 FILE_HEADER_HEIGHT
22399 }
22400
22401 pub fn restore(
22402 &mut self,
22403 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22404 window: &mut Window,
22405 cx: &mut Context<Self>,
22406 ) {
22407 let workspace = self.workspace();
22408 let project = self.project();
22409 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22410 let mut tasks = Vec::new();
22411 for (buffer_id, changes) in revert_changes {
22412 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22413 buffer.update(cx, |buffer, cx| {
22414 buffer.edit(
22415 changes
22416 .into_iter()
22417 .map(|(range, text)| (range, text.to_string())),
22418 None,
22419 cx,
22420 );
22421 });
22422
22423 if let Some(project) =
22424 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22425 {
22426 project.update(cx, |project, cx| {
22427 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22428 })
22429 }
22430 }
22431 }
22432 tasks
22433 });
22434 cx.spawn_in(window, async move |_, cx| {
22435 for (buffer, task) in save_tasks {
22436 let result = task.await;
22437 if result.is_err() {
22438 let Some(path) = buffer
22439 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22440 .ok()
22441 else {
22442 continue;
22443 };
22444 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22445 let Some(task) = cx
22446 .update_window_entity(workspace, |workspace, window, cx| {
22447 workspace
22448 .open_path_preview(path, None, false, false, false, window, cx)
22449 })
22450 .ok()
22451 else {
22452 continue;
22453 };
22454 task.await.log_err();
22455 }
22456 }
22457 }
22458 })
22459 .detach();
22460 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22461 selections.refresh()
22462 });
22463 }
22464
22465 pub fn to_pixel_point(
22466 &self,
22467 source: multi_buffer::Anchor,
22468 editor_snapshot: &EditorSnapshot,
22469 window: &mut Window,
22470 ) -> Option<gpui::Point<Pixels>> {
22471 let source_point = source.to_display_point(editor_snapshot);
22472 self.display_to_pixel_point(source_point, editor_snapshot, window)
22473 }
22474
22475 pub fn display_to_pixel_point(
22476 &self,
22477 source: DisplayPoint,
22478 editor_snapshot: &EditorSnapshot,
22479 window: &mut Window,
22480 ) -> Option<gpui::Point<Pixels>> {
22481 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22482 let text_layout_details = self.text_layout_details(window);
22483 let scroll_top = text_layout_details
22484 .scroll_anchor
22485 .scroll_position(editor_snapshot)
22486 .y;
22487
22488 if source.row().as_f64() < scroll_top.floor() {
22489 return None;
22490 }
22491 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22492 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22493 Some(gpui::Point::new(source_x, source_y))
22494 }
22495
22496 pub fn has_visible_completions_menu(&self) -> bool {
22497 !self.edit_prediction_preview_is_active()
22498 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22499 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22500 })
22501 }
22502
22503 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22504 if self.mode.is_minimap() {
22505 return;
22506 }
22507 self.addons
22508 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22509 }
22510
22511 pub fn unregister_addon<T: Addon>(&mut self) {
22512 self.addons.remove(&std::any::TypeId::of::<T>());
22513 }
22514
22515 pub fn addon<T: Addon>(&self) -> Option<&T> {
22516 let type_id = std::any::TypeId::of::<T>();
22517 self.addons
22518 .get(&type_id)
22519 .and_then(|item| item.to_any().downcast_ref::<T>())
22520 }
22521
22522 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22523 let type_id = std::any::TypeId::of::<T>();
22524 self.addons
22525 .get_mut(&type_id)
22526 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22527 }
22528
22529 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22530 let text_layout_details = self.text_layout_details(window);
22531 let style = &text_layout_details.editor_style;
22532 let font_id = window.text_system().resolve_font(&style.text.font());
22533 let font_size = style.text.font_size.to_pixels(window.rem_size());
22534 let line_height = style.text.line_height_in_pixels(window.rem_size());
22535 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22536 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22537
22538 CharacterDimensions {
22539 em_width,
22540 em_advance,
22541 line_height,
22542 }
22543 }
22544
22545 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22546 self.load_diff_task.clone()
22547 }
22548
22549 fn read_metadata_from_db(
22550 &mut self,
22551 item_id: u64,
22552 workspace_id: WorkspaceId,
22553 window: &mut Window,
22554 cx: &mut Context<Editor>,
22555 ) {
22556 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22557 && !self.mode.is_minimap()
22558 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22559 {
22560 let buffer_snapshot = OnceCell::new();
22561
22562 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22563 && !folds.is_empty()
22564 {
22565 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22566 self.fold_ranges(
22567 folds
22568 .into_iter()
22569 .map(|(start, end)| {
22570 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22571 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22572 })
22573 .collect(),
22574 false,
22575 window,
22576 cx,
22577 );
22578 }
22579
22580 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22581 && !selections.is_empty()
22582 {
22583 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22584 // skip adding the initial selection to selection history
22585 self.selection_history.mode = SelectionHistoryMode::Skipping;
22586 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22587 s.select_ranges(selections.into_iter().map(|(start, end)| {
22588 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22589 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22590 }));
22591 });
22592 self.selection_history.mode = SelectionHistoryMode::Normal;
22593 };
22594 }
22595
22596 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22597 }
22598
22599 fn update_lsp_data(
22600 &mut self,
22601 for_buffer: Option<BufferId>,
22602 window: &mut Window,
22603 cx: &mut Context<'_, Self>,
22604 ) {
22605 self.pull_diagnostics(for_buffer, window, cx);
22606 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22607 }
22608
22609 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22610 if self.ignore_lsp_data() {
22611 return;
22612 }
22613 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
22614 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22615 }
22616 }
22617
22618 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22619 if self.ignore_lsp_data() {
22620 return;
22621 }
22622
22623 if !self.registered_buffers.contains_key(&buffer_id)
22624 && let Some(project) = self.project.as_ref()
22625 {
22626 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22627 project.update(cx, |project, cx| {
22628 self.registered_buffers.insert(
22629 buffer_id,
22630 project.register_buffer_with_language_servers(&buffer, cx),
22631 );
22632 });
22633 } else {
22634 self.registered_buffers.remove(&buffer_id);
22635 }
22636 }
22637 }
22638
22639 fn ignore_lsp_data(&self) -> bool {
22640 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22641 // skip any LSP updates for it.
22642 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22643 }
22644}
22645
22646fn edit_for_markdown_paste<'a>(
22647 buffer: &MultiBufferSnapshot,
22648 range: Range<MultiBufferOffset>,
22649 to_insert: &'a str,
22650 url: Option<url::Url>,
22651) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22652 if url.is_none() {
22653 return (range, Cow::Borrowed(to_insert));
22654 };
22655
22656 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22657
22658 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22659 Cow::Borrowed(to_insert)
22660 } else {
22661 Cow::Owned(format!("[{old_text}]({to_insert})"))
22662 };
22663 (range, new_text)
22664}
22665
22666fn process_completion_for_edit(
22667 completion: &Completion,
22668 intent: CompletionIntent,
22669 buffer: &Entity<Buffer>,
22670 cursor_position: &text::Anchor,
22671 cx: &mut Context<Editor>,
22672) -> CompletionEdit {
22673 let buffer = buffer.read(cx);
22674 let buffer_snapshot = buffer.snapshot();
22675 let (snippet, new_text) = if completion.is_snippet() {
22676 let mut snippet_source = completion.new_text.clone();
22677 // Workaround for typescript language server issues so that methods don't expand within
22678 // strings and functions with type expressions. The previous point is used because the query
22679 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22680 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22681 let previous_point = if previous_point.column > 0 {
22682 cursor_position.to_previous_offset(&buffer_snapshot)
22683 } else {
22684 cursor_position.to_offset(&buffer_snapshot)
22685 };
22686 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22687 && scope.prefers_label_for_snippet_in_completion()
22688 && let Some(label) = completion.label()
22689 && matches!(
22690 completion.kind(),
22691 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22692 )
22693 {
22694 snippet_source = label;
22695 }
22696 match Snippet::parse(&snippet_source).log_err() {
22697 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22698 None => (None, completion.new_text.clone()),
22699 }
22700 } else {
22701 (None, completion.new_text.clone())
22702 };
22703
22704 let mut range_to_replace = {
22705 let replace_range = &completion.replace_range;
22706 if let CompletionSource::Lsp {
22707 insert_range: Some(insert_range),
22708 ..
22709 } = &completion.source
22710 {
22711 debug_assert_eq!(
22712 insert_range.start, replace_range.start,
22713 "insert_range and replace_range should start at the same position"
22714 );
22715 debug_assert!(
22716 insert_range
22717 .start
22718 .cmp(cursor_position, &buffer_snapshot)
22719 .is_le(),
22720 "insert_range should start before or at cursor position"
22721 );
22722 debug_assert!(
22723 replace_range
22724 .start
22725 .cmp(cursor_position, &buffer_snapshot)
22726 .is_le(),
22727 "replace_range should start before or at cursor position"
22728 );
22729
22730 let should_replace = match intent {
22731 CompletionIntent::CompleteWithInsert => false,
22732 CompletionIntent::CompleteWithReplace => true,
22733 CompletionIntent::Complete | CompletionIntent::Compose => {
22734 let insert_mode =
22735 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22736 .completions
22737 .lsp_insert_mode;
22738 match insert_mode {
22739 LspInsertMode::Insert => false,
22740 LspInsertMode::Replace => true,
22741 LspInsertMode::ReplaceSubsequence => {
22742 let mut text_to_replace = buffer.chars_for_range(
22743 buffer.anchor_before(replace_range.start)
22744 ..buffer.anchor_after(replace_range.end),
22745 );
22746 let mut current_needle = text_to_replace.next();
22747 for haystack_ch in completion.label.text.chars() {
22748 if let Some(needle_ch) = current_needle
22749 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22750 {
22751 current_needle = text_to_replace.next();
22752 }
22753 }
22754 current_needle.is_none()
22755 }
22756 LspInsertMode::ReplaceSuffix => {
22757 if replace_range
22758 .end
22759 .cmp(cursor_position, &buffer_snapshot)
22760 .is_gt()
22761 {
22762 let range_after_cursor = *cursor_position..replace_range.end;
22763 let text_after_cursor = buffer
22764 .text_for_range(
22765 buffer.anchor_before(range_after_cursor.start)
22766 ..buffer.anchor_after(range_after_cursor.end),
22767 )
22768 .collect::<String>()
22769 .to_ascii_lowercase();
22770 completion
22771 .label
22772 .text
22773 .to_ascii_lowercase()
22774 .ends_with(&text_after_cursor)
22775 } else {
22776 true
22777 }
22778 }
22779 }
22780 }
22781 };
22782
22783 if should_replace {
22784 replace_range.clone()
22785 } else {
22786 insert_range.clone()
22787 }
22788 } else {
22789 replace_range.clone()
22790 }
22791 };
22792
22793 if range_to_replace
22794 .end
22795 .cmp(cursor_position, &buffer_snapshot)
22796 .is_lt()
22797 {
22798 range_to_replace.end = *cursor_position;
22799 }
22800
22801 let replace_range = range_to_replace.to_offset(buffer);
22802 CompletionEdit {
22803 new_text,
22804 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22805 snippet,
22806 }
22807}
22808
22809struct CompletionEdit {
22810 new_text: String,
22811 replace_range: Range<BufferOffset>,
22812 snippet: Option<Snippet>,
22813}
22814
22815fn insert_extra_newline_brackets(
22816 buffer: &MultiBufferSnapshot,
22817 range: Range<MultiBufferOffset>,
22818 language: &language::LanguageScope,
22819) -> bool {
22820 let leading_whitespace_len = buffer
22821 .reversed_chars_at(range.start)
22822 .take_while(|c| c.is_whitespace() && *c != '\n')
22823 .map(|c| c.len_utf8())
22824 .sum::<usize>();
22825 let trailing_whitespace_len = buffer
22826 .chars_at(range.end)
22827 .take_while(|c| c.is_whitespace() && *c != '\n')
22828 .map(|c| c.len_utf8())
22829 .sum::<usize>();
22830 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22831
22832 language.brackets().any(|(pair, enabled)| {
22833 let pair_start = pair.start.trim_end();
22834 let pair_end = pair.end.trim_start();
22835
22836 enabled
22837 && pair.newline
22838 && buffer.contains_str_at(range.end, pair_end)
22839 && buffer.contains_str_at(
22840 range.start.saturating_sub_usize(pair_start.len()),
22841 pair_start,
22842 )
22843 })
22844}
22845
22846fn insert_extra_newline_tree_sitter(
22847 buffer: &MultiBufferSnapshot,
22848 range: Range<MultiBufferOffset>,
22849) -> bool {
22850 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22851 [(buffer, range, _)] => (*buffer, range.clone()),
22852 _ => return false,
22853 };
22854 let pair = {
22855 let mut result: Option<BracketMatch<usize>> = None;
22856
22857 for pair in buffer
22858 .all_bracket_ranges(range.start.0..range.end.0)
22859 .filter(move |pair| {
22860 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22861 })
22862 {
22863 let len = pair.close_range.end - pair.open_range.start;
22864
22865 if let Some(existing) = &result {
22866 let existing_len = existing.close_range.end - existing.open_range.start;
22867 if len > existing_len {
22868 continue;
22869 }
22870 }
22871
22872 result = Some(pair);
22873 }
22874
22875 result
22876 };
22877 let Some(pair) = pair else {
22878 return false;
22879 };
22880 pair.newline_only
22881 && buffer
22882 .chars_for_range(pair.open_range.end..range.start.0)
22883 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22884 .all(|c| c.is_whitespace() && c != '\n')
22885}
22886
22887fn update_uncommitted_diff_for_buffer(
22888 editor: Entity<Editor>,
22889 project: &Entity<Project>,
22890 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22891 buffer: Entity<MultiBuffer>,
22892 cx: &mut App,
22893) -> Task<()> {
22894 let mut tasks = Vec::new();
22895 project.update(cx, |project, cx| {
22896 for buffer in buffers {
22897 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22898 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22899 }
22900 }
22901 });
22902 cx.spawn(async move |cx| {
22903 let diffs = future::join_all(tasks).await;
22904 if editor
22905 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22906 .unwrap_or(false)
22907 {
22908 return;
22909 }
22910
22911 buffer
22912 .update(cx, |buffer, cx| {
22913 for diff in diffs.into_iter().flatten() {
22914 buffer.add_diff(diff, cx);
22915 }
22916 })
22917 .ok();
22918 })
22919}
22920
22921fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22922 let tab_size = tab_size.get() as usize;
22923 let mut width = offset;
22924
22925 for ch in text.chars() {
22926 width += if ch == '\t' {
22927 tab_size - (width % tab_size)
22928 } else {
22929 1
22930 };
22931 }
22932
22933 width - offset
22934}
22935
22936#[cfg(test)]
22937mod tests {
22938 use super::*;
22939
22940 #[test]
22941 fn test_string_size_with_expanded_tabs() {
22942 let nz = |val| NonZeroU32::new(val).unwrap();
22943 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22944 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22945 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22946 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22947 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22948 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22949 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22950 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22951 }
22952}
22953
22954/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22955struct WordBreakingTokenizer<'a> {
22956 input: &'a str,
22957}
22958
22959impl<'a> WordBreakingTokenizer<'a> {
22960 fn new(input: &'a str) -> Self {
22961 Self { input }
22962 }
22963}
22964
22965fn is_char_ideographic(ch: char) -> bool {
22966 use unicode_script::Script::*;
22967 use unicode_script::UnicodeScript;
22968 matches!(ch.script(), Han | Tangut | Yi)
22969}
22970
22971fn is_grapheme_ideographic(text: &str) -> bool {
22972 text.chars().any(is_char_ideographic)
22973}
22974
22975fn is_grapheme_whitespace(text: &str) -> bool {
22976 text.chars().any(|x| x.is_whitespace())
22977}
22978
22979fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22980 text.chars()
22981 .next()
22982 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22983}
22984
22985#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22986enum WordBreakToken<'a> {
22987 Word { token: &'a str, grapheme_len: usize },
22988 InlineWhitespace { token: &'a str, grapheme_len: usize },
22989 Newline,
22990}
22991
22992impl<'a> Iterator for WordBreakingTokenizer<'a> {
22993 /// Yields a span, the count of graphemes in the token, and whether it was
22994 /// whitespace. Note that it also breaks at word boundaries.
22995 type Item = WordBreakToken<'a>;
22996
22997 fn next(&mut self) -> Option<Self::Item> {
22998 use unicode_segmentation::UnicodeSegmentation;
22999 if self.input.is_empty() {
23000 return None;
23001 }
23002
23003 let mut iter = self.input.graphemes(true).peekable();
23004 let mut offset = 0;
23005 let mut grapheme_len = 0;
23006 if let Some(first_grapheme) = iter.next() {
23007 let is_newline = first_grapheme == "\n";
23008 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23009 offset += first_grapheme.len();
23010 grapheme_len += 1;
23011 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23012 if let Some(grapheme) = iter.peek().copied()
23013 && should_stay_with_preceding_ideograph(grapheme)
23014 {
23015 offset += grapheme.len();
23016 grapheme_len += 1;
23017 }
23018 } else {
23019 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23020 let mut next_word_bound = words.peek().copied();
23021 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23022 next_word_bound = words.next();
23023 }
23024 while let Some(grapheme) = iter.peek().copied() {
23025 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23026 break;
23027 };
23028 if is_grapheme_whitespace(grapheme) != is_whitespace
23029 || (grapheme == "\n") != is_newline
23030 {
23031 break;
23032 };
23033 offset += grapheme.len();
23034 grapheme_len += 1;
23035 iter.next();
23036 }
23037 }
23038 let token = &self.input[..offset];
23039 self.input = &self.input[offset..];
23040 if token == "\n" {
23041 Some(WordBreakToken::Newline)
23042 } else if is_whitespace {
23043 Some(WordBreakToken::InlineWhitespace {
23044 token,
23045 grapheme_len,
23046 })
23047 } else {
23048 Some(WordBreakToken::Word {
23049 token,
23050 grapheme_len,
23051 })
23052 }
23053 } else {
23054 None
23055 }
23056 }
23057}
23058
23059#[test]
23060fn test_word_breaking_tokenizer() {
23061 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23062 ("", &[]),
23063 (" ", &[whitespace(" ", 2)]),
23064 ("Ʒ", &[word("Ʒ", 1)]),
23065 ("Ǽ", &[word("Ǽ", 1)]),
23066 ("⋑", &[word("⋑", 1)]),
23067 ("⋑⋑", &[word("⋑⋑", 2)]),
23068 (
23069 "原理,进而",
23070 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23071 ),
23072 (
23073 "hello world",
23074 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23075 ),
23076 (
23077 "hello, world",
23078 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23079 ),
23080 (
23081 " hello world",
23082 &[
23083 whitespace(" ", 2),
23084 word("hello", 5),
23085 whitespace(" ", 1),
23086 word("world", 5),
23087 ],
23088 ),
23089 (
23090 "这是什么 \n 钢笔",
23091 &[
23092 word("这", 1),
23093 word("是", 1),
23094 word("什", 1),
23095 word("么", 1),
23096 whitespace(" ", 1),
23097 newline(),
23098 whitespace(" ", 1),
23099 word("钢", 1),
23100 word("笔", 1),
23101 ],
23102 ),
23103 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23104 ];
23105
23106 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23107 WordBreakToken::Word {
23108 token,
23109 grapheme_len,
23110 }
23111 }
23112
23113 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23114 WordBreakToken::InlineWhitespace {
23115 token,
23116 grapheme_len,
23117 }
23118 }
23119
23120 fn newline() -> WordBreakToken<'static> {
23121 WordBreakToken::Newline
23122 }
23123
23124 for (input, result) in tests {
23125 assert_eq!(
23126 WordBreakingTokenizer::new(input)
23127 .collect::<Vec<_>>()
23128 .as_slice(),
23129 *result,
23130 );
23131 }
23132}
23133
23134fn wrap_with_prefix(
23135 first_line_prefix: String,
23136 subsequent_lines_prefix: String,
23137 unwrapped_text: String,
23138 wrap_column: usize,
23139 tab_size: NonZeroU32,
23140 preserve_existing_whitespace: bool,
23141) -> String {
23142 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23143 let subsequent_lines_prefix_len =
23144 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23145 let mut wrapped_text = String::new();
23146 let mut current_line = first_line_prefix;
23147 let mut is_first_line = true;
23148
23149 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23150 let mut current_line_len = first_line_prefix_len;
23151 let mut in_whitespace = false;
23152 for token in tokenizer {
23153 let have_preceding_whitespace = in_whitespace;
23154 match token {
23155 WordBreakToken::Word {
23156 token,
23157 grapheme_len,
23158 } => {
23159 in_whitespace = false;
23160 let current_prefix_len = if is_first_line {
23161 first_line_prefix_len
23162 } else {
23163 subsequent_lines_prefix_len
23164 };
23165 if current_line_len + grapheme_len > wrap_column
23166 && current_line_len != current_prefix_len
23167 {
23168 wrapped_text.push_str(current_line.trim_end());
23169 wrapped_text.push('\n');
23170 is_first_line = false;
23171 current_line = subsequent_lines_prefix.clone();
23172 current_line_len = subsequent_lines_prefix_len;
23173 }
23174 current_line.push_str(token);
23175 current_line_len += grapheme_len;
23176 }
23177 WordBreakToken::InlineWhitespace {
23178 mut token,
23179 mut grapheme_len,
23180 } => {
23181 in_whitespace = true;
23182 if have_preceding_whitespace && !preserve_existing_whitespace {
23183 continue;
23184 }
23185 if !preserve_existing_whitespace {
23186 // Keep a single whitespace grapheme as-is
23187 if let Some(first) =
23188 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23189 {
23190 token = first;
23191 } else {
23192 token = " ";
23193 }
23194 grapheme_len = 1;
23195 }
23196 let current_prefix_len = if is_first_line {
23197 first_line_prefix_len
23198 } else {
23199 subsequent_lines_prefix_len
23200 };
23201 if current_line_len + grapheme_len > wrap_column {
23202 wrapped_text.push_str(current_line.trim_end());
23203 wrapped_text.push('\n');
23204 is_first_line = false;
23205 current_line = subsequent_lines_prefix.clone();
23206 current_line_len = subsequent_lines_prefix_len;
23207 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23208 current_line.push_str(token);
23209 current_line_len += grapheme_len;
23210 }
23211 }
23212 WordBreakToken::Newline => {
23213 in_whitespace = true;
23214 let current_prefix_len = if is_first_line {
23215 first_line_prefix_len
23216 } else {
23217 subsequent_lines_prefix_len
23218 };
23219 if preserve_existing_whitespace {
23220 wrapped_text.push_str(current_line.trim_end());
23221 wrapped_text.push('\n');
23222 is_first_line = false;
23223 current_line = subsequent_lines_prefix.clone();
23224 current_line_len = subsequent_lines_prefix_len;
23225 } else if have_preceding_whitespace {
23226 continue;
23227 } else if current_line_len + 1 > wrap_column
23228 && current_line_len != current_prefix_len
23229 {
23230 wrapped_text.push_str(current_line.trim_end());
23231 wrapped_text.push('\n');
23232 is_first_line = false;
23233 current_line = subsequent_lines_prefix.clone();
23234 current_line_len = subsequent_lines_prefix_len;
23235 } else if current_line_len != current_prefix_len {
23236 current_line.push(' ');
23237 current_line_len += 1;
23238 }
23239 }
23240 }
23241 }
23242
23243 if !current_line.is_empty() {
23244 wrapped_text.push_str(¤t_line);
23245 }
23246 wrapped_text
23247}
23248
23249#[test]
23250fn test_wrap_with_prefix() {
23251 assert_eq!(
23252 wrap_with_prefix(
23253 "# ".to_string(),
23254 "# ".to_string(),
23255 "abcdefg".to_string(),
23256 4,
23257 NonZeroU32::new(4).unwrap(),
23258 false,
23259 ),
23260 "# abcdefg"
23261 );
23262 assert_eq!(
23263 wrap_with_prefix(
23264 "".to_string(),
23265 "".to_string(),
23266 "\thello world".to_string(),
23267 8,
23268 NonZeroU32::new(4).unwrap(),
23269 false,
23270 ),
23271 "hello\nworld"
23272 );
23273 assert_eq!(
23274 wrap_with_prefix(
23275 "// ".to_string(),
23276 "// ".to_string(),
23277 "xx \nyy zz aa bb cc".to_string(),
23278 12,
23279 NonZeroU32::new(4).unwrap(),
23280 false,
23281 ),
23282 "// xx yy zz\n// aa bb cc"
23283 );
23284 assert_eq!(
23285 wrap_with_prefix(
23286 String::new(),
23287 String::new(),
23288 "这是什么 \n 钢笔".to_string(),
23289 3,
23290 NonZeroU32::new(4).unwrap(),
23291 false,
23292 ),
23293 "这是什\n么 钢\n笔"
23294 );
23295 assert_eq!(
23296 wrap_with_prefix(
23297 String::new(),
23298 String::new(),
23299 format!("foo{}bar", '\u{2009}'), // thin space
23300 80,
23301 NonZeroU32::new(4).unwrap(),
23302 false,
23303 ),
23304 format!("foo{}bar", '\u{2009}')
23305 );
23306}
23307
23308pub trait CollaborationHub {
23309 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23310 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23311 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23312}
23313
23314impl CollaborationHub for Entity<Project> {
23315 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23316 self.read(cx).collaborators()
23317 }
23318
23319 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23320 self.read(cx).user_store().read(cx).participant_indices()
23321 }
23322
23323 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23324 let this = self.read(cx);
23325 let user_ids = this.collaborators().values().map(|c| c.user_id);
23326 this.user_store().read(cx).participant_names(user_ids, cx)
23327 }
23328}
23329
23330pub trait SemanticsProvider {
23331 fn hover(
23332 &self,
23333 buffer: &Entity<Buffer>,
23334 position: text::Anchor,
23335 cx: &mut App,
23336 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23337
23338 fn inline_values(
23339 &self,
23340 buffer_handle: Entity<Buffer>,
23341 range: Range<text::Anchor>,
23342 cx: &mut App,
23343 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23344
23345 fn applicable_inlay_chunks(
23346 &self,
23347 buffer: &Entity<Buffer>,
23348 ranges: &[Range<text::Anchor>],
23349 cx: &mut App,
23350 ) -> Vec<Range<BufferRow>>;
23351
23352 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23353
23354 fn inlay_hints(
23355 &self,
23356 invalidate: InvalidationStrategy,
23357 buffer: Entity<Buffer>,
23358 ranges: Vec<Range<text::Anchor>>,
23359 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23360 cx: &mut App,
23361 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23362
23363 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23364
23365 fn document_highlights(
23366 &self,
23367 buffer: &Entity<Buffer>,
23368 position: text::Anchor,
23369 cx: &mut App,
23370 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23371
23372 fn definitions(
23373 &self,
23374 buffer: &Entity<Buffer>,
23375 position: text::Anchor,
23376 kind: GotoDefinitionKind,
23377 cx: &mut App,
23378 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23379
23380 fn range_for_rename(
23381 &self,
23382 buffer: &Entity<Buffer>,
23383 position: text::Anchor,
23384 cx: &mut App,
23385 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23386
23387 fn perform_rename(
23388 &self,
23389 buffer: &Entity<Buffer>,
23390 position: text::Anchor,
23391 new_name: String,
23392 cx: &mut App,
23393 ) -> Option<Task<Result<ProjectTransaction>>>;
23394}
23395
23396pub trait CompletionProvider {
23397 fn completions(
23398 &self,
23399 excerpt_id: ExcerptId,
23400 buffer: &Entity<Buffer>,
23401 buffer_position: text::Anchor,
23402 trigger: CompletionContext,
23403 window: &mut Window,
23404 cx: &mut Context<Editor>,
23405 ) -> Task<Result<Vec<CompletionResponse>>>;
23406
23407 fn resolve_completions(
23408 &self,
23409 _buffer: Entity<Buffer>,
23410 _completion_indices: Vec<usize>,
23411 _completions: Rc<RefCell<Box<[Completion]>>>,
23412 _cx: &mut Context<Editor>,
23413 ) -> Task<Result<bool>> {
23414 Task::ready(Ok(false))
23415 }
23416
23417 fn apply_additional_edits_for_completion(
23418 &self,
23419 _buffer: Entity<Buffer>,
23420 _completions: Rc<RefCell<Box<[Completion]>>>,
23421 _completion_index: usize,
23422 _push_to_history: bool,
23423 _cx: &mut Context<Editor>,
23424 ) -> Task<Result<Option<language::Transaction>>> {
23425 Task::ready(Ok(None))
23426 }
23427
23428 fn is_completion_trigger(
23429 &self,
23430 buffer: &Entity<Buffer>,
23431 position: language::Anchor,
23432 text: &str,
23433 trigger_in_words: bool,
23434 menu_is_open: bool,
23435 cx: &mut Context<Editor>,
23436 ) -> bool;
23437
23438 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23439
23440 fn sort_completions(&self) -> bool {
23441 true
23442 }
23443
23444 fn filter_completions(&self) -> bool {
23445 true
23446 }
23447
23448 fn show_snippets(&self) -> bool {
23449 false
23450 }
23451}
23452
23453pub trait CodeActionProvider {
23454 fn id(&self) -> Arc<str>;
23455
23456 fn code_actions(
23457 &self,
23458 buffer: &Entity<Buffer>,
23459 range: Range<text::Anchor>,
23460 window: &mut Window,
23461 cx: &mut App,
23462 ) -> Task<Result<Vec<CodeAction>>>;
23463
23464 fn apply_code_action(
23465 &self,
23466 buffer_handle: Entity<Buffer>,
23467 action: CodeAction,
23468 excerpt_id: ExcerptId,
23469 push_to_history: bool,
23470 window: &mut Window,
23471 cx: &mut App,
23472 ) -> Task<Result<ProjectTransaction>>;
23473}
23474
23475impl CodeActionProvider for Entity<Project> {
23476 fn id(&self) -> Arc<str> {
23477 "project".into()
23478 }
23479
23480 fn code_actions(
23481 &self,
23482 buffer: &Entity<Buffer>,
23483 range: Range<text::Anchor>,
23484 _window: &mut Window,
23485 cx: &mut App,
23486 ) -> Task<Result<Vec<CodeAction>>> {
23487 self.update(cx, |project, cx| {
23488 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23489 let code_actions = project.code_actions(buffer, range, None, cx);
23490 cx.background_spawn(async move {
23491 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23492 Ok(code_lens_actions
23493 .context("code lens fetch")?
23494 .into_iter()
23495 .flatten()
23496 .chain(
23497 code_actions
23498 .context("code action fetch")?
23499 .into_iter()
23500 .flatten(),
23501 )
23502 .collect())
23503 })
23504 })
23505 }
23506
23507 fn apply_code_action(
23508 &self,
23509 buffer_handle: Entity<Buffer>,
23510 action: CodeAction,
23511 _excerpt_id: ExcerptId,
23512 push_to_history: bool,
23513 _window: &mut Window,
23514 cx: &mut App,
23515 ) -> Task<Result<ProjectTransaction>> {
23516 self.update(cx, |project, cx| {
23517 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23518 })
23519 }
23520}
23521
23522fn snippet_completions(
23523 project: &Project,
23524 buffer: &Entity<Buffer>,
23525 buffer_anchor: text::Anchor,
23526 classifier: CharClassifier,
23527 cx: &mut App,
23528) -> Task<Result<CompletionResponse>> {
23529 let languages = buffer.read(cx).languages_at(buffer_anchor);
23530 let snippet_store = project.snippets().read(cx);
23531
23532 let scopes: Vec<_> = languages
23533 .iter()
23534 .filter_map(|language| {
23535 let language_name = language.lsp_id();
23536 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23537
23538 if snippets.is_empty() {
23539 None
23540 } else {
23541 Some((language.default_scope(), snippets))
23542 }
23543 })
23544 .collect();
23545
23546 if scopes.is_empty() {
23547 return Task::ready(Ok(CompletionResponse {
23548 completions: vec![],
23549 display_options: CompletionDisplayOptions::default(),
23550 is_incomplete: false,
23551 }));
23552 }
23553
23554 let snapshot = buffer.read(cx).text_snapshot();
23555 let executor = cx.background_executor().clone();
23556
23557 cx.background_spawn(async move {
23558 let is_word_char = |c| classifier.is_word(c);
23559
23560 let mut is_incomplete = false;
23561 let mut completions: Vec<Completion> = Vec::new();
23562
23563 const MAX_PREFIX_LEN: usize = 128;
23564 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23565 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23566 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23567
23568 let max_buffer_window: String = snapshot
23569 .text_for_range(window_start..buffer_offset)
23570 .collect();
23571
23572 if max_buffer_window.is_empty() {
23573 return Ok(CompletionResponse {
23574 completions: vec![],
23575 display_options: CompletionDisplayOptions::default(),
23576 is_incomplete: true,
23577 });
23578 }
23579
23580 for (_scope, snippets) in scopes.into_iter() {
23581 // Sort snippets by word count to match longer snippet prefixes first.
23582 let mut sorted_snippet_candidates = snippets
23583 .iter()
23584 .enumerate()
23585 .flat_map(|(snippet_ix, snippet)| {
23586 snippet
23587 .prefix
23588 .iter()
23589 .enumerate()
23590 .map(move |(prefix_ix, prefix)| {
23591 let word_count =
23592 snippet_candidate_suffixes(prefix, is_word_char).count();
23593 ((snippet_ix, prefix_ix), prefix, word_count)
23594 })
23595 })
23596 .collect_vec();
23597 sorted_snippet_candidates
23598 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23599
23600 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23601
23602 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23603 .take(
23604 sorted_snippet_candidates
23605 .first()
23606 .map(|(_, _, word_count)| *word_count)
23607 .unwrap_or_default(),
23608 )
23609 .collect_vec();
23610
23611 const MAX_RESULTS: usize = 100;
23612 // Each match also remembers how many characters from the buffer it consumed
23613 let mut matches: Vec<(StringMatch, usize)> = vec![];
23614
23615 let mut snippet_list_cutoff_index = 0;
23616 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23617 let word_count = buffer_index + 1;
23618 // Increase `snippet_list_cutoff_index` until we have all of the
23619 // snippets with sufficiently many words.
23620 while sorted_snippet_candidates
23621 .get(snippet_list_cutoff_index)
23622 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23623 *snippet_word_count >= word_count
23624 })
23625 {
23626 snippet_list_cutoff_index += 1;
23627 }
23628
23629 // Take only the candidates with at least `word_count` many words
23630 let snippet_candidates_at_word_len =
23631 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23632
23633 let candidates = snippet_candidates_at_word_len
23634 .iter()
23635 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23636 .enumerate() // index in `sorted_snippet_candidates`
23637 // First char must match
23638 .filter(|(_ix, prefix)| {
23639 itertools::equal(
23640 prefix
23641 .chars()
23642 .next()
23643 .into_iter()
23644 .flat_map(|c| c.to_lowercase()),
23645 buffer_window
23646 .chars()
23647 .next()
23648 .into_iter()
23649 .flat_map(|c| c.to_lowercase()),
23650 )
23651 })
23652 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23653 .collect::<Vec<StringMatchCandidate>>();
23654
23655 matches.extend(
23656 fuzzy::match_strings(
23657 &candidates,
23658 &buffer_window,
23659 buffer_window.chars().any(|c| c.is_uppercase()),
23660 true,
23661 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23662 &Default::default(),
23663 executor.clone(),
23664 )
23665 .await
23666 .into_iter()
23667 .map(|string_match| (string_match, buffer_window.len())),
23668 );
23669
23670 if matches.len() >= MAX_RESULTS {
23671 break;
23672 }
23673 }
23674
23675 let to_lsp = |point: &text::Anchor| {
23676 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23677 point_to_lsp(end)
23678 };
23679 let lsp_end = to_lsp(&buffer_anchor);
23680
23681 if matches.len() >= MAX_RESULTS {
23682 is_incomplete = true;
23683 }
23684
23685 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23686 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23687 sorted_snippet_candidates[string_match.candidate_id];
23688 let snippet = &snippets[snippet_index];
23689 let start = buffer_offset - buffer_window_len;
23690 let start = snapshot.anchor_before(start);
23691 let range = start..buffer_anchor;
23692 let lsp_start = to_lsp(&start);
23693 let lsp_range = lsp::Range {
23694 start: lsp_start,
23695 end: lsp_end,
23696 };
23697 Completion {
23698 replace_range: range,
23699 new_text: snippet.body.clone(),
23700 source: CompletionSource::Lsp {
23701 insert_range: None,
23702 server_id: LanguageServerId(usize::MAX),
23703 resolved: true,
23704 lsp_completion: Box::new(lsp::CompletionItem {
23705 label: snippet.prefix.first().unwrap().clone(),
23706 kind: Some(CompletionItemKind::SNIPPET),
23707 label_details: snippet.description.as_ref().map(|description| {
23708 lsp::CompletionItemLabelDetails {
23709 detail: Some(description.clone()),
23710 description: None,
23711 }
23712 }),
23713 insert_text_format: Some(InsertTextFormat::SNIPPET),
23714 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23715 lsp::InsertReplaceEdit {
23716 new_text: snippet.body.clone(),
23717 insert: lsp_range,
23718 replace: lsp_range,
23719 },
23720 )),
23721 filter_text: Some(snippet.body.clone()),
23722 sort_text: Some(char::MAX.to_string()),
23723 ..lsp::CompletionItem::default()
23724 }),
23725 lsp_defaults: None,
23726 },
23727 label: CodeLabel {
23728 text: matching_prefix.clone(),
23729 runs: Vec::new(),
23730 filter_range: 0..matching_prefix.len(),
23731 },
23732 icon_path: None,
23733 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23734 single_line: snippet.name.clone().into(),
23735 plain_text: snippet
23736 .description
23737 .clone()
23738 .map(|description| description.into()),
23739 }),
23740 insert_text_mode: None,
23741 confirm: None,
23742 match_start: Some(start),
23743 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23744 }
23745 }));
23746 }
23747
23748 Ok(CompletionResponse {
23749 completions,
23750 display_options: CompletionDisplayOptions::default(),
23751 is_incomplete,
23752 })
23753 })
23754}
23755
23756impl CompletionProvider for Entity<Project> {
23757 fn completions(
23758 &self,
23759 _excerpt_id: ExcerptId,
23760 buffer: &Entity<Buffer>,
23761 buffer_position: text::Anchor,
23762 options: CompletionContext,
23763 _window: &mut Window,
23764 cx: &mut Context<Editor>,
23765 ) -> Task<Result<Vec<CompletionResponse>>> {
23766 self.update(cx, |project, cx| {
23767 let task = project.completions(buffer, buffer_position, options, cx);
23768 cx.background_spawn(task)
23769 })
23770 }
23771
23772 fn resolve_completions(
23773 &self,
23774 buffer: Entity<Buffer>,
23775 completion_indices: Vec<usize>,
23776 completions: Rc<RefCell<Box<[Completion]>>>,
23777 cx: &mut Context<Editor>,
23778 ) -> Task<Result<bool>> {
23779 self.update(cx, |project, cx| {
23780 project.lsp_store().update(cx, |lsp_store, cx| {
23781 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23782 })
23783 })
23784 }
23785
23786 fn apply_additional_edits_for_completion(
23787 &self,
23788 buffer: Entity<Buffer>,
23789 completions: Rc<RefCell<Box<[Completion]>>>,
23790 completion_index: usize,
23791 push_to_history: bool,
23792 cx: &mut Context<Editor>,
23793 ) -> Task<Result<Option<language::Transaction>>> {
23794 self.update(cx, |project, cx| {
23795 project.lsp_store().update(cx, |lsp_store, cx| {
23796 lsp_store.apply_additional_edits_for_completion(
23797 buffer,
23798 completions,
23799 completion_index,
23800 push_to_history,
23801 cx,
23802 )
23803 })
23804 })
23805 }
23806
23807 fn is_completion_trigger(
23808 &self,
23809 buffer: &Entity<Buffer>,
23810 position: language::Anchor,
23811 text: &str,
23812 trigger_in_words: bool,
23813 menu_is_open: bool,
23814 cx: &mut Context<Editor>,
23815 ) -> bool {
23816 let mut chars = text.chars();
23817 let char = if let Some(char) = chars.next() {
23818 char
23819 } else {
23820 return false;
23821 };
23822 if chars.next().is_some() {
23823 return false;
23824 }
23825
23826 let buffer = buffer.read(cx);
23827 let snapshot = buffer.snapshot();
23828 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23829 return false;
23830 }
23831 let classifier = snapshot
23832 .char_classifier_at(position)
23833 .scope_context(Some(CharScopeContext::Completion));
23834 if trigger_in_words && classifier.is_word(char) {
23835 return true;
23836 }
23837
23838 buffer.completion_triggers().contains(text)
23839 }
23840
23841 fn show_snippets(&self) -> bool {
23842 true
23843 }
23844}
23845
23846impl SemanticsProvider for Entity<Project> {
23847 fn hover(
23848 &self,
23849 buffer: &Entity<Buffer>,
23850 position: text::Anchor,
23851 cx: &mut App,
23852 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23853 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23854 }
23855
23856 fn document_highlights(
23857 &self,
23858 buffer: &Entity<Buffer>,
23859 position: text::Anchor,
23860 cx: &mut App,
23861 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23862 Some(self.update(cx, |project, cx| {
23863 project.document_highlights(buffer, position, cx)
23864 }))
23865 }
23866
23867 fn definitions(
23868 &self,
23869 buffer: &Entity<Buffer>,
23870 position: text::Anchor,
23871 kind: GotoDefinitionKind,
23872 cx: &mut App,
23873 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23874 Some(self.update(cx, |project, cx| match kind {
23875 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23876 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23877 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23878 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23879 }))
23880 }
23881
23882 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23883 self.update(cx, |project, cx| {
23884 if project
23885 .active_debug_session(cx)
23886 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23887 {
23888 return true;
23889 }
23890
23891 buffer.update(cx, |buffer, cx| {
23892 project.any_language_server_supports_inlay_hints(buffer, cx)
23893 })
23894 })
23895 }
23896
23897 fn inline_values(
23898 &self,
23899 buffer_handle: Entity<Buffer>,
23900 range: Range<text::Anchor>,
23901 cx: &mut App,
23902 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23903 self.update(cx, |project, cx| {
23904 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23905
23906 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23907 })
23908 }
23909
23910 fn applicable_inlay_chunks(
23911 &self,
23912 buffer: &Entity<Buffer>,
23913 ranges: &[Range<text::Anchor>],
23914 cx: &mut App,
23915 ) -> Vec<Range<BufferRow>> {
23916 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23917 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23918 })
23919 }
23920
23921 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23922 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23923 lsp_store.invalidate_inlay_hints(for_buffers)
23924 });
23925 }
23926
23927 fn inlay_hints(
23928 &self,
23929 invalidate: InvalidationStrategy,
23930 buffer: Entity<Buffer>,
23931 ranges: Vec<Range<text::Anchor>>,
23932 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23933 cx: &mut App,
23934 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23935 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23936 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23937 }))
23938 }
23939
23940 fn range_for_rename(
23941 &self,
23942 buffer: &Entity<Buffer>,
23943 position: text::Anchor,
23944 cx: &mut App,
23945 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23946 Some(self.update(cx, |project, cx| {
23947 let buffer = buffer.clone();
23948 let task = project.prepare_rename(buffer.clone(), position, cx);
23949 cx.spawn(async move |_, cx| {
23950 Ok(match task.await? {
23951 PrepareRenameResponse::Success(range) => Some(range),
23952 PrepareRenameResponse::InvalidPosition => None,
23953 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23954 // Fallback on using TreeSitter info to determine identifier range
23955 buffer.read_with(cx, |buffer, _| {
23956 let snapshot = buffer.snapshot();
23957 let (range, kind) = snapshot.surrounding_word(position, None);
23958 if kind != Some(CharKind::Word) {
23959 return None;
23960 }
23961 Some(
23962 snapshot.anchor_before(range.start)
23963 ..snapshot.anchor_after(range.end),
23964 )
23965 })?
23966 }
23967 })
23968 })
23969 }))
23970 }
23971
23972 fn perform_rename(
23973 &self,
23974 buffer: &Entity<Buffer>,
23975 position: text::Anchor,
23976 new_name: String,
23977 cx: &mut App,
23978 ) -> Option<Task<Result<ProjectTransaction>>> {
23979 Some(self.update(cx, |project, cx| {
23980 project.perform_rename(buffer.clone(), position, new_name, cx)
23981 }))
23982 }
23983}
23984
23985fn consume_contiguous_rows(
23986 contiguous_row_selections: &mut Vec<Selection<Point>>,
23987 selection: &Selection<Point>,
23988 display_map: &DisplaySnapshot,
23989 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23990) -> (MultiBufferRow, MultiBufferRow) {
23991 contiguous_row_selections.push(selection.clone());
23992 let start_row = starting_row(selection, display_map);
23993 let mut end_row = ending_row(selection, display_map);
23994
23995 while let Some(next_selection) = selections.peek() {
23996 if next_selection.start.row <= end_row.0 {
23997 end_row = ending_row(next_selection, display_map);
23998 contiguous_row_selections.push(selections.next().unwrap().clone());
23999 } else {
24000 break;
24001 }
24002 }
24003 (start_row, end_row)
24004}
24005
24006fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24007 if selection.start.column > 0 {
24008 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24009 } else {
24010 MultiBufferRow(selection.start.row)
24011 }
24012}
24013
24014fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24015 if next_selection.end.column > 0 || next_selection.is_empty() {
24016 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24017 } else {
24018 MultiBufferRow(next_selection.end.row)
24019 }
24020}
24021
24022impl EditorSnapshot {
24023 pub fn remote_selections_in_range<'a>(
24024 &'a self,
24025 range: &'a Range<Anchor>,
24026 collaboration_hub: &dyn CollaborationHub,
24027 cx: &'a App,
24028 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24029 let participant_names = collaboration_hub.user_names(cx);
24030 let participant_indices = collaboration_hub.user_participant_indices(cx);
24031 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24032 let collaborators_by_replica_id = collaborators_by_peer_id
24033 .values()
24034 .map(|collaborator| (collaborator.replica_id, collaborator))
24035 .collect::<HashMap<_, _>>();
24036 self.buffer_snapshot()
24037 .selections_in_range(range, false)
24038 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24039 if replica_id == ReplicaId::AGENT {
24040 Some(RemoteSelection {
24041 replica_id,
24042 selection,
24043 cursor_shape,
24044 line_mode,
24045 collaborator_id: CollaboratorId::Agent,
24046 user_name: Some("Agent".into()),
24047 color: cx.theme().players().agent(),
24048 })
24049 } else {
24050 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24051 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24052 let user_name = participant_names.get(&collaborator.user_id).cloned();
24053 Some(RemoteSelection {
24054 replica_id,
24055 selection,
24056 cursor_shape,
24057 line_mode,
24058 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24059 user_name,
24060 color: if let Some(index) = participant_index {
24061 cx.theme().players().color_for_participant(index.0)
24062 } else {
24063 cx.theme().players().absent()
24064 },
24065 })
24066 }
24067 })
24068 }
24069
24070 pub fn hunks_for_ranges(
24071 &self,
24072 ranges: impl IntoIterator<Item = Range<Point>>,
24073 ) -> Vec<MultiBufferDiffHunk> {
24074 let mut hunks = Vec::new();
24075 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24076 HashMap::default();
24077 for query_range in ranges {
24078 let query_rows =
24079 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24080 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24081 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24082 ) {
24083 // Include deleted hunks that are adjacent to the query range, because
24084 // otherwise they would be missed.
24085 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24086 if hunk.status().is_deleted() {
24087 intersects_range |= hunk.row_range.start == query_rows.end;
24088 intersects_range |= hunk.row_range.end == query_rows.start;
24089 }
24090 if intersects_range {
24091 if !processed_buffer_rows
24092 .entry(hunk.buffer_id)
24093 .or_default()
24094 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24095 {
24096 continue;
24097 }
24098 hunks.push(hunk);
24099 }
24100 }
24101 }
24102
24103 hunks
24104 }
24105
24106 fn display_diff_hunks_for_rows<'a>(
24107 &'a self,
24108 display_rows: Range<DisplayRow>,
24109 folded_buffers: &'a HashSet<BufferId>,
24110 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24111 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24112 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24113
24114 self.buffer_snapshot()
24115 .diff_hunks_in_range(buffer_start..buffer_end)
24116 .filter_map(|hunk| {
24117 if folded_buffers.contains(&hunk.buffer_id) {
24118 return None;
24119 }
24120
24121 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24122 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24123
24124 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24125 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24126
24127 let display_hunk = if hunk_display_start.column() != 0 {
24128 DisplayDiffHunk::Folded {
24129 display_row: hunk_display_start.row(),
24130 }
24131 } else {
24132 let mut end_row = hunk_display_end.row();
24133 if hunk_display_end.column() > 0 {
24134 end_row.0 += 1;
24135 }
24136 let is_created_file = hunk.is_created_file();
24137
24138 DisplayDiffHunk::Unfolded {
24139 status: hunk.status(),
24140 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24141 ..hunk.diff_base_byte_range.end.0,
24142 word_diffs: hunk.word_diffs,
24143 display_row_range: hunk_display_start.row()..end_row,
24144 multi_buffer_range: Anchor::range_in_buffer(
24145 hunk.excerpt_id,
24146 hunk.buffer_range,
24147 ),
24148 is_created_file,
24149 }
24150 };
24151
24152 Some(display_hunk)
24153 })
24154 }
24155
24156 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24157 self.display_snapshot
24158 .buffer_snapshot()
24159 .language_at(position)
24160 }
24161
24162 pub fn is_focused(&self) -> bool {
24163 self.is_focused
24164 }
24165
24166 pub fn placeholder_text(&self) -> Option<String> {
24167 self.placeholder_display_snapshot
24168 .as_ref()
24169 .map(|display_map| display_map.text())
24170 }
24171
24172 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24173 self.scroll_anchor.scroll_position(&self.display_snapshot)
24174 }
24175
24176 fn gutter_dimensions(
24177 &self,
24178 font_id: FontId,
24179 font_size: Pixels,
24180 max_line_number_width: Pixels,
24181 cx: &App,
24182 ) -> Option<GutterDimensions> {
24183 if !self.show_gutter {
24184 return None;
24185 }
24186
24187 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24188 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24189
24190 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24191 matches!(
24192 ProjectSettings::get_global(cx).git.git_gutter,
24193 GitGutterSetting::TrackedFiles
24194 )
24195 });
24196 let gutter_settings = EditorSettings::get_global(cx).gutter;
24197 let show_line_numbers = self
24198 .show_line_numbers
24199 .unwrap_or(gutter_settings.line_numbers);
24200 let line_gutter_width = if show_line_numbers {
24201 // Avoid flicker-like gutter resizes when the line number gains another digit by
24202 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24203 let min_width_for_number_on_gutter =
24204 ch_advance * gutter_settings.min_line_number_digits as f32;
24205 max_line_number_width.max(min_width_for_number_on_gutter)
24206 } else {
24207 0.0.into()
24208 };
24209
24210 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24211 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24212
24213 let git_blame_entries_width =
24214 self.git_blame_gutter_max_author_length
24215 .map(|max_author_length| {
24216 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24217 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24218
24219 /// The number of characters to dedicate to gaps and margins.
24220 const SPACING_WIDTH: usize = 4;
24221
24222 let max_char_count = max_author_length.min(renderer.max_author_length())
24223 + ::git::SHORT_SHA_LENGTH
24224 + MAX_RELATIVE_TIMESTAMP.len()
24225 + SPACING_WIDTH;
24226
24227 ch_advance * max_char_count
24228 });
24229
24230 let is_singleton = self.buffer_snapshot().is_singleton();
24231
24232 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24233 left_padding += if !is_singleton {
24234 ch_width * 4.0
24235 } else if show_runnables || show_breakpoints {
24236 ch_width * 3.0
24237 } else if show_git_gutter && show_line_numbers {
24238 ch_width * 2.0
24239 } else if show_git_gutter || show_line_numbers {
24240 ch_width
24241 } else {
24242 px(0.)
24243 };
24244
24245 let shows_folds = is_singleton && gutter_settings.folds;
24246
24247 let right_padding = if shows_folds && show_line_numbers {
24248 ch_width * 4.0
24249 } else if shows_folds || (!is_singleton && show_line_numbers) {
24250 ch_width * 3.0
24251 } else if show_line_numbers {
24252 ch_width
24253 } else {
24254 px(0.)
24255 };
24256
24257 Some(GutterDimensions {
24258 left_padding,
24259 right_padding,
24260 width: line_gutter_width + left_padding + right_padding,
24261 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24262 git_blame_entries_width,
24263 })
24264 }
24265
24266 pub fn render_crease_toggle(
24267 &self,
24268 buffer_row: MultiBufferRow,
24269 row_contains_cursor: bool,
24270 editor: Entity<Editor>,
24271 window: &mut Window,
24272 cx: &mut App,
24273 ) -> Option<AnyElement> {
24274 let folded = self.is_line_folded(buffer_row);
24275 let mut is_foldable = false;
24276
24277 if let Some(crease) = self
24278 .crease_snapshot
24279 .query_row(buffer_row, self.buffer_snapshot())
24280 {
24281 is_foldable = true;
24282 match crease {
24283 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24284 if let Some(render_toggle) = render_toggle {
24285 let toggle_callback =
24286 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24287 if folded {
24288 editor.update(cx, |editor, cx| {
24289 editor.fold_at(buffer_row, window, cx)
24290 });
24291 } else {
24292 editor.update(cx, |editor, cx| {
24293 editor.unfold_at(buffer_row, window, cx)
24294 });
24295 }
24296 });
24297 return Some((render_toggle)(
24298 buffer_row,
24299 folded,
24300 toggle_callback,
24301 window,
24302 cx,
24303 ));
24304 }
24305 }
24306 }
24307 }
24308
24309 is_foldable |= self.starts_indent(buffer_row);
24310
24311 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24312 Some(
24313 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24314 .toggle_state(folded)
24315 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24316 if folded {
24317 this.unfold_at(buffer_row, window, cx);
24318 } else {
24319 this.fold_at(buffer_row, window, cx);
24320 }
24321 }))
24322 .into_any_element(),
24323 )
24324 } else {
24325 None
24326 }
24327 }
24328
24329 pub fn render_crease_trailer(
24330 &self,
24331 buffer_row: MultiBufferRow,
24332 window: &mut Window,
24333 cx: &mut App,
24334 ) -> Option<AnyElement> {
24335 let folded = self.is_line_folded(buffer_row);
24336 if let Crease::Inline { render_trailer, .. } = self
24337 .crease_snapshot
24338 .query_row(buffer_row, self.buffer_snapshot())?
24339 {
24340 let render_trailer = render_trailer.as_ref()?;
24341 Some(render_trailer(buffer_row, folded, window, cx))
24342 } else {
24343 None
24344 }
24345 }
24346}
24347
24348impl Deref for EditorSnapshot {
24349 type Target = DisplaySnapshot;
24350
24351 fn deref(&self) -> &Self::Target {
24352 &self.display_snapshot
24353 }
24354}
24355
24356#[derive(Clone, Debug, PartialEq, Eq)]
24357pub enum EditorEvent {
24358 InputIgnored {
24359 text: Arc<str>,
24360 },
24361 InputHandled {
24362 utf16_range_to_replace: Option<Range<isize>>,
24363 text: Arc<str>,
24364 },
24365 ExcerptsAdded {
24366 buffer: Entity<Buffer>,
24367 predecessor: ExcerptId,
24368 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24369 },
24370 ExcerptsRemoved {
24371 ids: Vec<ExcerptId>,
24372 removed_buffer_ids: Vec<BufferId>,
24373 },
24374 BufferFoldToggled {
24375 ids: Vec<ExcerptId>,
24376 folded: bool,
24377 },
24378 ExcerptsEdited {
24379 ids: Vec<ExcerptId>,
24380 },
24381 ExcerptsExpanded {
24382 ids: Vec<ExcerptId>,
24383 },
24384 BufferEdited,
24385 Edited {
24386 transaction_id: clock::Lamport,
24387 },
24388 Reparsed(BufferId),
24389 Focused,
24390 FocusedIn,
24391 Blurred,
24392 DirtyChanged,
24393 Saved,
24394 TitleChanged,
24395 SelectionsChanged {
24396 local: bool,
24397 },
24398 ScrollPositionChanged {
24399 local: bool,
24400 autoscroll: bool,
24401 },
24402 TransactionUndone {
24403 transaction_id: clock::Lamport,
24404 },
24405 TransactionBegun {
24406 transaction_id: clock::Lamport,
24407 },
24408 CursorShapeChanged,
24409 BreadcrumbsChanged,
24410 PushedToNavHistory {
24411 anchor: Anchor,
24412 is_deactivate: bool,
24413 },
24414}
24415
24416impl EventEmitter<EditorEvent> for Editor {}
24417
24418impl Focusable for Editor {
24419 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24420 self.focus_handle.clone()
24421 }
24422}
24423
24424impl Render for Editor {
24425 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24426 let settings = ThemeSettings::get_global(cx);
24427
24428 let mut text_style = match self.mode {
24429 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24430 color: cx.theme().colors().editor_foreground,
24431 font_family: settings.ui_font.family.clone(),
24432 font_features: settings.ui_font.features.clone(),
24433 font_fallbacks: settings.ui_font.fallbacks.clone(),
24434 font_size: rems(0.875).into(),
24435 font_weight: settings.ui_font.weight,
24436 line_height: relative(settings.buffer_line_height.value()),
24437 ..Default::default()
24438 },
24439 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24440 color: cx.theme().colors().editor_foreground,
24441 font_family: settings.buffer_font.family.clone(),
24442 font_features: settings.buffer_font.features.clone(),
24443 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24444 font_size: settings.buffer_font_size(cx).into(),
24445 font_weight: settings.buffer_font.weight,
24446 line_height: relative(settings.buffer_line_height.value()),
24447 ..Default::default()
24448 },
24449 };
24450 if let Some(text_style_refinement) = &self.text_style_refinement {
24451 text_style.refine(text_style_refinement)
24452 }
24453
24454 let background = match self.mode {
24455 EditorMode::SingleLine => cx.theme().system().transparent,
24456 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24457 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24458 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24459 };
24460
24461 EditorElement::new(
24462 &cx.entity(),
24463 EditorStyle {
24464 background,
24465 border: cx.theme().colors().border,
24466 local_player: cx.theme().players().local(),
24467 text: text_style,
24468 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24469 syntax: cx.theme().syntax().clone(),
24470 status: cx.theme().status().clone(),
24471 inlay_hints_style: make_inlay_hints_style(cx),
24472 edit_prediction_styles: make_suggestion_styles(cx),
24473 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24474 show_underlines: self.diagnostics_enabled(),
24475 },
24476 )
24477 }
24478}
24479
24480impl EntityInputHandler for Editor {
24481 fn text_for_range(
24482 &mut self,
24483 range_utf16: Range<usize>,
24484 adjusted_range: &mut Option<Range<usize>>,
24485 _: &mut Window,
24486 cx: &mut Context<Self>,
24487 ) -> Option<String> {
24488 let snapshot = self.buffer.read(cx).read(cx);
24489 let start = snapshot.clip_offset_utf16(
24490 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24491 Bias::Left,
24492 );
24493 let end = snapshot.clip_offset_utf16(
24494 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24495 Bias::Right,
24496 );
24497 if (start.0.0..end.0.0) != range_utf16 {
24498 adjusted_range.replace(start.0.0..end.0.0);
24499 }
24500 Some(snapshot.text_for_range(start..end).collect())
24501 }
24502
24503 fn selected_text_range(
24504 &mut self,
24505 ignore_disabled_input: bool,
24506 _: &mut Window,
24507 cx: &mut Context<Self>,
24508 ) -> Option<UTF16Selection> {
24509 // Prevent the IME menu from appearing when holding down an alphabetic key
24510 // while input is disabled.
24511 if !ignore_disabled_input && !self.input_enabled {
24512 return None;
24513 }
24514
24515 let selection = self
24516 .selections
24517 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24518 let range = selection.range();
24519
24520 Some(UTF16Selection {
24521 range: range.start.0.0..range.end.0.0,
24522 reversed: selection.reversed,
24523 })
24524 }
24525
24526 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24527 let snapshot = self.buffer.read(cx).read(cx);
24528 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24529 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24530 }
24531
24532 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24533 self.clear_highlights::<InputComposition>(cx);
24534 self.ime_transaction.take();
24535 }
24536
24537 fn replace_text_in_range(
24538 &mut self,
24539 range_utf16: Option<Range<usize>>,
24540 text: &str,
24541 window: &mut Window,
24542 cx: &mut Context<Self>,
24543 ) {
24544 if !self.input_enabled {
24545 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24546 return;
24547 }
24548
24549 self.transact(window, cx, |this, window, cx| {
24550 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24551 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24552 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24553 Some(this.selection_replacement_ranges(range_utf16, cx))
24554 } else {
24555 this.marked_text_ranges(cx)
24556 };
24557
24558 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24559 let newest_selection_id = this.selections.newest_anchor().id;
24560 this.selections
24561 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24562 .iter()
24563 .zip(ranges_to_replace.iter())
24564 .find_map(|(selection, range)| {
24565 if selection.id == newest_selection_id {
24566 Some(
24567 (range.start.0.0 as isize - selection.head().0.0 as isize)
24568 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24569 )
24570 } else {
24571 None
24572 }
24573 })
24574 });
24575
24576 cx.emit(EditorEvent::InputHandled {
24577 utf16_range_to_replace: range_to_replace,
24578 text: text.into(),
24579 });
24580
24581 if let Some(new_selected_ranges) = new_selected_ranges {
24582 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24583 selections.select_ranges(new_selected_ranges)
24584 });
24585 this.backspace(&Default::default(), window, cx);
24586 }
24587
24588 this.handle_input(text, window, cx);
24589 });
24590
24591 if let Some(transaction) = self.ime_transaction {
24592 self.buffer.update(cx, |buffer, cx| {
24593 buffer.group_until_transaction(transaction, cx);
24594 });
24595 }
24596
24597 self.unmark_text(window, cx);
24598 }
24599
24600 fn replace_and_mark_text_in_range(
24601 &mut self,
24602 range_utf16: Option<Range<usize>>,
24603 text: &str,
24604 new_selected_range_utf16: Option<Range<usize>>,
24605 window: &mut Window,
24606 cx: &mut Context<Self>,
24607 ) {
24608 if !self.input_enabled {
24609 return;
24610 }
24611
24612 let transaction = self.transact(window, cx, |this, window, cx| {
24613 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24614 let snapshot = this.buffer.read(cx).read(cx);
24615 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24616 for marked_range in &mut marked_ranges {
24617 marked_range.end = marked_range.start + relative_range_utf16.end;
24618 marked_range.start += relative_range_utf16.start;
24619 marked_range.start =
24620 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24621 marked_range.end =
24622 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24623 }
24624 }
24625 Some(marked_ranges)
24626 } else if let Some(range_utf16) = range_utf16 {
24627 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24628 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24629 Some(this.selection_replacement_ranges(range_utf16, cx))
24630 } else {
24631 None
24632 };
24633
24634 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24635 let newest_selection_id = this.selections.newest_anchor().id;
24636 this.selections
24637 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24638 .iter()
24639 .zip(ranges_to_replace.iter())
24640 .find_map(|(selection, range)| {
24641 if selection.id == newest_selection_id {
24642 Some(
24643 (range.start.0.0 as isize - selection.head().0.0 as isize)
24644 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24645 )
24646 } else {
24647 None
24648 }
24649 })
24650 });
24651
24652 cx.emit(EditorEvent::InputHandled {
24653 utf16_range_to_replace: range_to_replace,
24654 text: text.into(),
24655 });
24656
24657 if let Some(ranges) = ranges_to_replace {
24658 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24659 s.select_ranges(ranges)
24660 });
24661 }
24662
24663 let marked_ranges = {
24664 let snapshot = this.buffer.read(cx).read(cx);
24665 this.selections
24666 .disjoint_anchors_arc()
24667 .iter()
24668 .map(|selection| {
24669 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24670 })
24671 .collect::<Vec<_>>()
24672 };
24673
24674 if text.is_empty() {
24675 this.unmark_text(window, cx);
24676 } else {
24677 this.highlight_text::<InputComposition>(
24678 marked_ranges.clone(),
24679 HighlightStyle {
24680 underline: Some(UnderlineStyle {
24681 thickness: px(1.),
24682 color: None,
24683 wavy: false,
24684 }),
24685 ..Default::default()
24686 },
24687 cx,
24688 );
24689 }
24690
24691 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24692 let use_autoclose = this.use_autoclose;
24693 let use_auto_surround = this.use_auto_surround;
24694 this.set_use_autoclose(false);
24695 this.set_use_auto_surround(false);
24696 this.handle_input(text, window, cx);
24697 this.set_use_autoclose(use_autoclose);
24698 this.set_use_auto_surround(use_auto_surround);
24699
24700 if let Some(new_selected_range) = new_selected_range_utf16 {
24701 let snapshot = this.buffer.read(cx).read(cx);
24702 let new_selected_ranges = marked_ranges
24703 .into_iter()
24704 .map(|marked_range| {
24705 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24706 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24707 insertion_start.0 + new_selected_range.start,
24708 ));
24709 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24710 insertion_start.0 + new_selected_range.end,
24711 ));
24712 snapshot.clip_offset_utf16(new_start, Bias::Left)
24713 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24714 })
24715 .collect::<Vec<_>>();
24716
24717 drop(snapshot);
24718 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24719 selections.select_ranges(new_selected_ranges)
24720 });
24721 }
24722 });
24723
24724 self.ime_transaction = self.ime_transaction.or(transaction);
24725 if let Some(transaction) = self.ime_transaction {
24726 self.buffer.update(cx, |buffer, cx| {
24727 buffer.group_until_transaction(transaction, cx);
24728 });
24729 }
24730
24731 if self.text_highlights::<InputComposition>(cx).is_none() {
24732 self.ime_transaction.take();
24733 }
24734 }
24735
24736 fn bounds_for_range(
24737 &mut self,
24738 range_utf16: Range<usize>,
24739 element_bounds: gpui::Bounds<Pixels>,
24740 window: &mut Window,
24741 cx: &mut Context<Self>,
24742 ) -> Option<gpui::Bounds<Pixels>> {
24743 let text_layout_details = self.text_layout_details(window);
24744 let CharacterDimensions {
24745 em_width,
24746 em_advance,
24747 line_height,
24748 } = self.character_dimensions(window);
24749
24750 let snapshot = self.snapshot(window, cx);
24751 let scroll_position = snapshot.scroll_position();
24752 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24753
24754 let start =
24755 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24756 let x = Pixels::from(
24757 ScrollOffset::from(
24758 snapshot.x_for_display_point(start, &text_layout_details)
24759 + self.gutter_dimensions.full_width(),
24760 ) - scroll_left,
24761 );
24762 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24763
24764 Some(Bounds {
24765 origin: element_bounds.origin + point(x, y),
24766 size: size(em_width, line_height),
24767 })
24768 }
24769
24770 fn character_index_for_point(
24771 &mut self,
24772 point: gpui::Point<Pixels>,
24773 _window: &mut Window,
24774 _cx: &mut Context<Self>,
24775 ) -> Option<usize> {
24776 let position_map = self.last_position_map.as_ref()?;
24777 if !position_map.text_hitbox.contains(&point) {
24778 return None;
24779 }
24780 let display_point = position_map.point_for_position(point).previous_valid;
24781 let anchor = position_map
24782 .snapshot
24783 .display_point_to_anchor(display_point, Bias::Left);
24784 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24785 Some(utf16_offset.0.0)
24786 }
24787
24788 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24789 self.input_enabled
24790 }
24791}
24792
24793trait SelectionExt {
24794 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24795 fn spanned_rows(
24796 &self,
24797 include_end_if_at_line_start: bool,
24798 map: &DisplaySnapshot,
24799 ) -> Range<MultiBufferRow>;
24800}
24801
24802impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24803 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24804 let start = self
24805 .start
24806 .to_point(map.buffer_snapshot())
24807 .to_display_point(map);
24808 let end = self
24809 .end
24810 .to_point(map.buffer_snapshot())
24811 .to_display_point(map);
24812 if self.reversed {
24813 end..start
24814 } else {
24815 start..end
24816 }
24817 }
24818
24819 fn spanned_rows(
24820 &self,
24821 include_end_if_at_line_start: bool,
24822 map: &DisplaySnapshot,
24823 ) -> Range<MultiBufferRow> {
24824 let start = self.start.to_point(map.buffer_snapshot());
24825 let mut end = self.end.to_point(map.buffer_snapshot());
24826 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24827 end.row -= 1;
24828 }
24829
24830 let buffer_start = map.prev_line_boundary(start).0;
24831 let buffer_end = map.next_line_boundary(end).0;
24832 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24833 }
24834}
24835
24836impl<T: InvalidationRegion> InvalidationStack<T> {
24837 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24838 where
24839 S: Clone + ToOffset,
24840 {
24841 while let Some(region) = self.last() {
24842 let all_selections_inside_invalidation_ranges =
24843 if selections.len() == region.ranges().len() {
24844 selections
24845 .iter()
24846 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24847 .all(|(selection, invalidation_range)| {
24848 let head = selection.head().to_offset(buffer);
24849 invalidation_range.start <= head && invalidation_range.end >= head
24850 })
24851 } else {
24852 false
24853 };
24854
24855 if all_selections_inside_invalidation_ranges {
24856 break;
24857 } else {
24858 self.pop();
24859 }
24860 }
24861 }
24862}
24863
24864impl<T> Default for InvalidationStack<T> {
24865 fn default() -> Self {
24866 Self(Default::default())
24867 }
24868}
24869
24870impl<T> Deref for InvalidationStack<T> {
24871 type Target = Vec<T>;
24872
24873 fn deref(&self) -> &Self::Target {
24874 &self.0
24875 }
24876}
24877
24878impl<T> DerefMut for InvalidationStack<T> {
24879 fn deref_mut(&mut self) -> &mut Self::Target {
24880 &mut self.0
24881 }
24882}
24883
24884impl InvalidationRegion for SnippetState {
24885 fn ranges(&self) -> &[Range<Anchor>] {
24886 &self.ranges[self.active_index]
24887 }
24888}
24889
24890fn edit_prediction_edit_text(
24891 current_snapshot: &BufferSnapshot,
24892 edits: &[(Range<Anchor>, impl AsRef<str>)],
24893 edit_preview: &EditPreview,
24894 include_deletions: bool,
24895 cx: &App,
24896) -> HighlightedText {
24897 let edits = edits
24898 .iter()
24899 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24900 .collect::<Vec<_>>();
24901
24902 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24903}
24904
24905fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24906 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24907 // Just show the raw edit text with basic styling
24908 let mut text = String::new();
24909 let mut highlights = Vec::new();
24910
24911 let insertion_highlight_style = HighlightStyle {
24912 color: Some(cx.theme().colors().text),
24913 ..Default::default()
24914 };
24915
24916 for (_, edit_text) in edits {
24917 let start_offset = text.len();
24918 text.push_str(edit_text);
24919 let end_offset = text.len();
24920
24921 if start_offset < end_offset {
24922 highlights.push((start_offset..end_offset, insertion_highlight_style));
24923 }
24924 }
24925
24926 HighlightedText {
24927 text: text.into(),
24928 highlights,
24929 }
24930}
24931
24932pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24933 match severity {
24934 lsp::DiagnosticSeverity::ERROR => colors.error,
24935 lsp::DiagnosticSeverity::WARNING => colors.warning,
24936 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24937 lsp::DiagnosticSeverity::HINT => colors.info,
24938 _ => colors.ignored,
24939 }
24940}
24941
24942pub fn styled_runs_for_code_label<'a>(
24943 label: &'a CodeLabel,
24944 syntax_theme: &'a theme::SyntaxTheme,
24945 local_player: &'a theme::PlayerColor,
24946) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24947 let fade_out = HighlightStyle {
24948 fade_out: Some(0.35),
24949 ..Default::default()
24950 };
24951
24952 let mut prev_end = label.filter_range.end;
24953 label
24954 .runs
24955 .iter()
24956 .enumerate()
24957 .flat_map(move |(ix, (range, highlight_id))| {
24958 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
24959 HighlightStyle {
24960 color: Some(local_player.cursor),
24961 ..Default::default()
24962 }
24963 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
24964 HighlightStyle {
24965 background_color: Some(local_player.selection),
24966 ..Default::default()
24967 }
24968 } else if let Some(style) = highlight_id.style(syntax_theme) {
24969 style
24970 } else {
24971 return Default::default();
24972 };
24973 let muted_style = style.highlight(fade_out);
24974
24975 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24976 if range.start >= label.filter_range.end {
24977 if range.start > prev_end {
24978 runs.push((prev_end..range.start, fade_out));
24979 }
24980 runs.push((range.clone(), muted_style));
24981 } else if range.end <= label.filter_range.end {
24982 runs.push((range.clone(), style));
24983 } else {
24984 runs.push((range.start..label.filter_range.end, style));
24985 runs.push((label.filter_range.end..range.end, muted_style));
24986 }
24987 prev_end = cmp::max(prev_end, range.end);
24988
24989 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24990 runs.push((prev_end..label.text.len(), fade_out));
24991 }
24992
24993 runs
24994 })
24995}
24996
24997pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24998 let mut prev_index = 0;
24999 let mut prev_codepoint: Option<char> = None;
25000 text.char_indices()
25001 .chain([(text.len(), '\0')])
25002 .filter_map(move |(index, codepoint)| {
25003 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25004 let is_boundary = index == text.len()
25005 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25006 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25007 if is_boundary {
25008 let chunk = &text[prev_index..index];
25009 prev_index = index;
25010 Some(chunk)
25011 } else {
25012 None
25013 }
25014 })
25015}
25016
25017/// Given a string of text immediately before the cursor, iterates over possible
25018/// strings a snippet could match to. More precisely: returns an iterator over
25019/// suffixes of `text` created by splitting at word boundaries (before & after
25020/// every non-word character).
25021///
25022/// Shorter suffixes are returned first.
25023pub(crate) fn snippet_candidate_suffixes(
25024 text: &str,
25025 is_word_char: impl Fn(char) -> bool,
25026) -> impl std::iter::Iterator<Item = &str> {
25027 let mut prev_index = text.len();
25028 let mut prev_codepoint = None;
25029 text.char_indices()
25030 .rev()
25031 .chain([(0, '\0')])
25032 .filter_map(move |(index, codepoint)| {
25033 let prev_index = std::mem::replace(&mut prev_index, index);
25034 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25035 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25036 None
25037 } else {
25038 let chunk = &text[prev_index..]; // go to end of string
25039 Some(chunk)
25040 }
25041 })
25042}
25043
25044pub trait RangeToAnchorExt: Sized {
25045 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25046
25047 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25048 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25049 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25050 }
25051}
25052
25053impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25054 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25055 let start_offset = self.start.to_offset(snapshot);
25056 let end_offset = self.end.to_offset(snapshot);
25057 if start_offset == end_offset {
25058 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25059 } else {
25060 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25061 }
25062 }
25063}
25064
25065pub trait RowExt {
25066 fn as_f64(&self) -> f64;
25067
25068 fn next_row(&self) -> Self;
25069
25070 fn previous_row(&self) -> Self;
25071
25072 fn minus(&self, other: Self) -> u32;
25073}
25074
25075impl RowExt for DisplayRow {
25076 fn as_f64(&self) -> f64 {
25077 self.0 as _
25078 }
25079
25080 fn next_row(&self) -> Self {
25081 Self(self.0 + 1)
25082 }
25083
25084 fn previous_row(&self) -> Self {
25085 Self(self.0.saturating_sub(1))
25086 }
25087
25088 fn minus(&self, other: Self) -> u32 {
25089 self.0 - other.0
25090 }
25091}
25092
25093impl RowExt for MultiBufferRow {
25094 fn as_f64(&self) -> f64 {
25095 self.0 as _
25096 }
25097
25098 fn next_row(&self) -> Self {
25099 Self(self.0 + 1)
25100 }
25101
25102 fn previous_row(&self) -> Self {
25103 Self(self.0.saturating_sub(1))
25104 }
25105
25106 fn minus(&self, other: Self) -> u32 {
25107 self.0 - other.0
25108 }
25109}
25110
25111trait RowRangeExt {
25112 type Row;
25113
25114 fn len(&self) -> usize;
25115
25116 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25117}
25118
25119impl RowRangeExt for Range<MultiBufferRow> {
25120 type Row = MultiBufferRow;
25121
25122 fn len(&self) -> usize {
25123 (self.end.0 - self.start.0) as usize
25124 }
25125
25126 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25127 (self.start.0..self.end.0).map(MultiBufferRow)
25128 }
25129}
25130
25131impl RowRangeExt for Range<DisplayRow> {
25132 type Row = DisplayRow;
25133
25134 fn len(&self) -> usize {
25135 (self.end.0 - self.start.0) as usize
25136 }
25137
25138 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25139 (self.start.0..self.end.0).map(DisplayRow)
25140 }
25141}
25142
25143/// If select range has more than one line, we
25144/// just point the cursor to range.start.
25145fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25146 if range.start.row == range.end.row {
25147 range
25148 } else {
25149 range.start..range.start
25150 }
25151}
25152pub struct KillRing(ClipboardItem);
25153impl Global for KillRing {}
25154
25155const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25156
25157enum BreakpointPromptEditAction {
25158 Log,
25159 Condition,
25160 HitCondition,
25161}
25162
25163struct BreakpointPromptEditor {
25164 pub(crate) prompt: Entity<Editor>,
25165 editor: WeakEntity<Editor>,
25166 breakpoint_anchor: Anchor,
25167 breakpoint: Breakpoint,
25168 edit_action: BreakpointPromptEditAction,
25169 block_ids: HashSet<CustomBlockId>,
25170 editor_margins: Arc<Mutex<EditorMargins>>,
25171 _subscriptions: Vec<Subscription>,
25172}
25173
25174impl BreakpointPromptEditor {
25175 const MAX_LINES: u8 = 4;
25176
25177 fn new(
25178 editor: WeakEntity<Editor>,
25179 breakpoint_anchor: Anchor,
25180 breakpoint: Breakpoint,
25181 edit_action: BreakpointPromptEditAction,
25182 window: &mut Window,
25183 cx: &mut Context<Self>,
25184 ) -> Self {
25185 let base_text = match edit_action {
25186 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25187 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25188 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25189 }
25190 .map(|msg| msg.to_string())
25191 .unwrap_or_default();
25192
25193 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25194 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25195
25196 let prompt = cx.new(|cx| {
25197 let mut prompt = Editor::new(
25198 EditorMode::AutoHeight {
25199 min_lines: 1,
25200 max_lines: Some(Self::MAX_LINES as usize),
25201 },
25202 buffer,
25203 None,
25204 window,
25205 cx,
25206 );
25207 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25208 prompt.set_show_cursor_when_unfocused(false, cx);
25209 prompt.set_placeholder_text(
25210 match edit_action {
25211 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25212 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25213 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25214 },
25215 window,
25216 cx,
25217 );
25218
25219 prompt
25220 });
25221
25222 Self {
25223 prompt,
25224 editor,
25225 breakpoint_anchor,
25226 breakpoint,
25227 edit_action,
25228 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25229 block_ids: Default::default(),
25230 _subscriptions: vec![],
25231 }
25232 }
25233
25234 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25235 self.block_ids.extend(block_ids)
25236 }
25237
25238 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25239 if let Some(editor) = self.editor.upgrade() {
25240 let message = self
25241 .prompt
25242 .read(cx)
25243 .buffer
25244 .read(cx)
25245 .as_singleton()
25246 .expect("A multi buffer in breakpoint prompt isn't possible")
25247 .read(cx)
25248 .as_rope()
25249 .to_string();
25250
25251 editor.update(cx, |editor, cx| {
25252 editor.edit_breakpoint_at_anchor(
25253 self.breakpoint_anchor,
25254 self.breakpoint.clone(),
25255 match self.edit_action {
25256 BreakpointPromptEditAction::Log => {
25257 BreakpointEditAction::EditLogMessage(message.into())
25258 }
25259 BreakpointPromptEditAction::Condition => {
25260 BreakpointEditAction::EditCondition(message.into())
25261 }
25262 BreakpointPromptEditAction::HitCondition => {
25263 BreakpointEditAction::EditHitCondition(message.into())
25264 }
25265 },
25266 cx,
25267 );
25268
25269 editor.remove_blocks(self.block_ids.clone(), None, cx);
25270 cx.focus_self(window);
25271 });
25272 }
25273 }
25274
25275 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25276 self.editor
25277 .update(cx, |editor, cx| {
25278 editor.remove_blocks(self.block_ids.clone(), None, cx);
25279 window.focus(&editor.focus_handle);
25280 })
25281 .log_err();
25282 }
25283
25284 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25285 let settings = ThemeSettings::get_global(cx);
25286 let text_style = TextStyle {
25287 color: if self.prompt.read(cx).read_only(cx) {
25288 cx.theme().colors().text_disabled
25289 } else {
25290 cx.theme().colors().text
25291 },
25292 font_family: settings.buffer_font.family.clone(),
25293 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25294 font_size: settings.buffer_font_size(cx).into(),
25295 font_weight: settings.buffer_font.weight,
25296 line_height: relative(settings.buffer_line_height.value()),
25297 ..Default::default()
25298 };
25299 EditorElement::new(
25300 &self.prompt,
25301 EditorStyle {
25302 background: cx.theme().colors().editor_background,
25303 local_player: cx.theme().players().local(),
25304 text: text_style,
25305 ..Default::default()
25306 },
25307 )
25308 }
25309}
25310
25311impl Render for BreakpointPromptEditor {
25312 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25313 let editor_margins = *self.editor_margins.lock();
25314 let gutter_dimensions = editor_margins.gutter;
25315 h_flex()
25316 .key_context("Editor")
25317 .bg(cx.theme().colors().editor_background)
25318 .border_y_1()
25319 .border_color(cx.theme().status().info_border)
25320 .size_full()
25321 .py(window.line_height() / 2.5)
25322 .on_action(cx.listener(Self::confirm))
25323 .on_action(cx.listener(Self::cancel))
25324 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25325 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25326 }
25327}
25328
25329impl Focusable for BreakpointPromptEditor {
25330 fn focus_handle(&self, cx: &App) -> FocusHandle {
25331 self.prompt.focus_handle(cx)
25332 }
25333}
25334
25335fn all_edits_insertions_or_deletions(
25336 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25337 snapshot: &MultiBufferSnapshot,
25338) -> bool {
25339 let mut all_insertions = true;
25340 let mut all_deletions = true;
25341
25342 for (range, new_text) in edits.iter() {
25343 let range_is_empty = range.to_offset(snapshot).is_empty();
25344 let text_is_empty = new_text.is_empty();
25345
25346 if range_is_empty != text_is_empty {
25347 if range_is_empty {
25348 all_deletions = false;
25349 } else {
25350 all_insertions = false;
25351 }
25352 } else {
25353 return false;
25354 }
25355
25356 if !all_insertions && !all_deletions {
25357 return false;
25358 }
25359 }
25360 all_insertions || all_deletions
25361}
25362
25363struct MissingEditPredictionKeybindingTooltip;
25364
25365impl Render for MissingEditPredictionKeybindingTooltip {
25366 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25367 ui::tooltip_container(cx, |container, cx| {
25368 container
25369 .flex_shrink_0()
25370 .max_w_80()
25371 .min_h(rems_from_px(124.))
25372 .justify_between()
25373 .child(
25374 v_flex()
25375 .flex_1()
25376 .text_ui_sm(cx)
25377 .child(Label::new("Conflict with Accept Keybinding"))
25378 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25379 )
25380 .child(
25381 h_flex()
25382 .pb_1()
25383 .gap_1()
25384 .items_end()
25385 .w_full()
25386 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25387 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25388 }))
25389 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25390 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25391 })),
25392 )
25393 })
25394 }
25395}
25396
25397#[derive(Debug, Clone, Copy, PartialEq)]
25398pub struct LineHighlight {
25399 pub background: Background,
25400 pub border: Option<gpui::Hsla>,
25401 pub include_gutter: bool,
25402 pub type_id: Option<TypeId>,
25403}
25404
25405struct LineManipulationResult {
25406 pub new_text: String,
25407 pub line_count_before: usize,
25408 pub line_count_after: usize,
25409}
25410
25411fn render_diff_hunk_controls(
25412 row: u32,
25413 status: &DiffHunkStatus,
25414 hunk_range: Range<Anchor>,
25415 is_created_file: bool,
25416 line_height: Pixels,
25417 editor: &Entity<Editor>,
25418 _window: &mut Window,
25419 cx: &mut App,
25420) -> AnyElement {
25421 h_flex()
25422 .h(line_height)
25423 .mr_1()
25424 .gap_1()
25425 .px_0p5()
25426 .pb_1()
25427 .border_x_1()
25428 .border_b_1()
25429 .border_color(cx.theme().colors().border_variant)
25430 .rounded_b_lg()
25431 .bg(cx.theme().colors().editor_background)
25432 .gap_1()
25433 .block_mouse_except_scroll()
25434 .shadow_md()
25435 .child(if status.has_secondary_hunk() {
25436 Button::new(("stage", row as u64), "Stage")
25437 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25438 .tooltip({
25439 let focus_handle = editor.focus_handle(cx);
25440 move |_window, cx| {
25441 Tooltip::for_action_in(
25442 "Stage Hunk",
25443 &::git::ToggleStaged,
25444 &focus_handle,
25445 cx,
25446 )
25447 }
25448 })
25449 .on_click({
25450 let editor = editor.clone();
25451 move |_event, _window, cx| {
25452 editor.update(cx, |editor, cx| {
25453 editor.stage_or_unstage_diff_hunks(
25454 true,
25455 vec![hunk_range.start..hunk_range.start],
25456 cx,
25457 );
25458 });
25459 }
25460 })
25461 } else {
25462 Button::new(("unstage", row as u64), "Unstage")
25463 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25464 .tooltip({
25465 let focus_handle = editor.focus_handle(cx);
25466 move |_window, cx| {
25467 Tooltip::for_action_in(
25468 "Unstage Hunk",
25469 &::git::ToggleStaged,
25470 &focus_handle,
25471 cx,
25472 )
25473 }
25474 })
25475 .on_click({
25476 let editor = editor.clone();
25477 move |_event, _window, cx| {
25478 editor.update(cx, |editor, cx| {
25479 editor.stage_or_unstage_diff_hunks(
25480 false,
25481 vec![hunk_range.start..hunk_range.start],
25482 cx,
25483 );
25484 });
25485 }
25486 })
25487 })
25488 .child(
25489 Button::new(("restore", row as u64), "Restore")
25490 .tooltip({
25491 let focus_handle = editor.focus_handle(cx);
25492 move |_window, cx| {
25493 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25494 }
25495 })
25496 .on_click({
25497 let editor = editor.clone();
25498 move |_event, window, cx| {
25499 editor.update(cx, |editor, cx| {
25500 let snapshot = editor.snapshot(window, cx);
25501 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25502 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25503 });
25504 }
25505 })
25506 .disabled(is_created_file),
25507 )
25508 .when(
25509 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25510 |el| {
25511 el.child(
25512 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25513 .shape(IconButtonShape::Square)
25514 .icon_size(IconSize::Small)
25515 // .disabled(!has_multiple_hunks)
25516 .tooltip({
25517 let focus_handle = editor.focus_handle(cx);
25518 move |_window, cx| {
25519 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25520 }
25521 })
25522 .on_click({
25523 let editor = editor.clone();
25524 move |_event, window, cx| {
25525 editor.update(cx, |editor, cx| {
25526 let snapshot = editor.snapshot(window, cx);
25527 let position =
25528 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25529 editor.go_to_hunk_before_or_after_position(
25530 &snapshot,
25531 position,
25532 Direction::Next,
25533 window,
25534 cx,
25535 );
25536 editor.expand_selected_diff_hunks(cx);
25537 });
25538 }
25539 }),
25540 )
25541 .child(
25542 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25543 .shape(IconButtonShape::Square)
25544 .icon_size(IconSize::Small)
25545 // .disabled(!has_multiple_hunks)
25546 .tooltip({
25547 let focus_handle = editor.focus_handle(cx);
25548 move |_window, cx| {
25549 Tooltip::for_action_in(
25550 "Previous Hunk",
25551 &GoToPreviousHunk,
25552 &focus_handle,
25553 cx,
25554 )
25555 }
25556 })
25557 .on_click({
25558 let editor = editor.clone();
25559 move |_event, window, cx| {
25560 editor.update(cx, |editor, cx| {
25561 let snapshot = editor.snapshot(window, cx);
25562 let point =
25563 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25564 editor.go_to_hunk_before_or_after_position(
25565 &snapshot,
25566 point,
25567 Direction::Prev,
25568 window,
25569 cx,
25570 );
25571 editor.expand_selected_diff_hunks(cx);
25572 });
25573 }
25574 }),
25575 )
25576 },
25577 )
25578 .into_any_element()
25579}
25580
25581pub fn multibuffer_context_lines(cx: &App) -> u32 {
25582 EditorSettings::try_get(cx)
25583 .map(|settings| settings.excerpt_context_lines)
25584 .unwrap_or(2)
25585 .min(32)
25586}