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, LspAction, PrepareRenameResponse, Project,
150 ProjectItem, 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 menu_is_open = matches!(
5513 self.context_menu.borrow().as_ref(),
5514 Some(CodeContextMenu::Completions(_))
5515 );
5516
5517 let language = buffer_snapshot
5518 .language_at(buffer_position.text_anchor)
5519 .map(|language| language.name());
5520
5521 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5522 let completion_settings = language_settings.completions.clone();
5523
5524 if !menu_is_open && trigger.is_some() && !language_settings.show_completions_on_input {
5525 return;
5526 }
5527
5528 let query: Option<Arc<String>> =
5529 Self::completion_query(&multibuffer_snapshot, buffer_position)
5530 .map(|query| query.into());
5531
5532 drop(multibuffer_snapshot);
5533
5534 // Hide the current completions menu when query is empty. Without this, cached
5535 // completions from before the trigger char may be reused (#32774).
5536 if query.is_none() && menu_is_open {
5537 self.hide_context_menu(window, cx);
5538 }
5539
5540 let mut ignore_word_threshold = false;
5541 let provider = match requested_source {
5542 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5543 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5544 ignore_word_threshold = ignore_threshold;
5545 None
5546 }
5547 Some(CompletionsMenuSource::SnippetChoices)
5548 | Some(CompletionsMenuSource::SnippetsOnly) => {
5549 log::error!("bug: SnippetChoices requested_source is not handled");
5550 None
5551 }
5552 };
5553
5554 let sort_completions = provider
5555 .as_ref()
5556 .is_some_and(|provider| provider.sort_completions());
5557
5558 let filter_completions = provider
5559 .as_ref()
5560 .is_none_or(|provider| provider.filter_completions());
5561
5562 let was_snippets_only = matches!(
5563 completions_source,
5564 Some(CompletionsMenuSource::SnippetsOnly)
5565 );
5566
5567 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5568 if filter_completions {
5569 menu.filter(
5570 query.clone().unwrap_or_default(),
5571 buffer_position.text_anchor,
5572 &buffer,
5573 provider.clone(),
5574 window,
5575 cx,
5576 );
5577 }
5578 // When `is_incomplete` is false, no need to re-query completions when the current query
5579 // is a suffix of the initial query.
5580 let was_complete = !menu.is_incomplete;
5581 if was_complete && !was_snippets_only {
5582 // If the new query is a suffix of the old query (typing more characters) and
5583 // the previous result was complete, the existing completions can be filtered.
5584 //
5585 // Note that snippet completions are always complete.
5586 let query_matches = match (&menu.initial_query, &query) {
5587 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5588 (None, _) => true,
5589 _ => false,
5590 };
5591 if query_matches {
5592 let position_matches = if menu.initial_position == position {
5593 true
5594 } else {
5595 let snapshot = self.buffer.read(cx).read(cx);
5596 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5597 };
5598 if position_matches {
5599 return;
5600 }
5601 }
5602 }
5603 };
5604
5605 let Anchor {
5606 excerpt_id: buffer_excerpt_id,
5607 text_anchor: buffer_position,
5608 ..
5609 } = buffer_position;
5610
5611 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5612 buffer_snapshot.surrounding_word(buffer_position, None)
5613 {
5614 let word_to_exclude = buffer_snapshot
5615 .text_for_range(word_range.clone())
5616 .collect::<String>();
5617 (
5618 buffer_snapshot.anchor_before(word_range.start)
5619 ..buffer_snapshot.anchor_after(buffer_position),
5620 Some(word_to_exclude),
5621 )
5622 } else {
5623 (buffer_position..buffer_position, None)
5624 };
5625
5626 let show_completion_documentation = buffer_snapshot
5627 .settings_at(buffer_position, cx)
5628 .show_completion_documentation;
5629
5630 // The document can be large, so stay in reasonable bounds when searching for words,
5631 // otherwise completion pop-up might be slow to appear.
5632 const WORD_LOOKUP_ROWS: u32 = 5_000;
5633 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5634 let min_word_search = buffer_snapshot.clip_point(
5635 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5636 Bias::Left,
5637 );
5638 let max_word_search = buffer_snapshot.clip_point(
5639 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5640 Bias::Right,
5641 );
5642 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5643 ..buffer_snapshot.point_to_offset(max_word_search);
5644
5645 let skip_digits = query
5646 .as_ref()
5647 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5648
5649 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5650 trigger.as_ref().is_none_or(|trigger| {
5651 provider.is_completion_trigger(
5652 &buffer,
5653 position.text_anchor,
5654 trigger,
5655 trigger_in_words,
5656 cx,
5657 )
5658 })
5659 });
5660
5661 let provider_responses = if let Some(provider) = &provider
5662 && load_provider_completions
5663 {
5664 let trigger_character =
5665 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5666 let completion_context = CompletionContext {
5667 trigger_kind: match &trigger_character {
5668 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5669 None => CompletionTriggerKind::INVOKED,
5670 },
5671 trigger_character,
5672 };
5673
5674 provider.completions(
5675 buffer_excerpt_id,
5676 &buffer,
5677 buffer_position,
5678 completion_context,
5679 window,
5680 cx,
5681 )
5682 } else {
5683 Task::ready(Ok(Vec::new()))
5684 };
5685
5686 let load_word_completions = if !self.word_completions_enabled {
5687 false
5688 } else if requested_source
5689 == Some(CompletionsMenuSource::Words {
5690 ignore_threshold: true,
5691 })
5692 {
5693 true
5694 } else {
5695 load_provider_completions
5696 && completion_settings.words != WordsCompletionMode::Disabled
5697 && (ignore_word_threshold || {
5698 let words_min_length = completion_settings.words_min_length;
5699 // check whether word has at least `words_min_length` characters
5700 let query_chars = query.iter().flat_map(|q| q.chars());
5701 query_chars.take(words_min_length).count() == words_min_length
5702 })
5703 };
5704
5705 let mut words = if load_word_completions {
5706 cx.background_spawn({
5707 let buffer_snapshot = buffer_snapshot.clone();
5708 async move {
5709 buffer_snapshot.words_in_range(WordsQuery {
5710 fuzzy_contents: None,
5711 range: word_search_range,
5712 skip_digits,
5713 })
5714 }
5715 })
5716 } else {
5717 Task::ready(BTreeMap::default())
5718 };
5719
5720 let snippets = if let Some(provider) = &provider
5721 && provider.show_snippets()
5722 && let Some(project) = self.project()
5723 {
5724 let char_classifier = buffer_snapshot
5725 .char_classifier_at(buffer_position)
5726 .scope_context(Some(CharScopeContext::Completion));
5727 project.update(cx, |project, cx| {
5728 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5729 })
5730 } else {
5731 Task::ready(Ok(CompletionResponse {
5732 completions: Vec::new(),
5733 display_options: Default::default(),
5734 is_incomplete: false,
5735 }))
5736 };
5737
5738 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5739
5740 let id = post_inc(&mut self.next_completion_id);
5741 let task = cx.spawn_in(window, async move |editor, cx| {
5742 let Ok(()) = editor.update(cx, |this, _| {
5743 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5744 }) else {
5745 return;
5746 };
5747
5748 // TODO: Ideally completions from different sources would be selectively re-queried, so
5749 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5750 let mut completions = Vec::new();
5751 let mut is_incomplete = false;
5752 let mut display_options: Option<CompletionDisplayOptions> = None;
5753 if let Some(provider_responses) = provider_responses.await.log_err()
5754 && !provider_responses.is_empty()
5755 {
5756 for response in provider_responses {
5757 completions.extend(response.completions);
5758 is_incomplete = is_incomplete || response.is_incomplete;
5759 match display_options.as_mut() {
5760 None => {
5761 display_options = Some(response.display_options);
5762 }
5763 Some(options) => options.merge(&response.display_options),
5764 }
5765 }
5766 if completion_settings.words == WordsCompletionMode::Fallback {
5767 words = Task::ready(BTreeMap::default());
5768 }
5769 }
5770 let display_options = display_options.unwrap_or_default();
5771
5772 let mut words = words.await;
5773 if let Some(word_to_exclude) = &word_to_exclude {
5774 words.remove(word_to_exclude);
5775 }
5776 for lsp_completion in &completions {
5777 words.remove(&lsp_completion.new_text);
5778 }
5779 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5780 replace_range: word_replace_range.clone(),
5781 new_text: word.clone(),
5782 label: CodeLabel::plain(word, None),
5783 match_start: None,
5784 snippet_deduplication_key: None,
5785 icon_path: None,
5786 documentation: None,
5787 source: CompletionSource::BufferWord {
5788 word_range,
5789 resolved: false,
5790 },
5791 insert_text_mode: Some(InsertTextMode::AS_IS),
5792 confirm: None,
5793 }));
5794
5795 completions.extend(
5796 snippets
5797 .await
5798 .into_iter()
5799 .flat_map(|response| response.completions),
5800 );
5801
5802 let menu = if completions.is_empty() {
5803 None
5804 } else {
5805 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5806 let languages = editor
5807 .workspace
5808 .as_ref()
5809 .and_then(|(workspace, _)| workspace.upgrade())
5810 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5811 let menu = CompletionsMenu::new(
5812 id,
5813 requested_source.unwrap_or(if load_provider_completions {
5814 CompletionsMenuSource::Normal
5815 } else {
5816 CompletionsMenuSource::SnippetsOnly
5817 }),
5818 sort_completions,
5819 show_completion_documentation,
5820 position,
5821 query.clone(),
5822 is_incomplete,
5823 buffer.clone(),
5824 completions.into(),
5825 display_options,
5826 snippet_sort_order,
5827 languages,
5828 language,
5829 cx,
5830 );
5831
5832 let query = if filter_completions { query } else { None };
5833 let matches_task = menu.do_async_filtering(
5834 query.unwrap_or_default(),
5835 buffer_position,
5836 &buffer,
5837 cx,
5838 );
5839 (menu, matches_task)
5840 }) else {
5841 return;
5842 };
5843
5844 let matches = matches_task.await;
5845
5846 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5847 // Newer menu already set, so exit.
5848 if let Some(CodeContextMenu::Completions(prev_menu)) =
5849 editor.context_menu.borrow().as_ref()
5850 && prev_menu.id > id
5851 {
5852 return;
5853 };
5854
5855 // Only valid to take prev_menu because either the new menu is immediately set
5856 // below, or the menu is hidden.
5857 if let Some(CodeContextMenu::Completions(prev_menu)) =
5858 editor.context_menu.borrow_mut().take()
5859 {
5860 let position_matches =
5861 if prev_menu.initial_position == menu.initial_position {
5862 true
5863 } else {
5864 let snapshot = editor.buffer.read(cx).read(cx);
5865 prev_menu.initial_position.to_offset(&snapshot)
5866 == menu.initial_position.to_offset(&snapshot)
5867 };
5868 if position_matches {
5869 // Preserve markdown cache before `set_filter_results` because it will
5870 // try to populate the documentation cache.
5871 menu.preserve_markdown_cache(prev_menu);
5872 }
5873 };
5874
5875 menu.set_filter_results(matches, provider, window, cx);
5876 }) else {
5877 return;
5878 };
5879
5880 menu.visible().then_some(menu)
5881 };
5882
5883 editor
5884 .update_in(cx, |editor, window, cx| {
5885 if editor.focus_handle.is_focused(window)
5886 && let Some(menu) = menu
5887 {
5888 *editor.context_menu.borrow_mut() =
5889 Some(CodeContextMenu::Completions(menu));
5890
5891 crate::hover_popover::hide_hover(editor, cx);
5892 if editor.show_edit_predictions_in_menu() {
5893 editor.update_visible_edit_prediction(window, cx);
5894 } else {
5895 editor.discard_edit_prediction(false, cx);
5896 }
5897
5898 cx.notify();
5899 return;
5900 }
5901
5902 if editor.completion_tasks.len() <= 1 {
5903 // If there are no more completion tasks and the last menu was empty, we should hide it.
5904 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5905 // If it was already hidden and we don't show edit predictions in the menu,
5906 // we should also show the edit prediction when available.
5907 if was_hidden && editor.show_edit_predictions_in_menu() {
5908 editor.update_visible_edit_prediction(window, cx);
5909 }
5910 }
5911 })
5912 .ok();
5913 });
5914
5915 self.completion_tasks.push((id, task));
5916 }
5917
5918 #[cfg(feature = "test-support")]
5919 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5920 let menu = self.context_menu.borrow();
5921 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5922 let completions = menu.completions.borrow();
5923 Some(completions.to_vec())
5924 } else {
5925 None
5926 }
5927 }
5928
5929 pub fn with_completions_menu_matching_id<R>(
5930 &self,
5931 id: CompletionId,
5932 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5933 ) -> R {
5934 let mut context_menu = self.context_menu.borrow_mut();
5935 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5936 return f(None);
5937 };
5938 if completions_menu.id != id {
5939 return f(None);
5940 }
5941 f(Some(completions_menu))
5942 }
5943
5944 pub fn confirm_completion(
5945 &mut self,
5946 action: &ConfirmCompletion,
5947 window: &mut Window,
5948 cx: &mut Context<Self>,
5949 ) -> Option<Task<Result<()>>> {
5950 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5951 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5952 }
5953
5954 pub fn confirm_completion_insert(
5955 &mut self,
5956 _: &ConfirmCompletionInsert,
5957 window: &mut Window,
5958 cx: &mut Context<Self>,
5959 ) -> Option<Task<Result<()>>> {
5960 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5961 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5962 }
5963
5964 pub fn confirm_completion_replace(
5965 &mut self,
5966 _: &ConfirmCompletionReplace,
5967 window: &mut Window,
5968 cx: &mut Context<Self>,
5969 ) -> Option<Task<Result<()>>> {
5970 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5971 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5972 }
5973
5974 pub fn compose_completion(
5975 &mut self,
5976 action: &ComposeCompletion,
5977 window: &mut Window,
5978 cx: &mut Context<Self>,
5979 ) -> Option<Task<Result<()>>> {
5980 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5981 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5982 }
5983
5984 fn do_completion(
5985 &mut self,
5986 item_ix: Option<usize>,
5987 intent: CompletionIntent,
5988 window: &mut Window,
5989 cx: &mut Context<Editor>,
5990 ) -> Option<Task<Result<()>>> {
5991 use language::ToOffset as _;
5992
5993 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5994 else {
5995 return None;
5996 };
5997
5998 let candidate_id = {
5999 let entries = completions_menu.entries.borrow();
6000 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6001 if self.show_edit_predictions_in_menu() {
6002 self.discard_edit_prediction(true, cx);
6003 }
6004 mat.candidate_id
6005 };
6006
6007 let completion = completions_menu
6008 .completions
6009 .borrow()
6010 .get(candidate_id)?
6011 .clone();
6012 cx.stop_propagation();
6013
6014 let buffer_handle = completions_menu.buffer.clone();
6015
6016 let CompletionEdit {
6017 new_text,
6018 snippet,
6019 replace_range,
6020 } = process_completion_for_edit(
6021 &completion,
6022 intent,
6023 &buffer_handle,
6024 &completions_menu.initial_position.text_anchor,
6025 cx,
6026 );
6027
6028 let buffer = buffer_handle.read(cx);
6029 let snapshot = self.buffer.read(cx).snapshot(cx);
6030 let newest_anchor = self.selections.newest_anchor();
6031 let replace_range_multibuffer = {
6032 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6033 excerpt.map_range_from_buffer(replace_range.clone())
6034 };
6035 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6036 return None;
6037 }
6038
6039 let old_text = buffer
6040 .text_for_range(replace_range.clone())
6041 .collect::<String>();
6042 let lookbehind = newest_anchor
6043 .start
6044 .text_anchor
6045 .to_offset(buffer)
6046 .saturating_sub(replace_range.start.0);
6047 let lookahead = replace_range
6048 .end
6049 .0
6050 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6051 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6052 let suffix = &old_text[lookbehind.min(old_text.len())..];
6053
6054 let selections = self
6055 .selections
6056 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6057 let mut ranges = Vec::new();
6058 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6059
6060 for selection in &selections {
6061 let range = if selection.id == newest_anchor.id {
6062 replace_range_multibuffer.clone()
6063 } else {
6064 let mut range = selection.range();
6065
6066 // if prefix is present, don't duplicate it
6067 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6068 range.start = range.start.saturating_sub_usize(lookbehind);
6069
6070 // if suffix is also present, mimic the newest cursor and replace it
6071 if selection.id != newest_anchor.id
6072 && snapshot.contains_str_at(range.end, suffix)
6073 {
6074 range.end += lookahead;
6075 }
6076 }
6077 range
6078 };
6079
6080 ranges.push(range.clone());
6081
6082 if !self.linked_edit_ranges.is_empty() {
6083 let start_anchor = snapshot.anchor_before(range.start);
6084 let end_anchor = snapshot.anchor_after(range.end);
6085 if let Some(ranges) = self
6086 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6087 {
6088 for (buffer, edits) in ranges {
6089 linked_edits
6090 .entry(buffer.clone())
6091 .or_default()
6092 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6093 }
6094 }
6095 }
6096 }
6097
6098 let common_prefix_len = old_text
6099 .chars()
6100 .zip(new_text.chars())
6101 .take_while(|(a, b)| a == b)
6102 .map(|(a, _)| a.len_utf8())
6103 .sum::<usize>();
6104
6105 cx.emit(EditorEvent::InputHandled {
6106 utf16_range_to_replace: None,
6107 text: new_text[common_prefix_len..].into(),
6108 });
6109
6110 self.transact(window, cx, |editor, window, cx| {
6111 if let Some(mut snippet) = snippet {
6112 snippet.text = new_text.to_string();
6113 editor
6114 .insert_snippet(&ranges, snippet, window, cx)
6115 .log_err();
6116 } else {
6117 editor.buffer.update(cx, |multi_buffer, cx| {
6118 let auto_indent = match completion.insert_text_mode {
6119 Some(InsertTextMode::AS_IS) => None,
6120 _ => editor.autoindent_mode.clone(),
6121 };
6122 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6123 multi_buffer.edit(edits, auto_indent, cx);
6124 });
6125 }
6126 for (buffer, edits) in linked_edits {
6127 buffer.update(cx, |buffer, cx| {
6128 let snapshot = buffer.snapshot();
6129 let edits = edits
6130 .into_iter()
6131 .map(|(range, text)| {
6132 use text::ToPoint as TP;
6133 let end_point = TP::to_point(&range.end, &snapshot);
6134 let start_point = TP::to_point(&range.start, &snapshot);
6135 (start_point..end_point, text)
6136 })
6137 .sorted_by_key(|(range, _)| range.start);
6138 buffer.edit(edits, None, cx);
6139 })
6140 }
6141
6142 editor.refresh_edit_prediction(true, false, window, cx);
6143 });
6144 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6145
6146 let show_new_completions_on_confirm = completion
6147 .confirm
6148 .as_ref()
6149 .is_some_and(|confirm| confirm(intent, window, cx));
6150 if show_new_completions_on_confirm {
6151 self.open_or_update_completions_menu(None, None, false, window, cx);
6152 }
6153
6154 let provider = self.completion_provider.as_ref()?;
6155
6156 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6157 let command = lsp_store.as_ref().and_then(|lsp_store| {
6158 let CompletionSource::Lsp {
6159 lsp_completion,
6160 server_id,
6161 ..
6162 } = &completion.source
6163 else {
6164 return None;
6165 };
6166 let lsp_command = lsp_completion.command.as_ref()?;
6167 let available_commands = lsp_store
6168 .read(cx)
6169 .lsp_server_capabilities
6170 .get(server_id)
6171 .and_then(|server_capabilities| {
6172 server_capabilities
6173 .execute_command_provider
6174 .as_ref()
6175 .map(|options| options.commands.as_slice())
6176 })?;
6177 if available_commands.contains(&lsp_command.command) {
6178 Some(CodeAction {
6179 server_id: *server_id,
6180 range: language::Anchor::MIN..language::Anchor::MIN,
6181 lsp_action: LspAction::Command(lsp_command.clone()),
6182 resolved: false,
6183 })
6184 } else {
6185 None
6186 }
6187 });
6188
6189 drop(completion);
6190 let apply_edits = provider.apply_additional_edits_for_completion(
6191 buffer_handle.clone(),
6192 completions_menu.completions.clone(),
6193 candidate_id,
6194 true,
6195 cx,
6196 );
6197
6198 let editor_settings = EditorSettings::get_global(cx);
6199 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6200 // After the code completion is finished, users often want to know what signatures are needed.
6201 // so we should automatically call signature_help
6202 self.show_signature_help(&ShowSignatureHelp, window, cx);
6203 }
6204
6205 Some(cx.spawn_in(window, async move |editor, cx| {
6206 apply_edits.await?;
6207
6208 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6209 let title = command.lsp_action.title().to_owned();
6210 let project_transaction = lsp_store
6211 .update(cx, |lsp_store, cx| {
6212 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6213 })?
6214 .await
6215 .context("applying post-completion command")?;
6216 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6217 Self::open_project_transaction(
6218 &editor,
6219 workspace.downgrade(),
6220 project_transaction,
6221 title,
6222 cx,
6223 )
6224 .await?;
6225 }
6226 }
6227
6228 Ok(())
6229 }))
6230 }
6231
6232 pub fn toggle_code_actions(
6233 &mut self,
6234 action: &ToggleCodeActions,
6235 window: &mut Window,
6236 cx: &mut Context<Self>,
6237 ) {
6238 let quick_launch = action.quick_launch;
6239 let mut context_menu = self.context_menu.borrow_mut();
6240 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6241 if code_actions.deployed_from == action.deployed_from {
6242 // Toggle if we're selecting the same one
6243 *context_menu = None;
6244 cx.notify();
6245 return;
6246 } else {
6247 // Otherwise, clear it and start a new one
6248 *context_menu = None;
6249 cx.notify();
6250 }
6251 }
6252 drop(context_menu);
6253 let snapshot = self.snapshot(window, cx);
6254 let deployed_from = action.deployed_from.clone();
6255 let action = action.clone();
6256 self.completion_tasks.clear();
6257 self.discard_edit_prediction(false, cx);
6258
6259 let multibuffer_point = match &action.deployed_from {
6260 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6261 DisplayPoint::new(*row, 0).to_point(&snapshot)
6262 }
6263 _ => self
6264 .selections
6265 .newest::<Point>(&snapshot.display_snapshot)
6266 .head(),
6267 };
6268 let Some((buffer, buffer_row)) = snapshot
6269 .buffer_snapshot()
6270 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6271 .and_then(|(buffer_snapshot, range)| {
6272 self.buffer()
6273 .read(cx)
6274 .buffer(buffer_snapshot.remote_id())
6275 .map(|buffer| (buffer, range.start.row))
6276 })
6277 else {
6278 return;
6279 };
6280 let buffer_id = buffer.read(cx).remote_id();
6281 let tasks = self
6282 .tasks
6283 .get(&(buffer_id, buffer_row))
6284 .map(|t| Arc::new(t.to_owned()));
6285
6286 if !self.focus_handle.is_focused(window) {
6287 return;
6288 }
6289 let project = self.project.clone();
6290
6291 let code_actions_task = match deployed_from {
6292 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6293 _ => self.code_actions(buffer_row, window, cx),
6294 };
6295
6296 let runnable_task = match deployed_from {
6297 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6298 _ => {
6299 let mut task_context_task = Task::ready(None);
6300 if let Some(tasks) = &tasks
6301 && let Some(project) = project
6302 {
6303 task_context_task =
6304 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6305 }
6306
6307 cx.spawn_in(window, {
6308 let buffer = buffer.clone();
6309 async move |editor, cx| {
6310 let task_context = task_context_task.await;
6311
6312 let resolved_tasks =
6313 tasks
6314 .zip(task_context.clone())
6315 .map(|(tasks, task_context)| ResolvedTasks {
6316 templates: tasks.resolve(&task_context).collect(),
6317 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6318 multibuffer_point.row,
6319 tasks.column,
6320 )),
6321 });
6322 let debug_scenarios = editor
6323 .update(cx, |editor, cx| {
6324 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6325 })?
6326 .await;
6327 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6328 }
6329 })
6330 }
6331 };
6332
6333 cx.spawn_in(window, async move |editor, cx| {
6334 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6335 let code_actions = code_actions_task.await;
6336 let spawn_straight_away = quick_launch
6337 && resolved_tasks
6338 .as_ref()
6339 .is_some_and(|tasks| tasks.templates.len() == 1)
6340 && code_actions
6341 .as_ref()
6342 .is_none_or(|actions| actions.is_empty())
6343 && debug_scenarios.is_empty();
6344
6345 editor.update_in(cx, |editor, window, cx| {
6346 crate::hover_popover::hide_hover(editor, cx);
6347 let actions = CodeActionContents::new(
6348 resolved_tasks,
6349 code_actions,
6350 debug_scenarios,
6351 task_context.unwrap_or_default(),
6352 );
6353
6354 // Don't show the menu if there are no actions available
6355 if actions.is_empty() {
6356 cx.notify();
6357 return Task::ready(Ok(()));
6358 }
6359
6360 *editor.context_menu.borrow_mut() =
6361 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6362 buffer,
6363 actions,
6364 selected_item: Default::default(),
6365 scroll_handle: UniformListScrollHandle::default(),
6366 deployed_from,
6367 }));
6368 cx.notify();
6369 if spawn_straight_away
6370 && let Some(task) = editor.confirm_code_action(
6371 &ConfirmCodeAction { item_ix: Some(0) },
6372 window,
6373 cx,
6374 )
6375 {
6376 return task;
6377 }
6378
6379 Task::ready(Ok(()))
6380 })
6381 })
6382 .detach_and_log_err(cx);
6383 }
6384
6385 fn debug_scenarios(
6386 &mut self,
6387 resolved_tasks: &Option<ResolvedTasks>,
6388 buffer: &Entity<Buffer>,
6389 cx: &mut App,
6390 ) -> Task<Vec<task::DebugScenario>> {
6391 maybe!({
6392 let project = self.project()?;
6393 let dap_store = project.read(cx).dap_store();
6394 let mut scenarios = vec![];
6395 let resolved_tasks = resolved_tasks.as_ref()?;
6396 let buffer = buffer.read(cx);
6397 let language = buffer.language()?;
6398 let file = buffer.file();
6399 let debug_adapter = language_settings(language.name().into(), file, cx)
6400 .debuggers
6401 .first()
6402 .map(SharedString::from)
6403 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6404
6405 dap_store.update(cx, |dap_store, cx| {
6406 for (_, task) in &resolved_tasks.templates {
6407 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6408 task.original_task().clone(),
6409 debug_adapter.clone().into(),
6410 task.display_label().to_owned().into(),
6411 cx,
6412 );
6413 scenarios.push(maybe_scenario);
6414 }
6415 });
6416 Some(cx.background_spawn(async move {
6417 futures::future::join_all(scenarios)
6418 .await
6419 .into_iter()
6420 .flatten()
6421 .collect::<Vec<_>>()
6422 }))
6423 })
6424 .unwrap_or_else(|| Task::ready(vec![]))
6425 }
6426
6427 fn code_actions(
6428 &mut self,
6429 buffer_row: u32,
6430 window: &mut Window,
6431 cx: &mut Context<Self>,
6432 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6433 let mut task = self.code_actions_task.take();
6434 cx.spawn_in(window, async move |editor, cx| {
6435 while let Some(prev_task) = task {
6436 prev_task.await.log_err();
6437 task = editor
6438 .update(cx, |this, _| this.code_actions_task.take())
6439 .ok()?;
6440 }
6441
6442 editor
6443 .update(cx, |editor, cx| {
6444 editor
6445 .available_code_actions
6446 .clone()
6447 .and_then(|(location, code_actions)| {
6448 let snapshot = location.buffer.read(cx).snapshot();
6449 let point_range = location.range.to_point(&snapshot);
6450 let point_range = point_range.start.row..=point_range.end.row;
6451 if point_range.contains(&buffer_row) {
6452 Some(code_actions)
6453 } else {
6454 None
6455 }
6456 })
6457 })
6458 .ok()
6459 .flatten()
6460 })
6461 }
6462
6463 pub fn confirm_code_action(
6464 &mut self,
6465 action: &ConfirmCodeAction,
6466 window: &mut Window,
6467 cx: &mut Context<Self>,
6468 ) -> Option<Task<Result<()>>> {
6469 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6470
6471 let actions_menu =
6472 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6473 menu
6474 } else {
6475 return None;
6476 };
6477
6478 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6479 let action = actions_menu.actions.get(action_ix)?;
6480 let title = action.label();
6481 let buffer = actions_menu.buffer;
6482 let workspace = self.workspace()?;
6483
6484 match action {
6485 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6486 workspace.update(cx, |workspace, cx| {
6487 workspace.schedule_resolved_task(
6488 task_source_kind,
6489 resolved_task,
6490 false,
6491 window,
6492 cx,
6493 );
6494
6495 Some(Task::ready(Ok(())))
6496 })
6497 }
6498 CodeActionsItem::CodeAction {
6499 excerpt_id,
6500 action,
6501 provider,
6502 } => {
6503 let apply_code_action =
6504 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6505 let workspace = workspace.downgrade();
6506 Some(cx.spawn_in(window, async move |editor, cx| {
6507 let project_transaction = apply_code_action.await?;
6508 Self::open_project_transaction(
6509 &editor,
6510 workspace,
6511 project_transaction,
6512 title,
6513 cx,
6514 )
6515 .await
6516 }))
6517 }
6518 CodeActionsItem::DebugScenario(scenario) => {
6519 let context = actions_menu.actions.context;
6520
6521 workspace.update(cx, |workspace, cx| {
6522 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6523 workspace.start_debug_session(
6524 scenario,
6525 context,
6526 Some(buffer),
6527 None,
6528 window,
6529 cx,
6530 );
6531 });
6532 Some(Task::ready(Ok(())))
6533 }
6534 }
6535 }
6536
6537 pub async fn open_project_transaction(
6538 editor: &WeakEntity<Editor>,
6539 workspace: WeakEntity<Workspace>,
6540 transaction: ProjectTransaction,
6541 title: String,
6542 cx: &mut AsyncWindowContext,
6543 ) -> Result<()> {
6544 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6545 cx.update(|_, cx| {
6546 entries.sort_unstable_by_key(|(buffer, _)| {
6547 buffer.read(cx).file().map(|f| f.path().clone())
6548 });
6549 })?;
6550 if entries.is_empty() {
6551 return Ok(());
6552 }
6553
6554 // If the project transaction's edits are all contained within this editor, then
6555 // avoid opening a new editor to display them.
6556
6557 if let [(buffer, transaction)] = &*entries {
6558 let excerpt = editor.update(cx, |editor, cx| {
6559 editor
6560 .buffer()
6561 .read(cx)
6562 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6563 })?;
6564 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6565 && excerpted_buffer == *buffer
6566 {
6567 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6568 let excerpt_range = excerpt_range.to_offset(buffer);
6569 buffer
6570 .edited_ranges_for_transaction::<usize>(transaction)
6571 .all(|range| {
6572 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6573 })
6574 })?;
6575
6576 if all_edits_within_excerpt {
6577 return Ok(());
6578 }
6579 }
6580 }
6581
6582 let mut ranges_to_highlight = Vec::new();
6583 let excerpt_buffer = cx.new(|cx| {
6584 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6585 for (buffer_handle, transaction) in &entries {
6586 let edited_ranges = buffer_handle
6587 .read(cx)
6588 .edited_ranges_for_transaction::<Point>(transaction)
6589 .collect::<Vec<_>>();
6590 let (ranges, _) = multibuffer.set_excerpts_for_path(
6591 PathKey::for_buffer(buffer_handle, cx),
6592 buffer_handle.clone(),
6593 edited_ranges,
6594 multibuffer_context_lines(cx),
6595 cx,
6596 );
6597
6598 ranges_to_highlight.extend(ranges);
6599 }
6600 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6601 multibuffer
6602 })?;
6603
6604 workspace.update_in(cx, |workspace, window, cx| {
6605 let project = workspace.project().clone();
6606 let editor =
6607 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6608 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6609 editor.update(cx, |editor, cx| {
6610 editor.highlight_background::<Self>(
6611 &ranges_to_highlight,
6612 |theme| theme.colors().editor_highlighted_line_background,
6613 cx,
6614 );
6615 });
6616 })?;
6617
6618 Ok(())
6619 }
6620
6621 pub fn clear_code_action_providers(&mut self) {
6622 self.code_action_providers.clear();
6623 self.available_code_actions.take();
6624 }
6625
6626 pub fn add_code_action_provider(
6627 &mut self,
6628 provider: Rc<dyn CodeActionProvider>,
6629 window: &mut Window,
6630 cx: &mut Context<Self>,
6631 ) {
6632 if self
6633 .code_action_providers
6634 .iter()
6635 .any(|existing_provider| existing_provider.id() == provider.id())
6636 {
6637 return;
6638 }
6639
6640 self.code_action_providers.push(provider);
6641 self.refresh_code_actions(window, cx);
6642 }
6643
6644 pub fn remove_code_action_provider(
6645 &mut self,
6646 id: Arc<str>,
6647 window: &mut Window,
6648 cx: &mut Context<Self>,
6649 ) {
6650 self.code_action_providers
6651 .retain(|provider| provider.id() != id);
6652 self.refresh_code_actions(window, cx);
6653 }
6654
6655 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6656 !self.code_action_providers.is_empty()
6657 && EditorSettings::get_global(cx).toolbar.code_actions
6658 }
6659
6660 pub fn has_available_code_actions(&self) -> bool {
6661 self.available_code_actions
6662 .as_ref()
6663 .is_some_and(|(_, actions)| !actions.is_empty())
6664 }
6665
6666 fn render_inline_code_actions(
6667 &self,
6668 icon_size: ui::IconSize,
6669 display_row: DisplayRow,
6670 is_active: bool,
6671 cx: &mut Context<Self>,
6672 ) -> AnyElement {
6673 let show_tooltip = !self.context_menu_visible();
6674 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6675 .icon_size(icon_size)
6676 .shape(ui::IconButtonShape::Square)
6677 .icon_color(ui::Color::Hidden)
6678 .toggle_state(is_active)
6679 .when(show_tooltip, |this| {
6680 this.tooltip({
6681 let focus_handle = self.focus_handle.clone();
6682 move |_window, cx| {
6683 Tooltip::for_action_in(
6684 "Toggle Code Actions",
6685 &ToggleCodeActions {
6686 deployed_from: None,
6687 quick_launch: false,
6688 },
6689 &focus_handle,
6690 cx,
6691 )
6692 }
6693 })
6694 })
6695 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6696 window.focus(&editor.focus_handle(cx));
6697 editor.toggle_code_actions(
6698 &crate::actions::ToggleCodeActions {
6699 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6700 display_row,
6701 )),
6702 quick_launch: false,
6703 },
6704 window,
6705 cx,
6706 );
6707 }))
6708 .into_any_element()
6709 }
6710
6711 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6712 &self.context_menu
6713 }
6714
6715 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6716 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6717 cx.background_executor()
6718 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6719 .await;
6720
6721 let (start_buffer, start, _, end, newest_selection) = this
6722 .update(cx, |this, cx| {
6723 let newest_selection = this.selections.newest_anchor().clone();
6724 if newest_selection.head().diff_base_anchor.is_some() {
6725 return None;
6726 }
6727 let display_snapshot = this.display_snapshot(cx);
6728 let newest_selection_adjusted =
6729 this.selections.newest_adjusted(&display_snapshot);
6730 let buffer = this.buffer.read(cx);
6731
6732 let (start_buffer, start) =
6733 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6734 let (end_buffer, end) =
6735 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6736
6737 Some((start_buffer, start, end_buffer, end, newest_selection))
6738 })?
6739 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6740 .context(
6741 "Expected selection to lie in a single buffer when refreshing code actions",
6742 )?;
6743 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6744 let providers = this.code_action_providers.clone();
6745 let tasks = this
6746 .code_action_providers
6747 .iter()
6748 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6749 .collect::<Vec<_>>();
6750 (providers, tasks)
6751 })?;
6752
6753 let mut actions = Vec::new();
6754 for (provider, provider_actions) in
6755 providers.into_iter().zip(future::join_all(tasks).await)
6756 {
6757 if let Some(provider_actions) = provider_actions.log_err() {
6758 actions.extend(provider_actions.into_iter().map(|action| {
6759 AvailableCodeAction {
6760 excerpt_id: newest_selection.start.excerpt_id,
6761 action,
6762 provider: provider.clone(),
6763 }
6764 }));
6765 }
6766 }
6767
6768 this.update(cx, |this, cx| {
6769 this.available_code_actions = if actions.is_empty() {
6770 None
6771 } else {
6772 Some((
6773 Location {
6774 buffer: start_buffer,
6775 range: start..end,
6776 },
6777 actions.into(),
6778 ))
6779 };
6780 cx.notify();
6781 })
6782 }));
6783 }
6784
6785 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6786 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6787 self.show_git_blame_inline = false;
6788
6789 self.show_git_blame_inline_delay_task =
6790 Some(cx.spawn_in(window, async move |this, cx| {
6791 cx.background_executor().timer(delay).await;
6792
6793 this.update(cx, |this, cx| {
6794 this.show_git_blame_inline = true;
6795 cx.notify();
6796 })
6797 .log_err();
6798 }));
6799 }
6800 }
6801
6802 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6803 let snapshot = self.snapshot(window, cx);
6804 let cursor = self
6805 .selections
6806 .newest::<Point>(&snapshot.display_snapshot)
6807 .head();
6808 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6809 else {
6810 return;
6811 };
6812
6813 let Some(blame) = self.blame.as_ref() else {
6814 return;
6815 };
6816
6817 let row_info = RowInfo {
6818 buffer_id: Some(buffer.remote_id()),
6819 buffer_row: Some(point.row),
6820 ..Default::default()
6821 };
6822 let Some((buffer, blame_entry)) = blame
6823 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6824 .flatten()
6825 else {
6826 return;
6827 };
6828
6829 let anchor = self.selections.newest_anchor().head();
6830 let position = self.to_pixel_point(anchor, &snapshot, window);
6831 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6832 self.show_blame_popover(
6833 buffer,
6834 &blame_entry,
6835 position + last_bounds.origin,
6836 true,
6837 cx,
6838 );
6839 };
6840 }
6841
6842 fn show_blame_popover(
6843 &mut self,
6844 buffer: BufferId,
6845 blame_entry: &BlameEntry,
6846 position: gpui::Point<Pixels>,
6847 ignore_timeout: bool,
6848 cx: &mut Context<Self>,
6849 ) {
6850 if let Some(state) = &mut self.inline_blame_popover {
6851 state.hide_task.take();
6852 } else {
6853 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6854 let blame_entry = blame_entry.clone();
6855 let show_task = cx.spawn(async move |editor, cx| {
6856 if !ignore_timeout {
6857 cx.background_executor()
6858 .timer(std::time::Duration::from_millis(blame_popover_delay))
6859 .await;
6860 }
6861 editor
6862 .update(cx, |editor, cx| {
6863 editor.inline_blame_popover_show_task.take();
6864 let Some(blame) = editor.blame.as_ref() else {
6865 return;
6866 };
6867 let blame = blame.read(cx);
6868 let details = blame.details_for_entry(buffer, &blame_entry);
6869 let markdown = cx.new(|cx| {
6870 Markdown::new(
6871 details
6872 .as_ref()
6873 .map(|message| message.message.clone())
6874 .unwrap_or_default(),
6875 None,
6876 None,
6877 cx,
6878 )
6879 });
6880 editor.inline_blame_popover = Some(InlineBlamePopover {
6881 position,
6882 hide_task: None,
6883 popover_bounds: None,
6884 popover_state: InlineBlamePopoverState {
6885 scroll_handle: ScrollHandle::new(),
6886 commit_message: details,
6887 markdown,
6888 },
6889 keyboard_grace: ignore_timeout,
6890 });
6891 cx.notify();
6892 })
6893 .ok();
6894 });
6895 self.inline_blame_popover_show_task = Some(show_task);
6896 }
6897 }
6898
6899 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6900 self.inline_blame_popover_show_task.take();
6901 if let Some(state) = &mut self.inline_blame_popover {
6902 let hide_task = cx.spawn(async move |editor, cx| {
6903 if !ignore_timeout {
6904 cx.background_executor()
6905 .timer(std::time::Duration::from_millis(100))
6906 .await;
6907 }
6908 editor
6909 .update(cx, |editor, cx| {
6910 editor.inline_blame_popover.take();
6911 cx.notify();
6912 })
6913 .ok();
6914 });
6915 state.hide_task = Some(hide_task);
6916 true
6917 } else {
6918 false
6919 }
6920 }
6921
6922 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6923 if self.pending_rename.is_some() {
6924 return None;
6925 }
6926
6927 let provider = self.semantics_provider.clone()?;
6928 let buffer = self.buffer.read(cx);
6929 let newest_selection = self.selections.newest_anchor().clone();
6930 let cursor_position = newest_selection.head();
6931 let (cursor_buffer, cursor_buffer_position) =
6932 buffer.text_anchor_for_position(cursor_position, cx)?;
6933 let (tail_buffer, tail_buffer_position) =
6934 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6935 if cursor_buffer != tail_buffer {
6936 return None;
6937 }
6938
6939 let snapshot = cursor_buffer.read(cx).snapshot();
6940 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6941 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6942 if start_word_range != end_word_range {
6943 self.document_highlights_task.take();
6944 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6945 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6946 return None;
6947 }
6948
6949 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6950 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6951 cx.background_executor()
6952 .timer(Duration::from_millis(debounce))
6953 .await;
6954
6955 let highlights = if let Some(highlights) = cx
6956 .update(|cx| {
6957 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6958 })
6959 .ok()
6960 .flatten()
6961 {
6962 highlights.await.log_err()
6963 } else {
6964 None
6965 };
6966
6967 if let Some(highlights) = highlights {
6968 this.update(cx, |this, cx| {
6969 if this.pending_rename.is_some() {
6970 return;
6971 }
6972
6973 let buffer = this.buffer.read(cx);
6974 if buffer
6975 .text_anchor_for_position(cursor_position, cx)
6976 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6977 {
6978 return;
6979 }
6980
6981 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6982 let mut write_ranges = Vec::new();
6983 let mut read_ranges = Vec::new();
6984 for highlight in highlights {
6985 let buffer_id = cursor_buffer.read(cx).remote_id();
6986 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6987 {
6988 let start = highlight
6989 .range
6990 .start
6991 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6992 let end = highlight
6993 .range
6994 .end
6995 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6996 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6997 continue;
6998 }
6999
7000 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7001 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7002 write_ranges.push(range);
7003 } else {
7004 read_ranges.push(range);
7005 }
7006 }
7007 }
7008
7009 this.highlight_background::<DocumentHighlightRead>(
7010 &read_ranges,
7011 |theme| theme.colors().editor_document_highlight_read_background,
7012 cx,
7013 );
7014 this.highlight_background::<DocumentHighlightWrite>(
7015 &write_ranges,
7016 |theme| theme.colors().editor_document_highlight_write_background,
7017 cx,
7018 );
7019 cx.notify();
7020 })
7021 .log_err();
7022 }
7023 }));
7024 None
7025 }
7026
7027 fn prepare_highlight_query_from_selection(
7028 &mut self,
7029 window: &Window,
7030 cx: &mut Context<Editor>,
7031 ) -> Option<(String, Range<Anchor>)> {
7032 if matches!(self.mode, EditorMode::SingleLine) {
7033 return None;
7034 }
7035 if !EditorSettings::get_global(cx).selection_highlight {
7036 return None;
7037 }
7038 if self.selections.count() != 1 || self.selections.line_mode() {
7039 return None;
7040 }
7041 let snapshot = self.snapshot(window, cx);
7042 let selection = self.selections.newest::<Point>(&snapshot);
7043 // If the selection spans multiple rows OR it is empty
7044 if selection.start.row != selection.end.row
7045 || selection.start.column == selection.end.column
7046 {
7047 return None;
7048 }
7049 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7050 let query = snapshot
7051 .buffer_snapshot()
7052 .text_for_range(selection_anchor_range.clone())
7053 .collect::<String>();
7054 if query.trim().is_empty() {
7055 return None;
7056 }
7057 Some((query, selection_anchor_range))
7058 }
7059
7060 fn update_selection_occurrence_highlights(
7061 &mut self,
7062 query_text: String,
7063 query_range: Range<Anchor>,
7064 multi_buffer_range_to_query: Range<Point>,
7065 use_debounce: bool,
7066 window: &mut Window,
7067 cx: &mut Context<Editor>,
7068 ) -> Task<()> {
7069 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7070 cx.spawn_in(window, async move |editor, cx| {
7071 if use_debounce {
7072 cx.background_executor()
7073 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7074 .await;
7075 }
7076 let match_task = cx.background_spawn(async move {
7077 let buffer_ranges = multi_buffer_snapshot
7078 .range_to_buffer_ranges(multi_buffer_range_to_query)
7079 .into_iter()
7080 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7081 let mut match_ranges = Vec::new();
7082 let Ok(regex) = project::search::SearchQuery::text(
7083 query_text.clone(),
7084 false,
7085 false,
7086 false,
7087 Default::default(),
7088 Default::default(),
7089 false,
7090 None,
7091 ) else {
7092 return Vec::default();
7093 };
7094 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7095 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7096 match_ranges.extend(
7097 regex
7098 .search(
7099 buffer_snapshot,
7100 Some(search_range.start.0..search_range.end.0),
7101 )
7102 .await
7103 .into_iter()
7104 .filter_map(|match_range| {
7105 let match_start = buffer_snapshot
7106 .anchor_after(search_range.start + match_range.start);
7107 let match_end = buffer_snapshot
7108 .anchor_before(search_range.start + match_range.end);
7109 let match_anchor_range =
7110 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7111 (match_anchor_range != query_range).then_some(match_anchor_range)
7112 }),
7113 );
7114 }
7115 match_ranges
7116 });
7117 let match_ranges = match_task.await;
7118 editor
7119 .update_in(cx, |editor, _, cx| {
7120 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7121 if !match_ranges.is_empty() {
7122 editor.highlight_background::<SelectedTextHighlight>(
7123 &match_ranges,
7124 |theme| theme.colors().editor_document_highlight_bracket_background,
7125 cx,
7126 )
7127 }
7128 })
7129 .log_err();
7130 })
7131 }
7132
7133 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7134 struct NewlineFold;
7135 let type_id = std::any::TypeId::of::<NewlineFold>();
7136 if !self.mode.is_single_line() {
7137 return;
7138 }
7139 let snapshot = self.snapshot(window, cx);
7140 if snapshot.buffer_snapshot().max_point().row == 0 {
7141 return;
7142 }
7143 let task = cx.background_spawn(async move {
7144 let new_newlines = snapshot
7145 .buffer_chars_at(MultiBufferOffset(0))
7146 .filter_map(|(c, i)| {
7147 if c == '\n' {
7148 Some(
7149 snapshot.buffer_snapshot().anchor_after(i)
7150 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7151 )
7152 } else {
7153 None
7154 }
7155 })
7156 .collect::<Vec<_>>();
7157 let existing_newlines = snapshot
7158 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7159 .filter_map(|fold| {
7160 if fold.placeholder.type_tag == Some(type_id) {
7161 Some(fold.range.start..fold.range.end)
7162 } else {
7163 None
7164 }
7165 })
7166 .collect::<Vec<_>>();
7167
7168 (new_newlines, existing_newlines)
7169 });
7170 self.folding_newlines = cx.spawn(async move |this, cx| {
7171 let (new_newlines, existing_newlines) = task.await;
7172 if new_newlines == existing_newlines {
7173 return;
7174 }
7175 let placeholder = FoldPlaceholder {
7176 render: Arc::new(move |_, _, cx| {
7177 div()
7178 .bg(cx.theme().status().hint_background)
7179 .border_b_1()
7180 .size_full()
7181 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7182 .border_color(cx.theme().status().hint)
7183 .child("\\n")
7184 .into_any()
7185 }),
7186 constrain_width: false,
7187 merge_adjacent: false,
7188 type_tag: Some(type_id),
7189 };
7190 let creases = new_newlines
7191 .into_iter()
7192 .map(|range| Crease::simple(range, placeholder.clone()))
7193 .collect();
7194 this.update(cx, |this, cx| {
7195 this.display_map.update(cx, |display_map, cx| {
7196 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7197 display_map.fold(creases, cx);
7198 });
7199 })
7200 .ok();
7201 });
7202 }
7203
7204 fn refresh_selected_text_highlights(
7205 &mut self,
7206 on_buffer_edit: bool,
7207 window: &mut Window,
7208 cx: &mut Context<Editor>,
7209 ) {
7210 let Some((query_text, query_range)) =
7211 self.prepare_highlight_query_from_selection(window, cx)
7212 else {
7213 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7214 self.quick_selection_highlight_task.take();
7215 self.debounced_selection_highlight_task.take();
7216 return;
7217 };
7218 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7219 if on_buffer_edit
7220 || self
7221 .quick_selection_highlight_task
7222 .as_ref()
7223 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7224 {
7225 let multi_buffer_visible_start = self
7226 .scroll_manager
7227 .anchor()
7228 .anchor
7229 .to_point(&multi_buffer_snapshot);
7230 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7231 multi_buffer_visible_start
7232 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7233 Bias::Left,
7234 );
7235 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7236 self.quick_selection_highlight_task = Some((
7237 query_range.clone(),
7238 self.update_selection_occurrence_highlights(
7239 query_text.clone(),
7240 query_range.clone(),
7241 multi_buffer_visible_range,
7242 false,
7243 window,
7244 cx,
7245 ),
7246 ));
7247 }
7248 if on_buffer_edit
7249 || self
7250 .debounced_selection_highlight_task
7251 .as_ref()
7252 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7253 {
7254 let multi_buffer_start = multi_buffer_snapshot
7255 .anchor_before(MultiBufferOffset(0))
7256 .to_point(&multi_buffer_snapshot);
7257 let multi_buffer_end = multi_buffer_snapshot
7258 .anchor_after(multi_buffer_snapshot.len())
7259 .to_point(&multi_buffer_snapshot);
7260 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7261 self.debounced_selection_highlight_task = Some((
7262 query_range.clone(),
7263 self.update_selection_occurrence_highlights(
7264 query_text,
7265 query_range,
7266 multi_buffer_full_range,
7267 true,
7268 window,
7269 cx,
7270 ),
7271 ));
7272 }
7273 }
7274
7275 pub fn refresh_edit_prediction(
7276 &mut self,
7277 debounce: bool,
7278 user_requested: bool,
7279 window: &mut Window,
7280 cx: &mut Context<Self>,
7281 ) -> Option<()> {
7282 if DisableAiSettings::get_global(cx).disable_ai {
7283 return None;
7284 }
7285
7286 let provider = self.edit_prediction_provider()?;
7287 let cursor = self.selections.newest_anchor().head();
7288 let (buffer, cursor_buffer_position) =
7289 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7290
7291 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7292 self.discard_edit_prediction(false, cx);
7293 return None;
7294 }
7295
7296 self.update_visible_edit_prediction(window, cx);
7297
7298 if !user_requested
7299 && (!self.should_show_edit_predictions()
7300 || !self.is_focused(window)
7301 || buffer.read(cx).is_empty())
7302 {
7303 self.discard_edit_prediction(false, cx);
7304 return None;
7305 }
7306
7307 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7308 Some(())
7309 }
7310
7311 fn show_edit_predictions_in_menu(&self) -> bool {
7312 match self.edit_prediction_settings {
7313 EditPredictionSettings::Disabled => false,
7314 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7315 }
7316 }
7317
7318 pub fn edit_predictions_enabled(&self) -> bool {
7319 match self.edit_prediction_settings {
7320 EditPredictionSettings::Disabled => false,
7321 EditPredictionSettings::Enabled { .. } => true,
7322 }
7323 }
7324
7325 fn edit_prediction_requires_modifier(&self) -> bool {
7326 match self.edit_prediction_settings {
7327 EditPredictionSettings::Disabled => false,
7328 EditPredictionSettings::Enabled {
7329 preview_requires_modifier,
7330 ..
7331 } => preview_requires_modifier,
7332 }
7333 }
7334
7335 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7336 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7337 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7338 self.discard_edit_prediction(false, cx);
7339 } else {
7340 let selection = self.selections.newest_anchor();
7341 let cursor = selection.head();
7342
7343 if let Some((buffer, cursor_buffer_position)) =
7344 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7345 {
7346 self.edit_prediction_settings =
7347 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7348 }
7349 }
7350 }
7351
7352 fn edit_prediction_settings_at_position(
7353 &self,
7354 buffer: &Entity<Buffer>,
7355 buffer_position: language::Anchor,
7356 cx: &App,
7357 ) -> EditPredictionSettings {
7358 if !self.mode.is_full()
7359 || !self.show_edit_predictions_override.unwrap_or(true)
7360 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7361 {
7362 return EditPredictionSettings::Disabled;
7363 }
7364
7365 let buffer = buffer.read(cx);
7366
7367 let file = buffer.file();
7368
7369 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7370 return EditPredictionSettings::Disabled;
7371 };
7372
7373 let by_provider = matches!(
7374 self.menu_edit_predictions_policy,
7375 MenuEditPredictionsPolicy::ByProvider
7376 );
7377
7378 let show_in_menu = by_provider
7379 && self
7380 .edit_prediction_provider
7381 .as_ref()
7382 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7383
7384 let preview_requires_modifier =
7385 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7386
7387 EditPredictionSettings::Enabled {
7388 show_in_menu,
7389 preview_requires_modifier,
7390 }
7391 }
7392
7393 fn should_show_edit_predictions(&self) -> bool {
7394 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7395 }
7396
7397 pub fn edit_prediction_preview_is_active(&self) -> bool {
7398 matches!(
7399 self.edit_prediction_preview,
7400 EditPredictionPreview::Active { .. }
7401 )
7402 }
7403
7404 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7405 let cursor = self.selections.newest_anchor().head();
7406 if let Some((buffer, cursor_position)) =
7407 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7408 {
7409 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7410 } else {
7411 false
7412 }
7413 }
7414
7415 pub fn supports_minimap(&self, cx: &App) -> bool {
7416 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7417 }
7418
7419 fn edit_predictions_enabled_in_buffer(
7420 &self,
7421 buffer: &Entity<Buffer>,
7422 buffer_position: language::Anchor,
7423 cx: &App,
7424 ) -> bool {
7425 maybe!({
7426 if self.read_only(cx) {
7427 return Some(false);
7428 }
7429 let provider = self.edit_prediction_provider()?;
7430 if !provider.is_enabled(buffer, buffer_position, cx) {
7431 return Some(false);
7432 }
7433 let buffer = buffer.read(cx);
7434 let Some(file) = buffer.file() else {
7435 return Some(true);
7436 };
7437 let settings = all_language_settings(Some(file), cx);
7438 Some(settings.edit_predictions_enabled_for_file(file, cx))
7439 })
7440 .unwrap_or(false)
7441 }
7442
7443 fn cycle_edit_prediction(
7444 &mut self,
7445 direction: Direction,
7446 window: &mut Window,
7447 cx: &mut Context<Self>,
7448 ) -> Option<()> {
7449 let provider = self.edit_prediction_provider()?;
7450 let cursor = self.selections.newest_anchor().head();
7451 let (buffer, cursor_buffer_position) =
7452 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7453 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7454 return None;
7455 }
7456
7457 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7458 self.update_visible_edit_prediction(window, cx);
7459
7460 Some(())
7461 }
7462
7463 pub fn show_edit_prediction(
7464 &mut self,
7465 _: &ShowEditPrediction,
7466 window: &mut Window,
7467 cx: &mut Context<Self>,
7468 ) {
7469 if !self.has_active_edit_prediction() {
7470 self.refresh_edit_prediction(false, true, window, cx);
7471 return;
7472 }
7473
7474 self.update_visible_edit_prediction(window, cx);
7475 }
7476
7477 pub fn display_cursor_names(
7478 &mut self,
7479 _: &DisplayCursorNames,
7480 window: &mut Window,
7481 cx: &mut Context<Self>,
7482 ) {
7483 self.show_cursor_names(window, cx);
7484 }
7485
7486 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7487 self.show_cursor_names = true;
7488 cx.notify();
7489 cx.spawn_in(window, async move |this, cx| {
7490 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7491 this.update(cx, |this, cx| {
7492 this.show_cursor_names = false;
7493 cx.notify()
7494 })
7495 .ok()
7496 })
7497 .detach();
7498 }
7499
7500 pub fn next_edit_prediction(
7501 &mut self,
7502 _: &NextEditPrediction,
7503 window: &mut Window,
7504 cx: &mut Context<Self>,
7505 ) {
7506 if self.has_active_edit_prediction() {
7507 self.cycle_edit_prediction(Direction::Next, window, cx);
7508 } else {
7509 let is_copilot_disabled = self
7510 .refresh_edit_prediction(false, true, window, cx)
7511 .is_none();
7512 if is_copilot_disabled {
7513 cx.propagate();
7514 }
7515 }
7516 }
7517
7518 pub fn previous_edit_prediction(
7519 &mut self,
7520 _: &PreviousEditPrediction,
7521 window: &mut Window,
7522 cx: &mut Context<Self>,
7523 ) {
7524 if self.has_active_edit_prediction() {
7525 self.cycle_edit_prediction(Direction::Prev, window, cx);
7526 } else {
7527 let is_copilot_disabled = self
7528 .refresh_edit_prediction(false, true, window, cx)
7529 .is_none();
7530 if is_copilot_disabled {
7531 cx.propagate();
7532 }
7533 }
7534 }
7535
7536 pub fn accept_edit_prediction(
7537 &mut self,
7538 _: &AcceptEditPrediction,
7539 window: &mut Window,
7540 cx: &mut Context<Self>,
7541 ) {
7542 if self.show_edit_predictions_in_menu() {
7543 self.hide_context_menu(window, cx);
7544 }
7545
7546 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7547 return;
7548 };
7549
7550 match &active_edit_prediction.completion {
7551 EditPrediction::MoveWithin { target, .. } => {
7552 let target = *target;
7553
7554 if let Some(position_map) = &self.last_position_map {
7555 if position_map
7556 .visible_row_range
7557 .contains(&target.to_display_point(&position_map.snapshot).row())
7558 || !self.edit_prediction_requires_modifier()
7559 {
7560 self.unfold_ranges(&[target..target], true, false, cx);
7561 // Note that this is also done in vim's handler of the Tab action.
7562 self.change_selections(
7563 SelectionEffects::scroll(Autoscroll::newest()),
7564 window,
7565 cx,
7566 |selections| {
7567 selections.select_anchor_ranges([target..target]);
7568 },
7569 );
7570 self.clear_row_highlights::<EditPredictionPreview>();
7571
7572 self.edit_prediction_preview
7573 .set_previous_scroll_position(None);
7574 } else {
7575 self.edit_prediction_preview
7576 .set_previous_scroll_position(Some(
7577 position_map.snapshot.scroll_anchor,
7578 ));
7579
7580 self.highlight_rows::<EditPredictionPreview>(
7581 target..target,
7582 cx.theme().colors().editor_highlighted_line_background,
7583 RowHighlightOptions {
7584 autoscroll: true,
7585 ..Default::default()
7586 },
7587 cx,
7588 );
7589 self.request_autoscroll(Autoscroll::fit(), cx);
7590 }
7591 }
7592 }
7593 EditPrediction::MoveOutside { snapshot, target } => {
7594 if let Some(workspace) = self.workspace() {
7595 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7596 .detach_and_log_err(cx);
7597 }
7598 }
7599 EditPrediction::Edit { edits, .. } => {
7600 self.report_edit_prediction_event(
7601 active_edit_prediction.completion_id.clone(),
7602 true,
7603 cx,
7604 );
7605
7606 if let Some(provider) = self.edit_prediction_provider() {
7607 provider.accept(cx);
7608 }
7609
7610 // Store the transaction ID and selections before applying the edit
7611 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7612
7613 let snapshot = self.buffer.read(cx).snapshot(cx);
7614 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7615
7616 self.buffer.update(cx, |buffer, cx| {
7617 buffer.edit(edits.iter().cloned(), None, cx)
7618 });
7619
7620 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7621 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7622 });
7623
7624 let selections = self.selections.disjoint_anchors_arc();
7625 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7626 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7627 if has_new_transaction {
7628 self.selection_history
7629 .insert_transaction(transaction_id_now, selections);
7630 }
7631 }
7632
7633 self.update_visible_edit_prediction(window, cx);
7634 if self.active_edit_prediction.is_none() {
7635 self.refresh_edit_prediction(true, true, window, cx);
7636 }
7637
7638 cx.notify();
7639 }
7640 }
7641
7642 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7643 }
7644
7645 pub fn accept_partial_edit_prediction(
7646 &mut self,
7647 _: &AcceptPartialEditPrediction,
7648 window: &mut Window,
7649 cx: &mut Context<Self>,
7650 ) {
7651 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7652 return;
7653 };
7654 if self.selections.count() != 1 {
7655 return;
7656 }
7657
7658 match &active_edit_prediction.completion {
7659 EditPrediction::MoveWithin { target, .. } => {
7660 let target = *target;
7661 self.change_selections(
7662 SelectionEffects::scroll(Autoscroll::newest()),
7663 window,
7664 cx,
7665 |selections| {
7666 selections.select_anchor_ranges([target..target]);
7667 },
7668 );
7669 }
7670 EditPrediction::MoveOutside { snapshot, target } => {
7671 if let Some(workspace) = self.workspace() {
7672 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7673 .detach_and_log_err(cx);
7674 }
7675 }
7676 EditPrediction::Edit { edits, .. } => {
7677 self.report_edit_prediction_event(
7678 active_edit_prediction.completion_id.clone(),
7679 true,
7680 cx,
7681 );
7682
7683 // Find an insertion that starts at the cursor position.
7684 let snapshot = self.buffer.read(cx).snapshot(cx);
7685 let cursor_offset = self
7686 .selections
7687 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7688 .head();
7689 let insertion = edits.iter().find_map(|(range, text)| {
7690 let range = range.to_offset(&snapshot);
7691 if range.is_empty() && range.start == cursor_offset {
7692 Some(text)
7693 } else {
7694 None
7695 }
7696 });
7697
7698 if let Some(text) = insertion {
7699 let mut partial_completion = text
7700 .chars()
7701 .by_ref()
7702 .take_while(|c| c.is_alphabetic())
7703 .collect::<String>();
7704 if partial_completion.is_empty() {
7705 partial_completion = text
7706 .chars()
7707 .by_ref()
7708 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7709 .collect::<String>();
7710 }
7711
7712 cx.emit(EditorEvent::InputHandled {
7713 utf16_range_to_replace: None,
7714 text: partial_completion.clone().into(),
7715 });
7716
7717 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7718
7719 self.refresh_edit_prediction(true, true, window, cx);
7720 cx.notify();
7721 } else {
7722 self.accept_edit_prediction(&Default::default(), window, cx);
7723 }
7724 }
7725 }
7726 }
7727
7728 fn discard_edit_prediction(
7729 &mut self,
7730 should_report_edit_prediction_event: bool,
7731 cx: &mut Context<Self>,
7732 ) -> bool {
7733 if should_report_edit_prediction_event {
7734 let completion_id = self
7735 .active_edit_prediction
7736 .as_ref()
7737 .and_then(|active_completion| active_completion.completion_id.clone());
7738
7739 self.report_edit_prediction_event(completion_id, false, cx);
7740 }
7741
7742 if let Some(provider) = self.edit_prediction_provider() {
7743 provider.discard(cx);
7744 }
7745
7746 self.take_active_edit_prediction(cx)
7747 }
7748
7749 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7750 let Some(provider) = self.edit_prediction_provider() else {
7751 return;
7752 };
7753
7754 let Some((_, buffer, _)) = self
7755 .buffer
7756 .read(cx)
7757 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7758 else {
7759 return;
7760 };
7761
7762 let extension = buffer
7763 .read(cx)
7764 .file()
7765 .and_then(|file| Some(file.path().extension()?.to_string()));
7766
7767 let event_type = match accepted {
7768 true => "Edit Prediction Accepted",
7769 false => "Edit Prediction Discarded",
7770 };
7771 telemetry::event!(
7772 event_type,
7773 provider = provider.name(),
7774 prediction_id = id,
7775 suggestion_accepted = accepted,
7776 file_extension = extension,
7777 );
7778 }
7779
7780 fn open_editor_at_anchor(
7781 snapshot: &language::BufferSnapshot,
7782 target: language::Anchor,
7783 workspace: &Entity<Workspace>,
7784 window: &mut Window,
7785 cx: &mut App,
7786 ) -> Task<Result<()>> {
7787 workspace.update(cx, |workspace, cx| {
7788 let path = snapshot.file().map(|file| file.full_path(cx));
7789 let Some(path) =
7790 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7791 else {
7792 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7793 };
7794 let target = text::ToPoint::to_point(&target, snapshot);
7795 let item = workspace.open_path(path, None, true, window, cx);
7796 window.spawn(cx, async move |cx| {
7797 let Some(editor) = item.await?.downcast::<Editor>() else {
7798 return Ok(());
7799 };
7800 editor
7801 .update_in(cx, |editor, window, cx| {
7802 editor.go_to_singleton_buffer_point(target, window, cx);
7803 })
7804 .ok();
7805 anyhow::Ok(())
7806 })
7807 })
7808 }
7809
7810 pub fn has_active_edit_prediction(&self) -> bool {
7811 self.active_edit_prediction.is_some()
7812 }
7813
7814 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7815 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7816 return false;
7817 };
7818
7819 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7820 self.clear_highlights::<EditPredictionHighlight>(cx);
7821 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7822 true
7823 }
7824
7825 /// Returns true when we're displaying the edit prediction popover below the cursor
7826 /// like we are not previewing and the LSP autocomplete menu is visible
7827 /// or we are in `when_holding_modifier` mode.
7828 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7829 if self.edit_prediction_preview_is_active()
7830 || !self.show_edit_predictions_in_menu()
7831 || !self.edit_predictions_enabled()
7832 {
7833 return false;
7834 }
7835
7836 if self.has_visible_completions_menu() {
7837 return true;
7838 }
7839
7840 has_completion && self.edit_prediction_requires_modifier()
7841 }
7842
7843 fn handle_modifiers_changed(
7844 &mut self,
7845 modifiers: Modifiers,
7846 position_map: &PositionMap,
7847 window: &mut Window,
7848 cx: &mut Context<Self>,
7849 ) {
7850 // Ensure that the edit prediction preview is updated, even when not
7851 // enabled, if there's an active edit prediction preview.
7852 if self.show_edit_predictions_in_menu()
7853 || matches!(
7854 self.edit_prediction_preview,
7855 EditPredictionPreview::Active { .. }
7856 )
7857 {
7858 self.update_edit_prediction_preview(&modifiers, window, cx);
7859 }
7860
7861 self.update_selection_mode(&modifiers, position_map, window, cx);
7862
7863 let mouse_position = window.mouse_position();
7864 if !position_map.text_hitbox.is_hovered(window) {
7865 return;
7866 }
7867
7868 self.update_hovered_link(
7869 position_map.point_for_position(mouse_position),
7870 &position_map.snapshot,
7871 modifiers,
7872 window,
7873 cx,
7874 )
7875 }
7876
7877 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7878 match EditorSettings::get_global(cx).multi_cursor_modifier {
7879 MultiCursorModifier::Alt => modifiers.secondary(),
7880 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7881 }
7882 }
7883
7884 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7885 match EditorSettings::get_global(cx).multi_cursor_modifier {
7886 MultiCursorModifier::Alt => modifiers.alt,
7887 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7888 }
7889 }
7890
7891 fn columnar_selection_mode(
7892 modifiers: &Modifiers,
7893 cx: &mut Context<Self>,
7894 ) -> Option<ColumnarMode> {
7895 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7896 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7897 Some(ColumnarMode::FromMouse)
7898 } else if Self::is_alt_pressed(modifiers, cx) {
7899 Some(ColumnarMode::FromSelection)
7900 } else {
7901 None
7902 }
7903 } else {
7904 None
7905 }
7906 }
7907
7908 fn update_selection_mode(
7909 &mut self,
7910 modifiers: &Modifiers,
7911 position_map: &PositionMap,
7912 window: &mut Window,
7913 cx: &mut Context<Self>,
7914 ) {
7915 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7916 return;
7917 };
7918 if self.selections.pending_anchor().is_none() {
7919 return;
7920 }
7921
7922 let mouse_position = window.mouse_position();
7923 let point_for_position = position_map.point_for_position(mouse_position);
7924 let position = point_for_position.previous_valid;
7925
7926 self.select(
7927 SelectPhase::BeginColumnar {
7928 position,
7929 reset: false,
7930 mode,
7931 goal_column: point_for_position.exact_unclipped.column(),
7932 },
7933 window,
7934 cx,
7935 );
7936 }
7937
7938 fn update_edit_prediction_preview(
7939 &mut self,
7940 modifiers: &Modifiers,
7941 window: &mut Window,
7942 cx: &mut Context<Self>,
7943 ) {
7944 let mut modifiers_held = false;
7945 if let Some(accept_keystroke) = self
7946 .accept_edit_prediction_keybind(false, window, cx)
7947 .keystroke()
7948 {
7949 modifiers_held = modifiers_held
7950 || (accept_keystroke.modifiers() == modifiers
7951 && accept_keystroke.modifiers().modified());
7952 };
7953 if let Some(accept_partial_keystroke) = self
7954 .accept_edit_prediction_keybind(true, window, cx)
7955 .keystroke()
7956 {
7957 modifiers_held = modifiers_held
7958 || (accept_partial_keystroke.modifiers() == modifiers
7959 && accept_partial_keystroke.modifiers().modified());
7960 }
7961
7962 if modifiers_held {
7963 if matches!(
7964 self.edit_prediction_preview,
7965 EditPredictionPreview::Inactive { .. }
7966 ) {
7967 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7968 provider.provider.did_show(cx)
7969 }
7970
7971 self.edit_prediction_preview = EditPredictionPreview::Active {
7972 previous_scroll_position: None,
7973 since: Instant::now(),
7974 };
7975
7976 self.update_visible_edit_prediction(window, cx);
7977 cx.notify();
7978 }
7979 } else if let EditPredictionPreview::Active {
7980 previous_scroll_position,
7981 since,
7982 } = self.edit_prediction_preview
7983 {
7984 if let (Some(previous_scroll_position), Some(position_map)) =
7985 (previous_scroll_position, self.last_position_map.as_ref())
7986 {
7987 self.set_scroll_position(
7988 previous_scroll_position
7989 .scroll_position(&position_map.snapshot.display_snapshot),
7990 window,
7991 cx,
7992 );
7993 }
7994
7995 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7996 released_too_fast: since.elapsed() < Duration::from_millis(200),
7997 };
7998 self.clear_row_highlights::<EditPredictionPreview>();
7999 self.update_visible_edit_prediction(window, cx);
8000 cx.notify();
8001 }
8002 }
8003
8004 fn update_visible_edit_prediction(
8005 &mut self,
8006 _window: &mut Window,
8007 cx: &mut Context<Self>,
8008 ) -> Option<()> {
8009 if DisableAiSettings::get_global(cx).disable_ai {
8010 return None;
8011 }
8012
8013 if self.ime_transaction.is_some() {
8014 self.discard_edit_prediction(false, cx);
8015 return None;
8016 }
8017
8018 let selection = self.selections.newest_anchor();
8019 let cursor = selection.head();
8020 let multibuffer = self.buffer.read(cx).snapshot(cx);
8021 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8022 let excerpt_id = cursor.excerpt_id;
8023
8024 let show_in_menu = self.show_edit_predictions_in_menu();
8025 let completions_menu_has_precedence = !show_in_menu
8026 && (self.context_menu.borrow().is_some()
8027 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8028
8029 if completions_menu_has_precedence
8030 || !offset_selection.is_empty()
8031 || self
8032 .active_edit_prediction
8033 .as_ref()
8034 .is_some_and(|completion| {
8035 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8036 return false;
8037 };
8038 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8039 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8040 !invalidation_range.contains(&offset_selection.head())
8041 })
8042 {
8043 self.discard_edit_prediction(false, cx);
8044 return None;
8045 }
8046
8047 self.take_active_edit_prediction(cx);
8048 let Some(provider) = self.edit_prediction_provider() else {
8049 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8050 return None;
8051 };
8052
8053 let (buffer, cursor_buffer_position) =
8054 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8055
8056 self.edit_prediction_settings =
8057 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8058
8059 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8060
8061 if self.edit_prediction_indent_conflict {
8062 let cursor_point = cursor.to_point(&multibuffer);
8063
8064 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
8065
8066 if let Some((_, indent)) = indents.iter().next()
8067 && indent.len == cursor_point.column
8068 {
8069 self.edit_prediction_indent_conflict = false;
8070 }
8071 }
8072
8073 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8074
8075 let (completion_id, edits, edit_preview) = match edit_prediction {
8076 edit_prediction::EditPrediction::Local {
8077 id,
8078 edits,
8079 edit_preview,
8080 } => (id, edits, edit_preview),
8081 edit_prediction::EditPrediction::Jump {
8082 id,
8083 snapshot,
8084 target,
8085 } => {
8086 self.stale_edit_prediction_in_menu = None;
8087 self.active_edit_prediction = Some(EditPredictionState {
8088 inlay_ids: vec![],
8089 completion: EditPrediction::MoveOutside { snapshot, target },
8090 completion_id: id,
8091 invalidation_range: None,
8092 });
8093 cx.notify();
8094 return Some(());
8095 }
8096 };
8097
8098 let edits = edits
8099 .into_iter()
8100 .flat_map(|(range, new_text)| {
8101 Some((
8102 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8103 new_text,
8104 ))
8105 })
8106 .collect::<Vec<_>>();
8107 if edits.is_empty() {
8108 return None;
8109 }
8110
8111 let first_edit_start = edits.first().unwrap().0.start;
8112 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8113 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8114
8115 let last_edit_end = edits.last().unwrap().0.end;
8116 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8117 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8118
8119 let cursor_row = cursor.to_point(&multibuffer).row;
8120
8121 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8122
8123 let mut inlay_ids = Vec::new();
8124 let invalidation_row_range;
8125 let move_invalidation_row_range = if cursor_row < edit_start_row {
8126 Some(cursor_row..edit_end_row)
8127 } else if cursor_row > edit_end_row {
8128 Some(edit_start_row..cursor_row)
8129 } else {
8130 None
8131 };
8132 let supports_jump = self
8133 .edit_prediction_provider
8134 .as_ref()
8135 .map(|provider| provider.provider.supports_jump_to_edit())
8136 .unwrap_or(true);
8137
8138 let is_move = supports_jump
8139 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8140 let completion = if is_move {
8141 invalidation_row_range =
8142 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8143 let target = first_edit_start;
8144 EditPrediction::MoveWithin { target, snapshot }
8145 } else {
8146 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8147 && !self.edit_predictions_hidden_for_vim_mode;
8148
8149 if show_completions_in_buffer {
8150 if let Some(provider) = &self.edit_prediction_provider {
8151 provider.provider.did_show(cx);
8152 }
8153 if edits
8154 .iter()
8155 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8156 {
8157 let mut inlays = Vec::new();
8158 for (range, new_text) in &edits {
8159 let inlay = Inlay::edit_prediction(
8160 post_inc(&mut self.next_inlay_id),
8161 range.start,
8162 new_text.as_ref(),
8163 );
8164 inlay_ids.push(inlay.id);
8165 inlays.push(inlay);
8166 }
8167
8168 self.splice_inlays(&[], inlays, cx);
8169 } else {
8170 let background_color = cx.theme().status().deleted_background;
8171 self.highlight_text::<EditPredictionHighlight>(
8172 edits.iter().map(|(range, _)| range.clone()).collect(),
8173 HighlightStyle {
8174 background_color: Some(background_color),
8175 ..Default::default()
8176 },
8177 cx,
8178 );
8179 }
8180 }
8181
8182 invalidation_row_range = edit_start_row..edit_end_row;
8183
8184 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8185 if provider.show_tab_accept_marker() {
8186 EditDisplayMode::TabAccept
8187 } else {
8188 EditDisplayMode::Inline
8189 }
8190 } else {
8191 EditDisplayMode::DiffPopover
8192 };
8193
8194 EditPrediction::Edit {
8195 edits,
8196 edit_preview,
8197 display_mode,
8198 snapshot,
8199 }
8200 };
8201
8202 let invalidation_range = multibuffer
8203 .anchor_before(Point::new(invalidation_row_range.start, 0))
8204 ..multibuffer.anchor_after(Point::new(
8205 invalidation_row_range.end,
8206 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8207 ));
8208
8209 self.stale_edit_prediction_in_menu = None;
8210 self.active_edit_prediction = Some(EditPredictionState {
8211 inlay_ids,
8212 completion,
8213 completion_id,
8214 invalidation_range: Some(invalidation_range),
8215 });
8216
8217 cx.notify();
8218
8219 Some(())
8220 }
8221
8222 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8223 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8224 }
8225
8226 fn clear_tasks(&mut self) {
8227 self.tasks.clear()
8228 }
8229
8230 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8231 if self.tasks.insert(key, value).is_some() {
8232 // This case should hopefully be rare, but just in case...
8233 log::error!(
8234 "multiple different run targets found on a single line, only the last target will be rendered"
8235 )
8236 }
8237 }
8238
8239 /// Get all display points of breakpoints that will be rendered within editor
8240 ///
8241 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8242 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8243 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8244 fn active_breakpoints(
8245 &self,
8246 range: Range<DisplayRow>,
8247 window: &mut Window,
8248 cx: &mut Context<Self>,
8249 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8250 let mut breakpoint_display_points = HashMap::default();
8251
8252 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8253 return breakpoint_display_points;
8254 };
8255
8256 let snapshot = self.snapshot(window, cx);
8257
8258 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8259 let Some(project) = self.project() else {
8260 return breakpoint_display_points;
8261 };
8262
8263 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8264 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8265
8266 for (buffer_snapshot, range, excerpt_id) in
8267 multi_buffer_snapshot.range_to_buffer_ranges(range)
8268 {
8269 let Some(buffer) = project
8270 .read(cx)
8271 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8272 else {
8273 continue;
8274 };
8275 let breakpoints = breakpoint_store.read(cx).breakpoints(
8276 &buffer,
8277 Some(
8278 buffer_snapshot.anchor_before(range.start)
8279 ..buffer_snapshot.anchor_after(range.end),
8280 ),
8281 buffer_snapshot,
8282 cx,
8283 );
8284 for (breakpoint, state) in breakpoints {
8285 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8286 let position = multi_buffer_anchor
8287 .to_point(&multi_buffer_snapshot)
8288 .to_display_point(&snapshot);
8289
8290 breakpoint_display_points.insert(
8291 position.row(),
8292 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8293 );
8294 }
8295 }
8296
8297 breakpoint_display_points
8298 }
8299
8300 fn breakpoint_context_menu(
8301 &self,
8302 anchor: Anchor,
8303 window: &mut Window,
8304 cx: &mut Context<Self>,
8305 ) -> Entity<ui::ContextMenu> {
8306 let weak_editor = cx.weak_entity();
8307 let focus_handle = self.focus_handle(cx);
8308
8309 let row = self
8310 .buffer
8311 .read(cx)
8312 .snapshot(cx)
8313 .summary_for_anchor::<Point>(&anchor)
8314 .row;
8315
8316 let breakpoint = self
8317 .breakpoint_at_row(row, window, cx)
8318 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8319
8320 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8321 "Edit Log Breakpoint"
8322 } else {
8323 "Set Log Breakpoint"
8324 };
8325
8326 let condition_breakpoint_msg = if breakpoint
8327 .as_ref()
8328 .is_some_and(|bp| bp.1.condition.is_some())
8329 {
8330 "Edit Condition Breakpoint"
8331 } else {
8332 "Set Condition Breakpoint"
8333 };
8334
8335 let hit_condition_breakpoint_msg = if breakpoint
8336 .as_ref()
8337 .is_some_and(|bp| bp.1.hit_condition.is_some())
8338 {
8339 "Edit Hit Condition Breakpoint"
8340 } else {
8341 "Set Hit Condition Breakpoint"
8342 };
8343
8344 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8345 "Unset Breakpoint"
8346 } else {
8347 "Set Breakpoint"
8348 };
8349
8350 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8351
8352 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8353 BreakpointState::Enabled => Some("Disable"),
8354 BreakpointState::Disabled => Some("Enable"),
8355 });
8356
8357 let (anchor, breakpoint) =
8358 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8359
8360 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8361 menu.on_blur_subscription(Subscription::new(|| {}))
8362 .context(focus_handle)
8363 .when(run_to_cursor, |this| {
8364 let weak_editor = weak_editor.clone();
8365 this.entry("Run to cursor", None, move |window, cx| {
8366 weak_editor
8367 .update(cx, |editor, cx| {
8368 editor.change_selections(
8369 SelectionEffects::no_scroll(),
8370 window,
8371 cx,
8372 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8373 );
8374 })
8375 .ok();
8376
8377 window.dispatch_action(Box::new(RunToCursor), cx);
8378 })
8379 .separator()
8380 })
8381 .when_some(toggle_state_msg, |this, msg| {
8382 this.entry(msg, None, {
8383 let weak_editor = weak_editor.clone();
8384 let breakpoint = breakpoint.clone();
8385 move |_window, cx| {
8386 weak_editor
8387 .update(cx, |this, cx| {
8388 this.edit_breakpoint_at_anchor(
8389 anchor,
8390 breakpoint.as_ref().clone(),
8391 BreakpointEditAction::InvertState,
8392 cx,
8393 );
8394 })
8395 .log_err();
8396 }
8397 })
8398 })
8399 .entry(set_breakpoint_msg, None, {
8400 let weak_editor = weak_editor.clone();
8401 let breakpoint = breakpoint.clone();
8402 move |_window, cx| {
8403 weak_editor
8404 .update(cx, |this, cx| {
8405 this.edit_breakpoint_at_anchor(
8406 anchor,
8407 breakpoint.as_ref().clone(),
8408 BreakpointEditAction::Toggle,
8409 cx,
8410 );
8411 })
8412 .log_err();
8413 }
8414 })
8415 .entry(log_breakpoint_msg, None, {
8416 let breakpoint = breakpoint.clone();
8417 let weak_editor = weak_editor.clone();
8418 move |window, cx| {
8419 weak_editor
8420 .update(cx, |this, cx| {
8421 this.add_edit_breakpoint_block(
8422 anchor,
8423 breakpoint.as_ref(),
8424 BreakpointPromptEditAction::Log,
8425 window,
8426 cx,
8427 );
8428 })
8429 .log_err();
8430 }
8431 })
8432 .entry(condition_breakpoint_msg, None, {
8433 let breakpoint = breakpoint.clone();
8434 let weak_editor = weak_editor.clone();
8435 move |window, cx| {
8436 weak_editor
8437 .update(cx, |this, cx| {
8438 this.add_edit_breakpoint_block(
8439 anchor,
8440 breakpoint.as_ref(),
8441 BreakpointPromptEditAction::Condition,
8442 window,
8443 cx,
8444 );
8445 })
8446 .log_err();
8447 }
8448 })
8449 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8450 weak_editor
8451 .update(cx, |this, cx| {
8452 this.add_edit_breakpoint_block(
8453 anchor,
8454 breakpoint.as_ref(),
8455 BreakpointPromptEditAction::HitCondition,
8456 window,
8457 cx,
8458 );
8459 })
8460 .log_err();
8461 })
8462 })
8463 }
8464
8465 fn render_breakpoint(
8466 &self,
8467 position: Anchor,
8468 row: DisplayRow,
8469 breakpoint: &Breakpoint,
8470 state: Option<BreakpointSessionState>,
8471 cx: &mut Context<Self>,
8472 ) -> IconButton {
8473 let is_rejected = state.is_some_and(|s| !s.verified);
8474 // Is it a breakpoint that shows up when hovering over gutter?
8475 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8476 (false, false),
8477 |PhantomBreakpointIndicator {
8478 is_active,
8479 display_row,
8480 collides_with_existing_breakpoint,
8481 }| {
8482 (
8483 is_active && display_row == row,
8484 collides_with_existing_breakpoint,
8485 )
8486 },
8487 );
8488
8489 let (color, icon) = {
8490 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8491 (false, false) => ui::IconName::DebugBreakpoint,
8492 (true, false) => ui::IconName::DebugLogBreakpoint,
8493 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8494 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8495 };
8496
8497 let color = cx.theme().colors();
8498
8499 let color = if is_phantom {
8500 if collides_with_existing {
8501 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8502 } else {
8503 Color::Hint
8504 }
8505 } else if is_rejected {
8506 Color::Disabled
8507 } else {
8508 Color::Debugger
8509 };
8510
8511 (color, icon)
8512 };
8513
8514 let breakpoint = Arc::from(breakpoint.clone());
8515
8516 let alt_as_text = gpui::Keystroke {
8517 modifiers: Modifiers::secondary_key(),
8518 ..Default::default()
8519 };
8520 let primary_action_text = if breakpoint.is_disabled() {
8521 "Enable breakpoint"
8522 } else if is_phantom && !collides_with_existing {
8523 "Set breakpoint"
8524 } else {
8525 "Unset breakpoint"
8526 };
8527 let focus_handle = self.focus_handle.clone();
8528
8529 let meta = if is_rejected {
8530 SharedString::from("No executable code is associated with this line.")
8531 } else if collides_with_existing && !breakpoint.is_disabled() {
8532 SharedString::from(format!(
8533 "{alt_as_text}-click to disable,\nright-click for more options."
8534 ))
8535 } else {
8536 SharedString::from("Right-click for more options.")
8537 };
8538 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8539 .icon_size(IconSize::XSmall)
8540 .size(ui::ButtonSize::None)
8541 .when(is_rejected, |this| {
8542 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8543 })
8544 .icon_color(color)
8545 .style(ButtonStyle::Transparent)
8546 .on_click(cx.listener({
8547 move |editor, event: &ClickEvent, window, cx| {
8548 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8549 BreakpointEditAction::InvertState
8550 } else {
8551 BreakpointEditAction::Toggle
8552 };
8553
8554 window.focus(&editor.focus_handle(cx));
8555 editor.edit_breakpoint_at_anchor(
8556 position,
8557 breakpoint.as_ref().clone(),
8558 edit_action,
8559 cx,
8560 );
8561 }
8562 }))
8563 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8564 editor.set_breakpoint_context_menu(
8565 row,
8566 Some(position),
8567 event.position(),
8568 window,
8569 cx,
8570 );
8571 }))
8572 .tooltip(move |_window, cx| {
8573 Tooltip::with_meta_in(
8574 primary_action_text,
8575 Some(&ToggleBreakpoint),
8576 meta.clone(),
8577 &focus_handle,
8578 cx,
8579 )
8580 })
8581 }
8582
8583 fn build_tasks_context(
8584 project: &Entity<Project>,
8585 buffer: &Entity<Buffer>,
8586 buffer_row: u32,
8587 tasks: &Arc<RunnableTasks>,
8588 cx: &mut Context<Self>,
8589 ) -> Task<Option<task::TaskContext>> {
8590 let position = Point::new(buffer_row, tasks.column);
8591 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8592 let location = Location {
8593 buffer: buffer.clone(),
8594 range: range_start..range_start,
8595 };
8596 // Fill in the environmental variables from the tree-sitter captures
8597 let mut captured_task_variables = TaskVariables::default();
8598 for (capture_name, value) in tasks.extra_variables.clone() {
8599 captured_task_variables.insert(
8600 task::VariableName::Custom(capture_name.into()),
8601 value.clone(),
8602 );
8603 }
8604 project.update(cx, |project, cx| {
8605 project.task_store().update(cx, |task_store, cx| {
8606 task_store.task_context_for_location(captured_task_variables, location, cx)
8607 })
8608 })
8609 }
8610
8611 pub fn spawn_nearest_task(
8612 &mut self,
8613 action: &SpawnNearestTask,
8614 window: &mut Window,
8615 cx: &mut Context<Self>,
8616 ) {
8617 let Some((workspace, _)) = self.workspace.clone() else {
8618 return;
8619 };
8620 let Some(project) = self.project.clone() else {
8621 return;
8622 };
8623
8624 // Try to find a closest, enclosing node using tree-sitter that has a task
8625 let Some((buffer, buffer_row, tasks)) = self
8626 .find_enclosing_node_task(cx)
8627 // Or find the task that's closest in row-distance.
8628 .or_else(|| self.find_closest_task(cx))
8629 else {
8630 return;
8631 };
8632
8633 let reveal_strategy = action.reveal;
8634 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8635 cx.spawn_in(window, async move |_, cx| {
8636 let context = task_context.await?;
8637 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8638
8639 let resolved = &mut resolved_task.resolved;
8640 resolved.reveal = reveal_strategy;
8641
8642 workspace
8643 .update_in(cx, |workspace, window, cx| {
8644 workspace.schedule_resolved_task(
8645 task_source_kind,
8646 resolved_task,
8647 false,
8648 window,
8649 cx,
8650 );
8651 })
8652 .ok()
8653 })
8654 .detach();
8655 }
8656
8657 fn find_closest_task(
8658 &mut self,
8659 cx: &mut Context<Self>,
8660 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8661 let cursor_row = self
8662 .selections
8663 .newest_adjusted(&self.display_snapshot(cx))
8664 .head()
8665 .row;
8666
8667 let ((buffer_id, row), tasks) = self
8668 .tasks
8669 .iter()
8670 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8671
8672 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8673 let tasks = Arc::new(tasks.to_owned());
8674 Some((buffer, *row, tasks))
8675 }
8676
8677 fn find_enclosing_node_task(
8678 &mut self,
8679 cx: &mut Context<Self>,
8680 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8681 let snapshot = self.buffer.read(cx).snapshot(cx);
8682 let offset = self
8683 .selections
8684 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8685 .head();
8686 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8687 let offset = excerpt.map_offset_to_buffer(offset);
8688 let buffer_id = excerpt.buffer().remote_id();
8689
8690 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8691 let mut cursor = layer.node().walk();
8692
8693 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8694 if cursor.node().end_byte() == offset.0 {
8695 cursor.goto_next_sibling();
8696 }
8697 }
8698
8699 // Ascend to the smallest ancestor that contains the range and has a task.
8700 loop {
8701 let node = cursor.node();
8702 let node_range = node.byte_range();
8703 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8704
8705 // Check if this node contains our offset
8706 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8707 // If it contains offset, check for task
8708 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8709 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8710 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8711 }
8712 }
8713
8714 if !cursor.goto_parent() {
8715 break;
8716 }
8717 }
8718 None
8719 }
8720
8721 fn render_run_indicator(
8722 &self,
8723 _style: &EditorStyle,
8724 is_active: bool,
8725 row: DisplayRow,
8726 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8727 cx: &mut Context<Self>,
8728 ) -> IconButton {
8729 let color = Color::Muted;
8730 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8731
8732 IconButton::new(
8733 ("run_indicator", row.0 as usize),
8734 ui::IconName::PlayOutlined,
8735 )
8736 .shape(ui::IconButtonShape::Square)
8737 .icon_size(IconSize::XSmall)
8738 .icon_color(color)
8739 .toggle_state(is_active)
8740 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8741 let quick_launch = match e {
8742 ClickEvent::Keyboard(_) => true,
8743 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8744 };
8745
8746 window.focus(&editor.focus_handle(cx));
8747 editor.toggle_code_actions(
8748 &ToggleCodeActions {
8749 deployed_from: Some(CodeActionSource::RunMenu(row)),
8750 quick_launch,
8751 },
8752 window,
8753 cx,
8754 );
8755 }))
8756 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8757 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8758 }))
8759 }
8760
8761 pub fn context_menu_visible(&self) -> bool {
8762 !self.edit_prediction_preview_is_active()
8763 && self
8764 .context_menu
8765 .borrow()
8766 .as_ref()
8767 .is_some_and(|menu| menu.visible())
8768 }
8769
8770 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8771 self.context_menu
8772 .borrow()
8773 .as_ref()
8774 .map(|menu| menu.origin())
8775 }
8776
8777 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8778 self.context_menu_options = Some(options);
8779 }
8780
8781 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8782 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8783
8784 fn render_edit_prediction_popover(
8785 &mut self,
8786 text_bounds: &Bounds<Pixels>,
8787 content_origin: gpui::Point<Pixels>,
8788 right_margin: Pixels,
8789 editor_snapshot: &EditorSnapshot,
8790 visible_row_range: Range<DisplayRow>,
8791 scroll_top: ScrollOffset,
8792 scroll_bottom: ScrollOffset,
8793 line_layouts: &[LineWithInvisibles],
8794 line_height: Pixels,
8795 scroll_position: gpui::Point<ScrollOffset>,
8796 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8797 newest_selection_head: Option<DisplayPoint>,
8798 editor_width: Pixels,
8799 style: &EditorStyle,
8800 window: &mut Window,
8801 cx: &mut App,
8802 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8803 if self.mode().is_minimap() {
8804 return None;
8805 }
8806 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8807
8808 if self.edit_prediction_visible_in_cursor_popover(true) {
8809 return None;
8810 }
8811
8812 match &active_edit_prediction.completion {
8813 EditPrediction::MoveWithin { target, .. } => {
8814 let target_display_point = target.to_display_point(editor_snapshot);
8815
8816 if self.edit_prediction_requires_modifier() {
8817 if !self.edit_prediction_preview_is_active() {
8818 return None;
8819 }
8820
8821 self.render_edit_prediction_modifier_jump_popover(
8822 text_bounds,
8823 content_origin,
8824 visible_row_range,
8825 line_layouts,
8826 line_height,
8827 scroll_pixel_position,
8828 newest_selection_head,
8829 target_display_point,
8830 window,
8831 cx,
8832 )
8833 } else {
8834 self.render_edit_prediction_eager_jump_popover(
8835 text_bounds,
8836 content_origin,
8837 editor_snapshot,
8838 visible_row_range,
8839 scroll_top,
8840 scroll_bottom,
8841 line_height,
8842 scroll_pixel_position,
8843 target_display_point,
8844 editor_width,
8845 window,
8846 cx,
8847 )
8848 }
8849 }
8850 EditPrediction::Edit {
8851 display_mode: EditDisplayMode::Inline,
8852 ..
8853 } => None,
8854 EditPrediction::Edit {
8855 display_mode: EditDisplayMode::TabAccept,
8856 edits,
8857 ..
8858 } => {
8859 let range = &edits.first()?.0;
8860 let target_display_point = range.end.to_display_point(editor_snapshot);
8861
8862 self.render_edit_prediction_end_of_line_popover(
8863 "Accept",
8864 editor_snapshot,
8865 visible_row_range,
8866 target_display_point,
8867 line_height,
8868 scroll_pixel_position,
8869 content_origin,
8870 editor_width,
8871 window,
8872 cx,
8873 )
8874 }
8875 EditPrediction::Edit {
8876 edits,
8877 edit_preview,
8878 display_mode: EditDisplayMode::DiffPopover,
8879 snapshot,
8880 } => self.render_edit_prediction_diff_popover(
8881 text_bounds,
8882 content_origin,
8883 right_margin,
8884 editor_snapshot,
8885 visible_row_range,
8886 line_layouts,
8887 line_height,
8888 scroll_position,
8889 scroll_pixel_position,
8890 newest_selection_head,
8891 editor_width,
8892 style,
8893 edits,
8894 edit_preview,
8895 snapshot,
8896 window,
8897 cx,
8898 ),
8899 EditPrediction::MoveOutside { snapshot, .. } => {
8900 let mut element = self
8901 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8902 .into_any();
8903
8904 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8905 let origin_x = text_bounds.size.width - size.width - px(30.);
8906 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8907 element.prepaint_at(origin, window, cx);
8908
8909 Some((element, origin))
8910 }
8911 }
8912 }
8913
8914 fn render_edit_prediction_modifier_jump_popover(
8915 &mut self,
8916 text_bounds: &Bounds<Pixels>,
8917 content_origin: gpui::Point<Pixels>,
8918 visible_row_range: Range<DisplayRow>,
8919 line_layouts: &[LineWithInvisibles],
8920 line_height: Pixels,
8921 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8922 newest_selection_head: Option<DisplayPoint>,
8923 target_display_point: DisplayPoint,
8924 window: &mut Window,
8925 cx: &mut App,
8926 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8927 let scrolled_content_origin =
8928 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8929
8930 const SCROLL_PADDING_Y: Pixels = px(12.);
8931
8932 if target_display_point.row() < visible_row_range.start {
8933 return self.render_edit_prediction_scroll_popover(
8934 |_| SCROLL_PADDING_Y,
8935 IconName::ArrowUp,
8936 visible_row_range,
8937 line_layouts,
8938 newest_selection_head,
8939 scrolled_content_origin,
8940 window,
8941 cx,
8942 );
8943 } else if target_display_point.row() >= visible_row_range.end {
8944 return self.render_edit_prediction_scroll_popover(
8945 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8946 IconName::ArrowDown,
8947 visible_row_range,
8948 line_layouts,
8949 newest_selection_head,
8950 scrolled_content_origin,
8951 window,
8952 cx,
8953 );
8954 }
8955
8956 const POLE_WIDTH: Pixels = px(2.);
8957
8958 let line_layout =
8959 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8960 let target_column = target_display_point.column() as usize;
8961
8962 let target_x = line_layout.x_for_index(target_column);
8963 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8964 - scroll_pixel_position.y;
8965
8966 let flag_on_right = target_x < text_bounds.size.width / 2.;
8967
8968 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8969 border_color.l += 0.001;
8970
8971 let mut element = v_flex()
8972 .items_end()
8973 .when(flag_on_right, |el| el.items_start())
8974 .child(if flag_on_right {
8975 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8976 .rounded_bl(px(0.))
8977 .rounded_tl(px(0.))
8978 .border_l_2()
8979 .border_color(border_color)
8980 } else {
8981 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8982 .rounded_br(px(0.))
8983 .rounded_tr(px(0.))
8984 .border_r_2()
8985 .border_color(border_color)
8986 })
8987 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8988 .into_any();
8989
8990 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8991
8992 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8993 - point(
8994 if flag_on_right {
8995 POLE_WIDTH
8996 } else {
8997 size.width - POLE_WIDTH
8998 },
8999 size.height - line_height,
9000 );
9001
9002 origin.x = origin.x.max(content_origin.x);
9003
9004 element.prepaint_at(origin, window, cx);
9005
9006 Some((element, origin))
9007 }
9008
9009 fn render_edit_prediction_scroll_popover(
9010 &mut self,
9011 to_y: impl Fn(Size<Pixels>) -> Pixels,
9012 scroll_icon: IconName,
9013 visible_row_range: Range<DisplayRow>,
9014 line_layouts: &[LineWithInvisibles],
9015 newest_selection_head: Option<DisplayPoint>,
9016 scrolled_content_origin: gpui::Point<Pixels>,
9017 window: &mut Window,
9018 cx: &mut App,
9019 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9020 let mut element = self
9021 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9022 .into_any();
9023
9024 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9025
9026 let cursor = newest_selection_head?;
9027 let cursor_row_layout =
9028 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9029 let cursor_column = cursor.column() as usize;
9030
9031 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9032
9033 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9034
9035 element.prepaint_at(origin, window, cx);
9036 Some((element, origin))
9037 }
9038
9039 fn render_edit_prediction_eager_jump_popover(
9040 &mut self,
9041 text_bounds: &Bounds<Pixels>,
9042 content_origin: gpui::Point<Pixels>,
9043 editor_snapshot: &EditorSnapshot,
9044 visible_row_range: Range<DisplayRow>,
9045 scroll_top: ScrollOffset,
9046 scroll_bottom: ScrollOffset,
9047 line_height: Pixels,
9048 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9049 target_display_point: DisplayPoint,
9050 editor_width: Pixels,
9051 window: &mut Window,
9052 cx: &mut App,
9053 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9054 if target_display_point.row().as_f64() < scroll_top {
9055 let mut element = self
9056 .render_edit_prediction_line_popover(
9057 "Jump to Edit",
9058 Some(IconName::ArrowUp),
9059 window,
9060 cx,
9061 )
9062 .into_any();
9063
9064 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9065 let offset = point(
9066 (text_bounds.size.width - size.width) / 2.,
9067 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9068 );
9069
9070 let origin = text_bounds.origin + offset;
9071 element.prepaint_at(origin, window, cx);
9072 Some((element, origin))
9073 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9074 let mut element = self
9075 .render_edit_prediction_line_popover(
9076 "Jump to Edit",
9077 Some(IconName::ArrowDown),
9078 window,
9079 cx,
9080 )
9081 .into_any();
9082
9083 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9084 let offset = point(
9085 (text_bounds.size.width - size.width) / 2.,
9086 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9087 );
9088
9089 let origin = text_bounds.origin + offset;
9090 element.prepaint_at(origin, window, cx);
9091 Some((element, origin))
9092 } else {
9093 self.render_edit_prediction_end_of_line_popover(
9094 "Jump to Edit",
9095 editor_snapshot,
9096 visible_row_range,
9097 target_display_point,
9098 line_height,
9099 scroll_pixel_position,
9100 content_origin,
9101 editor_width,
9102 window,
9103 cx,
9104 )
9105 }
9106 }
9107
9108 fn render_edit_prediction_end_of_line_popover(
9109 self: &mut Editor,
9110 label: &'static str,
9111 editor_snapshot: &EditorSnapshot,
9112 visible_row_range: Range<DisplayRow>,
9113 target_display_point: DisplayPoint,
9114 line_height: Pixels,
9115 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9116 content_origin: gpui::Point<Pixels>,
9117 editor_width: Pixels,
9118 window: &mut Window,
9119 cx: &mut App,
9120 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9121 let target_line_end = DisplayPoint::new(
9122 target_display_point.row(),
9123 editor_snapshot.line_len(target_display_point.row()),
9124 );
9125
9126 let mut element = self
9127 .render_edit_prediction_line_popover(label, None, window, cx)
9128 .into_any();
9129
9130 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9131
9132 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9133
9134 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9135 let mut origin = start_point
9136 + line_origin
9137 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9138 origin.x = origin.x.max(content_origin.x);
9139
9140 let max_x = content_origin.x + editor_width - size.width;
9141
9142 if origin.x > max_x {
9143 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9144
9145 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9146 origin.y += offset;
9147 IconName::ArrowUp
9148 } else {
9149 origin.y -= offset;
9150 IconName::ArrowDown
9151 };
9152
9153 element = self
9154 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9155 .into_any();
9156
9157 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9158
9159 origin.x = content_origin.x + editor_width - size.width - px(2.);
9160 }
9161
9162 element.prepaint_at(origin, window, cx);
9163 Some((element, origin))
9164 }
9165
9166 fn render_edit_prediction_diff_popover(
9167 self: &Editor,
9168 text_bounds: &Bounds<Pixels>,
9169 content_origin: gpui::Point<Pixels>,
9170 right_margin: Pixels,
9171 editor_snapshot: &EditorSnapshot,
9172 visible_row_range: Range<DisplayRow>,
9173 line_layouts: &[LineWithInvisibles],
9174 line_height: Pixels,
9175 scroll_position: gpui::Point<ScrollOffset>,
9176 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9177 newest_selection_head: Option<DisplayPoint>,
9178 editor_width: Pixels,
9179 style: &EditorStyle,
9180 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9181 edit_preview: &Option<language::EditPreview>,
9182 snapshot: &language::BufferSnapshot,
9183 window: &mut Window,
9184 cx: &mut App,
9185 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9186 let edit_start = edits
9187 .first()
9188 .unwrap()
9189 .0
9190 .start
9191 .to_display_point(editor_snapshot);
9192 let edit_end = edits
9193 .last()
9194 .unwrap()
9195 .0
9196 .end
9197 .to_display_point(editor_snapshot);
9198
9199 let is_visible = visible_row_range.contains(&edit_start.row())
9200 || visible_row_range.contains(&edit_end.row());
9201 if !is_visible {
9202 return None;
9203 }
9204
9205 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9206 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9207 } else {
9208 // Fallback for providers without edit_preview
9209 crate::edit_prediction_fallback_text(edits, cx)
9210 };
9211
9212 let styled_text = highlighted_edits.to_styled_text(&style.text);
9213 let line_count = highlighted_edits.text.lines().count();
9214
9215 const BORDER_WIDTH: Pixels = px(1.);
9216
9217 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9218 let has_keybind = keybind.is_some();
9219
9220 let mut element = h_flex()
9221 .items_start()
9222 .child(
9223 h_flex()
9224 .bg(cx.theme().colors().editor_background)
9225 .border(BORDER_WIDTH)
9226 .shadow_xs()
9227 .border_color(cx.theme().colors().border)
9228 .rounded_l_lg()
9229 .when(line_count > 1, |el| el.rounded_br_lg())
9230 .pr_1()
9231 .child(styled_text),
9232 )
9233 .child(
9234 h_flex()
9235 .h(line_height + BORDER_WIDTH * 2.)
9236 .px_1p5()
9237 .gap_1()
9238 // Workaround: For some reason, there's a gap if we don't do this
9239 .ml(-BORDER_WIDTH)
9240 .shadow(vec![gpui::BoxShadow {
9241 color: gpui::black().opacity(0.05),
9242 offset: point(px(1.), px(1.)),
9243 blur_radius: px(2.),
9244 spread_radius: px(0.),
9245 }])
9246 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9247 .border(BORDER_WIDTH)
9248 .border_color(cx.theme().colors().border)
9249 .rounded_r_lg()
9250 .id("edit_prediction_diff_popover_keybind")
9251 .when(!has_keybind, |el| {
9252 let status_colors = cx.theme().status();
9253
9254 el.bg(status_colors.error_background)
9255 .border_color(status_colors.error.opacity(0.6))
9256 .child(Icon::new(IconName::Info).color(Color::Error))
9257 .cursor_default()
9258 .hoverable_tooltip(move |_window, cx| {
9259 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9260 })
9261 })
9262 .children(keybind),
9263 )
9264 .into_any();
9265
9266 let longest_row =
9267 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9268 let longest_line_width = if visible_row_range.contains(&longest_row) {
9269 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9270 } else {
9271 layout_line(
9272 longest_row,
9273 editor_snapshot,
9274 style,
9275 editor_width,
9276 |_| false,
9277 window,
9278 cx,
9279 )
9280 .width
9281 };
9282
9283 let viewport_bounds =
9284 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9285 right: -right_margin,
9286 ..Default::default()
9287 });
9288
9289 let x_after_longest = Pixels::from(
9290 ScrollPixelOffset::from(
9291 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9292 ) - scroll_pixel_position.x,
9293 );
9294
9295 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9296
9297 // Fully visible if it can be displayed within the window (allow overlapping other
9298 // panes). However, this is only allowed if the popover starts within text_bounds.
9299 let can_position_to_the_right = x_after_longest < text_bounds.right()
9300 && x_after_longest + element_bounds.width < viewport_bounds.right();
9301
9302 let mut origin = if can_position_to_the_right {
9303 point(
9304 x_after_longest,
9305 text_bounds.origin.y
9306 + Pixels::from(
9307 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9308 - scroll_pixel_position.y,
9309 ),
9310 )
9311 } else {
9312 let cursor_row = newest_selection_head.map(|head| head.row());
9313 let above_edit = edit_start
9314 .row()
9315 .0
9316 .checked_sub(line_count as u32)
9317 .map(DisplayRow);
9318 let below_edit = Some(edit_end.row() + 1);
9319 let above_cursor =
9320 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9321 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9322
9323 // Place the edit popover adjacent to the edit if there is a location
9324 // available that is onscreen and does not obscure the cursor. Otherwise,
9325 // place it adjacent to the cursor.
9326 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9327 .into_iter()
9328 .flatten()
9329 .find(|&start_row| {
9330 let end_row = start_row + line_count as u32;
9331 visible_row_range.contains(&start_row)
9332 && visible_row_range.contains(&end_row)
9333 && cursor_row
9334 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9335 })?;
9336
9337 content_origin
9338 + point(
9339 Pixels::from(-scroll_pixel_position.x),
9340 Pixels::from(
9341 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9342 ),
9343 )
9344 };
9345
9346 origin.x -= BORDER_WIDTH;
9347
9348 window.defer_draw(element, origin, 1);
9349
9350 // Do not return an element, since it will already be drawn due to defer_draw.
9351 None
9352 }
9353
9354 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9355 px(30.)
9356 }
9357
9358 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9359 if self.read_only(cx) {
9360 cx.theme().players().read_only()
9361 } else {
9362 self.style.as_ref().unwrap().local_player
9363 }
9364 }
9365
9366 fn render_edit_prediction_accept_keybind(
9367 &self,
9368 window: &mut Window,
9369 cx: &mut App,
9370 ) -> Option<AnyElement> {
9371 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9372 let accept_keystroke = accept_binding.keystroke()?;
9373
9374 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9375
9376 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9377 Color::Accent
9378 } else {
9379 Color::Muted
9380 };
9381
9382 h_flex()
9383 .px_0p5()
9384 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9385 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9386 .text_size(TextSize::XSmall.rems(cx))
9387 .child(h_flex().children(ui::render_modifiers(
9388 accept_keystroke.modifiers(),
9389 PlatformStyle::platform(),
9390 Some(modifiers_color),
9391 Some(IconSize::XSmall.rems().into()),
9392 true,
9393 )))
9394 .when(is_platform_style_mac, |parent| {
9395 parent.child(accept_keystroke.key().to_string())
9396 })
9397 .when(!is_platform_style_mac, |parent| {
9398 parent.child(
9399 Key::new(
9400 util::capitalize(accept_keystroke.key()),
9401 Some(Color::Default),
9402 )
9403 .size(Some(IconSize::XSmall.rems().into())),
9404 )
9405 })
9406 .into_any()
9407 .into()
9408 }
9409
9410 fn render_edit_prediction_line_popover(
9411 &self,
9412 label: impl Into<SharedString>,
9413 icon: Option<IconName>,
9414 window: &mut Window,
9415 cx: &mut App,
9416 ) -> Stateful<Div> {
9417 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9418
9419 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9420 let has_keybind = keybind.is_some();
9421
9422 h_flex()
9423 .id("ep-line-popover")
9424 .py_0p5()
9425 .pl_1()
9426 .pr(padding_right)
9427 .gap_1()
9428 .rounded_md()
9429 .border_1()
9430 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9431 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9432 .shadow_xs()
9433 .when(!has_keybind, |el| {
9434 let status_colors = cx.theme().status();
9435
9436 el.bg(status_colors.error_background)
9437 .border_color(status_colors.error.opacity(0.6))
9438 .pl_2()
9439 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9440 .cursor_default()
9441 .hoverable_tooltip(move |_window, cx| {
9442 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9443 })
9444 })
9445 .children(keybind)
9446 .child(
9447 Label::new(label)
9448 .size(LabelSize::Small)
9449 .when(!has_keybind, |el| {
9450 el.color(cx.theme().status().error.into()).strikethrough()
9451 }),
9452 )
9453 .when(!has_keybind, |el| {
9454 el.child(
9455 h_flex().ml_1().child(
9456 Icon::new(IconName::Info)
9457 .size(IconSize::Small)
9458 .color(cx.theme().status().error.into()),
9459 ),
9460 )
9461 })
9462 .when_some(icon, |element, icon| {
9463 element.child(
9464 div()
9465 .mt(px(1.5))
9466 .child(Icon::new(icon).size(IconSize::Small)),
9467 )
9468 })
9469 }
9470
9471 fn render_edit_prediction_jump_outside_popover(
9472 &self,
9473 snapshot: &BufferSnapshot,
9474 window: &mut Window,
9475 cx: &mut App,
9476 ) -> Stateful<Div> {
9477 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9478 let has_keybind = keybind.is_some();
9479
9480 let file_name = snapshot
9481 .file()
9482 .map(|file| SharedString::new(file.file_name(cx)))
9483 .unwrap_or(SharedString::new_static("untitled"));
9484
9485 h_flex()
9486 .id("ep-jump-outside-popover")
9487 .py_1()
9488 .px_2()
9489 .gap_1()
9490 .rounded_md()
9491 .border_1()
9492 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9493 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9494 .shadow_xs()
9495 .when(!has_keybind, |el| {
9496 let status_colors = cx.theme().status();
9497
9498 el.bg(status_colors.error_background)
9499 .border_color(status_colors.error.opacity(0.6))
9500 .pl_2()
9501 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9502 .cursor_default()
9503 .hoverable_tooltip(move |_window, cx| {
9504 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9505 })
9506 })
9507 .children(keybind)
9508 .child(
9509 Label::new(file_name)
9510 .size(LabelSize::Small)
9511 .buffer_font(cx)
9512 .when(!has_keybind, |el| {
9513 el.color(cx.theme().status().error.into()).strikethrough()
9514 }),
9515 )
9516 .when(!has_keybind, |el| {
9517 el.child(
9518 h_flex().ml_1().child(
9519 Icon::new(IconName::Info)
9520 .size(IconSize::Small)
9521 .color(cx.theme().status().error.into()),
9522 ),
9523 )
9524 })
9525 .child(
9526 div()
9527 .mt(px(1.5))
9528 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9529 )
9530 }
9531
9532 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9533 let accent_color = cx.theme().colors().text_accent;
9534 let editor_bg_color = cx.theme().colors().editor_background;
9535 editor_bg_color.blend(accent_color.opacity(0.1))
9536 }
9537
9538 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9539 let accent_color = cx.theme().colors().text_accent;
9540 let editor_bg_color = cx.theme().colors().editor_background;
9541 editor_bg_color.blend(accent_color.opacity(0.6))
9542 }
9543 fn get_prediction_provider_icon_name(
9544 provider: &Option<RegisteredEditPredictionProvider>,
9545 ) -> IconName {
9546 match provider {
9547 Some(provider) => match provider.provider.name() {
9548 "copilot" => IconName::Copilot,
9549 "supermaven" => IconName::Supermaven,
9550 _ => IconName::ZedPredict,
9551 },
9552 None => IconName::ZedPredict,
9553 }
9554 }
9555
9556 fn render_edit_prediction_cursor_popover(
9557 &self,
9558 min_width: Pixels,
9559 max_width: Pixels,
9560 cursor_point: Point,
9561 style: &EditorStyle,
9562 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9563 _window: &Window,
9564 cx: &mut Context<Editor>,
9565 ) -> Option<AnyElement> {
9566 let provider = self.edit_prediction_provider.as_ref()?;
9567 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9568
9569 let is_refreshing = provider.provider.is_refreshing(cx);
9570
9571 fn pending_completion_container(icon: IconName) -> Div {
9572 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9573 }
9574
9575 let completion = match &self.active_edit_prediction {
9576 Some(prediction) => {
9577 if !self.has_visible_completions_menu() {
9578 const RADIUS: Pixels = px(6.);
9579 const BORDER_WIDTH: Pixels = px(1.);
9580
9581 return Some(
9582 h_flex()
9583 .elevation_2(cx)
9584 .border(BORDER_WIDTH)
9585 .border_color(cx.theme().colors().border)
9586 .when(accept_keystroke.is_none(), |el| {
9587 el.border_color(cx.theme().status().error)
9588 })
9589 .rounded(RADIUS)
9590 .rounded_tl(px(0.))
9591 .overflow_hidden()
9592 .child(div().px_1p5().child(match &prediction.completion {
9593 EditPrediction::MoveWithin { target, snapshot } => {
9594 use text::ToPoint as _;
9595 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9596 {
9597 Icon::new(IconName::ZedPredictDown)
9598 } else {
9599 Icon::new(IconName::ZedPredictUp)
9600 }
9601 }
9602 EditPrediction::MoveOutside { .. } => {
9603 // TODO [zeta2] custom icon for external jump?
9604 Icon::new(provider_icon)
9605 }
9606 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9607 }))
9608 .child(
9609 h_flex()
9610 .gap_1()
9611 .py_1()
9612 .px_2()
9613 .rounded_r(RADIUS - BORDER_WIDTH)
9614 .border_l_1()
9615 .border_color(cx.theme().colors().border)
9616 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9617 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9618 el.child(
9619 Label::new("Hold")
9620 .size(LabelSize::Small)
9621 .when(accept_keystroke.is_none(), |el| {
9622 el.strikethrough()
9623 })
9624 .line_height_style(LineHeightStyle::UiLabel),
9625 )
9626 })
9627 .id("edit_prediction_cursor_popover_keybind")
9628 .when(accept_keystroke.is_none(), |el| {
9629 let status_colors = cx.theme().status();
9630
9631 el.bg(status_colors.error_background)
9632 .border_color(status_colors.error.opacity(0.6))
9633 .child(Icon::new(IconName::Info).color(Color::Error))
9634 .cursor_default()
9635 .hoverable_tooltip(move |_window, cx| {
9636 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9637 .into()
9638 })
9639 })
9640 .when_some(
9641 accept_keystroke.as_ref(),
9642 |el, accept_keystroke| {
9643 el.child(h_flex().children(ui::render_modifiers(
9644 accept_keystroke.modifiers(),
9645 PlatformStyle::platform(),
9646 Some(Color::Default),
9647 Some(IconSize::XSmall.rems().into()),
9648 false,
9649 )))
9650 },
9651 ),
9652 )
9653 .into_any(),
9654 );
9655 }
9656
9657 self.render_edit_prediction_cursor_popover_preview(
9658 prediction,
9659 cursor_point,
9660 style,
9661 cx,
9662 )?
9663 }
9664
9665 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9666 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9667 stale_completion,
9668 cursor_point,
9669 style,
9670 cx,
9671 )?,
9672
9673 None => pending_completion_container(provider_icon)
9674 .child(Label::new("...").size(LabelSize::Small)),
9675 },
9676
9677 None => pending_completion_container(provider_icon)
9678 .child(Label::new("...").size(LabelSize::Small)),
9679 };
9680
9681 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9682 completion
9683 .with_animation(
9684 "loading-completion",
9685 Animation::new(Duration::from_secs(2))
9686 .repeat()
9687 .with_easing(pulsating_between(0.4, 0.8)),
9688 |label, delta| label.opacity(delta),
9689 )
9690 .into_any_element()
9691 } else {
9692 completion.into_any_element()
9693 };
9694
9695 let has_completion = self.active_edit_prediction.is_some();
9696
9697 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9698 Some(
9699 h_flex()
9700 .min_w(min_width)
9701 .max_w(max_width)
9702 .flex_1()
9703 .elevation_2(cx)
9704 .border_color(cx.theme().colors().border)
9705 .child(
9706 div()
9707 .flex_1()
9708 .py_1()
9709 .px_2()
9710 .overflow_hidden()
9711 .child(completion),
9712 )
9713 .when_some(accept_keystroke, |el, accept_keystroke| {
9714 if !accept_keystroke.modifiers().modified() {
9715 return el;
9716 }
9717
9718 el.child(
9719 h_flex()
9720 .h_full()
9721 .border_l_1()
9722 .rounded_r_lg()
9723 .border_color(cx.theme().colors().border)
9724 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9725 .gap_1()
9726 .py_1()
9727 .px_2()
9728 .child(
9729 h_flex()
9730 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9731 .when(is_platform_style_mac, |parent| parent.gap_1())
9732 .child(h_flex().children(ui::render_modifiers(
9733 accept_keystroke.modifiers(),
9734 PlatformStyle::platform(),
9735 Some(if !has_completion {
9736 Color::Muted
9737 } else {
9738 Color::Default
9739 }),
9740 None,
9741 false,
9742 ))),
9743 )
9744 .child(Label::new("Preview").into_any_element())
9745 .opacity(if has_completion { 1.0 } else { 0.4 }),
9746 )
9747 })
9748 .into_any(),
9749 )
9750 }
9751
9752 fn render_edit_prediction_cursor_popover_preview(
9753 &self,
9754 completion: &EditPredictionState,
9755 cursor_point: Point,
9756 style: &EditorStyle,
9757 cx: &mut Context<Editor>,
9758 ) -> Option<Div> {
9759 use text::ToPoint as _;
9760
9761 fn render_relative_row_jump(
9762 prefix: impl Into<String>,
9763 current_row: u32,
9764 target_row: u32,
9765 ) -> Div {
9766 let (row_diff, arrow) = if target_row < current_row {
9767 (current_row - target_row, IconName::ArrowUp)
9768 } else {
9769 (target_row - current_row, IconName::ArrowDown)
9770 };
9771
9772 h_flex()
9773 .child(
9774 Label::new(format!("{}{}", prefix.into(), row_diff))
9775 .color(Color::Muted)
9776 .size(LabelSize::Small),
9777 )
9778 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9779 }
9780
9781 let supports_jump = self
9782 .edit_prediction_provider
9783 .as_ref()
9784 .map(|provider| provider.provider.supports_jump_to_edit())
9785 .unwrap_or(true);
9786
9787 match &completion.completion {
9788 EditPrediction::MoveWithin {
9789 target, snapshot, ..
9790 } => {
9791 if !supports_jump {
9792 return None;
9793 }
9794
9795 Some(
9796 h_flex()
9797 .px_2()
9798 .gap_2()
9799 .flex_1()
9800 .child(
9801 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9802 Icon::new(IconName::ZedPredictDown)
9803 } else {
9804 Icon::new(IconName::ZedPredictUp)
9805 },
9806 )
9807 .child(Label::new("Jump to Edit")),
9808 )
9809 }
9810 EditPrediction::MoveOutside { snapshot, .. } => {
9811 let file_name = snapshot
9812 .file()
9813 .map(|file| file.file_name(cx))
9814 .unwrap_or("untitled");
9815 Some(
9816 h_flex()
9817 .px_2()
9818 .gap_2()
9819 .flex_1()
9820 .child(Icon::new(IconName::ZedPredict))
9821 .child(Label::new(format!("Jump to {file_name}"))),
9822 )
9823 }
9824 EditPrediction::Edit {
9825 edits,
9826 edit_preview,
9827 snapshot,
9828 display_mode: _,
9829 } => {
9830 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9831
9832 let (highlighted_edits, has_more_lines) =
9833 if let Some(edit_preview) = edit_preview.as_ref() {
9834 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9835 .first_line_preview()
9836 } else {
9837 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9838 };
9839
9840 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9841 .with_default_highlights(&style.text, highlighted_edits.highlights);
9842
9843 let preview = h_flex()
9844 .gap_1()
9845 .min_w_16()
9846 .child(styled_text)
9847 .when(has_more_lines, |parent| parent.child("…"));
9848
9849 let left = if supports_jump && first_edit_row != cursor_point.row {
9850 render_relative_row_jump("", cursor_point.row, first_edit_row)
9851 .into_any_element()
9852 } else {
9853 let icon_name =
9854 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9855 Icon::new(icon_name).into_any_element()
9856 };
9857
9858 Some(
9859 h_flex()
9860 .h_full()
9861 .flex_1()
9862 .gap_2()
9863 .pr_1()
9864 .overflow_x_hidden()
9865 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9866 .child(left)
9867 .child(preview),
9868 )
9869 }
9870 }
9871 }
9872
9873 pub fn render_context_menu(
9874 &self,
9875 style: &EditorStyle,
9876 max_height_in_lines: u32,
9877 window: &mut Window,
9878 cx: &mut Context<Editor>,
9879 ) -> Option<AnyElement> {
9880 let menu = self.context_menu.borrow();
9881 let menu = menu.as_ref()?;
9882 if !menu.visible() {
9883 return None;
9884 };
9885 Some(menu.render(style, max_height_in_lines, window, cx))
9886 }
9887
9888 fn render_context_menu_aside(
9889 &mut self,
9890 max_size: Size<Pixels>,
9891 window: &mut Window,
9892 cx: &mut Context<Editor>,
9893 ) -> Option<AnyElement> {
9894 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9895 if menu.visible() {
9896 menu.render_aside(max_size, window, cx)
9897 } else {
9898 None
9899 }
9900 })
9901 }
9902
9903 fn hide_context_menu(
9904 &mut self,
9905 window: &mut Window,
9906 cx: &mut Context<Self>,
9907 ) -> Option<CodeContextMenu> {
9908 cx.notify();
9909 self.completion_tasks.clear();
9910 let context_menu = self.context_menu.borrow_mut().take();
9911 self.stale_edit_prediction_in_menu.take();
9912 self.update_visible_edit_prediction(window, cx);
9913 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9914 && let Some(completion_provider) = &self.completion_provider
9915 {
9916 completion_provider.selection_changed(None, window, cx);
9917 }
9918 context_menu
9919 }
9920
9921 fn show_snippet_choices(
9922 &mut self,
9923 choices: &Vec<String>,
9924 selection: Range<Anchor>,
9925 cx: &mut Context<Self>,
9926 ) {
9927 let Some((_, buffer, _)) = self
9928 .buffer()
9929 .read(cx)
9930 .excerpt_containing(selection.start, cx)
9931 else {
9932 return;
9933 };
9934 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9935 else {
9936 return;
9937 };
9938 if buffer != end_buffer {
9939 log::error!("expected anchor range to have matching buffer IDs");
9940 return;
9941 }
9942
9943 let id = post_inc(&mut self.next_completion_id);
9944 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9945 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9946 CompletionsMenu::new_snippet_choices(
9947 id,
9948 true,
9949 choices,
9950 selection,
9951 buffer,
9952 snippet_sort_order,
9953 ),
9954 ));
9955 }
9956
9957 pub fn insert_snippet(
9958 &mut self,
9959 insertion_ranges: &[Range<MultiBufferOffset>],
9960 snippet: Snippet,
9961 window: &mut Window,
9962 cx: &mut Context<Self>,
9963 ) -> Result<()> {
9964 struct Tabstop<T> {
9965 is_end_tabstop: bool,
9966 ranges: Vec<Range<T>>,
9967 choices: Option<Vec<String>>,
9968 }
9969
9970 let tabstops = self.buffer.update(cx, |buffer, cx| {
9971 let snippet_text: Arc<str> = snippet.text.clone().into();
9972 let edits = insertion_ranges
9973 .iter()
9974 .cloned()
9975 .map(|range| (range, snippet_text.clone()));
9976 let autoindent_mode = AutoindentMode::Block {
9977 original_indent_columns: Vec::new(),
9978 };
9979 buffer.edit(edits, Some(autoindent_mode), cx);
9980
9981 let snapshot = &*buffer.read(cx);
9982 let snippet = &snippet;
9983 snippet
9984 .tabstops
9985 .iter()
9986 .map(|tabstop| {
9987 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9988 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9989 });
9990 let mut tabstop_ranges = tabstop
9991 .ranges
9992 .iter()
9993 .flat_map(|tabstop_range| {
9994 let mut delta = 0_isize;
9995 insertion_ranges.iter().map(move |insertion_range| {
9996 let insertion_start = insertion_range.start + delta;
9997 delta += snippet.text.len() as isize
9998 - (insertion_range.end - insertion_range.start) as isize;
9999
10000 let start =
10001 (insertion_start + tabstop_range.start).min(snapshot.len());
10002 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10003 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10004 })
10005 })
10006 .collect::<Vec<_>>();
10007 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10008
10009 Tabstop {
10010 is_end_tabstop,
10011 ranges: tabstop_ranges,
10012 choices: tabstop.choices.clone(),
10013 }
10014 })
10015 .collect::<Vec<_>>()
10016 });
10017 if let Some(tabstop) = tabstops.first() {
10018 self.change_selections(Default::default(), window, cx, |s| {
10019 // Reverse order so that the first range is the newest created selection.
10020 // Completions will use it and autoscroll will prioritize it.
10021 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10022 });
10023
10024 if let Some(choices) = &tabstop.choices
10025 && let Some(selection) = tabstop.ranges.first()
10026 {
10027 self.show_snippet_choices(choices, selection.clone(), cx)
10028 }
10029
10030 // If we're already at the last tabstop and it's at the end of the snippet,
10031 // we're done, we don't need to keep the state around.
10032 if !tabstop.is_end_tabstop {
10033 let choices = tabstops
10034 .iter()
10035 .map(|tabstop| tabstop.choices.clone())
10036 .collect();
10037
10038 let ranges = tabstops
10039 .into_iter()
10040 .map(|tabstop| tabstop.ranges)
10041 .collect::<Vec<_>>();
10042
10043 self.snippet_stack.push(SnippetState {
10044 active_index: 0,
10045 ranges,
10046 choices,
10047 });
10048 }
10049
10050 // Check whether the just-entered snippet ends with an auto-closable bracket.
10051 if self.autoclose_regions.is_empty() {
10052 let snapshot = self.buffer.read(cx).snapshot(cx);
10053 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10054 let selection_head = selection.head();
10055 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10056 continue;
10057 };
10058
10059 let mut bracket_pair = None;
10060 let max_lookup_length = scope
10061 .brackets()
10062 .map(|(pair, _)| {
10063 pair.start
10064 .as_str()
10065 .chars()
10066 .count()
10067 .max(pair.end.as_str().chars().count())
10068 })
10069 .max();
10070 if let Some(max_lookup_length) = max_lookup_length {
10071 let next_text = snapshot
10072 .chars_at(selection_head)
10073 .take(max_lookup_length)
10074 .collect::<String>();
10075 let prev_text = snapshot
10076 .reversed_chars_at(selection_head)
10077 .take(max_lookup_length)
10078 .collect::<String>();
10079
10080 for (pair, enabled) in scope.brackets() {
10081 if enabled
10082 && pair.close
10083 && prev_text.starts_with(pair.start.as_str())
10084 && next_text.starts_with(pair.end.as_str())
10085 {
10086 bracket_pair = Some(pair.clone());
10087 break;
10088 }
10089 }
10090 }
10091
10092 if let Some(pair) = bracket_pair {
10093 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10094 let autoclose_enabled =
10095 self.use_autoclose && snapshot_settings.use_autoclose;
10096 if autoclose_enabled {
10097 let start = snapshot.anchor_after(selection_head);
10098 let end = snapshot.anchor_after(selection_head);
10099 self.autoclose_regions.push(AutocloseRegion {
10100 selection_id: selection.id,
10101 range: start..end,
10102 pair,
10103 });
10104 }
10105 }
10106 }
10107 }
10108 }
10109 Ok(())
10110 }
10111
10112 pub fn move_to_next_snippet_tabstop(
10113 &mut self,
10114 window: &mut Window,
10115 cx: &mut Context<Self>,
10116 ) -> bool {
10117 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10118 }
10119
10120 pub fn move_to_prev_snippet_tabstop(
10121 &mut self,
10122 window: &mut Window,
10123 cx: &mut Context<Self>,
10124 ) -> bool {
10125 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10126 }
10127
10128 pub fn move_to_snippet_tabstop(
10129 &mut self,
10130 bias: Bias,
10131 window: &mut Window,
10132 cx: &mut Context<Self>,
10133 ) -> bool {
10134 if let Some(mut snippet) = self.snippet_stack.pop() {
10135 match bias {
10136 Bias::Left => {
10137 if snippet.active_index > 0 {
10138 snippet.active_index -= 1;
10139 } else {
10140 self.snippet_stack.push(snippet);
10141 return false;
10142 }
10143 }
10144 Bias::Right => {
10145 if snippet.active_index + 1 < snippet.ranges.len() {
10146 snippet.active_index += 1;
10147 } else {
10148 self.snippet_stack.push(snippet);
10149 return false;
10150 }
10151 }
10152 }
10153 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10154 self.change_selections(Default::default(), window, cx, |s| {
10155 // Reverse order so that the first range is the newest created selection.
10156 // Completions will use it and autoscroll will prioritize it.
10157 s.select_ranges(current_ranges.iter().rev().cloned())
10158 });
10159
10160 if let Some(choices) = &snippet.choices[snippet.active_index]
10161 && let Some(selection) = current_ranges.first()
10162 {
10163 self.show_snippet_choices(choices, selection.clone(), cx);
10164 }
10165
10166 // If snippet state is not at the last tabstop, push it back on the stack
10167 if snippet.active_index + 1 < snippet.ranges.len() {
10168 self.snippet_stack.push(snippet);
10169 }
10170 return true;
10171 }
10172 }
10173
10174 false
10175 }
10176
10177 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10178 self.transact(window, cx, |this, window, cx| {
10179 this.select_all(&SelectAll, window, cx);
10180 this.insert("", window, cx);
10181 });
10182 }
10183
10184 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10185 if self.read_only(cx) {
10186 return;
10187 }
10188 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10189 self.transact(window, cx, |this, window, cx| {
10190 this.select_autoclose_pair(window, cx);
10191
10192 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10193
10194 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10195 if !this.linked_edit_ranges.is_empty() {
10196 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10197 let snapshot = this.buffer.read(cx).snapshot(cx);
10198
10199 for selection in selections.iter() {
10200 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10201 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10202 if selection_start.buffer_id != selection_end.buffer_id {
10203 continue;
10204 }
10205 if let Some(ranges) =
10206 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10207 {
10208 for (buffer, entries) in ranges {
10209 linked_ranges.entry(buffer).or_default().extend(entries);
10210 }
10211 }
10212 }
10213 }
10214
10215 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10216 for selection in &mut selections {
10217 if selection.is_empty() {
10218 let old_head = selection.head();
10219 let mut new_head =
10220 movement::left(&display_map, old_head.to_display_point(&display_map))
10221 .to_point(&display_map);
10222 if let Some((buffer, line_buffer_range)) = display_map
10223 .buffer_snapshot()
10224 .buffer_line_for_row(MultiBufferRow(old_head.row))
10225 {
10226 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10227 let indent_len = match indent_size.kind {
10228 IndentKind::Space => {
10229 buffer.settings_at(line_buffer_range.start, cx).tab_size
10230 }
10231 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10232 };
10233 if old_head.column <= indent_size.len && old_head.column > 0 {
10234 let indent_len = indent_len.get();
10235 new_head = cmp::min(
10236 new_head,
10237 MultiBufferPoint::new(
10238 old_head.row,
10239 ((old_head.column - 1) / indent_len) * indent_len,
10240 ),
10241 );
10242 }
10243 }
10244
10245 selection.set_head(new_head, SelectionGoal::None);
10246 }
10247 }
10248
10249 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10250 this.insert("", window, cx);
10251 let empty_str: Arc<str> = Arc::from("");
10252 for (buffer, edits) in linked_ranges {
10253 let snapshot = buffer.read(cx).snapshot();
10254 use text::ToPoint as TP;
10255
10256 let edits = edits
10257 .into_iter()
10258 .map(|range| {
10259 let end_point = TP::to_point(&range.end, &snapshot);
10260 let mut start_point = TP::to_point(&range.start, &snapshot);
10261
10262 if end_point == start_point {
10263 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10264 .saturating_sub(1);
10265 start_point =
10266 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10267 };
10268
10269 (start_point..end_point, empty_str.clone())
10270 })
10271 .sorted_by_key(|(range, _)| range.start)
10272 .collect::<Vec<_>>();
10273 buffer.update(cx, |this, cx| {
10274 this.edit(edits, None, cx);
10275 })
10276 }
10277 this.refresh_edit_prediction(true, false, window, cx);
10278 refresh_linked_ranges(this, window, cx);
10279 });
10280 }
10281
10282 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10283 if self.read_only(cx) {
10284 return;
10285 }
10286 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10287 self.transact(window, cx, |this, window, cx| {
10288 this.change_selections(Default::default(), window, cx, |s| {
10289 s.move_with(|map, selection| {
10290 if selection.is_empty() {
10291 let cursor = movement::right(map, selection.head());
10292 selection.end = cursor;
10293 selection.reversed = true;
10294 selection.goal = SelectionGoal::None;
10295 }
10296 })
10297 });
10298 this.insert("", window, cx);
10299 this.refresh_edit_prediction(true, false, window, cx);
10300 });
10301 }
10302
10303 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10304 if self.mode.is_single_line() {
10305 cx.propagate();
10306 return;
10307 }
10308
10309 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10310 if self.move_to_prev_snippet_tabstop(window, cx) {
10311 return;
10312 }
10313 self.outdent(&Outdent, window, cx);
10314 }
10315
10316 pub fn next_snippet_tabstop(
10317 &mut self,
10318 _: &NextSnippetTabstop,
10319 window: &mut Window,
10320 cx: &mut Context<Self>,
10321 ) {
10322 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10323 cx.propagate();
10324 return;
10325 }
10326
10327 if self.move_to_next_snippet_tabstop(window, cx) {
10328 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10329 return;
10330 }
10331 cx.propagate();
10332 }
10333
10334 pub fn previous_snippet_tabstop(
10335 &mut self,
10336 _: &PreviousSnippetTabstop,
10337 window: &mut Window,
10338 cx: &mut Context<Self>,
10339 ) {
10340 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10341 cx.propagate();
10342 return;
10343 }
10344
10345 if self.move_to_prev_snippet_tabstop(window, cx) {
10346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10347 return;
10348 }
10349 cx.propagate();
10350 }
10351
10352 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10353 if self.mode.is_single_line() {
10354 cx.propagate();
10355 return;
10356 }
10357
10358 if self.move_to_next_snippet_tabstop(window, cx) {
10359 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10360 return;
10361 }
10362 if self.read_only(cx) {
10363 return;
10364 }
10365 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10366 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10367 let buffer = self.buffer.read(cx);
10368 let snapshot = buffer.snapshot(cx);
10369 let rows_iter = selections.iter().map(|s| s.head().row);
10370 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10371
10372 let has_some_cursor_in_whitespace = selections
10373 .iter()
10374 .filter(|selection| selection.is_empty())
10375 .any(|selection| {
10376 let cursor = selection.head();
10377 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10378 cursor.column < current_indent.len
10379 });
10380
10381 let mut edits = Vec::new();
10382 let mut prev_edited_row = 0;
10383 let mut row_delta = 0;
10384 for selection in &mut selections {
10385 if selection.start.row != prev_edited_row {
10386 row_delta = 0;
10387 }
10388 prev_edited_row = selection.end.row;
10389
10390 // If the selection is non-empty, then increase the indentation of the selected lines.
10391 if !selection.is_empty() {
10392 row_delta =
10393 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10394 continue;
10395 }
10396
10397 let cursor = selection.head();
10398 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10399 if let Some(suggested_indent) =
10400 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10401 {
10402 // Don't do anything if already at suggested indent
10403 // and there is any other cursor which is not
10404 if has_some_cursor_in_whitespace
10405 && cursor.column == current_indent.len
10406 && current_indent.len == suggested_indent.len
10407 {
10408 continue;
10409 }
10410
10411 // Adjust line and move cursor to suggested indent
10412 // if cursor is not at suggested indent
10413 if cursor.column < suggested_indent.len
10414 && cursor.column <= current_indent.len
10415 && current_indent.len <= suggested_indent.len
10416 {
10417 selection.start = Point::new(cursor.row, suggested_indent.len);
10418 selection.end = selection.start;
10419 if row_delta == 0 {
10420 edits.extend(Buffer::edit_for_indent_size_adjustment(
10421 cursor.row,
10422 current_indent,
10423 suggested_indent,
10424 ));
10425 row_delta = suggested_indent.len - current_indent.len;
10426 }
10427 continue;
10428 }
10429
10430 // If current indent is more than suggested indent
10431 // only move cursor to current indent and skip indent
10432 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10433 selection.start = Point::new(cursor.row, current_indent.len);
10434 selection.end = selection.start;
10435 continue;
10436 }
10437 }
10438
10439 // Otherwise, insert a hard or soft tab.
10440 let settings = buffer.language_settings_at(cursor, cx);
10441 let tab_size = if settings.hard_tabs {
10442 IndentSize::tab()
10443 } else {
10444 let tab_size = settings.tab_size.get();
10445 let indent_remainder = snapshot
10446 .text_for_range(Point::new(cursor.row, 0)..cursor)
10447 .flat_map(str::chars)
10448 .fold(row_delta % tab_size, |counter: u32, c| {
10449 if c == '\t' {
10450 0
10451 } else {
10452 (counter + 1) % tab_size
10453 }
10454 });
10455
10456 let chars_to_next_tab_stop = tab_size - indent_remainder;
10457 IndentSize::spaces(chars_to_next_tab_stop)
10458 };
10459 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10460 selection.end = selection.start;
10461 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10462 row_delta += tab_size.len;
10463 }
10464
10465 self.transact(window, cx, |this, window, cx| {
10466 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10467 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10468 this.refresh_edit_prediction(true, false, window, cx);
10469 });
10470 }
10471
10472 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10473 if self.read_only(cx) {
10474 return;
10475 }
10476 if self.mode.is_single_line() {
10477 cx.propagate();
10478 return;
10479 }
10480
10481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10482 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10483 let mut prev_edited_row = 0;
10484 let mut row_delta = 0;
10485 let mut edits = Vec::new();
10486 let buffer = self.buffer.read(cx);
10487 let snapshot = buffer.snapshot(cx);
10488 for selection in &mut selections {
10489 if selection.start.row != prev_edited_row {
10490 row_delta = 0;
10491 }
10492 prev_edited_row = selection.end.row;
10493
10494 row_delta =
10495 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10496 }
10497
10498 self.transact(window, cx, |this, window, cx| {
10499 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10500 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10501 });
10502 }
10503
10504 fn indent_selection(
10505 buffer: &MultiBuffer,
10506 snapshot: &MultiBufferSnapshot,
10507 selection: &mut Selection<Point>,
10508 edits: &mut Vec<(Range<Point>, String)>,
10509 delta_for_start_row: u32,
10510 cx: &App,
10511 ) -> u32 {
10512 let settings = buffer.language_settings_at(selection.start, cx);
10513 let tab_size = settings.tab_size.get();
10514 let indent_kind = if settings.hard_tabs {
10515 IndentKind::Tab
10516 } else {
10517 IndentKind::Space
10518 };
10519 let mut start_row = selection.start.row;
10520 let mut end_row = selection.end.row + 1;
10521
10522 // If a selection ends at the beginning of a line, don't indent
10523 // that last line.
10524 if selection.end.column == 0 && selection.end.row > selection.start.row {
10525 end_row -= 1;
10526 }
10527
10528 // Avoid re-indenting a row that has already been indented by a
10529 // previous selection, but still update this selection's column
10530 // to reflect that indentation.
10531 if delta_for_start_row > 0 {
10532 start_row += 1;
10533 selection.start.column += delta_for_start_row;
10534 if selection.end.row == selection.start.row {
10535 selection.end.column += delta_for_start_row;
10536 }
10537 }
10538
10539 let mut delta_for_end_row = 0;
10540 let has_multiple_rows = start_row + 1 != end_row;
10541 for row in start_row..end_row {
10542 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10543 let indent_delta = match (current_indent.kind, indent_kind) {
10544 (IndentKind::Space, IndentKind::Space) => {
10545 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10546 IndentSize::spaces(columns_to_next_tab_stop)
10547 }
10548 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10549 (_, IndentKind::Tab) => IndentSize::tab(),
10550 };
10551
10552 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10553 0
10554 } else {
10555 selection.start.column
10556 };
10557 let row_start = Point::new(row, start);
10558 edits.push((
10559 row_start..row_start,
10560 indent_delta.chars().collect::<String>(),
10561 ));
10562
10563 // Update this selection's endpoints to reflect the indentation.
10564 if row == selection.start.row {
10565 selection.start.column += indent_delta.len;
10566 }
10567 if row == selection.end.row {
10568 selection.end.column += indent_delta.len;
10569 delta_for_end_row = indent_delta.len;
10570 }
10571 }
10572
10573 if selection.start.row == selection.end.row {
10574 delta_for_start_row + delta_for_end_row
10575 } else {
10576 delta_for_end_row
10577 }
10578 }
10579
10580 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10581 if self.read_only(cx) {
10582 return;
10583 }
10584 if self.mode.is_single_line() {
10585 cx.propagate();
10586 return;
10587 }
10588
10589 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10590 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10591 let selections = self.selections.all::<Point>(&display_map);
10592 let mut deletion_ranges = Vec::new();
10593 let mut last_outdent = None;
10594 {
10595 let buffer = self.buffer.read(cx);
10596 let snapshot = buffer.snapshot(cx);
10597 for selection in &selections {
10598 let settings = buffer.language_settings_at(selection.start, cx);
10599 let tab_size = settings.tab_size.get();
10600 let mut rows = selection.spanned_rows(false, &display_map);
10601
10602 // Avoid re-outdenting a row that has already been outdented by a
10603 // previous selection.
10604 if let Some(last_row) = last_outdent
10605 && last_row == rows.start
10606 {
10607 rows.start = rows.start.next_row();
10608 }
10609 let has_multiple_rows = rows.len() > 1;
10610 for row in rows.iter_rows() {
10611 let indent_size = snapshot.indent_size_for_line(row);
10612 if indent_size.len > 0 {
10613 let deletion_len = match indent_size.kind {
10614 IndentKind::Space => {
10615 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10616 if columns_to_prev_tab_stop == 0 {
10617 tab_size
10618 } else {
10619 columns_to_prev_tab_stop
10620 }
10621 }
10622 IndentKind::Tab => 1,
10623 };
10624 let start = if has_multiple_rows
10625 || deletion_len > selection.start.column
10626 || indent_size.len < selection.start.column
10627 {
10628 0
10629 } else {
10630 selection.start.column - deletion_len
10631 };
10632 deletion_ranges.push(
10633 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10634 );
10635 last_outdent = Some(row);
10636 }
10637 }
10638 }
10639 }
10640
10641 self.transact(window, cx, |this, window, cx| {
10642 this.buffer.update(cx, |buffer, cx| {
10643 let empty_str: Arc<str> = Arc::default();
10644 buffer.edit(
10645 deletion_ranges
10646 .into_iter()
10647 .map(|range| (range, empty_str.clone())),
10648 None,
10649 cx,
10650 );
10651 });
10652 let selections = this
10653 .selections
10654 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10655 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10656 });
10657 }
10658
10659 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10660 if self.read_only(cx) {
10661 return;
10662 }
10663 if self.mode.is_single_line() {
10664 cx.propagate();
10665 return;
10666 }
10667
10668 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10669 let selections = self
10670 .selections
10671 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10672 .into_iter()
10673 .map(|s| s.range());
10674
10675 self.transact(window, cx, |this, window, cx| {
10676 this.buffer.update(cx, |buffer, cx| {
10677 buffer.autoindent_ranges(selections, cx);
10678 });
10679 let selections = this
10680 .selections
10681 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10682 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10683 });
10684 }
10685
10686 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10687 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10688 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10689 let selections = self.selections.all::<Point>(&display_map);
10690
10691 let mut new_cursors = Vec::new();
10692 let mut edit_ranges = Vec::new();
10693 let mut selections = selections.iter().peekable();
10694 while let Some(selection) = selections.next() {
10695 let mut rows = selection.spanned_rows(false, &display_map);
10696
10697 // Accumulate contiguous regions of rows that we want to delete.
10698 while let Some(next_selection) = selections.peek() {
10699 let next_rows = next_selection.spanned_rows(false, &display_map);
10700 if next_rows.start <= rows.end {
10701 rows.end = next_rows.end;
10702 selections.next().unwrap();
10703 } else {
10704 break;
10705 }
10706 }
10707
10708 let buffer = display_map.buffer_snapshot();
10709 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10710 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10711 // If there's a line after the range, delete the \n from the end of the row range
10712 (
10713 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10714 rows.end,
10715 )
10716 } else {
10717 // If there isn't a line after the range, delete the \n from the line before the
10718 // start of the row range
10719 edit_start = edit_start.saturating_sub_usize(1);
10720 (buffer.len(), rows.start.previous_row())
10721 };
10722
10723 let text_layout_details = self.text_layout_details(window);
10724 let x = display_map.x_for_display_point(
10725 selection.head().to_display_point(&display_map),
10726 &text_layout_details,
10727 );
10728 let row = Point::new(target_row.0, 0)
10729 .to_display_point(&display_map)
10730 .row();
10731 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10732
10733 new_cursors.push((
10734 selection.id,
10735 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10736 SelectionGoal::None,
10737 ));
10738 edit_ranges.push(edit_start..edit_end);
10739 }
10740
10741 self.transact(window, cx, |this, window, cx| {
10742 let buffer = this.buffer.update(cx, |buffer, cx| {
10743 let empty_str: Arc<str> = Arc::default();
10744 buffer.edit(
10745 edit_ranges
10746 .into_iter()
10747 .map(|range| (range, empty_str.clone())),
10748 None,
10749 cx,
10750 );
10751 buffer.snapshot(cx)
10752 });
10753 let new_selections = new_cursors
10754 .into_iter()
10755 .map(|(id, cursor, goal)| {
10756 let cursor = cursor.to_point(&buffer);
10757 Selection {
10758 id,
10759 start: cursor,
10760 end: cursor,
10761 reversed: false,
10762 goal,
10763 }
10764 })
10765 .collect();
10766
10767 this.change_selections(Default::default(), window, cx, |s| {
10768 s.select(new_selections);
10769 });
10770 });
10771 }
10772
10773 pub fn join_lines_impl(
10774 &mut self,
10775 insert_whitespace: bool,
10776 window: &mut Window,
10777 cx: &mut Context<Self>,
10778 ) {
10779 if self.read_only(cx) {
10780 return;
10781 }
10782 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10783 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10784 let start = MultiBufferRow(selection.start.row);
10785 // Treat single line selections as if they include the next line. Otherwise this action
10786 // would do nothing for single line selections individual cursors.
10787 let end = if selection.start.row == selection.end.row {
10788 MultiBufferRow(selection.start.row + 1)
10789 } else {
10790 MultiBufferRow(selection.end.row)
10791 };
10792
10793 if let Some(last_row_range) = row_ranges.last_mut()
10794 && start <= last_row_range.end
10795 {
10796 last_row_range.end = end;
10797 continue;
10798 }
10799 row_ranges.push(start..end);
10800 }
10801
10802 let snapshot = self.buffer.read(cx).snapshot(cx);
10803 let mut cursor_positions = Vec::new();
10804 for row_range in &row_ranges {
10805 let anchor = snapshot.anchor_before(Point::new(
10806 row_range.end.previous_row().0,
10807 snapshot.line_len(row_range.end.previous_row()),
10808 ));
10809 cursor_positions.push(anchor..anchor);
10810 }
10811
10812 self.transact(window, cx, |this, window, cx| {
10813 for row_range in row_ranges.into_iter().rev() {
10814 for row in row_range.iter_rows().rev() {
10815 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10816 let next_line_row = row.next_row();
10817 let indent = snapshot.indent_size_for_line(next_line_row);
10818 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10819
10820 let replace =
10821 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10822 " "
10823 } else {
10824 ""
10825 };
10826
10827 this.buffer.update(cx, |buffer, cx| {
10828 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10829 });
10830 }
10831 }
10832
10833 this.change_selections(Default::default(), window, cx, |s| {
10834 s.select_anchor_ranges(cursor_positions)
10835 });
10836 });
10837 }
10838
10839 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10840 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10841 self.join_lines_impl(true, window, cx);
10842 }
10843
10844 pub fn sort_lines_case_sensitive(
10845 &mut self,
10846 _: &SortLinesCaseSensitive,
10847 window: &mut Window,
10848 cx: &mut Context<Self>,
10849 ) {
10850 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10851 }
10852
10853 pub fn sort_lines_by_length(
10854 &mut self,
10855 _: &SortLinesByLength,
10856 window: &mut Window,
10857 cx: &mut Context<Self>,
10858 ) {
10859 self.manipulate_immutable_lines(window, cx, |lines| {
10860 lines.sort_by_key(|&line| line.chars().count())
10861 })
10862 }
10863
10864 pub fn sort_lines_case_insensitive(
10865 &mut self,
10866 _: &SortLinesCaseInsensitive,
10867 window: &mut Window,
10868 cx: &mut Context<Self>,
10869 ) {
10870 self.manipulate_immutable_lines(window, cx, |lines| {
10871 lines.sort_by_key(|line| line.to_lowercase())
10872 })
10873 }
10874
10875 pub fn unique_lines_case_insensitive(
10876 &mut self,
10877 _: &UniqueLinesCaseInsensitive,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 ) {
10881 self.manipulate_immutable_lines(window, cx, |lines| {
10882 let mut seen = HashSet::default();
10883 lines.retain(|line| seen.insert(line.to_lowercase()));
10884 })
10885 }
10886
10887 pub fn unique_lines_case_sensitive(
10888 &mut self,
10889 _: &UniqueLinesCaseSensitive,
10890 window: &mut Window,
10891 cx: &mut Context<Self>,
10892 ) {
10893 self.manipulate_immutable_lines(window, cx, |lines| {
10894 let mut seen = HashSet::default();
10895 lines.retain(|line| seen.insert(*line));
10896 })
10897 }
10898
10899 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10900 let snapshot = self.buffer.read(cx).snapshot(cx);
10901 for selection in self.selections.disjoint_anchors_arc().iter() {
10902 if snapshot
10903 .language_at(selection.start)
10904 .and_then(|lang| lang.config().wrap_characters.as_ref())
10905 .is_some()
10906 {
10907 return true;
10908 }
10909 }
10910 false
10911 }
10912
10913 fn wrap_selections_in_tag(
10914 &mut self,
10915 _: &WrapSelectionsInTag,
10916 window: &mut Window,
10917 cx: &mut Context<Self>,
10918 ) {
10919 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10920
10921 let snapshot = self.buffer.read(cx).snapshot(cx);
10922
10923 let mut edits = Vec::new();
10924 let mut boundaries = Vec::new();
10925
10926 for selection in self
10927 .selections
10928 .all_adjusted(&self.display_snapshot(cx))
10929 .iter()
10930 {
10931 let Some(wrap_config) = snapshot
10932 .language_at(selection.start)
10933 .and_then(|lang| lang.config().wrap_characters.clone())
10934 else {
10935 continue;
10936 };
10937
10938 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10939 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10940
10941 let start_before = snapshot.anchor_before(selection.start);
10942 let end_after = snapshot.anchor_after(selection.end);
10943
10944 edits.push((start_before..start_before, open_tag));
10945 edits.push((end_after..end_after, close_tag));
10946
10947 boundaries.push((
10948 start_before,
10949 end_after,
10950 wrap_config.start_prefix.len(),
10951 wrap_config.end_suffix.len(),
10952 ));
10953 }
10954
10955 if edits.is_empty() {
10956 return;
10957 }
10958
10959 self.transact(window, cx, |this, window, cx| {
10960 let buffer = this.buffer.update(cx, |buffer, cx| {
10961 buffer.edit(edits, None, cx);
10962 buffer.snapshot(cx)
10963 });
10964
10965 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10966 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10967 boundaries.into_iter()
10968 {
10969 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10970 let close_offset = end_after
10971 .to_offset(&buffer)
10972 .saturating_sub_usize(end_suffix_len);
10973 new_selections.push(open_offset..open_offset);
10974 new_selections.push(close_offset..close_offset);
10975 }
10976
10977 this.change_selections(Default::default(), window, cx, |s| {
10978 s.select_ranges(new_selections);
10979 });
10980
10981 this.request_autoscroll(Autoscroll::fit(), cx);
10982 });
10983 }
10984
10985 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10986 let Some(project) = self.project.clone() else {
10987 return;
10988 };
10989 self.reload(project, window, cx)
10990 .detach_and_notify_err(window, cx);
10991 }
10992
10993 pub fn restore_file(
10994 &mut self,
10995 _: &::git::RestoreFile,
10996 window: &mut Window,
10997 cx: &mut Context<Self>,
10998 ) {
10999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11000 let mut buffer_ids = HashSet::default();
11001 let snapshot = self.buffer().read(cx).snapshot(cx);
11002 for selection in self
11003 .selections
11004 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11005 {
11006 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11007 }
11008
11009 let buffer = self.buffer().read(cx);
11010 let ranges = buffer_ids
11011 .into_iter()
11012 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11013 .collect::<Vec<_>>();
11014
11015 self.restore_hunks_in_ranges(ranges, window, cx);
11016 }
11017
11018 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11019 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11020 let selections = self
11021 .selections
11022 .all(&self.display_snapshot(cx))
11023 .into_iter()
11024 .map(|s| s.range())
11025 .collect();
11026 self.restore_hunks_in_ranges(selections, window, cx);
11027 }
11028
11029 pub fn restore_hunks_in_ranges(
11030 &mut self,
11031 ranges: Vec<Range<Point>>,
11032 window: &mut Window,
11033 cx: &mut Context<Editor>,
11034 ) {
11035 let mut revert_changes = HashMap::default();
11036 let chunk_by = self
11037 .snapshot(window, cx)
11038 .hunks_for_ranges(ranges)
11039 .into_iter()
11040 .chunk_by(|hunk| hunk.buffer_id);
11041 for (buffer_id, hunks) in &chunk_by {
11042 let hunks = hunks.collect::<Vec<_>>();
11043 for hunk in &hunks {
11044 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11045 }
11046 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11047 }
11048 drop(chunk_by);
11049 if !revert_changes.is_empty() {
11050 self.transact(window, cx, |editor, window, cx| {
11051 editor.restore(revert_changes, window, cx);
11052 });
11053 }
11054 }
11055
11056 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11057 if let Some(status) = self
11058 .addons
11059 .iter()
11060 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11061 {
11062 return Some(status);
11063 }
11064 self.project
11065 .as_ref()?
11066 .read(cx)
11067 .status_for_buffer_id(buffer_id, cx)
11068 }
11069
11070 pub fn open_active_item_in_terminal(
11071 &mut self,
11072 _: &OpenInTerminal,
11073 window: &mut Window,
11074 cx: &mut Context<Self>,
11075 ) {
11076 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11077 let project_path = buffer.read(cx).project_path(cx)?;
11078 let project = self.project()?.read(cx);
11079 let entry = project.entry_for_path(&project_path, cx)?;
11080 let parent = match &entry.canonical_path {
11081 Some(canonical_path) => canonical_path.to_path_buf(),
11082 None => project.absolute_path(&project_path, cx)?,
11083 }
11084 .parent()?
11085 .to_path_buf();
11086 Some(parent)
11087 }) {
11088 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11089 }
11090 }
11091
11092 fn set_breakpoint_context_menu(
11093 &mut self,
11094 display_row: DisplayRow,
11095 position: Option<Anchor>,
11096 clicked_point: gpui::Point<Pixels>,
11097 window: &mut Window,
11098 cx: &mut Context<Self>,
11099 ) {
11100 let source = self
11101 .buffer
11102 .read(cx)
11103 .snapshot(cx)
11104 .anchor_before(Point::new(display_row.0, 0u32));
11105
11106 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11107
11108 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11109 self,
11110 source,
11111 clicked_point,
11112 context_menu,
11113 window,
11114 cx,
11115 );
11116 }
11117
11118 fn add_edit_breakpoint_block(
11119 &mut self,
11120 anchor: Anchor,
11121 breakpoint: &Breakpoint,
11122 edit_action: BreakpointPromptEditAction,
11123 window: &mut Window,
11124 cx: &mut Context<Self>,
11125 ) {
11126 let weak_editor = cx.weak_entity();
11127 let bp_prompt = cx.new(|cx| {
11128 BreakpointPromptEditor::new(
11129 weak_editor,
11130 anchor,
11131 breakpoint.clone(),
11132 edit_action,
11133 window,
11134 cx,
11135 )
11136 });
11137
11138 let height = bp_prompt.update(cx, |this, cx| {
11139 this.prompt
11140 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11141 });
11142 let cloned_prompt = bp_prompt.clone();
11143 let blocks = vec![BlockProperties {
11144 style: BlockStyle::Sticky,
11145 placement: BlockPlacement::Above(anchor),
11146 height: Some(height),
11147 render: Arc::new(move |cx| {
11148 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11149 cloned_prompt.clone().into_any_element()
11150 }),
11151 priority: 0,
11152 }];
11153
11154 let focus_handle = bp_prompt.focus_handle(cx);
11155 window.focus(&focus_handle);
11156
11157 let block_ids = self.insert_blocks(blocks, None, cx);
11158 bp_prompt.update(cx, |prompt, _| {
11159 prompt.add_block_ids(block_ids);
11160 });
11161 }
11162
11163 pub(crate) fn breakpoint_at_row(
11164 &self,
11165 row: u32,
11166 window: &mut Window,
11167 cx: &mut Context<Self>,
11168 ) -> Option<(Anchor, Breakpoint)> {
11169 let snapshot = self.snapshot(window, cx);
11170 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11171
11172 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11173 }
11174
11175 pub(crate) fn breakpoint_at_anchor(
11176 &self,
11177 breakpoint_position: Anchor,
11178 snapshot: &EditorSnapshot,
11179 cx: &mut Context<Self>,
11180 ) -> Option<(Anchor, Breakpoint)> {
11181 let buffer = self
11182 .buffer
11183 .read(cx)
11184 .buffer_for_anchor(breakpoint_position, cx)?;
11185
11186 let enclosing_excerpt = breakpoint_position.excerpt_id;
11187 let buffer_snapshot = buffer.read(cx).snapshot();
11188
11189 let row = buffer_snapshot
11190 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11191 .row;
11192
11193 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11194 let anchor_end = snapshot
11195 .buffer_snapshot()
11196 .anchor_after(Point::new(row, line_len));
11197
11198 self.breakpoint_store
11199 .as_ref()?
11200 .read_with(cx, |breakpoint_store, cx| {
11201 breakpoint_store
11202 .breakpoints(
11203 &buffer,
11204 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11205 &buffer_snapshot,
11206 cx,
11207 )
11208 .next()
11209 .and_then(|(bp, _)| {
11210 let breakpoint_row = buffer_snapshot
11211 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11212 .row;
11213
11214 if breakpoint_row == row {
11215 snapshot
11216 .buffer_snapshot()
11217 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11218 .map(|position| (position, bp.bp.clone()))
11219 } else {
11220 None
11221 }
11222 })
11223 })
11224 }
11225
11226 pub fn edit_log_breakpoint(
11227 &mut self,
11228 _: &EditLogBreakpoint,
11229 window: &mut Window,
11230 cx: &mut Context<Self>,
11231 ) {
11232 if self.breakpoint_store.is_none() {
11233 return;
11234 }
11235
11236 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11237 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11238 message: None,
11239 state: BreakpointState::Enabled,
11240 condition: None,
11241 hit_condition: None,
11242 });
11243
11244 self.add_edit_breakpoint_block(
11245 anchor,
11246 &breakpoint,
11247 BreakpointPromptEditAction::Log,
11248 window,
11249 cx,
11250 );
11251 }
11252 }
11253
11254 fn breakpoints_at_cursors(
11255 &self,
11256 window: &mut Window,
11257 cx: &mut Context<Self>,
11258 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11259 let snapshot = self.snapshot(window, cx);
11260 let cursors = self
11261 .selections
11262 .disjoint_anchors_arc()
11263 .iter()
11264 .map(|selection| {
11265 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11266
11267 let breakpoint_position = self
11268 .breakpoint_at_row(cursor_position.row, window, cx)
11269 .map(|bp| bp.0)
11270 .unwrap_or_else(|| {
11271 snapshot
11272 .display_snapshot
11273 .buffer_snapshot()
11274 .anchor_after(Point::new(cursor_position.row, 0))
11275 });
11276
11277 let breakpoint = self
11278 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11279 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11280
11281 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11282 })
11283 // 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.
11284 .collect::<HashMap<Anchor, _>>();
11285
11286 cursors.into_iter().collect()
11287 }
11288
11289 pub fn enable_breakpoint(
11290 &mut self,
11291 _: &crate::actions::EnableBreakpoint,
11292 window: &mut Window,
11293 cx: &mut Context<Self>,
11294 ) {
11295 if self.breakpoint_store.is_none() {
11296 return;
11297 }
11298
11299 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11300 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11301 continue;
11302 };
11303 self.edit_breakpoint_at_anchor(
11304 anchor,
11305 breakpoint,
11306 BreakpointEditAction::InvertState,
11307 cx,
11308 );
11309 }
11310 }
11311
11312 pub fn disable_breakpoint(
11313 &mut self,
11314 _: &crate::actions::DisableBreakpoint,
11315 window: &mut Window,
11316 cx: &mut Context<Self>,
11317 ) {
11318 if self.breakpoint_store.is_none() {
11319 return;
11320 }
11321
11322 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11323 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11324 continue;
11325 };
11326 self.edit_breakpoint_at_anchor(
11327 anchor,
11328 breakpoint,
11329 BreakpointEditAction::InvertState,
11330 cx,
11331 );
11332 }
11333 }
11334
11335 pub fn toggle_breakpoint(
11336 &mut self,
11337 _: &crate::actions::ToggleBreakpoint,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 ) {
11341 if self.breakpoint_store.is_none() {
11342 return;
11343 }
11344
11345 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11346 if let Some(breakpoint) = breakpoint {
11347 self.edit_breakpoint_at_anchor(
11348 anchor,
11349 breakpoint,
11350 BreakpointEditAction::Toggle,
11351 cx,
11352 );
11353 } else {
11354 self.edit_breakpoint_at_anchor(
11355 anchor,
11356 Breakpoint::new_standard(),
11357 BreakpointEditAction::Toggle,
11358 cx,
11359 );
11360 }
11361 }
11362 }
11363
11364 pub fn edit_breakpoint_at_anchor(
11365 &mut self,
11366 breakpoint_position: Anchor,
11367 breakpoint: Breakpoint,
11368 edit_action: BreakpointEditAction,
11369 cx: &mut Context<Self>,
11370 ) {
11371 let Some(breakpoint_store) = &self.breakpoint_store else {
11372 return;
11373 };
11374
11375 let Some(buffer) = self
11376 .buffer
11377 .read(cx)
11378 .buffer_for_anchor(breakpoint_position, cx)
11379 else {
11380 return;
11381 };
11382
11383 breakpoint_store.update(cx, |breakpoint_store, cx| {
11384 breakpoint_store.toggle_breakpoint(
11385 buffer,
11386 BreakpointWithPosition {
11387 position: breakpoint_position.text_anchor,
11388 bp: breakpoint,
11389 },
11390 edit_action,
11391 cx,
11392 );
11393 });
11394
11395 cx.notify();
11396 }
11397
11398 #[cfg(any(test, feature = "test-support"))]
11399 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11400 self.breakpoint_store.clone()
11401 }
11402
11403 pub fn prepare_restore_change(
11404 &self,
11405 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11406 hunk: &MultiBufferDiffHunk,
11407 cx: &mut App,
11408 ) -> Option<()> {
11409 if hunk.is_created_file() {
11410 return None;
11411 }
11412 let buffer = self.buffer.read(cx);
11413 let diff = buffer.diff_for(hunk.buffer_id)?;
11414 let buffer = buffer.buffer(hunk.buffer_id)?;
11415 let buffer = buffer.read(cx);
11416 let original_text = diff
11417 .read(cx)
11418 .base_text()
11419 .as_rope()
11420 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11421 let buffer_snapshot = buffer.snapshot();
11422 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11423 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11424 probe
11425 .0
11426 .start
11427 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11428 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11429 }) {
11430 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11431 Some(())
11432 } else {
11433 None
11434 }
11435 }
11436
11437 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11438 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11439 }
11440
11441 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11442 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11443 }
11444
11445 fn manipulate_lines<M>(
11446 &mut self,
11447 window: &mut Window,
11448 cx: &mut Context<Self>,
11449 mut manipulate: M,
11450 ) where
11451 M: FnMut(&str) -> LineManipulationResult,
11452 {
11453 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11454
11455 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11456 let buffer = self.buffer.read(cx).snapshot(cx);
11457
11458 let mut edits = Vec::new();
11459
11460 let selections = self.selections.all::<Point>(&display_map);
11461 let mut selections = selections.iter().peekable();
11462 let mut contiguous_row_selections = Vec::new();
11463 let mut new_selections = Vec::new();
11464 let mut added_lines = 0;
11465 let mut removed_lines = 0;
11466
11467 while let Some(selection) = selections.next() {
11468 let (start_row, end_row) = consume_contiguous_rows(
11469 &mut contiguous_row_selections,
11470 selection,
11471 &display_map,
11472 &mut selections,
11473 );
11474
11475 let start_point = Point::new(start_row.0, 0);
11476 let end_point = Point::new(
11477 end_row.previous_row().0,
11478 buffer.line_len(end_row.previous_row()),
11479 );
11480 let text = buffer
11481 .text_for_range(start_point..end_point)
11482 .collect::<String>();
11483
11484 let LineManipulationResult {
11485 new_text,
11486 line_count_before,
11487 line_count_after,
11488 } = manipulate(&text);
11489
11490 edits.push((start_point..end_point, new_text));
11491
11492 // Selections must change based on added and removed line count
11493 let start_row =
11494 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11495 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11496 new_selections.push(Selection {
11497 id: selection.id,
11498 start: start_row,
11499 end: end_row,
11500 goal: SelectionGoal::None,
11501 reversed: selection.reversed,
11502 });
11503
11504 if line_count_after > line_count_before {
11505 added_lines += line_count_after - line_count_before;
11506 } else if line_count_before > line_count_after {
11507 removed_lines += line_count_before - line_count_after;
11508 }
11509 }
11510
11511 self.transact(window, cx, |this, window, cx| {
11512 let buffer = this.buffer.update(cx, |buffer, cx| {
11513 buffer.edit(edits, None, cx);
11514 buffer.snapshot(cx)
11515 });
11516
11517 // Recalculate offsets on newly edited buffer
11518 let new_selections = new_selections
11519 .iter()
11520 .map(|s| {
11521 let start_point = Point::new(s.start.0, 0);
11522 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11523 Selection {
11524 id: s.id,
11525 start: buffer.point_to_offset(start_point),
11526 end: buffer.point_to_offset(end_point),
11527 goal: s.goal,
11528 reversed: s.reversed,
11529 }
11530 })
11531 .collect();
11532
11533 this.change_selections(Default::default(), window, cx, |s| {
11534 s.select(new_selections);
11535 });
11536
11537 this.request_autoscroll(Autoscroll::fit(), cx);
11538 });
11539 }
11540
11541 fn manipulate_immutable_lines<Fn>(
11542 &mut self,
11543 window: &mut Window,
11544 cx: &mut Context<Self>,
11545 mut callback: Fn,
11546 ) where
11547 Fn: FnMut(&mut Vec<&str>),
11548 {
11549 self.manipulate_lines(window, cx, |text| {
11550 let mut lines: Vec<&str> = text.split('\n').collect();
11551 let line_count_before = lines.len();
11552
11553 callback(&mut lines);
11554
11555 LineManipulationResult {
11556 new_text: lines.join("\n"),
11557 line_count_before,
11558 line_count_after: lines.len(),
11559 }
11560 });
11561 }
11562
11563 fn manipulate_mutable_lines<Fn>(
11564 &mut self,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 mut callback: Fn,
11568 ) where
11569 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11570 {
11571 self.manipulate_lines(window, cx, |text| {
11572 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11573 let line_count_before = lines.len();
11574
11575 callback(&mut lines);
11576
11577 LineManipulationResult {
11578 new_text: lines.join("\n"),
11579 line_count_before,
11580 line_count_after: lines.len(),
11581 }
11582 });
11583 }
11584
11585 pub fn convert_indentation_to_spaces(
11586 &mut self,
11587 _: &ConvertIndentationToSpaces,
11588 window: &mut Window,
11589 cx: &mut Context<Self>,
11590 ) {
11591 let settings = self.buffer.read(cx).language_settings(cx);
11592 let tab_size = settings.tab_size.get() as usize;
11593
11594 self.manipulate_mutable_lines(window, cx, |lines| {
11595 // Allocates a reasonably sized scratch buffer once for the whole loop
11596 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11597 // Avoids recomputing spaces that could be inserted many times
11598 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11599 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11600 .collect();
11601
11602 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11603 let mut chars = line.as_ref().chars();
11604 let mut col = 0;
11605 let mut changed = false;
11606
11607 for ch in chars.by_ref() {
11608 match ch {
11609 ' ' => {
11610 reindented_line.push(' ');
11611 col += 1;
11612 }
11613 '\t' => {
11614 // \t are converted to spaces depending on the current column
11615 let spaces_len = tab_size - (col % tab_size);
11616 reindented_line.extend(&space_cache[spaces_len - 1]);
11617 col += spaces_len;
11618 changed = true;
11619 }
11620 _ => {
11621 // If we dont append before break, the character is consumed
11622 reindented_line.push(ch);
11623 break;
11624 }
11625 }
11626 }
11627
11628 if !changed {
11629 reindented_line.clear();
11630 continue;
11631 }
11632 // Append the rest of the line and replace old reference with new one
11633 reindented_line.extend(chars);
11634 *line = Cow::Owned(reindented_line.clone());
11635 reindented_line.clear();
11636 }
11637 });
11638 }
11639
11640 pub fn convert_indentation_to_tabs(
11641 &mut self,
11642 _: &ConvertIndentationToTabs,
11643 window: &mut Window,
11644 cx: &mut Context<Self>,
11645 ) {
11646 let settings = self.buffer.read(cx).language_settings(cx);
11647 let tab_size = settings.tab_size.get() as usize;
11648
11649 self.manipulate_mutable_lines(window, cx, |lines| {
11650 // Allocates a reasonably sized buffer once for the whole loop
11651 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11652 // Avoids recomputing spaces that could be inserted many times
11653 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11654 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11655 .collect();
11656
11657 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11658 let mut chars = line.chars();
11659 let mut spaces_count = 0;
11660 let mut first_non_indent_char = None;
11661 let mut changed = false;
11662
11663 for ch in chars.by_ref() {
11664 match ch {
11665 ' ' => {
11666 // Keep track of spaces. Append \t when we reach tab_size
11667 spaces_count += 1;
11668 changed = true;
11669 if spaces_count == tab_size {
11670 reindented_line.push('\t');
11671 spaces_count = 0;
11672 }
11673 }
11674 '\t' => {
11675 reindented_line.push('\t');
11676 spaces_count = 0;
11677 }
11678 _ => {
11679 // Dont append it yet, we might have remaining spaces
11680 first_non_indent_char = Some(ch);
11681 break;
11682 }
11683 }
11684 }
11685
11686 if !changed {
11687 reindented_line.clear();
11688 continue;
11689 }
11690 // Remaining spaces that didn't make a full tab stop
11691 if spaces_count > 0 {
11692 reindented_line.extend(&space_cache[spaces_count - 1]);
11693 }
11694 // If we consume an extra character that was not indentation, add it back
11695 if let Some(extra_char) = first_non_indent_char {
11696 reindented_line.push(extra_char);
11697 }
11698 // Append the rest of the line and replace old reference with new one
11699 reindented_line.extend(chars);
11700 *line = Cow::Owned(reindented_line.clone());
11701 reindented_line.clear();
11702 }
11703 });
11704 }
11705
11706 pub fn convert_to_upper_case(
11707 &mut self,
11708 _: &ConvertToUpperCase,
11709 window: &mut Window,
11710 cx: &mut Context<Self>,
11711 ) {
11712 self.manipulate_text(window, cx, |text| text.to_uppercase())
11713 }
11714
11715 pub fn convert_to_lower_case(
11716 &mut self,
11717 _: &ConvertToLowerCase,
11718 window: &mut Window,
11719 cx: &mut Context<Self>,
11720 ) {
11721 self.manipulate_text(window, cx, |text| text.to_lowercase())
11722 }
11723
11724 pub fn convert_to_title_case(
11725 &mut self,
11726 _: &ConvertToTitleCase,
11727 window: &mut Window,
11728 cx: &mut Context<Self>,
11729 ) {
11730 self.manipulate_text(window, cx, |text| {
11731 text.split('\n')
11732 .map(|line| line.to_case(Case::Title))
11733 .join("\n")
11734 })
11735 }
11736
11737 pub fn convert_to_snake_case(
11738 &mut self,
11739 _: &ConvertToSnakeCase,
11740 window: &mut Window,
11741 cx: &mut Context<Self>,
11742 ) {
11743 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11744 }
11745
11746 pub fn convert_to_kebab_case(
11747 &mut self,
11748 _: &ConvertToKebabCase,
11749 window: &mut Window,
11750 cx: &mut Context<Self>,
11751 ) {
11752 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11753 }
11754
11755 pub fn convert_to_upper_camel_case(
11756 &mut self,
11757 _: &ConvertToUpperCamelCase,
11758 window: &mut Window,
11759 cx: &mut Context<Self>,
11760 ) {
11761 self.manipulate_text(window, cx, |text| {
11762 text.split('\n')
11763 .map(|line| line.to_case(Case::UpperCamel))
11764 .join("\n")
11765 })
11766 }
11767
11768 pub fn convert_to_lower_camel_case(
11769 &mut self,
11770 _: &ConvertToLowerCamelCase,
11771 window: &mut Window,
11772 cx: &mut Context<Self>,
11773 ) {
11774 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11775 }
11776
11777 pub fn convert_to_opposite_case(
11778 &mut self,
11779 _: &ConvertToOppositeCase,
11780 window: &mut Window,
11781 cx: &mut Context<Self>,
11782 ) {
11783 self.manipulate_text(window, cx, |text| {
11784 text.chars()
11785 .fold(String::with_capacity(text.len()), |mut t, c| {
11786 if c.is_uppercase() {
11787 t.extend(c.to_lowercase());
11788 } else {
11789 t.extend(c.to_uppercase());
11790 }
11791 t
11792 })
11793 })
11794 }
11795
11796 pub fn convert_to_sentence_case(
11797 &mut self,
11798 _: &ConvertToSentenceCase,
11799 window: &mut Window,
11800 cx: &mut Context<Self>,
11801 ) {
11802 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11803 }
11804
11805 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11806 self.manipulate_text(window, cx, |text| {
11807 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11808 if has_upper_case_characters {
11809 text.to_lowercase()
11810 } else {
11811 text.to_uppercase()
11812 }
11813 })
11814 }
11815
11816 pub fn convert_to_rot13(
11817 &mut self,
11818 _: &ConvertToRot13,
11819 window: &mut Window,
11820 cx: &mut Context<Self>,
11821 ) {
11822 self.manipulate_text(window, cx, |text| {
11823 text.chars()
11824 .map(|c| match c {
11825 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11826 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11827 _ => c,
11828 })
11829 .collect()
11830 })
11831 }
11832
11833 pub fn convert_to_rot47(
11834 &mut self,
11835 _: &ConvertToRot47,
11836 window: &mut Window,
11837 cx: &mut Context<Self>,
11838 ) {
11839 self.manipulate_text(window, cx, |text| {
11840 text.chars()
11841 .map(|c| {
11842 let code_point = c as u32;
11843 if code_point >= 33 && code_point <= 126 {
11844 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11845 }
11846 c
11847 })
11848 .collect()
11849 })
11850 }
11851
11852 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11853 where
11854 Fn: FnMut(&str) -> String,
11855 {
11856 let buffer = self.buffer.read(cx).snapshot(cx);
11857
11858 let mut new_selections = Vec::new();
11859 let mut edits = Vec::new();
11860 let mut selection_adjustment = 0isize;
11861
11862 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11863 let selection_is_empty = selection.is_empty();
11864
11865 let (start, end) = if selection_is_empty {
11866 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11867 (word_range.start, word_range.end)
11868 } else {
11869 (
11870 buffer.point_to_offset(selection.start),
11871 buffer.point_to_offset(selection.end),
11872 )
11873 };
11874
11875 let text = buffer.text_for_range(start..end).collect::<String>();
11876 let old_length = text.len() as isize;
11877 let text = callback(&text);
11878
11879 new_selections.push(Selection {
11880 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11881 end: MultiBufferOffset(
11882 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11883 ),
11884 goal: SelectionGoal::None,
11885 id: selection.id,
11886 reversed: selection.reversed,
11887 });
11888
11889 selection_adjustment += old_length - text.len() as isize;
11890
11891 edits.push((start..end, text));
11892 }
11893
11894 self.transact(window, cx, |this, window, cx| {
11895 this.buffer.update(cx, |buffer, cx| {
11896 buffer.edit(edits, None, cx);
11897 });
11898
11899 this.change_selections(Default::default(), window, cx, |s| {
11900 s.select(new_selections);
11901 });
11902
11903 this.request_autoscroll(Autoscroll::fit(), cx);
11904 });
11905 }
11906
11907 pub fn move_selection_on_drop(
11908 &mut self,
11909 selection: &Selection<Anchor>,
11910 target: DisplayPoint,
11911 is_cut: bool,
11912 window: &mut Window,
11913 cx: &mut Context<Self>,
11914 ) {
11915 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11916 let buffer = display_map.buffer_snapshot();
11917 let mut edits = Vec::new();
11918 let insert_point = display_map
11919 .clip_point(target, Bias::Left)
11920 .to_point(&display_map);
11921 let text = buffer
11922 .text_for_range(selection.start..selection.end)
11923 .collect::<String>();
11924 if is_cut {
11925 edits.push(((selection.start..selection.end), String::new()));
11926 }
11927 let insert_anchor = buffer.anchor_before(insert_point);
11928 edits.push(((insert_anchor..insert_anchor), text));
11929 let last_edit_start = insert_anchor.bias_left(buffer);
11930 let last_edit_end = insert_anchor.bias_right(buffer);
11931 self.transact(window, cx, |this, window, cx| {
11932 this.buffer.update(cx, |buffer, cx| {
11933 buffer.edit(edits, None, cx);
11934 });
11935 this.change_selections(Default::default(), window, cx, |s| {
11936 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11937 });
11938 });
11939 }
11940
11941 pub fn clear_selection_drag_state(&mut self) {
11942 self.selection_drag_state = SelectionDragState::None;
11943 }
11944
11945 pub fn duplicate(
11946 &mut self,
11947 upwards: bool,
11948 whole_lines: bool,
11949 window: &mut Window,
11950 cx: &mut Context<Self>,
11951 ) {
11952 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11953
11954 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11955 let buffer = display_map.buffer_snapshot();
11956 let selections = self.selections.all::<Point>(&display_map);
11957
11958 let mut edits = Vec::new();
11959 let mut selections_iter = selections.iter().peekable();
11960 while let Some(selection) = selections_iter.next() {
11961 let mut rows = selection.spanned_rows(false, &display_map);
11962 // duplicate line-wise
11963 if whole_lines || selection.start == selection.end {
11964 // Avoid duplicating the same lines twice.
11965 while let Some(next_selection) = selections_iter.peek() {
11966 let next_rows = next_selection.spanned_rows(false, &display_map);
11967 if next_rows.start < rows.end {
11968 rows.end = next_rows.end;
11969 selections_iter.next().unwrap();
11970 } else {
11971 break;
11972 }
11973 }
11974
11975 // Copy the text from the selected row region and splice it either at the start
11976 // or end of the region.
11977 let start = Point::new(rows.start.0, 0);
11978 let end = Point::new(
11979 rows.end.previous_row().0,
11980 buffer.line_len(rows.end.previous_row()),
11981 );
11982
11983 let mut text = buffer.text_for_range(start..end).collect::<String>();
11984
11985 let insert_location = if upwards {
11986 // When duplicating upward, we need to insert before the current line.
11987 // If we're on the last line and it doesn't end with a newline,
11988 // we need to add a newline before the duplicated content.
11989 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11990 && buffer.max_point().column > 0
11991 && !text.ends_with('\n');
11992
11993 if needs_leading_newline {
11994 text.insert(0, '\n');
11995 end
11996 } else {
11997 text.push('\n');
11998 Point::new(rows.start.0, 0)
11999 }
12000 } else {
12001 text.push('\n');
12002 start
12003 };
12004 edits.push((insert_location..insert_location, text));
12005 } else {
12006 // duplicate character-wise
12007 let start = selection.start;
12008 let end = selection.end;
12009 let text = buffer.text_for_range(start..end).collect::<String>();
12010 edits.push((selection.end..selection.end, text));
12011 }
12012 }
12013
12014 self.transact(window, cx, |this, window, cx| {
12015 this.buffer.update(cx, |buffer, cx| {
12016 buffer.edit(edits, None, cx);
12017 });
12018
12019 // When duplicating upward with whole lines, move the cursor to the duplicated line
12020 if upwards && whole_lines {
12021 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12022
12023 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12024 let mut new_ranges = Vec::new();
12025 let selections = s.all::<Point>(&display_map);
12026 let mut selections_iter = selections.iter().peekable();
12027
12028 while let Some(first_selection) = selections_iter.next() {
12029 // Group contiguous selections together to find the total row span
12030 let mut group_selections = vec![first_selection];
12031 let mut rows = first_selection.spanned_rows(false, &display_map);
12032
12033 while let Some(next_selection) = selections_iter.peek() {
12034 let next_rows = next_selection.spanned_rows(false, &display_map);
12035 if next_rows.start < rows.end {
12036 rows.end = next_rows.end;
12037 group_selections.push(selections_iter.next().unwrap());
12038 } else {
12039 break;
12040 }
12041 }
12042
12043 let row_count = rows.end.0 - rows.start.0;
12044
12045 // Move all selections in this group up by the total number of duplicated rows
12046 for selection in group_selections {
12047 let new_start = Point::new(
12048 selection.start.row.saturating_sub(row_count),
12049 selection.start.column,
12050 );
12051
12052 let new_end = Point::new(
12053 selection.end.row.saturating_sub(row_count),
12054 selection.end.column,
12055 );
12056
12057 new_ranges.push(new_start..new_end);
12058 }
12059 }
12060
12061 s.select_ranges(new_ranges);
12062 });
12063 }
12064
12065 this.request_autoscroll(Autoscroll::fit(), cx);
12066 });
12067 }
12068
12069 pub fn duplicate_line_up(
12070 &mut self,
12071 _: &DuplicateLineUp,
12072 window: &mut Window,
12073 cx: &mut Context<Self>,
12074 ) {
12075 self.duplicate(true, true, window, cx);
12076 }
12077
12078 pub fn duplicate_line_down(
12079 &mut self,
12080 _: &DuplicateLineDown,
12081 window: &mut Window,
12082 cx: &mut Context<Self>,
12083 ) {
12084 self.duplicate(false, true, window, cx);
12085 }
12086
12087 pub fn duplicate_selection(
12088 &mut self,
12089 _: &DuplicateSelection,
12090 window: &mut Window,
12091 cx: &mut Context<Self>,
12092 ) {
12093 self.duplicate(false, false, window, cx);
12094 }
12095
12096 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12097 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12098 if self.mode.is_single_line() {
12099 cx.propagate();
12100 return;
12101 }
12102
12103 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12104 let buffer = self.buffer.read(cx).snapshot(cx);
12105
12106 let mut edits = Vec::new();
12107 let mut unfold_ranges = Vec::new();
12108 let mut refold_creases = Vec::new();
12109
12110 let selections = self.selections.all::<Point>(&display_map);
12111 let mut selections = selections.iter().peekable();
12112 let mut contiguous_row_selections = Vec::new();
12113 let mut new_selections = Vec::new();
12114
12115 while let Some(selection) = selections.next() {
12116 // Find all the selections that span a contiguous row range
12117 let (start_row, end_row) = consume_contiguous_rows(
12118 &mut contiguous_row_selections,
12119 selection,
12120 &display_map,
12121 &mut selections,
12122 );
12123
12124 // Move the text spanned by the row range to be before the line preceding the row range
12125 if start_row.0 > 0 {
12126 let range_to_move = Point::new(
12127 start_row.previous_row().0,
12128 buffer.line_len(start_row.previous_row()),
12129 )
12130 ..Point::new(
12131 end_row.previous_row().0,
12132 buffer.line_len(end_row.previous_row()),
12133 );
12134 let insertion_point = display_map
12135 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12136 .0;
12137
12138 // Don't move lines across excerpts
12139 if buffer
12140 .excerpt_containing(insertion_point..range_to_move.end)
12141 .is_some()
12142 {
12143 let text = buffer
12144 .text_for_range(range_to_move.clone())
12145 .flat_map(|s| s.chars())
12146 .skip(1)
12147 .chain(['\n'])
12148 .collect::<String>();
12149
12150 edits.push((
12151 buffer.anchor_after(range_to_move.start)
12152 ..buffer.anchor_before(range_to_move.end),
12153 String::new(),
12154 ));
12155 let insertion_anchor = buffer.anchor_after(insertion_point);
12156 edits.push((insertion_anchor..insertion_anchor, text));
12157
12158 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12159
12160 // Move selections up
12161 new_selections.extend(contiguous_row_selections.drain(..).map(
12162 |mut selection| {
12163 selection.start.row -= row_delta;
12164 selection.end.row -= row_delta;
12165 selection
12166 },
12167 ));
12168
12169 // Move folds up
12170 unfold_ranges.push(range_to_move.clone());
12171 for fold in display_map.folds_in_range(
12172 buffer.anchor_before(range_to_move.start)
12173 ..buffer.anchor_after(range_to_move.end),
12174 ) {
12175 let mut start = fold.range.start.to_point(&buffer);
12176 let mut end = fold.range.end.to_point(&buffer);
12177 start.row -= row_delta;
12178 end.row -= row_delta;
12179 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12180 }
12181 }
12182 }
12183
12184 // If we didn't move line(s), preserve the existing selections
12185 new_selections.append(&mut contiguous_row_selections);
12186 }
12187
12188 self.transact(window, cx, |this, window, cx| {
12189 this.unfold_ranges(&unfold_ranges, true, true, cx);
12190 this.buffer.update(cx, |buffer, cx| {
12191 for (range, text) in edits {
12192 buffer.edit([(range, text)], None, cx);
12193 }
12194 });
12195 this.fold_creases(refold_creases, true, window, cx);
12196 this.change_selections(Default::default(), window, cx, |s| {
12197 s.select(new_selections);
12198 })
12199 });
12200 }
12201
12202 pub fn move_line_down(
12203 &mut self,
12204 _: &MoveLineDown,
12205 window: &mut Window,
12206 cx: &mut Context<Self>,
12207 ) {
12208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12209 if self.mode.is_single_line() {
12210 cx.propagate();
12211 return;
12212 }
12213
12214 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12215 let buffer = self.buffer.read(cx).snapshot(cx);
12216
12217 let mut edits = Vec::new();
12218 let mut unfold_ranges = Vec::new();
12219 let mut refold_creases = Vec::new();
12220
12221 let selections = self.selections.all::<Point>(&display_map);
12222 let mut selections = selections.iter().peekable();
12223 let mut contiguous_row_selections = Vec::new();
12224 let mut new_selections = Vec::new();
12225
12226 while let Some(selection) = selections.next() {
12227 // Find all the selections that span a contiguous row range
12228 let (start_row, end_row) = consume_contiguous_rows(
12229 &mut contiguous_row_selections,
12230 selection,
12231 &display_map,
12232 &mut selections,
12233 );
12234
12235 // Move the text spanned by the row range to be after the last line of the row range
12236 if end_row.0 <= buffer.max_point().row {
12237 let range_to_move =
12238 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12239 let insertion_point = display_map
12240 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12241 .0;
12242
12243 // Don't move lines across excerpt boundaries
12244 if buffer
12245 .excerpt_containing(range_to_move.start..insertion_point)
12246 .is_some()
12247 {
12248 let mut text = String::from("\n");
12249 text.extend(buffer.text_for_range(range_to_move.clone()));
12250 text.pop(); // Drop trailing newline
12251 edits.push((
12252 buffer.anchor_after(range_to_move.start)
12253 ..buffer.anchor_before(range_to_move.end),
12254 String::new(),
12255 ));
12256 let insertion_anchor = buffer.anchor_after(insertion_point);
12257 edits.push((insertion_anchor..insertion_anchor, text));
12258
12259 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12260
12261 // Move selections down
12262 new_selections.extend(contiguous_row_selections.drain(..).map(
12263 |mut selection| {
12264 selection.start.row += row_delta;
12265 selection.end.row += row_delta;
12266 selection
12267 },
12268 ));
12269
12270 // Move folds down
12271 unfold_ranges.push(range_to_move.clone());
12272 for fold in display_map.folds_in_range(
12273 buffer.anchor_before(range_to_move.start)
12274 ..buffer.anchor_after(range_to_move.end),
12275 ) {
12276 let mut start = fold.range.start.to_point(&buffer);
12277 let mut end = fold.range.end.to_point(&buffer);
12278 start.row += row_delta;
12279 end.row += row_delta;
12280 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12281 }
12282 }
12283 }
12284
12285 // If we didn't move line(s), preserve the existing selections
12286 new_selections.append(&mut contiguous_row_selections);
12287 }
12288
12289 self.transact(window, cx, |this, window, cx| {
12290 this.unfold_ranges(&unfold_ranges, true, true, cx);
12291 this.buffer.update(cx, |buffer, cx| {
12292 for (range, text) in edits {
12293 buffer.edit([(range, text)], None, cx);
12294 }
12295 });
12296 this.fold_creases(refold_creases, true, window, cx);
12297 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12298 });
12299 }
12300
12301 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12302 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12303 let text_layout_details = &self.text_layout_details(window);
12304 self.transact(window, cx, |this, window, cx| {
12305 let edits = this.change_selections(Default::default(), window, cx, |s| {
12306 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12307 s.move_with(|display_map, selection| {
12308 if !selection.is_empty() {
12309 return;
12310 }
12311
12312 let mut head = selection.head();
12313 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12314 if head.column() == display_map.line_len(head.row()) {
12315 transpose_offset = display_map
12316 .buffer_snapshot()
12317 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12318 }
12319
12320 if transpose_offset == MultiBufferOffset(0) {
12321 return;
12322 }
12323
12324 *head.column_mut() += 1;
12325 head = display_map.clip_point(head, Bias::Right);
12326 let goal = SelectionGoal::HorizontalPosition(
12327 display_map
12328 .x_for_display_point(head, text_layout_details)
12329 .into(),
12330 );
12331 selection.collapse_to(head, goal);
12332
12333 let transpose_start = display_map
12334 .buffer_snapshot()
12335 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12336 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12337 let transpose_end = display_map
12338 .buffer_snapshot()
12339 .clip_offset(transpose_offset + 1usize, Bias::Right);
12340 if let Some(ch) = display_map
12341 .buffer_snapshot()
12342 .chars_at(transpose_start)
12343 .next()
12344 {
12345 edits.push((transpose_start..transpose_offset, String::new()));
12346 edits.push((transpose_end..transpose_end, ch.to_string()));
12347 }
12348 }
12349 });
12350 edits
12351 });
12352 this.buffer
12353 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12354 let selections = this
12355 .selections
12356 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12357 this.change_selections(Default::default(), window, cx, |s| {
12358 s.select(selections);
12359 });
12360 });
12361 }
12362
12363 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12364 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12365 if self.mode.is_single_line() {
12366 cx.propagate();
12367 return;
12368 }
12369
12370 self.rewrap_impl(RewrapOptions::default(), cx)
12371 }
12372
12373 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12374 let buffer = self.buffer.read(cx).snapshot(cx);
12375 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12376
12377 #[derive(Clone, Debug, PartialEq)]
12378 enum CommentFormat {
12379 /// single line comment, with prefix for line
12380 Line(String),
12381 /// single line within a block comment, with prefix for line
12382 BlockLine(String),
12383 /// a single line of a block comment that includes the initial delimiter
12384 BlockCommentWithStart(BlockCommentConfig),
12385 /// a single line of a block comment that includes the ending delimiter
12386 BlockCommentWithEnd(BlockCommentConfig),
12387 }
12388
12389 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12390 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12391 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12392 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12393 .peekable();
12394
12395 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12396 row
12397 } else {
12398 return Vec::new();
12399 };
12400
12401 let language_settings = buffer.language_settings_at(selection.head(), cx);
12402 let language_scope = buffer.language_scope_at(selection.head());
12403
12404 let indent_and_prefix_for_row =
12405 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12406 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12407 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12408 &language_scope
12409 {
12410 let indent_end = Point::new(row, indent.len);
12411 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12412 let line_text_after_indent = buffer
12413 .text_for_range(indent_end..line_end)
12414 .collect::<String>();
12415
12416 let is_within_comment_override = buffer
12417 .language_scope_at(indent_end)
12418 .is_some_and(|scope| scope.override_name() == Some("comment"));
12419 let comment_delimiters = if is_within_comment_override {
12420 // we are within a comment syntax node, but we don't
12421 // yet know what kind of comment: block, doc or line
12422 match (
12423 language_scope.documentation_comment(),
12424 language_scope.block_comment(),
12425 ) {
12426 (Some(config), _) | (_, Some(config))
12427 if buffer.contains_str_at(indent_end, &config.start) =>
12428 {
12429 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12430 }
12431 (Some(config), _) | (_, Some(config))
12432 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12433 {
12434 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12435 }
12436 (Some(config), _) | (_, Some(config))
12437 if buffer.contains_str_at(indent_end, &config.prefix) =>
12438 {
12439 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12440 }
12441 (_, _) => language_scope
12442 .line_comment_prefixes()
12443 .iter()
12444 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12445 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12446 }
12447 } else {
12448 // we not in an overridden comment node, but we may
12449 // be within a non-overridden line comment node
12450 language_scope
12451 .line_comment_prefixes()
12452 .iter()
12453 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12454 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12455 };
12456
12457 let rewrap_prefix = language_scope
12458 .rewrap_prefixes()
12459 .iter()
12460 .find_map(|prefix_regex| {
12461 prefix_regex.find(&line_text_after_indent).map(|mat| {
12462 if mat.start() == 0 {
12463 Some(mat.as_str().to_string())
12464 } else {
12465 None
12466 }
12467 })
12468 })
12469 .flatten();
12470 (comment_delimiters, rewrap_prefix)
12471 } else {
12472 (None, None)
12473 };
12474 (indent, comment_prefix, rewrap_prefix)
12475 };
12476
12477 let mut ranges = Vec::new();
12478 let from_empty_selection = selection.is_empty();
12479
12480 let mut current_range_start = first_row;
12481 let mut prev_row = first_row;
12482 let (
12483 mut current_range_indent,
12484 mut current_range_comment_delimiters,
12485 mut current_range_rewrap_prefix,
12486 ) = indent_and_prefix_for_row(first_row);
12487
12488 for row in non_blank_rows_iter.skip(1) {
12489 let has_paragraph_break = row > prev_row + 1;
12490
12491 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12492 indent_and_prefix_for_row(row);
12493
12494 let has_indent_change = row_indent != current_range_indent;
12495 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12496
12497 let has_boundary_change = has_comment_change
12498 || row_rewrap_prefix.is_some()
12499 || (has_indent_change && current_range_comment_delimiters.is_some());
12500
12501 if has_paragraph_break || has_boundary_change {
12502 ranges.push((
12503 language_settings.clone(),
12504 Point::new(current_range_start, 0)
12505 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12506 current_range_indent,
12507 current_range_comment_delimiters.clone(),
12508 current_range_rewrap_prefix.clone(),
12509 from_empty_selection,
12510 ));
12511 current_range_start = row;
12512 current_range_indent = row_indent;
12513 current_range_comment_delimiters = row_comment_delimiters;
12514 current_range_rewrap_prefix = row_rewrap_prefix;
12515 }
12516 prev_row = row;
12517 }
12518
12519 ranges.push((
12520 language_settings.clone(),
12521 Point::new(current_range_start, 0)
12522 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12523 current_range_indent,
12524 current_range_comment_delimiters,
12525 current_range_rewrap_prefix,
12526 from_empty_selection,
12527 ));
12528
12529 ranges
12530 });
12531
12532 let mut edits = Vec::new();
12533 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12534
12535 for (
12536 language_settings,
12537 wrap_range,
12538 mut indent_size,
12539 comment_prefix,
12540 rewrap_prefix,
12541 from_empty_selection,
12542 ) in wrap_ranges
12543 {
12544 let mut start_row = wrap_range.start.row;
12545 let mut end_row = wrap_range.end.row;
12546
12547 // Skip selections that overlap with a range that has already been rewrapped.
12548 let selection_range = start_row..end_row;
12549 if rewrapped_row_ranges
12550 .iter()
12551 .any(|range| range.overlaps(&selection_range))
12552 {
12553 continue;
12554 }
12555
12556 let tab_size = language_settings.tab_size;
12557
12558 let (line_prefix, inside_comment) = match &comment_prefix {
12559 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12560 (Some(prefix.as_str()), true)
12561 }
12562 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12563 (Some(prefix.as_ref()), true)
12564 }
12565 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12566 start: _,
12567 end: _,
12568 prefix,
12569 tab_size,
12570 })) => {
12571 indent_size.len += tab_size;
12572 (Some(prefix.as_ref()), true)
12573 }
12574 None => (None, false),
12575 };
12576 let indent_prefix = indent_size.chars().collect::<String>();
12577 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12578
12579 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12580 RewrapBehavior::InComments => inside_comment,
12581 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12582 RewrapBehavior::Anywhere => true,
12583 };
12584
12585 let should_rewrap = options.override_language_settings
12586 || allow_rewrap_based_on_language
12587 || self.hard_wrap.is_some();
12588 if !should_rewrap {
12589 continue;
12590 }
12591
12592 if from_empty_selection {
12593 'expand_upwards: while start_row > 0 {
12594 let prev_row = start_row - 1;
12595 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12596 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12597 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12598 {
12599 start_row = prev_row;
12600 } else {
12601 break 'expand_upwards;
12602 }
12603 }
12604
12605 'expand_downwards: while end_row < buffer.max_point().row {
12606 let next_row = end_row + 1;
12607 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12608 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12609 && !buffer.is_line_blank(MultiBufferRow(next_row))
12610 {
12611 end_row = next_row;
12612 } else {
12613 break 'expand_downwards;
12614 }
12615 }
12616 }
12617
12618 let start = Point::new(start_row, 0);
12619 let start_offset = ToOffset::to_offset(&start, &buffer);
12620 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12621 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12622 let mut first_line_delimiter = None;
12623 let mut last_line_delimiter = None;
12624 let Some(lines_without_prefixes) = selection_text
12625 .lines()
12626 .enumerate()
12627 .map(|(ix, line)| {
12628 let line_trimmed = line.trim_start();
12629 if rewrap_prefix.is_some() && ix > 0 {
12630 Ok(line_trimmed)
12631 } else if let Some(
12632 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12633 start,
12634 prefix,
12635 end,
12636 tab_size,
12637 })
12638 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12639 start,
12640 prefix,
12641 end,
12642 tab_size,
12643 }),
12644 ) = &comment_prefix
12645 {
12646 let line_trimmed = line_trimmed
12647 .strip_prefix(start.as_ref())
12648 .map(|s| {
12649 let mut indent_size = indent_size;
12650 indent_size.len -= tab_size;
12651 let indent_prefix: String = indent_size.chars().collect();
12652 first_line_delimiter = Some((indent_prefix, start));
12653 s.trim_start()
12654 })
12655 .unwrap_or(line_trimmed);
12656 let line_trimmed = line_trimmed
12657 .strip_suffix(end.as_ref())
12658 .map(|s| {
12659 last_line_delimiter = Some(end);
12660 s.trim_end()
12661 })
12662 .unwrap_or(line_trimmed);
12663 let line_trimmed = line_trimmed
12664 .strip_prefix(prefix.as_ref())
12665 .unwrap_or(line_trimmed);
12666 Ok(line_trimmed)
12667 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12668 line_trimmed.strip_prefix(prefix).with_context(|| {
12669 format!("line did not start with prefix {prefix:?}: {line:?}")
12670 })
12671 } else {
12672 line_trimmed
12673 .strip_prefix(&line_prefix.trim_start())
12674 .with_context(|| {
12675 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12676 })
12677 }
12678 })
12679 .collect::<Result<Vec<_>, _>>()
12680 .log_err()
12681 else {
12682 continue;
12683 };
12684
12685 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12686 buffer
12687 .language_settings_at(Point::new(start_row, 0), cx)
12688 .preferred_line_length as usize
12689 });
12690
12691 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12692 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12693 } else {
12694 line_prefix.clone()
12695 };
12696
12697 let wrapped_text = {
12698 let mut wrapped_text = wrap_with_prefix(
12699 line_prefix,
12700 subsequent_lines_prefix,
12701 lines_without_prefixes.join("\n"),
12702 wrap_column,
12703 tab_size,
12704 options.preserve_existing_whitespace,
12705 );
12706
12707 if let Some((indent, delimiter)) = first_line_delimiter {
12708 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12709 }
12710 if let Some(last_line) = last_line_delimiter {
12711 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12712 }
12713
12714 wrapped_text
12715 };
12716
12717 // TODO: should always use char-based diff while still supporting cursor behavior that
12718 // matches vim.
12719 let mut diff_options = DiffOptions::default();
12720 if options.override_language_settings {
12721 diff_options.max_word_diff_len = 0;
12722 diff_options.max_word_diff_line_count = 0;
12723 } else {
12724 diff_options.max_word_diff_len = usize::MAX;
12725 diff_options.max_word_diff_line_count = usize::MAX;
12726 }
12727
12728 for (old_range, new_text) in
12729 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12730 {
12731 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12732 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12733 edits.push((edit_start..edit_end, new_text));
12734 }
12735
12736 rewrapped_row_ranges.push(start_row..=end_row);
12737 }
12738
12739 self.buffer
12740 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12741 }
12742
12743 pub fn cut_common(
12744 &mut self,
12745 cut_no_selection_line: bool,
12746 window: &mut Window,
12747 cx: &mut Context<Self>,
12748 ) -> ClipboardItem {
12749 let mut text = String::new();
12750 let buffer = self.buffer.read(cx).snapshot(cx);
12751 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12752 let mut clipboard_selections = Vec::with_capacity(selections.len());
12753 {
12754 let max_point = buffer.max_point();
12755 let mut is_first = true;
12756 let mut prev_selection_was_entire_line = false;
12757 for selection in &mut selections {
12758 let is_entire_line =
12759 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12760 if is_entire_line {
12761 selection.start = Point::new(selection.start.row, 0);
12762 if !selection.is_empty() && selection.end.column == 0 {
12763 selection.end = cmp::min(max_point, selection.end);
12764 } else {
12765 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12766 }
12767 selection.goal = SelectionGoal::None;
12768 }
12769 if is_first {
12770 is_first = false;
12771 } else if !prev_selection_was_entire_line {
12772 text += "\n";
12773 }
12774 prev_selection_was_entire_line = is_entire_line;
12775 let mut len = 0;
12776 for chunk in buffer.text_for_range(selection.start..selection.end) {
12777 text.push_str(chunk);
12778 len += chunk.len();
12779 }
12780 clipboard_selections.push(ClipboardSelection {
12781 len,
12782 is_entire_line,
12783 first_line_indent: buffer
12784 .indent_size_for_line(MultiBufferRow(selection.start.row))
12785 .len,
12786 });
12787 }
12788 }
12789
12790 self.transact(window, cx, |this, window, cx| {
12791 this.change_selections(Default::default(), window, cx, |s| {
12792 s.select(selections);
12793 });
12794 this.insert("", window, cx);
12795 });
12796 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12797 }
12798
12799 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12800 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12801 let item = self.cut_common(true, window, cx);
12802 cx.write_to_clipboard(item);
12803 }
12804
12805 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12806 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12807 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12808 s.move_with(|snapshot, sel| {
12809 if sel.is_empty() {
12810 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12811 }
12812 if sel.is_empty() {
12813 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12814 }
12815 });
12816 });
12817 let item = self.cut_common(false, window, cx);
12818 cx.set_global(KillRing(item))
12819 }
12820
12821 pub fn kill_ring_yank(
12822 &mut self,
12823 _: &KillRingYank,
12824 window: &mut Window,
12825 cx: &mut Context<Self>,
12826 ) {
12827 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12828 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12829 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12830 (kill_ring.text().to_string(), kill_ring.metadata_json())
12831 } else {
12832 return;
12833 }
12834 } else {
12835 return;
12836 };
12837 self.do_paste(&text, metadata, false, window, cx);
12838 }
12839
12840 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12841 self.do_copy(true, cx);
12842 }
12843
12844 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12845 self.do_copy(false, cx);
12846 }
12847
12848 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12849 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12850 let buffer = self.buffer.read(cx).read(cx);
12851 let mut text = String::new();
12852
12853 let mut clipboard_selections = Vec::with_capacity(selections.len());
12854 {
12855 let max_point = buffer.max_point();
12856 let mut is_first = true;
12857 let mut prev_selection_was_entire_line = false;
12858 for selection in &selections {
12859 let mut start = selection.start;
12860 let mut end = selection.end;
12861 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12862 let mut add_trailing_newline = false;
12863 if is_entire_line {
12864 start = Point::new(start.row, 0);
12865 let next_line_start = Point::new(end.row + 1, 0);
12866 if next_line_start <= max_point {
12867 end = next_line_start;
12868 } else {
12869 // We're on the last line without a trailing newline.
12870 // Copy to the end of the line and add a newline afterwards.
12871 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12872 add_trailing_newline = true;
12873 }
12874 }
12875
12876 let mut trimmed_selections = Vec::new();
12877 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12878 let row = MultiBufferRow(start.row);
12879 let first_indent = buffer.indent_size_for_line(row);
12880 if first_indent.len == 0 || start.column > first_indent.len {
12881 trimmed_selections.push(start..end);
12882 } else {
12883 trimmed_selections.push(
12884 Point::new(row.0, first_indent.len)
12885 ..Point::new(row.0, buffer.line_len(row)),
12886 );
12887 for row in start.row + 1..=end.row {
12888 let mut line_len = buffer.line_len(MultiBufferRow(row));
12889 if row == end.row {
12890 line_len = end.column;
12891 }
12892 if line_len == 0 {
12893 trimmed_selections
12894 .push(Point::new(row, 0)..Point::new(row, line_len));
12895 continue;
12896 }
12897 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12898 if row_indent_size.len >= first_indent.len {
12899 trimmed_selections.push(
12900 Point::new(row, first_indent.len)..Point::new(row, line_len),
12901 );
12902 } else {
12903 trimmed_selections.clear();
12904 trimmed_selections.push(start..end);
12905 break;
12906 }
12907 }
12908 }
12909 } else {
12910 trimmed_selections.push(start..end);
12911 }
12912
12913 for trimmed_range in trimmed_selections {
12914 if is_first {
12915 is_first = false;
12916 } else if !prev_selection_was_entire_line {
12917 text += "\n";
12918 }
12919 prev_selection_was_entire_line = is_entire_line;
12920 let mut len = 0;
12921 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12922 text.push_str(chunk);
12923 len += chunk.len();
12924 }
12925 if add_trailing_newline {
12926 text.push('\n');
12927 len += 1;
12928 }
12929 clipboard_selections.push(ClipboardSelection {
12930 len,
12931 is_entire_line,
12932 first_line_indent: buffer
12933 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12934 .len,
12935 });
12936 }
12937 }
12938 }
12939
12940 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12941 text,
12942 clipboard_selections,
12943 ));
12944 }
12945
12946 pub fn do_paste(
12947 &mut self,
12948 text: &String,
12949 clipboard_selections: Option<Vec<ClipboardSelection>>,
12950 handle_entire_lines: bool,
12951 window: &mut Window,
12952 cx: &mut Context<Self>,
12953 ) {
12954 if self.read_only(cx) {
12955 return;
12956 }
12957
12958 let clipboard_text = Cow::Borrowed(text.as_str());
12959
12960 self.transact(window, cx, |this, window, cx| {
12961 let had_active_edit_prediction = this.has_active_edit_prediction();
12962 let display_map = this.display_snapshot(cx);
12963 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12964 let cursor_offset = this
12965 .selections
12966 .last::<MultiBufferOffset>(&display_map)
12967 .head();
12968
12969 if let Some(mut clipboard_selections) = clipboard_selections {
12970 let all_selections_were_entire_line =
12971 clipboard_selections.iter().all(|s| s.is_entire_line);
12972 let first_selection_indent_column =
12973 clipboard_selections.first().map(|s| s.first_line_indent);
12974 if clipboard_selections.len() != old_selections.len() {
12975 clipboard_selections.drain(..);
12976 }
12977 let mut auto_indent_on_paste = true;
12978
12979 this.buffer.update(cx, |buffer, cx| {
12980 let snapshot = buffer.read(cx);
12981 auto_indent_on_paste = snapshot
12982 .language_settings_at(cursor_offset, cx)
12983 .auto_indent_on_paste;
12984
12985 let mut start_offset = 0;
12986 let mut edits = Vec::new();
12987 let mut original_indent_columns = Vec::new();
12988 for (ix, selection) in old_selections.iter().enumerate() {
12989 let to_insert;
12990 let entire_line;
12991 let original_indent_column;
12992 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12993 let end_offset = start_offset + clipboard_selection.len;
12994 to_insert = &clipboard_text[start_offset..end_offset];
12995 entire_line = clipboard_selection.is_entire_line;
12996 start_offset = if entire_line {
12997 end_offset
12998 } else {
12999 end_offset + 1
13000 };
13001 original_indent_column = Some(clipboard_selection.first_line_indent);
13002 } else {
13003 to_insert = &*clipboard_text;
13004 entire_line = all_selections_were_entire_line;
13005 original_indent_column = first_selection_indent_column
13006 }
13007
13008 let (range, to_insert) =
13009 if selection.is_empty() && handle_entire_lines && entire_line {
13010 // If the corresponding selection was empty when this slice of the
13011 // clipboard text was written, then the entire line containing the
13012 // selection was copied. If this selection is also currently empty,
13013 // then paste the line before the current line of the buffer.
13014 let column = selection.start.to_point(&snapshot).column as usize;
13015 let line_start = selection.start - column;
13016 (line_start..line_start, Cow::Borrowed(to_insert))
13017 } else {
13018 let language = snapshot.language_at(selection.head());
13019 let range = selection.range();
13020 if let Some(language) = language
13021 && language.name() == "Markdown".into()
13022 {
13023 edit_for_markdown_paste(
13024 &snapshot,
13025 range,
13026 to_insert,
13027 url::Url::parse(to_insert).ok(),
13028 )
13029 } else {
13030 (range, Cow::Borrowed(to_insert))
13031 }
13032 };
13033
13034 edits.push((range, to_insert));
13035 original_indent_columns.push(original_indent_column);
13036 }
13037 drop(snapshot);
13038
13039 buffer.edit(
13040 edits,
13041 if auto_indent_on_paste {
13042 Some(AutoindentMode::Block {
13043 original_indent_columns,
13044 })
13045 } else {
13046 None
13047 },
13048 cx,
13049 );
13050 });
13051
13052 let selections = this
13053 .selections
13054 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13055 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13056 } else {
13057 let url = url::Url::parse(&clipboard_text).ok();
13058
13059 let auto_indent_mode = if !clipboard_text.is_empty() {
13060 Some(AutoindentMode::Block {
13061 original_indent_columns: Vec::new(),
13062 })
13063 } else {
13064 None
13065 };
13066
13067 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13068 let snapshot = buffer.snapshot(cx);
13069
13070 let anchors = old_selections
13071 .iter()
13072 .map(|s| {
13073 let anchor = snapshot.anchor_after(s.head());
13074 s.map(|_| anchor)
13075 })
13076 .collect::<Vec<_>>();
13077
13078 let mut edits = Vec::new();
13079
13080 for selection in old_selections.iter() {
13081 let language = snapshot.language_at(selection.head());
13082 let range = selection.range();
13083
13084 let (edit_range, edit_text) = if let Some(language) = language
13085 && language.name() == "Markdown".into()
13086 {
13087 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13088 } else {
13089 (range, clipboard_text.clone())
13090 };
13091
13092 edits.push((edit_range, edit_text));
13093 }
13094
13095 drop(snapshot);
13096 buffer.edit(edits, auto_indent_mode, cx);
13097
13098 anchors
13099 });
13100
13101 this.change_selections(Default::default(), window, cx, |s| {
13102 s.select_anchors(selection_anchors);
13103 });
13104 }
13105
13106 // 🤔 | .. | show_in_menu |
13107 // | .. | true true
13108 // | had_edit_prediction | false true
13109
13110 let trigger_in_words =
13111 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13112
13113 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13114 });
13115 }
13116
13117 pub fn diff_clipboard_with_selection(
13118 &mut self,
13119 _: &DiffClipboardWithSelection,
13120 window: &mut Window,
13121 cx: &mut Context<Self>,
13122 ) {
13123 let selections = self
13124 .selections
13125 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13126
13127 if selections.is_empty() {
13128 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13129 return;
13130 };
13131
13132 let clipboard_text = match cx.read_from_clipboard() {
13133 Some(item) => match item.entries().first() {
13134 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13135 _ => None,
13136 },
13137 None => None,
13138 };
13139
13140 let Some(clipboard_text) = clipboard_text else {
13141 log::warn!("Clipboard doesn't contain text.");
13142 return;
13143 };
13144
13145 window.dispatch_action(
13146 Box::new(DiffClipboardWithSelectionData {
13147 clipboard_text,
13148 editor: cx.entity(),
13149 }),
13150 cx,
13151 );
13152 }
13153
13154 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13155 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13156 if let Some(item) = cx.read_from_clipboard() {
13157 let entries = item.entries();
13158
13159 match entries.first() {
13160 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13161 // of all the pasted entries.
13162 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13163 .do_paste(
13164 clipboard_string.text(),
13165 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13166 true,
13167 window,
13168 cx,
13169 ),
13170 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13171 }
13172 }
13173 }
13174
13175 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13176 if self.read_only(cx) {
13177 return;
13178 }
13179
13180 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13181
13182 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13183 if let Some((selections, _)) =
13184 self.selection_history.transaction(transaction_id).cloned()
13185 {
13186 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13187 s.select_anchors(selections.to_vec());
13188 });
13189 } else {
13190 log::error!(
13191 "No entry in selection_history found for undo. \
13192 This may correspond to a bug where undo does not update the selection. \
13193 If this is occurring, please add details to \
13194 https://github.com/zed-industries/zed/issues/22692"
13195 );
13196 }
13197 self.request_autoscroll(Autoscroll::fit(), cx);
13198 self.unmark_text(window, cx);
13199 self.refresh_edit_prediction(true, false, window, cx);
13200 cx.emit(EditorEvent::Edited { transaction_id });
13201 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13202 }
13203 }
13204
13205 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13206 if self.read_only(cx) {
13207 return;
13208 }
13209
13210 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13211
13212 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13213 if let Some((_, Some(selections))) =
13214 self.selection_history.transaction(transaction_id).cloned()
13215 {
13216 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13217 s.select_anchors(selections.to_vec());
13218 });
13219 } else {
13220 log::error!(
13221 "No entry in selection_history found for redo. \
13222 This may correspond to a bug where undo does not update the selection. \
13223 If this is occurring, please add details to \
13224 https://github.com/zed-industries/zed/issues/22692"
13225 );
13226 }
13227 self.request_autoscroll(Autoscroll::fit(), cx);
13228 self.unmark_text(window, cx);
13229 self.refresh_edit_prediction(true, false, window, cx);
13230 cx.emit(EditorEvent::Edited { transaction_id });
13231 }
13232 }
13233
13234 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13235 self.buffer
13236 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13237 }
13238
13239 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13240 self.buffer
13241 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13242 }
13243
13244 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13245 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13246 self.change_selections(Default::default(), window, cx, |s| {
13247 s.move_with(|map, selection| {
13248 let cursor = if selection.is_empty() {
13249 movement::left(map, selection.start)
13250 } else {
13251 selection.start
13252 };
13253 selection.collapse_to(cursor, SelectionGoal::None);
13254 });
13255 })
13256 }
13257
13258 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13260 self.change_selections(Default::default(), window, cx, |s| {
13261 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13262 })
13263 }
13264
13265 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13266 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13267 self.change_selections(Default::default(), window, cx, |s| {
13268 s.move_with(|map, selection| {
13269 let cursor = if selection.is_empty() {
13270 movement::right(map, selection.end)
13271 } else {
13272 selection.end
13273 };
13274 selection.collapse_to(cursor, SelectionGoal::None)
13275 });
13276 })
13277 }
13278
13279 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13280 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13281 self.change_selections(Default::default(), window, cx, |s| {
13282 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13283 });
13284 }
13285
13286 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13287 if self.take_rename(true, window, cx).is_some() {
13288 return;
13289 }
13290
13291 if self.mode.is_single_line() {
13292 cx.propagate();
13293 return;
13294 }
13295
13296 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13297
13298 let text_layout_details = &self.text_layout_details(window);
13299 let selection_count = self.selections.count();
13300 let first_selection = self.selections.first_anchor();
13301
13302 self.change_selections(Default::default(), window, cx, |s| {
13303 s.move_with(|map, selection| {
13304 if !selection.is_empty() {
13305 selection.goal = SelectionGoal::None;
13306 }
13307 let (cursor, goal) = movement::up(
13308 map,
13309 selection.start,
13310 selection.goal,
13311 false,
13312 text_layout_details,
13313 );
13314 selection.collapse_to(cursor, goal);
13315 });
13316 });
13317
13318 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13319 {
13320 cx.propagate();
13321 }
13322 }
13323
13324 pub fn move_up_by_lines(
13325 &mut self,
13326 action: &MoveUpByLines,
13327 window: &mut Window,
13328 cx: &mut Context<Self>,
13329 ) {
13330 if self.take_rename(true, window, cx).is_some() {
13331 return;
13332 }
13333
13334 if self.mode.is_single_line() {
13335 cx.propagate();
13336 return;
13337 }
13338
13339 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13340
13341 let text_layout_details = &self.text_layout_details(window);
13342
13343 self.change_selections(Default::default(), window, cx, |s| {
13344 s.move_with(|map, selection| {
13345 if !selection.is_empty() {
13346 selection.goal = SelectionGoal::None;
13347 }
13348 let (cursor, goal) = movement::up_by_rows(
13349 map,
13350 selection.start,
13351 action.lines,
13352 selection.goal,
13353 false,
13354 text_layout_details,
13355 );
13356 selection.collapse_to(cursor, goal);
13357 });
13358 })
13359 }
13360
13361 pub fn move_down_by_lines(
13362 &mut self,
13363 action: &MoveDownByLines,
13364 window: &mut Window,
13365 cx: &mut Context<Self>,
13366 ) {
13367 if self.take_rename(true, window, cx).is_some() {
13368 return;
13369 }
13370
13371 if self.mode.is_single_line() {
13372 cx.propagate();
13373 return;
13374 }
13375
13376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13377
13378 let text_layout_details = &self.text_layout_details(window);
13379
13380 self.change_selections(Default::default(), window, cx, |s| {
13381 s.move_with(|map, selection| {
13382 if !selection.is_empty() {
13383 selection.goal = SelectionGoal::None;
13384 }
13385 let (cursor, goal) = movement::down_by_rows(
13386 map,
13387 selection.start,
13388 action.lines,
13389 selection.goal,
13390 false,
13391 text_layout_details,
13392 );
13393 selection.collapse_to(cursor, goal);
13394 });
13395 })
13396 }
13397
13398 pub fn select_down_by_lines(
13399 &mut self,
13400 action: &SelectDownByLines,
13401 window: &mut Window,
13402 cx: &mut Context<Self>,
13403 ) {
13404 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13405 let text_layout_details = &self.text_layout_details(window);
13406 self.change_selections(Default::default(), window, cx, |s| {
13407 s.move_heads_with(|map, head, goal| {
13408 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13409 })
13410 })
13411 }
13412
13413 pub fn select_up_by_lines(
13414 &mut self,
13415 action: &SelectUpByLines,
13416 window: &mut Window,
13417 cx: &mut Context<Self>,
13418 ) {
13419 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13420 let text_layout_details = &self.text_layout_details(window);
13421 self.change_selections(Default::default(), window, cx, |s| {
13422 s.move_heads_with(|map, head, goal| {
13423 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13424 })
13425 })
13426 }
13427
13428 pub fn select_page_up(
13429 &mut self,
13430 _: &SelectPageUp,
13431 window: &mut Window,
13432 cx: &mut Context<Self>,
13433 ) {
13434 let Some(row_count) = self.visible_row_count() else {
13435 return;
13436 };
13437
13438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13439
13440 let text_layout_details = &self.text_layout_details(window);
13441
13442 self.change_selections(Default::default(), window, cx, |s| {
13443 s.move_heads_with(|map, head, goal| {
13444 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13445 })
13446 })
13447 }
13448
13449 pub fn move_page_up(
13450 &mut self,
13451 action: &MovePageUp,
13452 window: &mut Window,
13453 cx: &mut Context<Self>,
13454 ) {
13455 if self.take_rename(true, window, cx).is_some() {
13456 return;
13457 }
13458
13459 if self
13460 .context_menu
13461 .borrow_mut()
13462 .as_mut()
13463 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13464 .unwrap_or(false)
13465 {
13466 return;
13467 }
13468
13469 if matches!(self.mode, EditorMode::SingleLine) {
13470 cx.propagate();
13471 return;
13472 }
13473
13474 let Some(row_count) = self.visible_row_count() else {
13475 return;
13476 };
13477
13478 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13479
13480 let effects = if action.center_cursor {
13481 SelectionEffects::scroll(Autoscroll::center())
13482 } else {
13483 SelectionEffects::default()
13484 };
13485
13486 let text_layout_details = &self.text_layout_details(window);
13487
13488 self.change_selections(effects, window, cx, |s| {
13489 s.move_with(|map, selection| {
13490 if !selection.is_empty() {
13491 selection.goal = SelectionGoal::None;
13492 }
13493 let (cursor, goal) = movement::up_by_rows(
13494 map,
13495 selection.end,
13496 row_count,
13497 selection.goal,
13498 false,
13499 text_layout_details,
13500 );
13501 selection.collapse_to(cursor, goal);
13502 });
13503 });
13504 }
13505
13506 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13507 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13508 let text_layout_details = &self.text_layout_details(window);
13509 self.change_selections(Default::default(), window, cx, |s| {
13510 s.move_heads_with(|map, head, goal| {
13511 movement::up(map, head, goal, false, text_layout_details)
13512 })
13513 })
13514 }
13515
13516 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13517 self.take_rename(true, window, cx);
13518
13519 if self.mode.is_single_line() {
13520 cx.propagate();
13521 return;
13522 }
13523
13524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13525
13526 let text_layout_details = &self.text_layout_details(window);
13527 let selection_count = self.selections.count();
13528 let first_selection = self.selections.first_anchor();
13529
13530 self.change_selections(Default::default(), window, cx, |s| {
13531 s.move_with(|map, selection| {
13532 if !selection.is_empty() {
13533 selection.goal = SelectionGoal::None;
13534 }
13535 let (cursor, goal) = movement::down(
13536 map,
13537 selection.end,
13538 selection.goal,
13539 false,
13540 text_layout_details,
13541 );
13542 selection.collapse_to(cursor, goal);
13543 });
13544 });
13545
13546 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13547 {
13548 cx.propagate();
13549 }
13550 }
13551
13552 pub fn select_page_down(
13553 &mut self,
13554 _: &SelectPageDown,
13555 window: &mut Window,
13556 cx: &mut Context<Self>,
13557 ) {
13558 let Some(row_count) = self.visible_row_count() else {
13559 return;
13560 };
13561
13562 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13563
13564 let text_layout_details = &self.text_layout_details(window);
13565
13566 self.change_selections(Default::default(), window, cx, |s| {
13567 s.move_heads_with(|map, head, goal| {
13568 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13569 })
13570 })
13571 }
13572
13573 pub fn move_page_down(
13574 &mut self,
13575 action: &MovePageDown,
13576 window: &mut Window,
13577 cx: &mut Context<Self>,
13578 ) {
13579 if self.take_rename(true, window, cx).is_some() {
13580 return;
13581 }
13582
13583 if self
13584 .context_menu
13585 .borrow_mut()
13586 .as_mut()
13587 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13588 .unwrap_or(false)
13589 {
13590 return;
13591 }
13592
13593 if matches!(self.mode, EditorMode::SingleLine) {
13594 cx.propagate();
13595 return;
13596 }
13597
13598 let Some(row_count) = self.visible_row_count() else {
13599 return;
13600 };
13601
13602 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13603
13604 let effects = if action.center_cursor {
13605 SelectionEffects::scroll(Autoscroll::center())
13606 } else {
13607 SelectionEffects::default()
13608 };
13609
13610 let text_layout_details = &self.text_layout_details(window);
13611 self.change_selections(effects, window, cx, |s| {
13612 s.move_with(|map, selection| {
13613 if !selection.is_empty() {
13614 selection.goal = SelectionGoal::None;
13615 }
13616 let (cursor, goal) = movement::down_by_rows(
13617 map,
13618 selection.end,
13619 row_count,
13620 selection.goal,
13621 false,
13622 text_layout_details,
13623 );
13624 selection.collapse_to(cursor, goal);
13625 });
13626 });
13627 }
13628
13629 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13630 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13631 let text_layout_details = &self.text_layout_details(window);
13632 self.change_selections(Default::default(), window, cx, |s| {
13633 s.move_heads_with(|map, head, goal| {
13634 movement::down(map, head, goal, false, text_layout_details)
13635 })
13636 });
13637 }
13638
13639 pub fn context_menu_first(
13640 &mut self,
13641 _: &ContextMenuFirst,
13642 window: &mut Window,
13643 cx: &mut Context<Self>,
13644 ) {
13645 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13646 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13647 }
13648 }
13649
13650 pub fn context_menu_prev(
13651 &mut self,
13652 _: &ContextMenuPrevious,
13653 window: &mut Window,
13654 cx: &mut Context<Self>,
13655 ) {
13656 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13657 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13658 }
13659 }
13660
13661 pub fn context_menu_next(
13662 &mut self,
13663 _: &ContextMenuNext,
13664 window: &mut Window,
13665 cx: &mut Context<Self>,
13666 ) {
13667 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13668 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13669 }
13670 }
13671
13672 pub fn context_menu_last(
13673 &mut self,
13674 _: &ContextMenuLast,
13675 window: &mut Window,
13676 cx: &mut Context<Self>,
13677 ) {
13678 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13679 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13680 }
13681 }
13682
13683 pub fn signature_help_prev(
13684 &mut self,
13685 _: &SignatureHelpPrevious,
13686 _: &mut Window,
13687 cx: &mut Context<Self>,
13688 ) {
13689 if let Some(popover) = self.signature_help_state.popover_mut() {
13690 if popover.current_signature == 0 {
13691 popover.current_signature = popover.signatures.len() - 1;
13692 } else {
13693 popover.current_signature -= 1;
13694 }
13695 cx.notify();
13696 }
13697 }
13698
13699 pub fn signature_help_next(
13700 &mut self,
13701 _: &SignatureHelpNext,
13702 _: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 if let Some(popover) = self.signature_help_state.popover_mut() {
13706 if popover.current_signature + 1 == popover.signatures.len() {
13707 popover.current_signature = 0;
13708 } else {
13709 popover.current_signature += 1;
13710 }
13711 cx.notify();
13712 }
13713 }
13714
13715 pub fn move_to_previous_word_start(
13716 &mut self,
13717 _: &MoveToPreviousWordStart,
13718 window: &mut Window,
13719 cx: &mut Context<Self>,
13720 ) {
13721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13722 self.change_selections(Default::default(), window, cx, |s| {
13723 s.move_cursors_with(|map, head, _| {
13724 (
13725 movement::previous_word_start(map, head),
13726 SelectionGoal::None,
13727 )
13728 });
13729 })
13730 }
13731
13732 pub fn move_to_previous_subword_start(
13733 &mut self,
13734 _: &MoveToPreviousSubwordStart,
13735 window: &mut Window,
13736 cx: &mut Context<Self>,
13737 ) {
13738 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13739 self.change_selections(Default::default(), window, cx, |s| {
13740 s.move_cursors_with(|map, head, _| {
13741 (
13742 movement::previous_subword_start(map, head),
13743 SelectionGoal::None,
13744 )
13745 });
13746 })
13747 }
13748
13749 pub fn select_to_previous_word_start(
13750 &mut self,
13751 _: &SelectToPreviousWordStart,
13752 window: &mut Window,
13753 cx: &mut Context<Self>,
13754 ) {
13755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13756 self.change_selections(Default::default(), window, cx, |s| {
13757 s.move_heads_with(|map, head, _| {
13758 (
13759 movement::previous_word_start(map, head),
13760 SelectionGoal::None,
13761 )
13762 });
13763 })
13764 }
13765
13766 pub fn select_to_previous_subword_start(
13767 &mut self,
13768 _: &SelectToPreviousSubwordStart,
13769 window: &mut Window,
13770 cx: &mut Context<Self>,
13771 ) {
13772 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13773 self.change_selections(Default::default(), window, cx, |s| {
13774 s.move_heads_with(|map, head, _| {
13775 (
13776 movement::previous_subword_start(map, head),
13777 SelectionGoal::None,
13778 )
13779 });
13780 })
13781 }
13782
13783 pub fn delete_to_previous_word_start(
13784 &mut self,
13785 action: &DeleteToPreviousWordStart,
13786 window: &mut Window,
13787 cx: &mut Context<Self>,
13788 ) {
13789 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13790 self.transact(window, cx, |this, window, cx| {
13791 this.select_autoclose_pair(window, cx);
13792 this.change_selections(Default::default(), window, cx, |s| {
13793 s.move_with(|map, selection| {
13794 if selection.is_empty() {
13795 let mut cursor = if action.ignore_newlines {
13796 movement::previous_word_start(map, selection.head())
13797 } else {
13798 movement::previous_word_start_or_newline(map, selection.head())
13799 };
13800 cursor = movement::adjust_greedy_deletion(
13801 map,
13802 selection.head(),
13803 cursor,
13804 action.ignore_brackets,
13805 );
13806 selection.set_head(cursor, SelectionGoal::None);
13807 }
13808 });
13809 });
13810 this.insert("", window, cx);
13811 });
13812 }
13813
13814 pub fn delete_to_previous_subword_start(
13815 &mut self,
13816 _: &DeleteToPreviousSubwordStart,
13817 window: &mut Window,
13818 cx: &mut Context<Self>,
13819 ) {
13820 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13821 self.transact(window, cx, |this, window, cx| {
13822 this.select_autoclose_pair(window, cx);
13823 this.change_selections(Default::default(), window, cx, |s| {
13824 s.move_with(|map, selection| {
13825 if selection.is_empty() {
13826 let mut cursor = movement::previous_subword_start(map, selection.head());
13827 cursor =
13828 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13829 selection.set_head(cursor, SelectionGoal::None);
13830 }
13831 });
13832 });
13833 this.insert("", window, cx);
13834 });
13835 }
13836
13837 pub fn move_to_next_word_end(
13838 &mut self,
13839 _: &MoveToNextWordEnd,
13840 window: &mut Window,
13841 cx: &mut Context<Self>,
13842 ) {
13843 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13844 self.change_selections(Default::default(), window, cx, |s| {
13845 s.move_cursors_with(|map, head, _| {
13846 (movement::next_word_end(map, head), SelectionGoal::None)
13847 });
13848 })
13849 }
13850
13851 pub fn move_to_next_subword_end(
13852 &mut self,
13853 _: &MoveToNextSubwordEnd,
13854 window: &mut Window,
13855 cx: &mut Context<Self>,
13856 ) {
13857 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13858 self.change_selections(Default::default(), window, cx, |s| {
13859 s.move_cursors_with(|map, head, _| {
13860 (movement::next_subword_end(map, head), SelectionGoal::None)
13861 });
13862 })
13863 }
13864
13865 pub fn select_to_next_word_end(
13866 &mut self,
13867 _: &SelectToNextWordEnd,
13868 window: &mut Window,
13869 cx: &mut Context<Self>,
13870 ) {
13871 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13872 self.change_selections(Default::default(), window, cx, |s| {
13873 s.move_heads_with(|map, head, _| {
13874 (movement::next_word_end(map, head), SelectionGoal::None)
13875 });
13876 })
13877 }
13878
13879 pub fn select_to_next_subword_end(
13880 &mut self,
13881 _: &SelectToNextSubwordEnd,
13882 window: &mut Window,
13883 cx: &mut Context<Self>,
13884 ) {
13885 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13886 self.change_selections(Default::default(), window, cx, |s| {
13887 s.move_heads_with(|map, head, _| {
13888 (movement::next_subword_end(map, head), SelectionGoal::None)
13889 });
13890 })
13891 }
13892
13893 pub fn delete_to_next_word_end(
13894 &mut self,
13895 action: &DeleteToNextWordEnd,
13896 window: &mut Window,
13897 cx: &mut Context<Self>,
13898 ) {
13899 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13900 self.transact(window, cx, |this, window, cx| {
13901 this.change_selections(Default::default(), window, cx, |s| {
13902 s.move_with(|map, selection| {
13903 if selection.is_empty() {
13904 let mut cursor = if action.ignore_newlines {
13905 movement::next_word_end(map, selection.head())
13906 } else {
13907 movement::next_word_end_or_newline(map, selection.head())
13908 };
13909 cursor = movement::adjust_greedy_deletion(
13910 map,
13911 selection.head(),
13912 cursor,
13913 action.ignore_brackets,
13914 );
13915 selection.set_head(cursor, SelectionGoal::None);
13916 }
13917 });
13918 });
13919 this.insert("", window, cx);
13920 });
13921 }
13922
13923 pub fn delete_to_next_subword_end(
13924 &mut self,
13925 _: &DeleteToNextSubwordEnd,
13926 window: &mut Window,
13927 cx: &mut Context<Self>,
13928 ) {
13929 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13930 self.transact(window, cx, |this, window, cx| {
13931 this.change_selections(Default::default(), window, cx, |s| {
13932 s.move_with(|map, selection| {
13933 if selection.is_empty() {
13934 let mut cursor = movement::next_subword_end(map, selection.head());
13935 cursor =
13936 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13937 selection.set_head(cursor, SelectionGoal::None);
13938 }
13939 });
13940 });
13941 this.insert("", window, cx);
13942 });
13943 }
13944
13945 pub fn move_to_beginning_of_line(
13946 &mut self,
13947 action: &MoveToBeginningOfLine,
13948 window: &mut Window,
13949 cx: &mut Context<Self>,
13950 ) {
13951 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13952 self.change_selections(Default::default(), window, cx, |s| {
13953 s.move_cursors_with(|map, head, _| {
13954 (
13955 movement::indented_line_beginning(
13956 map,
13957 head,
13958 action.stop_at_soft_wraps,
13959 action.stop_at_indent,
13960 ),
13961 SelectionGoal::None,
13962 )
13963 });
13964 })
13965 }
13966
13967 pub fn select_to_beginning_of_line(
13968 &mut self,
13969 action: &SelectToBeginningOfLine,
13970 window: &mut Window,
13971 cx: &mut Context<Self>,
13972 ) {
13973 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13974 self.change_selections(Default::default(), window, cx, |s| {
13975 s.move_heads_with(|map, head, _| {
13976 (
13977 movement::indented_line_beginning(
13978 map,
13979 head,
13980 action.stop_at_soft_wraps,
13981 action.stop_at_indent,
13982 ),
13983 SelectionGoal::None,
13984 )
13985 });
13986 });
13987 }
13988
13989 pub fn delete_to_beginning_of_line(
13990 &mut self,
13991 action: &DeleteToBeginningOfLine,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) {
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13996 self.transact(window, cx, |this, window, cx| {
13997 this.change_selections(Default::default(), window, cx, |s| {
13998 s.move_with(|_, selection| {
13999 selection.reversed = true;
14000 });
14001 });
14002
14003 this.select_to_beginning_of_line(
14004 &SelectToBeginningOfLine {
14005 stop_at_soft_wraps: false,
14006 stop_at_indent: action.stop_at_indent,
14007 },
14008 window,
14009 cx,
14010 );
14011 this.backspace(&Backspace, window, cx);
14012 });
14013 }
14014
14015 pub fn move_to_end_of_line(
14016 &mut self,
14017 action: &MoveToEndOfLine,
14018 window: &mut Window,
14019 cx: &mut Context<Self>,
14020 ) {
14021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14022 self.change_selections(Default::default(), window, cx, |s| {
14023 s.move_cursors_with(|map, head, _| {
14024 (
14025 movement::line_end(map, head, action.stop_at_soft_wraps),
14026 SelectionGoal::None,
14027 )
14028 });
14029 })
14030 }
14031
14032 pub fn select_to_end_of_line(
14033 &mut self,
14034 action: &SelectToEndOfLine,
14035 window: &mut Window,
14036 cx: &mut Context<Self>,
14037 ) {
14038 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14039 self.change_selections(Default::default(), window, cx, |s| {
14040 s.move_heads_with(|map, head, _| {
14041 (
14042 movement::line_end(map, head, action.stop_at_soft_wraps),
14043 SelectionGoal::None,
14044 )
14045 });
14046 })
14047 }
14048
14049 pub fn delete_to_end_of_line(
14050 &mut self,
14051 _: &DeleteToEndOfLine,
14052 window: &mut Window,
14053 cx: &mut Context<Self>,
14054 ) {
14055 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14056 self.transact(window, cx, |this, window, cx| {
14057 this.select_to_end_of_line(
14058 &SelectToEndOfLine {
14059 stop_at_soft_wraps: false,
14060 },
14061 window,
14062 cx,
14063 );
14064 this.delete(&Delete, window, cx);
14065 });
14066 }
14067
14068 pub fn cut_to_end_of_line(
14069 &mut self,
14070 action: &CutToEndOfLine,
14071 window: &mut Window,
14072 cx: &mut Context<Self>,
14073 ) {
14074 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14075 self.transact(window, cx, |this, window, cx| {
14076 this.select_to_end_of_line(
14077 &SelectToEndOfLine {
14078 stop_at_soft_wraps: false,
14079 },
14080 window,
14081 cx,
14082 );
14083 if !action.stop_at_newlines {
14084 this.change_selections(Default::default(), window, cx, |s| {
14085 s.move_with(|_, sel| {
14086 if sel.is_empty() {
14087 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14088 }
14089 });
14090 });
14091 }
14092 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14093 let item = this.cut_common(false, window, cx);
14094 cx.write_to_clipboard(item);
14095 });
14096 }
14097
14098 pub fn move_to_start_of_paragraph(
14099 &mut self,
14100 _: &MoveToStartOfParagraph,
14101 window: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) {
14104 if matches!(self.mode, EditorMode::SingleLine) {
14105 cx.propagate();
14106 return;
14107 }
14108 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14109 self.change_selections(Default::default(), window, cx, |s| {
14110 s.move_with(|map, selection| {
14111 selection.collapse_to(
14112 movement::start_of_paragraph(map, selection.head(), 1),
14113 SelectionGoal::None,
14114 )
14115 });
14116 })
14117 }
14118
14119 pub fn move_to_end_of_paragraph(
14120 &mut self,
14121 _: &MoveToEndOfParagraph,
14122 window: &mut Window,
14123 cx: &mut Context<Self>,
14124 ) {
14125 if matches!(self.mode, EditorMode::SingleLine) {
14126 cx.propagate();
14127 return;
14128 }
14129 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14130 self.change_selections(Default::default(), window, cx, |s| {
14131 s.move_with(|map, selection| {
14132 selection.collapse_to(
14133 movement::end_of_paragraph(map, selection.head(), 1),
14134 SelectionGoal::None,
14135 )
14136 });
14137 })
14138 }
14139
14140 pub fn select_to_start_of_paragraph(
14141 &mut self,
14142 _: &SelectToStartOfParagraph,
14143 window: &mut Window,
14144 cx: &mut Context<Self>,
14145 ) {
14146 if matches!(self.mode, EditorMode::SingleLine) {
14147 cx.propagate();
14148 return;
14149 }
14150 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14151 self.change_selections(Default::default(), window, cx, |s| {
14152 s.move_heads_with(|map, head, _| {
14153 (
14154 movement::start_of_paragraph(map, head, 1),
14155 SelectionGoal::None,
14156 )
14157 });
14158 })
14159 }
14160
14161 pub fn select_to_end_of_paragraph(
14162 &mut self,
14163 _: &SelectToEndOfParagraph,
14164 window: &mut Window,
14165 cx: &mut Context<Self>,
14166 ) {
14167 if matches!(self.mode, EditorMode::SingleLine) {
14168 cx.propagate();
14169 return;
14170 }
14171 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14172 self.change_selections(Default::default(), window, cx, |s| {
14173 s.move_heads_with(|map, head, _| {
14174 (
14175 movement::end_of_paragraph(map, head, 1),
14176 SelectionGoal::None,
14177 )
14178 });
14179 })
14180 }
14181
14182 pub fn move_to_start_of_excerpt(
14183 &mut self,
14184 _: &MoveToStartOfExcerpt,
14185 window: &mut Window,
14186 cx: &mut Context<Self>,
14187 ) {
14188 if matches!(self.mode, EditorMode::SingleLine) {
14189 cx.propagate();
14190 return;
14191 }
14192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14193 self.change_selections(Default::default(), window, cx, |s| {
14194 s.move_with(|map, selection| {
14195 selection.collapse_to(
14196 movement::start_of_excerpt(
14197 map,
14198 selection.head(),
14199 workspace::searchable::Direction::Prev,
14200 ),
14201 SelectionGoal::None,
14202 )
14203 });
14204 })
14205 }
14206
14207 pub fn move_to_start_of_next_excerpt(
14208 &mut self,
14209 _: &MoveToStartOfNextExcerpt,
14210 window: &mut Window,
14211 cx: &mut Context<Self>,
14212 ) {
14213 if matches!(self.mode, EditorMode::SingleLine) {
14214 cx.propagate();
14215 return;
14216 }
14217
14218 self.change_selections(Default::default(), window, cx, |s| {
14219 s.move_with(|map, selection| {
14220 selection.collapse_to(
14221 movement::start_of_excerpt(
14222 map,
14223 selection.head(),
14224 workspace::searchable::Direction::Next,
14225 ),
14226 SelectionGoal::None,
14227 )
14228 });
14229 })
14230 }
14231
14232 pub fn move_to_end_of_excerpt(
14233 &mut self,
14234 _: &MoveToEndOfExcerpt,
14235 window: &mut Window,
14236 cx: &mut Context<Self>,
14237 ) {
14238 if matches!(self.mode, EditorMode::SingleLine) {
14239 cx.propagate();
14240 return;
14241 }
14242 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14243 self.change_selections(Default::default(), window, cx, |s| {
14244 s.move_with(|map, selection| {
14245 selection.collapse_to(
14246 movement::end_of_excerpt(
14247 map,
14248 selection.head(),
14249 workspace::searchable::Direction::Next,
14250 ),
14251 SelectionGoal::None,
14252 )
14253 });
14254 })
14255 }
14256
14257 pub fn move_to_end_of_previous_excerpt(
14258 &mut self,
14259 _: &MoveToEndOfPreviousExcerpt,
14260 window: &mut Window,
14261 cx: &mut Context<Self>,
14262 ) {
14263 if matches!(self.mode, EditorMode::SingleLine) {
14264 cx.propagate();
14265 return;
14266 }
14267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14268 self.change_selections(Default::default(), window, cx, |s| {
14269 s.move_with(|map, selection| {
14270 selection.collapse_to(
14271 movement::end_of_excerpt(
14272 map,
14273 selection.head(),
14274 workspace::searchable::Direction::Prev,
14275 ),
14276 SelectionGoal::None,
14277 )
14278 });
14279 })
14280 }
14281
14282 pub fn select_to_start_of_excerpt(
14283 &mut self,
14284 _: &SelectToStartOfExcerpt,
14285 window: &mut Window,
14286 cx: &mut Context<Self>,
14287 ) {
14288 if matches!(self.mode, EditorMode::SingleLine) {
14289 cx.propagate();
14290 return;
14291 }
14292 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14293 self.change_selections(Default::default(), window, cx, |s| {
14294 s.move_heads_with(|map, head, _| {
14295 (
14296 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14297 SelectionGoal::None,
14298 )
14299 });
14300 })
14301 }
14302
14303 pub fn select_to_start_of_next_excerpt(
14304 &mut self,
14305 _: &SelectToStartOfNextExcerpt,
14306 window: &mut Window,
14307 cx: &mut Context<Self>,
14308 ) {
14309 if matches!(self.mode, EditorMode::SingleLine) {
14310 cx.propagate();
14311 return;
14312 }
14313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14314 self.change_selections(Default::default(), window, cx, |s| {
14315 s.move_heads_with(|map, head, _| {
14316 (
14317 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14318 SelectionGoal::None,
14319 )
14320 });
14321 })
14322 }
14323
14324 pub fn select_to_end_of_excerpt(
14325 &mut self,
14326 _: &SelectToEndOfExcerpt,
14327 window: &mut Window,
14328 cx: &mut Context<Self>,
14329 ) {
14330 if matches!(self.mode, EditorMode::SingleLine) {
14331 cx.propagate();
14332 return;
14333 }
14334 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14335 self.change_selections(Default::default(), window, cx, |s| {
14336 s.move_heads_with(|map, head, _| {
14337 (
14338 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14339 SelectionGoal::None,
14340 )
14341 });
14342 })
14343 }
14344
14345 pub fn select_to_end_of_previous_excerpt(
14346 &mut self,
14347 _: &SelectToEndOfPreviousExcerpt,
14348 window: &mut Window,
14349 cx: &mut Context<Self>,
14350 ) {
14351 if matches!(self.mode, EditorMode::SingleLine) {
14352 cx.propagate();
14353 return;
14354 }
14355 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14356 self.change_selections(Default::default(), window, cx, |s| {
14357 s.move_heads_with(|map, head, _| {
14358 (
14359 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14360 SelectionGoal::None,
14361 )
14362 });
14363 })
14364 }
14365
14366 pub fn move_to_beginning(
14367 &mut self,
14368 _: &MoveToBeginning,
14369 window: &mut Window,
14370 cx: &mut Context<Self>,
14371 ) {
14372 if matches!(self.mode, EditorMode::SingleLine) {
14373 cx.propagate();
14374 return;
14375 }
14376 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14377 self.change_selections(Default::default(), window, cx, |s| {
14378 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14379 });
14380 }
14381
14382 pub fn select_to_beginning(
14383 &mut self,
14384 _: &SelectToBeginning,
14385 window: &mut Window,
14386 cx: &mut Context<Self>,
14387 ) {
14388 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14389 selection.set_head(Point::zero(), SelectionGoal::None);
14390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14391 self.change_selections(Default::default(), window, cx, |s| {
14392 s.select(vec![selection]);
14393 });
14394 }
14395
14396 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14397 if matches!(self.mode, EditorMode::SingleLine) {
14398 cx.propagate();
14399 return;
14400 }
14401 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14402 let cursor = self.buffer.read(cx).read(cx).len();
14403 self.change_selections(Default::default(), window, cx, |s| {
14404 s.select_ranges(vec![cursor..cursor])
14405 });
14406 }
14407
14408 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14409 self.nav_history = nav_history;
14410 }
14411
14412 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14413 self.nav_history.as_ref()
14414 }
14415
14416 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14417 self.push_to_nav_history(
14418 self.selections.newest_anchor().head(),
14419 None,
14420 false,
14421 true,
14422 cx,
14423 );
14424 }
14425
14426 fn push_to_nav_history(
14427 &mut self,
14428 cursor_anchor: Anchor,
14429 new_position: Option<Point>,
14430 is_deactivate: bool,
14431 always: bool,
14432 cx: &mut Context<Self>,
14433 ) {
14434 if let Some(nav_history) = self.nav_history.as_mut() {
14435 let buffer = self.buffer.read(cx).read(cx);
14436 let cursor_position = cursor_anchor.to_point(&buffer);
14437 let scroll_state = self.scroll_manager.anchor();
14438 let scroll_top_row = scroll_state.top_row(&buffer);
14439 drop(buffer);
14440
14441 if let Some(new_position) = new_position {
14442 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14443 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14444 return;
14445 }
14446 }
14447
14448 nav_history.push(
14449 Some(NavigationData {
14450 cursor_anchor,
14451 cursor_position,
14452 scroll_anchor: scroll_state,
14453 scroll_top_row,
14454 }),
14455 cx,
14456 );
14457 cx.emit(EditorEvent::PushedToNavHistory {
14458 anchor: cursor_anchor,
14459 is_deactivate,
14460 })
14461 }
14462 }
14463
14464 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14465 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14466 let buffer = self.buffer.read(cx).snapshot(cx);
14467 let mut selection = self
14468 .selections
14469 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14470 selection.set_head(buffer.len(), SelectionGoal::None);
14471 self.change_selections(Default::default(), window, cx, |s| {
14472 s.select(vec![selection]);
14473 });
14474 }
14475
14476 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14477 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14478 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14479 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14480 });
14481 }
14482
14483 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14485 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14486 let mut selections = self.selections.all::<Point>(&display_map);
14487 let max_point = display_map.buffer_snapshot().max_point();
14488 for selection in &mut selections {
14489 let rows = selection.spanned_rows(true, &display_map);
14490 selection.start = Point::new(rows.start.0, 0);
14491 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14492 selection.reversed = false;
14493 }
14494 self.change_selections(Default::default(), window, cx, |s| {
14495 s.select(selections);
14496 });
14497 }
14498
14499 pub fn split_selection_into_lines(
14500 &mut self,
14501 action: &SplitSelectionIntoLines,
14502 window: &mut Window,
14503 cx: &mut Context<Self>,
14504 ) {
14505 let selections = self
14506 .selections
14507 .all::<Point>(&self.display_snapshot(cx))
14508 .into_iter()
14509 .map(|selection| selection.start..selection.end)
14510 .collect::<Vec<_>>();
14511 self.unfold_ranges(&selections, true, true, cx);
14512
14513 let mut new_selection_ranges = Vec::new();
14514 {
14515 let buffer = self.buffer.read(cx).read(cx);
14516 for selection in selections {
14517 for row in selection.start.row..selection.end.row {
14518 let line_start = Point::new(row, 0);
14519 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14520
14521 if action.keep_selections {
14522 // Keep the selection range for each line
14523 let selection_start = if row == selection.start.row {
14524 selection.start
14525 } else {
14526 line_start
14527 };
14528 new_selection_ranges.push(selection_start..line_end);
14529 } else {
14530 // Collapse to cursor at end of line
14531 new_selection_ranges.push(line_end..line_end);
14532 }
14533 }
14534
14535 let is_multiline_selection = selection.start.row != selection.end.row;
14536 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14537 // so this action feels more ergonomic when paired with other selection operations
14538 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14539 if !should_skip_last {
14540 if action.keep_selections {
14541 if is_multiline_selection {
14542 let line_start = Point::new(selection.end.row, 0);
14543 new_selection_ranges.push(line_start..selection.end);
14544 } else {
14545 new_selection_ranges.push(selection.start..selection.end);
14546 }
14547 } else {
14548 new_selection_ranges.push(selection.end..selection.end);
14549 }
14550 }
14551 }
14552 }
14553 self.change_selections(Default::default(), window, cx, |s| {
14554 s.select_ranges(new_selection_ranges);
14555 });
14556 }
14557
14558 pub fn add_selection_above(
14559 &mut self,
14560 action: &AddSelectionAbove,
14561 window: &mut Window,
14562 cx: &mut Context<Self>,
14563 ) {
14564 self.add_selection(true, action.skip_soft_wrap, window, cx);
14565 }
14566
14567 pub fn add_selection_below(
14568 &mut self,
14569 action: &AddSelectionBelow,
14570 window: &mut Window,
14571 cx: &mut Context<Self>,
14572 ) {
14573 self.add_selection(false, action.skip_soft_wrap, window, cx);
14574 }
14575
14576 fn add_selection(
14577 &mut self,
14578 above: bool,
14579 skip_soft_wrap: bool,
14580 window: &mut Window,
14581 cx: &mut Context<Self>,
14582 ) {
14583 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14584
14585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14586 let all_selections = self.selections.all::<Point>(&display_map);
14587 let text_layout_details = self.text_layout_details(window);
14588
14589 let (mut columnar_selections, new_selections_to_columnarize) = {
14590 if let Some(state) = self.add_selections_state.as_ref() {
14591 let columnar_selection_ids: HashSet<_> = state
14592 .groups
14593 .iter()
14594 .flat_map(|group| group.stack.iter())
14595 .copied()
14596 .collect();
14597
14598 all_selections
14599 .into_iter()
14600 .partition(|s| columnar_selection_ids.contains(&s.id))
14601 } else {
14602 (Vec::new(), all_selections)
14603 }
14604 };
14605
14606 let mut state = self
14607 .add_selections_state
14608 .take()
14609 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14610
14611 for selection in new_selections_to_columnarize {
14612 let range = selection.display_range(&display_map).sorted();
14613 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14614 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14615 let positions = start_x.min(end_x)..start_x.max(end_x);
14616 let mut stack = Vec::new();
14617 for row in range.start.row().0..=range.end.row().0 {
14618 if let Some(selection) = self.selections.build_columnar_selection(
14619 &display_map,
14620 DisplayRow(row),
14621 &positions,
14622 selection.reversed,
14623 &text_layout_details,
14624 ) {
14625 stack.push(selection.id);
14626 columnar_selections.push(selection);
14627 }
14628 }
14629 if !stack.is_empty() {
14630 if above {
14631 stack.reverse();
14632 }
14633 state.groups.push(AddSelectionsGroup { above, stack });
14634 }
14635 }
14636
14637 let mut final_selections = Vec::new();
14638 let end_row = if above {
14639 DisplayRow(0)
14640 } else {
14641 display_map.max_point().row()
14642 };
14643
14644 let mut last_added_item_per_group = HashMap::default();
14645 for group in state.groups.iter_mut() {
14646 if let Some(last_id) = group.stack.last() {
14647 last_added_item_per_group.insert(*last_id, group);
14648 }
14649 }
14650
14651 for selection in columnar_selections {
14652 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14653 if above == group.above {
14654 let range = selection.display_range(&display_map).sorted();
14655 debug_assert_eq!(range.start.row(), range.end.row());
14656 let mut row = range.start.row();
14657 let positions =
14658 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14659 Pixels::from(start)..Pixels::from(end)
14660 } else {
14661 let start_x =
14662 display_map.x_for_display_point(range.start, &text_layout_details);
14663 let end_x =
14664 display_map.x_for_display_point(range.end, &text_layout_details);
14665 start_x.min(end_x)..start_x.max(end_x)
14666 };
14667
14668 let mut maybe_new_selection = None;
14669 let direction = if above { -1 } else { 1 };
14670
14671 while row != end_row {
14672 if skip_soft_wrap {
14673 row = display_map
14674 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14675 .row();
14676 } else if above {
14677 row.0 -= 1;
14678 } else {
14679 row.0 += 1;
14680 }
14681
14682 if let Some(new_selection) = self.selections.build_columnar_selection(
14683 &display_map,
14684 row,
14685 &positions,
14686 selection.reversed,
14687 &text_layout_details,
14688 ) {
14689 maybe_new_selection = Some(new_selection);
14690 break;
14691 }
14692 }
14693
14694 if let Some(new_selection) = maybe_new_selection {
14695 group.stack.push(new_selection.id);
14696 if above {
14697 final_selections.push(new_selection);
14698 final_selections.push(selection);
14699 } else {
14700 final_selections.push(selection);
14701 final_selections.push(new_selection);
14702 }
14703 } else {
14704 final_selections.push(selection);
14705 }
14706 } else {
14707 group.stack.pop();
14708 }
14709 } else {
14710 final_selections.push(selection);
14711 }
14712 }
14713
14714 self.change_selections(Default::default(), window, cx, |s| {
14715 s.select(final_selections);
14716 });
14717
14718 let final_selection_ids: HashSet<_> = self
14719 .selections
14720 .all::<Point>(&display_map)
14721 .iter()
14722 .map(|s| s.id)
14723 .collect();
14724 state.groups.retain_mut(|group| {
14725 // selections might get merged above so we remove invalid items from stacks
14726 group.stack.retain(|id| final_selection_ids.contains(id));
14727
14728 // single selection in stack can be treated as initial state
14729 group.stack.len() > 1
14730 });
14731
14732 if !state.groups.is_empty() {
14733 self.add_selections_state = Some(state);
14734 }
14735 }
14736
14737 fn select_match_ranges(
14738 &mut self,
14739 range: Range<MultiBufferOffset>,
14740 reversed: bool,
14741 replace_newest: bool,
14742 auto_scroll: Option<Autoscroll>,
14743 window: &mut Window,
14744 cx: &mut Context<Editor>,
14745 ) {
14746 self.unfold_ranges(
14747 std::slice::from_ref(&range),
14748 false,
14749 auto_scroll.is_some(),
14750 cx,
14751 );
14752 let effects = if let Some(scroll) = auto_scroll {
14753 SelectionEffects::scroll(scroll)
14754 } else {
14755 SelectionEffects::no_scroll()
14756 };
14757 self.change_selections(effects, window, cx, |s| {
14758 if replace_newest {
14759 s.delete(s.newest_anchor().id);
14760 }
14761 if reversed {
14762 s.insert_range(range.end..range.start);
14763 } else {
14764 s.insert_range(range);
14765 }
14766 });
14767 }
14768
14769 pub fn select_next_match_internal(
14770 &mut self,
14771 display_map: &DisplaySnapshot,
14772 replace_newest: bool,
14773 autoscroll: Option<Autoscroll>,
14774 window: &mut Window,
14775 cx: &mut Context<Self>,
14776 ) -> Result<()> {
14777 let buffer = display_map.buffer_snapshot();
14778 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14779 if let Some(mut select_next_state) = self.select_next_state.take() {
14780 let query = &select_next_state.query;
14781 if !select_next_state.done {
14782 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14783 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14784 let mut next_selected_range = None;
14785
14786 let bytes_after_last_selection =
14787 buffer.bytes_in_range(last_selection.end..buffer.len());
14788 let bytes_before_first_selection =
14789 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14790 let query_matches = query
14791 .stream_find_iter(bytes_after_last_selection)
14792 .map(|result| (last_selection.end, result))
14793 .chain(
14794 query
14795 .stream_find_iter(bytes_before_first_selection)
14796 .map(|result| (MultiBufferOffset(0), result)),
14797 );
14798
14799 for (start_offset, query_match) in query_matches {
14800 let query_match = query_match.unwrap(); // can only fail due to I/O
14801 let offset_range =
14802 start_offset + query_match.start()..start_offset + query_match.end();
14803
14804 if !select_next_state.wordwise
14805 || (!buffer.is_inside_word(offset_range.start, None)
14806 && !buffer.is_inside_word(offset_range.end, None))
14807 {
14808 let idx = selections
14809 .partition_point(|selection| selection.end <= offset_range.start);
14810 let overlaps = selections
14811 .get(idx)
14812 .map_or(false, |selection| selection.start < offset_range.end);
14813
14814 if !overlaps {
14815 next_selected_range = Some(offset_range);
14816 break;
14817 }
14818 }
14819 }
14820
14821 if let Some(next_selected_range) = next_selected_range {
14822 self.select_match_ranges(
14823 next_selected_range,
14824 last_selection.reversed,
14825 replace_newest,
14826 autoscroll,
14827 window,
14828 cx,
14829 );
14830 } else {
14831 select_next_state.done = true;
14832 }
14833 }
14834
14835 self.select_next_state = Some(select_next_state);
14836 } else {
14837 let mut only_carets = true;
14838 let mut same_text_selected = true;
14839 let mut selected_text = None;
14840
14841 let mut selections_iter = selections.iter().peekable();
14842 while let Some(selection) = selections_iter.next() {
14843 if selection.start != selection.end {
14844 only_carets = false;
14845 }
14846
14847 if same_text_selected {
14848 if selected_text.is_none() {
14849 selected_text =
14850 Some(buffer.text_for_range(selection.range()).collect::<String>());
14851 }
14852
14853 if let Some(next_selection) = selections_iter.peek() {
14854 if next_selection.len() == selection.len() {
14855 let next_selected_text = buffer
14856 .text_for_range(next_selection.range())
14857 .collect::<String>();
14858 if Some(next_selected_text) != selected_text {
14859 same_text_selected = false;
14860 selected_text = None;
14861 }
14862 } else {
14863 same_text_selected = false;
14864 selected_text = None;
14865 }
14866 }
14867 }
14868 }
14869
14870 if only_carets {
14871 for selection in &mut selections {
14872 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14873 selection.start = word_range.start;
14874 selection.end = word_range.end;
14875 selection.goal = SelectionGoal::None;
14876 selection.reversed = false;
14877 self.select_match_ranges(
14878 selection.start..selection.end,
14879 selection.reversed,
14880 replace_newest,
14881 autoscroll,
14882 window,
14883 cx,
14884 );
14885 }
14886
14887 if selections.len() == 1 {
14888 let selection = selections
14889 .last()
14890 .expect("ensured that there's only one selection");
14891 let query = buffer
14892 .text_for_range(selection.start..selection.end)
14893 .collect::<String>();
14894 let is_empty = query.is_empty();
14895 let select_state = SelectNextState {
14896 query: self.build_query(&[query], cx)?,
14897 wordwise: true,
14898 done: is_empty,
14899 };
14900 self.select_next_state = Some(select_state);
14901 } else {
14902 self.select_next_state = None;
14903 }
14904 } else if let Some(selected_text) = selected_text {
14905 self.select_next_state = Some(SelectNextState {
14906 query: self.build_query(&[selected_text], cx)?,
14907 wordwise: false,
14908 done: false,
14909 });
14910 self.select_next_match_internal(
14911 display_map,
14912 replace_newest,
14913 autoscroll,
14914 window,
14915 cx,
14916 )?;
14917 }
14918 }
14919 Ok(())
14920 }
14921
14922 pub fn select_all_matches(
14923 &mut self,
14924 _action: &SelectAllMatches,
14925 window: &mut Window,
14926 cx: &mut Context<Self>,
14927 ) -> Result<()> {
14928 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14929
14930 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14931
14932 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14933 let Some(select_next_state) = self.select_next_state.as_mut() else {
14934 return Ok(());
14935 };
14936 if select_next_state.done {
14937 return Ok(());
14938 }
14939
14940 let mut new_selections = Vec::new();
14941
14942 let reversed = self
14943 .selections
14944 .oldest::<MultiBufferOffset>(&display_map)
14945 .reversed;
14946 let buffer = display_map.buffer_snapshot();
14947 let query_matches = select_next_state
14948 .query
14949 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14950
14951 for query_match in query_matches.into_iter() {
14952 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14953 let offset_range = if reversed {
14954 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14955 } else {
14956 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14957 };
14958
14959 if !select_next_state.wordwise
14960 || (!buffer.is_inside_word(offset_range.start, None)
14961 && !buffer.is_inside_word(offset_range.end, None))
14962 {
14963 new_selections.push(offset_range.start..offset_range.end);
14964 }
14965 }
14966
14967 select_next_state.done = true;
14968
14969 if new_selections.is_empty() {
14970 log::error!("bug: new_selections is empty in select_all_matches");
14971 return Ok(());
14972 }
14973
14974 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14975 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14976 selections.select_ranges(new_selections)
14977 });
14978
14979 Ok(())
14980 }
14981
14982 pub fn select_next(
14983 &mut self,
14984 action: &SelectNext,
14985 window: &mut Window,
14986 cx: &mut Context<Self>,
14987 ) -> Result<()> {
14988 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14989 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14990 self.select_next_match_internal(
14991 &display_map,
14992 action.replace_newest,
14993 Some(Autoscroll::newest()),
14994 window,
14995 cx,
14996 )?;
14997 Ok(())
14998 }
14999
15000 pub fn select_previous(
15001 &mut self,
15002 action: &SelectPrevious,
15003 window: &mut Window,
15004 cx: &mut Context<Self>,
15005 ) -> Result<()> {
15006 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15007 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15008 let buffer = display_map.buffer_snapshot();
15009 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15010 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15011 let query = &select_prev_state.query;
15012 if !select_prev_state.done {
15013 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15014 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15015 let mut next_selected_range = None;
15016 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15017 let bytes_before_last_selection =
15018 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15019 let bytes_after_first_selection =
15020 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15021 let query_matches = query
15022 .stream_find_iter(bytes_before_last_selection)
15023 .map(|result| (last_selection.start, result))
15024 .chain(
15025 query
15026 .stream_find_iter(bytes_after_first_selection)
15027 .map(|result| (buffer.len(), result)),
15028 );
15029 for (end_offset, query_match) in query_matches {
15030 let query_match = query_match.unwrap(); // can only fail due to I/O
15031 let offset_range =
15032 end_offset - query_match.end()..end_offset - query_match.start();
15033
15034 if !select_prev_state.wordwise
15035 || (!buffer.is_inside_word(offset_range.start, None)
15036 && !buffer.is_inside_word(offset_range.end, None))
15037 {
15038 next_selected_range = Some(offset_range);
15039 break;
15040 }
15041 }
15042
15043 if let Some(next_selected_range) = next_selected_range {
15044 self.select_match_ranges(
15045 next_selected_range,
15046 last_selection.reversed,
15047 action.replace_newest,
15048 Some(Autoscroll::newest()),
15049 window,
15050 cx,
15051 );
15052 } else {
15053 select_prev_state.done = true;
15054 }
15055 }
15056
15057 self.select_prev_state = Some(select_prev_state);
15058 } else {
15059 let mut only_carets = true;
15060 let mut same_text_selected = true;
15061 let mut selected_text = None;
15062
15063 let mut selections_iter = selections.iter().peekable();
15064 while let Some(selection) = selections_iter.next() {
15065 if selection.start != selection.end {
15066 only_carets = false;
15067 }
15068
15069 if same_text_selected {
15070 if selected_text.is_none() {
15071 selected_text =
15072 Some(buffer.text_for_range(selection.range()).collect::<String>());
15073 }
15074
15075 if let Some(next_selection) = selections_iter.peek() {
15076 if next_selection.len() == selection.len() {
15077 let next_selected_text = buffer
15078 .text_for_range(next_selection.range())
15079 .collect::<String>();
15080 if Some(next_selected_text) != selected_text {
15081 same_text_selected = false;
15082 selected_text = None;
15083 }
15084 } else {
15085 same_text_selected = false;
15086 selected_text = None;
15087 }
15088 }
15089 }
15090 }
15091
15092 if only_carets {
15093 for selection in &mut selections {
15094 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15095 selection.start = word_range.start;
15096 selection.end = word_range.end;
15097 selection.goal = SelectionGoal::None;
15098 selection.reversed = false;
15099 self.select_match_ranges(
15100 selection.start..selection.end,
15101 selection.reversed,
15102 action.replace_newest,
15103 Some(Autoscroll::newest()),
15104 window,
15105 cx,
15106 );
15107 }
15108 if selections.len() == 1 {
15109 let selection = selections
15110 .last()
15111 .expect("ensured that there's only one selection");
15112 let query = buffer
15113 .text_for_range(selection.start..selection.end)
15114 .collect::<String>();
15115 let is_empty = query.is_empty();
15116 let select_state = SelectNextState {
15117 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15118 wordwise: true,
15119 done: is_empty,
15120 };
15121 self.select_prev_state = Some(select_state);
15122 } else {
15123 self.select_prev_state = None;
15124 }
15125 } else if let Some(selected_text) = selected_text {
15126 self.select_prev_state = Some(SelectNextState {
15127 query: self
15128 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15129 wordwise: false,
15130 done: false,
15131 });
15132 self.select_previous(action, window, cx)?;
15133 }
15134 }
15135 Ok(())
15136 }
15137
15138 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15139 /// setting the case sensitivity based on the global
15140 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15141 /// editor's settings.
15142 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15143 where
15144 I: IntoIterator<Item = P>,
15145 P: AsRef<[u8]>,
15146 {
15147 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15148 || EditorSettings::get_global(cx).search.case_sensitive,
15149 |value| value,
15150 );
15151
15152 let mut builder = AhoCorasickBuilder::new();
15153 builder.ascii_case_insensitive(!case_sensitive);
15154 builder.build(patterns)
15155 }
15156
15157 pub fn find_next_match(
15158 &mut self,
15159 _: &FindNextMatch,
15160 window: &mut Window,
15161 cx: &mut Context<Self>,
15162 ) -> Result<()> {
15163 let selections = self.selections.disjoint_anchors_arc();
15164 match selections.first() {
15165 Some(first) if selections.len() >= 2 => {
15166 self.change_selections(Default::default(), window, cx, |s| {
15167 s.select_ranges([first.range()]);
15168 });
15169 }
15170 _ => self.select_next(
15171 &SelectNext {
15172 replace_newest: true,
15173 },
15174 window,
15175 cx,
15176 )?,
15177 }
15178 Ok(())
15179 }
15180
15181 pub fn find_previous_match(
15182 &mut self,
15183 _: &FindPreviousMatch,
15184 window: &mut Window,
15185 cx: &mut Context<Self>,
15186 ) -> Result<()> {
15187 let selections = self.selections.disjoint_anchors_arc();
15188 match selections.last() {
15189 Some(last) if selections.len() >= 2 => {
15190 self.change_selections(Default::default(), window, cx, |s| {
15191 s.select_ranges([last.range()]);
15192 });
15193 }
15194 _ => self.select_previous(
15195 &SelectPrevious {
15196 replace_newest: true,
15197 },
15198 window,
15199 cx,
15200 )?,
15201 }
15202 Ok(())
15203 }
15204
15205 pub fn toggle_comments(
15206 &mut self,
15207 action: &ToggleComments,
15208 window: &mut Window,
15209 cx: &mut Context<Self>,
15210 ) {
15211 if self.read_only(cx) {
15212 return;
15213 }
15214 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15215 let text_layout_details = &self.text_layout_details(window);
15216 self.transact(window, cx, |this, window, cx| {
15217 let mut selections = this
15218 .selections
15219 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15220 let mut edits = Vec::new();
15221 let mut selection_edit_ranges = Vec::new();
15222 let mut last_toggled_row = None;
15223 let snapshot = this.buffer.read(cx).read(cx);
15224 let empty_str: Arc<str> = Arc::default();
15225 let mut suffixes_inserted = Vec::new();
15226 let ignore_indent = action.ignore_indent;
15227
15228 fn comment_prefix_range(
15229 snapshot: &MultiBufferSnapshot,
15230 row: MultiBufferRow,
15231 comment_prefix: &str,
15232 comment_prefix_whitespace: &str,
15233 ignore_indent: bool,
15234 ) -> Range<Point> {
15235 let indent_size = if ignore_indent {
15236 0
15237 } else {
15238 snapshot.indent_size_for_line(row).len
15239 };
15240
15241 let start = Point::new(row.0, indent_size);
15242
15243 let mut line_bytes = snapshot
15244 .bytes_in_range(start..snapshot.max_point())
15245 .flatten()
15246 .copied();
15247
15248 // If this line currently begins with the line comment prefix, then record
15249 // the range containing the prefix.
15250 if line_bytes
15251 .by_ref()
15252 .take(comment_prefix.len())
15253 .eq(comment_prefix.bytes())
15254 {
15255 // Include any whitespace that matches the comment prefix.
15256 let matching_whitespace_len = line_bytes
15257 .zip(comment_prefix_whitespace.bytes())
15258 .take_while(|(a, b)| a == b)
15259 .count() as u32;
15260 let end = Point::new(
15261 start.row,
15262 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15263 );
15264 start..end
15265 } else {
15266 start..start
15267 }
15268 }
15269
15270 fn comment_suffix_range(
15271 snapshot: &MultiBufferSnapshot,
15272 row: MultiBufferRow,
15273 comment_suffix: &str,
15274 comment_suffix_has_leading_space: bool,
15275 ) -> Range<Point> {
15276 let end = Point::new(row.0, snapshot.line_len(row));
15277 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15278
15279 let mut line_end_bytes = snapshot
15280 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15281 .flatten()
15282 .copied();
15283
15284 let leading_space_len = if suffix_start_column > 0
15285 && line_end_bytes.next() == Some(b' ')
15286 && comment_suffix_has_leading_space
15287 {
15288 1
15289 } else {
15290 0
15291 };
15292
15293 // If this line currently begins with the line comment prefix, then record
15294 // the range containing the prefix.
15295 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15296 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15297 start..end
15298 } else {
15299 end..end
15300 }
15301 }
15302
15303 // TODO: Handle selections that cross excerpts
15304 for selection in &mut selections {
15305 let start_column = snapshot
15306 .indent_size_for_line(MultiBufferRow(selection.start.row))
15307 .len;
15308 let language = if let Some(language) =
15309 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15310 {
15311 language
15312 } else {
15313 continue;
15314 };
15315
15316 selection_edit_ranges.clear();
15317
15318 // If multiple selections contain a given row, avoid processing that
15319 // row more than once.
15320 let mut start_row = MultiBufferRow(selection.start.row);
15321 if last_toggled_row == Some(start_row) {
15322 start_row = start_row.next_row();
15323 }
15324 let end_row =
15325 if selection.end.row > selection.start.row && selection.end.column == 0 {
15326 MultiBufferRow(selection.end.row - 1)
15327 } else {
15328 MultiBufferRow(selection.end.row)
15329 };
15330 last_toggled_row = Some(end_row);
15331
15332 if start_row > end_row {
15333 continue;
15334 }
15335
15336 // If the language has line comments, toggle those.
15337 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15338
15339 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15340 if ignore_indent {
15341 full_comment_prefixes = full_comment_prefixes
15342 .into_iter()
15343 .map(|s| Arc::from(s.trim_end()))
15344 .collect();
15345 }
15346
15347 if !full_comment_prefixes.is_empty() {
15348 let first_prefix = full_comment_prefixes
15349 .first()
15350 .expect("prefixes is non-empty");
15351 let prefix_trimmed_lengths = full_comment_prefixes
15352 .iter()
15353 .map(|p| p.trim_end_matches(' ').len())
15354 .collect::<SmallVec<[usize; 4]>>();
15355
15356 let mut all_selection_lines_are_comments = true;
15357
15358 for row in start_row.0..=end_row.0 {
15359 let row = MultiBufferRow(row);
15360 if start_row < end_row && snapshot.is_line_blank(row) {
15361 continue;
15362 }
15363
15364 let prefix_range = full_comment_prefixes
15365 .iter()
15366 .zip(prefix_trimmed_lengths.iter().copied())
15367 .map(|(prefix, trimmed_prefix_len)| {
15368 comment_prefix_range(
15369 snapshot.deref(),
15370 row,
15371 &prefix[..trimmed_prefix_len],
15372 &prefix[trimmed_prefix_len..],
15373 ignore_indent,
15374 )
15375 })
15376 .max_by_key(|range| range.end.column - range.start.column)
15377 .expect("prefixes is non-empty");
15378
15379 if prefix_range.is_empty() {
15380 all_selection_lines_are_comments = false;
15381 }
15382
15383 selection_edit_ranges.push(prefix_range);
15384 }
15385
15386 if all_selection_lines_are_comments {
15387 edits.extend(
15388 selection_edit_ranges
15389 .iter()
15390 .cloned()
15391 .map(|range| (range, empty_str.clone())),
15392 );
15393 } else {
15394 let min_column = selection_edit_ranges
15395 .iter()
15396 .map(|range| range.start.column)
15397 .min()
15398 .unwrap_or(0);
15399 edits.extend(selection_edit_ranges.iter().map(|range| {
15400 let position = Point::new(range.start.row, min_column);
15401 (position..position, first_prefix.clone())
15402 }));
15403 }
15404 } else if let Some(BlockCommentConfig {
15405 start: full_comment_prefix,
15406 end: comment_suffix,
15407 ..
15408 }) = language.block_comment()
15409 {
15410 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15411 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15412 let prefix_range = comment_prefix_range(
15413 snapshot.deref(),
15414 start_row,
15415 comment_prefix,
15416 comment_prefix_whitespace,
15417 ignore_indent,
15418 );
15419 let suffix_range = comment_suffix_range(
15420 snapshot.deref(),
15421 end_row,
15422 comment_suffix.trim_start_matches(' '),
15423 comment_suffix.starts_with(' '),
15424 );
15425
15426 if prefix_range.is_empty() || suffix_range.is_empty() {
15427 edits.push((
15428 prefix_range.start..prefix_range.start,
15429 full_comment_prefix.clone(),
15430 ));
15431 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15432 suffixes_inserted.push((end_row, comment_suffix.len()));
15433 } else {
15434 edits.push((prefix_range, empty_str.clone()));
15435 edits.push((suffix_range, empty_str.clone()));
15436 }
15437 } else {
15438 continue;
15439 }
15440 }
15441
15442 drop(snapshot);
15443 this.buffer.update(cx, |buffer, cx| {
15444 buffer.edit(edits, None, cx);
15445 });
15446
15447 // Adjust selections so that they end before any comment suffixes that
15448 // were inserted.
15449 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15450 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15451 let snapshot = this.buffer.read(cx).read(cx);
15452 for selection in &mut selections {
15453 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15454 match row.cmp(&MultiBufferRow(selection.end.row)) {
15455 Ordering::Less => {
15456 suffixes_inserted.next();
15457 continue;
15458 }
15459 Ordering::Greater => break,
15460 Ordering::Equal => {
15461 if selection.end.column == snapshot.line_len(row) {
15462 if selection.is_empty() {
15463 selection.start.column -= suffix_len as u32;
15464 }
15465 selection.end.column -= suffix_len as u32;
15466 }
15467 break;
15468 }
15469 }
15470 }
15471 }
15472
15473 drop(snapshot);
15474 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15475
15476 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15477 let selections_on_single_row = selections.windows(2).all(|selections| {
15478 selections[0].start.row == selections[1].start.row
15479 && selections[0].end.row == selections[1].end.row
15480 && selections[0].start.row == selections[0].end.row
15481 });
15482 let selections_selecting = selections
15483 .iter()
15484 .any(|selection| selection.start != selection.end);
15485 let advance_downwards = action.advance_downwards
15486 && selections_on_single_row
15487 && !selections_selecting
15488 && !matches!(this.mode, EditorMode::SingleLine);
15489
15490 if advance_downwards {
15491 let snapshot = this.buffer.read(cx).snapshot(cx);
15492
15493 this.change_selections(Default::default(), window, cx, |s| {
15494 s.move_cursors_with(|display_snapshot, display_point, _| {
15495 let mut point = display_point.to_point(display_snapshot);
15496 point.row += 1;
15497 point = snapshot.clip_point(point, Bias::Left);
15498 let display_point = point.to_display_point(display_snapshot);
15499 let goal = SelectionGoal::HorizontalPosition(
15500 display_snapshot
15501 .x_for_display_point(display_point, text_layout_details)
15502 .into(),
15503 );
15504 (display_point, goal)
15505 })
15506 });
15507 }
15508 });
15509 }
15510
15511 pub fn select_enclosing_symbol(
15512 &mut self,
15513 _: &SelectEnclosingSymbol,
15514 window: &mut Window,
15515 cx: &mut Context<Self>,
15516 ) {
15517 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15518
15519 let buffer = self.buffer.read(cx).snapshot(cx);
15520 let old_selections = self
15521 .selections
15522 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15523 .into_boxed_slice();
15524
15525 fn update_selection(
15526 selection: &Selection<MultiBufferOffset>,
15527 buffer_snap: &MultiBufferSnapshot,
15528 ) -> Option<Selection<MultiBufferOffset>> {
15529 let cursor = selection.head();
15530 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15531 for symbol in symbols.iter().rev() {
15532 let start = symbol.range.start.to_offset(buffer_snap);
15533 let end = symbol.range.end.to_offset(buffer_snap);
15534 let new_range = start..end;
15535 if start < selection.start || end > selection.end {
15536 return Some(Selection {
15537 id: selection.id,
15538 start: new_range.start,
15539 end: new_range.end,
15540 goal: SelectionGoal::None,
15541 reversed: selection.reversed,
15542 });
15543 }
15544 }
15545 None
15546 }
15547
15548 let mut selected_larger_symbol = false;
15549 let new_selections = old_selections
15550 .iter()
15551 .map(|selection| match update_selection(selection, &buffer) {
15552 Some(new_selection) => {
15553 if new_selection.range() != selection.range() {
15554 selected_larger_symbol = true;
15555 }
15556 new_selection
15557 }
15558 None => selection.clone(),
15559 })
15560 .collect::<Vec<_>>();
15561
15562 if selected_larger_symbol {
15563 self.change_selections(Default::default(), window, cx, |s| {
15564 s.select(new_selections);
15565 });
15566 }
15567 }
15568
15569 pub fn select_larger_syntax_node(
15570 &mut self,
15571 _: &SelectLargerSyntaxNode,
15572 window: &mut Window,
15573 cx: &mut Context<Self>,
15574 ) {
15575 let Some(visible_row_count) = self.visible_row_count() else {
15576 return;
15577 };
15578 let old_selections: Box<[_]> = self
15579 .selections
15580 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15581 .into();
15582 if old_selections.is_empty() {
15583 return;
15584 }
15585
15586 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15587
15588 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15589 let buffer = self.buffer.read(cx).snapshot(cx);
15590
15591 let mut selected_larger_node = false;
15592 let mut new_selections = old_selections
15593 .iter()
15594 .map(|selection| {
15595 let old_range = selection.start..selection.end;
15596
15597 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15598 // manually select word at selection
15599 if ["string_content", "inline"].contains(&node.kind()) {
15600 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15601 // ignore if word is already selected
15602 if !word_range.is_empty() && old_range != word_range {
15603 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15604 // only select word if start and end point belongs to same word
15605 if word_range == last_word_range {
15606 selected_larger_node = true;
15607 return Selection {
15608 id: selection.id,
15609 start: word_range.start,
15610 end: word_range.end,
15611 goal: SelectionGoal::None,
15612 reversed: selection.reversed,
15613 };
15614 }
15615 }
15616 }
15617 }
15618
15619 let mut new_range = old_range.clone();
15620 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15621 new_range = range;
15622 if !node.is_named() {
15623 continue;
15624 }
15625 if !display_map.intersects_fold(new_range.start)
15626 && !display_map.intersects_fold(new_range.end)
15627 {
15628 break;
15629 }
15630 }
15631
15632 selected_larger_node |= new_range != old_range;
15633 Selection {
15634 id: selection.id,
15635 start: new_range.start,
15636 end: new_range.end,
15637 goal: SelectionGoal::None,
15638 reversed: selection.reversed,
15639 }
15640 })
15641 .collect::<Vec<_>>();
15642
15643 if !selected_larger_node {
15644 return; // don't put this call in the history
15645 }
15646
15647 // scroll based on transformation done to the last selection created by the user
15648 let (last_old, last_new) = old_selections
15649 .last()
15650 .zip(new_selections.last().cloned())
15651 .expect("old_selections isn't empty");
15652
15653 // revert selection
15654 let is_selection_reversed = {
15655 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15656 new_selections.last_mut().expect("checked above").reversed =
15657 should_newest_selection_be_reversed;
15658 should_newest_selection_be_reversed
15659 };
15660
15661 if selected_larger_node {
15662 self.select_syntax_node_history.disable_clearing = true;
15663 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15664 s.select(new_selections.clone());
15665 });
15666 self.select_syntax_node_history.disable_clearing = false;
15667 }
15668
15669 let start_row = last_new.start.to_display_point(&display_map).row().0;
15670 let end_row = last_new.end.to_display_point(&display_map).row().0;
15671 let selection_height = end_row - start_row + 1;
15672 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15673
15674 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15675 let scroll_behavior = if fits_on_the_screen {
15676 self.request_autoscroll(Autoscroll::fit(), cx);
15677 SelectSyntaxNodeScrollBehavior::FitSelection
15678 } else if is_selection_reversed {
15679 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15680 SelectSyntaxNodeScrollBehavior::CursorTop
15681 } else {
15682 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15683 SelectSyntaxNodeScrollBehavior::CursorBottom
15684 };
15685
15686 self.select_syntax_node_history.push((
15687 old_selections,
15688 scroll_behavior,
15689 is_selection_reversed,
15690 ));
15691 }
15692
15693 pub fn select_smaller_syntax_node(
15694 &mut self,
15695 _: &SelectSmallerSyntaxNode,
15696 window: &mut Window,
15697 cx: &mut Context<Self>,
15698 ) {
15699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15700
15701 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15702 self.select_syntax_node_history.pop()
15703 {
15704 if let Some(selection) = selections.last_mut() {
15705 selection.reversed = is_selection_reversed;
15706 }
15707
15708 self.select_syntax_node_history.disable_clearing = true;
15709 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15710 s.select(selections.to_vec());
15711 });
15712 self.select_syntax_node_history.disable_clearing = false;
15713
15714 match scroll_behavior {
15715 SelectSyntaxNodeScrollBehavior::CursorTop => {
15716 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15717 }
15718 SelectSyntaxNodeScrollBehavior::FitSelection => {
15719 self.request_autoscroll(Autoscroll::fit(), cx);
15720 }
15721 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15722 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15723 }
15724 }
15725 }
15726 }
15727
15728 pub fn unwrap_syntax_node(
15729 &mut self,
15730 _: &UnwrapSyntaxNode,
15731 window: &mut Window,
15732 cx: &mut Context<Self>,
15733 ) {
15734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15735
15736 let buffer = self.buffer.read(cx).snapshot(cx);
15737 let selections = self
15738 .selections
15739 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15740 .into_iter()
15741 // subtracting the offset requires sorting
15742 .sorted_by_key(|i| i.start);
15743
15744 let full_edits = selections
15745 .into_iter()
15746 .filter_map(|selection| {
15747 let child = if selection.is_empty()
15748 && let Some((_, ancestor_range)) =
15749 buffer.syntax_ancestor(selection.start..selection.end)
15750 {
15751 ancestor_range
15752 } else {
15753 selection.range()
15754 };
15755
15756 let mut parent = child.clone();
15757 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15758 parent = ancestor_range;
15759 if parent.start < child.start || parent.end > child.end {
15760 break;
15761 }
15762 }
15763
15764 if parent == child {
15765 return None;
15766 }
15767 let text = buffer.text_for_range(child).collect::<String>();
15768 Some((selection.id, parent, text))
15769 })
15770 .collect::<Vec<_>>();
15771 if full_edits.is_empty() {
15772 return;
15773 }
15774
15775 self.transact(window, cx, |this, window, cx| {
15776 this.buffer.update(cx, |buffer, cx| {
15777 buffer.edit(
15778 full_edits
15779 .iter()
15780 .map(|(_, p, t)| (p.clone(), t.clone()))
15781 .collect::<Vec<_>>(),
15782 None,
15783 cx,
15784 );
15785 });
15786 this.change_selections(Default::default(), window, cx, |s| {
15787 let mut offset = 0;
15788 let mut selections = vec![];
15789 for (id, parent, text) in full_edits {
15790 let start = parent.start - offset;
15791 offset += (parent.end - parent.start) - text.len();
15792 selections.push(Selection {
15793 id,
15794 start,
15795 end: start + text.len(),
15796 reversed: false,
15797 goal: Default::default(),
15798 });
15799 }
15800 s.select(selections);
15801 });
15802 });
15803 }
15804
15805 pub fn select_next_syntax_node(
15806 &mut self,
15807 _: &SelectNextSyntaxNode,
15808 window: &mut Window,
15809 cx: &mut Context<Self>,
15810 ) {
15811 let old_selections: Box<[_]> = self
15812 .selections
15813 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15814 .into();
15815 if old_selections.is_empty() {
15816 return;
15817 }
15818
15819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15820
15821 let buffer = self.buffer.read(cx).snapshot(cx);
15822 let mut selected_sibling = false;
15823
15824 let new_selections = old_selections
15825 .iter()
15826 .map(|selection| {
15827 let old_range = selection.start..selection.end;
15828
15829 let old_range =
15830 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15831 let excerpt = buffer.excerpt_containing(old_range.clone());
15832
15833 if let Some(mut excerpt) = excerpt
15834 && let Some(node) = excerpt
15835 .buffer()
15836 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15837 {
15838 let new_range = excerpt.map_range_from_buffer(
15839 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15840 );
15841 selected_sibling = true;
15842 Selection {
15843 id: selection.id,
15844 start: new_range.start,
15845 end: new_range.end,
15846 goal: SelectionGoal::None,
15847 reversed: selection.reversed,
15848 }
15849 } else {
15850 selection.clone()
15851 }
15852 })
15853 .collect::<Vec<_>>();
15854
15855 if selected_sibling {
15856 self.change_selections(
15857 SelectionEffects::scroll(Autoscroll::fit()),
15858 window,
15859 cx,
15860 |s| {
15861 s.select(new_selections);
15862 },
15863 );
15864 }
15865 }
15866
15867 pub fn select_prev_syntax_node(
15868 &mut self,
15869 _: &SelectPreviousSyntaxNode,
15870 window: &mut Window,
15871 cx: &mut Context<Self>,
15872 ) {
15873 let old_selections: Box<[_]> = self
15874 .selections
15875 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15876 .into();
15877 if old_selections.is_empty() {
15878 return;
15879 }
15880
15881 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15882
15883 let buffer = self.buffer.read(cx).snapshot(cx);
15884 let mut selected_sibling = false;
15885
15886 let new_selections = old_selections
15887 .iter()
15888 .map(|selection| {
15889 let old_range = selection.start..selection.end;
15890 let old_range =
15891 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15892 let excerpt = buffer.excerpt_containing(old_range.clone());
15893
15894 if let Some(mut excerpt) = excerpt
15895 && let Some(node) = excerpt
15896 .buffer()
15897 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15898 {
15899 let new_range = excerpt.map_range_from_buffer(
15900 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15901 );
15902 selected_sibling = true;
15903 Selection {
15904 id: selection.id,
15905 start: new_range.start,
15906 end: new_range.end,
15907 goal: SelectionGoal::None,
15908 reversed: selection.reversed,
15909 }
15910 } else {
15911 selection.clone()
15912 }
15913 })
15914 .collect::<Vec<_>>();
15915
15916 if selected_sibling {
15917 self.change_selections(
15918 SelectionEffects::scroll(Autoscroll::fit()),
15919 window,
15920 cx,
15921 |s| {
15922 s.select(new_selections);
15923 },
15924 );
15925 }
15926 }
15927
15928 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15929 if !EditorSettings::get_global(cx).gutter.runnables {
15930 self.clear_tasks();
15931 return Task::ready(());
15932 }
15933 let project = self.project().map(Entity::downgrade);
15934 let task_sources = self.lsp_task_sources(cx);
15935 let multi_buffer = self.buffer.downgrade();
15936 cx.spawn_in(window, async move |editor, cx| {
15937 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15938 let Some(project) = project.and_then(|p| p.upgrade()) else {
15939 return;
15940 };
15941 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15942 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15943 }) else {
15944 return;
15945 };
15946
15947 let hide_runnables = project
15948 .update(cx, |project, _| project.is_via_collab())
15949 .unwrap_or(true);
15950 if hide_runnables {
15951 return;
15952 }
15953 let new_rows =
15954 cx.background_spawn({
15955 let snapshot = display_snapshot.clone();
15956 async move {
15957 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15958 }
15959 })
15960 .await;
15961 let Ok(lsp_tasks) =
15962 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15963 else {
15964 return;
15965 };
15966 let lsp_tasks = lsp_tasks.await;
15967
15968 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15969 lsp_tasks
15970 .into_iter()
15971 .flat_map(|(kind, tasks)| {
15972 tasks.into_iter().filter_map(move |(location, task)| {
15973 Some((kind.clone(), location?, task))
15974 })
15975 })
15976 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15977 let buffer = location.target.buffer;
15978 let buffer_snapshot = buffer.read(cx).snapshot();
15979 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15980 |(excerpt_id, snapshot, _)| {
15981 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15982 display_snapshot
15983 .buffer_snapshot()
15984 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15985 } else {
15986 None
15987 }
15988 },
15989 );
15990 if let Some(offset) = offset {
15991 let task_buffer_range =
15992 location.target.range.to_point(&buffer_snapshot);
15993 let context_buffer_range =
15994 task_buffer_range.to_offset(&buffer_snapshot);
15995 let context_range = BufferOffset(context_buffer_range.start)
15996 ..BufferOffset(context_buffer_range.end);
15997
15998 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15999 .or_insert_with(|| RunnableTasks {
16000 templates: Vec::new(),
16001 offset,
16002 column: task_buffer_range.start.column,
16003 extra_variables: HashMap::default(),
16004 context_range,
16005 })
16006 .templates
16007 .push((kind, task.original_task().clone()));
16008 }
16009
16010 acc
16011 })
16012 }) else {
16013 return;
16014 };
16015
16016 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16017 buffer.language_settings(cx).tasks.prefer_lsp
16018 }) else {
16019 return;
16020 };
16021
16022 let rows = Self::runnable_rows(
16023 project,
16024 display_snapshot,
16025 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16026 new_rows,
16027 cx.clone(),
16028 )
16029 .await;
16030 editor
16031 .update(cx, |editor, _| {
16032 editor.clear_tasks();
16033 for (key, mut value) in rows {
16034 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16035 value.templates.extend(lsp_tasks.templates);
16036 }
16037
16038 editor.insert_tasks(key, value);
16039 }
16040 for (key, value) in lsp_tasks_by_rows {
16041 editor.insert_tasks(key, value);
16042 }
16043 })
16044 .ok();
16045 })
16046 }
16047 fn fetch_runnable_ranges(
16048 snapshot: &DisplaySnapshot,
16049 range: Range<Anchor>,
16050 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16051 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16052 }
16053
16054 fn runnable_rows(
16055 project: Entity<Project>,
16056 snapshot: DisplaySnapshot,
16057 prefer_lsp: bool,
16058 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16059 cx: AsyncWindowContext,
16060 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16061 cx.spawn(async move |cx| {
16062 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16063 for (run_range, mut runnable) in runnable_ranges {
16064 let Some(tasks) = cx
16065 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16066 .ok()
16067 else {
16068 continue;
16069 };
16070 let mut tasks = tasks.await;
16071
16072 if prefer_lsp {
16073 tasks.retain(|(task_kind, _)| {
16074 !matches!(task_kind, TaskSourceKind::Language { .. })
16075 });
16076 }
16077 if tasks.is_empty() {
16078 continue;
16079 }
16080
16081 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16082 let Some(row) = snapshot
16083 .buffer_snapshot()
16084 .buffer_line_for_row(MultiBufferRow(point.row))
16085 .map(|(_, range)| range.start.row)
16086 else {
16087 continue;
16088 };
16089
16090 let context_range =
16091 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16092 runnable_rows.push((
16093 (runnable.buffer_id, row),
16094 RunnableTasks {
16095 templates: tasks,
16096 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16097 context_range,
16098 column: point.column,
16099 extra_variables: runnable.extra_captures,
16100 },
16101 ));
16102 }
16103 runnable_rows
16104 })
16105 }
16106
16107 fn templates_with_tags(
16108 project: &Entity<Project>,
16109 runnable: &mut Runnable,
16110 cx: &mut App,
16111 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16112 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16113 let (worktree_id, file) = project
16114 .buffer_for_id(runnable.buffer, cx)
16115 .and_then(|buffer| buffer.read(cx).file())
16116 .map(|file| (file.worktree_id(cx), file.clone()))
16117 .unzip();
16118
16119 (
16120 project.task_store().read(cx).task_inventory().cloned(),
16121 worktree_id,
16122 file,
16123 )
16124 });
16125
16126 let tags = mem::take(&mut runnable.tags);
16127 let language = runnable.language.clone();
16128 cx.spawn(async move |cx| {
16129 let mut templates_with_tags = Vec::new();
16130 if let Some(inventory) = inventory {
16131 for RunnableTag(tag) in tags {
16132 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16133 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16134 }) else {
16135 return templates_with_tags;
16136 };
16137 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16138 move |(_, template)| {
16139 template.tags.iter().any(|source_tag| source_tag == &tag)
16140 },
16141 ));
16142 }
16143 }
16144 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16145
16146 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16147 // Strongest source wins; if we have worktree tag binding, prefer that to
16148 // global and language bindings;
16149 // if we have a global binding, prefer that to language binding.
16150 let first_mismatch = templates_with_tags
16151 .iter()
16152 .position(|(tag_source, _)| tag_source != leading_tag_source);
16153 if let Some(index) = first_mismatch {
16154 templates_with_tags.truncate(index);
16155 }
16156 }
16157
16158 templates_with_tags
16159 })
16160 }
16161
16162 pub fn move_to_enclosing_bracket(
16163 &mut self,
16164 _: &MoveToEnclosingBracket,
16165 window: &mut Window,
16166 cx: &mut Context<Self>,
16167 ) {
16168 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16169 self.change_selections(Default::default(), window, cx, |s| {
16170 s.move_offsets_with(|snapshot, selection| {
16171 let Some(enclosing_bracket_ranges) =
16172 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16173 else {
16174 return;
16175 };
16176
16177 let mut best_length = usize::MAX;
16178 let mut best_inside = false;
16179 let mut best_in_bracket_range = false;
16180 let mut best_destination = None;
16181 for (open, close) in enclosing_bracket_ranges {
16182 let close = close.to_inclusive();
16183 let length = *close.end() - open.start;
16184 let inside = selection.start >= open.end && selection.end <= *close.start();
16185 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16186 || close.contains(&selection.head());
16187
16188 // If best is next to a bracket and current isn't, skip
16189 if !in_bracket_range && best_in_bracket_range {
16190 continue;
16191 }
16192
16193 // Prefer smaller lengths unless best is inside and current isn't
16194 if length > best_length && (best_inside || !inside) {
16195 continue;
16196 }
16197
16198 best_length = length;
16199 best_inside = inside;
16200 best_in_bracket_range = in_bracket_range;
16201 best_destination = Some(
16202 if close.contains(&selection.start) && close.contains(&selection.end) {
16203 if inside { open.end } else { open.start }
16204 } else if inside {
16205 *close.start()
16206 } else {
16207 *close.end()
16208 },
16209 );
16210 }
16211
16212 if let Some(destination) = best_destination {
16213 selection.collapse_to(destination, SelectionGoal::None);
16214 }
16215 })
16216 });
16217 }
16218
16219 pub fn undo_selection(
16220 &mut self,
16221 _: &UndoSelection,
16222 window: &mut Window,
16223 cx: &mut Context<Self>,
16224 ) {
16225 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16226 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16227 self.selection_history.mode = SelectionHistoryMode::Undoing;
16228 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16229 this.end_selection(window, cx);
16230 this.change_selections(
16231 SelectionEffects::scroll(Autoscroll::newest()),
16232 window,
16233 cx,
16234 |s| s.select_anchors(entry.selections.to_vec()),
16235 );
16236 });
16237 self.selection_history.mode = SelectionHistoryMode::Normal;
16238
16239 self.select_next_state = entry.select_next_state;
16240 self.select_prev_state = entry.select_prev_state;
16241 self.add_selections_state = entry.add_selections_state;
16242 }
16243 }
16244
16245 pub fn redo_selection(
16246 &mut self,
16247 _: &RedoSelection,
16248 window: &mut Window,
16249 cx: &mut Context<Self>,
16250 ) {
16251 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16252 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16253 self.selection_history.mode = SelectionHistoryMode::Redoing;
16254 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16255 this.end_selection(window, cx);
16256 this.change_selections(
16257 SelectionEffects::scroll(Autoscroll::newest()),
16258 window,
16259 cx,
16260 |s| s.select_anchors(entry.selections.to_vec()),
16261 );
16262 });
16263 self.selection_history.mode = SelectionHistoryMode::Normal;
16264
16265 self.select_next_state = entry.select_next_state;
16266 self.select_prev_state = entry.select_prev_state;
16267 self.add_selections_state = entry.add_selections_state;
16268 }
16269 }
16270
16271 pub fn expand_excerpts(
16272 &mut self,
16273 action: &ExpandExcerpts,
16274 _: &mut Window,
16275 cx: &mut Context<Self>,
16276 ) {
16277 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16278 }
16279
16280 pub fn expand_excerpts_down(
16281 &mut self,
16282 action: &ExpandExcerptsDown,
16283 _: &mut Window,
16284 cx: &mut Context<Self>,
16285 ) {
16286 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16287 }
16288
16289 pub fn expand_excerpts_up(
16290 &mut self,
16291 action: &ExpandExcerptsUp,
16292 _: &mut Window,
16293 cx: &mut Context<Self>,
16294 ) {
16295 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16296 }
16297
16298 pub fn expand_excerpts_for_direction(
16299 &mut self,
16300 lines: u32,
16301 direction: ExpandExcerptDirection,
16302
16303 cx: &mut Context<Self>,
16304 ) {
16305 let selections = self.selections.disjoint_anchors_arc();
16306
16307 let lines = if lines == 0 {
16308 EditorSettings::get_global(cx).expand_excerpt_lines
16309 } else {
16310 lines
16311 };
16312
16313 self.buffer.update(cx, |buffer, cx| {
16314 let snapshot = buffer.snapshot(cx);
16315 let mut excerpt_ids = selections
16316 .iter()
16317 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16318 .collect::<Vec<_>>();
16319 excerpt_ids.sort();
16320 excerpt_ids.dedup();
16321 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16322 })
16323 }
16324
16325 pub fn expand_excerpt(
16326 &mut self,
16327 excerpt: ExcerptId,
16328 direction: ExpandExcerptDirection,
16329 window: &mut Window,
16330 cx: &mut Context<Self>,
16331 ) {
16332 let current_scroll_position = self.scroll_position(cx);
16333 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16334 let mut scroll = None;
16335
16336 if direction == ExpandExcerptDirection::Down {
16337 let multi_buffer = self.buffer.read(cx);
16338 let snapshot = multi_buffer.snapshot(cx);
16339 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16340 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16341 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16342 {
16343 let buffer_snapshot = buffer.read(cx).snapshot();
16344 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16345 let last_row = buffer_snapshot.max_point().row;
16346 let lines_below = last_row.saturating_sub(excerpt_end_row);
16347 if lines_below >= lines_to_expand {
16348 scroll = Some(
16349 current_scroll_position
16350 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16351 );
16352 }
16353 }
16354 }
16355 if direction == ExpandExcerptDirection::Up
16356 && self
16357 .buffer
16358 .read(cx)
16359 .snapshot(cx)
16360 .excerpt_before(excerpt)
16361 .is_none()
16362 {
16363 scroll = Some(current_scroll_position);
16364 }
16365
16366 self.buffer.update(cx, |buffer, cx| {
16367 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16368 });
16369
16370 if let Some(new_scroll_position) = scroll {
16371 self.set_scroll_position(new_scroll_position, window, cx);
16372 }
16373 }
16374
16375 pub fn go_to_singleton_buffer_point(
16376 &mut self,
16377 point: Point,
16378 window: &mut Window,
16379 cx: &mut Context<Self>,
16380 ) {
16381 self.go_to_singleton_buffer_range(point..point, window, cx);
16382 }
16383
16384 pub fn go_to_singleton_buffer_range(
16385 &mut self,
16386 range: Range<Point>,
16387 window: &mut Window,
16388 cx: &mut Context<Self>,
16389 ) {
16390 let multibuffer = self.buffer().read(cx);
16391 let Some(buffer) = multibuffer.as_singleton() else {
16392 return;
16393 };
16394 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16395 return;
16396 };
16397 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16398 return;
16399 };
16400 self.change_selections(
16401 SelectionEffects::default().nav_history(true),
16402 window,
16403 cx,
16404 |s| s.select_anchor_ranges([start..end]),
16405 );
16406 }
16407
16408 pub fn go_to_diagnostic(
16409 &mut self,
16410 action: &GoToDiagnostic,
16411 window: &mut Window,
16412 cx: &mut Context<Self>,
16413 ) {
16414 if !self.diagnostics_enabled() {
16415 return;
16416 }
16417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16418 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16419 }
16420
16421 pub fn go_to_prev_diagnostic(
16422 &mut self,
16423 action: &GoToPreviousDiagnostic,
16424 window: &mut Window,
16425 cx: &mut Context<Self>,
16426 ) {
16427 if !self.diagnostics_enabled() {
16428 return;
16429 }
16430 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16431 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16432 }
16433
16434 pub fn go_to_diagnostic_impl(
16435 &mut self,
16436 direction: Direction,
16437 severity: GoToDiagnosticSeverityFilter,
16438 window: &mut Window,
16439 cx: &mut Context<Self>,
16440 ) {
16441 let buffer = self.buffer.read(cx).snapshot(cx);
16442 let selection = self
16443 .selections
16444 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16445
16446 let mut active_group_id = None;
16447 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16448 && active_group.active_range.start.to_offset(&buffer) == selection.start
16449 {
16450 active_group_id = Some(active_group.group_id);
16451 }
16452
16453 fn filtered<'a>(
16454 severity: GoToDiagnosticSeverityFilter,
16455 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16456 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16457 diagnostics
16458 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16459 .filter(|entry| entry.range.start != entry.range.end)
16460 .filter(|entry| !entry.diagnostic.is_unnecessary)
16461 }
16462
16463 let before = filtered(
16464 severity,
16465 buffer
16466 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16467 .filter(|entry| entry.range.start <= selection.start),
16468 );
16469 let after = filtered(
16470 severity,
16471 buffer
16472 .diagnostics_in_range(selection.start..buffer.len())
16473 .filter(|entry| entry.range.start >= selection.start),
16474 );
16475
16476 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16477 if direction == Direction::Prev {
16478 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16479 {
16480 for diagnostic in prev_diagnostics.into_iter().rev() {
16481 if diagnostic.range.start != selection.start
16482 || active_group_id
16483 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16484 {
16485 found = Some(diagnostic);
16486 break 'outer;
16487 }
16488 }
16489 }
16490 } else {
16491 for diagnostic in after.chain(before) {
16492 if diagnostic.range.start != selection.start
16493 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16494 {
16495 found = Some(diagnostic);
16496 break;
16497 }
16498 }
16499 }
16500 let Some(next_diagnostic) = found else {
16501 return;
16502 };
16503
16504 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16505 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16506 return;
16507 };
16508 let snapshot = self.snapshot(window, cx);
16509 if snapshot.intersects_fold(next_diagnostic.range.start) {
16510 self.unfold_ranges(
16511 std::slice::from_ref(&next_diagnostic.range),
16512 true,
16513 false,
16514 cx,
16515 );
16516 }
16517 self.change_selections(Default::default(), window, cx, |s| {
16518 s.select_ranges(vec![
16519 next_diagnostic.range.start..next_diagnostic.range.start,
16520 ])
16521 });
16522 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16523 self.refresh_edit_prediction(false, true, window, cx);
16524 }
16525
16526 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16527 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16528 let snapshot = self.snapshot(window, cx);
16529 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16530 self.go_to_hunk_before_or_after_position(
16531 &snapshot,
16532 selection.head(),
16533 Direction::Next,
16534 window,
16535 cx,
16536 );
16537 }
16538
16539 pub fn go_to_hunk_before_or_after_position(
16540 &mut self,
16541 snapshot: &EditorSnapshot,
16542 position: Point,
16543 direction: Direction,
16544 window: &mut Window,
16545 cx: &mut Context<Editor>,
16546 ) {
16547 let row = if direction == Direction::Next {
16548 self.hunk_after_position(snapshot, position)
16549 .map(|hunk| hunk.row_range.start)
16550 } else {
16551 self.hunk_before_position(snapshot, position)
16552 };
16553
16554 if let Some(row) = row {
16555 let destination = Point::new(row.0, 0);
16556 let autoscroll = Autoscroll::center();
16557
16558 self.unfold_ranges(&[destination..destination], false, false, cx);
16559 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16560 s.select_ranges([destination..destination]);
16561 });
16562 }
16563 }
16564
16565 fn hunk_after_position(
16566 &mut self,
16567 snapshot: &EditorSnapshot,
16568 position: Point,
16569 ) -> Option<MultiBufferDiffHunk> {
16570 snapshot
16571 .buffer_snapshot()
16572 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16573 .find(|hunk| hunk.row_range.start.0 > position.row)
16574 .or_else(|| {
16575 snapshot
16576 .buffer_snapshot()
16577 .diff_hunks_in_range(Point::zero()..position)
16578 .find(|hunk| hunk.row_range.end.0 < position.row)
16579 })
16580 }
16581
16582 fn go_to_prev_hunk(
16583 &mut self,
16584 _: &GoToPreviousHunk,
16585 window: &mut Window,
16586 cx: &mut Context<Self>,
16587 ) {
16588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16589 let snapshot = self.snapshot(window, cx);
16590 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16591 self.go_to_hunk_before_or_after_position(
16592 &snapshot,
16593 selection.head(),
16594 Direction::Prev,
16595 window,
16596 cx,
16597 );
16598 }
16599
16600 fn hunk_before_position(
16601 &mut self,
16602 snapshot: &EditorSnapshot,
16603 position: Point,
16604 ) -> Option<MultiBufferRow> {
16605 snapshot
16606 .buffer_snapshot()
16607 .diff_hunk_before(position)
16608 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16609 }
16610
16611 fn go_to_next_change(
16612 &mut self,
16613 _: &GoToNextChange,
16614 window: &mut Window,
16615 cx: &mut Context<Self>,
16616 ) {
16617 if let Some(selections) = self
16618 .change_list
16619 .next_change(1, Direction::Next)
16620 .map(|s| s.to_vec())
16621 {
16622 self.change_selections(Default::default(), window, cx, |s| {
16623 let map = s.display_snapshot();
16624 s.select_display_ranges(selections.iter().map(|a| {
16625 let point = a.to_display_point(&map);
16626 point..point
16627 }))
16628 })
16629 }
16630 }
16631
16632 fn go_to_previous_change(
16633 &mut self,
16634 _: &GoToPreviousChange,
16635 window: &mut Window,
16636 cx: &mut Context<Self>,
16637 ) {
16638 if let Some(selections) = self
16639 .change_list
16640 .next_change(1, Direction::Prev)
16641 .map(|s| s.to_vec())
16642 {
16643 self.change_selections(Default::default(), window, cx, |s| {
16644 let map = s.display_snapshot();
16645 s.select_display_ranges(selections.iter().map(|a| {
16646 let point = a.to_display_point(&map);
16647 point..point
16648 }))
16649 })
16650 }
16651 }
16652
16653 pub fn go_to_next_document_highlight(
16654 &mut self,
16655 _: &GoToNextDocumentHighlight,
16656 window: &mut Window,
16657 cx: &mut Context<Self>,
16658 ) {
16659 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16660 }
16661
16662 pub fn go_to_prev_document_highlight(
16663 &mut self,
16664 _: &GoToPreviousDocumentHighlight,
16665 window: &mut Window,
16666 cx: &mut Context<Self>,
16667 ) {
16668 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16669 }
16670
16671 pub fn go_to_document_highlight_before_or_after_position(
16672 &mut self,
16673 direction: Direction,
16674 window: &mut Window,
16675 cx: &mut Context<Editor>,
16676 ) {
16677 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16678 let snapshot = self.snapshot(window, cx);
16679 let buffer = &snapshot.buffer_snapshot();
16680 let position = self
16681 .selections
16682 .newest::<Point>(&snapshot.display_snapshot)
16683 .head();
16684 let anchor_position = buffer.anchor_after(position);
16685
16686 // Get all document highlights (both read and write)
16687 let mut all_highlights = Vec::new();
16688
16689 if let Some((_, read_highlights)) = self
16690 .background_highlights
16691 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16692 {
16693 all_highlights.extend(read_highlights.iter());
16694 }
16695
16696 if let Some((_, write_highlights)) = self
16697 .background_highlights
16698 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16699 {
16700 all_highlights.extend(write_highlights.iter());
16701 }
16702
16703 if all_highlights.is_empty() {
16704 return;
16705 }
16706
16707 // Sort highlights by position
16708 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16709
16710 let target_highlight = match direction {
16711 Direction::Next => {
16712 // Find the first highlight after the current position
16713 all_highlights
16714 .iter()
16715 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16716 }
16717 Direction::Prev => {
16718 // Find the last highlight before the current position
16719 all_highlights
16720 .iter()
16721 .rev()
16722 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16723 }
16724 };
16725
16726 if let Some(highlight) = target_highlight {
16727 let destination = highlight.start.to_point(buffer);
16728 let autoscroll = Autoscroll::center();
16729
16730 self.unfold_ranges(&[destination..destination], false, false, cx);
16731 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16732 s.select_ranges([destination..destination]);
16733 });
16734 }
16735 }
16736
16737 fn go_to_line<T: 'static>(
16738 &mut self,
16739 position: Anchor,
16740 highlight_color: Option<Hsla>,
16741 window: &mut Window,
16742 cx: &mut Context<Self>,
16743 ) {
16744 let snapshot = self.snapshot(window, cx).display_snapshot;
16745 let position = position.to_point(&snapshot.buffer_snapshot());
16746 let start = snapshot
16747 .buffer_snapshot()
16748 .clip_point(Point::new(position.row, 0), Bias::Left);
16749 let end = start + Point::new(1, 0);
16750 let start = snapshot.buffer_snapshot().anchor_before(start);
16751 let end = snapshot.buffer_snapshot().anchor_before(end);
16752
16753 self.highlight_rows::<T>(
16754 start..end,
16755 highlight_color
16756 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16757 Default::default(),
16758 cx,
16759 );
16760
16761 if self.buffer.read(cx).is_singleton() {
16762 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16763 }
16764 }
16765
16766 pub fn go_to_definition(
16767 &mut self,
16768 _: &GoToDefinition,
16769 window: &mut Window,
16770 cx: &mut Context<Self>,
16771 ) -> Task<Result<Navigated>> {
16772 let definition =
16773 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16774 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16775 cx.spawn_in(window, async move |editor, cx| {
16776 if definition.await? == Navigated::Yes {
16777 return Ok(Navigated::Yes);
16778 }
16779 match fallback_strategy {
16780 GoToDefinitionFallback::None => Ok(Navigated::No),
16781 GoToDefinitionFallback::FindAllReferences => {
16782 match editor.update_in(cx, |editor, window, cx| {
16783 editor.find_all_references(&FindAllReferences, window, cx)
16784 })? {
16785 Some(references) => references.await,
16786 None => Ok(Navigated::No),
16787 }
16788 }
16789 }
16790 })
16791 }
16792
16793 pub fn go_to_declaration(
16794 &mut self,
16795 _: &GoToDeclaration,
16796 window: &mut Window,
16797 cx: &mut Context<Self>,
16798 ) -> Task<Result<Navigated>> {
16799 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16800 }
16801
16802 pub fn go_to_declaration_split(
16803 &mut self,
16804 _: &GoToDeclaration,
16805 window: &mut Window,
16806 cx: &mut Context<Self>,
16807 ) -> Task<Result<Navigated>> {
16808 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16809 }
16810
16811 pub fn go_to_implementation(
16812 &mut self,
16813 _: &GoToImplementation,
16814 window: &mut Window,
16815 cx: &mut Context<Self>,
16816 ) -> Task<Result<Navigated>> {
16817 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16818 }
16819
16820 pub fn go_to_implementation_split(
16821 &mut self,
16822 _: &GoToImplementationSplit,
16823 window: &mut Window,
16824 cx: &mut Context<Self>,
16825 ) -> Task<Result<Navigated>> {
16826 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16827 }
16828
16829 pub fn go_to_type_definition(
16830 &mut self,
16831 _: &GoToTypeDefinition,
16832 window: &mut Window,
16833 cx: &mut Context<Self>,
16834 ) -> Task<Result<Navigated>> {
16835 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16836 }
16837
16838 pub fn go_to_definition_split(
16839 &mut self,
16840 _: &GoToDefinitionSplit,
16841 window: &mut Window,
16842 cx: &mut Context<Self>,
16843 ) -> Task<Result<Navigated>> {
16844 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16845 }
16846
16847 pub fn go_to_type_definition_split(
16848 &mut self,
16849 _: &GoToTypeDefinitionSplit,
16850 window: &mut Window,
16851 cx: &mut Context<Self>,
16852 ) -> Task<Result<Navigated>> {
16853 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16854 }
16855
16856 fn go_to_definition_of_kind(
16857 &mut self,
16858 kind: GotoDefinitionKind,
16859 split: bool,
16860 window: &mut Window,
16861 cx: &mut Context<Self>,
16862 ) -> Task<Result<Navigated>> {
16863 let Some(provider) = self.semantics_provider.clone() else {
16864 return Task::ready(Ok(Navigated::No));
16865 };
16866 let head = self
16867 .selections
16868 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16869 .head();
16870 let buffer = self.buffer.read(cx);
16871 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16872 return Task::ready(Ok(Navigated::No));
16873 };
16874 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16875 return Task::ready(Ok(Navigated::No));
16876 };
16877
16878 cx.spawn_in(window, async move |editor, cx| {
16879 let Some(definitions) = definitions.await? else {
16880 return Ok(Navigated::No);
16881 };
16882 let navigated = editor
16883 .update_in(cx, |editor, window, cx| {
16884 editor.navigate_to_hover_links(
16885 Some(kind),
16886 definitions
16887 .into_iter()
16888 .filter(|location| {
16889 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16890 })
16891 .map(HoverLink::Text)
16892 .collect::<Vec<_>>(),
16893 split,
16894 window,
16895 cx,
16896 )
16897 })?
16898 .await?;
16899 anyhow::Ok(navigated)
16900 })
16901 }
16902
16903 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16904 let selection = self.selections.newest_anchor();
16905 let head = selection.head();
16906 let tail = selection.tail();
16907
16908 let Some((buffer, start_position)) =
16909 self.buffer.read(cx).text_anchor_for_position(head, cx)
16910 else {
16911 return;
16912 };
16913
16914 let end_position = if head != tail {
16915 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16916 return;
16917 };
16918 Some(pos)
16919 } else {
16920 None
16921 };
16922
16923 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16924 let url = if let Some(end_pos) = end_position {
16925 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16926 } else {
16927 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16928 };
16929
16930 if let Some(url) = url {
16931 cx.update(|window, cx| {
16932 if parse_zed_link(&url, cx).is_some() {
16933 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16934 } else {
16935 cx.open_url(&url);
16936 }
16937 })?;
16938 }
16939
16940 anyhow::Ok(())
16941 });
16942
16943 url_finder.detach();
16944 }
16945
16946 pub fn open_selected_filename(
16947 &mut self,
16948 _: &OpenSelectedFilename,
16949 window: &mut Window,
16950 cx: &mut Context<Self>,
16951 ) {
16952 let Some(workspace) = self.workspace() else {
16953 return;
16954 };
16955
16956 let position = self.selections.newest_anchor().head();
16957
16958 let Some((buffer, buffer_position)) =
16959 self.buffer.read(cx).text_anchor_for_position(position, cx)
16960 else {
16961 return;
16962 };
16963
16964 let project = self.project.clone();
16965
16966 cx.spawn_in(window, async move |_, cx| {
16967 let result = find_file(&buffer, project, buffer_position, cx).await;
16968
16969 if let Some((_, path)) = result {
16970 workspace
16971 .update_in(cx, |workspace, window, cx| {
16972 workspace.open_resolved_path(path, window, cx)
16973 })?
16974 .await?;
16975 }
16976 anyhow::Ok(())
16977 })
16978 .detach();
16979 }
16980
16981 pub(crate) fn navigate_to_hover_links(
16982 &mut self,
16983 kind: Option<GotoDefinitionKind>,
16984 definitions: Vec<HoverLink>,
16985 split: bool,
16986 window: &mut Window,
16987 cx: &mut Context<Editor>,
16988 ) -> Task<Result<Navigated>> {
16989 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16990 let mut first_url_or_file = None;
16991 let definitions: Vec<_> = definitions
16992 .into_iter()
16993 .filter_map(|def| match def {
16994 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16995 HoverLink::InlayHint(lsp_location, server_id) => {
16996 let computation =
16997 self.compute_target_location(lsp_location, server_id, window, cx);
16998 Some(cx.background_spawn(computation))
16999 }
17000 HoverLink::Url(url) => {
17001 first_url_or_file = Some(Either::Left(url));
17002 None
17003 }
17004 HoverLink::File(path) => {
17005 first_url_or_file = Some(Either::Right(path));
17006 None
17007 }
17008 })
17009 .collect();
17010
17011 let workspace = self.workspace();
17012
17013 cx.spawn_in(window, async move |editor, cx| {
17014 let locations: Vec<Location> = future::join_all(definitions)
17015 .await
17016 .into_iter()
17017 .filter_map(|location| location.transpose())
17018 .collect::<Result<_>>()
17019 .context("location tasks")?;
17020 let mut locations = cx.update(|_, cx| {
17021 locations
17022 .into_iter()
17023 .map(|location| {
17024 let buffer = location.buffer.read(cx);
17025 (location.buffer, location.range.to_point(buffer))
17026 })
17027 .into_group_map()
17028 })?;
17029 let mut num_locations = 0;
17030 for ranges in locations.values_mut() {
17031 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17032 ranges.dedup();
17033 num_locations += ranges.len();
17034 }
17035
17036 if num_locations > 1 {
17037 let Some(workspace) = workspace else {
17038 return Ok(Navigated::No);
17039 };
17040
17041 let tab_kind = match kind {
17042 Some(GotoDefinitionKind::Implementation) => "Implementations",
17043 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17044 Some(GotoDefinitionKind::Declaration) => "Declarations",
17045 Some(GotoDefinitionKind::Type) => "Types",
17046 };
17047 let title = editor
17048 .update_in(cx, |_, _, cx| {
17049 let target = locations
17050 .iter()
17051 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17052 .map(|(buffer, location)| {
17053 buffer
17054 .read(cx)
17055 .text_for_range(location.clone())
17056 .collect::<String>()
17057 })
17058 .filter(|text| !text.contains('\n'))
17059 .unique()
17060 .take(3)
17061 .join(", ");
17062 if target.is_empty() {
17063 tab_kind.to_owned()
17064 } else {
17065 format!("{tab_kind} for {target}")
17066 }
17067 })
17068 .context("buffer title")?;
17069
17070 let opened = workspace
17071 .update_in(cx, |workspace, window, cx| {
17072 Self::open_locations_in_multibuffer(
17073 workspace,
17074 locations,
17075 title,
17076 split,
17077 MultibufferSelectionMode::First,
17078 window,
17079 cx,
17080 )
17081 })
17082 .is_ok();
17083
17084 anyhow::Ok(Navigated::from_bool(opened))
17085 } else if num_locations == 0 {
17086 // If there is one url or file, open it directly
17087 match first_url_or_file {
17088 Some(Either::Left(url)) => {
17089 cx.update(|_, cx| cx.open_url(&url))?;
17090 Ok(Navigated::Yes)
17091 }
17092 Some(Either::Right(path)) => {
17093 let Some(workspace) = workspace else {
17094 return Ok(Navigated::No);
17095 };
17096
17097 workspace
17098 .update_in(cx, |workspace, window, cx| {
17099 workspace.open_resolved_path(path, window, cx)
17100 })?
17101 .await?;
17102 Ok(Navigated::Yes)
17103 }
17104 None => Ok(Navigated::No),
17105 }
17106 } else {
17107 let Some(workspace) = workspace else {
17108 return Ok(Navigated::No);
17109 };
17110
17111 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17112 let target_range = target_ranges.first().unwrap().clone();
17113
17114 editor.update_in(cx, |editor, window, cx| {
17115 let range = target_range.to_point(target_buffer.read(cx));
17116 let range = editor.range_for_match(&range);
17117 let range = collapse_multiline_range(range);
17118
17119 if !split
17120 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17121 {
17122 editor.go_to_singleton_buffer_range(range, window, cx);
17123 } else {
17124 let pane = workspace.read(cx).active_pane().clone();
17125 window.defer(cx, move |window, cx| {
17126 let target_editor: Entity<Self> =
17127 workspace.update(cx, |workspace, cx| {
17128 let pane = if split {
17129 workspace.adjacent_pane(window, cx)
17130 } else {
17131 workspace.active_pane().clone()
17132 };
17133
17134 workspace.open_project_item(
17135 pane,
17136 target_buffer.clone(),
17137 true,
17138 true,
17139 window,
17140 cx,
17141 )
17142 });
17143 target_editor.update(cx, |target_editor, cx| {
17144 // When selecting a definition in a different buffer, disable the nav history
17145 // to avoid creating a history entry at the previous cursor location.
17146 pane.update(cx, |pane, _| pane.disable_history());
17147 target_editor.go_to_singleton_buffer_range(range, window, cx);
17148 pane.update(cx, |pane, _| pane.enable_history());
17149 });
17150 });
17151 }
17152 Navigated::Yes
17153 })
17154 }
17155 })
17156 }
17157
17158 fn compute_target_location(
17159 &self,
17160 lsp_location: lsp::Location,
17161 server_id: LanguageServerId,
17162 window: &mut Window,
17163 cx: &mut Context<Self>,
17164 ) -> Task<anyhow::Result<Option<Location>>> {
17165 let Some(project) = self.project.clone() else {
17166 return Task::ready(Ok(None));
17167 };
17168
17169 cx.spawn_in(window, async move |editor, cx| {
17170 let location_task = editor.update(cx, |_, cx| {
17171 project.update(cx, |project, cx| {
17172 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17173 })
17174 })?;
17175 let location = Some({
17176 let target_buffer_handle = location_task.await.context("open local buffer")?;
17177 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17178 let target_start = target_buffer
17179 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17180 let target_end = target_buffer
17181 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17182 target_buffer.anchor_after(target_start)
17183 ..target_buffer.anchor_before(target_end)
17184 })?;
17185 Location {
17186 buffer: target_buffer_handle,
17187 range,
17188 }
17189 });
17190 Ok(location)
17191 })
17192 }
17193
17194 fn go_to_next_reference(
17195 &mut self,
17196 _: &GoToNextReference,
17197 window: &mut Window,
17198 cx: &mut Context<Self>,
17199 ) {
17200 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17201 if let Some(task) = task {
17202 task.detach();
17203 };
17204 }
17205
17206 fn go_to_prev_reference(
17207 &mut self,
17208 _: &GoToPreviousReference,
17209 window: &mut Window,
17210 cx: &mut Context<Self>,
17211 ) {
17212 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17213 if let Some(task) = task {
17214 task.detach();
17215 };
17216 }
17217
17218 pub fn go_to_reference_before_or_after_position(
17219 &mut self,
17220 direction: Direction,
17221 count: usize,
17222 window: &mut Window,
17223 cx: &mut Context<Self>,
17224 ) -> Option<Task<Result<()>>> {
17225 let selection = self.selections.newest_anchor();
17226 let head = selection.head();
17227
17228 let multi_buffer = self.buffer.read(cx);
17229
17230 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17231 let workspace = self.workspace()?;
17232 let project = workspace.read(cx).project().clone();
17233 let references =
17234 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17235 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17236 let Some(locations) = references.await? else {
17237 return Ok(());
17238 };
17239
17240 if locations.is_empty() {
17241 // totally normal - the cursor may be on something which is not
17242 // a symbol (e.g. a keyword)
17243 log::info!("no references found under cursor");
17244 return Ok(());
17245 }
17246
17247 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17248
17249 let (locations, current_location_index) =
17250 multi_buffer.update(cx, |multi_buffer, cx| {
17251 let mut locations = locations
17252 .into_iter()
17253 .filter_map(|loc| {
17254 let start = multi_buffer.buffer_anchor_to_anchor(
17255 &loc.buffer,
17256 loc.range.start,
17257 cx,
17258 )?;
17259 let end = multi_buffer.buffer_anchor_to_anchor(
17260 &loc.buffer,
17261 loc.range.end,
17262 cx,
17263 )?;
17264 Some(start..end)
17265 })
17266 .collect::<Vec<_>>();
17267
17268 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17269 // There is an O(n) implementation, but given this list will be
17270 // small (usually <100 items), the extra O(log(n)) factor isn't
17271 // worth the (surprisingly large amount of) extra complexity.
17272 locations
17273 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17274
17275 let head_offset = head.to_offset(&multi_buffer_snapshot);
17276
17277 let current_location_index = locations.iter().position(|loc| {
17278 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17279 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17280 });
17281
17282 (locations, current_location_index)
17283 })?;
17284
17285 let Some(current_location_index) = current_location_index else {
17286 // This indicates something has gone wrong, because we already
17287 // handle the "no references" case above
17288 log::error!(
17289 "failed to find current reference under cursor. Total references: {}",
17290 locations.len()
17291 );
17292 return Ok(());
17293 };
17294
17295 let destination_location_index = match direction {
17296 Direction::Next => (current_location_index + count) % locations.len(),
17297 Direction::Prev => {
17298 (current_location_index + locations.len() - count % locations.len())
17299 % locations.len()
17300 }
17301 };
17302
17303 // TODO(cameron): is this needed?
17304 // the thinking is to avoid "jumping to the current location" (avoid
17305 // polluting "jumplist" in vim terms)
17306 if current_location_index == destination_location_index {
17307 return Ok(());
17308 }
17309
17310 let Range { start, end } = locations[destination_location_index];
17311
17312 editor.update_in(cx, |editor, window, cx| {
17313 let effects = SelectionEffects::default();
17314
17315 editor.unfold_ranges(&[start..end], false, false, cx);
17316 editor.change_selections(effects, window, cx, |s| {
17317 s.select_ranges([start..start]);
17318 });
17319 })?;
17320
17321 Ok(())
17322 }))
17323 }
17324
17325 pub fn find_all_references(
17326 &mut self,
17327 _: &FindAllReferences,
17328 window: &mut Window,
17329 cx: &mut Context<Self>,
17330 ) -> Option<Task<Result<Navigated>>> {
17331 let selection = self
17332 .selections
17333 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17334 let multi_buffer = self.buffer.read(cx);
17335 let head = selection.head();
17336
17337 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17338 let head_anchor = multi_buffer_snapshot.anchor_at(
17339 head,
17340 if head < selection.tail() {
17341 Bias::Right
17342 } else {
17343 Bias::Left
17344 },
17345 );
17346
17347 match self
17348 .find_all_references_task_sources
17349 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17350 {
17351 Ok(_) => {
17352 log::info!(
17353 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17354 );
17355 return None;
17356 }
17357 Err(i) => {
17358 self.find_all_references_task_sources.insert(i, head_anchor);
17359 }
17360 }
17361
17362 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17363 let workspace = self.workspace()?;
17364 let project = workspace.read(cx).project().clone();
17365 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17366 Some(cx.spawn_in(window, async move |editor, cx| {
17367 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17368 if let Ok(i) = editor
17369 .find_all_references_task_sources
17370 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17371 {
17372 editor.find_all_references_task_sources.remove(i);
17373 }
17374 });
17375
17376 let Some(locations) = references.await? else {
17377 return anyhow::Ok(Navigated::No);
17378 };
17379 let mut locations = cx.update(|_, cx| {
17380 locations
17381 .into_iter()
17382 .map(|location| {
17383 let buffer = location.buffer.read(cx);
17384 (location.buffer, location.range.to_point(buffer))
17385 })
17386 .into_group_map()
17387 })?;
17388 if locations.is_empty() {
17389 return anyhow::Ok(Navigated::No);
17390 }
17391 for ranges in locations.values_mut() {
17392 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17393 ranges.dedup();
17394 }
17395
17396 workspace.update_in(cx, |workspace, window, cx| {
17397 let target = locations
17398 .iter()
17399 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17400 .map(|(buffer, location)| {
17401 buffer
17402 .read(cx)
17403 .text_for_range(location.clone())
17404 .collect::<String>()
17405 })
17406 .filter(|text| !text.contains('\n'))
17407 .unique()
17408 .take(3)
17409 .join(", ");
17410 let title = if target.is_empty() {
17411 "References".to_owned()
17412 } else {
17413 format!("References to {target}")
17414 };
17415 Self::open_locations_in_multibuffer(
17416 workspace,
17417 locations,
17418 title,
17419 false,
17420 MultibufferSelectionMode::First,
17421 window,
17422 cx,
17423 );
17424 Navigated::Yes
17425 })
17426 }))
17427 }
17428
17429 /// Opens a multibuffer with the given project locations in it
17430 pub fn open_locations_in_multibuffer(
17431 workspace: &mut Workspace,
17432 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17433 title: String,
17434 split: bool,
17435 multibuffer_selection_mode: MultibufferSelectionMode,
17436 window: &mut Window,
17437 cx: &mut Context<Workspace>,
17438 ) {
17439 if locations.is_empty() {
17440 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17441 return;
17442 }
17443
17444 let capability = workspace.project().read(cx).capability();
17445 let mut ranges = <Vec<Range<Anchor>>>::new();
17446
17447 // a key to find existing multibuffer editors with the same set of locations
17448 // to prevent us from opening more and more multibuffer tabs for searches and the like
17449 let mut key = (title.clone(), vec![]);
17450 let excerpt_buffer = cx.new(|cx| {
17451 let key = &mut key.1;
17452 let mut multibuffer = MultiBuffer::new(capability);
17453 for (buffer, mut ranges_for_buffer) in locations {
17454 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17455 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17456 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17457 PathKey::for_buffer(&buffer, cx),
17458 buffer.clone(),
17459 ranges_for_buffer,
17460 multibuffer_context_lines(cx),
17461 cx,
17462 );
17463 ranges.extend(new_ranges)
17464 }
17465
17466 multibuffer.with_title(title)
17467 });
17468 let existing = workspace.active_pane().update(cx, |pane, cx| {
17469 pane.items()
17470 .filter_map(|item| item.downcast::<Editor>())
17471 .find(|editor| {
17472 editor
17473 .read(cx)
17474 .lookup_key
17475 .as_ref()
17476 .and_then(|it| {
17477 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17478 })
17479 .is_some_and(|it| *it == key)
17480 })
17481 });
17482 let editor = existing.unwrap_or_else(|| {
17483 cx.new(|cx| {
17484 let mut editor = Editor::for_multibuffer(
17485 excerpt_buffer,
17486 Some(workspace.project().clone()),
17487 window,
17488 cx,
17489 );
17490 editor.lookup_key = Some(Box::new(key));
17491 editor
17492 })
17493 });
17494 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17495 MultibufferSelectionMode::First => {
17496 if let Some(first_range) = ranges.first() {
17497 editor.change_selections(
17498 SelectionEffects::no_scroll(),
17499 window,
17500 cx,
17501 |selections| {
17502 selections.clear_disjoint();
17503 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17504 },
17505 );
17506 }
17507 editor.highlight_background::<Self>(
17508 &ranges,
17509 |theme| theme.colors().editor_highlighted_line_background,
17510 cx,
17511 );
17512 }
17513 MultibufferSelectionMode::All => {
17514 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17515 selections.clear_disjoint();
17516 selections.select_anchor_ranges(ranges);
17517 });
17518 }
17519 });
17520
17521 let item = Box::new(editor);
17522 let item_id = item.item_id();
17523
17524 if split {
17525 let pane = workspace.adjacent_pane(window, cx);
17526 workspace.add_item(pane, item, None, true, true, window, cx);
17527 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17528 let (preview_item_id, preview_item_idx) =
17529 workspace.active_pane().read_with(cx, |pane, _| {
17530 (pane.preview_item_id(), pane.preview_item_idx())
17531 });
17532
17533 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17534
17535 if let Some(preview_item_id) = preview_item_id {
17536 workspace.active_pane().update(cx, |pane, cx| {
17537 pane.remove_item(preview_item_id, false, false, window, cx);
17538 });
17539 }
17540 } else {
17541 workspace.add_item_to_active_pane(item, None, true, window, cx);
17542 }
17543 workspace.active_pane().update(cx, |pane, cx| {
17544 pane.set_preview_item_id(Some(item_id), cx);
17545 });
17546 }
17547
17548 pub fn rename(
17549 &mut self,
17550 _: &Rename,
17551 window: &mut Window,
17552 cx: &mut Context<Self>,
17553 ) -> Option<Task<Result<()>>> {
17554 use language::ToOffset as _;
17555
17556 let provider = self.semantics_provider.clone()?;
17557 let selection = self.selections.newest_anchor().clone();
17558 let (cursor_buffer, cursor_buffer_position) = self
17559 .buffer
17560 .read(cx)
17561 .text_anchor_for_position(selection.head(), cx)?;
17562 let (tail_buffer, cursor_buffer_position_end) = self
17563 .buffer
17564 .read(cx)
17565 .text_anchor_for_position(selection.tail(), cx)?;
17566 if tail_buffer != cursor_buffer {
17567 return None;
17568 }
17569
17570 let snapshot = cursor_buffer.read(cx).snapshot();
17571 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17572 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17573 let prepare_rename = provider
17574 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17575 .unwrap_or_else(|| Task::ready(Ok(None)));
17576 drop(snapshot);
17577
17578 Some(cx.spawn_in(window, async move |this, cx| {
17579 let rename_range = if let Some(range) = prepare_rename.await? {
17580 Some(range)
17581 } else {
17582 this.update(cx, |this, cx| {
17583 let buffer = this.buffer.read(cx).snapshot(cx);
17584 let mut buffer_highlights = this
17585 .document_highlights_for_position(selection.head(), &buffer)
17586 .filter(|highlight| {
17587 highlight.start.excerpt_id == selection.head().excerpt_id
17588 && highlight.end.excerpt_id == selection.head().excerpt_id
17589 });
17590 buffer_highlights
17591 .next()
17592 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17593 })?
17594 };
17595 if let Some(rename_range) = rename_range {
17596 this.update_in(cx, |this, window, cx| {
17597 let snapshot = cursor_buffer.read(cx).snapshot();
17598 let rename_buffer_range = rename_range.to_offset(&snapshot);
17599 let cursor_offset_in_rename_range =
17600 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17601 let cursor_offset_in_rename_range_end =
17602 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17603
17604 this.take_rename(false, window, cx);
17605 let buffer = this.buffer.read(cx).read(cx);
17606 let cursor_offset = selection.head().to_offset(&buffer);
17607 let rename_start =
17608 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17609 let rename_end = rename_start + rename_buffer_range.len();
17610 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17611 let mut old_highlight_id = None;
17612 let old_name: Arc<str> = buffer
17613 .chunks(rename_start..rename_end, true)
17614 .map(|chunk| {
17615 if old_highlight_id.is_none() {
17616 old_highlight_id = chunk.syntax_highlight_id;
17617 }
17618 chunk.text
17619 })
17620 .collect::<String>()
17621 .into();
17622
17623 drop(buffer);
17624
17625 // Position the selection in the rename editor so that it matches the current selection.
17626 this.show_local_selections = false;
17627 let rename_editor = cx.new(|cx| {
17628 let mut editor = Editor::single_line(window, cx);
17629 editor.buffer.update(cx, |buffer, cx| {
17630 buffer.edit(
17631 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17632 None,
17633 cx,
17634 )
17635 });
17636 let cursor_offset_in_rename_range =
17637 MultiBufferOffset(cursor_offset_in_rename_range);
17638 let cursor_offset_in_rename_range_end =
17639 MultiBufferOffset(cursor_offset_in_rename_range_end);
17640 let rename_selection_range = match cursor_offset_in_rename_range
17641 .cmp(&cursor_offset_in_rename_range_end)
17642 {
17643 Ordering::Equal => {
17644 editor.select_all(&SelectAll, window, cx);
17645 return editor;
17646 }
17647 Ordering::Less => {
17648 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17649 }
17650 Ordering::Greater => {
17651 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17652 }
17653 };
17654 if rename_selection_range.end.0 > old_name.len() {
17655 editor.select_all(&SelectAll, window, cx);
17656 } else {
17657 editor.change_selections(Default::default(), window, cx, |s| {
17658 s.select_ranges([rename_selection_range]);
17659 });
17660 }
17661 editor
17662 });
17663 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17664 if e == &EditorEvent::Focused {
17665 cx.emit(EditorEvent::FocusedIn)
17666 }
17667 })
17668 .detach();
17669
17670 let write_highlights =
17671 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17672 let read_highlights =
17673 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17674 let ranges = write_highlights
17675 .iter()
17676 .flat_map(|(_, ranges)| ranges.iter())
17677 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17678 .cloned()
17679 .collect();
17680
17681 this.highlight_text::<Rename>(
17682 ranges,
17683 HighlightStyle {
17684 fade_out: Some(0.6),
17685 ..Default::default()
17686 },
17687 cx,
17688 );
17689 let rename_focus_handle = rename_editor.focus_handle(cx);
17690 window.focus(&rename_focus_handle);
17691 let block_id = this.insert_blocks(
17692 [BlockProperties {
17693 style: BlockStyle::Flex,
17694 placement: BlockPlacement::Below(range.start),
17695 height: Some(1),
17696 render: Arc::new({
17697 let rename_editor = rename_editor.clone();
17698 move |cx: &mut BlockContext| {
17699 let mut text_style = cx.editor_style.text.clone();
17700 if let Some(highlight_style) = old_highlight_id
17701 .and_then(|h| h.style(&cx.editor_style.syntax))
17702 {
17703 text_style = text_style.highlight(highlight_style);
17704 }
17705 div()
17706 .block_mouse_except_scroll()
17707 .pl(cx.anchor_x)
17708 .child(EditorElement::new(
17709 &rename_editor,
17710 EditorStyle {
17711 background: cx.theme().system().transparent,
17712 local_player: cx.editor_style.local_player,
17713 text: text_style,
17714 scrollbar_width: cx.editor_style.scrollbar_width,
17715 syntax: cx.editor_style.syntax.clone(),
17716 status: cx.editor_style.status.clone(),
17717 inlay_hints_style: HighlightStyle {
17718 font_weight: Some(FontWeight::BOLD),
17719 ..make_inlay_hints_style(cx.app)
17720 },
17721 edit_prediction_styles: make_suggestion_styles(
17722 cx.app,
17723 ),
17724 ..EditorStyle::default()
17725 },
17726 ))
17727 .into_any_element()
17728 }
17729 }),
17730 priority: 0,
17731 }],
17732 Some(Autoscroll::fit()),
17733 cx,
17734 )[0];
17735 this.pending_rename = Some(RenameState {
17736 range,
17737 old_name,
17738 editor: rename_editor,
17739 block_id,
17740 });
17741 })?;
17742 }
17743
17744 Ok(())
17745 }))
17746 }
17747
17748 pub fn confirm_rename(
17749 &mut self,
17750 _: &ConfirmRename,
17751 window: &mut Window,
17752 cx: &mut Context<Self>,
17753 ) -> Option<Task<Result<()>>> {
17754 let rename = self.take_rename(false, window, cx)?;
17755 let workspace = self.workspace()?.downgrade();
17756 let (buffer, start) = self
17757 .buffer
17758 .read(cx)
17759 .text_anchor_for_position(rename.range.start, cx)?;
17760 let (end_buffer, _) = self
17761 .buffer
17762 .read(cx)
17763 .text_anchor_for_position(rename.range.end, cx)?;
17764 if buffer != end_buffer {
17765 return None;
17766 }
17767
17768 let old_name = rename.old_name;
17769 let new_name = rename.editor.read(cx).text(cx);
17770
17771 let rename = self.semantics_provider.as_ref()?.perform_rename(
17772 &buffer,
17773 start,
17774 new_name.clone(),
17775 cx,
17776 )?;
17777
17778 Some(cx.spawn_in(window, async move |editor, cx| {
17779 let project_transaction = rename.await?;
17780 Self::open_project_transaction(
17781 &editor,
17782 workspace,
17783 project_transaction,
17784 format!("Rename: {} → {}", old_name, new_name),
17785 cx,
17786 )
17787 .await?;
17788
17789 editor.update(cx, |editor, cx| {
17790 editor.refresh_document_highlights(cx);
17791 })?;
17792 Ok(())
17793 }))
17794 }
17795
17796 fn take_rename(
17797 &mut self,
17798 moving_cursor: bool,
17799 window: &mut Window,
17800 cx: &mut Context<Self>,
17801 ) -> Option<RenameState> {
17802 let rename = self.pending_rename.take()?;
17803 if rename.editor.focus_handle(cx).is_focused(window) {
17804 window.focus(&self.focus_handle);
17805 }
17806
17807 self.remove_blocks(
17808 [rename.block_id].into_iter().collect(),
17809 Some(Autoscroll::fit()),
17810 cx,
17811 );
17812 self.clear_highlights::<Rename>(cx);
17813 self.show_local_selections = true;
17814
17815 if moving_cursor {
17816 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17817 editor
17818 .selections
17819 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17820 .head()
17821 });
17822
17823 // Update the selection to match the position of the selection inside
17824 // the rename editor.
17825 let snapshot = self.buffer.read(cx).read(cx);
17826 let rename_range = rename.range.to_offset(&snapshot);
17827 let cursor_in_editor = snapshot
17828 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17829 .min(rename_range.end);
17830 drop(snapshot);
17831
17832 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17833 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17834 });
17835 } else {
17836 self.refresh_document_highlights(cx);
17837 }
17838
17839 Some(rename)
17840 }
17841
17842 pub fn pending_rename(&self) -> Option<&RenameState> {
17843 self.pending_rename.as_ref()
17844 }
17845
17846 fn format(
17847 &mut self,
17848 _: &Format,
17849 window: &mut Window,
17850 cx: &mut Context<Self>,
17851 ) -> Option<Task<Result<()>>> {
17852 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17853
17854 let project = match &self.project {
17855 Some(project) => project.clone(),
17856 None => return None,
17857 };
17858
17859 Some(self.perform_format(
17860 project,
17861 FormatTrigger::Manual,
17862 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17863 window,
17864 cx,
17865 ))
17866 }
17867
17868 fn format_selections(
17869 &mut self,
17870 _: &FormatSelections,
17871 window: &mut Window,
17872 cx: &mut Context<Self>,
17873 ) -> Option<Task<Result<()>>> {
17874 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17875
17876 let project = match &self.project {
17877 Some(project) => project.clone(),
17878 None => return None,
17879 };
17880
17881 let ranges = self
17882 .selections
17883 .all_adjusted(&self.display_snapshot(cx))
17884 .into_iter()
17885 .map(|selection| selection.range())
17886 .collect_vec();
17887
17888 Some(self.perform_format(
17889 project,
17890 FormatTrigger::Manual,
17891 FormatTarget::Ranges(ranges),
17892 window,
17893 cx,
17894 ))
17895 }
17896
17897 fn perform_format(
17898 &mut self,
17899 project: Entity<Project>,
17900 trigger: FormatTrigger,
17901 target: FormatTarget,
17902 window: &mut Window,
17903 cx: &mut Context<Self>,
17904 ) -> Task<Result<()>> {
17905 let buffer = self.buffer.clone();
17906 let (buffers, target) = match target {
17907 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17908 FormatTarget::Ranges(selection_ranges) => {
17909 let multi_buffer = buffer.read(cx);
17910 let snapshot = multi_buffer.read(cx);
17911 let mut buffers = HashSet::default();
17912 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17913 BTreeMap::new();
17914 for selection_range in selection_ranges {
17915 for (buffer, buffer_range, _) in
17916 snapshot.range_to_buffer_ranges(selection_range)
17917 {
17918 let buffer_id = buffer.remote_id();
17919 let start = buffer.anchor_before(buffer_range.start);
17920 let end = buffer.anchor_after(buffer_range.end);
17921 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17922 buffer_id_to_ranges
17923 .entry(buffer_id)
17924 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17925 .or_insert_with(|| vec![start..end]);
17926 }
17927 }
17928 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17929 }
17930 };
17931
17932 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17933 let selections_prev = transaction_id_prev
17934 .and_then(|transaction_id_prev| {
17935 // default to selections as they were after the last edit, if we have them,
17936 // instead of how they are now.
17937 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17938 // will take you back to where you made the last edit, instead of staying where you scrolled
17939 self.selection_history
17940 .transaction(transaction_id_prev)
17941 .map(|t| t.0.clone())
17942 })
17943 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17944
17945 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17946 let format = project.update(cx, |project, cx| {
17947 project.format(buffers, target, true, trigger, cx)
17948 });
17949
17950 cx.spawn_in(window, async move |editor, cx| {
17951 let transaction = futures::select_biased! {
17952 transaction = format.log_err().fuse() => transaction,
17953 () = timeout => {
17954 log::warn!("timed out waiting for formatting");
17955 None
17956 }
17957 };
17958
17959 buffer
17960 .update(cx, |buffer, cx| {
17961 if let Some(transaction) = transaction
17962 && !buffer.is_singleton()
17963 {
17964 buffer.push_transaction(&transaction.0, cx);
17965 }
17966 cx.notify();
17967 })
17968 .ok();
17969
17970 if let Some(transaction_id_now) =
17971 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17972 {
17973 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17974 if has_new_transaction {
17975 _ = editor.update(cx, |editor, _| {
17976 editor
17977 .selection_history
17978 .insert_transaction(transaction_id_now, selections_prev);
17979 });
17980 }
17981 }
17982
17983 Ok(())
17984 })
17985 }
17986
17987 fn organize_imports(
17988 &mut self,
17989 _: &OrganizeImports,
17990 window: &mut Window,
17991 cx: &mut Context<Self>,
17992 ) -> Option<Task<Result<()>>> {
17993 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17994 let project = match &self.project {
17995 Some(project) => project.clone(),
17996 None => return None,
17997 };
17998 Some(self.perform_code_action_kind(
17999 project,
18000 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18001 window,
18002 cx,
18003 ))
18004 }
18005
18006 fn perform_code_action_kind(
18007 &mut self,
18008 project: Entity<Project>,
18009 kind: CodeActionKind,
18010 window: &mut Window,
18011 cx: &mut Context<Self>,
18012 ) -> Task<Result<()>> {
18013 let buffer = self.buffer.clone();
18014 let buffers = buffer.read(cx).all_buffers();
18015 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18016 let apply_action = project.update(cx, |project, cx| {
18017 project.apply_code_action_kind(buffers, kind, true, cx)
18018 });
18019 cx.spawn_in(window, async move |_, cx| {
18020 let transaction = futures::select_biased! {
18021 () = timeout => {
18022 log::warn!("timed out waiting for executing code action");
18023 None
18024 }
18025 transaction = apply_action.log_err().fuse() => transaction,
18026 };
18027 buffer
18028 .update(cx, |buffer, cx| {
18029 // check if we need this
18030 if let Some(transaction) = transaction
18031 && !buffer.is_singleton()
18032 {
18033 buffer.push_transaction(&transaction.0, cx);
18034 }
18035 cx.notify();
18036 })
18037 .ok();
18038 Ok(())
18039 })
18040 }
18041
18042 pub fn restart_language_server(
18043 &mut self,
18044 _: &RestartLanguageServer,
18045 _: &mut Window,
18046 cx: &mut Context<Self>,
18047 ) {
18048 if let Some(project) = self.project.clone() {
18049 self.buffer.update(cx, |multi_buffer, cx| {
18050 project.update(cx, |project, cx| {
18051 project.restart_language_servers_for_buffers(
18052 multi_buffer.all_buffers().into_iter().collect(),
18053 HashSet::default(),
18054 cx,
18055 );
18056 });
18057 })
18058 }
18059 }
18060
18061 pub fn stop_language_server(
18062 &mut self,
18063 _: &StopLanguageServer,
18064 _: &mut Window,
18065 cx: &mut Context<Self>,
18066 ) {
18067 if let Some(project) = self.project.clone() {
18068 self.buffer.update(cx, |multi_buffer, cx| {
18069 project.update(cx, |project, cx| {
18070 project.stop_language_servers_for_buffers(
18071 multi_buffer.all_buffers().into_iter().collect(),
18072 HashSet::default(),
18073 cx,
18074 );
18075 });
18076 });
18077 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18078 }
18079 }
18080
18081 fn cancel_language_server_work(
18082 workspace: &mut Workspace,
18083 _: &actions::CancelLanguageServerWork,
18084 _: &mut Window,
18085 cx: &mut Context<Workspace>,
18086 ) {
18087 let project = workspace.project();
18088 let buffers = workspace
18089 .active_item(cx)
18090 .and_then(|item| item.act_as::<Editor>(cx))
18091 .map_or(HashSet::default(), |editor| {
18092 editor.read(cx).buffer.read(cx).all_buffers()
18093 });
18094 project.update(cx, |project, cx| {
18095 project.cancel_language_server_work_for_buffers(buffers, cx);
18096 });
18097 }
18098
18099 fn show_character_palette(
18100 &mut self,
18101 _: &ShowCharacterPalette,
18102 window: &mut Window,
18103 _: &mut Context<Self>,
18104 ) {
18105 window.show_character_palette();
18106 }
18107
18108 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18109 if !self.diagnostics_enabled() {
18110 return;
18111 }
18112
18113 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18114 let buffer = self.buffer.read(cx).snapshot(cx);
18115 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18116 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18117 let is_valid = buffer
18118 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18119 .any(|entry| {
18120 entry.diagnostic.is_primary
18121 && !entry.range.is_empty()
18122 && entry.range.start == primary_range_start
18123 && entry.diagnostic.message == active_diagnostics.active_message
18124 });
18125
18126 if !is_valid {
18127 self.dismiss_diagnostics(cx);
18128 }
18129 }
18130 }
18131
18132 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18133 match &self.active_diagnostics {
18134 ActiveDiagnostic::Group(group) => Some(group),
18135 _ => None,
18136 }
18137 }
18138
18139 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18140 if !self.diagnostics_enabled() {
18141 return;
18142 }
18143 self.dismiss_diagnostics(cx);
18144 self.active_diagnostics = ActiveDiagnostic::All;
18145 }
18146
18147 fn activate_diagnostics(
18148 &mut self,
18149 buffer_id: BufferId,
18150 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18151 window: &mut Window,
18152 cx: &mut Context<Self>,
18153 ) {
18154 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18155 return;
18156 }
18157 self.dismiss_diagnostics(cx);
18158 let snapshot = self.snapshot(window, cx);
18159 let buffer = self.buffer.read(cx).snapshot(cx);
18160 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18161 return;
18162 };
18163
18164 let diagnostic_group = buffer
18165 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18166 .collect::<Vec<_>>();
18167
18168 let language_registry = self
18169 .project()
18170 .map(|project| project.read(cx).languages().clone());
18171
18172 let blocks = renderer.render_group(
18173 diagnostic_group,
18174 buffer_id,
18175 snapshot,
18176 cx.weak_entity(),
18177 language_registry,
18178 cx,
18179 );
18180
18181 let blocks = self.display_map.update(cx, |display_map, cx| {
18182 display_map.insert_blocks(blocks, cx).into_iter().collect()
18183 });
18184 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18185 active_range: buffer.anchor_before(diagnostic.range.start)
18186 ..buffer.anchor_after(diagnostic.range.end),
18187 active_message: diagnostic.diagnostic.message.clone(),
18188 group_id: diagnostic.diagnostic.group_id,
18189 blocks,
18190 });
18191 cx.notify();
18192 }
18193
18194 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18195 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18196 return;
18197 };
18198
18199 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18200 if let ActiveDiagnostic::Group(group) = prev {
18201 self.display_map.update(cx, |display_map, cx| {
18202 display_map.remove_blocks(group.blocks, cx);
18203 });
18204 cx.notify();
18205 }
18206 }
18207
18208 /// Disable inline diagnostics rendering for this editor.
18209 pub fn disable_inline_diagnostics(&mut self) {
18210 self.inline_diagnostics_enabled = false;
18211 self.inline_diagnostics_update = Task::ready(());
18212 self.inline_diagnostics.clear();
18213 }
18214
18215 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18216 self.diagnostics_enabled = false;
18217 self.dismiss_diagnostics(cx);
18218 self.inline_diagnostics_update = Task::ready(());
18219 self.inline_diagnostics.clear();
18220 }
18221
18222 pub fn disable_word_completions(&mut self) {
18223 self.word_completions_enabled = false;
18224 }
18225
18226 pub fn diagnostics_enabled(&self) -> bool {
18227 self.diagnostics_enabled && self.mode.is_full()
18228 }
18229
18230 pub fn inline_diagnostics_enabled(&self) -> bool {
18231 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18232 }
18233
18234 pub fn show_inline_diagnostics(&self) -> bool {
18235 self.show_inline_diagnostics
18236 }
18237
18238 pub fn toggle_inline_diagnostics(
18239 &mut self,
18240 _: &ToggleInlineDiagnostics,
18241 window: &mut Window,
18242 cx: &mut Context<Editor>,
18243 ) {
18244 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18245 self.refresh_inline_diagnostics(false, window, cx);
18246 }
18247
18248 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18249 self.diagnostics_max_severity = severity;
18250 self.display_map.update(cx, |display_map, _| {
18251 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18252 });
18253 }
18254
18255 pub fn toggle_diagnostics(
18256 &mut self,
18257 _: &ToggleDiagnostics,
18258 window: &mut Window,
18259 cx: &mut Context<Editor>,
18260 ) {
18261 if !self.diagnostics_enabled() {
18262 return;
18263 }
18264
18265 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18266 EditorSettings::get_global(cx)
18267 .diagnostics_max_severity
18268 .filter(|severity| severity != &DiagnosticSeverity::Off)
18269 .unwrap_or(DiagnosticSeverity::Hint)
18270 } else {
18271 DiagnosticSeverity::Off
18272 };
18273 self.set_max_diagnostics_severity(new_severity, cx);
18274 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18275 self.active_diagnostics = ActiveDiagnostic::None;
18276 self.inline_diagnostics_update = Task::ready(());
18277 self.inline_diagnostics.clear();
18278 } else {
18279 self.refresh_inline_diagnostics(false, window, cx);
18280 }
18281
18282 cx.notify();
18283 }
18284
18285 pub fn toggle_minimap(
18286 &mut self,
18287 _: &ToggleMinimap,
18288 window: &mut Window,
18289 cx: &mut Context<Editor>,
18290 ) {
18291 if self.supports_minimap(cx) {
18292 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18293 }
18294 }
18295
18296 fn refresh_inline_diagnostics(
18297 &mut self,
18298 debounce: bool,
18299 window: &mut Window,
18300 cx: &mut Context<Self>,
18301 ) {
18302 let max_severity = ProjectSettings::get_global(cx)
18303 .diagnostics
18304 .inline
18305 .max_severity
18306 .unwrap_or(self.diagnostics_max_severity);
18307
18308 if !self.inline_diagnostics_enabled()
18309 || !self.diagnostics_enabled()
18310 || !self.show_inline_diagnostics
18311 || max_severity == DiagnosticSeverity::Off
18312 {
18313 self.inline_diagnostics_update = Task::ready(());
18314 self.inline_diagnostics.clear();
18315 return;
18316 }
18317
18318 let debounce_ms = ProjectSettings::get_global(cx)
18319 .diagnostics
18320 .inline
18321 .update_debounce_ms;
18322 let debounce = if debounce && debounce_ms > 0 {
18323 Some(Duration::from_millis(debounce_ms))
18324 } else {
18325 None
18326 };
18327 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18328 if let Some(debounce) = debounce {
18329 cx.background_executor().timer(debounce).await;
18330 }
18331 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18332 editor
18333 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18334 .ok()
18335 }) else {
18336 return;
18337 };
18338
18339 let new_inline_diagnostics = cx
18340 .background_spawn(async move {
18341 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18342 for diagnostic_entry in
18343 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18344 {
18345 let message = diagnostic_entry
18346 .diagnostic
18347 .message
18348 .split_once('\n')
18349 .map(|(line, _)| line)
18350 .map(SharedString::new)
18351 .unwrap_or_else(|| {
18352 SharedString::new(&*diagnostic_entry.diagnostic.message)
18353 });
18354 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18355 let (Ok(i) | Err(i)) = inline_diagnostics
18356 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18357 inline_diagnostics.insert(
18358 i,
18359 (
18360 start_anchor,
18361 InlineDiagnostic {
18362 message,
18363 group_id: diagnostic_entry.diagnostic.group_id,
18364 start: diagnostic_entry.range.start.to_point(&snapshot),
18365 is_primary: diagnostic_entry.diagnostic.is_primary,
18366 severity: diagnostic_entry.diagnostic.severity,
18367 },
18368 ),
18369 );
18370 }
18371 inline_diagnostics
18372 })
18373 .await;
18374
18375 editor
18376 .update(cx, |editor, cx| {
18377 editor.inline_diagnostics = new_inline_diagnostics;
18378 cx.notify();
18379 })
18380 .ok();
18381 });
18382 }
18383
18384 fn pull_diagnostics(
18385 &mut self,
18386 buffer_id: Option<BufferId>,
18387 window: &Window,
18388 cx: &mut Context<Self>,
18389 ) -> Option<()> {
18390 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18391 return None;
18392 }
18393 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18394 .diagnostics
18395 .lsp_pull_diagnostics;
18396 if !pull_diagnostics_settings.enabled {
18397 return None;
18398 }
18399 let project = self.project()?.downgrade();
18400 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18401 let mut buffers = self.buffer.read(cx).all_buffers();
18402 buffers.retain(|buffer| {
18403 let buffer_id_to_retain = buffer.read(cx).remote_id();
18404 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18405 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18406 });
18407 if buffers.is_empty() {
18408 self.pull_diagnostics_task = Task::ready(());
18409 return None;
18410 }
18411
18412 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18413 cx.background_executor().timer(debounce).await;
18414
18415 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18416 buffers
18417 .into_iter()
18418 .filter_map(|buffer| {
18419 project
18420 .update(cx, |project, cx| {
18421 project.lsp_store().update(cx, |lsp_store, cx| {
18422 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18423 })
18424 })
18425 .ok()
18426 })
18427 .collect::<FuturesUnordered<_>>()
18428 }) else {
18429 return;
18430 };
18431
18432 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18433 match pull_task {
18434 Ok(()) => {
18435 if editor
18436 .update_in(cx, |editor, window, cx| {
18437 editor.update_diagnostics_state(window, cx);
18438 })
18439 .is_err()
18440 {
18441 return;
18442 }
18443 }
18444 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18445 }
18446 }
18447 });
18448
18449 Some(())
18450 }
18451
18452 pub fn set_selections_from_remote(
18453 &mut self,
18454 selections: Vec<Selection<Anchor>>,
18455 pending_selection: Option<Selection<Anchor>>,
18456 window: &mut Window,
18457 cx: &mut Context<Self>,
18458 ) {
18459 let old_cursor_position = self.selections.newest_anchor().head();
18460 self.selections
18461 .change_with(&self.display_snapshot(cx), |s| {
18462 s.select_anchors(selections);
18463 if let Some(pending_selection) = pending_selection {
18464 s.set_pending(pending_selection, SelectMode::Character);
18465 } else {
18466 s.clear_pending();
18467 }
18468 });
18469 self.selections_did_change(
18470 false,
18471 &old_cursor_position,
18472 SelectionEffects::default(),
18473 window,
18474 cx,
18475 );
18476 }
18477
18478 pub fn transact(
18479 &mut self,
18480 window: &mut Window,
18481 cx: &mut Context<Self>,
18482 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18483 ) -> Option<TransactionId> {
18484 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18485 this.start_transaction_at(Instant::now(), window, cx);
18486 update(this, window, cx);
18487 this.end_transaction_at(Instant::now(), cx)
18488 })
18489 }
18490
18491 pub fn start_transaction_at(
18492 &mut self,
18493 now: Instant,
18494 window: &mut Window,
18495 cx: &mut Context<Self>,
18496 ) -> Option<TransactionId> {
18497 self.end_selection(window, cx);
18498 if let Some(tx_id) = self
18499 .buffer
18500 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18501 {
18502 self.selection_history
18503 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18504 cx.emit(EditorEvent::TransactionBegun {
18505 transaction_id: tx_id,
18506 });
18507 Some(tx_id)
18508 } else {
18509 None
18510 }
18511 }
18512
18513 pub fn end_transaction_at(
18514 &mut self,
18515 now: Instant,
18516 cx: &mut Context<Self>,
18517 ) -> Option<TransactionId> {
18518 if let Some(transaction_id) = self
18519 .buffer
18520 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18521 {
18522 if let Some((_, end_selections)) =
18523 self.selection_history.transaction_mut(transaction_id)
18524 {
18525 *end_selections = Some(self.selections.disjoint_anchors_arc());
18526 } else {
18527 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18528 }
18529
18530 cx.emit(EditorEvent::Edited { transaction_id });
18531 Some(transaction_id)
18532 } else {
18533 None
18534 }
18535 }
18536
18537 pub fn modify_transaction_selection_history(
18538 &mut self,
18539 transaction_id: TransactionId,
18540 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18541 ) -> bool {
18542 self.selection_history
18543 .transaction_mut(transaction_id)
18544 .map(modify)
18545 .is_some()
18546 }
18547
18548 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18549 if self.selection_mark_mode {
18550 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18551 s.move_with(|_, sel| {
18552 sel.collapse_to(sel.head(), SelectionGoal::None);
18553 });
18554 })
18555 }
18556 self.selection_mark_mode = true;
18557 cx.notify();
18558 }
18559
18560 pub fn swap_selection_ends(
18561 &mut self,
18562 _: &actions::SwapSelectionEnds,
18563 window: &mut Window,
18564 cx: &mut Context<Self>,
18565 ) {
18566 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18567 s.move_with(|_, sel| {
18568 if sel.start != sel.end {
18569 sel.reversed = !sel.reversed
18570 }
18571 });
18572 });
18573 self.request_autoscroll(Autoscroll::newest(), cx);
18574 cx.notify();
18575 }
18576
18577 pub fn toggle_focus(
18578 workspace: &mut Workspace,
18579 _: &actions::ToggleFocus,
18580 window: &mut Window,
18581 cx: &mut Context<Workspace>,
18582 ) {
18583 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18584 return;
18585 };
18586 workspace.activate_item(&item, true, true, window, cx);
18587 }
18588
18589 pub fn toggle_fold(
18590 &mut self,
18591 _: &actions::ToggleFold,
18592 window: &mut Window,
18593 cx: &mut Context<Self>,
18594 ) {
18595 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18596 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18597 let selection = self.selections.newest::<Point>(&display_map);
18598
18599 let range = if selection.is_empty() {
18600 let point = selection.head().to_display_point(&display_map);
18601 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18602 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18603 .to_point(&display_map);
18604 start..end
18605 } else {
18606 selection.range()
18607 };
18608 if display_map.folds_in_range(range).next().is_some() {
18609 self.unfold_lines(&Default::default(), window, cx)
18610 } else {
18611 self.fold(&Default::default(), window, cx)
18612 }
18613 } else {
18614 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18615 let buffer_ids: HashSet<_> = self
18616 .selections
18617 .disjoint_anchor_ranges()
18618 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18619 .collect();
18620
18621 let should_unfold = buffer_ids
18622 .iter()
18623 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18624
18625 for buffer_id in buffer_ids {
18626 if should_unfold {
18627 self.unfold_buffer(buffer_id, cx);
18628 } else {
18629 self.fold_buffer(buffer_id, cx);
18630 }
18631 }
18632 }
18633 }
18634
18635 pub fn toggle_fold_recursive(
18636 &mut self,
18637 _: &actions::ToggleFoldRecursive,
18638 window: &mut Window,
18639 cx: &mut Context<Self>,
18640 ) {
18641 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18642
18643 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18644 let range = if selection.is_empty() {
18645 let point = selection.head().to_display_point(&display_map);
18646 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18647 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18648 .to_point(&display_map);
18649 start..end
18650 } else {
18651 selection.range()
18652 };
18653 if display_map.folds_in_range(range).next().is_some() {
18654 self.unfold_recursive(&Default::default(), window, cx)
18655 } else {
18656 self.fold_recursive(&Default::default(), window, cx)
18657 }
18658 }
18659
18660 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18661 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18662 let mut to_fold = Vec::new();
18663 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18664 let selections = self.selections.all_adjusted(&display_map);
18665
18666 for selection in selections {
18667 let range = selection.range().sorted();
18668 let buffer_start_row = range.start.row;
18669
18670 if range.start.row != range.end.row {
18671 let mut found = false;
18672 let mut row = range.start.row;
18673 while row <= range.end.row {
18674 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18675 {
18676 found = true;
18677 row = crease.range().end.row + 1;
18678 to_fold.push(crease);
18679 } else {
18680 row += 1
18681 }
18682 }
18683 if found {
18684 continue;
18685 }
18686 }
18687
18688 for row in (0..=range.start.row).rev() {
18689 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18690 && crease.range().end.row >= buffer_start_row
18691 {
18692 to_fold.push(crease);
18693 if row <= range.start.row {
18694 break;
18695 }
18696 }
18697 }
18698 }
18699
18700 self.fold_creases(to_fold, true, window, cx);
18701 } else {
18702 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18703 let buffer_ids = self
18704 .selections
18705 .disjoint_anchor_ranges()
18706 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18707 .collect::<HashSet<_>>();
18708 for buffer_id in buffer_ids {
18709 self.fold_buffer(buffer_id, cx);
18710 }
18711 }
18712 }
18713
18714 pub fn toggle_fold_all(
18715 &mut self,
18716 _: &actions::ToggleFoldAll,
18717 window: &mut Window,
18718 cx: &mut Context<Self>,
18719 ) {
18720 if self.buffer.read(cx).is_singleton() {
18721 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18722 let has_folds = display_map
18723 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18724 .next()
18725 .is_some();
18726
18727 if has_folds {
18728 self.unfold_all(&actions::UnfoldAll, window, cx);
18729 } else {
18730 self.fold_all(&actions::FoldAll, window, cx);
18731 }
18732 } else {
18733 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18734 let should_unfold = buffer_ids
18735 .iter()
18736 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18737
18738 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18739 editor
18740 .update_in(cx, |editor, _, cx| {
18741 for buffer_id in buffer_ids {
18742 if should_unfold {
18743 editor.unfold_buffer(buffer_id, cx);
18744 } else {
18745 editor.fold_buffer(buffer_id, cx);
18746 }
18747 }
18748 })
18749 .ok();
18750 });
18751 }
18752 }
18753
18754 fn fold_at_level(
18755 &mut self,
18756 fold_at: &FoldAtLevel,
18757 window: &mut Window,
18758 cx: &mut Context<Self>,
18759 ) {
18760 if !self.buffer.read(cx).is_singleton() {
18761 return;
18762 }
18763
18764 let fold_at_level = fold_at.0;
18765 let snapshot = self.buffer.read(cx).snapshot(cx);
18766 let mut to_fold = Vec::new();
18767 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18768
18769 let row_ranges_to_keep: Vec<Range<u32>> = self
18770 .selections
18771 .all::<Point>(&self.display_snapshot(cx))
18772 .into_iter()
18773 .map(|sel| sel.start.row..sel.end.row)
18774 .collect();
18775
18776 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18777 while start_row < end_row {
18778 match self
18779 .snapshot(window, cx)
18780 .crease_for_buffer_row(MultiBufferRow(start_row))
18781 {
18782 Some(crease) => {
18783 let nested_start_row = crease.range().start.row + 1;
18784 let nested_end_row = crease.range().end.row;
18785
18786 if current_level < fold_at_level {
18787 stack.push((nested_start_row, nested_end_row, current_level + 1));
18788 } else if current_level == fold_at_level {
18789 // Fold iff there is no selection completely contained within the fold region
18790 if !row_ranges_to_keep.iter().any(|selection| {
18791 selection.end >= nested_start_row
18792 && selection.start <= nested_end_row
18793 }) {
18794 to_fold.push(crease);
18795 }
18796 }
18797
18798 start_row = nested_end_row + 1;
18799 }
18800 None => start_row += 1,
18801 }
18802 }
18803 }
18804
18805 self.fold_creases(to_fold, true, window, cx);
18806 }
18807
18808 pub fn fold_at_level_1(
18809 &mut self,
18810 _: &actions::FoldAtLevel1,
18811 window: &mut Window,
18812 cx: &mut Context<Self>,
18813 ) {
18814 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18815 }
18816
18817 pub fn fold_at_level_2(
18818 &mut self,
18819 _: &actions::FoldAtLevel2,
18820 window: &mut Window,
18821 cx: &mut Context<Self>,
18822 ) {
18823 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18824 }
18825
18826 pub fn fold_at_level_3(
18827 &mut self,
18828 _: &actions::FoldAtLevel3,
18829 window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) {
18832 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18833 }
18834
18835 pub fn fold_at_level_4(
18836 &mut self,
18837 _: &actions::FoldAtLevel4,
18838 window: &mut Window,
18839 cx: &mut Context<Self>,
18840 ) {
18841 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18842 }
18843
18844 pub fn fold_at_level_5(
18845 &mut self,
18846 _: &actions::FoldAtLevel5,
18847 window: &mut Window,
18848 cx: &mut Context<Self>,
18849 ) {
18850 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18851 }
18852
18853 pub fn fold_at_level_6(
18854 &mut self,
18855 _: &actions::FoldAtLevel6,
18856 window: &mut Window,
18857 cx: &mut Context<Self>,
18858 ) {
18859 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18860 }
18861
18862 pub fn fold_at_level_7(
18863 &mut self,
18864 _: &actions::FoldAtLevel7,
18865 window: &mut Window,
18866 cx: &mut Context<Self>,
18867 ) {
18868 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18869 }
18870
18871 pub fn fold_at_level_8(
18872 &mut self,
18873 _: &actions::FoldAtLevel8,
18874 window: &mut Window,
18875 cx: &mut Context<Self>,
18876 ) {
18877 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18878 }
18879
18880 pub fn fold_at_level_9(
18881 &mut self,
18882 _: &actions::FoldAtLevel9,
18883 window: &mut Window,
18884 cx: &mut Context<Self>,
18885 ) {
18886 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18887 }
18888
18889 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18890 if self.buffer.read(cx).is_singleton() {
18891 let mut fold_ranges = Vec::new();
18892 let snapshot = self.buffer.read(cx).snapshot(cx);
18893
18894 for row in 0..snapshot.max_row().0 {
18895 if let Some(foldable_range) = self
18896 .snapshot(window, cx)
18897 .crease_for_buffer_row(MultiBufferRow(row))
18898 {
18899 fold_ranges.push(foldable_range);
18900 }
18901 }
18902
18903 self.fold_creases(fold_ranges, true, window, cx);
18904 } else {
18905 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18906 editor
18907 .update_in(cx, |editor, _, cx| {
18908 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18909 editor.fold_buffer(buffer_id, cx);
18910 }
18911 })
18912 .ok();
18913 });
18914 }
18915 }
18916
18917 pub fn fold_function_bodies(
18918 &mut self,
18919 _: &actions::FoldFunctionBodies,
18920 window: &mut Window,
18921 cx: &mut Context<Self>,
18922 ) {
18923 let snapshot = self.buffer.read(cx).snapshot(cx);
18924
18925 let ranges = snapshot
18926 .text_object_ranges(
18927 MultiBufferOffset(0)..snapshot.len(),
18928 TreeSitterOptions::default(),
18929 )
18930 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18931 .collect::<Vec<_>>();
18932
18933 let creases = ranges
18934 .into_iter()
18935 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18936 .collect();
18937
18938 self.fold_creases(creases, true, window, cx);
18939 }
18940
18941 pub fn fold_recursive(
18942 &mut self,
18943 _: &actions::FoldRecursive,
18944 window: &mut Window,
18945 cx: &mut Context<Self>,
18946 ) {
18947 let mut to_fold = Vec::new();
18948 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18949 let selections = self.selections.all_adjusted(&display_map);
18950
18951 for selection in selections {
18952 let range = selection.range().sorted();
18953 let buffer_start_row = range.start.row;
18954
18955 if range.start.row != range.end.row {
18956 let mut found = false;
18957 for row in range.start.row..=range.end.row {
18958 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18959 found = true;
18960 to_fold.push(crease);
18961 }
18962 }
18963 if found {
18964 continue;
18965 }
18966 }
18967
18968 for row in (0..=range.start.row).rev() {
18969 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18970 if crease.range().end.row >= buffer_start_row {
18971 to_fold.push(crease);
18972 } else {
18973 break;
18974 }
18975 }
18976 }
18977 }
18978
18979 self.fold_creases(to_fold, true, window, cx);
18980 }
18981
18982 pub fn fold_at(
18983 &mut self,
18984 buffer_row: MultiBufferRow,
18985 window: &mut Window,
18986 cx: &mut Context<Self>,
18987 ) {
18988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18989
18990 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18991 let autoscroll = self
18992 .selections
18993 .all::<Point>(&display_map)
18994 .iter()
18995 .any(|selection| crease.range().overlaps(&selection.range()));
18996
18997 self.fold_creases(vec![crease], autoscroll, window, cx);
18998 }
18999 }
19000
19001 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19002 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19003 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19004 let buffer = display_map.buffer_snapshot();
19005 let selections = self.selections.all::<Point>(&display_map);
19006 let ranges = selections
19007 .iter()
19008 .map(|s| {
19009 let range = s.display_range(&display_map).sorted();
19010 let mut start = range.start.to_point(&display_map);
19011 let mut end = range.end.to_point(&display_map);
19012 start.column = 0;
19013 end.column = buffer.line_len(MultiBufferRow(end.row));
19014 start..end
19015 })
19016 .collect::<Vec<_>>();
19017
19018 self.unfold_ranges(&ranges, true, true, cx);
19019 } else {
19020 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19021 let buffer_ids = self
19022 .selections
19023 .disjoint_anchor_ranges()
19024 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19025 .collect::<HashSet<_>>();
19026 for buffer_id in buffer_ids {
19027 self.unfold_buffer(buffer_id, cx);
19028 }
19029 }
19030 }
19031
19032 pub fn unfold_recursive(
19033 &mut self,
19034 _: &UnfoldRecursive,
19035 _window: &mut Window,
19036 cx: &mut Context<Self>,
19037 ) {
19038 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19039 let selections = self.selections.all::<Point>(&display_map);
19040 let ranges = selections
19041 .iter()
19042 .map(|s| {
19043 let mut range = s.display_range(&display_map).sorted();
19044 *range.start.column_mut() = 0;
19045 *range.end.column_mut() = display_map.line_len(range.end.row());
19046 let start = range.start.to_point(&display_map);
19047 let end = range.end.to_point(&display_map);
19048 start..end
19049 })
19050 .collect::<Vec<_>>();
19051
19052 self.unfold_ranges(&ranges, true, true, cx);
19053 }
19054
19055 pub fn unfold_at(
19056 &mut self,
19057 buffer_row: MultiBufferRow,
19058 _window: &mut Window,
19059 cx: &mut Context<Self>,
19060 ) {
19061 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19062
19063 let intersection_range = Point::new(buffer_row.0, 0)
19064 ..Point::new(
19065 buffer_row.0,
19066 display_map.buffer_snapshot().line_len(buffer_row),
19067 );
19068
19069 let autoscroll = self
19070 .selections
19071 .all::<Point>(&display_map)
19072 .iter()
19073 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19074
19075 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19076 }
19077
19078 pub fn unfold_all(
19079 &mut self,
19080 _: &actions::UnfoldAll,
19081 _window: &mut Window,
19082 cx: &mut Context<Self>,
19083 ) {
19084 if self.buffer.read(cx).is_singleton() {
19085 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19086 self.unfold_ranges(
19087 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19088 true,
19089 true,
19090 cx,
19091 );
19092 } else {
19093 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19094 editor
19095 .update(cx, |editor, cx| {
19096 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19097 editor.unfold_buffer(buffer_id, cx);
19098 }
19099 })
19100 .ok();
19101 });
19102 }
19103 }
19104
19105 pub fn fold_selected_ranges(
19106 &mut self,
19107 _: &FoldSelectedRanges,
19108 window: &mut Window,
19109 cx: &mut Context<Self>,
19110 ) {
19111 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19112 let selections = self.selections.all_adjusted(&display_map);
19113 let ranges = selections
19114 .into_iter()
19115 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19116 .collect::<Vec<_>>();
19117 self.fold_creases(ranges, true, window, cx);
19118 }
19119
19120 pub fn fold_ranges<T: ToOffset + Clone>(
19121 &mut self,
19122 ranges: Vec<Range<T>>,
19123 auto_scroll: bool,
19124 window: &mut Window,
19125 cx: &mut Context<Self>,
19126 ) {
19127 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19128 let ranges = ranges
19129 .into_iter()
19130 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19131 .collect::<Vec<_>>();
19132 self.fold_creases(ranges, auto_scroll, window, cx);
19133 }
19134
19135 pub fn fold_creases<T: ToOffset + Clone>(
19136 &mut self,
19137 creases: Vec<Crease<T>>,
19138 auto_scroll: bool,
19139 _window: &mut Window,
19140 cx: &mut Context<Self>,
19141 ) {
19142 if creases.is_empty() {
19143 return;
19144 }
19145
19146 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19147
19148 if auto_scroll {
19149 self.request_autoscroll(Autoscroll::fit(), cx);
19150 }
19151
19152 cx.notify();
19153
19154 self.scrollbar_marker_state.dirty = true;
19155 self.folds_did_change(cx);
19156 }
19157
19158 /// Removes any folds whose ranges intersect any of the given ranges.
19159 pub fn unfold_ranges<T: ToOffset + Clone>(
19160 &mut self,
19161 ranges: &[Range<T>],
19162 inclusive: bool,
19163 auto_scroll: bool,
19164 cx: &mut Context<Self>,
19165 ) {
19166 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19167 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19168 });
19169 self.folds_did_change(cx);
19170 }
19171
19172 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19173 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19174 return;
19175 }
19176
19177 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19178 self.display_map.update(cx, |display_map, cx| {
19179 display_map.fold_buffers([buffer_id], cx)
19180 });
19181
19182 let snapshot = self.display_snapshot(cx);
19183 self.selections.change_with(&snapshot, |selections| {
19184 selections.remove_selections_from_buffer(buffer_id);
19185 });
19186
19187 cx.emit(EditorEvent::BufferFoldToggled {
19188 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19189 folded: true,
19190 });
19191 cx.notify();
19192 }
19193
19194 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19195 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19196 return;
19197 }
19198 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19199 self.display_map.update(cx, |display_map, cx| {
19200 display_map.unfold_buffers([buffer_id], cx);
19201 });
19202 cx.emit(EditorEvent::BufferFoldToggled {
19203 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19204 folded: false,
19205 });
19206 cx.notify();
19207 }
19208
19209 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19210 self.display_map.read(cx).is_buffer_folded(buffer)
19211 }
19212
19213 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19214 self.display_map.read(cx).folded_buffers()
19215 }
19216
19217 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19218 self.display_map.update(cx, |display_map, cx| {
19219 display_map.disable_header_for_buffer(buffer_id, cx);
19220 });
19221 cx.notify();
19222 }
19223
19224 /// Removes any folds with the given ranges.
19225 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19226 &mut self,
19227 ranges: &[Range<T>],
19228 type_id: TypeId,
19229 auto_scroll: bool,
19230 cx: &mut Context<Self>,
19231 ) {
19232 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19233 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19234 });
19235 self.folds_did_change(cx);
19236 }
19237
19238 fn remove_folds_with<T: ToOffset + Clone>(
19239 &mut self,
19240 ranges: &[Range<T>],
19241 auto_scroll: bool,
19242 cx: &mut Context<Self>,
19243 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19244 ) {
19245 if ranges.is_empty() {
19246 return;
19247 }
19248
19249 let mut buffers_affected = HashSet::default();
19250 let multi_buffer = self.buffer().read(cx);
19251 for range in ranges {
19252 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19253 buffers_affected.insert(buffer.read(cx).remote_id());
19254 };
19255 }
19256
19257 self.display_map.update(cx, update);
19258
19259 if auto_scroll {
19260 self.request_autoscroll(Autoscroll::fit(), cx);
19261 }
19262
19263 cx.notify();
19264 self.scrollbar_marker_state.dirty = true;
19265 self.active_indent_guides_state.dirty = true;
19266 }
19267
19268 pub fn update_renderer_widths(
19269 &mut self,
19270 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19271 cx: &mut Context<Self>,
19272 ) -> bool {
19273 self.display_map
19274 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19275 }
19276
19277 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19278 self.display_map.read(cx).fold_placeholder.clone()
19279 }
19280
19281 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19282 self.use_base_text_line_numbers = show;
19283 }
19284
19285 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19286 self.buffer.update(cx, |buffer, cx| {
19287 buffer.set_all_diff_hunks_expanded(cx);
19288 });
19289 }
19290
19291 pub fn expand_all_diff_hunks(
19292 &mut self,
19293 _: &ExpandAllDiffHunks,
19294 _window: &mut Window,
19295 cx: &mut Context<Self>,
19296 ) {
19297 self.buffer.update(cx, |buffer, cx| {
19298 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19299 });
19300 }
19301
19302 pub fn collapse_all_diff_hunks(
19303 &mut self,
19304 _: &CollapseAllDiffHunks,
19305 _window: &mut Window,
19306 cx: &mut Context<Self>,
19307 ) {
19308 self.buffer.update(cx, |buffer, cx| {
19309 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19310 });
19311 }
19312
19313 pub fn toggle_selected_diff_hunks(
19314 &mut self,
19315 _: &ToggleSelectedDiffHunks,
19316 _window: &mut Window,
19317 cx: &mut Context<Self>,
19318 ) {
19319 let ranges: Vec<_> = self
19320 .selections
19321 .disjoint_anchors()
19322 .iter()
19323 .map(|s| s.range())
19324 .collect();
19325 self.toggle_diff_hunks_in_ranges(ranges, cx);
19326 }
19327
19328 pub fn diff_hunks_in_ranges<'a>(
19329 &'a self,
19330 ranges: &'a [Range<Anchor>],
19331 buffer: &'a MultiBufferSnapshot,
19332 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19333 ranges.iter().flat_map(move |range| {
19334 let end_excerpt_id = range.end.excerpt_id;
19335 let range = range.to_point(buffer);
19336 let mut peek_end = range.end;
19337 if range.end.row < buffer.max_row().0 {
19338 peek_end = Point::new(range.end.row + 1, 0);
19339 }
19340 buffer
19341 .diff_hunks_in_range(range.start..peek_end)
19342 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19343 })
19344 }
19345
19346 pub fn has_stageable_diff_hunks_in_ranges(
19347 &self,
19348 ranges: &[Range<Anchor>],
19349 snapshot: &MultiBufferSnapshot,
19350 ) -> bool {
19351 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19352 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19353 }
19354
19355 pub fn toggle_staged_selected_diff_hunks(
19356 &mut self,
19357 _: &::git::ToggleStaged,
19358 _: &mut Window,
19359 cx: &mut Context<Self>,
19360 ) {
19361 let snapshot = self.buffer.read(cx).snapshot(cx);
19362 let ranges: Vec<_> = self
19363 .selections
19364 .disjoint_anchors()
19365 .iter()
19366 .map(|s| s.range())
19367 .collect();
19368 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19369 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19370 }
19371
19372 pub fn set_render_diff_hunk_controls(
19373 &mut self,
19374 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19375 cx: &mut Context<Self>,
19376 ) {
19377 self.render_diff_hunk_controls = render_diff_hunk_controls;
19378 cx.notify();
19379 }
19380
19381 pub fn stage_and_next(
19382 &mut self,
19383 _: &::git::StageAndNext,
19384 window: &mut Window,
19385 cx: &mut Context<Self>,
19386 ) {
19387 self.do_stage_or_unstage_and_next(true, window, cx);
19388 }
19389
19390 pub fn unstage_and_next(
19391 &mut self,
19392 _: &::git::UnstageAndNext,
19393 window: &mut Window,
19394 cx: &mut Context<Self>,
19395 ) {
19396 self.do_stage_or_unstage_and_next(false, window, cx);
19397 }
19398
19399 pub fn stage_or_unstage_diff_hunks(
19400 &mut self,
19401 stage: bool,
19402 ranges: Vec<Range<Anchor>>,
19403 cx: &mut Context<Self>,
19404 ) {
19405 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19406 cx.spawn(async move |this, cx| {
19407 task.await?;
19408 this.update(cx, |this, cx| {
19409 let snapshot = this.buffer.read(cx).snapshot(cx);
19410 let chunk_by = this
19411 .diff_hunks_in_ranges(&ranges, &snapshot)
19412 .chunk_by(|hunk| hunk.buffer_id);
19413 for (buffer_id, hunks) in &chunk_by {
19414 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19415 }
19416 })
19417 })
19418 .detach_and_log_err(cx);
19419 }
19420
19421 fn save_buffers_for_ranges_if_needed(
19422 &mut self,
19423 ranges: &[Range<Anchor>],
19424 cx: &mut Context<Editor>,
19425 ) -> Task<Result<()>> {
19426 let multibuffer = self.buffer.read(cx);
19427 let snapshot = multibuffer.read(cx);
19428 let buffer_ids: HashSet<_> = ranges
19429 .iter()
19430 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19431 .collect();
19432 drop(snapshot);
19433
19434 let mut buffers = HashSet::default();
19435 for buffer_id in buffer_ids {
19436 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19437 let buffer = buffer_entity.read(cx);
19438 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19439 {
19440 buffers.insert(buffer_entity);
19441 }
19442 }
19443 }
19444
19445 if let Some(project) = &self.project {
19446 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19447 } else {
19448 Task::ready(Ok(()))
19449 }
19450 }
19451
19452 fn do_stage_or_unstage_and_next(
19453 &mut self,
19454 stage: bool,
19455 window: &mut Window,
19456 cx: &mut Context<Self>,
19457 ) {
19458 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19459
19460 if ranges.iter().any(|range| range.start != range.end) {
19461 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19462 return;
19463 }
19464
19465 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19466 let snapshot = self.snapshot(window, cx);
19467 let position = self
19468 .selections
19469 .newest::<Point>(&snapshot.display_snapshot)
19470 .head();
19471 let mut row = snapshot
19472 .buffer_snapshot()
19473 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19474 .find(|hunk| hunk.row_range.start.0 > position.row)
19475 .map(|hunk| hunk.row_range.start);
19476
19477 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19478 // Outside of the project diff editor, wrap around to the beginning.
19479 if !all_diff_hunks_expanded {
19480 row = row.or_else(|| {
19481 snapshot
19482 .buffer_snapshot()
19483 .diff_hunks_in_range(Point::zero()..position)
19484 .find(|hunk| hunk.row_range.end.0 < position.row)
19485 .map(|hunk| hunk.row_range.start)
19486 });
19487 }
19488
19489 if let Some(row) = row {
19490 let destination = Point::new(row.0, 0);
19491 let autoscroll = Autoscroll::center();
19492
19493 self.unfold_ranges(&[destination..destination], false, false, cx);
19494 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19495 s.select_ranges([destination..destination]);
19496 });
19497 }
19498 }
19499
19500 fn do_stage_or_unstage(
19501 &self,
19502 stage: bool,
19503 buffer_id: BufferId,
19504 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19505 cx: &mut App,
19506 ) -> Option<()> {
19507 let project = self.project()?;
19508 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19509 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19510 let buffer_snapshot = buffer.read(cx).snapshot();
19511 let file_exists = buffer_snapshot
19512 .file()
19513 .is_some_and(|file| file.disk_state().exists());
19514 diff.update(cx, |diff, cx| {
19515 diff.stage_or_unstage_hunks(
19516 stage,
19517 &hunks
19518 .map(|hunk| buffer_diff::DiffHunk {
19519 buffer_range: hunk.buffer_range,
19520 // We don't need to pass in word diffs here because they're only used for rendering and
19521 // this function changes internal state
19522 base_word_diffs: Vec::default(),
19523 buffer_word_diffs: Vec::default(),
19524 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19525 ..hunk.diff_base_byte_range.end.0,
19526 secondary_status: hunk.secondary_status,
19527 range: Point::zero()..Point::zero(), // unused
19528 })
19529 .collect::<Vec<_>>(),
19530 &buffer_snapshot,
19531 file_exists,
19532 cx,
19533 )
19534 });
19535 None
19536 }
19537
19538 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19539 let ranges: Vec<_> = self
19540 .selections
19541 .disjoint_anchors()
19542 .iter()
19543 .map(|s| s.range())
19544 .collect();
19545 self.buffer
19546 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19547 }
19548
19549 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19550 self.buffer.update(cx, |buffer, cx| {
19551 let ranges = vec![Anchor::min()..Anchor::max()];
19552 if !buffer.all_diff_hunks_expanded()
19553 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19554 {
19555 buffer.collapse_diff_hunks(ranges, cx);
19556 true
19557 } else {
19558 false
19559 }
19560 })
19561 }
19562
19563 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19564 if self.buffer.read(cx).all_diff_hunks_expanded() {
19565 return true;
19566 }
19567 let ranges = vec![Anchor::min()..Anchor::max()];
19568 self.buffer
19569 .read(cx)
19570 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19571 }
19572
19573 fn toggle_diff_hunks_in_ranges(
19574 &mut self,
19575 ranges: Vec<Range<Anchor>>,
19576 cx: &mut Context<Editor>,
19577 ) {
19578 self.buffer.update(cx, |buffer, cx| {
19579 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19580 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19581 })
19582 }
19583
19584 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19585 self.buffer.update(cx, |buffer, cx| {
19586 let snapshot = buffer.snapshot(cx);
19587 let excerpt_id = range.end.excerpt_id;
19588 let point_range = range.to_point(&snapshot);
19589 let expand = !buffer.single_hunk_is_expanded(range, cx);
19590 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19591 })
19592 }
19593
19594 pub(crate) fn apply_all_diff_hunks(
19595 &mut self,
19596 _: &ApplyAllDiffHunks,
19597 window: &mut Window,
19598 cx: &mut Context<Self>,
19599 ) {
19600 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19601
19602 let buffers = self.buffer.read(cx).all_buffers();
19603 for branch_buffer in buffers {
19604 branch_buffer.update(cx, |branch_buffer, cx| {
19605 branch_buffer.merge_into_base(Vec::new(), cx);
19606 });
19607 }
19608
19609 if let Some(project) = self.project.clone() {
19610 self.save(
19611 SaveOptions {
19612 format: true,
19613 autosave: false,
19614 },
19615 project,
19616 window,
19617 cx,
19618 )
19619 .detach_and_log_err(cx);
19620 }
19621 }
19622
19623 pub(crate) fn apply_selected_diff_hunks(
19624 &mut self,
19625 _: &ApplyDiffHunk,
19626 window: &mut Window,
19627 cx: &mut Context<Self>,
19628 ) {
19629 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19630 let snapshot = self.snapshot(window, cx);
19631 let hunks = snapshot.hunks_for_ranges(
19632 self.selections
19633 .all(&snapshot.display_snapshot)
19634 .into_iter()
19635 .map(|selection| selection.range()),
19636 );
19637 let mut ranges_by_buffer = HashMap::default();
19638 self.transact(window, cx, |editor, _window, cx| {
19639 for hunk in hunks {
19640 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19641 ranges_by_buffer
19642 .entry(buffer.clone())
19643 .or_insert_with(Vec::new)
19644 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19645 }
19646 }
19647
19648 for (buffer, ranges) in ranges_by_buffer {
19649 buffer.update(cx, |buffer, cx| {
19650 buffer.merge_into_base(ranges, cx);
19651 });
19652 }
19653 });
19654
19655 if let Some(project) = self.project.clone() {
19656 self.save(
19657 SaveOptions {
19658 format: true,
19659 autosave: false,
19660 },
19661 project,
19662 window,
19663 cx,
19664 )
19665 .detach_and_log_err(cx);
19666 }
19667 }
19668
19669 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19670 if hovered != self.gutter_hovered {
19671 self.gutter_hovered = hovered;
19672 cx.notify();
19673 }
19674 }
19675
19676 pub fn insert_blocks(
19677 &mut self,
19678 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19679 autoscroll: Option<Autoscroll>,
19680 cx: &mut Context<Self>,
19681 ) -> Vec<CustomBlockId> {
19682 let blocks = self
19683 .display_map
19684 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19685 if let Some(autoscroll) = autoscroll {
19686 self.request_autoscroll(autoscroll, cx);
19687 }
19688 cx.notify();
19689 blocks
19690 }
19691
19692 pub fn resize_blocks(
19693 &mut self,
19694 heights: HashMap<CustomBlockId, u32>,
19695 autoscroll: Option<Autoscroll>,
19696 cx: &mut Context<Self>,
19697 ) {
19698 self.display_map
19699 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19700 if let Some(autoscroll) = autoscroll {
19701 self.request_autoscroll(autoscroll, cx);
19702 }
19703 cx.notify();
19704 }
19705
19706 pub fn replace_blocks(
19707 &mut self,
19708 renderers: HashMap<CustomBlockId, RenderBlock>,
19709 autoscroll: Option<Autoscroll>,
19710 cx: &mut Context<Self>,
19711 ) {
19712 self.display_map
19713 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19714 if let Some(autoscroll) = autoscroll {
19715 self.request_autoscroll(autoscroll, cx);
19716 }
19717 cx.notify();
19718 }
19719
19720 pub fn remove_blocks(
19721 &mut self,
19722 block_ids: HashSet<CustomBlockId>,
19723 autoscroll: Option<Autoscroll>,
19724 cx: &mut Context<Self>,
19725 ) {
19726 self.display_map.update(cx, |display_map, cx| {
19727 display_map.remove_blocks(block_ids, cx)
19728 });
19729 if let Some(autoscroll) = autoscroll {
19730 self.request_autoscroll(autoscroll, cx);
19731 }
19732 cx.notify();
19733 }
19734
19735 pub fn row_for_block(
19736 &self,
19737 block_id: CustomBlockId,
19738 cx: &mut Context<Self>,
19739 ) -> Option<DisplayRow> {
19740 self.display_map
19741 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19742 }
19743
19744 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19745 self.focused_block = Some(focused_block);
19746 }
19747
19748 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19749 self.focused_block.take()
19750 }
19751
19752 pub fn insert_creases(
19753 &mut self,
19754 creases: impl IntoIterator<Item = Crease<Anchor>>,
19755 cx: &mut Context<Self>,
19756 ) -> Vec<CreaseId> {
19757 self.display_map
19758 .update(cx, |map, cx| map.insert_creases(creases, cx))
19759 }
19760
19761 pub fn remove_creases(
19762 &mut self,
19763 ids: impl IntoIterator<Item = CreaseId>,
19764 cx: &mut Context<Self>,
19765 ) -> Vec<(CreaseId, Range<Anchor>)> {
19766 self.display_map
19767 .update(cx, |map, cx| map.remove_creases(ids, cx))
19768 }
19769
19770 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19771 self.display_map
19772 .update(cx, |map, cx| map.snapshot(cx))
19773 .longest_row()
19774 }
19775
19776 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19777 self.display_map
19778 .update(cx, |map, cx| map.snapshot(cx))
19779 .max_point()
19780 }
19781
19782 pub fn text(&self, cx: &App) -> String {
19783 self.buffer.read(cx).read(cx).text()
19784 }
19785
19786 pub fn is_empty(&self, cx: &App) -> bool {
19787 self.buffer.read(cx).read(cx).is_empty()
19788 }
19789
19790 pub fn text_option(&self, cx: &App) -> Option<String> {
19791 let text = self.text(cx);
19792 let text = text.trim();
19793
19794 if text.is_empty() {
19795 return None;
19796 }
19797
19798 Some(text.to_string())
19799 }
19800
19801 pub fn set_text(
19802 &mut self,
19803 text: impl Into<Arc<str>>,
19804 window: &mut Window,
19805 cx: &mut Context<Self>,
19806 ) {
19807 self.transact(window, cx, |this, _, cx| {
19808 this.buffer
19809 .read(cx)
19810 .as_singleton()
19811 .expect("you can only call set_text on editors for singleton buffers")
19812 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19813 });
19814 }
19815
19816 pub fn display_text(&self, cx: &mut App) -> String {
19817 self.display_map
19818 .update(cx, |map, cx| map.snapshot(cx))
19819 .text()
19820 }
19821
19822 fn create_minimap(
19823 &self,
19824 minimap_settings: MinimapSettings,
19825 window: &mut Window,
19826 cx: &mut Context<Self>,
19827 ) -> Option<Entity<Self>> {
19828 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19829 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19830 }
19831
19832 fn initialize_new_minimap(
19833 &self,
19834 minimap_settings: MinimapSettings,
19835 window: &mut Window,
19836 cx: &mut Context<Self>,
19837 ) -> Entity<Self> {
19838 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19839
19840 let mut minimap = Editor::new_internal(
19841 EditorMode::Minimap {
19842 parent: cx.weak_entity(),
19843 },
19844 self.buffer.clone(),
19845 None,
19846 Some(self.display_map.clone()),
19847 window,
19848 cx,
19849 );
19850 minimap.scroll_manager.clone_state(&self.scroll_manager);
19851 minimap.set_text_style_refinement(TextStyleRefinement {
19852 font_size: Some(MINIMAP_FONT_SIZE),
19853 font_weight: Some(MINIMAP_FONT_WEIGHT),
19854 ..Default::default()
19855 });
19856 minimap.update_minimap_configuration(minimap_settings, cx);
19857 cx.new(|_| minimap)
19858 }
19859
19860 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19861 let current_line_highlight = minimap_settings
19862 .current_line_highlight
19863 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19864 self.set_current_line_highlight(Some(current_line_highlight));
19865 }
19866
19867 pub fn minimap(&self) -> Option<&Entity<Self>> {
19868 self.minimap
19869 .as_ref()
19870 .filter(|_| self.minimap_visibility.visible())
19871 }
19872
19873 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19874 let mut wrap_guides = smallvec![];
19875
19876 if self.show_wrap_guides == Some(false) {
19877 return wrap_guides;
19878 }
19879
19880 let settings = self.buffer.read(cx).language_settings(cx);
19881 if settings.show_wrap_guides {
19882 match self.soft_wrap_mode(cx) {
19883 SoftWrap::Column(soft_wrap) => {
19884 wrap_guides.push((soft_wrap as usize, true));
19885 }
19886 SoftWrap::Bounded(soft_wrap) => {
19887 wrap_guides.push((soft_wrap as usize, true));
19888 }
19889 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19890 }
19891 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19892 }
19893
19894 wrap_guides
19895 }
19896
19897 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19898 let settings = self.buffer.read(cx).language_settings(cx);
19899 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19900 match mode {
19901 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19902 SoftWrap::None
19903 }
19904 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19905 language_settings::SoftWrap::PreferredLineLength => {
19906 SoftWrap::Column(settings.preferred_line_length)
19907 }
19908 language_settings::SoftWrap::Bounded => {
19909 SoftWrap::Bounded(settings.preferred_line_length)
19910 }
19911 }
19912 }
19913
19914 pub fn set_soft_wrap_mode(
19915 &mut self,
19916 mode: language_settings::SoftWrap,
19917
19918 cx: &mut Context<Self>,
19919 ) {
19920 self.soft_wrap_mode_override = Some(mode);
19921 cx.notify();
19922 }
19923
19924 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19925 self.hard_wrap = hard_wrap;
19926 cx.notify();
19927 }
19928
19929 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19930 self.text_style_refinement = Some(style);
19931 }
19932
19933 /// called by the Element so we know what style we were most recently rendered with.
19934 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19935 // We intentionally do not inform the display map about the minimap style
19936 // so that wrapping is not recalculated and stays consistent for the editor
19937 // and its linked minimap.
19938 if !self.mode.is_minimap() {
19939 let font = style.text.font();
19940 let font_size = style.text.font_size.to_pixels(window.rem_size());
19941 let display_map = self
19942 .placeholder_display_map
19943 .as_ref()
19944 .filter(|_| self.is_empty(cx))
19945 .unwrap_or(&self.display_map);
19946
19947 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19948 }
19949 self.style = Some(style);
19950 }
19951
19952 pub fn style(&self) -> Option<&EditorStyle> {
19953 self.style.as_ref()
19954 }
19955
19956 // Called by the element. This method is not designed to be called outside of the editor
19957 // element's layout code because it does not notify when rewrapping is computed synchronously.
19958 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19959 if self.is_empty(cx) {
19960 self.placeholder_display_map
19961 .as_ref()
19962 .map_or(false, |display_map| {
19963 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19964 })
19965 } else {
19966 self.display_map
19967 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19968 }
19969 }
19970
19971 pub fn set_soft_wrap(&mut self) {
19972 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19973 }
19974
19975 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19976 if self.soft_wrap_mode_override.is_some() {
19977 self.soft_wrap_mode_override.take();
19978 } else {
19979 let soft_wrap = match self.soft_wrap_mode(cx) {
19980 SoftWrap::GitDiff => return,
19981 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19982 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19983 language_settings::SoftWrap::None
19984 }
19985 };
19986 self.soft_wrap_mode_override = Some(soft_wrap);
19987 }
19988 cx.notify();
19989 }
19990
19991 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19992 let Some(workspace) = self.workspace() else {
19993 return;
19994 };
19995 let fs = workspace.read(cx).app_state().fs.clone();
19996 let current_show = TabBarSettings::get_global(cx).show;
19997 update_settings_file(fs, cx, move |setting, _| {
19998 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19999 });
20000 }
20001
20002 pub fn toggle_indent_guides(
20003 &mut self,
20004 _: &ToggleIndentGuides,
20005 _: &mut Window,
20006 cx: &mut Context<Self>,
20007 ) {
20008 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20009 self.buffer
20010 .read(cx)
20011 .language_settings(cx)
20012 .indent_guides
20013 .enabled
20014 });
20015 self.show_indent_guides = Some(!currently_enabled);
20016 cx.notify();
20017 }
20018
20019 fn should_show_indent_guides(&self) -> Option<bool> {
20020 self.show_indent_guides
20021 }
20022
20023 pub fn toggle_line_numbers(
20024 &mut self,
20025 _: &ToggleLineNumbers,
20026 _: &mut Window,
20027 cx: &mut Context<Self>,
20028 ) {
20029 let mut editor_settings = EditorSettings::get_global(cx).clone();
20030 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20031 EditorSettings::override_global(editor_settings, cx);
20032 }
20033
20034 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20035 if let Some(show_line_numbers) = self.show_line_numbers {
20036 return show_line_numbers;
20037 }
20038 EditorSettings::get_global(cx).gutter.line_numbers
20039 }
20040
20041 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20042 match (
20043 self.use_relative_line_numbers,
20044 EditorSettings::get_global(cx).relative_line_numbers,
20045 ) {
20046 (None, setting) => setting,
20047 (Some(false), _) => RelativeLineNumbers::Disabled,
20048 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20049 (Some(true), _) => RelativeLineNumbers::Enabled,
20050 }
20051 }
20052
20053 pub fn toggle_relative_line_numbers(
20054 &mut self,
20055 _: &ToggleRelativeLineNumbers,
20056 _: &mut Window,
20057 cx: &mut Context<Self>,
20058 ) {
20059 let is_relative = self.relative_line_numbers(cx);
20060 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20061 }
20062
20063 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20064 self.use_relative_line_numbers = is_relative;
20065 cx.notify();
20066 }
20067
20068 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20069 self.show_gutter = show_gutter;
20070 cx.notify();
20071 }
20072
20073 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20074 self.show_scrollbars = ScrollbarAxes {
20075 horizontal: show,
20076 vertical: show,
20077 };
20078 cx.notify();
20079 }
20080
20081 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20082 self.show_scrollbars.vertical = show;
20083 cx.notify();
20084 }
20085
20086 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20087 self.show_scrollbars.horizontal = show;
20088 cx.notify();
20089 }
20090
20091 pub fn set_minimap_visibility(
20092 &mut self,
20093 minimap_visibility: MinimapVisibility,
20094 window: &mut Window,
20095 cx: &mut Context<Self>,
20096 ) {
20097 if self.minimap_visibility != minimap_visibility {
20098 if minimap_visibility.visible() && self.minimap.is_none() {
20099 let minimap_settings = EditorSettings::get_global(cx).minimap;
20100 self.minimap =
20101 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20102 }
20103 self.minimap_visibility = minimap_visibility;
20104 cx.notify();
20105 }
20106 }
20107
20108 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20109 self.set_show_scrollbars(false, cx);
20110 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20111 }
20112
20113 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20114 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20115 }
20116
20117 /// Normally the text in full mode and auto height editors is padded on the
20118 /// left side by roughly half a character width for improved hit testing.
20119 ///
20120 /// Use this method to disable this for cases where this is not wanted (e.g.
20121 /// if you want to align the editor text with some other text above or below)
20122 /// or if you want to add this padding to single-line editors.
20123 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20124 self.offset_content = offset_content;
20125 cx.notify();
20126 }
20127
20128 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20129 self.show_line_numbers = Some(show_line_numbers);
20130 cx.notify();
20131 }
20132
20133 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20134 self.disable_expand_excerpt_buttons = true;
20135 cx.notify();
20136 }
20137
20138 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20139 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20140 cx.notify();
20141 }
20142
20143 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20144 self.show_code_actions = Some(show_code_actions);
20145 cx.notify();
20146 }
20147
20148 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20149 self.show_runnables = Some(show_runnables);
20150 cx.notify();
20151 }
20152
20153 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20154 self.show_breakpoints = Some(show_breakpoints);
20155 cx.notify();
20156 }
20157
20158 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20159 if self.display_map.read(cx).masked != masked {
20160 self.display_map.update(cx, |map, _| map.masked = masked);
20161 }
20162 cx.notify()
20163 }
20164
20165 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20166 self.show_wrap_guides = Some(show_wrap_guides);
20167 cx.notify();
20168 }
20169
20170 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20171 self.show_indent_guides = Some(show_indent_guides);
20172 cx.notify();
20173 }
20174
20175 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20176 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20177 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20178 && let Some(dir) = file.abs_path(cx).parent()
20179 {
20180 return Some(dir.to_owned());
20181 }
20182 }
20183
20184 None
20185 }
20186
20187 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20188 self.active_excerpt(cx)?
20189 .1
20190 .read(cx)
20191 .file()
20192 .and_then(|f| f.as_local())
20193 }
20194
20195 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20196 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20197 let buffer = buffer.read(cx);
20198 if let Some(project_path) = buffer.project_path(cx) {
20199 let project = self.project()?.read(cx);
20200 project.absolute_path(&project_path, cx)
20201 } else {
20202 buffer
20203 .file()
20204 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20205 }
20206 })
20207 }
20208
20209 pub fn reveal_in_finder(
20210 &mut self,
20211 _: &RevealInFileManager,
20212 _window: &mut Window,
20213 cx: &mut Context<Self>,
20214 ) {
20215 if let Some(target) = self.target_file(cx) {
20216 cx.reveal_path(&target.abs_path(cx));
20217 }
20218 }
20219
20220 pub fn copy_path(
20221 &mut self,
20222 _: &zed_actions::workspace::CopyPath,
20223 _window: &mut Window,
20224 cx: &mut Context<Self>,
20225 ) {
20226 if let Some(path) = self.target_file_abs_path(cx)
20227 && let Some(path) = path.to_str()
20228 {
20229 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20230 } else {
20231 cx.propagate();
20232 }
20233 }
20234
20235 pub fn copy_relative_path(
20236 &mut self,
20237 _: &zed_actions::workspace::CopyRelativePath,
20238 _window: &mut Window,
20239 cx: &mut Context<Self>,
20240 ) {
20241 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20242 let project = self.project()?.read(cx);
20243 let path = buffer.read(cx).file()?.path();
20244 let path = path.display(project.path_style(cx));
20245 Some(path)
20246 }) {
20247 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20248 } else {
20249 cx.propagate();
20250 }
20251 }
20252
20253 /// Returns the project path for the editor's buffer, if any buffer is
20254 /// opened in the editor.
20255 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20256 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20257 buffer.read(cx).project_path(cx)
20258 } else {
20259 None
20260 }
20261 }
20262
20263 // Returns true if the editor handled a go-to-line request
20264 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20265 maybe!({
20266 let breakpoint_store = self.breakpoint_store.as_ref()?;
20267
20268 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20269 else {
20270 self.clear_row_highlights::<ActiveDebugLine>();
20271 return None;
20272 };
20273
20274 let position = active_stack_frame.position;
20275 let buffer_id = position.buffer_id?;
20276 let snapshot = self
20277 .project
20278 .as_ref()?
20279 .read(cx)
20280 .buffer_for_id(buffer_id, cx)?
20281 .read(cx)
20282 .snapshot();
20283
20284 let mut handled = false;
20285 for (id, ExcerptRange { context, .. }) in
20286 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20287 {
20288 if context.start.cmp(&position, &snapshot).is_ge()
20289 || context.end.cmp(&position, &snapshot).is_lt()
20290 {
20291 continue;
20292 }
20293 let snapshot = self.buffer.read(cx).snapshot(cx);
20294 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20295
20296 handled = true;
20297 self.clear_row_highlights::<ActiveDebugLine>();
20298
20299 self.go_to_line::<ActiveDebugLine>(
20300 multibuffer_anchor,
20301 Some(cx.theme().colors().editor_debugger_active_line_background),
20302 window,
20303 cx,
20304 );
20305
20306 cx.notify();
20307 }
20308
20309 handled.then_some(())
20310 })
20311 .is_some()
20312 }
20313
20314 pub fn copy_file_name_without_extension(
20315 &mut self,
20316 _: &CopyFileNameWithoutExtension,
20317 _: &mut Window,
20318 cx: &mut Context<Self>,
20319 ) {
20320 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20321 let file = buffer.read(cx).file()?;
20322 file.path().file_stem()
20323 }) {
20324 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20325 }
20326 }
20327
20328 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20329 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20330 let file = buffer.read(cx).file()?;
20331 Some(file.file_name(cx))
20332 }) {
20333 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20334 }
20335 }
20336
20337 pub fn toggle_git_blame(
20338 &mut self,
20339 _: &::git::Blame,
20340 window: &mut Window,
20341 cx: &mut Context<Self>,
20342 ) {
20343 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20344
20345 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20346 self.start_git_blame(true, window, cx);
20347 }
20348
20349 cx.notify();
20350 }
20351
20352 pub fn toggle_git_blame_inline(
20353 &mut self,
20354 _: &ToggleGitBlameInline,
20355 window: &mut Window,
20356 cx: &mut Context<Self>,
20357 ) {
20358 self.toggle_git_blame_inline_internal(true, window, cx);
20359 cx.notify();
20360 }
20361
20362 pub fn open_git_blame_commit(
20363 &mut self,
20364 _: &OpenGitBlameCommit,
20365 window: &mut Window,
20366 cx: &mut Context<Self>,
20367 ) {
20368 self.open_git_blame_commit_internal(window, cx);
20369 }
20370
20371 fn open_git_blame_commit_internal(
20372 &mut self,
20373 window: &mut Window,
20374 cx: &mut Context<Self>,
20375 ) -> Option<()> {
20376 let blame = self.blame.as_ref()?;
20377 let snapshot = self.snapshot(window, cx);
20378 let cursor = self
20379 .selections
20380 .newest::<Point>(&snapshot.display_snapshot)
20381 .head();
20382 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20383 let (_, blame_entry) = blame
20384 .update(cx, |blame, cx| {
20385 blame
20386 .blame_for_rows(
20387 &[RowInfo {
20388 buffer_id: Some(buffer.remote_id()),
20389 buffer_row: Some(point.row),
20390 ..Default::default()
20391 }],
20392 cx,
20393 )
20394 .next()
20395 })
20396 .flatten()?;
20397 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20398 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20399 let workspace = self.workspace()?.downgrade();
20400 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20401 None
20402 }
20403
20404 pub fn git_blame_inline_enabled(&self) -> bool {
20405 self.git_blame_inline_enabled
20406 }
20407
20408 pub fn toggle_selection_menu(
20409 &mut self,
20410 _: &ToggleSelectionMenu,
20411 _: &mut Window,
20412 cx: &mut Context<Self>,
20413 ) {
20414 self.show_selection_menu = self
20415 .show_selection_menu
20416 .map(|show_selections_menu| !show_selections_menu)
20417 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20418
20419 cx.notify();
20420 }
20421
20422 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20423 self.show_selection_menu
20424 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20425 }
20426
20427 fn start_git_blame(
20428 &mut self,
20429 user_triggered: bool,
20430 window: &mut Window,
20431 cx: &mut Context<Self>,
20432 ) {
20433 if let Some(project) = self.project() {
20434 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20435 && buffer.read(cx).file().is_none()
20436 {
20437 return;
20438 }
20439
20440 let focused = self.focus_handle(cx).contains_focused(window, cx);
20441
20442 let project = project.clone();
20443 let blame = cx
20444 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20445 self.blame_subscription =
20446 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20447 self.blame = Some(blame);
20448 }
20449 }
20450
20451 fn toggle_git_blame_inline_internal(
20452 &mut self,
20453 user_triggered: bool,
20454 window: &mut Window,
20455 cx: &mut Context<Self>,
20456 ) {
20457 if self.git_blame_inline_enabled {
20458 self.git_blame_inline_enabled = false;
20459 self.show_git_blame_inline = false;
20460 self.show_git_blame_inline_delay_task.take();
20461 } else {
20462 self.git_blame_inline_enabled = true;
20463 self.start_git_blame_inline(user_triggered, window, cx);
20464 }
20465
20466 cx.notify();
20467 }
20468
20469 fn start_git_blame_inline(
20470 &mut self,
20471 user_triggered: bool,
20472 window: &mut Window,
20473 cx: &mut Context<Self>,
20474 ) {
20475 self.start_git_blame(user_triggered, window, cx);
20476
20477 if ProjectSettings::get_global(cx)
20478 .git
20479 .inline_blame_delay()
20480 .is_some()
20481 {
20482 self.start_inline_blame_timer(window, cx);
20483 } else {
20484 self.show_git_blame_inline = true
20485 }
20486 }
20487
20488 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20489 self.blame.as_ref()
20490 }
20491
20492 pub fn show_git_blame_gutter(&self) -> bool {
20493 self.show_git_blame_gutter
20494 }
20495
20496 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20497 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20498 }
20499
20500 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20501 self.show_git_blame_inline
20502 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20503 && !self.newest_selection_head_on_empty_line(cx)
20504 && self.has_blame_entries(cx)
20505 }
20506
20507 fn has_blame_entries(&self, cx: &App) -> bool {
20508 self.blame()
20509 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20510 }
20511
20512 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20513 let cursor_anchor = self.selections.newest_anchor().head();
20514
20515 let snapshot = self.buffer.read(cx).snapshot(cx);
20516 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20517
20518 snapshot.line_len(buffer_row) == 0
20519 }
20520
20521 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20522 let buffer_and_selection = maybe!({
20523 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20524 let selection_range = selection.range();
20525
20526 let multi_buffer = self.buffer().read(cx);
20527 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20528 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20529
20530 let (buffer, range, _) = if selection.reversed {
20531 buffer_ranges.first()
20532 } else {
20533 buffer_ranges.last()
20534 }?;
20535
20536 let selection = text::ToPoint::to_point(&range.start, buffer).row
20537 ..text::ToPoint::to_point(&range.end, buffer).row;
20538 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20539 });
20540
20541 let Some((buffer, selection)) = buffer_and_selection else {
20542 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20543 };
20544
20545 let Some(project) = self.project() else {
20546 return Task::ready(Err(anyhow!("editor does not have project")));
20547 };
20548
20549 project.update(cx, |project, cx| {
20550 project.get_permalink_to_line(&buffer, selection, cx)
20551 })
20552 }
20553
20554 pub fn copy_permalink_to_line(
20555 &mut self,
20556 _: &CopyPermalinkToLine,
20557 window: &mut Window,
20558 cx: &mut Context<Self>,
20559 ) {
20560 let permalink_task = self.get_permalink_to_line(cx);
20561 let workspace = self.workspace();
20562
20563 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20564 Ok(permalink) => {
20565 cx.update(|_, cx| {
20566 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20567 })
20568 .ok();
20569 }
20570 Err(err) => {
20571 let message = format!("Failed to copy permalink: {err}");
20572
20573 anyhow::Result::<()>::Err(err).log_err();
20574
20575 if let Some(workspace) = workspace {
20576 workspace
20577 .update_in(cx, |workspace, _, cx| {
20578 struct CopyPermalinkToLine;
20579
20580 workspace.show_toast(
20581 Toast::new(
20582 NotificationId::unique::<CopyPermalinkToLine>(),
20583 message,
20584 ),
20585 cx,
20586 )
20587 })
20588 .ok();
20589 }
20590 }
20591 })
20592 .detach();
20593 }
20594
20595 pub fn copy_file_location(
20596 &mut self,
20597 _: &CopyFileLocation,
20598 _: &mut Window,
20599 cx: &mut Context<Self>,
20600 ) {
20601 let selection = self
20602 .selections
20603 .newest::<Point>(&self.display_snapshot(cx))
20604 .start
20605 .row
20606 + 1;
20607 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20608 let project = self.project()?.read(cx);
20609 let file = buffer.read(cx).file()?;
20610 let path = file.path().display(project.path_style(cx));
20611
20612 Some(format!("{path}:{selection}"))
20613 }) {
20614 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20615 }
20616 }
20617
20618 pub fn open_permalink_to_line(
20619 &mut self,
20620 _: &OpenPermalinkToLine,
20621 window: &mut Window,
20622 cx: &mut Context<Self>,
20623 ) {
20624 let permalink_task = self.get_permalink_to_line(cx);
20625 let workspace = self.workspace();
20626
20627 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20628 Ok(permalink) => {
20629 cx.update(|_, cx| {
20630 cx.open_url(permalink.as_ref());
20631 })
20632 .ok();
20633 }
20634 Err(err) => {
20635 let message = format!("Failed to open permalink: {err}");
20636
20637 anyhow::Result::<()>::Err(err).log_err();
20638
20639 if let Some(workspace) = workspace {
20640 workspace
20641 .update(cx, |workspace, cx| {
20642 struct OpenPermalinkToLine;
20643
20644 workspace.show_toast(
20645 Toast::new(
20646 NotificationId::unique::<OpenPermalinkToLine>(),
20647 message,
20648 ),
20649 cx,
20650 )
20651 })
20652 .ok();
20653 }
20654 }
20655 })
20656 .detach();
20657 }
20658
20659 pub fn insert_uuid_v4(
20660 &mut self,
20661 _: &InsertUuidV4,
20662 window: &mut Window,
20663 cx: &mut Context<Self>,
20664 ) {
20665 self.insert_uuid(UuidVersion::V4, window, cx);
20666 }
20667
20668 pub fn insert_uuid_v7(
20669 &mut self,
20670 _: &InsertUuidV7,
20671 window: &mut Window,
20672 cx: &mut Context<Self>,
20673 ) {
20674 self.insert_uuid(UuidVersion::V7, window, cx);
20675 }
20676
20677 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20678 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20679 self.transact(window, cx, |this, window, cx| {
20680 let edits = this
20681 .selections
20682 .all::<Point>(&this.display_snapshot(cx))
20683 .into_iter()
20684 .map(|selection| {
20685 let uuid = match version {
20686 UuidVersion::V4 => uuid::Uuid::new_v4(),
20687 UuidVersion::V7 => uuid::Uuid::now_v7(),
20688 };
20689
20690 (selection.range(), uuid.to_string())
20691 });
20692 this.edit(edits, cx);
20693 this.refresh_edit_prediction(true, false, window, cx);
20694 });
20695 }
20696
20697 pub fn open_selections_in_multibuffer(
20698 &mut self,
20699 _: &OpenSelectionsInMultibuffer,
20700 window: &mut Window,
20701 cx: &mut Context<Self>,
20702 ) {
20703 let multibuffer = self.buffer.read(cx);
20704
20705 let Some(buffer) = multibuffer.as_singleton() else {
20706 return;
20707 };
20708
20709 let Some(workspace) = self.workspace() else {
20710 return;
20711 };
20712
20713 let title = multibuffer.title(cx).to_string();
20714
20715 let locations = self
20716 .selections
20717 .all_anchors(&self.display_snapshot(cx))
20718 .iter()
20719 .map(|selection| {
20720 (
20721 buffer.clone(),
20722 (selection.start.text_anchor..selection.end.text_anchor)
20723 .to_point(buffer.read(cx)),
20724 )
20725 })
20726 .into_group_map();
20727
20728 cx.spawn_in(window, async move |_, cx| {
20729 workspace.update_in(cx, |workspace, window, cx| {
20730 Self::open_locations_in_multibuffer(
20731 workspace,
20732 locations,
20733 format!("Selections for '{title}'"),
20734 false,
20735 MultibufferSelectionMode::All,
20736 window,
20737 cx,
20738 );
20739 })
20740 })
20741 .detach();
20742 }
20743
20744 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20745 /// last highlight added will be used.
20746 ///
20747 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20748 pub fn highlight_rows<T: 'static>(
20749 &mut self,
20750 range: Range<Anchor>,
20751 color: Hsla,
20752 options: RowHighlightOptions,
20753 cx: &mut Context<Self>,
20754 ) {
20755 let snapshot = self.buffer().read(cx).snapshot(cx);
20756 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20757 let ix = row_highlights.binary_search_by(|highlight| {
20758 Ordering::Equal
20759 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20760 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20761 });
20762
20763 if let Err(mut ix) = ix {
20764 let index = post_inc(&mut self.highlight_order);
20765
20766 // If this range intersects with the preceding highlight, then merge it with
20767 // the preceding highlight. Otherwise insert a new highlight.
20768 let mut merged = false;
20769 if ix > 0 {
20770 let prev_highlight = &mut row_highlights[ix - 1];
20771 if prev_highlight
20772 .range
20773 .end
20774 .cmp(&range.start, &snapshot)
20775 .is_ge()
20776 {
20777 ix -= 1;
20778 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20779 prev_highlight.range.end = range.end;
20780 }
20781 merged = true;
20782 prev_highlight.index = index;
20783 prev_highlight.color = color;
20784 prev_highlight.options = options;
20785 }
20786 }
20787
20788 if !merged {
20789 row_highlights.insert(
20790 ix,
20791 RowHighlight {
20792 range,
20793 index,
20794 color,
20795 options,
20796 type_id: TypeId::of::<T>(),
20797 },
20798 );
20799 }
20800
20801 // If any of the following highlights intersect with this one, merge them.
20802 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20803 let highlight = &row_highlights[ix];
20804 if next_highlight
20805 .range
20806 .start
20807 .cmp(&highlight.range.end, &snapshot)
20808 .is_le()
20809 {
20810 if next_highlight
20811 .range
20812 .end
20813 .cmp(&highlight.range.end, &snapshot)
20814 .is_gt()
20815 {
20816 row_highlights[ix].range.end = next_highlight.range.end;
20817 }
20818 row_highlights.remove(ix + 1);
20819 } else {
20820 break;
20821 }
20822 }
20823 }
20824 }
20825
20826 /// Remove any highlighted row ranges of the given type that intersect the
20827 /// given ranges.
20828 pub fn remove_highlighted_rows<T: 'static>(
20829 &mut self,
20830 ranges_to_remove: Vec<Range<Anchor>>,
20831 cx: &mut Context<Self>,
20832 ) {
20833 let snapshot = self.buffer().read(cx).snapshot(cx);
20834 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20835 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20836 row_highlights.retain(|highlight| {
20837 while let Some(range_to_remove) = ranges_to_remove.peek() {
20838 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20839 Ordering::Less | Ordering::Equal => {
20840 ranges_to_remove.next();
20841 }
20842 Ordering::Greater => {
20843 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20844 Ordering::Less | Ordering::Equal => {
20845 return false;
20846 }
20847 Ordering::Greater => break,
20848 }
20849 }
20850 }
20851 }
20852
20853 true
20854 })
20855 }
20856
20857 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20858 pub fn clear_row_highlights<T: 'static>(&mut self) {
20859 self.highlighted_rows.remove(&TypeId::of::<T>());
20860 }
20861
20862 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20863 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20864 self.highlighted_rows
20865 .get(&TypeId::of::<T>())
20866 .map_or(&[] as &[_], |vec| vec.as_slice())
20867 .iter()
20868 .map(|highlight| (highlight.range.clone(), highlight.color))
20869 }
20870
20871 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20872 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20873 /// Allows to ignore certain kinds of highlights.
20874 pub fn highlighted_display_rows(
20875 &self,
20876 window: &mut Window,
20877 cx: &mut App,
20878 ) -> BTreeMap<DisplayRow, LineHighlight> {
20879 let snapshot = self.snapshot(window, cx);
20880 let mut used_highlight_orders = HashMap::default();
20881 self.highlighted_rows
20882 .iter()
20883 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20884 .fold(
20885 BTreeMap::<DisplayRow, LineHighlight>::new(),
20886 |mut unique_rows, highlight| {
20887 let start = highlight.range.start.to_display_point(&snapshot);
20888 let end = highlight.range.end.to_display_point(&snapshot);
20889 let start_row = start.row().0;
20890 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20891 {
20892 end.row().0.saturating_sub(1)
20893 } else {
20894 end.row().0
20895 };
20896 for row in start_row..=end_row {
20897 let used_index =
20898 used_highlight_orders.entry(row).or_insert(highlight.index);
20899 if highlight.index >= *used_index {
20900 *used_index = highlight.index;
20901 unique_rows.insert(
20902 DisplayRow(row),
20903 LineHighlight {
20904 include_gutter: highlight.options.include_gutter,
20905 border: None,
20906 background: highlight.color.into(),
20907 type_id: Some(highlight.type_id),
20908 },
20909 );
20910 }
20911 }
20912 unique_rows
20913 },
20914 )
20915 }
20916
20917 pub fn highlighted_display_row_for_autoscroll(
20918 &self,
20919 snapshot: &DisplaySnapshot,
20920 ) -> Option<DisplayRow> {
20921 self.highlighted_rows
20922 .values()
20923 .flat_map(|highlighted_rows| highlighted_rows.iter())
20924 .filter_map(|highlight| {
20925 if highlight.options.autoscroll {
20926 Some(highlight.range.start.to_display_point(snapshot).row())
20927 } else {
20928 None
20929 }
20930 })
20931 .min()
20932 }
20933
20934 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20935 self.highlight_background::<SearchWithinRange>(
20936 ranges,
20937 |colors| colors.colors().editor_document_highlight_read_background,
20938 cx,
20939 )
20940 }
20941
20942 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20943 self.breadcrumb_header = Some(new_header);
20944 }
20945
20946 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20947 self.clear_background_highlights::<SearchWithinRange>(cx);
20948 }
20949
20950 pub fn highlight_background<T: 'static>(
20951 &mut self,
20952 ranges: &[Range<Anchor>],
20953 color_fetcher: fn(&Theme) -> Hsla,
20954 cx: &mut Context<Self>,
20955 ) {
20956 self.background_highlights.insert(
20957 HighlightKey::Type(TypeId::of::<T>()),
20958 (color_fetcher, Arc::from(ranges)),
20959 );
20960 self.scrollbar_marker_state.dirty = true;
20961 cx.notify();
20962 }
20963
20964 pub fn highlight_background_key<T: 'static>(
20965 &mut self,
20966 key: usize,
20967 ranges: &[Range<Anchor>],
20968 color_fetcher: fn(&Theme) -> Hsla,
20969 cx: &mut Context<Self>,
20970 ) {
20971 self.background_highlights.insert(
20972 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20973 (color_fetcher, Arc::from(ranges)),
20974 );
20975 self.scrollbar_marker_state.dirty = true;
20976 cx.notify();
20977 }
20978
20979 pub fn clear_background_highlights<T: 'static>(
20980 &mut self,
20981 cx: &mut Context<Self>,
20982 ) -> Option<BackgroundHighlight> {
20983 let text_highlights = self
20984 .background_highlights
20985 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20986 if !text_highlights.1.is_empty() {
20987 self.scrollbar_marker_state.dirty = true;
20988 cx.notify();
20989 }
20990 Some(text_highlights)
20991 }
20992
20993 pub fn highlight_gutter<T: 'static>(
20994 &mut self,
20995 ranges: impl Into<Vec<Range<Anchor>>>,
20996 color_fetcher: fn(&App) -> Hsla,
20997 cx: &mut Context<Self>,
20998 ) {
20999 self.gutter_highlights
21000 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21001 cx.notify();
21002 }
21003
21004 pub fn clear_gutter_highlights<T: 'static>(
21005 &mut self,
21006 cx: &mut Context<Self>,
21007 ) -> Option<GutterHighlight> {
21008 cx.notify();
21009 self.gutter_highlights.remove(&TypeId::of::<T>())
21010 }
21011
21012 pub fn insert_gutter_highlight<T: 'static>(
21013 &mut self,
21014 range: Range<Anchor>,
21015 color_fetcher: fn(&App) -> Hsla,
21016 cx: &mut Context<Self>,
21017 ) {
21018 let snapshot = self.buffer().read(cx).snapshot(cx);
21019 let mut highlights = self
21020 .gutter_highlights
21021 .remove(&TypeId::of::<T>())
21022 .map(|(_, highlights)| highlights)
21023 .unwrap_or_default();
21024 let ix = highlights.binary_search_by(|highlight| {
21025 Ordering::Equal
21026 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21027 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21028 });
21029 if let Err(ix) = ix {
21030 highlights.insert(ix, range);
21031 }
21032 self.gutter_highlights
21033 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21034 }
21035
21036 pub fn remove_gutter_highlights<T: 'static>(
21037 &mut self,
21038 ranges_to_remove: Vec<Range<Anchor>>,
21039 cx: &mut Context<Self>,
21040 ) {
21041 let snapshot = self.buffer().read(cx).snapshot(cx);
21042 let Some((color_fetcher, mut gutter_highlights)) =
21043 self.gutter_highlights.remove(&TypeId::of::<T>())
21044 else {
21045 return;
21046 };
21047 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21048 gutter_highlights.retain(|highlight| {
21049 while let Some(range_to_remove) = ranges_to_remove.peek() {
21050 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21051 Ordering::Less | Ordering::Equal => {
21052 ranges_to_remove.next();
21053 }
21054 Ordering::Greater => {
21055 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21056 Ordering::Less | Ordering::Equal => {
21057 return false;
21058 }
21059 Ordering::Greater => break,
21060 }
21061 }
21062 }
21063 }
21064
21065 true
21066 });
21067 self.gutter_highlights
21068 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21069 }
21070
21071 #[cfg(feature = "test-support")]
21072 pub fn all_text_highlights(
21073 &self,
21074 window: &mut Window,
21075 cx: &mut Context<Self>,
21076 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21077 let snapshot = self.snapshot(window, cx);
21078 self.display_map.update(cx, |display_map, _| {
21079 display_map
21080 .all_text_highlights()
21081 .map(|highlight| {
21082 let (style, ranges) = highlight.as_ref();
21083 (
21084 *style,
21085 ranges
21086 .iter()
21087 .map(|range| range.clone().to_display_points(&snapshot))
21088 .collect(),
21089 )
21090 })
21091 .collect()
21092 })
21093 }
21094
21095 #[cfg(feature = "test-support")]
21096 pub fn all_text_background_highlights(
21097 &self,
21098 window: &mut Window,
21099 cx: &mut Context<Self>,
21100 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21101 let snapshot = self.snapshot(window, cx);
21102 let buffer = &snapshot.buffer_snapshot();
21103 let start = buffer.anchor_before(MultiBufferOffset(0));
21104 let end = buffer.anchor_after(buffer.len());
21105 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21106 }
21107
21108 #[cfg(any(test, feature = "test-support"))]
21109 pub fn sorted_background_highlights_in_range(
21110 &self,
21111 search_range: Range<Anchor>,
21112 display_snapshot: &DisplaySnapshot,
21113 theme: &Theme,
21114 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21115 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21116 res.sort_by(|a, b| {
21117 a.0.start
21118 .cmp(&b.0.start)
21119 .then_with(|| a.0.end.cmp(&b.0.end))
21120 .then_with(|| a.1.cmp(&b.1))
21121 });
21122 res
21123 }
21124
21125 #[cfg(feature = "test-support")]
21126 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21127 let snapshot = self.buffer().read(cx).snapshot(cx);
21128
21129 let highlights = self
21130 .background_highlights
21131 .get(&HighlightKey::Type(TypeId::of::<
21132 items::BufferSearchHighlights,
21133 >()));
21134
21135 if let Some((_color, ranges)) = highlights {
21136 ranges
21137 .iter()
21138 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21139 .collect_vec()
21140 } else {
21141 vec![]
21142 }
21143 }
21144
21145 fn document_highlights_for_position<'a>(
21146 &'a self,
21147 position: Anchor,
21148 buffer: &'a MultiBufferSnapshot,
21149 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21150 let read_highlights = self
21151 .background_highlights
21152 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21153 .map(|h| &h.1);
21154 let write_highlights = self
21155 .background_highlights
21156 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21157 .map(|h| &h.1);
21158 let left_position = position.bias_left(buffer);
21159 let right_position = position.bias_right(buffer);
21160 read_highlights
21161 .into_iter()
21162 .chain(write_highlights)
21163 .flat_map(move |ranges| {
21164 let start_ix = match ranges.binary_search_by(|probe| {
21165 let cmp = probe.end.cmp(&left_position, buffer);
21166 if cmp.is_ge() {
21167 Ordering::Greater
21168 } else {
21169 Ordering::Less
21170 }
21171 }) {
21172 Ok(i) | Err(i) => i,
21173 };
21174
21175 ranges[start_ix..]
21176 .iter()
21177 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21178 })
21179 }
21180
21181 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21182 self.background_highlights
21183 .get(&HighlightKey::Type(TypeId::of::<T>()))
21184 .is_some_and(|(_, highlights)| !highlights.is_empty())
21185 }
21186
21187 /// Returns all background highlights for a given range.
21188 ///
21189 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21190 pub fn background_highlights_in_range(
21191 &self,
21192 search_range: Range<Anchor>,
21193 display_snapshot: &DisplaySnapshot,
21194 theme: &Theme,
21195 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21196 let mut results = Vec::new();
21197 for (color_fetcher, ranges) in self.background_highlights.values() {
21198 let color = color_fetcher(theme);
21199 let start_ix = match ranges.binary_search_by(|probe| {
21200 let cmp = probe
21201 .end
21202 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21203 if cmp.is_gt() {
21204 Ordering::Greater
21205 } else {
21206 Ordering::Less
21207 }
21208 }) {
21209 Ok(i) | Err(i) => i,
21210 };
21211 for range in &ranges[start_ix..] {
21212 if range
21213 .start
21214 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21215 .is_ge()
21216 {
21217 break;
21218 }
21219
21220 let start = range.start.to_display_point(display_snapshot);
21221 let end = range.end.to_display_point(display_snapshot);
21222 results.push((start..end, color))
21223 }
21224 }
21225 results
21226 }
21227
21228 pub fn gutter_highlights_in_range(
21229 &self,
21230 search_range: Range<Anchor>,
21231 display_snapshot: &DisplaySnapshot,
21232 cx: &App,
21233 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21234 let mut results = Vec::new();
21235 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21236 let color = color_fetcher(cx);
21237 let start_ix = match ranges.binary_search_by(|probe| {
21238 let cmp = probe
21239 .end
21240 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21241 if cmp.is_gt() {
21242 Ordering::Greater
21243 } else {
21244 Ordering::Less
21245 }
21246 }) {
21247 Ok(i) | Err(i) => i,
21248 };
21249 for range in &ranges[start_ix..] {
21250 if range
21251 .start
21252 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21253 .is_ge()
21254 {
21255 break;
21256 }
21257
21258 let start = range.start.to_display_point(display_snapshot);
21259 let end = range.end.to_display_point(display_snapshot);
21260 results.push((start..end, color))
21261 }
21262 }
21263 results
21264 }
21265
21266 /// Get the text ranges corresponding to the redaction query
21267 pub fn redacted_ranges(
21268 &self,
21269 search_range: Range<Anchor>,
21270 display_snapshot: &DisplaySnapshot,
21271 cx: &App,
21272 ) -> Vec<Range<DisplayPoint>> {
21273 display_snapshot
21274 .buffer_snapshot()
21275 .redacted_ranges(search_range, |file| {
21276 if let Some(file) = file {
21277 file.is_private()
21278 && EditorSettings::get(
21279 Some(SettingsLocation {
21280 worktree_id: file.worktree_id(cx),
21281 path: file.path().as_ref(),
21282 }),
21283 cx,
21284 )
21285 .redact_private_values
21286 } else {
21287 false
21288 }
21289 })
21290 .map(|range| {
21291 range.start.to_display_point(display_snapshot)
21292 ..range.end.to_display_point(display_snapshot)
21293 })
21294 .collect()
21295 }
21296
21297 pub fn highlight_text_key<T: 'static>(
21298 &mut self,
21299 key: usize,
21300 ranges: Vec<Range<Anchor>>,
21301 style: HighlightStyle,
21302 merge: bool,
21303 cx: &mut Context<Self>,
21304 ) {
21305 self.display_map.update(cx, |map, cx| {
21306 map.highlight_text(
21307 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21308 ranges,
21309 style,
21310 merge,
21311 cx,
21312 );
21313 });
21314 cx.notify();
21315 }
21316
21317 pub fn highlight_text<T: 'static>(
21318 &mut self,
21319 ranges: Vec<Range<Anchor>>,
21320 style: HighlightStyle,
21321 cx: &mut Context<Self>,
21322 ) {
21323 self.display_map.update(cx, |map, cx| {
21324 map.highlight_text(
21325 HighlightKey::Type(TypeId::of::<T>()),
21326 ranges,
21327 style,
21328 false,
21329 cx,
21330 )
21331 });
21332 cx.notify();
21333 }
21334
21335 pub fn text_highlights<'a, T: 'static>(
21336 &'a self,
21337 cx: &'a App,
21338 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21339 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21340 }
21341
21342 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21343 let cleared = self
21344 .display_map
21345 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21346 if cleared {
21347 cx.notify();
21348 }
21349 }
21350
21351 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21352 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21353 && self.focus_handle.is_focused(window)
21354 }
21355
21356 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21357 self.show_cursor_when_unfocused = is_enabled;
21358 cx.notify();
21359 }
21360
21361 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21362 cx.notify();
21363 }
21364
21365 fn on_debug_session_event(
21366 &mut self,
21367 _session: Entity<Session>,
21368 event: &SessionEvent,
21369 cx: &mut Context<Self>,
21370 ) {
21371 if let SessionEvent::InvalidateInlineValue = event {
21372 self.refresh_inline_values(cx);
21373 }
21374 }
21375
21376 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21377 let Some(project) = self.project.clone() else {
21378 return;
21379 };
21380
21381 if !self.inline_value_cache.enabled {
21382 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21383 self.splice_inlays(&inlays, Vec::new(), cx);
21384 return;
21385 }
21386
21387 let current_execution_position = self
21388 .highlighted_rows
21389 .get(&TypeId::of::<ActiveDebugLine>())
21390 .and_then(|lines| lines.last().map(|line| line.range.end));
21391
21392 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21393 let inline_values = editor
21394 .update(cx, |editor, cx| {
21395 let Some(current_execution_position) = current_execution_position else {
21396 return Some(Task::ready(Ok(Vec::new())));
21397 };
21398
21399 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21400 let snapshot = buffer.snapshot(cx);
21401
21402 let excerpt = snapshot.excerpt_containing(
21403 current_execution_position..current_execution_position,
21404 )?;
21405
21406 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21407 })?;
21408
21409 let range =
21410 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21411
21412 project.inline_values(buffer, range, cx)
21413 })
21414 .ok()
21415 .flatten()?
21416 .await
21417 .context("refreshing debugger inlays")
21418 .log_err()?;
21419
21420 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21421
21422 for (buffer_id, inline_value) in inline_values
21423 .into_iter()
21424 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21425 {
21426 buffer_inline_values
21427 .entry(buffer_id)
21428 .or_default()
21429 .push(inline_value);
21430 }
21431
21432 editor
21433 .update(cx, |editor, cx| {
21434 let snapshot = editor.buffer.read(cx).snapshot(cx);
21435 let mut new_inlays = Vec::default();
21436
21437 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21438 let buffer_id = buffer_snapshot.remote_id();
21439 buffer_inline_values
21440 .get(&buffer_id)
21441 .into_iter()
21442 .flatten()
21443 .for_each(|hint| {
21444 let inlay = Inlay::debugger(
21445 post_inc(&mut editor.next_inlay_id),
21446 Anchor::in_buffer(excerpt_id, hint.position),
21447 hint.text(),
21448 );
21449 if !inlay.text().chars().contains(&'\n') {
21450 new_inlays.push(inlay);
21451 }
21452 });
21453 }
21454
21455 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21456 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21457
21458 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21459 })
21460 .ok()?;
21461 Some(())
21462 });
21463 }
21464
21465 fn on_buffer_event(
21466 &mut self,
21467 multibuffer: &Entity<MultiBuffer>,
21468 event: &multi_buffer::Event,
21469 window: &mut Window,
21470 cx: &mut Context<Self>,
21471 ) {
21472 match event {
21473 multi_buffer::Event::Edited { edited_buffer } => {
21474 self.scrollbar_marker_state.dirty = true;
21475 self.active_indent_guides_state.dirty = true;
21476 self.refresh_active_diagnostics(cx);
21477 self.refresh_code_actions(window, cx);
21478 self.refresh_single_line_folds(window, cx);
21479 self.refresh_matching_bracket_highlights(window, cx);
21480 if self.has_active_edit_prediction() {
21481 self.update_visible_edit_prediction(window, cx);
21482 }
21483
21484 if let Some(buffer) = edited_buffer {
21485 if buffer.read(cx).file().is_none() {
21486 cx.emit(EditorEvent::TitleChanged);
21487 }
21488
21489 if self.project.is_some() {
21490 let buffer_id = buffer.read(cx).remote_id();
21491 self.register_buffer(buffer_id, cx);
21492 self.update_lsp_data(Some(buffer_id), window, cx);
21493 self.refresh_inlay_hints(
21494 InlayHintRefreshReason::BufferEdited(buffer_id),
21495 cx,
21496 );
21497 }
21498 }
21499
21500 cx.emit(EditorEvent::BufferEdited);
21501 cx.emit(SearchEvent::MatchesInvalidated);
21502
21503 let Some(project) = &self.project else { return };
21504 let (telemetry, is_via_ssh) = {
21505 let project = project.read(cx);
21506 let telemetry = project.client().telemetry().clone();
21507 let is_via_ssh = project.is_via_remote_server();
21508 (telemetry, is_via_ssh)
21509 };
21510 telemetry.log_edit_event("editor", is_via_ssh);
21511 }
21512 multi_buffer::Event::ExcerptsAdded {
21513 buffer,
21514 predecessor,
21515 excerpts,
21516 } => {
21517 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21518 let buffer_id = buffer.read(cx).remote_id();
21519 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21520 && let Some(project) = &self.project
21521 {
21522 update_uncommitted_diff_for_buffer(
21523 cx.entity(),
21524 project,
21525 [buffer.clone()],
21526 self.buffer.clone(),
21527 cx,
21528 )
21529 .detach();
21530 }
21531 self.update_lsp_data(Some(buffer_id), window, cx);
21532 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21533 self.colorize_brackets(false, cx);
21534 cx.emit(EditorEvent::ExcerptsAdded {
21535 buffer: buffer.clone(),
21536 predecessor: *predecessor,
21537 excerpts: excerpts.clone(),
21538 });
21539 }
21540 multi_buffer::Event::ExcerptsRemoved {
21541 ids,
21542 removed_buffer_ids,
21543 } => {
21544 if let Some(inlay_hints) = &mut self.inlay_hints {
21545 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21546 }
21547 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21548 for buffer_id in removed_buffer_ids {
21549 self.registered_buffers.remove(buffer_id);
21550 }
21551 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21552 cx.emit(EditorEvent::ExcerptsRemoved {
21553 ids: ids.clone(),
21554 removed_buffer_ids: removed_buffer_ids.clone(),
21555 });
21556 }
21557 multi_buffer::Event::ExcerptsEdited {
21558 excerpt_ids,
21559 buffer_ids,
21560 } => {
21561 self.display_map.update(cx, |map, cx| {
21562 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21563 });
21564 cx.emit(EditorEvent::ExcerptsEdited {
21565 ids: excerpt_ids.clone(),
21566 });
21567 }
21568 multi_buffer::Event::ExcerptsExpanded { ids } => {
21569 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21570 self.refresh_document_highlights(cx);
21571 for id in ids {
21572 self.fetched_tree_sitter_chunks.remove(id);
21573 }
21574 self.colorize_brackets(false, cx);
21575 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21576 }
21577 multi_buffer::Event::Reparsed(buffer_id) => {
21578 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21579 self.refresh_selected_text_highlights(true, window, cx);
21580 self.colorize_brackets(true, cx);
21581 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21582
21583 cx.emit(EditorEvent::Reparsed(*buffer_id));
21584 }
21585 multi_buffer::Event::DiffHunksToggled => {
21586 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21587 }
21588 multi_buffer::Event::LanguageChanged(buffer_id) => {
21589 self.registered_buffers.remove(&buffer_id);
21590 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21591 cx.emit(EditorEvent::Reparsed(*buffer_id));
21592 cx.notify();
21593 }
21594 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21595 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21596 multi_buffer::Event::FileHandleChanged
21597 | multi_buffer::Event::Reloaded
21598 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21599 multi_buffer::Event::DiagnosticsUpdated => {
21600 self.update_diagnostics_state(window, cx);
21601 }
21602 _ => {}
21603 };
21604 }
21605
21606 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21607 if !self.diagnostics_enabled() {
21608 return;
21609 }
21610 self.refresh_active_diagnostics(cx);
21611 self.refresh_inline_diagnostics(true, window, cx);
21612 self.scrollbar_marker_state.dirty = true;
21613 cx.notify();
21614 }
21615
21616 pub fn start_temporary_diff_override(&mut self) {
21617 self.load_diff_task.take();
21618 self.temporary_diff_override = true;
21619 }
21620
21621 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21622 self.temporary_diff_override = false;
21623 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21624 self.buffer.update(cx, |buffer, cx| {
21625 buffer.set_all_diff_hunks_collapsed(cx);
21626 });
21627
21628 if let Some(project) = self.project.clone() {
21629 self.load_diff_task = Some(
21630 update_uncommitted_diff_for_buffer(
21631 cx.entity(),
21632 &project,
21633 self.buffer.read(cx).all_buffers(),
21634 self.buffer.clone(),
21635 cx,
21636 )
21637 .shared(),
21638 );
21639 }
21640 }
21641
21642 fn on_display_map_changed(
21643 &mut self,
21644 _: Entity<DisplayMap>,
21645 _: &mut Window,
21646 cx: &mut Context<Self>,
21647 ) {
21648 cx.notify();
21649 }
21650
21651 fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
21652 if !self.mode.is_full() {
21653 return Vec::new();
21654 }
21655
21656 let theme_settings = theme::ThemeSettings::get_global(cx);
21657
21658 theme_settings
21659 .theme_overrides
21660 .get(cx.theme().name.as_ref())
21661 .map(|theme_style| &theme_style.accents)
21662 .into_iter()
21663 .flatten()
21664 .chain(
21665 theme_settings
21666 .experimental_theme_overrides
21667 .as_ref()
21668 .map(|overrides| &overrides.accents)
21669 .into_iter()
21670 .flatten(),
21671 )
21672 .flat_map(|accent| accent.0.clone())
21673 .collect()
21674 }
21675
21676 fn fetch_applicable_language_settings(
21677 &self,
21678 cx: &App,
21679 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21680 if !self.mode.is_full() {
21681 return HashMap::default();
21682 }
21683
21684 self.buffer().read(cx).all_buffers().into_iter().fold(
21685 HashMap::default(),
21686 |mut acc, buffer| {
21687 let buffer = buffer.read(cx);
21688 let language = buffer.language().map(|language| language.name());
21689 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21690 let file = buffer.file();
21691 v.insert(language_settings(language, file, cx).into_owned());
21692 }
21693 acc
21694 },
21695 )
21696 }
21697
21698 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21699 let new_language_settings = self.fetch_applicable_language_settings(cx);
21700 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21701 self.applicable_language_settings = new_language_settings;
21702
21703 let new_accent_overrides = self.fetch_accent_overrides(cx);
21704 let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
21705 self.accent_overrides = new_accent_overrides;
21706
21707 if self.diagnostics_enabled() {
21708 let new_severity = EditorSettings::get_global(cx)
21709 .diagnostics_max_severity
21710 .unwrap_or(DiagnosticSeverity::Hint);
21711 self.set_max_diagnostics_severity(new_severity, cx);
21712 }
21713 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21714 self.update_edit_prediction_settings(cx);
21715 self.refresh_edit_prediction(true, false, window, cx);
21716 self.refresh_inline_values(cx);
21717 self.refresh_inlay_hints(
21718 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21719 self.selections.newest_anchor().head(),
21720 &self.buffer.read(cx).snapshot(cx),
21721 cx,
21722 )),
21723 cx,
21724 );
21725
21726 let old_cursor_shape = self.cursor_shape;
21727 let old_show_breadcrumbs = self.show_breadcrumbs;
21728
21729 {
21730 let editor_settings = EditorSettings::get_global(cx);
21731 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21732 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21733 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21734 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21735 }
21736
21737 if old_cursor_shape != self.cursor_shape {
21738 cx.emit(EditorEvent::CursorShapeChanged);
21739 }
21740
21741 if old_show_breadcrumbs != self.show_breadcrumbs {
21742 cx.emit(EditorEvent::BreadcrumbsChanged);
21743 }
21744
21745 let project_settings = ProjectSettings::get_global(cx);
21746 self.buffer_serialization = self
21747 .should_serialize_buffer()
21748 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21749
21750 if self.mode.is_full() {
21751 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21752 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21753 if self.show_inline_diagnostics != show_inline_diagnostics {
21754 self.show_inline_diagnostics = show_inline_diagnostics;
21755 self.refresh_inline_diagnostics(false, window, cx);
21756 }
21757
21758 if self.git_blame_inline_enabled != inline_blame_enabled {
21759 self.toggle_git_blame_inline_internal(false, window, cx);
21760 }
21761
21762 let minimap_settings = EditorSettings::get_global(cx).minimap;
21763 if self.minimap_visibility != MinimapVisibility::Disabled {
21764 if self.minimap_visibility.settings_visibility()
21765 != minimap_settings.minimap_enabled()
21766 {
21767 self.set_minimap_visibility(
21768 MinimapVisibility::for_mode(self.mode(), cx),
21769 window,
21770 cx,
21771 );
21772 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21773 minimap_entity.update(cx, |minimap_editor, cx| {
21774 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21775 })
21776 }
21777 }
21778
21779 if language_settings_changed || accent_overrides_changed {
21780 self.colorize_brackets(true, cx);
21781 }
21782
21783 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21784 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21785 }) {
21786 if !inlay_splice.is_empty() {
21787 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21788 }
21789 self.refresh_colors_for_visible_range(None, window, cx);
21790 }
21791 }
21792
21793 cx.notify();
21794 }
21795
21796 pub fn set_searchable(&mut self, searchable: bool) {
21797 self.searchable = searchable;
21798 }
21799
21800 pub fn searchable(&self) -> bool {
21801 self.searchable
21802 }
21803
21804 pub fn open_excerpts_in_split(
21805 &mut self,
21806 _: &OpenExcerptsSplit,
21807 window: &mut Window,
21808 cx: &mut Context<Self>,
21809 ) {
21810 self.open_excerpts_common(None, true, window, cx)
21811 }
21812
21813 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21814 self.open_excerpts_common(None, false, window, cx)
21815 }
21816
21817 fn open_excerpts_common(
21818 &mut self,
21819 jump_data: Option<JumpData>,
21820 split: bool,
21821 window: &mut Window,
21822 cx: &mut Context<Self>,
21823 ) {
21824 let Some(workspace) = self.workspace() else {
21825 cx.propagate();
21826 return;
21827 };
21828
21829 if self.buffer.read(cx).is_singleton() {
21830 cx.propagate();
21831 return;
21832 }
21833
21834 let mut new_selections_by_buffer = HashMap::default();
21835 match &jump_data {
21836 Some(JumpData::MultiBufferPoint {
21837 excerpt_id,
21838 position,
21839 anchor,
21840 line_offset_from_top,
21841 }) => {
21842 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21843 if let Some(buffer) = multi_buffer_snapshot
21844 .buffer_id_for_excerpt(*excerpt_id)
21845 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21846 {
21847 let buffer_snapshot = buffer.read(cx).snapshot();
21848 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21849 language::ToPoint::to_point(anchor, &buffer_snapshot)
21850 } else {
21851 buffer_snapshot.clip_point(*position, Bias::Left)
21852 };
21853 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21854 new_selections_by_buffer.insert(
21855 buffer,
21856 (
21857 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21858 Some(*line_offset_from_top),
21859 ),
21860 );
21861 }
21862 }
21863 Some(JumpData::MultiBufferRow {
21864 row,
21865 line_offset_from_top,
21866 }) => {
21867 let point = MultiBufferPoint::new(row.0, 0);
21868 if let Some((buffer, buffer_point, _)) =
21869 self.buffer.read(cx).point_to_buffer_point(point, cx)
21870 {
21871 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21872 new_selections_by_buffer
21873 .entry(buffer)
21874 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21875 .0
21876 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21877 }
21878 }
21879 None => {
21880 let selections = self
21881 .selections
21882 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21883 let multi_buffer = self.buffer.read(cx);
21884 for selection in selections {
21885 for (snapshot, range, _, anchor) in multi_buffer
21886 .snapshot(cx)
21887 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21888 {
21889 if let Some(anchor) = anchor {
21890 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21891 else {
21892 continue;
21893 };
21894 let offset = text::ToOffset::to_offset(
21895 &anchor.text_anchor,
21896 &buffer_handle.read(cx).snapshot(),
21897 );
21898 let range = BufferOffset(offset)..BufferOffset(offset);
21899 new_selections_by_buffer
21900 .entry(buffer_handle)
21901 .or_insert((Vec::new(), None))
21902 .0
21903 .push(range)
21904 } else {
21905 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21906 else {
21907 continue;
21908 };
21909 new_selections_by_buffer
21910 .entry(buffer_handle)
21911 .or_insert((Vec::new(), None))
21912 .0
21913 .push(range)
21914 }
21915 }
21916 }
21917 }
21918 }
21919
21920 new_selections_by_buffer
21921 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21922
21923 if new_selections_by_buffer.is_empty() {
21924 return;
21925 }
21926
21927 // We defer the pane interaction because we ourselves are a workspace item
21928 // and activating a new item causes the pane to call a method on us reentrantly,
21929 // which panics if we're on the stack.
21930 window.defer(cx, move |window, cx| {
21931 workspace.update(cx, |workspace, cx| {
21932 let pane = if split {
21933 workspace.adjacent_pane(window, cx)
21934 } else {
21935 workspace.active_pane().clone()
21936 };
21937
21938 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21939 let editor = buffer
21940 .read(cx)
21941 .file()
21942 .is_none()
21943 .then(|| {
21944 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21945 // so `workspace.open_project_item` will never find them, always opening a new editor.
21946 // Instead, we try to activate the existing editor in the pane first.
21947 let (editor, pane_item_index) =
21948 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21949 let editor = item.downcast::<Editor>()?;
21950 let singleton_buffer =
21951 editor.read(cx).buffer().read(cx).as_singleton()?;
21952 if singleton_buffer == buffer {
21953 Some((editor, i))
21954 } else {
21955 None
21956 }
21957 })?;
21958 pane.update(cx, |pane, cx| {
21959 pane.activate_item(pane_item_index, true, true, window, cx)
21960 });
21961 Some(editor)
21962 })
21963 .flatten()
21964 .unwrap_or_else(|| {
21965 workspace.open_project_item::<Self>(
21966 pane.clone(),
21967 buffer,
21968 true,
21969 true,
21970 window,
21971 cx,
21972 )
21973 });
21974
21975 editor.update(cx, |editor, cx| {
21976 let autoscroll = match scroll_offset {
21977 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21978 None => Autoscroll::newest(),
21979 };
21980 let nav_history = editor.nav_history.take();
21981 editor.change_selections(
21982 SelectionEffects::scroll(autoscroll),
21983 window,
21984 cx,
21985 |s| {
21986 s.select_ranges(ranges.into_iter().map(|range| {
21987 // we checked that the editor is a singleton editor so the offsets are valid
21988 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21989 }));
21990 },
21991 );
21992 editor.nav_history = nav_history;
21993 });
21994 }
21995 })
21996 });
21997 }
21998
21999 // For now, don't allow opening excerpts in buffers that aren't backed by
22000 // regular project files.
22001 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22002 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
22003 }
22004
22005 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22006 let snapshot = self.buffer.read(cx).read(cx);
22007 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22008 Some(
22009 ranges
22010 .iter()
22011 .map(move |range| {
22012 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22013 })
22014 .collect(),
22015 )
22016 }
22017
22018 fn selection_replacement_ranges(
22019 &self,
22020 range: Range<MultiBufferOffsetUtf16>,
22021 cx: &mut App,
22022 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22023 let selections = self
22024 .selections
22025 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22026 let newest_selection = selections
22027 .iter()
22028 .max_by_key(|selection| selection.id)
22029 .unwrap();
22030 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22031 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22032 let snapshot = self.buffer.read(cx).read(cx);
22033 selections
22034 .into_iter()
22035 .map(|mut selection| {
22036 selection.start.0.0 =
22037 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22038 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22039 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22040 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22041 })
22042 .collect()
22043 }
22044
22045 fn report_editor_event(
22046 &self,
22047 reported_event: ReportEditorEvent,
22048 file_extension: Option<String>,
22049 cx: &App,
22050 ) {
22051 if cfg!(any(test, feature = "test-support")) {
22052 return;
22053 }
22054
22055 let Some(project) = &self.project else { return };
22056
22057 // If None, we are in a file without an extension
22058 let file = self
22059 .buffer
22060 .read(cx)
22061 .as_singleton()
22062 .and_then(|b| b.read(cx).file());
22063 let file_extension = file_extension.or(file
22064 .as_ref()
22065 .and_then(|file| Path::new(file.file_name(cx)).extension())
22066 .and_then(|e| e.to_str())
22067 .map(|a| a.to_string()));
22068
22069 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22070 .map(|vim_mode| vim_mode.0)
22071 .unwrap_or(false);
22072
22073 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22074 let copilot_enabled = edit_predictions_provider
22075 == language::language_settings::EditPredictionProvider::Copilot;
22076 let copilot_enabled_for_language = self
22077 .buffer
22078 .read(cx)
22079 .language_settings(cx)
22080 .show_edit_predictions;
22081
22082 let project = project.read(cx);
22083 let event_type = reported_event.event_type();
22084
22085 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22086 telemetry::event!(
22087 event_type,
22088 type = if auto_saved {"autosave"} else {"manual"},
22089 file_extension,
22090 vim_mode,
22091 copilot_enabled,
22092 copilot_enabled_for_language,
22093 edit_predictions_provider,
22094 is_via_ssh = project.is_via_remote_server(),
22095 );
22096 } else {
22097 telemetry::event!(
22098 event_type,
22099 file_extension,
22100 vim_mode,
22101 copilot_enabled,
22102 copilot_enabled_for_language,
22103 edit_predictions_provider,
22104 is_via_ssh = project.is_via_remote_server(),
22105 );
22106 };
22107 }
22108
22109 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22110 /// with each line being an array of {text, highlight} objects.
22111 fn copy_highlight_json(
22112 &mut self,
22113 _: &CopyHighlightJson,
22114 window: &mut Window,
22115 cx: &mut Context<Self>,
22116 ) {
22117 #[derive(Serialize)]
22118 struct Chunk<'a> {
22119 text: String,
22120 highlight: Option<&'a str>,
22121 }
22122
22123 let snapshot = self.buffer.read(cx).snapshot(cx);
22124 let range = self
22125 .selected_text_range(false, window, cx)
22126 .and_then(|selection| {
22127 if selection.range.is_empty() {
22128 None
22129 } else {
22130 Some(
22131 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22132 selection.range.start,
22133 )))
22134 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22135 selection.range.end,
22136 ))),
22137 )
22138 }
22139 })
22140 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22141
22142 let chunks = snapshot.chunks(range, true);
22143 let mut lines = Vec::new();
22144 let mut line: VecDeque<Chunk> = VecDeque::new();
22145
22146 let Some(style) = self.style.as_ref() else {
22147 return;
22148 };
22149
22150 for chunk in chunks {
22151 let highlight = chunk
22152 .syntax_highlight_id
22153 .and_then(|id| id.name(&style.syntax));
22154 let mut chunk_lines = chunk.text.split('\n').peekable();
22155 while let Some(text) = chunk_lines.next() {
22156 let mut merged_with_last_token = false;
22157 if let Some(last_token) = line.back_mut()
22158 && last_token.highlight == highlight
22159 {
22160 last_token.text.push_str(text);
22161 merged_with_last_token = true;
22162 }
22163
22164 if !merged_with_last_token {
22165 line.push_back(Chunk {
22166 text: text.into(),
22167 highlight,
22168 });
22169 }
22170
22171 if chunk_lines.peek().is_some() {
22172 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22173 line.pop_front();
22174 }
22175 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22176 line.pop_back();
22177 }
22178
22179 lines.push(mem::take(&mut line));
22180 }
22181 }
22182 }
22183
22184 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22185 return;
22186 };
22187 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22188 }
22189
22190 pub fn open_context_menu(
22191 &mut self,
22192 _: &OpenContextMenu,
22193 window: &mut Window,
22194 cx: &mut Context<Self>,
22195 ) {
22196 self.request_autoscroll(Autoscroll::newest(), cx);
22197 let position = self
22198 .selections
22199 .newest_display(&self.display_snapshot(cx))
22200 .start;
22201 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22202 }
22203
22204 pub fn replay_insert_event(
22205 &mut self,
22206 text: &str,
22207 relative_utf16_range: Option<Range<isize>>,
22208 window: &mut Window,
22209 cx: &mut Context<Self>,
22210 ) {
22211 if !self.input_enabled {
22212 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22213 return;
22214 }
22215 if let Some(relative_utf16_range) = relative_utf16_range {
22216 let selections = self
22217 .selections
22218 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22219 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22220 let new_ranges = selections.into_iter().map(|range| {
22221 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22222 range
22223 .head()
22224 .0
22225 .0
22226 .saturating_add_signed(relative_utf16_range.start),
22227 ));
22228 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22229 range
22230 .head()
22231 .0
22232 .0
22233 .saturating_add_signed(relative_utf16_range.end),
22234 ));
22235 start..end
22236 });
22237 s.select_ranges(new_ranges);
22238 });
22239 }
22240
22241 self.handle_input(text, window, cx);
22242 }
22243
22244 pub fn is_focused(&self, window: &Window) -> bool {
22245 self.focus_handle.is_focused(window)
22246 }
22247
22248 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22249 cx.emit(EditorEvent::Focused);
22250
22251 if let Some(descendant) = self
22252 .last_focused_descendant
22253 .take()
22254 .and_then(|descendant| descendant.upgrade())
22255 {
22256 window.focus(&descendant);
22257 } else {
22258 if let Some(blame) = self.blame.as_ref() {
22259 blame.update(cx, GitBlame::focus)
22260 }
22261
22262 self.blink_manager.update(cx, BlinkManager::enable);
22263 self.show_cursor_names(window, cx);
22264 self.buffer.update(cx, |buffer, cx| {
22265 buffer.finalize_last_transaction(cx);
22266 if self.leader_id.is_none() {
22267 buffer.set_active_selections(
22268 &self.selections.disjoint_anchors_arc(),
22269 self.selections.line_mode(),
22270 self.cursor_shape,
22271 cx,
22272 );
22273 }
22274 });
22275
22276 if let Some(position_map) = self.last_position_map.clone() {
22277 EditorElement::mouse_moved(
22278 self,
22279 &MouseMoveEvent {
22280 position: window.mouse_position(),
22281 pressed_button: None,
22282 modifiers: window.modifiers(),
22283 },
22284 &position_map,
22285 window,
22286 cx,
22287 );
22288 }
22289 }
22290 }
22291
22292 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22293 cx.emit(EditorEvent::FocusedIn)
22294 }
22295
22296 fn handle_focus_out(
22297 &mut self,
22298 event: FocusOutEvent,
22299 _window: &mut Window,
22300 cx: &mut Context<Self>,
22301 ) {
22302 if event.blurred != self.focus_handle {
22303 self.last_focused_descendant = Some(event.blurred);
22304 }
22305 self.selection_drag_state = SelectionDragState::None;
22306 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22307 }
22308
22309 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22310 self.blink_manager.update(cx, BlinkManager::disable);
22311 self.buffer
22312 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22313
22314 if let Some(blame) = self.blame.as_ref() {
22315 blame.update(cx, GitBlame::blur)
22316 }
22317 if !self.hover_state.focused(window, cx) {
22318 hide_hover(self, cx);
22319 }
22320 if !self
22321 .context_menu
22322 .borrow()
22323 .as_ref()
22324 .is_some_and(|context_menu| context_menu.focused(window, cx))
22325 {
22326 self.hide_context_menu(window, cx);
22327 }
22328 self.take_active_edit_prediction(cx);
22329 cx.emit(EditorEvent::Blurred);
22330 cx.notify();
22331 }
22332
22333 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22334 let mut pending: String = window
22335 .pending_input_keystrokes()
22336 .into_iter()
22337 .flatten()
22338 .filter_map(|keystroke| keystroke.key_char.clone())
22339 .collect();
22340
22341 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22342 pending = "".to_string();
22343 }
22344
22345 let existing_pending = self
22346 .text_highlights::<PendingInput>(cx)
22347 .map(|(_, ranges)| ranges.to_vec());
22348 if existing_pending.is_none() && pending.is_empty() {
22349 return;
22350 }
22351 let transaction =
22352 self.transact(window, cx, |this, window, cx| {
22353 let selections = this
22354 .selections
22355 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22356 let edits = selections
22357 .iter()
22358 .map(|selection| (selection.end..selection.end, pending.clone()));
22359 this.edit(edits, cx);
22360 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22361 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22362 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22363 }));
22364 });
22365 if let Some(existing_ranges) = existing_pending {
22366 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22367 this.edit(edits, cx);
22368 }
22369 });
22370
22371 let snapshot = self.snapshot(window, cx);
22372 let ranges = self
22373 .selections
22374 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22375 .into_iter()
22376 .map(|selection| {
22377 snapshot.buffer_snapshot().anchor_after(selection.end)
22378 ..snapshot
22379 .buffer_snapshot()
22380 .anchor_before(selection.end + pending.len())
22381 })
22382 .collect();
22383
22384 if pending.is_empty() {
22385 self.clear_highlights::<PendingInput>(cx);
22386 } else {
22387 self.highlight_text::<PendingInput>(
22388 ranges,
22389 HighlightStyle {
22390 underline: Some(UnderlineStyle {
22391 thickness: px(1.),
22392 color: None,
22393 wavy: false,
22394 }),
22395 ..Default::default()
22396 },
22397 cx,
22398 );
22399 }
22400
22401 self.ime_transaction = self.ime_transaction.or(transaction);
22402 if let Some(transaction) = self.ime_transaction {
22403 self.buffer.update(cx, |buffer, cx| {
22404 buffer.group_until_transaction(transaction, cx);
22405 });
22406 }
22407
22408 if self.text_highlights::<PendingInput>(cx).is_none() {
22409 self.ime_transaction.take();
22410 }
22411 }
22412
22413 pub fn register_action_renderer(
22414 &mut self,
22415 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22416 ) -> Subscription {
22417 let id = self.next_editor_action_id.post_inc();
22418 self.editor_actions
22419 .borrow_mut()
22420 .insert(id, Box::new(listener));
22421
22422 let editor_actions = self.editor_actions.clone();
22423 Subscription::new(move || {
22424 editor_actions.borrow_mut().remove(&id);
22425 })
22426 }
22427
22428 pub fn register_action<A: Action>(
22429 &mut self,
22430 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22431 ) -> Subscription {
22432 let id = self.next_editor_action_id.post_inc();
22433 let listener = Arc::new(listener);
22434 self.editor_actions.borrow_mut().insert(
22435 id,
22436 Box::new(move |_, window, _| {
22437 let listener = listener.clone();
22438 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22439 let action = action.downcast_ref().unwrap();
22440 if phase == DispatchPhase::Bubble {
22441 listener(action, window, cx)
22442 }
22443 })
22444 }),
22445 );
22446
22447 let editor_actions = self.editor_actions.clone();
22448 Subscription::new(move || {
22449 editor_actions.borrow_mut().remove(&id);
22450 })
22451 }
22452
22453 pub fn file_header_size(&self) -> u32 {
22454 FILE_HEADER_HEIGHT
22455 }
22456
22457 pub fn restore(
22458 &mut self,
22459 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22460 window: &mut Window,
22461 cx: &mut Context<Self>,
22462 ) {
22463 let workspace = self.workspace();
22464 let project = self.project();
22465 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22466 let mut tasks = Vec::new();
22467 for (buffer_id, changes) in revert_changes {
22468 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22469 buffer.update(cx, |buffer, cx| {
22470 buffer.edit(
22471 changes
22472 .into_iter()
22473 .map(|(range, text)| (range, text.to_string())),
22474 None,
22475 cx,
22476 );
22477 });
22478
22479 if let Some(project) =
22480 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22481 {
22482 project.update(cx, |project, cx| {
22483 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22484 })
22485 }
22486 }
22487 }
22488 tasks
22489 });
22490 cx.spawn_in(window, async move |_, cx| {
22491 for (buffer, task) in save_tasks {
22492 let result = task.await;
22493 if result.is_err() {
22494 let Some(path) = buffer
22495 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22496 .ok()
22497 else {
22498 continue;
22499 };
22500 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22501 let Some(task) = cx
22502 .update_window_entity(workspace, |workspace, window, cx| {
22503 workspace
22504 .open_path_preview(path, None, false, false, false, window, cx)
22505 })
22506 .ok()
22507 else {
22508 continue;
22509 };
22510 task.await.log_err();
22511 }
22512 }
22513 }
22514 })
22515 .detach();
22516 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22517 selections.refresh()
22518 });
22519 }
22520
22521 pub fn to_pixel_point(
22522 &self,
22523 source: multi_buffer::Anchor,
22524 editor_snapshot: &EditorSnapshot,
22525 window: &mut Window,
22526 ) -> Option<gpui::Point<Pixels>> {
22527 let source_point = source.to_display_point(editor_snapshot);
22528 self.display_to_pixel_point(source_point, editor_snapshot, window)
22529 }
22530
22531 pub fn display_to_pixel_point(
22532 &self,
22533 source: DisplayPoint,
22534 editor_snapshot: &EditorSnapshot,
22535 window: &mut Window,
22536 ) -> Option<gpui::Point<Pixels>> {
22537 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22538 let text_layout_details = self.text_layout_details(window);
22539 let scroll_top = text_layout_details
22540 .scroll_anchor
22541 .scroll_position(editor_snapshot)
22542 .y;
22543
22544 if source.row().as_f64() < scroll_top.floor() {
22545 return None;
22546 }
22547 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22548 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22549 Some(gpui::Point::new(source_x, source_y))
22550 }
22551
22552 pub fn has_visible_completions_menu(&self) -> bool {
22553 !self.edit_prediction_preview_is_active()
22554 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22555 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22556 })
22557 }
22558
22559 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22560 if self.mode.is_minimap() {
22561 return;
22562 }
22563 self.addons
22564 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22565 }
22566
22567 pub fn unregister_addon<T: Addon>(&mut self) {
22568 self.addons.remove(&std::any::TypeId::of::<T>());
22569 }
22570
22571 pub fn addon<T: Addon>(&self) -> Option<&T> {
22572 let type_id = std::any::TypeId::of::<T>();
22573 self.addons
22574 .get(&type_id)
22575 .and_then(|item| item.to_any().downcast_ref::<T>())
22576 }
22577
22578 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22579 let type_id = std::any::TypeId::of::<T>();
22580 self.addons
22581 .get_mut(&type_id)
22582 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22583 }
22584
22585 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22586 let text_layout_details = self.text_layout_details(window);
22587 let style = &text_layout_details.editor_style;
22588 let font_id = window.text_system().resolve_font(&style.text.font());
22589 let font_size = style.text.font_size.to_pixels(window.rem_size());
22590 let line_height = style.text.line_height_in_pixels(window.rem_size());
22591 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22592 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22593
22594 CharacterDimensions {
22595 em_width,
22596 em_advance,
22597 line_height,
22598 }
22599 }
22600
22601 pub fn last_gutter_dimensions(&self) -> &GutterDimensions {
22602 &self.gutter_dimensions
22603 }
22604
22605 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22606 self.load_diff_task.clone()
22607 }
22608
22609 fn read_metadata_from_db(
22610 &mut self,
22611 item_id: u64,
22612 workspace_id: WorkspaceId,
22613 window: &mut Window,
22614 cx: &mut Context<Editor>,
22615 ) {
22616 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22617 && !self.mode.is_minimap()
22618 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22619 {
22620 let buffer_snapshot = OnceCell::new();
22621
22622 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22623 && !folds.is_empty()
22624 {
22625 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22626 self.fold_ranges(
22627 folds
22628 .into_iter()
22629 .map(|(start, end)| {
22630 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22631 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22632 })
22633 .collect(),
22634 false,
22635 window,
22636 cx,
22637 );
22638 }
22639
22640 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22641 && !selections.is_empty()
22642 {
22643 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22644 // skip adding the initial selection to selection history
22645 self.selection_history.mode = SelectionHistoryMode::Skipping;
22646 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22647 s.select_ranges(selections.into_iter().map(|(start, end)| {
22648 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22649 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22650 }));
22651 });
22652 self.selection_history.mode = SelectionHistoryMode::Normal;
22653 };
22654 }
22655
22656 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22657 }
22658
22659 fn update_lsp_data(
22660 &mut self,
22661 for_buffer: Option<BufferId>,
22662 window: &mut Window,
22663 cx: &mut Context<'_, Self>,
22664 ) {
22665 self.pull_diagnostics(for_buffer, window, cx);
22666 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22667 }
22668
22669 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22670 if self.ignore_lsp_data() {
22671 return;
22672 }
22673 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
22674 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22675 }
22676 }
22677
22678 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22679 if self.ignore_lsp_data() {
22680 return;
22681 }
22682
22683 if !self.registered_buffers.contains_key(&buffer_id)
22684 && let Some(project) = self.project.as_ref()
22685 {
22686 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22687 project.update(cx, |project, cx| {
22688 self.registered_buffers.insert(
22689 buffer_id,
22690 project.register_buffer_with_language_servers(&buffer, cx),
22691 );
22692 });
22693 } else {
22694 self.registered_buffers.remove(&buffer_id);
22695 }
22696 }
22697 }
22698
22699 fn ignore_lsp_data(&self) -> bool {
22700 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22701 // skip any LSP updates for it.
22702 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22703 }
22704}
22705
22706fn edit_for_markdown_paste<'a>(
22707 buffer: &MultiBufferSnapshot,
22708 range: Range<MultiBufferOffset>,
22709 to_insert: &'a str,
22710 url: Option<url::Url>,
22711) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22712 if url.is_none() {
22713 return (range, Cow::Borrowed(to_insert));
22714 };
22715
22716 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22717
22718 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22719 Cow::Borrowed(to_insert)
22720 } else {
22721 Cow::Owned(format!("[{old_text}]({to_insert})"))
22722 };
22723 (range, new_text)
22724}
22725
22726fn process_completion_for_edit(
22727 completion: &Completion,
22728 intent: CompletionIntent,
22729 buffer: &Entity<Buffer>,
22730 cursor_position: &text::Anchor,
22731 cx: &mut Context<Editor>,
22732) -> CompletionEdit {
22733 let buffer = buffer.read(cx);
22734 let buffer_snapshot = buffer.snapshot();
22735 let (snippet, new_text) = if completion.is_snippet() {
22736 let mut snippet_source = completion.new_text.clone();
22737 // Workaround for typescript language server issues so that methods don't expand within
22738 // strings and functions with type expressions. The previous point is used because the query
22739 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22740 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22741 let previous_point = if previous_point.column > 0 {
22742 cursor_position.to_previous_offset(&buffer_snapshot)
22743 } else {
22744 cursor_position.to_offset(&buffer_snapshot)
22745 };
22746 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22747 && scope.prefers_label_for_snippet_in_completion()
22748 && let Some(label) = completion.label()
22749 && matches!(
22750 completion.kind(),
22751 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22752 )
22753 {
22754 snippet_source = label;
22755 }
22756 match Snippet::parse(&snippet_source).log_err() {
22757 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22758 None => (None, completion.new_text.clone()),
22759 }
22760 } else {
22761 (None, completion.new_text.clone())
22762 };
22763
22764 let mut range_to_replace = {
22765 let replace_range = &completion.replace_range;
22766 if let CompletionSource::Lsp {
22767 insert_range: Some(insert_range),
22768 ..
22769 } = &completion.source
22770 {
22771 debug_assert_eq!(
22772 insert_range.start, replace_range.start,
22773 "insert_range and replace_range should start at the same position"
22774 );
22775 debug_assert!(
22776 insert_range
22777 .start
22778 .cmp(cursor_position, &buffer_snapshot)
22779 .is_le(),
22780 "insert_range should start before or at cursor position"
22781 );
22782 debug_assert!(
22783 replace_range
22784 .start
22785 .cmp(cursor_position, &buffer_snapshot)
22786 .is_le(),
22787 "replace_range should start before or at cursor position"
22788 );
22789
22790 let should_replace = match intent {
22791 CompletionIntent::CompleteWithInsert => false,
22792 CompletionIntent::CompleteWithReplace => true,
22793 CompletionIntent::Complete | CompletionIntent::Compose => {
22794 let insert_mode =
22795 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22796 .completions
22797 .lsp_insert_mode;
22798 match insert_mode {
22799 LspInsertMode::Insert => false,
22800 LspInsertMode::Replace => true,
22801 LspInsertMode::ReplaceSubsequence => {
22802 let mut text_to_replace = buffer.chars_for_range(
22803 buffer.anchor_before(replace_range.start)
22804 ..buffer.anchor_after(replace_range.end),
22805 );
22806 let mut current_needle = text_to_replace.next();
22807 for haystack_ch in completion.label.text.chars() {
22808 if let Some(needle_ch) = current_needle
22809 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22810 {
22811 current_needle = text_to_replace.next();
22812 }
22813 }
22814 current_needle.is_none()
22815 }
22816 LspInsertMode::ReplaceSuffix => {
22817 if replace_range
22818 .end
22819 .cmp(cursor_position, &buffer_snapshot)
22820 .is_gt()
22821 {
22822 let range_after_cursor = *cursor_position..replace_range.end;
22823 let text_after_cursor = buffer
22824 .text_for_range(
22825 buffer.anchor_before(range_after_cursor.start)
22826 ..buffer.anchor_after(range_after_cursor.end),
22827 )
22828 .collect::<String>()
22829 .to_ascii_lowercase();
22830 completion
22831 .label
22832 .text
22833 .to_ascii_lowercase()
22834 .ends_with(&text_after_cursor)
22835 } else {
22836 true
22837 }
22838 }
22839 }
22840 }
22841 };
22842
22843 if should_replace {
22844 replace_range.clone()
22845 } else {
22846 insert_range.clone()
22847 }
22848 } else {
22849 replace_range.clone()
22850 }
22851 };
22852
22853 if range_to_replace
22854 .end
22855 .cmp(cursor_position, &buffer_snapshot)
22856 .is_lt()
22857 {
22858 range_to_replace.end = *cursor_position;
22859 }
22860
22861 let replace_range = range_to_replace.to_offset(buffer);
22862 CompletionEdit {
22863 new_text,
22864 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22865 snippet,
22866 }
22867}
22868
22869struct CompletionEdit {
22870 new_text: String,
22871 replace_range: Range<BufferOffset>,
22872 snippet: Option<Snippet>,
22873}
22874
22875fn insert_extra_newline_brackets(
22876 buffer: &MultiBufferSnapshot,
22877 range: Range<MultiBufferOffset>,
22878 language: &language::LanguageScope,
22879) -> bool {
22880 let leading_whitespace_len = buffer
22881 .reversed_chars_at(range.start)
22882 .take_while(|c| c.is_whitespace() && *c != '\n')
22883 .map(|c| c.len_utf8())
22884 .sum::<usize>();
22885 let trailing_whitespace_len = buffer
22886 .chars_at(range.end)
22887 .take_while(|c| c.is_whitespace() && *c != '\n')
22888 .map(|c| c.len_utf8())
22889 .sum::<usize>();
22890 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22891
22892 language.brackets().any(|(pair, enabled)| {
22893 let pair_start = pair.start.trim_end();
22894 let pair_end = pair.end.trim_start();
22895
22896 enabled
22897 && pair.newline
22898 && buffer.contains_str_at(range.end, pair_end)
22899 && buffer.contains_str_at(
22900 range.start.saturating_sub_usize(pair_start.len()),
22901 pair_start,
22902 )
22903 })
22904}
22905
22906fn insert_extra_newline_tree_sitter(
22907 buffer: &MultiBufferSnapshot,
22908 range: Range<MultiBufferOffset>,
22909) -> bool {
22910 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22911 [(buffer, range, _)] => (*buffer, range.clone()),
22912 _ => return false,
22913 };
22914 let pair = {
22915 let mut result: Option<BracketMatch<usize>> = None;
22916
22917 for pair in buffer
22918 .all_bracket_ranges(range.start.0..range.end.0)
22919 .filter(move |pair| {
22920 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22921 })
22922 {
22923 let len = pair.close_range.end - pair.open_range.start;
22924
22925 if let Some(existing) = &result {
22926 let existing_len = existing.close_range.end - existing.open_range.start;
22927 if len > existing_len {
22928 continue;
22929 }
22930 }
22931
22932 result = Some(pair);
22933 }
22934
22935 result
22936 };
22937 let Some(pair) = pair else {
22938 return false;
22939 };
22940 pair.newline_only
22941 && buffer
22942 .chars_for_range(pair.open_range.end..range.start.0)
22943 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22944 .all(|c| c.is_whitespace() && c != '\n')
22945}
22946
22947fn update_uncommitted_diff_for_buffer(
22948 editor: Entity<Editor>,
22949 project: &Entity<Project>,
22950 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22951 buffer: Entity<MultiBuffer>,
22952 cx: &mut App,
22953) -> Task<()> {
22954 let mut tasks = Vec::new();
22955 project.update(cx, |project, cx| {
22956 for buffer in buffers {
22957 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22958 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22959 }
22960 }
22961 });
22962 cx.spawn(async move |cx| {
22963 let diffs = future::join_all(tasks).await;
22964 if editor
22965 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22966 .unwrap_or(false)
22967 {
22968 return;
22969 }
22970
22971 buffer
22972 .update(cx, |buffer, cx| {
22973 for diff in diffs.into_iter().flatten() {
22974 buffer.add_diff(diff, cx);
22975 }
22976 })
22977 .ok();
22978 })
22979}
22980
22981fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22982 let tab_size = tab_size.get() as usize;
22983 let mut width = offset;
22984
22985 for ch in text.chars() {
22986 width += if ch == '\t' {
22987 tab_size - (width % tab_size)
22988 } else {
22989 1
22990 };
22991 }
22992
22993 width - offset
22994}
22995
22996#[cfg(test)]
22997mod tests {
22998 use super::*;
22999
23000 #[test]
23001 fn test_string_size_with_expanded_tabs() {
23002 let nz = |val| NonZeroU32::new(val).unwrap();
23003 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23004 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23005 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23006 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23007 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23008 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23009 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23010 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23011 }
23012}
23013
23014/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23015struct WordBreakingTokenizer<'a> {
23016 input: &'a str,
23017}
23018
23019impl<'a> WordBreakingTokenizer<'a> {
23020 fn new(input: &'a str) -> Self {
23021 Self { input }
23022 }
23023}
23024
23025fn is_char_ideographic(ch: char) -> bool {
23026 use unicode_script::Script::*;
23027 use unicode_script::UnicodeScript;
23028 matches!(ch.script(), Han | Tangut | Yi)
23029}
23030
23031fn is_grapheme_ideographic(text: &str) -> bool {
23032 text.chars().any(is_char_ideographic)
23033}
23034
23035fn is_grapheme_whitespace(text: &str) -> bool {
23036 text.chars().any(|x| x.is_whitespace())
23037}
23038
23039fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23040 text.chars()
23041 .next()
23042 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23043}
23044
23045#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23046enum WordBreakToken<'a> {
23047 Word { token: &'a str, grapheme_len: usize },
23048 InlineWhitespace { token: &'a str, grapheme_len: usize },
23049 Newline,
23050}
23051
23052impl<'a> Iterator for WordBreakingTokenizer<'a> {
23053 /// Yields a span, the count of graphemes in the token, and whether it was
23054 /// whitespace. Note that it also breaks at word boundaries.
23055 type Item = WordBreakToken<'a>;
23056
23057 fn next(&mut self) -> Option<Self::Item> {
23058 use unicode_segmentation::UnicodeSegmentation;
23059 if self.input.is_empty() {
23060 return None;
23061 }
23062
23063 let mut iter = self.input.graphemes(true).peekable();
23064 let mut offset = 0;
23065 let mut grapheme_len = 0;
23066 if let Some(first_grapheme) = iter.next() {
23067 let is_newline = first_grapheme == "\n";
23068 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23069 offset += first_grapheme.len();
23070 grapheme_len += 1;
23071 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23072 if let Some(grapheme) = iter.peek().copied()
23073 && should_stay_with_preceding_ideograph(grapheme)
23074 {
23075 offset += grapheme.len();
23076 grapheme_len += 1;
23077 }
23078 } else {
23079 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23080 let mut next_word_bound = words.peek().copied();
23081 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23082 next_word_bound = words.next();
23083 }
23084 while let Some(grapheme) = iter.peek().copied() {
23085 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23086 break;
23087 };
23088 if is_grapheme_whitespace(grapheme) != is_whitespace
23089 || (grapheme == "\n") != is_newline
23090 {
23091 break;
23092 };
23093 offset += grapheme.len();
23094 grapheme_len += 1;
23095 iter.next();
23096 }
23097 }
23098 let token = &self.input[..offset];
23099 self.input = &self.input[offset..];
23100 if token == "\n" {
23101 Some(WordBreakToken::Newline)
23102 } else if is_whitespace {
23103 Some(WordBreakToken::InlineWhitespace {
23104 token,
23105 grapheme_len,
23106 })
23107 } else {
23108 Some(WordBreakToken::Word {
23109 token,
23110 grapheme_len,
23111 })
23112 }
23113 } else {
23114 None
23115 }
23116 }
23117}
23118
23119#[test]
23120fn test_word_breaking_tokenizer() {
23121 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23122 ("", &[]),
23123 (" ", &[whitespace(" ", 2)]),
23124 ("Ʒ", &[word("Ʒ", 1)]),
23125 ("Ǽ", &[word("Ǽ", 1)]),
23126 ("⋑", &[word("⋑", 1)]),
23127 ("⋑⋑", &[word("⋑⋑", 2)]),
23128 (
23129 "原理,进而",
23130 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23131 ),
23132 (
23133 "hello world",
23134 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23135 ),
23136 (
23137 "hello, world",
23138 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23139 ),
23140 (
23141 " hello world",
23142 &[
23143 whitespace(" ", 2),
23144 word("hello", 5),
23145 whitespace(" ", 1),
23146 word("world", 5),
23147 ],
23148 ),
23149 (
23150 "这是什么 \n 钢笔",
23151 &[
23152 word("这", 1),
23153 word("是", 1),
23154 word("什", 1),
23155 word("么", 1),
23156 whitespace(" ", 1),
23157 newline(),
23158 whitespace(" ", 1),
23159 word("钢", 1),
23160 word("笔", 1),
23161 ],
23162 ),
23163 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23164 ];
23165
23166 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23167 WordBreakToken::Word {
23168 token,
23169 grapheme_len,
23170 }
23171 }
23172
23173 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23174 WordBreakToken::InlineWhitespace {
23175 token,
23176 grapheme_len,
23177 }
23178 }
23179
23180 fn newline() -> WordBreakToken<'static> {
23181 WordBreakToken::Newline
23182 }
23183
23184 for (input, result) in tests {
23185 assert_eq!(
23186 WordBreakingTokenizer::new(input)
23187 .collect::<Vec<_>>()
23188 .as_slice(),
23189 *result,
23190 );
23191 }
23192}
23193
23194fn wrap_with_prefix(
23195 first_line_prefix: String,
23196 subsequent_lines_prefix: String,
23197 unwrapped_text: String,
23198 wrap_column: usize,
23199 tab_size: NonZeroU32,
23200 preserve_existing_whitespace: bool,
23201) -> String {
23202 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23203 let subsequent_lines_prefix_len =
23204 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23205 let mut wrapped_text = String::new();
23206 let mut current_line = first_line_prefix;
23207 let mut is_first_line = true;
23208
23209 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23210 let mut current_line_len = first_line_prefix_len;
23211 let mut in_whitespace = false;
23212 for token in tokenizer {
23213 let have_preceding_whitespace = in_whitespace;
23214 match token {
23215 WordBreakToken::Word {
23216 token,
23217 grapheme_len,
23218 } => {
23219 in_whitespace = false;
23220 let current_prefix_len = if is_first_line {
23221 first_line_prefix_len
23222 } else {
23223 subsequent_lines_prefix_len
23224 };
23225 if current_line_len + grapheme_len > wrap_column
23226 && current_line_len != current_prefix_len
23227 {
23228 wrapped_text.push_str(current_line.trim_end());
23229 wrapped_text.push('\n');
23230 is_first_line = false;
23231 current_line = subsequent_lines_prefix.clone();
23232 current_line_len = subsequent_lines_prefix_len;
23233 }
23234 current_line.push_str(token);
23235 current_line_len += grapheme_len;
23236 }
23237 WordBreakToken::InlineWhitespace {
23238 mut token,
23239 mut grapheme_len,
23240 } => {
23241 in_whitespace = true;
23242 if have_preceding_whitespace && !preserve_existing_whitespace {
23243 continue;
23244 }
23245 if !preserve_existing_whitespace {
23246 // Keep a single whitespace grapheme as-is
23247 if let Some(first) =
23248 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23249 {
23250 token = first;
23251 } else {
23252 token = " ";
23253 }
23254 grapheme_len = 1;
23255 }
23256 let current_prefix_len = if is_first_line {
23257 first_line_prefix_len
23258 } else {
23259 subsequent_lines_prefix_len
23260 };
23261 if current_line_len + grapheme_len > wrap_column {
23262 wrapped_text.push_str(current_line.trim_end());
23263 wrapped_text.push('\n');
23264 is_first_line = false;
23265 current_line = subsequent_lines_prefix.clone();
23266 current_line_len = subsequent_lines_prefix_len;
23267 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23268 current_line.push_str(token);
23269 current_line_len += grapheme_len;
23270 }
23271 }
23272 WordBreakToken::Newline => {
23273 in_whitespace = true;
23274 let current_prefix_len = if is_first_line {
23275 first_line_prefix_len
23276 } else {
23277 subsequent_lines_prefix_len
23278 };
23279 if preserve_existing_whitespace {
23280 wrapped_text.push_str(current_line.trim_end());
23281 wrapped_text.push('\n');
23282 is_first_line = false;
23283 current_line = subsequent_lines_prefix.clone();
23284 current_line_len = subsequent_lines_prefix_len;
23285 } else if have_preceding_whitespace {
23286 continue;
23287 } else if current_line_len + 1 > wrap_column
23288 && current_line_len != current_prefix_len
23289 {
23290 wrapped_text.push_str(current_line.trim_end());
23291 wrapped_text.push('\n');
23292 is_first_line = false;
23293 current_line = subsequent_lines_prefix.clone();
23294 current_line_len = subsequent_lines_prefix_len;
23295 } else if current_line_len != current_prefix_len {
23296 current_line.push(' ');
23297 current_line_len += 1;
23298 }
23299 }
23300 }
23301 }
23302
23303 if !current_line.is_empty() {
23304 wrapped_text.push_str(¤t_line);
23305 }
23306 wrapped_text
23307}
23308
23309#[test]
23310fn test_wrap_with_prefix() {
23311 assert_eq!(
23312 wrap_with_prefix(
23313 "# ".to_string(),
23314 "# ".to_string(),
23315 "abcdefg".to_string(),
23316 4,
23317 NonZeroU32::new(4).unwrap(),
23318 false,
23319 ),
23320 "# abcdefg"
23321 );
23322 assert_eq!(
23323 wrap_with_prefix(
23324 "".to_string(),
23325 "".to_string(),
23326 "\thello world".to_string(),
23327 8,
23328 NonZeroU32::new(4).unwrap(),
23329 false,
23330 ),
23331 "hello\nworld"
23332 );
23333 assert_eq!(
23334 wrap_with_prefix(
23335 "// ".to_string(),
23336 "// ".to_string(),
23337 "xx \nyy zz aa bb cc".to_string(),
23338 12,
23339 NonZeroU32::new(4).unwrap(),
23340 false,
23341 ),
23342 "// xx yy zz\n// aa bb cc"
23343 );
23344 assert_eq!(
23345 wrap_with_prefix(
23346 String::new(),
23347 String::new(),
23348 "这是什么 \n 钢笔".to_string(),
23349 3,
23350 NonZeroU32::new(4).unwrap(),
23351 false,
23352 ),
23353 "这是什\n么 钢\n笔"
23354 );
23355 assert_eq!(
23356 wrap_with_prefix(
23357 String::new(),
23358 String::new(),
23359 format!("foo{}bar", '\u{2009}'), // thin space
23360 80,
23361 NonZeroU32::new(4).unwrap(),
23362 false,
23363 ),
23364 format!("foo{}bar", '\u{2009}')
23365 );
23366}
23367
23368pub trait CollaborationHub {
23369 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23370 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23371 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23372}
23373
23374impl CollaborationHub for Entity<Project> {
23375 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23376 self.read(cx).collaborators()
23377 }
23378
23379 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23380 self.read(cx).user_store().read(cx).participant_indices()
23381 }
23382
23383 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23384 let this = self.read(cx);
23385 let user_ids = this.collaborators().values().map(|c| c.user_id);
23386 this.user_store().read(cx).participant_names(user_ids, cx)
23387 }
23388}
23389
23390pub trait SemanticsProvider {
23391 fn hover(
23392 &self,
23393 buffer: &Entity<Buffer>,
23394 position: text::Anchor,
23395 cx: &mut App,
23396 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23397
23398 fn inline_values(
23399 &self,
23400 buffer_handle: Entity<Buffer>,
23401 range: Range<text::Anchor>,
23402 cx: &mut App,
23403 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23404
23405 fn applicable_inlay_chunks(
23406 &self,
23407 buffer: &Entity<Buffer>,
23408 ranges: &[Range<text::Anchor>],
23409 cx: &mut App,
23410 ) -> Vec<Range<BufferRow>>;
23411
23412 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23413
23414 fn inlay_hints(
23415 &self,
23416 invalidate: InvalidationStrategy,
23417 buffer: Entity<Buffer>,
23418 ranges: Vec<Range<text::Anchor>>,
23419 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23420 cx: &mut App,
23421 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23422
23423 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23424
23425 fn document_highlights(
23426 &self,
23427 buffer: &Entity<Buffer>,
23428 position: text::Anchor,
23429 cx: &mut App,
23430 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23431
23432 fn definitions(
23433 &self,
23434 buffer: &Entity<Buffer>,
23435 position: text::Anchor,
23436 kind: GotoDefinitionKind,
23437 cx: &mut App,
23438 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23439
23440 fn range_for_rename(
23441 &self,
23442 buffer: &Entity<Buffer>,
23443 position: text::Anchor,
23444 cx: &mut App,
23445 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23446
23447 fn perform_rename(
23448 &self,
23449 buffer: &Entity<Buffer>,
23450 position: text::Anchor,
23451 new_name: String,
23452 cx: &mut App,
23453 ) -> Option<Task<Result<ProjectTransaction>>>;
23454}
23455
23456pub trait CompletionProvider {
23457 fn completions(
23458 &self,
23459 excerpt_id: ExcerptId,
23460 buffer: &Entity<Buffer>,
23461 buffer_position: text::Anchor,
23462 trigger: CompletionContext,
23463 window: &mut Window,
23464 cx: &mut Context<Editor>,
23465 ) -> Task<Result<Vec<CompletionResponse>>>;
23466
23467 fn resolve_completions(
23468 &self,
23469 _buffer: Entity<Buffer>,
23470 _completion_indices: Vec<usize>,
23471 _completions: Rc<RefCell<Box<[Completion]>>>,
23472 _cx: &mut Context<Editor>,
23473 ) -> Task<Result<bool>> {
23474 Task::ready(Ok(false))
23475 }
23476
23477 fn apply_additional_edits_for_completion(
23478 &self,
23479 _buffer: Entity<Buffer>,
23480 _completions: Rc<RefCell<Box<[Completion]>>>,
23481 _completion_index: usize,
23482 _push_to_history: bool,
23483 _cx: &mut Context<Editor>,
23484 ) -> Task<Result<Option<language::Transaction>>> {
23485 Task::ready(Ok(None))
23486 }
23487
23488 fn is_completion_trigger(
23489 &self,
23490 buffer: &Entity<Buffer>,
23491 position: language::Anchor,
23492 text: &str,
23493 trigger_in_words: bool,
23494 cx: &mut Context<Editor>,
23495 ) -> bool;
23496
23497 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23498
23499 fn sort_completions(&self) -> bool {
23500 true
23501 }
23502
23503 fn filter_completions(&self) -> bool {
23504 true
23505 }
23506
23507 fn show_snippets(&self) -> bool {
23508 false
23509 }
23510}
23511
23512pub trait CodeActionProvider {
23513 fn id(&self) -> Arc<str>;
23514
23515 fn code_actions(
23516 &self,
23517 buffer: &Entity<Buffer>,
23518 range: Range<text::Anchor>,
23519 window: &mut Window,
23520 cx: &mut App,
23521 ) -> Task<Result<Vec<CodeAction>>>;
23522
23523 fn apply_code_action(
23524 &self,
23525 buffer_handle: Entity<Buffer>,
23526 action: CodeAction,
23527 excerpt_id: ExcerptId,
23528 push_to_history: bool,
23529 window: &mut Window,
23530 cx: &mut App,
23531 ) -> Task<Result<ProjectTransaction>>;
23532}
23533
23534impl CodeActionProvider for Entity<Project> {
23535 fn id(&self) -> Arc<str> {
23536 "project".into()
23537 }
23538
23539 fn code_actions(
23540 &self,
23541 buffer: &Entity<Buffer>,
23542 range: Range<text::Anchor>,
23543 _window: &mut Window,
23544 cx: &mut App,
23545 ) -> Task<Result<Vec<CodeAction>>> {
23546 self.update(cx, |project, cx| {
23547 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23548 let code_actions = project.code_actions(buffer, range, None, cx);
23549 cx.background_spawn(async move {
23550 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23551 Ok(code_lens_actions
23552 .context("code lens fetch")?
23553 .into_iter()
23554 .flatten()
23555 .chain(
23556 code_actions
23557 .context("code action fetch")?
23558 .into_iter()
23559 .flatten(),
23560 )
23561 .collect())
23562 })
23563 })
23564 }
23565
23566 fn apply_code_action(
23567 &self,
23568 buffer_handle: Entity<Buffer>,
23569 action: CodeAction,
23570 _excerpt_id: ExcerptId,
23571 push_to_history: bool,
23572 _window: &mut Window,
23573 cx: &mut App,
23574 ) -> Task<Result<ProjectTransaction>> {
23575 self.update(cx, |project, cx| {
23576 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23577 })
23578 }
23579}
23580
23581fn snippet_completions(
23582 project: &Project,
23583 buffer: &Entity<Buffer>,
23584 buffer_anchor: text::Anchor,
23585 classifier: CharClassifier,
23586 cx: &mut App,
23587) -> Task<Result<CompletionResponse>> {
23588 let languages = buffer.read(cx).languages_at(buffer_anchor);
23589 let snippet_store = project.snippets().read(cx);
23590
23591 let scopes: Vec<_> = languages
23592 .iter()
23593 .filter_map(|language| {
23594 let language_name = language.lsp_id();
23595 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23596
23597 if snippets.is_empty() {
23598 None
23599 } else {
23600 Some((language.default_scope(), snippets))
23601 }
23602 })
23603 .collect();
23604
23605 if scopes.is_empty() {
23606 return Task::ready(Ok(CompletionResponse {
23607 completions: vec![],
23608 display_options: CompletionDisplayOptions::default(),
23609 is_incomplete: false,
23610 }));
23611 }
23612
23613 let snapshot = buffer.read(cx).text_snapshot();
23614 let executor = cx.background_executor().clone();
23615
23616 cx.background_spawn(async move {
23617 let is_word_char = |c| classifier.is_word(c);
23618
23619 let mut is_incomplete = false;
23620 let mut completions: Vec<Completion> = Vec::new();
23621
23622 const MAX_PREFIX_LEN: usize = 128;
23623 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23624 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23625 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23626
23627 let max_buffer_window: String = snapshot
23628 .text_for_range(window_start..buffer_offset)
23629 .collect();
23630
23631 if max_buffer_window.is_empty() {
23632 return Ok(CompletionResponse {
23633 completions: vec![],
23634 display_options: CompletionDisplayOptions::default(),
23635 is_incomplete: true,
23636 });
23637 }
23638
23639 for (_scope, snippets) in scopes.into_iter() {
23640 // Sort snippets by word count to match longer snippet prefixes first.
23641 let mut sorted_snippet_candidates = snippets
23642 .iter()
23643 .enumerate()
23644 .flat_map(|(snippet_ix, snippet)| {
23645 snippet
23646 .prefix
23647 .iter()
23648 .enumerate()
23649 .map(move |(prefix_ix, prefix)| {
23650 let word_count =
23651 snippet_candidate_suffixes(prefix, is_word_char).count();
23652 ((snippet_ix, prefix_ix), prefix, word_count)
23653 })
23654 })
23655 .collect_vec();
23656 sorted_snippet_candidates
23657 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23658
23659 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23660
23661 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23662 .take(
23663 sorted_snippet_candidates
23664 .first()
23665 .map(|(_, _, word_count)| *word_count)
23666 .unwrap_or_default(),
23667 )
23668 .collect_vec();
23669
23670 const MAX_RESULTS: usize = 100;
23671 // Each match also remembers how many characters from the buffer it consumed
23672 let mut matches: Vec<(StringMatch, usize)> = vec![];
23673
23674 let mut snippet_list_cutoff_index = 0;
23675 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23676 let word_count = buffer_index + 1;
23677 // Increase `snippet_list_cutoff_index` until we have all of the
23678 // snippets with sufficiently many words.
23679 while sorted_snippet_candidates
23680 .get(snippet_list_cutoff_index)
23681 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23682 *snippet_word_count >= word_count
23683 })
23684 {
23685 snippet_list_cutoff_index += 1;
23686 }
23687
23688 // Take only the candidates with at least `word_count` many words
23689 let snippet_candidates_at_word_len =
23690 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23691
23692 let candidates = snippet_candidates_at_word_len
23693 .iter()
23694 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23695 .enumerate() // index in `sorted_snippet_candidates`
23696 // First char must match
23697 .filter(|(_ix, prefix)| {
23698 itertools::equal(
23699 prefix
23700 .chars()
23701 .next()
23702 .into_iter()
23703 .flat_map(|c| c.to_lowercase()),
23704 buffer_window
23705 .chars()
23706 .next()
23707 .into_iter()
23708 .flat_map(|c| c.to_lowercase()),
23709 )
23710 })
23711 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23712 .collect::<Vec<StringMatchCandidate>>();
23713
23714 matches.extend(
23715 fuzzy::match_strings(
23716 &candidates,
23717 &buffer_window,
23718 buffer_window.chars().any(|c| c.is_uppercase()),
23719 true,
23720 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23721 &Default::default(),
23722 executor.clone(),
23723 )
23724 .await
23725 .into_iter()
23726 .map(|string_match| (string_match, buffer_window.len())),
23727 );
23728
23729 if matches.len() >= MAX_RESULTS {
23730 break;
23731 }
23732 }
23733
23734 let to_lsp = |point: &text::Anchor| {
23735 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23736 point_to_lsp(end)
23737 };
23738 let lsp_end = to_lsp(&buffer_anchor);
23739
23740 if matches.len() >= MAX_RESULTS {
23741 is_incomplete = true;
23742 }
23743
23744 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23745 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23746 sorted_snippet_candidates[string_match.candidate_id];
23747 let snippet = &snippets[snippet_index];
23748 let start = buffer_offset - buffer_window_len;
23749 let start = snapshot.anchor_before(start);
23750 let range = start..buffer_anchor;
23751 let lsp_start = to_lsp(&start);
23752 let lsp_range = lsp::Range {
23753 start: lsp_start,
23754 end: lsp_end,
23755 };
23756 Completion {
23757 replace_range: range,
23758 new_text: snippet.body.clone(),
23759 source: CompletionSource::Lsp {
23760 insert_range: None,
23761 server_id: LanguageServerId(usize::MAX),
23762 resolved: true,
23763 lsp_completion: Box::new(lsp::CompletionItem {
23764 label: snippet.prefix.first().unwrap().clone(),
23765 kind: Some(CompletionItemKind::SNIPPET),
23766 label_details: snippet.description.as_ref().map(|description| {
23767 lsp::CompletionItemLabelDetails {
23768 detail: Some(description.clone()),
23769 description: None,
23770 }
23771 }),
23772 insert_text_format: Some(InsertTextFormat::SNIPPET),
23773 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23774 lsp::InsertReplaceEdit {
23775 new_text: snippet.body.clone(),
23776 insert: lsp_range,
23777 replace: lsp_range,
23778 },
23779 )),
23780 filter_text: Some(snippet.body.clone()),
23781 sort_text: Some(char::MAX.to_string()),
23782 ..lsp::CompletionItem::default()
23783 }),
23784 lsp_defaults: None,
23785 },
23786 label: CodeLabel {
23787 text: matching_prefix.clone(),
23788 runs: Vec::new(),
23789 filter_range: 0..matching_prefix.len(),
23790 },
23791 icon_path: None,
23792 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23793 single_line: snippet.name.clone().into(),
23794 plain_text: snippet
23795 .description
23796 .clone()
23797 .map(|description| description.into()),
23798 }),
23799 insert_text_mode: None,
23800 confirm: None,
23801 match_start: Some(start),
23802 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23803 }
23804 }));
23805 }
23806
23807 Ok(CompletionResponse {
23808 completions,
23809 display_options: CompletionDisplayOptions::default(),
23810 is_incomplete,
23811 })
23812 })
23813}
23814
23815impl CompletionProvider for Entity<Project> {
23816 fn completions(
23817 &self,
23818 _excerpt_id: ExcerptId,
23819 buffer: &Entity<Buffer>,
23820 buffer_position: text::Anchor,
23821 options: CompletionContext,
23822 _window: &mut Window,
23823 cx: &mut Context<Editor>,
23824 ) -> Task<Result<Vec<CompletionResponse>>> {
23825 self.update(cx, |project, cx| {
23826 let task = project.completions(buffer, buffer_position, options, cx);
23827 cx.background_spawn(task)
23828 })
23829 }
23830
23831 fn resolve_completions(
23832 &self,
23833 buffer: Entity<Buffer>,
23834 completion_indices: Vec<usize>,
23835 completions: Rc<RefCell<Box<[Completion]>>>,
23836 cx: &mut Context<Editor>,
23837 ) -> Task<Result<bool>> {
23838 self.update(cx, |project, cx| {
23839 project.lsp_store().update(cx, |lsp_store, cx| {
23840 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23841 })
23842 })
23843 }
23844
23845 fn apply_additional_edits_for_completion(
23846 &self,
23847 buffer: Entity<Buffer>,
23848 completions: Rc<RefCell<Box<[Completion]>>>,
23849 completion_index: usize,
23850 push_to_history: bool,
23851 cx: &mut Context<Editor>,
23852 ) -> Task<Result<Option<language::Transaction>>> {
23853 self.update(cx, |project, cx| {
23854 project.lsp_store().update(cx, |lsp_store, cx| {
23855 lsp_store.apply_additional_edits_for_completion(
23856 buffer,
23857 completions,
23858 completion_index,
23859 push_to_history,
23860 cx,
23861 )
23862 })
23863 })
23864 }
23865
23866 fn is_completion_trigger(
23867 &self,
23868 buffer: &Entity<Buffer>,
23869 position: language::Anchor,
23870 text: &str,
23871 trigger_in_words: bool,
23872 cx: &mut Context<Editor>,
23873 ) -> bool {
23874 let mut chars = text.chars();
23875 let char = if let Some(char) = chars.next() {
23876 char
23877 } else {
23878 return false;
23879 };
23880 if chars.next().is_some() {
23881 return false;
23882 }
23883
23884 let buffer = buffer.read(cx);
23885 let snapshot = buffer.snapshot();
23886 let classifier = snapshot
23887 .char_classifier_at(position)
23888 .scope_context(Some(CharScopeContext::Completion));
23889 if trigger_in_words && classifier.is_word(char) {
23890 return true;
23891 }
23892
23893 buffer.completion_triggers().contains(text)
23894 }
23895
23896 fn show_snippets(&self) -> bool {
23897 true
23898 }
23899}
23900
23901impl SemanticsProvider for Entity<Project> {
23902 fn hover(
23903 &self,
23904 buffer: &Entity<Buffer>,
23905 position: text::Anchor,
23906 cx: &mut App,
23907 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23908 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23909 }
23910
23911 fn document_highlights(
23912 &self,
23913 buffer: &Entity<Buffer>,
23914 position: text::Anchor,
23915 cx: &mut App,
23916 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23917 Some(self.update(cx, |project, cx| {
23918 project.document_highlights(buffer, position, cx)
23919 }))
23920 }
23921
23922 fn definitions(
23923 &self,
23924 buffer: &Entity<Buffer>,
23925 position: text::Anchor,
23926 kind: GotoDefinitionKind,
23927 cx: &mut App,
23928 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23929 Some(self.update(cx, |project, cx| match kind {
23930 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23931 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23932 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23933 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23934 }))
23935 }
23936
23937 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23938 self.update(cx, |project, cx| {
23939 if project
23940 .active_debug_session(cx)
23941 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23942 {
23943 return true;
23944 }
23945
23946 buffer.update(cx, |buffer, cx| {
23947 project.any_language_server_supports_inlay_hints(buffer, cx)
23948 })
23949 })
23950 }
23951
23952 fn inline_values(
23953 &self,
23954 buffer_handle: Entity<Buffer>,
23955 range: Range<text::Anchor>,
23956 cx: &mut App,
23957 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23958 self.update(cx, |project, cx| {
23959 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23960
23961 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23962 })
23963 }
23964
23965 fn applicable_inlay_chunks(
23966 &self,
23967 buffer: &Entity<Buffer>,
23968 ranges: &[Range<text::Anchor>],
23969 cx: &mut App,
23970 ) -> Vec<Range<BufferRow>> {
23971 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23972 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23973 })
23974 }
23975
23976 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23977 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23978 lsp_store.invalidate_inlay_hints(for_buffers)
23979 });
23980 }
23981
23982 fn inlay_hints(
23983 &self,
23984 invalidate: InvalidationStrategy,
23985 buffer: Entity<Buffer>,
23986 ranges: Vec<Range<text::Anchor>>,
23987 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23988 cx: &mut App,
23989 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23990 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23991 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23992 }))
23993 }
23994
23995 fn range_for_rename(
23996 &self,
23997 buffer: &Entity<Buffer>,
23998 position: text::Anchor,
23999 cx: &mut App,
24000 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24001 Some(self.update(cx, |project, cx| {
24002 let buffer = buffer.clone();
24003 let task = project.prepare_rename(buffer.clone(), position, cx);
24004 cx.spawn(async move |_, cx| {
24005 Ok(match task.await? {
24006 PrepareRenameResponse::Success(range) => Some(range),
24007 PrepareRenameResponse::InvalidPosition => None,
24008 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24009 // Fallback on using TreeSitter info to determine identifier range
24010 buffer.read_with(cx, |buffer, _| {
24011 let snapshot = buffer.snapshot();
24012 let (range, kind) = snapshot.surrounding_word(position, None);
24013 if kind != Some(CharKind::Word) {
24014 return None;
24015 }
24016 Some(
24017 snapshot.anchor_before(range.start)
24018 ..snapshot.anchor_after(range.end),
24019 )
24020 })?
24021 }
24022 })
24023 })
24024 }))
24025 }
24026
24027 fn perform_rename(
24028 &self,
24029 buffer: &Entity<Buffer>,
24030 position: text::Anchor,
24031 new_name: String,
24032 cx: &mut App,
24033 ) -> Option<Task<Result<ProjectTransaction>>> {
24034 Some(self.update(cx, |project, cx| {
24035 project.perform_rename(buffer.clone(), position, new_name, cx)
24036 }))
24037 }
24038}
24039
24040fn consume_contiguous_rows(
24041 contiguous_row_selections: &mut Vec<Selection<Point>>,
24042 selection: &Selection<Point>,
24043 display_map: &DisplaySnapshot,
24044 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24045) -> (MultiBufferRow, MultiBufferRow) {
24046 contiguous_row_selections.push(selection.clone());
24047 let start_row = starting_row(selection, display_map);
24048 let mut end_row = ending_row(selection, display_map);
24049
24050 while let Some(next_selection) = selections.peek() {
24051 if next_selection.start.row <= end_row.0 {
24052 end_row = ending_row(next_selection, display_map);
24053 contiguous_row_selections.push(selections.next().unwrap().clone());
24054 } else {
24055 break;
24056 }
24057 }
24058 (start_row, end_row)
24059}
24060
24061fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24062 if selection.start.column > 0 {
24063 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24064 } else {
24065 MultiBufferRow(selection.start.row)
24066 }
24067}
24068
24069fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24070 if next_selection.end.column > 0 || next_selection.is_empty() {
24071 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24072 } else {
24073 MultiBufferRow(next_selection.end.row)
24074 }
24075}
24076
24077impl EditorSnapshot {
24078 pub fn remote_selections_in_range<'a>(
24079 &'a self,
24080 range: &'a Range<Anchor>,
24081 collaboration_hub: &dyn CollaborationHub,
24082 cx: &'a App,
24083 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24084 let participant_names = collaboration_hub.user_names(cx);
24085 let participant_indices = collaboration_hub.user_participant_indices(cx);
24086 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24087 let collaborators_by_replica_id = collaborators_by_peer_id
24088 .values()
24089 .map(|collaborator| (collaborator.replica_id, collaborator))
24090 .collect::<HashMap<_, _>>();
24091 self.buffer_snapshot()
24092 .selections_in_range(range, false)
24093 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24094 if replica_id == ReplicaId::AGENT {
24095 Some(RemoteSelection {
24096 replica_id,
24097 selection,
24098 cursor_shape,
24099 line_mode,
24100 collaborator_id: CollaboratorId::Agent,
24101 user_name: Some("Agent".into()),
24102 color: cx.theme().players().agent(),
24103 })
24104 } else {
24105 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24106 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24107 let user_name = participant_names.get(&collaborator.user_id).cloned();
24108 Some(RemoteSelection {
24109 replica_id,
24110 selection,
24111 cursor_shape,
24112 line_mode,
24113 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24114 user_name,
24115 color: if let Some(index) = participant_index {
24116 cx.theme().players().color_for_participant(index.0)
24117 } else {
24118 cx.theme().players().absent()
24119 },
24120 })
24121 }
24122 })
24123 }
24124
24125 pub fn hunks_for_ranges(
24126 &self,
24127 ranges: impl IntoIterator<Item = Range<Point>>,
24128 ) -> Vec<MultiBufferDiffHunk> {
24129 let mut hunks = Vec::new();
24130 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24131 HashMap::default();
24132 for query_range in ranges {
24133 let query_rows =
24134 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24135 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24136 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24137 ) {
24138 // Include deleted hunks that are adjacent to the query range, because
24139 // otherwise they would be missed.
24140 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24141 if hunk.status().is_deleted() {
24142 intersects_range |= hunk.row_range.start == query_rows.end;
24143 intersects_range |= hunk.row_range.end == query_rows.start;
24144 }
24145 if intersects_range {
24146 if !processed_buffer_rows
24147 .entry(hunk.buffer_id)
24148 .or_default()
24149 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24150 {
24151 continue;
24152 }
24153 hunks.push(hunk);
24154 }
24155 }
24156 }
24157
24158 hunks
24159 }
24160
24161 fn display_diff_hunks_for_rows<'a>(
24162 &'a self,
24163 display_rows: Range<DisplayRow>,
24164 folded_buffers: &'a HashSet<BufferId>,
24165 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24166 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24167 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24168
24169 self.buffer_snapshot()
24170 .diff_hunks_in_range(buffer_start..buffer_end)
24171 .filter_map(|hunk| {
24172 if folded_buffers.contains(&hunk.buffer_id) {
24173 return None;
24174 }
24175
24176 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24177 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24178
24179 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24180 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24181
24182 let display_hunk = if hunk_display_start.column() != 0 {
24183 DisplayDiffHunk::Folded {
24184 display_row: hunk_display_start.row(),
24185 }
24186 } else {
24187 let mut end_row = hunk_display_end.row();
24188 if hunk_display_end.column() > 0 {
24189 end_row.0 += 1;
24190 }
24191 let is_created_file = hunk.is_created_file();
24192
24193 DisplayDiffHunk::Unfolded {
24194 status: hunk.status(),
24195 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24196 ..hunk.diff_base_byte_range.end.0,
24197 word_diffs: hunk.word_diffs,
24198 display_row_range: hunk_display_start.row()..end_row,
24199 multi_buffer_range: Anchor::range_in_buffer(
24200 hunk.excerpt_id,
24201 hunk.buffer_range,
24202 ),
24203 is_created_file,
24204 }
24205 };
24206
24207 Some(display_hunk)
24208 })
24209 }
24210
24211 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24212 self.display_snapshot
24213 .buffer_snapshot()
24214 .language_at(position)
24215 }
24216
24217 pub fn is_focused(&self) -> bool {
24218 self.is_focused
24219 }
24220
24221 pub fn placeholder_text(&self) -> Option<String> {
24222 self.placeholder_display_snapshot
24223 .as_ref()
24224 .map(|display_map| display_map.text())
24225 }
24226
24227 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24228 self.scroll_anchor.scroll_position(&self.display_snapshot)
24229 }
24230
24231 fn gutter_dimensions(
24232 &self,
24233 font_id: FontId,
24234 font_size: Pixels,
24235 max_line_number_width: Pixels,
24236 cx: &App,
24237 ) -> Option<GutterDimensions> {
24238 if !self.show_gutter {
24239 return None;
24240 }
24241
24242 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24243 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24244
24245 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24246 matches!(
24247 ProjectSettings::get_global(cx).git.git_gutter,
24248 GitGutterSetting::TrackedFiles
24249 )
24250 });
24251 let gutter_settings = EditorSettings::get_global(cx).gutter;
24252 let show_line_numbers = self
24253 .show_line_numbers
24254 .unwrap_or(gutter_settings.line_numbers);
24255 let line_gutter_width = if show_line_numbers {
24256 // Avoid flicker-like gutter resizes when the line number gains another digit by
24257 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24258 let min_width_for_number_on_gutter =
24259 ch_advance * gutter_settings.min_line_number_digits as f32;
24260 max_line_number_width.max(min_width_for_number_on_gutter)
24261 } else {
24262 0.0.into()
24263 };
24264
24265 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24266 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24267
24268 let git_blame_entries_width =
24269 self.git_blame_gutter_max_author_length
24270 .map(|max_author_length| {
24271 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24272 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24273
24274 /// The number of characters to dedicate to gaps and margins.
24275 const SPACING_WIDTH: usize = 4;
24276
24277 let max_char_count = max_author_length.min(renderer.max_author_length())
24278 + ::git::SHORT_SHA_LENGTH
24279 + MAX_RELATIVE_TIMESTAMP.len()
24280 + SPACING_WIDTH;
24281
24282 ch_advance * max_char_count
24283 });
24284
24285 let is_singleton = self.buffer_snapshot().is_singleton();
24286
24287 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24288 left_padding += if !is_singleton {
24289 ch_width * 4.0
24290 } else if show_runnables || show_breakpoints {
24291 ch_width * 3.0
24292 } else if show_git_gutter && show_line_numbers {
24293 ch_width * 2.0
24294 } else if show_git_gutter || show_line_numbers {
24295 ch_width
24296 } else {
24297 px(0.)
24298 };
24299
24300 let shows_folds = is_singleton && gutter_settings.folds;
24301
24302 let right_padding = if shows_folds && show_line_numbers {
24303 ch_width * 4.0
24304 } else if shows_folds || (!is_singleton && show_line_numbers) {
24305 ch_width * 3.0
24306 } else if show_line_numbers {
24307 ch_width
24308 } else {
24309 px(0.)
24310 };
24311
24312 Some(GutterDimensions {
24313 left_padding,
24314 right_padding,
24315 width: line_gutter_width + left_padding + right_padding,
24316 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24317 git_blame_entries_width,
24318 })
24319 }
24320
24321 pub fn render_crease_toggle(
24322 &self,
24323 buffer_row: MultiBufferRow,
24324 row_contains_cursor: bool,
24325 editor: Entity<Editor>,
24326 window: &mut Window,
24327 cx: &mut App,
24328 ) -> Option<AnyElement> {
24329 let folded = self.is_line_folded(buffer_row);
24330 let mut is_foldable = false;
24331
24332 if let Some(crease) = self
24333 .crease_snapshot
24334 .query_row(buffer_row, self.buffer_snapshot())
24335 {
24336 is_foldable = true;
24337 match crease {
24338 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24339 if let Some(render_toggle) = render_toggle {
24340 let toggle_callback =
24341 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24342 if folded {
24343 editor.update(cx, |editor, cx| {
24344 editor.fold_at(buffer_row, window, cx)
24345 });
24346 } else {
24347 editor.update(cx, |editor, cx| {
24348 editor.unfold_at(buffer_row, window, cx)
24349 });
24350 }
24351 });
24352 return Some((render_toggle)(
24353 buffer_row,
24354 folded,
24355 toggle_callback,
24356 window,
24357 cx,
24358 ));
24359 }
24360 }
24361 }
24362 }
24363
24364 is_foldable |= self.starts_indent(buffer_row);
24365
24366 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24367 Some(
24368 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24369 .toggle_state(folded)
24370 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24371 if folded {
24372 this.unfold_at(buffer_row, window, cx);
24373 } else {
24374 this.fold_at(buffer_row, window, cx);
24375 }
24376 }))
24377 .into_any_element(),
24378 )
24379 } else {
24380 None
24381 }
24382 }
24383
24384 pub fn render_crease_trailer(
24385 &self,
24386 buffer_row: MultiBufferRow,
24387 window: &mut Window,
24388 cx: &mut App,
24389 ) -> Option<AnyElement> {
24390 let folded = self.is_line_folded(buffer_row);
24391 if let Crease::Inline { render_trailer, .. } = self
24392 .crease_snapshot
24393 .query_row(buffer_row, self.buffer_snapshot())?
24394 {
24395 let render_trailer = render_trailer.as_ref()?;
24396 Some(render_trailer(buffer_row, folded, window, cx))
24397 } else {
24398 None
24399 }
24400 }
24401}
24402
24403impl Deref for EditorSnapshot {
24404 type Target = DisplaySnapshot;
24405
24406 fn deref(&self) -> &Self::Target {
24407 &self.display_snapshot
24408 }
24409}
24410
24411#[derive(Clone, Debug, PartialEq, Eq)]
24412pub enum EditorEvent {
24413 InputIgnored {
24414 text: Arc<str>,
24415 },
24416 InputHandled {
24417 utf16_range_to_replace: Option<Range<isize>>,
24418 text: Arc<str>,
24419 },
24420 ExcerptsAdded {
24421 buffer: Entity<Buffer>,
24422 predecessor: ExcerptId,
24423 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24424 },
24425 ExcerptsRemoved {
24426 ids: Vec<ExcerptId>,
24427 removed_buffer_ids: Vec<BufferId>,
24428 },
24429 BufferFoldToggled {
24430 ids: Vec<ExcerptId>,
24431 folded: bool,
24432 },
24433 ExcerptsEdited {
24434 ids: Vec<ExcerptId>,
24435 },
24436 ExcerptsExpanded {
24437 ids: Vec<ExcerptId>,
24438 },
24439 BufferEdited,
24440 Edited {
24441 transaction_id: clock::Lamport,
24442 },
24443 Reparsed(BufferId),
24444 Focused,
24445 FocusedIn,
24446 Blurred,
24447 DirtyChanged,
24448 Saved,
24449 TitleChanged,
24450 SelectionsChanged {
24451 local: bool,
24452 },
24453 ScrollPositionChanged {
24454 local: bool,
24455 autoscroll: bool,
24456 },
24457 TransactionUndone {
24458 transaction_id: clock::Lamport,
24459 },
24460 TransactionBegun {
24461 transaction_id: clock::Lamport,
24462 },
24463 CursorShapeChanged,
24464 BreadcrumbsChanged,
24465 PushedToNavHistory {
24466 anchor: Anchor,
24467 is_deactivate: bool,
24468 },
24469}
24470
24471impl EventEmitter<EditorEvent> for Editor {}
24472
24473impl Focusable for Editor {
24474 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24475 self.focus_handle.clone()
24476 }
24477}
24478
24479impl Render for Editor {
24480 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24481 let settings = ThemeSettings::get_global(cx);
24482
24483 let mut text_style = match self.mode {
24484 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24485 color: cx.theme().colors().editor_foreground,
24486 font_family: settings.ui_font.family.clone(),
24487 font_features: settings.ui_font.features.clone(),
24488 font_fallbacks: settings.ui_font.fallbacks.clone(),
24489 font_size: rems(0.875).into(),
24490 font_weight: settings.ui_font.weight,
24491 line_height: relative(settings.buffer_line_height.value()),
24492 ..Default::default()
24493 },
24494 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24495 color: cx.theme().colors().editor_foreground,
24496 font_family: settings.buffer_font.family.clone(),
24497 font_features: settings.buffer_font.features.clone(),
24498 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24499 font_size: settings.buffer_font_size(cx).into(),
24500 font_weight: settings.buffer_font.weight,
24501 line_height: relative(settings.buffer_line_height.value()),
24502 ..Default::default()
24503 },
24504 };
24505 if let Some(text_style_refinement) = &self.text_style_refinement {
24506 text_style.refine(text_style_refinement)
24507 }
24508
24509 let background = match self.mode {
24510 EditorMode::SingleLine => cx.theme().system().transparent,
24511 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24512 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24513 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24514 };
24515
24516 EditorElement::new(
24517 &cx.entity(),
24518 EditorStyle {
24519 background,
24520 border: cx.theme().colors().border,
24521 local_player: cx.theme().players().local(),
24522 text: text_style,
24523 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24524 syntax: cx.theme().syntax().clone(),
24525 status: cx.theme().status().clone(),
24526 inlay_hints_style: make_inlay_hints_style(cx),
24527 edit_prediction_styles: make_suggestion_styles(cx),
24528 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24529 show_underlines: self.diagnostics_enabled(),
24530 },
24531 )
24532 }
24533}
24534
24535impl EntityInputHandler for Editor {
24536 fn text_for_range(
24537 &mut self,
24538 range_utf16: Range<usize>,
24539 adjusted_range: &mut Option<Range<usize>>,
24540 _: &mut Window,
24541 cx: &mut Context<Self>,
24542 ) -> Option<String> {
24543 let snapshot = self.buffer.read(cx).read(cx);
24544 let start = snapshot.clip_offset_utf16(
24545 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24546 Bias::Left,
24547 );
24548 let end = snapshot.clip_offset_utf16(
24549 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24550 Bias::Right,
24551 );
24552 if (start.0.0..end.0.0) != range_utf16 {
24553 adjusted_range.replace(start.0.0..end.0.0);
24554 }
24555 Some(snapshot.text_for_range(start..end).collect())
24556 }
24557
24558 fn selected_text_range(
24559 &mut self,
24560 ignore_disabled_input: bool,
24561 _: &mut Window,
24562 cx: &mut Context<Self>,
24563 ) -> Option<UTF16Selection> {
24564 // Prevent the IME menu from appearing when holding down an alphabetic key
24565 // while input is disabled.
24566 if !ignore_disabled_input && !self.input_enabled {
24567 return None;
24568 }
24569
24570 let selection = self
24571 .selections
24572 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24573 let range = selection.range();
24574
24575 Some(UTF16Selection {
24576 range: range.start.0.0..range.end.0.0,
24577 reversed: selection.reversed,
24578 })
24579 }
24580
24581 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24582 let snapshot = self.buffer.read(cx).read(cx);
24583 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24584 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24585 }
24586
24587 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24588 self.clear_highlights::<InputComposition>(cx);
24589 self.ime_transaction.take();
24590 }
24591
24592 fn replace_text_in_range(
24593 &mut self,
24594 range_utf16: Option<Range<usize>>,
24595 text: &str,
24596 window: &mut Window,
24597 cx: &mut Context<Self>,
24598 ) {
24599 if !self.input_enabled {
24600 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24601 return;
24602 }
24603
24604 self.transact(window, cx, |this, window, cx| {
24605 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24606 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24607 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24608 Some(this.selection_replacement_ranges(range_utf16, cx))
24609 } else {
24610 this.marked_text_ranges(cx)
24611 };
24612
24613 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24614 let newest_selection_id = this.selections.newest_anchor().id;
24615 this.selections
24616 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24617 .iter()
24618 .zip(ranges_to_replace.iter())
24619 .find_map(|(selection, range)| {
24620 if selection.id == newest_selection_id {
24621 Some(
24622 (range.start.0.0 as isize - selection.head().0.0 as isize)
24623 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24624 )
24625 } else {
24626 None
24627 }
24628 })
24629 });
24630
24631 cx.emit(EditorEvent::InputHandled {
24632 utf16_range_to_replace: range_to_replace,
24633 text: text.into(),
24634 });
24635
24636 if let Some(new_selected_ranges) = new_selected_ranges {
24637 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24638 selections.select_ranges(new_selected_ranges)
24639 });
24640 this.backspace(&Default::default(), window, cx);
24641 }
24642
24643 this.handle_input(text, window, cx);
24644 });
24645
24646 if let Some(transaction) = self.ime_transaction {
24647 self.buffer.update(cx, |buffer, cx| {
24648 buffer.group_until_transaction(transaction, cx);
24649 });
24650 }
24651
24652 self.unmark_text(window, cx);
24653 }
24654
24655 fn replace_and_mark_text_in_range(
24656 &mut self,
24657 range_utf16: Option<Range<usize>>,
24658 text: &str,
24659 new_selected_range_utf16: Option<Range<usize>>,
24660 window: &mut Window,
24661 cx: &mut Context<Self>,
24662 ) {
24663 if !self.input_enabled {
24664 return;
24665 }
24666
24667 let transaction = self.transact(window, cx, |this, window, cx| {
24668 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24669 let snapshot = this.buffer.read(cx).read(cx);
24670 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24671 for marked_range in &mut marked_ranges {
24672 marked_range.end = marked_range.start + relative_range_utf16.end;
24673 marked_range.start += relative_range_utf16.start;
24674 marked_range.start =
24675 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24676 marked_range.end =
24677 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24678 }
24679 }
24680 Some(marked_ranges)
24681 } else if let Some(range_utf16) = range_utf16 {
24682 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24683 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24684 Some(this.selection_replacement_ranges(range_utf16, cx))
24685 } else {
24686 None
24687 };
24688
24689 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24690 let newest_selection_id = this.selections.newest_anchor().id;
24691 this.selections
24692 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24693 .iter()
24694 .zip(ranges_to_replace.iter())
24695 .find_map(|(selection, range)| {
24696 if selection.id == newest_selection_id {
24697 Some(
24698 (range.start.0.0 as isize - selection.head().0.0 as isize)
24699 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24700 )
24701 } else {
24702 None
24703 }
24704 })
24705 });
24706
24707 cx.emit(EditorEvent::InputHandled {
24708 utf16_range_to_replace: range_to_replace,
24709 text: text.into(),
24710 });
24711
24712 if let Some(ranges) = ranges_to_replace {
24713 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24714 s.select_ranges(ranges)
24715 });
24716 }
24717
24718 let marked_ranges = {
24719 let snapshot = this.buffer.read(cx).read(cx);
24720 this.selections
24721 .disjoint_anchors_arc()
24722 .iter()
24723 .map(|selection| {
24724 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24725 })
24726 .collect::<Vec<_>>()
24727 };
24728
24729 if text.is_empty() {
24730 this.unmark_text(window, cx);
24731 } else {
24732 this.highlight_text::<InputComposition>(
24733 marked_ranges.clone(),
24734 HighlightStyle {
24735 underline: Some(UnderlineStyle {
24736 thickness: px(1.),
24737 color: None,
24738 wavy: false,
24739 }),
24740 ..Default::default()
24741 },
24742 cx,
24743 );
24744 }
24745
24746 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24747 let use_autoclose = this.use_autoclose;
24748 let use_auto_surround = this.use_auto_surround;
24749 this.set_use_autoclose(false);
24750 this.set_use_auto_surround(false);
24751 this.handle_input(text, window, cx);
24752 this.set_use_autoclose(use_autoclose);
24753 this.set_use_auto_surround(use_auto_surround);
24754
24755 if let Some(new_selected_range) = new_selected_range_utf16 {
24756 let snapshot = this.buffer.read(cx).read(cx);
24757 let new_selected_ranges = marked_ranges
24758 .into_iter()
24759 .map(|marked_range| {
24760 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24761 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24762 insertion_start.0 + new_selected_range.start,
24763 ));
24764 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24765 insertion_start.0 + new_selected_range.end,
24766 ));
24767 snapshot.clip_offset_utf16(new_start, Bias::Left)
24768 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24769 })
24770 .collect::<Vec<_>>();
24771
24772 drop(snapshot);
24773 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24774 selections.select_ranges(new_selected_ranges)
24775 });
24776 }
24777 });
24778
24779 self.ime_transaction = self.ime_transaction.or(transaction);
24780 if let Some(transaction) = self.ime_transaction {
24781 self.buffer.update(cx, |buffer, cx| {
24782 buffer.group_until_transaction(transaction, cx);
24783 });
24784 }
24785
24786 if self.text_highlights::<InputComposition>(cx).is_none() {
24787 self.ime_transaction.take();
24788 }
24789 }
24790
24791 fn bounds_for_range(
24792 &mut self,
24793 range_utf16: Range<usize>,
24794 element_bounds: gpui::Bounds<Pixels>,
24795 window: &mut Window,
24796 cx: &mut Context<Self>,
24797 ) -> Option<gpui::Bounds<Pixels>> {
24798 let text_layout_details = self.text_layout_details(window);
24799 let CharacterDimensions {
24800 em_width,
24801 em_advance,
24802 line_height,
24803 } = self.character_dimensions(window);
24804
24805 let snapshot = self.snapshot(window, cx);
24806 let scroll_position = snapshot.scroll_position();
24807 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24808
24809 let start =
24810 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24811 let x = Pixels::from(
24812 ScrollOffset::from(
24813 snapshot.x_for_display_point(start, &text_layout_details)
24814 + self.gutter_dimensions.full_width(),
24815 ) - scroll_left,
24816 );
24817 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24818
24819 Some(Bounds {
24820 origin: element_bounds.origin + point(x, y),
24821 size: size(em_width, line_height),
24822 })
24823 }
24824
24825 fn character_index_for_point(
24826 &mut self,
24827 point: gpui::Point<Pixels>,
24828 _window: &mut Window,
24829 _cx: &mut Context<Self>,
24830 ) -> Option<usize> {
24831 let position_map = self.last_position_map.as_ref()?;
24832 if !position_map.text_hitbox.contains(&point) {
24833 return None;
24834 }
24835 let display_point = position_map.point_for_position(point).previous_valid;
24836 let anchor = position_map
24837 .snapshot
24838 .display_point_to_anchor(display_point, Bias::Left);
24839 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24840 Some(utf16_offset.0.0)
24841 }
24842
24843 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24844 self.input_enabled
24845 }
24846}
24847
24848trait SelectionExt {
24849 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24850 fn spanned_rows(
24851 &self,
24852 include_end_if_at_line_start: bool,
24853 map: &DisplaySnapshot,
24854 ) -> Range<MultiBufferRow>;
24855}
24856
24857impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24858 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24859 let start = self
24860 .start
24861 .to_point(map.buffer_snapshot())
24862 .to_display_point(map);
24863 let end = self
24864 .end
24865 .to_point(map.buffer_snapshot())
24866 .to_display_point(map);
24867 if self.reversed {
24868 end..start
24869 } else {
24870 start..end
24871 }
24872 }
24873
24874 fn spanned_rows(
24875 &self,
24876 include_end_if_at_line_start: bool,
24877 map: &DisplaySnapshot,
24878 ) -> Range<MultiBufferRow> {
24879 let start = self.start.to_point(map.buffer_snapshot());
24880 let mut end = self.end.to_point(map.buffer_snapshot());
24881 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24882 end.row -= 1;
24883 }
24884
24885 let buffer_start = map.prev_line_boundary(start).0;
24886 let buffer_end = map.next_line_boundary(end).0;
24887 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24888 }
24889}
24890
24891impl<T: InvalidationRegion> InvalidationStack<T> {
24892 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24893 where
24894 S: Clone + ToOffset,
24895 {
24896 while let Some(region) = self.last() {
24897 let all_selections_inside_invalidation_ranges =
24898 if selections.len() == region.ranges().len() {
24899 selections
24900 .iter()
24901 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24902 .all(|(selection, invalidation_range)| {
24903 let head = selection.head().to_offset(buffer);
24904 invalidation_range.start <= head && invalidation_range.end >= head
24905 })
24906 } else {
24907 false
24908 };
24909
24910 if all_selections_inside_invalidation_ranges {
24911 break;
24912 } else {
24913 self.pop();
24914 }
24915 }
24916 }
24917}
24918
24919impl<T> Default for InvalidationStack<T> {
24920 fn default() -> Self {
24921 Self(Default::default())
24922 }
24923}
24924
24925impl<T> Deref for InvalidationStack<T> {
24926 type Target = Vec<T>;
24927
24928 fn deref(&self) -> &Self::Target {
24929 &self.0
24930 }
24931}
24932
24933impl<T> DerefMut for InvalidationStack<T> {
24934 fn deref_mut(&mut self) -> &mut Self::Target {
24935 &mut self.0
24936 }
24937}
24938
24939impl InvalidationRegion for SnippetState {
24940 fn ranges(&self) -> &[Range<Anchor>] {
24941 &self.ranges[self.active_index]
24942 }
24943}
24944
24945fn edit_prediction_edit_text(
24946 current_snapshot: &BufferSnapshot,
24947 edits: &[(Range<Anchor>, impl AsRef<str>)],
24948 edit_preview: &EditPreview,
24949 include_deletions: bool,
24950 cx: &App,
24951) -> HighlightedText {
24952 let edits = edits
24953 .iter()
24954 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24955 .collect::<Vec<_>>();
24956
24957 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24958}
24959
24960fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24961 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24962 // Just show the raw edit text with basic styling
24963 let mut text = String::new();
24964 let mut highlights = Vec::new();
24965
24966 let insertion_highlight_style = HighlightStyle {
24967 color: Some(cx.theme().colors().text),
24968 ..Default::default()
24969 };
24970
24971 for (_, edit_text) in edits {
24972 let start_offset = text.len();
24973 text.push_str(edit_text);
24974 let end_offset = text.len();
24975
24976 if start_offset < end_offset {
24977 highlights.push((start_offset..end_offset, insertion_highlight_style));
24978 }
24979 }
24980
24981 HighlightedText {
24982 text: text.into(),
24983 highlights,
24984 }
24985}
24986
24987pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24988 match severity {
24989 lsp::DiagnosticSeverity::ERROR => colors.error,
24990 lsp::DiagnosticSeverity::WARNING => colors.warning,
24991 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24992 lsp::DiagnosticSeverity::HINT => colors.info,
24993 _ => colors.ignored,
24994 }
24995}
24996
24997pub fn styled_runs_for_code_label<'a>(
24998 label: &'a CodeLabel,
24999 syntax_theme: &'a theme::SyntaxTheme,
25000 local_player: &'a theme::PlayerColor,
25001) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25002 let fade_out = HighlightStyle {
25003 fade_out: Some(0.35),
25004 ..Default::default()
25005 };
25006
25007 let mut prev_end = label.filter_range.end;
25008 label
25009 .runs
25010 .iter()
25011 .enumerate()
25012 .flat_map(move |(ix, (range, highlight_id))| {
25013 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25014 HighlightStyle {
25015 color: Some(local_player.cursor),
25016 ..Default::default()
25017 }
25018 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25019 HighlightStyle {
25020 background_color: Some(local_player.selection),
25021 ..Default::default()
25022 }
25023 } else if let Some(style) = highlight_id.style(syntax_theme) {
25024 style
25025 } else {
25026 return Default::default();
25027 };
25028 let muted_style = style.highlight(fade_out);
25029
25030 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25031 if range.start >= label.filter_range.end {
25032 if range.start > prev_end {
25033 runs.push((prev_end..range.start, fade_out));
25034 }
25035 runs.push((range.clone(), muted_style));
25036 } else if range.end <= label.filter_range.end {
25037 runs.push((range.clone(), style));
25038 } else {
25039 runs.push((range.start..label.filter_range.end, style));
25040 runs.push((label.filter_range.end..range.end, muted_style));
25041 }
25042 prev_end = cmp::max(prev_end, range.end);
25043
25044 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25045 runs.push((prev_end..label.text.len(), fade_out));
25046 }
25047
25048 runs
25049 })
25050}
25051
25052pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25053 let mut prev_index = 0;
25054 let mut prev_codepoint: Option<char> = None;
25055 text.char_indices()
25056 .chain([(text.len(), '\0')])
25057 .filter_map(move |(index, codepoint)| {
25058 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25059 let is_boundary = index == text.len()
25060 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25061 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25062 if is_boundary {
25063 let chunk = &text[prev_index..index];
25064 prev_index = index;
25065 Some(chunk)
25066 } else {
25067 None
25068 }
25069 })
25070}
25071
25072/// Given a string of text immediately before the cursor, iterates over possible
25073/// strings a snippet could match to. More precisely: returns an iterator over
25074/// suffixes of `text` created by splitting at word boundaries (before & after
25075/// every non-word character).
25076///
25077/// Shorter suffixes are returned first.
25078pub(crate) fn snippet_candidate_suffixes(
25079 text: &str,
25080 is_word_char: impl Fn(char) -> bool,
25081) -> impl std::iter::Iterator<Item = &str> {
25082 let mut prev_index = text.len();
25083 let mut prev_codepoint = None;
25084 text.char_indices()
25085 .rev()
25086 .chain([(0, '\0')])
25087 .filter_map(move |(index, codepoint)| {
25088 let prev_index = std::mem::replace(&mut prev_index, index);
25089 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25090 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25091 None
25092 } else {
25093 let chunk = &text[prev_index..]; // go to end of string
25094 Some(chunk)
25095 }
25096 })
25097}
25098
25099pub trait RangeToAnchorExt: Sized {
25100 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25101
25102 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25103 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25104 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25105 }
25106}
25107
25108impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25109 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25110 let start_offset = self.start.to_offset(snapshot);
25111 let end_offset = self.end.to_offset(snapshot);
25112 if start_offset == end_offset {
25113 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25114 } else {
25115 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25116 }
25117 }
25118}
25119
25120pub trait RowExt {
25121 fn as_f64(&self) -> f64;
25122
25123 fn next_row(&self) -> Self;
25124
25125 fn previous_row(&self) -> Self;
25126
25127 fn minus(&self, other: Self) -> u32;
25128}
25129
25130impl RowExt for DisplayRow {
25131 fn as_f64(&self) -> f64 {
25132 self.0 as _
25133 }
25134
25135 fn next_row(&self) -> Self {
25136 Self(self.0 + 1)
25137 }
25138
25139 fn previous_row(&self) -> Self {
25140 Self(self.0.saturating_sub(1))
25141 }
25142
25143 fn minus(&self, other: Self) -> u32 {
25144 self.0 - other.0
25145 }
25146}
25147
25148impl RowExt for MultiBufferRow {
25149 fn as_f64(&self) -> f64 {
25150 self.0 as _
25151 }
25152
25153 fn next_row(&self) -> Self {
25154 Self(self.0 + 1)
25155 }
25156
25157 fn previous_row(&self) -> Self {
25158 Self(self.0.saturating_sub(1))
25159 }
25160
25161 fn minus(&self, other: Self) -> u32 {
25162 self.0 - other.0
25163 }
25164}
25165
25166trait RowRangeExt {
25167 type Row;
25168
25169 fn len(&self) -> usize;
25170
25171 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25172}
25173
25174impl RowRangeExt for Range<MultiBufferRow> {
25175 type Row = MultiBufferRow;
25176
25177 fn len(&self) -> usize {
25178 (self.end.0 - self.start.0) as usize
25179 }
25180
25181 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25182 (self.start.0..self.end.0).map(MultiBufferRow)
25183 }
25184}
25185
25186impl RowRangeExt for Range<DisplayRow> {
25187 type Row = DisplayRow;
25188
25189 fn len(&self) -> usize {
25190 (self.end.0 - self.start.0) as usize
25191 }
25192
25193 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25194 (self.start.0..self.end.0).map(DisplayRow)
25195 }
25196}
25197
25198/// If select range has more than one line, we
25199/// just point the cursor to range.start.
25200fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25201 if range.start.row == range.end.row {
25202 range
25203 } else {
25204 range.start..range.start
25205 }
25206}
25207pub struct KillRing(ClipboardItem);
25208impl Global for KillRing {}
25209
25210const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25211
25212enum BreakpointPromptEditAction {
25213 Log,
25214 Condition,
25215 HitCondition,
25216}
25217
25218struct BreakpointPromptEditor {
25219 pub(crate) prompt: Entity<Editor>,
25220 editor: WeakEntity<Editor>,
25221 breakpoint_anchor: Anchor,
25222 breakpoint: Breakpoint,
25223 edit_action: BreakpointPromptEditAction,
25224 block_ids: HashSet<CustomBlockId>,
25225 editor_margins: Arc<Mutex<EditorMargins>>,
25226 _subscriptions: Vec<Subscription>,
25227}
25228
25229impl BreakpointPromptEditor {
25230 const MAX_LINES: u8 = 4;
25231
25232 fn new(
25233 editor: WeakEntity<Editor>,
25234 breakpoint_anchor: Anchor,
25235 breakpoint: Breakpoint,
25236 edit_action: BreakpointPromptEditAction,
25237 window: &mut Window,
25238 cx: &mut Context<Self>,
25239 ) -> Self {
25240 let base_text = match edit_action {
25241 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25242 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25243 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25244 }
25245 .map(|msg| msg.to_string())
25246 .unwrap_or_default();
25247
25248 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25249 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25250
25251 let prompt = cx.new(|cx| {
25252 let mut prompt = Editor::new(
25253 EditorMode::AutoHeight {
25254 min_lines: 1,
25255 max_lines: Some(Self::MAX_LINES as usize),
25256 },
25257 buffer,
25258 None,
25259 window,
25260 cx,
25261 );
25262 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25263 prompt.set_show_cursor_when_unfocused(false, cx);
25264 prompt.set_placeholder_text(
25265 match edit_action {
25266 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25267 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25268 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25269 },
25270 window,
25271 cx,
25272 );
25273
25274 prompt
25275 });
25276
25277 Self {
25278 prompt,
25279 editor,
25280 breakpoint_anchor,
25281 breakpoint,
25282 edit_action,
25283 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25284 block_ids: Default::default(),
25285 _subscriptions: vec![],
25286 }
25287 }
25288
25289 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25290 self.block_ids.extend(block_ids)
25291 }
25292
25293 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25294 if let Some(editor) = self.editor.upgrade() {
25295 let message = self
25296 .prompt
25297 .read(cx)
25298 .buffer
25299 .read(cx)
25300 .as_singleton()
25301 .expect("A multi buffer in breakpoint prompt isn't possible")
25302 .read(cx)
25303 .as_rope()
25304 .to_string();
25305
25306 editor.update(cx, |editor, cx| {
25307 editor.edit_breakpoint_at_anchor(
25308 self.breakpoint_anchor,
25309 self.breakpoint.clone(),
25310 match self.edit_action {
25311 BreakpointPromptEditAction::Log => {
25312 BreakpointEditAction::EditLogMessage(message.into())
25313 }
25314 BreakpointPromptEditAction::Condition => {
25315 BreakpointEditAction::EditCondition(message.into())
25316 }
25317 BreakpointPromptEditAction::HitCondition => {
25318 BreakpointEditAction::EditHitCondition(message.into())
25319 }
25320 },
25321 cx,
25322 );
25323
25324 editor.remove_blocks(self.block_ids.clone(), None, cx);
25325 cx.focus_self(window);
25326 });
25327 }
25328 }
25329
25330 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25331 self.editor
25332 .update(cx, |editor, cx| {
25333 editor.remove_blocks(self.block_ids.clone(), None, cx);
25334 window.focus(&editor.focus_handle);
25335 })
25336 .log_err();
25337 }
25338
25339 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25340 let settings = ThemeSettings::get_global(cx);
25341 let text_style = TextStyle {
25342 color: if self.prompt.read(cx).read_only(cx) {
25343 cx.theme().colors().text_disabled
25344 } else {
25345 cx.theme().colors().text
25346 },
25347 font_family: settings.buffer_font.family.clone(),
25348 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25349 font_size: settings.buffer_font_size(cx).into(),
25350 font_weight: settings.buffer_font.weight,
25351 line_height: relative(settings.buffer_line_height.value()),
25352 ..Default::default()
25353 };
25354 EditorElement::new(
25355 &self.prompt,
25356 EditorStyle {
25357 background: cx.theme().colors().editor_background,
25358 local_player: cx.theme().players().local(),
25359 text: text_style,
25360 ..Default::default()
25361 },
25362 )
25363 }
25364}
25365
25366impl Render for BreakpointPromptEditor {
25367 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25368 let editor_margins = *self.editor_margins.lock();
25369 let gutter_dimensions = editor_margins.gutter;
25370 h_flex()
25371 .key_context("Editor")
25372 .bg(cx.theme().colors().editor_background)
25373 .border_y_1()
25374 .border_color(cx.theme().status().info_border)
25375 .size_full()
25376 .py(window.line_height() / 2.5)
25377 .on_action(cx.listener(Self::confirm))
25378 .on_action(cx.listener(Self::cancel))
25379 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25380 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25381 }
25382}
25383
25384impl Focusable for BreakpointPromptEditor {
25385 fn focus_handle(&self, cx: &App) -> FocusHandle {
25386 self.prompt.focus_handle(cx)
25387 }
25388}
25389
25390fn all_edits_insertions_or_deletions(
25391 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25392 snapshot: &MultiBufferSnapshot,
25393) -> bool {
25394 let mut all_insertions = true;
25395 let mut all_deletions = true;
25396
25397 for (range, new_text) in edits.iter() {
25398 let range_is_empty = range.to_offset(snapshot).is_empty();
25399 let text_is_empty = new_text.is_empty();
25400
25401 if range_is_empty != text_is_empty {
25402 if range_is_empty {
25403 all_deletions = false;
25404 } else {
25405 all_insertions = false;
25406 }
25407 } else {
25408 return false;
25409 }
25410
25411 if !all_insertions && !all_deletions {
25412 return false;
25413 }
25414 }
25415 all_insertions || all_deletions
25416}
25417
25418struct MissingEditPredictionKeybindingTooltip;
25419
25420impl Render for MissingEditPredictionKeybindingTooltip {
25421 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25422 ui::tooltip_container(cx, |container, cx| {
25423 container
25424 .flex_shrink_0()
25425 .max_w_80()
25426 .min_h(rems_from_px(124.))
25427 .justify_between()
25428 .child(
25429 v_flex()
25430 .flex_1()
25431 .text_ui_sm(cx)
25432 .child(Label::new("Conflict with Accept Keybinding"))
25433 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25434 )
25435 .child(
25436 h_flex()
25437 .pb_1()
25438 .gap_1()
25439 .items_end()
25440 .w_full()
25441 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25442 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25443 }))
25444 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25445 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25446 })),
25447 )
25448 })
25449 }
25450}
25451
25452#[derive(Debug, Clone, Copy, PartialEq)]
25453pub struct LineHighlight {
25454 pub background: Background,
25455 pub border: Option<gpui::Hsla>,
25456 pub include_gutter: bool,
25457 pub type_id: Option<TypeId>,
25458}
25459
25460struct LineManipulationResult {
25461 pub new_text: String,
25462 pub line_count_before: usize,
25463 pub line_count_after: usize,
25464}
25465
25466fn render_diff_hunk_controls(
25467 row: u32,
25468 status: &DiffHunkStatus,
25469 hunk_range: Range<Anchor>,
25470 is_created_file: bool,
25471 line_height: Pixels,
25472 editor: &Entity<Editor>,
25473 _window: &mut Window,
25474 cx: &mut App,
25475) -> AnyElement {
25476 h_flex()
25477 .h(line_height)
25478 .mr_1()
25479 .gap_1()
25480 .px_0p5()
25481 .pb_1()
25482 .border_x_1()
25483 .border_b_1()
25484 .border_color(cx.theme().colors().border_variant)
25485 .rounded_b_lg()
25486 .bg(cx.theme().colors().editor_background)
25487 .gap_1()
25488 .block_mouse_except_scroll()
25489 .shadow_md()
25490 .child(if status.has_secondary_hunk() {
25491 Button::new(("stage", row as u64), "Stage")
25492 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25493 .tooltip({
25494 let focus_handle = editor.focus_handle(cx);
25495 move |_window, cx| {
25496 Tooltip::for_action_in(
25497 "Stage Hunk",
25498 &::git::ToggleStaged,
25499 &focus_handle,
25500 cx,
25501 )
25502 }
25503 })
25504 .on_click({
25505 let editor = editor.clone();
25506 move |_event, _window, cx| {
25507 editor.update(cx, |editor, cx| {
25508 editor.stage_or_unstage_diff_hunks(
25509 true,
25510 vec![hunk_range.start..hunk_range.start],
25511 cx,
25512 );
25513 });
25514 }
25515 })
25516 } else {
25517 Button::new(("unstage", row as u64), "Unstage")
25518 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25519 .tooltip({
25520 let focus_handle = editor.focus_handle(cx);
25521 move |_window, cx| {
25522 Tooltip::for_action_in(
25523 "Unstage Hunk",
25524 &::git::ToggleStaged,
25525 &focus_handle,
25526 cx,
25527 )
25528 }
25529 })
25530 .on_click({
25531 let editor = editor.clone();
25532 move |_event, _window, cx| {
25533 editor.update(cx, |editor, cx| {
25534 editor.stage_or_unstage_diff_hunks(
25535 false,
25536 vec![hunk_range.start..hunk_range.start],
25537 cx,
25538 );
25539 });
25540 }
25541 })
25542 })
25543 .child(
25544 Button::new(("restore", row as u64), "Restore")
25545 .tooltip({
25546 let focus_handle = editor.focus_handle(cx);
25547 move |_window, cx| {
25548 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25549 }
25550 })
25551 .on_click({
25552 let editor = editor.clone();
25553 move |_event, window, cx| {
25554 editor.update(cx, |editor, cx| {
25555 let snapshot = editor.snapshot(window, cx);
25556 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25557 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25558 });
25559 }
25560 })
25561 .disabled(is_created_file),
25562 )
25563 .when(
25564 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25565 |el| {
25566 el.child(
25567 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25568 .shape(IconButtonShape::Square)
25569 .icon_size(IconSize::Small)
25570 // .disabled(!has_multiple_hunks)
25571 .tooltip({
25572 let focus_handle = editor.focus_handle(cx);
25573 move |_window, cx| {
25574 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25575 }
25576 })
25577 .on_click({
25578 let editor = editor.clone();
25579 move |_event, window, cx| {
25580 editor.update(cx, |editor, cx| {
25581 let snapshot = editor.snapshot(window, cx);
25582 let position =
25583 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25584 editor.go_to_hunk_before_or_after_position(
25585 &snapshot,
25586 position,
25587 Direction::Next,
25588 window,
25589 cx,
25590 );
25591 editor.expand_selected_diff_hunks(cx);
25592 });
25593 }
25594 }),
25595 )
25596 .child(
25597 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25598 .shape(IconButtonShape::Square)
25599 .icon_size(IconSize::Small)
25600 // .disabled(!has_multiple_hunks)
25601 .tooltip({
25602 let focus_handle = editor.focus_handle(cx);
25603 move |_window, cx| {
25604 Tooltip::for_action_in(
25605 "Previous Hunk",
25606 &GoToPreviousHunk,
25607 &focus_handle,
25608 cx,
25609 )
25610 }
25611 })
25612 .on_click({
25613 let editor = editor.clone();
25614 move |_event, window, cx| {
25615 editor.update(cx, |editor, cx| {
25616 let snapshot = editor.snapshot(window, cx);
25617 let point =
25618 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25619 editor.go_to_hunk_before_or_after_position(
25620 &snapshot,
25621 point,
25622 Direction::Prev,
25623 window,
25624 cx,
25625 );
25626 editor.expand_selected_diff_hunks(cx);
25627 });
25628 }
25629 }),
25630 )
25631 },
25632 )
25633 .into_any_element()
25634}
25635
25636pub fn multibuffer_context_lines(cx: &App) -> u32 {
25637 EditorSettings::try_get(cx)
25638 .map(|settings| settings.excerpt_context_lines)
25639 .unwrap_or(2)
25640 .min(32)
25641}