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 query: Option<Arc<String>> =
5513 Self::completion_query(&multibuffer_snapshot, buffer_position)
5514 .map(|query| query.into());
5515
5516 drop(multibuffer_snapshot);
5517
5518 // Hide the current completions menu when query is empty. Without this, cached
5519 // completions from before the trigger char may be reused (#32774).
5520 if query.is_none() {
5521 let menu_is_open = matches!(
5522 self.context_menu.borrow().as_ref(),
5523 Some(CodeContextMenu::Completions(_))
5524 );
5525 if menu_is_open {
5526 self.hide_context_menu(window, cx);
5527 }
5528 }
5529
5530 let mut ignore_word_threshold = false;
5531 let provider = match requested_source {
5532 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5533 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5534 ignore_word_threshold = ignore_threshold;
5535 None
5536 }
5537 Some(CompletionsMenuSource::SnippetChoices)
5538 | Some(CompletionsMenuSource::SnippetsOnly) => {
5539 log::error!("bug: SnippetChoices requested_source is not handled");
5540 None
5541 }
5542 };
5543
5544 let sort_completions = provider
5545 .as_ref()
5546 .is_some_and(|provider| provider.sort_completions());
5547
5548 let filter_completions = provider
5549 .as_ref()
5550 .is_none_or(|provider| provider.filter_completions());
5551
5552 let was_snippets_only = matches!(
5553 completions_source,
5554 Some(CompletionsMenuSource::SnippetsOnly)
5555 );
5556
5557 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5558 if filter_completions {
5559 menu.filter(
5560 query.clone().unwrap_or_default(),
5561 buffer_position.text_anchor,
5562 &buffer,
5563 provider.clone(),
5564 window,
5565 cx,
5566 );
5567 }
5568 // When `is_incomplete` is false, no need to re-query completions when the current query
5569 // is a suffix of the initial query.
5570 let was_complete = !menu.is_incomplete;
5571 if was_complete && !was_snippets_only {
5572 // If the new query is a suffix of the old query (typing more characters) and
5573 // the previous result was complete, the existing completions can be filtered.
5574 //
5575 // Note that snippet completions are always complete.
5576 let query_matches = match (&menu.initial_query, &query) {
5577 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5578 (None, _) => true,
5579 _ => false,
5580 };
5581 if query_matches {
5582 let position_matches = if menu.initial_position == position {
5583 true
5584 } else {
5585 let snapshot = self.buffer.read(cx).read(cx);
5586 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5587 };
5588 if position_matches {
5589 return;
5590 }
5591 }
5592 }
5593 };
5594
5595 let Anchor {
5596 excerpt_id: buffer_excerpt_id,
5597 text_anchor: buffer_position,
5598 ..
5599 } = buffer_position;
5600
5601 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5602 buffer_snapshot.surrounding_word(buffer_position, None)
5603 {
5604 let word_to_exclude = buffer_snapshot
5605 .text_for_range(word_range.clone())
5606 .collect::<String>();
5607 (
5608 buffer_snapshot.anchor_before(word_range.start)
5609 ..buffer_snapshot.anchor_after(buffer_position),
5610 Some(word_to_exclude),
5611 )
5612 } else {
5613 (buffer_position..buffer_position, None)
5614 };
5615
5616 let language = buffer_snapshot
5617 .language_at(buffer_position)
5618 .map(|language| language.name());
5619
5620 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5621 .completions
5622 .clone();
5623
5624 let show_completion_documentation = buffer_snapshot
5625 .settings_at(buffer_position, cx)
5626 .show_completion_documentation;
5627
5628 // The document can be large, so stay in reasonable bounds when searching for words,
5629 // otherwise completion pop-up might be slow to appear.
5630 const WORD_LOOKUP_ROWS: u32 = 5_000;
5631 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5632 let min_word_search = buffer_snapshot.clip_point(
5633 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5634 Bias::Left,
5635 );
5636 let max_word_search = buffer_snapshot.clip_point(
5637 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5638 Bias::Right,
5639 );
5640 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5641 ..buffer_snapshot.point_to_offset(max_word_search);
5642
5643 let skip_digits = query
5644 .as_ref()
5645 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5646
5647 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5648 trigger.as_ref().is_none_or(|trigger| {
5649 provider.is_completion_trigger(
5650 &buffer,
5651 position.text_anchor,
5652 trigger,
5653 trigger_in_words,
5654 completions_source.is_some(),
5655 cx,
5656 )
5657 })
5658 });
5659
5660 let provider_responses = if let Some(provider) = &provider
5661 && load_provider_completions
5662 {
5663 let trigger_character =
5664 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5665 let completion_context = CompletionContext {
5666 trigger_kind: match &trigger_character {
5667 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5668 None => CompletionTriggerKind::INVOKED,
5669 },
5670 trigger_character,
5671 };
5672
5673 provider.completions(
5674 buffer_excerpt_id,
5675 &buffer,
5676 buffer_position,
5677 completion_context,
5678 window,
5679 cx,
5680 )
5681 } else {
5682 Task::ready(Ok(Vec::new()))
5683 };
5684
5685 let load_word_completions = if !self.word_completions_enabled {
5686 false
5687 } else if requested_source
5688 == Some(CompletionsMenuSource::Words {
5689 ignore_threshold: true,
5690 })
5691 {
5692 true
5693 } else {
5694 load_provider_completions
5695 && completion_settings.words != WordsCompletionMode::Disabled
5696 && (ignore_word_threshold || {
5697 let words_min_length = completion_settings.words_min_length;
5698 // check whether word has at least `words_min_length` characters
5699 let query_chars = query.iter().flat_map(|q| q.chars());
5700 query_chars.take(words_min_length).count() == words_min_length
5701 })
5702 };
5703
5704 let mut words = if load_word_completions {
5705 cx.background_spawn({
5706 let buffer_snapshot = buffer_snapshot.clone();
5707 async move {
5708 buffer_snapshot.words_in_range(WordsQuery {
5709 fuzzy_contents: None,
5710 range: word_search_range,
5711 skip_digits,
5712 })
5713 }
5714 })
5715 } else {
5716 Task::ready(BTreeMap::default())
5717 };
5718
5719 let snippets = if let Some(provider) = &provider
5720 && provider.show_snippets()
5721 && let Some(project) = self.project()
5722 {
5723 let char_classifier = buffer_snapshot
5724 .char_classifier_at(buffer_position)
5725 .scope_context(Some(CharScopeContext::Completion));
5726 project.update(cx, |project, cx| {
5727 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5728 })
5729 } else {
5730 Task::ready(Ok(CompletionResponse {
5731 completions: Vec::new(),
5732 display_options: Default::default(),
5733 is_incomplete: false,
5734 }))
5735 };
5736
5737 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5738
5739 let id = post_inc(&mut self.next_completion_id);
5740 let task = cx.spawn_in(window, async move |editor, cx| {
5741 let Ok(()) = editor.update(cx, |this, _| {
5742 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5743 }) else {
5744 return;
5745 };
5746
5747 // TODO: Ideally completions from different sources would be selectively re-queried, so
5748 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5749 let mut completions = Vec::new();
5750 let mut is_incomplete = false;
5751 let mut display_options: Option<CompletionDisplayOptions> = None;
5752 if let Some(provider_responses) = provider_responses.await.log_err()
5753 && !provider_responses.is_empty()
5754 {
5755 for response in provider_responses {
5756 completions.extend(response.completions);
5757 is_incomplete = is_incomplete || response.is_incomplete;
5758 match display_options.as_mut() {
5759 None => {
5760 display_options = Some(response.display_options);
5761 }
5762 Some(options) => options.merge(&response.display_options),
5763 }
5764 }
5765 if completion_settings.words == WordsCompletionMode::Fallback {
5766 words = Task::ready(BTreeMap::default());
5767 }
5768 }
5769 let display_options = display_options.unwrap_or_default();
5770
5771 let mut words = words.await;
5772 if let Some(word_to_exclude) = &word_to_exclude {
5773 words.remove(word_to_exclude);
5774 }
5775 for lsp_completion in &completions {
5776 words.remove(&lsp_completion.new_text);
5777 }
5778 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5779 replace_range: word_replace_range.clone(),
5780 new_text: word.clone(),
5781 label: CodeLabel::plain(word, None),
5782 match_start: None,
5783 snippet_deduplication_key: None,
5784 icon_path: None,
5785 documentation: None,
5786 source: CompletionSource::BufferWord {
5787 word_range,
5788 resolved: false,
5789 },
5790 insert_text_mode: Some(InsertTextMode::AS_IS),
5791 confirm: None,
5792 }));
5793
5794 completions.extend(
5795 snippets
5796 .await
5797 .into_iter()
5798 .flat_map(|response| response.completions),
5799 );
5800
5801 let menu = if completions.is_empty() {
5802 None
5803 } else {
5804 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5805 let languages = editor
5806 .workspace
5807 .as_ref()
5808 .and_then(|(workspace, _)| workspace.upgrade())
5809 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5810 let menu = CompletionsMenu::new(
5811 id,
5812 requested_source.unwrap_or(if load_provider_completions {
5813 CompletionsMenuSource::Normal
5814 } else {
5815 CompletionsMenuSource::SnippetsOnly
5816 }),
5817 sort_completions,
5818 show_completion_documentation,
5819 position,
5820 query.clone(),
5821 is_incomplete,
5822 buffer.clone(),
5823 completions.into(),
5824 display_options,
5825 snippet_sort_order,
5826 languages,
5827 language,
5828 cx,
5829 );
5830
5831 let query = if filter_completions { query } else { None };
5832 let matches_task = menu.do_async_filtering(
5833 query.unwrap_or_default(),
5834 buffer_position,
5835 &buffer,
5836 cx,
5837 );
5838 (menu, matches_task)
5839 }) else {
5840 return;
5841 };
5842
5843 let matches = matches_task.await;
5844
5845 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5846 // Newer menu already set, so exit.
5847 if let Some(CodeContextMenu::Completions(prev_menu)) =
5848 editor.context_menu.borrow().as_ref()
5849 && prev_menu.id > id
5850 {
5851 return;
5852 };
5853
5854 // Only valid to take prev_menu because either the new menu is immediately set
5855 // below, or the menu is hidden.
5856 if let Some(CodeContextMenu::Completions(prev_menu)) =
5857 editor.context_menu.borrow_mut().take()
5858 {
5859 let position_matches =
5860 if prev_menu.initial_position == menu.initial_position {
5861 true
5862 } else {
5863 let snapshot = editor.buffer.read(cx).read(cx);
5864 prev_menu.initial_position.to_offset(&snapshot)
5865 == menu.initial_position.to_offset(&snapshot)
5866 };
5867 if position_matches {
5868 // Preserve markdown cache before `set_filter_results` because it will
5869 // try to populate the documentation cache.
5870 menu.preserve_markdown_cache(prev_menu);
5871 }
5872 };
5873
5874 menu.set_filter_results(matches, provider, window, cx);
5875 }) else {
5876 return;
5877 };
5878
5879 menu.visible().then_some(menu)
5880 };
5881
5882 editor
5883 .update_in(cx, |editor, window, cx| {
5884 if editor.focus_handle.is_focused(window)
5885 && let Some(menu) = menu
5886 {
5887 *editor.context_menu.borrow_mut() =
5888 Some(CodeContextMenu::Completions(menu));
5889
5890 crate::hover_popover::hide_hover(editor, cx);
5891 if editor.show_edit_predictions_in_menu() {
5892 editor.update_visible_edit_prediction(window, cx);
5893 } else {
5894 editor.discard_edit_prediction(false, cx);
5895 }
5896
5897 cx.notify();
5898 return;
5899 }
5900
5901 if editor.completion_tasks.len() <= 1 {
5902 // If there are no more completion tasks and the last menu was empty, we should hide it.
5903 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5904 // If it was already hidden and we don't show edit predictions in the menu,
5905 // we should also show the edit prediction when available.
5906 if was_hidden && editor.show_edit_predictions_in_menu() {
5907 editor.update_visible_edit_prediction(window, cx);
5908 }
5909 }
5910 })
5911 .ok();
5912 });
5913
5914 self.completion_tasks.push((id, task));
5915 }
5916
5917 #[cfg(feature = "test-support")]
5918 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5919 let menu = self.context_menu.borrow();
5920 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5921 let completions = menu.completions.borrow();
5922 Some(completions.to_vec())
5923 } else {
5924 None
5925 }
5926 }
5927
5928 pub fn with_completions_menu_matching_id<R>(
5929 &self,
5930 id: CompletionId,
5931 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5932 ) -> R {
5933 let mut context_menu = self.context_menu.borrow_mut();
5934 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5935 return f(None);
5936 };
5937 if completions_menu.id != id {
5938 return f(None);
5939 }
5940 f(Some(completions_menu))
5941 }
5942
5943 pub fn confirm_completion(
5944 &mut self,
5945 action: &ConfirmCompletion,
5946 window: &mut Window,
5947 cx: &mut Context<Self>,
5948 ) -> Option<Task<Result<()>>> {
5949 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5950 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5951 }
5952
5953 pub fn confirm_completion_insert(
5954 &mut self,
5955 _: &ConfirmCompletionInsert,
5956 window: &mut Window,
5957 cx: &mut Context<Self>,
5958 ) -> Option<Task<Result<()>>> {
5959 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5960 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5961 }
5962
5963 pub fn confirm_completion_replace(
5964 &mut self,
5965 _: &ConfirmCompletionReplace,
5966 window: &mut Window,
5967 cx: &mut Context<Self>,
5968 ) -> Option<Task<Result<()>>> {
5969 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5970 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5971 }
5972
5973 pub fn compose_completion(
5974 &mut self,
5975 action: &ComposeCompletion,
5976 window: &mut Window,
5977 cx: &mut Context<Self>,
5978 ) -> Option<Task<Result<()>>> {
5979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5980 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5981 }
5982
5983 fn do_completion(
5984 &mut self,
5985 item_ix: Option<usize>,
5986 intent: CompletionIntent,
5987 window: &mut Window,
5988 cx: &mut Context<Editor>,
5989 ) -> Option<Task<Result<()>>> {
5990 use language::ToOffset as _;
5991
5992 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5993 else {
5994 return None;
5995 };
5996
5997 let candidate_id = {
5998 let entries = completions_menu.entries.borrow();
5999 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6000 if self.show_edit_predictions_in_menu() {
6001 self.discard_edit_prediction(true, cx);
6002 }
6003 mat.candidate_id
6004 };
6005
6006 let completion = completions_menu
6007 .completions
6008 .borrow()
6009 .get(candidate_id)?
6010 .clone();
6011 cx.stop_propagation();
6012
6013 let buffer_handle = completions_menu.buffer.clone();
6014
6015 let CompletionEdit {
6016 new_text,
6017 snippet,
6018 replace_range,
6019 } = process_completion_for_edit(
6020 &completion,
6021 intent,
6022 &buffer_handle,
6023 &completions_menu.initial_position.text_anchor,
6024 cx,
6025 );
6026
6027 let buffer = buffer_handle.read(cx);
6028 let snapshot = self.buffer.read(cx).snapshot(cx);
6029 let newest_anchor = self.selections.newest_anchor();
6030 let replace_range_multibuffer = {
6031 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6032 excerpt.map_range_from_buffer(replace_range.clone())
6033 };
6034 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6035 return None;
6036 }
6037
6038 let old_text = buffer
6039 .text_for_range(replace_range.clone())
6040 .collect::<String>();
6041 let lookbehind = newest_anchor
6042 .start
6043 .text_anchor
6044 .to_offset(buffer)
6045 .saturating_sub(replace_range.start.0);
6046 let lookahead = replace_range
6047 .end
6048 .0
6049 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6050 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6051 let suffix = &old_text[lookbehind.min(old_text.len())..];
6052
6053 let selections = self
6054 .selections
6055 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6056 let mut ranges = Vec::new();
6057 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6058
6059 for selection in &selections {
6060 let range = if selection.id == newest_anchor.id {
6061 replace_range_multibuffer.clone()
6062 } else {
6063 let mut range = selection.range();
6064
6065 // if prefix is present, don't duplicate it
6066 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6067 range.start = range.start.saturating_sub_usize(lookbehind);
6068
6069 // if suffix is also present, mimic the newest cursor and replace it
6070 if selection.id != newest_anchor.id
6071 && snapshot.contains_str_at(range.end, suffix)
6072 {
6073 range.end += lookahead;
6074 }
6075 }
6076 range
6077 };
6078
6079 ranges.push(range.clone());
6080
6081 if !self.linked_edit_ranges.is_empty() {
6082 let start_anchor = snapshot.anchor_before(range.start);
6083 let end_anchor = snapshot.anchor_after(range.end);
6084 if let Some(ranges) = self
6085 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6086 {
6087 for (buffer, edits) in ranges {
6088 linked_edits
6089 .entry(buffer.clone())
6090 .or_default()
6091 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6092 }
6093 }
6094 }
6095 }
6096
6097 let common_prefix_len = old_text
6098 .chars()
6099 .zip(new_text.chars())
6100 .take_while(|(a, b)| a == b)
6101 .map(|(a, _)| a.len_utf8())
6102 .sum::<usize>();
6103
6104 cx.emit(EditorEvent::InputHandled {
6105 utf16_range_to_replace: None,
6106 text: new_text[common_prefix_len..].into(),
6107 });
6108
6109 self.transact(window, cx, |editor, window, cx| {
6110 if let Some(mut snippet) = snippet {
6111 snippet.text = new_text.to_string();
6112 editor
6113 .insert_snippet(&ranges, snippet, window, cx)
6114 .log_err();
6115 } else {
6116 editor.buffer.update(cx, |multi_buffer, cx| {
6117 let auto_indent = match completion.insert_text_mode {
6118 Some(InsertTextMode::AS_IS) => None,
6119 _ => editor.autoindent_mode.clone(),
6120 };
6121 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6122 multi_buffer.edit(edits, auto_indent, cx);
6123 });
6124 }
6125 for (buffer, edits) in linked_edits {
6126 buffer.update(cx, |buffer, cx| {
6127 let snapshot = buffer.snapshot();
6128 let edits = edits
6129 .into_iter()
6130 .map(|(range, text)| {
6131 use text::ToPoint as TP;
6132 let end_point = TP::to_point(&range.end, &snapshot);
6133 let start_point = TP::to_point(&range.start, &snapshot);
6134 (start_point..end_point, text)
6135 })
6136 .sorted_by_key(|(range, _)| range.start);
6137 buffer.edit(edits, None, cx);
6138 })
6139 }
6140
6141 editor.refresh_edit_prediction(true, false, window, cx);
6142 });
6143 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6144
6145 let show_new_completions_on_confirm = completion
6146 .confirm
6147 .as_ref()
6148 .is_some_and(|confirm| confirm(intent, window, cx));
6149 if show_new_completions_on_confirm {
6150 self.open_or_update_completions_menu(None, None, false, window, cx);
6151 }
6152
6153 let provider = self.completion_provider.as_ref()?;
6154
6155 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6156 let command = lsp_store.as_ref().and_then(|lsp_store| {
6157 let CompletionSource::Lsp {
6158 lsp_completion,
6159 server_id,
6160 ..
6161 } = &completion.source
6162 else {
6163 return None;
6164 };
6165 let lsp_command = lsp_completion.command.as_ref()?;
6166 let available_commands = lsp_store
6167 .read(cx)
6168 .lsp_server_capabilities
6169 .get(server_id)
6170 .and_then(|server_capabilities| {
6171 server_capabilities
6172 .execute_command_provider
6173 .as_ref()
6174 .map(|options| options.commands.as_slice())
6175 })?;
6176 if available_commands.contains(&lsp_command.command) {
6177 Some(CodeAction {
6178 server_id: *server_id,
6179 range: language::Anchor::MIN..language::Anchor::MIN,
6180 lsp_action: LspAction::Command(lsp_command.clone()),
6181 resolved: false,
6182 })
6183 } else {
6184 None
6185 }
6186 });
6187
6188 drop(completion);
6189 let apply_edits = provider.apply_additional_edits_for_completion(
6190 buffer_handle.clone(),
6191 completions_menu.completions.clone(),
6192 candidate_id,
6193 true,
6194 cx,
6195 );
6196
6197 let editor_settings = EditorSettings::get_global(cx);
6198 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6199 // After the code completion is finished, users often want to know what signatures are needed.
6200 // so we should automatically call signature_help
6201 self.show_signature_help(&ShowSignatureHelp, window, cx);
6202 }
6203
6204 Some(cx.spawn_in(window, async move |editor, cx| {
6205 apply_edits.await?;
6206
6207 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6208 let title = command.lsp_action.title().to_owned();
6209 let project_transaction = lsp_store
6210 .update(cx, |lsp_store, cx| {
6211 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6212 })?
6213 .await
6214 .context("applying post-completion command")?;
6215 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6216 Self::open_project_transaction(
6217 &editor,
6218 workspace.downgrade(),
6219 project_transaction,
6220 title,
6221 cx,
6222 )
6223 .await?;
6224 }
6225 }
6226
6227 Ok(())
6228 }))
6229 }
6230
6231 pub fn toggle_code_actions(
6232 &mut self,
6233 action: &ToggleCodeActions,
6234 window: &mut Window,
6235 cx: &mut Context<Self>,
6236 ) {
6237 let quick_launch = action.quick_launch;
6238 let mut context_menu = self.context_menu.borrow_mut();
6239 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6240 if code_actions.deployed_from == action.deployed_from {
6241 // Toggle if we're selecting the same one
6242 *context_menu = None;
6243 cx.notify();
6244 return;
6245 } else {
6246 // Otherwise, clear it and start a new one
6247 *context_menu = None;
6248 cx.notify();
6249 }
6250 }
6251 drop(context_menu);
6252 let snapshot = self.snapshot(window, cx);
6253 let deployed_from = action.deployed_from.clone();
6254 let action = action.clone();
6255 self.completion_tasks.clear();
6256 self.discard_edit_prediction(false, cx);
6257
6258 let multibuffer_point = match &action.deployed_from {
6259 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6260 DisplayPoint::new(*row, 0).to_point(&snapshot)
6261 }
6262 _ => self
6263 .selections
6264 .newest::<Point>(&snapshot.display_snapshot)
6265 .head(),
6266 };
6267 let Some((buffer, buffer_row)) = snapshot
6268 .buffer_snapshot()
6269 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6270 .and_then(|(buffer_snapshot, range)| {
6271 self.buffer()
6272 .read(cx)
6273 .buffer(buffer_snapshot.remote_id())
6274 .map(|buffer| (buffer, range.start.row))
6275 })
6276 else {
6277 return;
6278 };
6279 let buffer_id = buffer.read(cx).remote_id();
6280 let tasks = self
6281 .tasks
6282 .get(&(buffer_id, buffer_row))
6283 .map(|t| Arc::new(t.to_owned()));
6284
6285 if !self.focus_handle.is_focused(window) {
6286 return;
6287 }
6288 let project = self.project.clone();
6289
6290 let code_actions_task = match deployed_from {
6291 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6292 _ => self.code_actions(buffer_row, window, cx),
6293 };
6294
6295 let runnable_task = match deployed_from {
6296 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6297 _ => {
6298 let mut task_context_task = Task::ready(None);
6299 if let Some(tasks) = &tasks
6300 && let Some(project) = project
6301 {
6302 task_context_task =
6303 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6304 }
6305
6306 cx.spawn_in(window, {
6307 let buffer = buffer.clone();
6308 async move |editor, cx| {
6309 let task_context = task_context_task.await;
6310
6311 let resolved_tasks =
6312 tasks
6313 .zip(task_context.clone())
6314 .map(|(tasks, task_context)| ResolvedTasks {
6315 templates: tasks.resolve(&task_context).collect(),
6316 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6317 multibuffer_point.row,
6318 tasks.column,
6319 )),
6320 });
6321 let debug_scenarios = editor
6322 .update(cx, |editor, cx| {
6323 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6324 })?
6325 .await;
6326 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6327 }
6328 })
6329 }
6330 };
6331
6332 cx.spawn_in(window, async move |editor, cx| {
6333 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6334 let code_actions = code_actions_task.await;
6335 let spawn_straight_away = quick_launch
6336 && resolved_tasks
6337 .as_ref()
6338 .is_some_and(|tasks| tasks.templates.len() == 1)
6339 && code_actions
6340 .as_ref()
6341 .is_none_or(|actions| actions.is_empty())
6342 && debug_scenarios.is_empty();
6343
6344 editor.update_in(cx, |editor, window, cx| {
6345 crate::hover_popover::hide_hover(editor, cx);
6346 let actions = CodeActionContents::new(
6347 resolved_tasks,
6348 code_actions,
6349 debug_scenarios,
6350 task_context.unwrap_or_default(),
6351 );
6352
6353 // Don't show the menu if there are no actions available
6354 if actions.is_empty() {
6355 cx.notify();
6356 return Task::ready(Ok(()));
6357 }
6358
6359 *editor.context_menu.borrow_mut() =
6360 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6361 buffer,
6362 actions,
6363 selected_item: Default::default(),
6364 scroll_handle: UniformListScrollHandle::default(),
6365 deployed_from,
6366 }));
6367 cx.notify();
6368 if spawn_straight_away
6369 && let Some(task) = editor.confirm_code_action(
6370 &ConfirmCodeAction { item_ix: Some(0) },
6371 window,
6372 cx,
6373 )
6374 {
6375 return task;
6376 }
6377
6378 Task::ready(Ok(()))
6379 })
6380 })
6381 .detach_and_log_err(cx);
6382 }
6383
6384 fn debug_scenarios(
6385 &mut self,
6386 resolved_tasks: &Option<ResolvedTasks>,
6387 buffer: &Entity<Buffer>,
6388 cx: &mut App,
6389 ) -> Task<Vec<task::DebugScenario>> {
6390 maybe!({
6391 let project = self.project()?;
6392 let dap_store = project.read(cx).dap_store();
6393 let mut scenarios = vec![];
6394 let resolved_tasks = resolved_tasks.as_ref()?;
6395 let buffer = buffer.read(cx);
6396 let language = buffer.language()?;
6397 let file = buffer.file();
6398 let debug_adapter = language_settings(language.name().into(), file, cx)
6399 .debuggers
6400 .first()
6401 .map(SharedString::from)
6402 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6403
6404 dap_store.update(cx, |dap_store, cx| {
6405 for (_, task) in &resolved_tasks.templates {
6406 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6407 task.original_task().clone(),
6408 debug_adapter.clone().into(),
6409 task.display_label().to_owned().into(),
6410 cx,
6411 );
6412 scenarios.push(maybe_scenario);
6413 }
6414 });
6415 Some(cx.background_spawn(async move {
6416 futures::future::join_all(scenarios)
6417 .await
6418 .into_iter()
6419 .flatten()
6420 .collect::<Vec<_>>()
6421 }))
6422 })
6423 .unwrap_or_else(|| Task::ready(vec![]))
6424 }
6425
6426 fn code_actions(
6427 &mut self,
6428 buffer_row: u32,
6429 window: &mut Window,
6430 cx: &mut Context<Self>,
6431 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6432 let mut task = self.code_actions_task.take();
6433 cx.spawn_in(window, async move |editor, cx| {
6434 while let Some(prev_task) = task {
6435 prev_task.await.log_err();
6436 task = editor
6437 .update(cx, |this, _| this.code_actions_task.take())
6438 .ok()?;
6439 }
6440
6441 editor
6442 .update(cx, |editor, cx| {
6443 editor
6444 .available_code_actions
6445 .clone()
6446 .and_then(|(location, code_actions)| {
6447 let snapshot = location.buffer.read(cx).snapshot();
6448 let point_range = location.range.to_point(&snapshot);
6449 let point_range = point_range.start.row..=point_range.end.row;
6450 if point_range.contains(&buffer_row) {
6451 Some(code_actions)
6452 } else {
6453 None
6454 }
6455 })
6456 })
6457 .ok()
6458 .flatten()
6459 })
6460 }
6461
6462 pub fn confirm_code_action(
6463 &mut self,
6464 action: &ConfirmCodeAction,
6465 window: &mut Window,
6466 cx: &mut Context<Self>,
6467 ) -> Option<Task<Result<()>>> {
6468 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6469
6470 let actions_menu =
6471 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6472 menu
6473 } else {
6474 return None;
6475 };
6476
6477 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6478 let action = actions_menu.actions.get(action_ix)?;
6479 let title = action.label();
6480 let buffer = actions_menu.buffer;
6481 let workspace = self.workspace()?;
6482
6483 match action {
6484 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6485 workspace.update(cx, |workspace, cx| {
6486 workspace.schedule_resolved_task(
6487 task_source_kind,
6488 resolved_task,
6489 false,
6490 window,
6491 cx,
6492 );
6493
6494 Some(Task::ready(Ok(())))
6495 })
6496 }
6497 CodeActionsItem::CodeAction {
6498 excerpt_id,
6499 action,
6500 provider,
6501 } => {
6502 let apply_code_action =
6503 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6504 let workspace = workspace.downgrade();
6505 Some(cx.spawn_in(window, async move |editor, cx| {
6506 let project_transaction = apply_code_action.await?;
6507 Self::open_project_transaction(
6508 &editor,
6509 workspace,
6510 project_transaction,
6511 title,
6512 cx,
6513 )
6514 .await
6515 }))
6516 }
6517 CodeActionsItem::DebugScenario(scenario) => {
6518 let context = actions_menu.actions.context;
6519
6520 workspace.update(cx, |workspace, cx| {
6521 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6522 workspace.start_debug_session(
6523 scenario,
6524 context,
6525 Some(buffer),
6526 None,
6527 window,
6528 cx,
6529 );
6530 });
6531 Some(Task::ready(Ok(())))
6532 }
6533 }
6534 }
6535
6536 pub async fn open_project_transaction(
6537 editor: &WeakEntity<Editor>,
6538 workspace: WeakEntity<Workspace>,
6539 transaction: ProjectTransaction,
6540 title: String,
6541 cx: &mut AsyncWindowContext,
6542 ) -> Result<()> {
6543 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6544 cx.update(|_, cx| {
6545 entries.sort_unstable_by_key(|(buffer, _)| {
6546 buffer.read(cx).file().map(|f| f.path().clone())
6547 });
6548 })?;
6549 if entries.is_empty() {
6550 return Ok(());
6551 }
6552
6553 // If the project transaction's edits are all contained within this editor, then
6554 // avoid opening a new editor to display them.
6555
6556 if let [(buffer, transaction)] = &*entries {
6557 let excerpt = editor.update(cx, |editor, cx| {
6558 editor
6559 .buffer()
6560 .read(cx)
6561 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6562 })?;
6563 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6564 && excerpted_buffer == *buffer
6565 {
6566 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6567 let excerpt_range = excerpt_range.to_offset(buffer);
6568 buffer
6569 .edited_ranges_for_transaction::<usize>(transaction)
6570 .all(|range| {
6571 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6572 })
6573 })?;
6574
6575 if all_edits_within_excerpt {
6576 return Ok(());
6577 }
6578 }
6579 }
6580
6581 let mut ranges_to_highlight = Vec::new();
6582 let excerpt_buffer = cx.new(|cx| {
6583 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6584 for (buffer_handle, transaction) in &entries {
6585 let edited_ranges = buffer_handle
6586 .read(cx)
6587 .edited_ranges_for_transaction::<Point>(transaction)
6588 .collect::<Vec<_>>();
6589 let (ranges, _) = multibuffer.set_excerpts_for_path(
6590 PathKey::for_buffer(buffer_handle, cx),
6591 buffer_handle.clone(),
6592 edited_ranges,
6593 multibuffer_context_lines(cx),
6594 cx,
6595 );
6596
6597 ranges_to_highlight.extend(ranges);
6598 }
6599 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6600 multibuffer
6601 })?;
6602
6603 workspace.update_in(cx, |workspace, window, cx| {
6604 let project = workspace.project().clone();
6605 let editor =
6606 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6607 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6608 editor.update(cx, |editor, cx| {
6609 editor.highlight_background::<Self>(
6610 &ranges_to_highlight,
6611 |theme| theme.colors().editor_highlighted_line_background,
6612 cx,
6613 );
6614 });
6615 })?;
6616
6617 Ok(())
6618 }
6619
6620 pub fn clear_code_action_providers(&mut self) {
6621 self.code_action_providers.clear();
6622 self.available_code_actions.take();
6623 }
6624
6625 pub fn add_code_action_provider(
6626 &mut self,
6627 provider: Rc<dyn CodeActionProvider>,
6628 window: &mut Window,
6629 cx: &mut Context<Self>,
6630 ) {
6631 if self
6632 .code_action_providers
6633 .iter()
6634 .any(|existing_provider| existing_provider.id() == provider.id())
6635 {
6636 return;
6637 }
6638
6639 self.code_action_providers.push(provider);
6640 self.refresh_code_actions(window, cx);
6641 }
6642
6643 pub fn remove_code_action_provider(
6644 &mut self,
6645 id: Arc<str>,
6646 window: &mut Window,
6647 cx: &mut Context<Self>,
6648 ) {
6649 self.code_action_providers
6650 .retain(|provider| provider.id() != id);
6651 self.refresh_code_actions(window, cx);
6652 }
6653
6654 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6655 !self.code_action_providers.is_empty()
6656 && EditorSettings::get_global(cx).toolbar.code_actions
6657 }
6658
6659 pub fn has_available_code_actions(&self) -> bool {
6660 self.available_code_actions
6661 .as_ref()
6662 .is_some_and(|(_, actions)| !actions.is_empty())
6663 }
6664
6665 fn render_inline_code_actions(
6666 &self,
6667 icon_size: ui::IconSize,
6668 display_row: DisplayRow,
6669 is_active: bool,
6670 cx: &mut Context<Self>,
6671 ) -> AnyElement {
6672 let show_tooltip = !self.context_menu_visible();
6673 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6674 .icon_size(icon_size)
6675 .shape(ui::IconButtonShape::Square)
6676 .icon_color(ui::Color::Hidden)
6677 .toggle_state(is_active)
6678 .when(show_tooltip, |this| {
6679 this.tooltip({
6680 let focus_handle = self.focus_handle.clone();
6681 move |_window, cx| {
6682 Tooltip::for_action_in(
6683 "Toggle Code Actions",
6684 &ToggleCodeActions {
6685 deployed_from: None,
6686 quick_launch: false,
6687 },
6688 &focus_handle,
6689 cx,
6690 )
6691 }
6692 })
6693 })
6694 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6695 window.focus(&editor.focus_handle(cx));
6696 editor.toggle_code_actions(
6697 &crate::actions::ToggleCodeActions {
6698 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6699 display_row,
6700 )),
6701 quick_launch: false,
6702 },
6703 window,
6704 cx,
6705 );
6706 }))
6707 .into_any_element()
6708 }
6709
6710 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6711 &self.context_menu
6712 }
6713
6714 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6715 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6716 cx.background_executor()
6717 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6718 .await;
6719
6720 let (start_buffer, start, _, end, newest_selection) = this
6721 .update(cx, |this, cx| {
6722 let newest_selection = this.selections.newest_anchor().clone();
6723 if newest_selection.head().diff_base_anchor.is_some() {
6724 return None;
6725 }
6726 let display_snapshot = this.display_snapshot(cx);
6727 let newest_selection_adjusted =
6728 this.selections.newest_adjusted(&display_snapshot);
6729 let buffer = this.buffer.read(cx);
6730
6731 let (start_buffer, start) =
6732 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6733 let (end_buffer, end) =
6734 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6735
6736 Some((start_buffer, start, end_buffer, end, newest_selection))
6737 })?
6738 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6739 .context(
6740 "Expected selection to lie in a single buffer when refreshing code actions",
6741 )?;
6742 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6743 let providers = this.code_action_providers.clone();
6744 let tasks = this
6745 .code_action_providers
6746 .iter()
6747 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6748 .collect::<Vec<_>>();
6749 (providers, tasks)
6750 })?;
6751
6752 let mut actions = Vec::new();
6753 for (provider, provider_actions) in
6754 providers.into_iter().zip(future::join_all(tasks).await)
6755 {
6756 if let Some(provider_actions) = provider_actions.log_err() {
6757 actions.extend(provider_actions.into_iter().map(|action| {
6758 AvailableCodeAction {
6759 excerpt_id: newest_selection.start.excerpt_id,
6760 action,
6761 provider: provider.clone(),
6762 }
6763 }));
6764 }
6765 }
6766
6767 this.update(cx, |this, cx| {
6768 this.available_code_actions = if actions.is_empty() {
6769 None
6770 } else {
6771 Some((
6772 Location {
6773 buffer: start_buffer,
6774 range: start..end,
6775 },
6776 actions.into(),
6777 ))
6778 };
6779 cx.notify();
6780 })
6781 }));
6782 }
6783
6784 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6785 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6786 self.show_git_blame_inline = false;
6787
6788 self.show_git_blame_inline_delay_task =
6789 Some(cx.spawn_in(window, async move |this, cx| {
6790 cx.background_executor().timer(delay).await;
6791
6792 this.update(cx, |this, cx| {
6793 this.show_git_blame_inline = true;
6794 cx.notify();
6795 })
6796 .log_err();
6797 }));
6798 }
6799 }
6800
6801 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6802 let snapshot = self.snapshot(window, cx);
6803 let cursor = self
6804 .selections
6805 .newest::<Point>(&snapshot.display_snapshot)
6806 .head();
6807 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6808 else {
6809 return;
6810 };
6811
6812 let Some(blame) = self.blame.as_ref() else {
6813 return;
6814 };
6815
6816 let row_info = RowInfo {
6817 buffer_id: Some(buffer.remote_id()),
6818 buffer_row: Some(point.row),
6819 ..Default::default()
6820 };
6821 let Some((buffer, blame_entry)) = blame
6822 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6823 .flatten()
6824 else {
6825 return;
6826 };
6827
6828 let anchor = self.selections.newest_anchor().head();
6829 let position = self.to_pixel_point(anchor, &snapshot, window);
6830 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6831 self.show_blame_popover(
6832 buffer,
6833 &blame_entry,
6834 position + last_bounds.origin,
6835 true,
6836 cx,
6837 );
6838 };
6839 }
6840
6841 fn show_blame_popover(
6842 &mut self,
6843 buffer: BufferId,
6844 blame_entry: &BlameEntry,
6845 position: gpui::Point<Pixels>,
6846 ignore_timeout: bool,
6847 cx: &mut Context<Self>,
6848 ) {
6849 if let Some(state) = &mut self.inline_blame_popover {
6850 state.hide_task.take();
6851 } else {
6852 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6853 let blame_entry = blame_entry.clone();
6854 let show_task = cx.spawn(async move |editor, cx| {
6855 if !ignore_timeout {
6856 cx.background_executor()
6857 .timer(std::time::Duration::from_millis(blame_popover_delay))
6858 .await;
6859 }
6860 editor
6861 .update(cx, |editor, cx| {
6862 editor.inline_blame_popover_show_task.take();
6863 let Some(blame) = editor.blame.as_ref() else {
6864 return;
6865 };
6866 let blame = blame.read(cx);
6867 let details = blame.details_for_entry(buffer, &blame_entry);
6868 let markdown = cx.new(|cx| {
6869 Markdown::new(
6870 details
6871 .as_ref()
6872 .map(|message| message.message.clone())
6873 .unwrap_or_default(),
6874 None,
6875 None,
6876 cx,
6877 )
6878 });
6879 editor.inline_blame_popover = Some(InlineBlamePopover {
6880 position,
6881 hide_task: None,
6882 popover_bounds: None,
6883 popover_state: InlineBlamePopoverState {
6884 scroll_handle: ScrollHandle::new(),
6885 commit_message: details,
6886 markdown,
6887 },
6888 keyboard_grace: ignore_timeout,
6889 });
6890 cx.notify();
6891 })
6892 .ok();
6893 });
6894 self.inline_blame_popover_show_task = Some(show_task);
6895 }
6896 }
6897
6898 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6899 self.inline_blame_popover_show_task.take();
6900 if let Some(state) = &mut self.inline_blame_popover {
6901 let hide_task = cx.spawn(async move |editor, cx| {
6902 if !ignore_timeout {
6903 cx.background_executor()
6904 .timer(std::time::Duration::from_millis(100))
6905 .await;
6906 }
6907 editor
6908 .update(cx, |editor, cx| {
6909 editor.inline_blame_popover.take();
6910 cx.notify();
6911 })
6912 .ok();
6913 });
6914 state.hide_task = Some(hide_task);
6915 true
6916 } else {
6917 false
6918 }
6919 }
6920
6921 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6922 if self.pending_rename.is_some() {
6923 return None;
6924 }
6925
6926 let provider = self.semantics_provider.clone()?;
6927 let buffer = self.buffer.read(cx);
6928 let newest_selection = self.selections.newest_anchor().clone();
6929 let cursor_position = newest_selection.head();
6930 let (cursor_buffer, cursor_buffer_position) =
6931 buffer.text_anchor_for_position(cursor_position, cx)?;
6932 let (tail_buffer, tail_buffer_position) =
6933 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6934 if cursor_buffer != tail_buffer {
6935 return None;
6936 }
6937
6938 let snapshot = cursor_buffer.read(cx).snapshot();
6939 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6940 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6941 if start_word_range != end_word_range {
6942 self.document_highlights_task.take();
6943 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6944 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6945 return None;
6946 }
6947
6948 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6949 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6950 cx.background_executor()
6951 .timer(Duration::from_millis(debounce))
6952 .await;
6953
6954 let highlights = if let Some(highlights) = cx
6955 .update(|cx| {
6956 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6957 })
6958 .ok()
6959 .flatten()
6960 {
6961 highlights.await.log_err()
6962 } else {
6963 None
6964 };
6965
6966 if let Some(highlights) = highlights {
6967 this.update(cx, |this, cx| {
6968 if this.pending_rename.is_some() {
6969 return;
6970 }
6971
6972 let buffer = this.buffer.read(cx);
6973 if buffer
6974 .text_anchor_for_position(cursor_position, cx)
6975 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6976 {
6977 return;
6978 }
6979
6980 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6981 let mut write_ranges = Vec::new();
6982 let mut read_ranges = Vec::new();
6983 for highlight in highlights {
6984 let buffer_id = cursor_buffer.read(cx).remote_id();
6985 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6986 {
6987 let start = highlight
6988 .range
6989 .start
6990 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6991 let end = highlight
6992 .range
6993 .end
6994 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6995 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6996 continue;
6997 }
6998
6999 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7000 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7001 write_ranges.push(range);
7002 } else {
7003 read_ranges.push(range);
7004 }
7005 }
7006 }
7007
7008 this.highlight_background::<DocumentHighlightRead>(
7009 &read_ranges,
7010 |theme| theme.colors().editor_document_highlight_read_background,
7011 cx,
7012 );
7013 this.highlight_background::<DocumentHighlightWrite>(
7014 &write_ranges,
7015 |theme| theme.colors().editor_document_highlight_write_background,
7016 cx,
7017 );
7018 cx.notify();
7019 })
7020 .log_err();
7021 }
7022 }));
7023 None
7024 }
7025
7026 fn prepare_highlight_query_from_selection(
7027 &mut self,
7028 window: &Window,
7029 cx: &mut Context<Editor>,
7030 ) -> Option<(String, Range<Anchor>)> {
7031 if matches!(self.mode, EditorMode::SingleLine) {
7032 return None;
7033 }
7034 if !EditorSettings::get_global(cx).selection_highlight {
7035 return None;
7036 }
7037 if self.selections.count() != 1 || self.selections.line_mode() {
7038 return None;
7039 }
7040 let snapshot = self.snapshot(window, cx);
7041 let selection = self.selections.newest::<Point>(&snapshot);
7042 // If the selection spans multiple rows OR it is empty
7043 if selection.start.row != selection.end.row
7044 || selection.start.column == selection.end.column
7045 {
7046 return None;
7047 }
7048 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7049 let query = snapshot
7050 .buffer_snapshot()
7051 .text_for_range(selection_anchor_range.clone())
7052 .collect::<String>();
7053 if query.trim().is_empty() {
7054 return None;
7055 }
7056 Some((query, selection_anchor_range))
7057 }
7058
7059 fn update_selection_occurrence_highlights(
7060 &mut self,
7061 query_text: String,
7062 query_range: Range<Anchor>,
7063 multi_buffer_range_to_query: Range<Point>,
7064 use_debounce: bool,
7065 window: &mut Window,
7066 cx: &mut Context<Editor>,
7067 ) -> Task<()> {
7068 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7069 cx.spawn_in(window, async move |editor, cx| {
7070 if use_debounce {
7071 cx.background_executor()
7072 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7073 .await;
7074 }
7075 let match_task = cx.background_spawn(async move {
7076 let buffer_ranges = multi_buffer_snapshot
7077 .range_to_buffer_ranges(multi_buffer_range_to_query)
7078 .into_iter()
7079 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7080 let mut match_ranges = Vec::new();
7081 let Ok(regex) = project::search::SearchQuery::text(
7082 query_text.clone(),
7083 false,
7084 false,
7085 false,
7086 Default::default(),
7087 Default::default(),
7088 false,
7089 None,
7090 ) else {
7091 return Vec::default();
7092 };
7093 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7094 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7095 match_ranges.extend(
7096 regex
7097 .search(
7098 buffer_snapshot,
7099 Some(search_range.start.0..search_range.end.0),
7100 )
7101 .await
7102 .into_iter()
7103 .filter_map(|match_range| {
7104 let match_start = buffer_snapshot
7105 .anchor_after(search_range.start + match_range.start);
7106 let match_end = buffer_snapshot
7107 .anchor_before(search_range.start + match_range.end);
7108 let match_anchor_range =
7109 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7110 (match_anchor_range != query_range).then_some(match_anchor_range)
7111 }),
7112 );
7113 }
7114 match_ranges
7115 });
7116 let match_ranges = match_task.await;
7117 editor
7118 .update_in(cx, |editor, _, cx| {
7119 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7120 if !match_ranges.is_empty() {
7121 editor.highlight_background::<SelectedTextHighlight>(
7122 &match_ranges,
7123 |theme| theme.colors().editor_document_highlight_bracket_background,
7124 cx,
7125 )
7126 }
7127 })
7128 .log_err();
7129 })
7130 }
7131
7132 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7133 struct NewlineFold;
7134 let type_id = std::any::TypeId::of::<NewlineFold>();
7135 if !self.mode.is_single_line() {
7136 return;
7137 }
7138 let snapshot = self.snapshot(window, cx);
7139 if snapshot.buffer_snapshot().max_point().row == 0 {
7140 return;
7141 }
7142 let task = cx.background_spawn(async move {
7143 let new_newlines = snapshot
7144 .buffer_chars_at(MultiBufferOffset(0))
7145 .filter_map(|(c, i)| {
7146 if c == '\n' {
7147 Some(
7148 snapshot.buffer_snapshot().anchor_after(i)
7149 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7150 )
7151 } else {
7152 None
7153 }
7154 })
7155 .collect::<Vec<_>>();
7156 let existing_newlines = snapshot
7157 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7158 .filter_map(|fold| {
7159 if fold.placeholder.type_tag == Some(type_id) {
7160 Some(fold.range.start..fold.range.end)
7161 } else {
7162 None
7163 }
7164 })
7165 .collect::<Vec<_>>();
7166
7167 (new_newlines, existing_newlines)
7168 });
7169 self.folding_newlines = cx.spawn(async move |this, cx| {
7170 let (new_newlines, existing_newlines) = task.await;
7171 if new_newlines == existing_newlines {
7172 return;
7173 }
7174 let placeholder = FoldPlaceholder {
7175 render: Arc::new(move |_, _, cx| {
7176 div()
7177 .bg(cx.theme().status().hint_background)
7178 .border_b_1()
7179 .size_full()
7180 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7181 .border_color(cx.theme().status().hint)
7182 .child("\\n")
7183 .into_any()
7184 }),
7185 constrain_width: false,
7186 merge_adjacent: false,
7187 type_tag: Some(type_id),
7188 };
7189 let creases = new_newlines
7190 .into_iter()
7191 .map(|range| Crease::simple(range, placeholder.clone()))
7192 .collect();
7193 this.update(cx, |this, cx| {
7194 this.display_map.update(cx, |display_map, cx| {
7195 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7196 display_map.fold(creases, cx);
7197 });
7198 })
7199 .ok();
7200 });
7201 }
7202
7203 fn refresh_selected_text_highlights(
7204 &mut self,
7205 on_buffer_edit: bool,
7206 window: &mut Window,
7207 cx: &mut Context<Editor>,
7208 ) {
7209 let Some((query_text, query_range)) =
7210 self.prepare_highlight_query_from_selection(window, cx)
7211 else {
7212 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7213 self.quick_selection_highlight_task.take();
7214 self.debounced_selection_highlight_task.take();
7215 return;
7216 };
7217 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7218 if on_buffer_edit
7219 || self
7220 .quick_selection_highlight_task
7221 .as_ref()
7222 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7223 {
7224 let multi_buffer_visible_start = self
7225 .scroll_manager
7226 .anchor()
7227 .anchor
7228 .to_point(&multi_buffer_snapshot);
7229 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7230 multi_buffer_visible_start
7231 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7232 Bias::Left,
7233 );
7234 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7235 self.quick_selection_highlight_task = Some((
7236 query_range.clone(),
7237 self.update_selection_occurrence_highlights(
7238 query_text.clone(),
7239 query_range.clone(),
7240 multi_buffer_visible_range,
7241 false,
7242 window,
7243 cx,
7244 ),
7245 ));
7246 }
7247 if on_buffer_edit
7248 || self
7249 .debounced_selection_highlight_task
7250 .as_ref()
7251 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7252 {
7253 let multi_buffer_start = multi_buffer_snapshot
7254 .anchor_before(MultiBufferOffset(0))
7255 .to_point(&multi_buffer_snapshot);
7256 let multi_buffer_end = multi_buffer_snapshot
7257 .anchor_after(multi_buffer_snapshot.len())
7258 .to_point(&multi_buffer_snapshot);
7259 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7260 self.debounced_selection_highlight_task = Some((
7261 query_range.clone(),
7262 self.update_selection_occurrence_highlights(
7263 query_text,
7264 query_range,
7265 multi_buffer_full_range,
7266 true,
7267 window,
7268 cx,
7269 ),
7270 ));
7271 }
7272 }
7273
7274 pub fn refresh_edit_prediction(
7275 &mut self,
7276 debounce: bool,
7277 user_requested: bool,
7278 window: &mut Window,
7279 cx: &mut Context<Self>,
7280 ) -> Option<()> {
7281 if DisableAiSettings::get_global(cx).disable_ai {
7282 return None;
7283 }
7284
7285 let provider = self.edit_prediction_provider()?;
7286 let cursor = self.selections.newest_anchor().head();
7287 let (buffer, cursor_buffer_position) =
7288 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7289
7290 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7291 self.discard_edit_prediction(false, cx);
7292 return None;
7293 }
7294
7295 self.update_visible_edit_prediction(window, cx);
7296
7297 if !user_requested
7298 && (!self.should_show_edit_predictions()
7299 || !self.is_focused(window)
7300 || buffer.read(cx).is_empty())
7301 {
7302 self.discard_edit_prediction(false, cx);
7303 return None;
7304 }
7305
7306 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7307 Some(())
7308 }
7309
7310 fn show_edit_predictions_in_menu(&self) -> bool {
7311 match self.edit_prediction_settings {
7312 EditPredictionSettings::Disabled => false,
7313 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7314 }
7315 }
7316
7317 pub fn edit_predictions_enabled(&self) -> bool {
7318 match self.edit_prediction_settings {
7319 EditPredictionSettings::Disabled => false,
7320 EditPredictionSettings::Enabled { .. } => true,
7321 }
7322 }
7323
7324 fn edit_prediction_requires_modifier(&self) -> bool {
7325 match self.edit_prediction_settings {
7326 EditPredictionSettings::Disabled => false,
7327 EditPredictionSettings::Enabled {
7328 preview_requires_modifier,
7329 ..
7330 } => preview_requires_modifier,
7331 }
7332 }
7333
7334 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7335 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7336 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7337 self.discard_edit_prediction(false, cx);
7338 } else {
7339 let selection = self.selections.newest_anchor();
7340 let cursor = selection.head();
7341
7342 if let Some((buffer, cursor_buffer_position)) =
7343 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7344 {
7345 self.edit_prediction_settings =
7346 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7347 }
7348 }
7349 }
7350
7351 fn edit_prediction_settings_at_position(
7352 &self,
7353 buffer: &Entity<Buffer>,
7354 buffer_position: language::Anchor,
7355 cx: &App,
7356 ) -> EditPredictionSettings {
7357 if !self.mode.is_full()
7358 || !self.show_edit_predictions_override.unwrap_or(true)
7359 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7360 {
7361 return EditPredictionSettings::Disabled;
7362 }
7363
7364 let buffer = buffer.read(cx);
7365
7366 let file = buffer.file();
7367
7368 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7369 return EditPredictionSettings::Disabled;
7370 };
7371
7372 let by_provider = matches!(
7373 self.menu_edit_predictions_policy,
7374 MenuEditPredictionsPolicy::ByProvider
7375 );
7376
7377 let show_in_menu = by_provider
7378 && self
7379 .edit_prediction_provider
7380 .as_ref()
7381 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7382
7383 let preview_requires_modifier =
7384 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7385
7386 EditPredictionSettings::Enabled {
7387 show_in_menu,
7388 preview_requires_modifier,
7389 }
7390 }
7391
7392 fn should_show_edit_predictions(&self) -> bool {
7393 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7394 }
7395
7396 pub fn edit_prediction_preview_is_active(&self) -> bool {
7397 matches!(
7398 self.edit_prediction_preview,
7399 EditPredictionPreview::Active { .. }
7400 )
7401 }
7402
7403 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7404 let cursor = self.selections.newest_anchor().head();
7405 if let Some((buffer, cursor_position)) =
7406 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7407 {
7408 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7409 } else {
7410 false
7411 }
7412 }
7413
7414 pub fn supports_minimap(&self, cx: &App) -> bool {
7415 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7416 }
7417
7418 fn edit_predictions_enabled_in_buffer(
7419 &self,
7420 buffer: &Entity<Buffer>,
7421 buffer_position: language::Anchor,
7422 cx: &App,
7423 ) -> bool {
7424 maybe!({
7425 if self.read_only(cx) {
7426 return Some(false);
7427 }
7428 let provider = self.edit_prediction_provider()?;
7429 if !provider.is_enabled(buffer, buffer_position, cx) {
7430 return Some(false);
7431 }
7432 let buffer = buffer.read(cx);
7433 let Some(file) = buffer.file() else {
7434 return Some(true);
7435 };
7436 let settings = all_language_settings(Some(file), cx);
7437 Some(settings.edit_predictions_enabled_for_file(file, cx))
7438 })
7439 .unwrap_or(false)
7440 }
7441
7442 fn cycle_edit_prediction(
7443 &mut self,
7444 direction: Direction,
7445 window: &mut Window,
7446 cx: &mut Context<Self>,
7447 ) -> Option<()> {
7448 let provider = self.edit_prediction_provider()?;
7449 let cursor = self.selections.newest_anchor().head();
7450 let (buffer, cursor_buffer_position) =
7451 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7452 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7453 return None;
7454 }
7455
7456 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7457 self.update_visible_edit_prediction(window, cx);
7458
7459 Some(())
7460 }
7461
7462 pub fn show_edit_prediction(
7463 &mut self,
7464 _: &ShowEditPrediction,
7465 window: &mut Window,
7466 cx: &mut Context<Self>,
7467 ) {
7468 if !self.has_active_edit_prediction() {
7469 self.refresh_edit_prediction(false, true, window, cx);
7470 return;
7471 }
7472
7473 self.update_visible_edit_prediction(window, cx);
7474 }
7475
7476 pub fn display_cursor_names(
7477 &mut self,
7478 _: &DisplayCursorNames,
7479 window: &mut Window,
7480 cx: &mut Context<Self>,
7481 ) {
7482 self.show_cursor_names(window, cx);
7483 }
7484
7485 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7486 self.show_cursor_names = true;
7487 cx.notify();
7488 cx.spawn_in(window, async move |this, cx| {
7489 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7490 this.update(cx, |this, cx| {
7491 this.show_cursor_names = false;
7492 cx.notify()
7493 })
7494 .ok()
7495 })
7496 .detach();
7497 }
7498
7499 pub fn next_edit_prediction(
7500 &mut self,
7501 _: &NextEditPrediction,
7502 window: &mut Window,
7503 cx: &mut Context<Self>,
7504 ) {
7505 if self.has_active_edit_prediction() {
7506 self.cycle_edit_prediction(Direction::Next, window, cx);
7507 } else {
7508 let is_copilot_disabled = self
7509 .refresh_edit_prediction(false, true, window, cx)
7510 .is_none();
7511 if is_copilot_disabled {
7512 cx.propagate();
7513 }
7514 }
7515 }
7516
7517 pub fn previous_edit_prediction(
7518 &mut self,
7519 _: &PreviousEditPrediction,
7520 window: &mut Window,
7521 cx: &mut Context<Self>,
7522 ) {
7523 if self.has_active_edit_prediction() {
7524 self.cycle_edit_prediction(Direction::Prev, window, cx);
7525 } else {
7526 let is_copilot_disabled = self
7527 .refresh_edit_prediction(false, true, window, cx)
7528 .is_none();
7529 if is_copilot_disabled {
7530 cx.propagate();
7531 }
7532 }
7533 }
7534
7535 pub fn accept_edit_prediction(
7536 &mut self,
7537 _: &AcceptEditPrediction,
7538 window: &mut Window,
7539 cx: &mut Context<Self>,
7540 ) {
7541 if self.show_edit_predictions_in_menu() {
7542 self.hide_context_menu(window, cx);
7543 }
7544
7545 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7546 return;
7547 };
7548
7549 match &active_edit_prediction.completion {
7550 EditPrediction::MoveWithin { target, .. } => {
7551 let target = *target;
7552
7553 if let Some(position_map) = &self.last_position_map {
7554 if position_map
7555 .visible_row_range
7556 .contains(&target.to_display_point(&position_map.snapshot).row())
7557 || !self.edit_prediction_requires_modifier()
7558 {
7559 self.unfold_ranges(&[target..target], true, false, cx);
7560 // Note that this is also done in vim's handler of the Tab action.
7561 self.change_selections(
7562 SelectionEffects::scroll(Autoscroll::newest()),
7563 window,
7564 cx,
7565 |selections| {
7566 selections.select_anchor_ranges([target..target]);
7567 },
7568 );
7569 self.clear_row_highlights::<EditPredictionPreview>();
7570
7571 self.edit_prediction_preview
7572 .set_previous_scroll_position(None);
7573 } else {
7574 self.edit_prediction_preview
7575 .set_previous_scroll_position(Some(
7576 position_map.snapshot.scroll_anchor,
7577 ));
7578
7579 self.highlight_rows::<EditPredictionPreview>(
7580 target..target,
7581 cx.theme().colors().editor_highlighted_line_background,
7582 RowHighlightOptions {
7583 autoscroll: true,
7584 ..Default::default()
7585 },
7586 cx,
7587 );
7588 self.request_autoscroll(Autoscroll::fit(), cx);
7589 }
7590 }
7591 }
7592 EditPrediction::MoveOutside { snapshot, target } => {
7593 if let Some(workspace) = self.workspace() {
7594 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7595 .detach_and_log_err(cx);
7596 }
7597 }
7598 EditPrediction::Edit { edits, .. } => {
7599 self.report_edit_prediction_event(
7600 active_edit_prediction.completion_id.clone(),
7601 true,
7602 cx,
7603 );
7604
7605 if let Some(provider) = self.edit_prediction_provider() {
7606 provider.accept(cx);
7607 }
7608
7609 // Store the transaction ID and selections before applying the edit
7610 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7611
7612 let snapshot = self.buffer.read(cx).snapshot(cx);
7613 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7614
7615 self.buffer.update(cx, |buffer, cx| {
7616 buffer.edit(edits.iter().cloned(), None, cx)
7617 });
7618
7619 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7620 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7621 });
7622
7623 let selections = self.selections.disjoint_anchors_arc();
7624 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7625 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7626 if has_new_transaction {
7627 self.selection_history
7628 .insert_transaction(transaction_id_now, selections);
7629 }
7630 }
7631
7632 self.update_visible_edit_prediction(window, cx);
7633 if self.active_edit_prediction.is_none() {
7634 self.refresh_edit_prediction(true, true, window, cx);
7635 }
7636
7637 cx.notify();
7638 }
7639 }
7640
7641 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7642 }
7643
7644 pub fn accept_partial_edit_prediction(
7645 &mut self,
7646 _: &AcceptPartialEditPrediction,
7647 window: &mut Window,
7648 cx: &mut Context<Self>,
7649 ) {
7650 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7651 return;
7652 };
7653 if self.selections.count() != 1 {
7654 return;
7655 }
7656
7657 match &active_edit_prediction.completion {
7658 EditPrediction::MoveWithin { target, .. } => {
7659 let target = *target;
7660 self.change_selections(
7661 SelectionEffects::scroll(Autoscroll::newest()),
7662 window,
7663 cx,
7664 |selections| {
7665 selections.select_anchor_ranges([target..target]);
7666 },
7667 );
7668 }
7669 EditPrediction::MoveOutside { snapshot, target } => {
7670 if let Some(workspace) = self.workspace() {
7671 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7672 .detach_and_log_err(cx);
7673 }
7674 }
7675 EditPrediction::Edit { edits, .. } => {
7676 self.report_edit_prediction_event(
7677 active_edit_prediction.completion_id.clone(),
7678 true,
7679 cx,
7680 );
7681
7682 // Find an insertion that starts at the cursor position.
7683 let snapshot = self.buffer.read(cx).snapshot(cx);
7684 let cursor_offset = self
7685 .selections
7686 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7687 .head();
7688 let insertion = edits.iter().find_map(|(range, text)| {
7689 let range = range.to_offset(&snapshot);
7690 if range.is_empty() && range.start == cursor_offset {
7691 Some(text)
7692 } else {
7693 None
7694 }
7695 });
7696
7697 if let Some(text) = insertion {
7698 let mut partial_completion = text
7699 .chars()
7700 .by_ref()
7701 .take_while(|c| c.is_alphabetic())
7702 .collect::<String>();
7703 if partial_completion.is_empty() {
7704 partial_completion = text
7705 .chars()
7706 .by_ref()
7707 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7708 .collect::<String>();
7709 }
7710
7711 cx.emit(EditorEvent::InputHandled {
7712 utf16_range_to_replace: None,
7713 text: partial_completion.clone().into(),
7714 });
7715
7716 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7717
7718 self.refresh_edit_prediction(true, true, window, cx);
7719 cx.notify();
7720 } else {
7721 self.accept_edit_prediction(&Default::default(), window, cx);
7722 }
7723 }
7724 }
7725 }
7726
7727 fn discard_edit_prediction(
7728 &mut self,
7729 should_report_edit_prediction_event: bool,
7730 cx: &mut Context<Self>,
7731 ) -> bool {
7732 if should_report_edit_prediction_event {
7733 let completion_id = self
7734 .active_edit_prediction
7735 .as_ref()
7736 .and_then(|active_completion| active_completion.completion_id.clone());
7737
7738 self.report_edit_prediction_event(completion_id, false, cx);
7739 }
7740
7741 if let Some(provider) = self.edit_prediction_provider() {
7742 provider.discard(cx);
7743 }
7744
7745 self.take_active_edit_prediction(cx)
7746 }
7747
7748 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7749 let Some(provider) = self.edit_prediction_provider() else {
7750 return;
7751 };
7752
7753 let Some((_, buffer, _)) = self
7754 .buffer
7755 .read(cx)
7756 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7757 else {
7758 return;
7759 };
7760
7761 let extension = buffer
7762 .read(cx)
7763 .file()
7764 .and_then(|file| Some(file.path().extension()?.to_string()));
7765
7766 let event_type = match accepted {
7767 true => "Edit Prediction Accepted",
7768 false => "Edit Prediction Discarded",
7769 };
7770 telemetry::event!(
7771 event_type,
7772 provider = provider.name(),
7773 prediction_id = id,
7774 suggestion_accepted = accepted,
7775 file_extension = extension,
7776 );
7777 }
7778
7779 fn open_editor_at_anchor(
7780 snapshot: &language::BufferSnapshot,
7781 target: language::Anchor,
7782 workspace: &Entity<Workspace>,
7783 window: &mut Window,
7784 cx: &mut App,
7785 ) -> Task<Result<()>> {
7786 workspace.update(cx, |workspace, cx| {
7787 let path = snapshot.file().map(|file| file.full_path(cx));
7788 let Some(path) =
7789 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7790 else {
7791 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7792 };
7793 let target = text::ToPoint::to_point(&target, snapshot);
7794 let item = workspace.open_path(path, None, true, window, cx);
7795 window.spawn(cx, async move |cx| {
7796 let Some(editor) = item.await?.downcast::<Editor>() else {
7797 return Ok(());
7798 };
7799 editor
7800 .update_in(cx, |editor, window, cx| {
7801 editor.go_to_singleton_buffer_point(target, window, cx);
7802 })
7803 .ok();
7804 anyhow::Ok(())
7805 })
7806 })
7807 }
7808
7809 pub fn has_active_edit_prediction(&self) -> bool {
7810 self.active_edit_prediction.is_some()
7811 }
7812
7813 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7814 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7815 return false;
7816 };
7817
7818 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7819 self.clear_highlights::<EditPredictionHighlight>(cx);
7820 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7821 true
7822 }
7823
7824 /// Returns true when we're displaying the edit prediction popover below the cursor
7825 /// like we are not previewing and the LSP autocomplete menu is visible
7826 /// or we are in `when_holding_modifier` mode.
7827 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7828 if self.edit_prediction_preview_is_active()
7829 || !self.show_edit_predictions_in_menu()
7830 || !self.edit_predictions_enabled()
7831 {
7832 return false;
7833 }
7834
7835 if self.has_visible_completions_menu() {
7836 return true;
7837 }
7838
7839 has_completion && self.edit_prediction_requires_modifier()
7840 }
7841
7842 fn handle_modifiers_changed(
7843 &mut self,
7844 modifiers: Modifiers,
7845 position_map: &PositionMap,
7846 window: &mut Window,
7847 cx: &mut Context<Self>,
7848 ) {
7849 // Ensure that the edit prediction preview is updated, even when not
7850 // enabled, if there's an active edit prediction preview.
7851 if self.show_edit_predictions_in_menu()
7852 || matches!(
7853 self.edit_prediction_preview,
7854 EditPredictionPreview::Active { .. }
7855 )
7856 {
7857 self.update_edit_prediction_preview(&modifiers, window, cx);
7858 }
7859
7860 self.update_selection_mode(&modifiers, position_map, window, cx);
7861
7862 let mouse_position = window.mouse_position();
7863 if !position_map.text_hitbox.is_hovered(window) {
7864 return;
7865 }
7866
7867 self.update_hovered_link(
7868 position_map.point_for_position(mouse_position),
7869 &position_map.snapshot,
7870 modifiers,
7871 window,
7872 cx,
7873 )
7874 }
7875
7876 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7877 match EditorSettings::get_global(cx).multi_cursor_modifier {
7878 MultiCursorModifier::Alt => modifiers.secondary(),
7879 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7880 }
7881 }
7882
7883 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7884 match EditorSettings::get_global(cx).multi_cursor_modifier {
7885 MultiCursorModifier::Alt => modifiers.alt,
7886 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7887 }
7888 }
7889
7890 fn columnar_selection_mode(
7891 modifiers: &Modifiers,
7892 cx: &mut Context<Self>,
7893 ) -> Option<ColumnarMode> {
7894 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7895 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7896 Some(ColumnarMode::FromMouse)
7897 } else if Self::is_alt_pressed(modifiers, cx) {
7898 Some(ColumnarMode::FromSelection)
7899 } else {
7900 None
7901 }
7902 } else {
7903 None
7904 }
7905 }
7906
7907 fn update_selection_mode(
7908 &mut self,
7909 modifiers: &Modifiers,
7910 position_map: &PositionMap,
7911 window: &mut Window,
7912 cx: &mut Context<Self>,
7913 ) {
7914 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7915 return;
7916 };
7917 if self.selections.pending_anchor().is_none() {
7918 return;
7919 }
7920
7921 let mouse_position = window.mouse_position();
7922 let point_for_position = position_map.point_for_position(mouse_position);
7923 let position = point_for_position.previous_valid;
7924
7925 self.select(
7926 SelectPhase::BeginColumnar {
7927 position,
7928 reset: false,
7929 mode,
7930 goal_column: point_for_position.exact_unclipped.column(),
7931 },
7932 window,
7933 cx,
7934 );
7935 }
7936
7937 fn update_edit_prediction_preview(
7938 &mut self,
7939 modifiers: &Modifiers,
7940 window: &mut Window,
7941 cx: &mut Context<Self>,
7942 ) {
7943 let mut modifiers_held = false;
7944 if let Some(accept_keystroke) = self
7945 .accept_edit_prediction_keybind(false, window, cx)
7946 .keystroke()
7947 {
7948 modifiers_held = modifiers_held
7949 || (accept_keystroke.modifiers() == modifiers
7950 && accept_keystroke.modifiers().modified());
7951 };
7952 if let Some(accept_partial_keystroke) = self
7953 .accept_edit_prediction_keybind(true, window, cx)
7954 .keystroke()
7955 {
7956 modifiers_held = modifiers_held
7957 || (accept_partial_keystroke.modifiers() == modifiers
7958 && accept_partial_keystroke.modifiers().modified());
7959 }
7960
7961 if modifiers_held {
7962 if matches!(
7963 self.edit_prediction_preview,
7964 EditPredictionPreview::Inactive { .. }
7965 ) {
7966 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7967 provider.provider.did_show(cx)
7968 }
7969
7970 self.edit_prediction_preview = EditPredictionPreview::Active {
7971 previous_scroll_position: None,
7972 since: Instant::now(),
7973 };
7974
7975 self.update_visible_edit_prediction(window, cx);
7976 cx.notify();
7977 }
7978 } else if let EditPredictionPreview::Active {
7979 previous_scroll_position,
7980 since,
7981 } = self.edit_prediction_preview
7982 {
7983 if let (Some(previous_scroll_position), Some(position_map)) =
7984 (previous_scroll_position, self.last_position_map.as_ref())
7985 {
7986 self.set_scroll_position(
7987 previous_scroll_position
7988 .scroll_position(&position_map.snapshot.display_snapshot),
7989 window,
7990 cx,
7991 );
7992 }
7993
7994 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7995 released_too_fast: since.elapsed() < Duration::from_millis(200),
7996 };
7997 self.clear_row_highlights::<EditPredictionPreview>();
7998 self.update_visible_edit_prediction(window, cx);
7999 cx.notify();
8000 }
8001 }
8002
8003 fn update_visible_edit_prediction(
8004 &mut self,
8005 _window: &mut Window,
8006 cx: &mut Context<Self>,
8007 ) -> Option<()> {
8008 if DisableAiSettings::get_global(cx).disable_ai {
8009 return None;
8010 }
8011
8012 if self.ime_transaction.is_some() {
8013 self.discard_edit_prediction(false, cx);
8014 return None;
8015 }
8016
8017 let selection = self.selections.newest_anchor();
8018 let cursor = selection.head();
8019 let multibuffer = self.buffer.read(cx).snapshot(cx);
8020 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8021 let excerpt_id = cursor.excerpt_id;
8022
8023 let show_in_menu = self.show_edit_predictions_in_menu();
8024 let completions_menu_has_precedence = !show_in_menu
8025 && (self.context_menu.borrow().is_some()
8026 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8027
8028 if completions_menu_has_precedence
8029 || !offset_selection.is_empty()
8030 || self
8031 .active_edit_prediction
8032 .as_ref()
8033 .is_some_and(|completion| {
8034 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8035 return false;
8036 };
8037 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8038 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8039 !invalidation_range.contains(&offset_selection.head())
8040 })
8041 {
8042 self.discard_edit_prediction(false, cx);
8043 return None;
8044 }
8045
8046 self.take_active_edit_prediction(cx);
8047 let Some(provider) = self.edit_prediction_provider() else {
8048 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8049 return None;
8050 };
8051
8052 let (buffer, cursor_buffer_position) =
8053 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8054
8055 self.edit_prediction_settings =
8056 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8057
8058 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8059
8060 if self.edit_prediction_indent_conflict {
8061 let cursor_point = cursor.to_point(&multibuffer);
8062
8063 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
8064
8065 if let Some((_, indent)) = indents.iter().next()
8066 && indent.len == cursor_point.column
8067 {
8068 self.edit_prediction_indent_conflict = false;
8069 }
8070 }
8071
8072 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8073
8074 let (completion_id, edits, edit_preview) = match edit_prediction {
8075 edit_prediction::EditPrediction::Local {
8076 id,
8077 edits,
8078 edit_preview,
8079 } => (id, edits, edit_preview),
8080 edit_prediction::EditPrediction::Jump {
8081 id,
8082 snapshot,
8083 target,
8084 } => {
8085 self.stale_edit_prediction_in_menu = None;
8086 self.active_edit_prediction = Some(EditPredictionState {
8087 inlay_ids: vec![],
8088 completion: EditPrediction::MoveOutside { snapshot, target },
8089 completion_id: id,
8090 invalidation_range: None,
8091 });
8092 cx.notify();
8093 return Some(());
8094 }
8095 };
8096
8097 let edits = edits
8098 .into_iter()
8099 .flat_map(|(range, new_text)| {
8100 Some((
8101 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8102 new_text,
8103 ))
8104 })
8105 .collect::<Vec<_>>();
8106 if edits.is_empty() {
8107 return None;
8108 }
8109
8110 let first_edit_start = edits.first().unwrap().0.start;
8111 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8112 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8113
8114 let last_edit_end = edits.last().unwrap().0.end;
8115 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8116 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8117
8118 let cursor_row = cursor.to_point(&multibuffer).row;
8119
8120 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8121
8122 let mut inlay_ids = Vec::new();
8123 let invalidation_row_range;
8124 let move_invalidation_row_range = if cursor_row < edit_start_row {
8125 Some(cursor_row..edit_end_row)
8126 } else if cursor_row > edit_end_row {
8127 Some(edit_start_row..cursor_row)
8128 } else {
8129 None
8130 };
8131 let supports_jump = self
8132 .edit_prediction_provider
8133 .as_ref()
8134 .map(|provider| provider.provider.supports_jump_to_edit())
8135 .unwrap_or(true);
8136
8137 let is_move = supports_jump
8138 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8139 let completion = if is_move {
8140 invalidation_row_range =
8141 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8142 let target = first_edit_start;
8143 EditPrediction::MoveWithin { target, snapshot }
8144 } else {
8145 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8146 && !self.edit_predictions_hidden_for_vim_mode;
8147
8148 if show_completions_in_buffer {
8149 if let Some(provider) = &self.edit_prediction_provider {
8150 provider.provider.did_show(cx);
8151 }
8152 if edits
8153 .iter()
8154 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8155 {
8156 let mut inlays = Vec::new();
8157 for (range, new_text) in &edits {
8158 let inlay = Inlay::edit_prediction(
8159 post_inc(&mut self.next_inlay_id),
8160 range.start,
8161 new_text.as_ref(),
8162 );
8163 inlay_ids.push(inlay.id);
8164 inlays.push(inlay);
8165 }
8166
8167 self.splice_inlays(&[], inlays, cx);
8168 } else {
8169 let background_color = cx.theme().status().deleted_background;
8170 self.highlight_text::<EditPredictionHighlight>(
8171 edits.iter().map(|(range, _)| range.clone()).collect(),
8172 HighlightStyle {
8173 background_color: Some(background_color),
8174 ..Default::default()
8175 },
8176 cx,
8177 );
8178 }
8179 }
8180
8181 invalidation_row_range = edit_start_row..edit_end_row;
8182
8183 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8184 if provider.show_tab_accept_marker() {
8185 EditDisplayMode::TabAccept
8186 } else {
8187 EditDisplayMode::Inline
8188 }
8189 } else {
8190 EditDisplayMode::DiffPopover
8191 };
8192
8193 EditPrediction::Edit {
8194 edits,
8195 edit_preview,
8196 display_mode,
8197 snapshot,
8198 }
8199 };
8200
8201 let invalidation_range = multibuffer
8202 .anchor_before(Point::new(invalidation_row_range.start, 0))
8203 ..multibuffer.anchor_after(Point::new(
8204 invalidation_row_range.end,
8205 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8206 ));
8207
8208 self.stale_edit_prediction_in_menu = None;
8209 self.active_edit_prediction = Some(EditPredictionState {
8210 inlay_ids,
8211 completion,
8212 completion_id,
8213 invalidation_range: Some(invalidation_range),
8214 });
8215
8216 cx.notify();
8217
8218 Some(())
8219 }
8220
8221 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8222 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8223 }
8224
8225 fn clear_tasks(&mut self) {
8226 self.tasks.clear()
8227 }
8228
8229 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8230 if self.tasks.insert(key, value).is_some() {
8231 // This case should hopefully be rare, but just in case...
8232 log::error!(
8233 "multiple different run targets found on a single line, only the last target will be rendered"
8234 )
8235 }
8236 }
8237
8238 /// Get all display points of breakpoints that will be rendered within editor
8239 ///
8240 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8241 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8242 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8243 fn active_breakpoints(
8244 &self,
8245 range: Range<DisplayRow>,
8246 window: &mut Window,
8247 cx: &mut Context<Self>,
8248 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8249 let mut breakpoint_display_points = HashMap::default();
8250
8251 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8252 return breakpoint_display_points;
8253 };
8254
8255 let snapshot = self.snapshot(window, cx);
8256
8257 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8258 let Some(project) = self.project() else {
8259 return breakpoint_display_points;
8260 };
8261
8262 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8263 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8264
8265 for (buffer_snapshot, range, excerpt_id) in
8266 multi_buffer_snapshot.range_to_buffer_ranges(range)
8267 {
8268 let Some(buffer) = project
8269 .read(cx)
8270 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8271 else {
8272 continue;
8273 };
8274 let breakpoints = breakpoint_store.read(cx).breakpoints(
8275 &buffer,
8276 Some(
8277 buffer_snapshot.anchor_before(range.start)
8278 ..buffer_snapshot.anchor_after(range.end),
8279 ),
8280 buffer_snapshot,
8281 cx,
8282 );
8283 for (breakpoint, state) in breakpoints {
8284 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8285 let position = multi_buffer_anchor
8286 .to_point(&multi_buffer_snapshot)
8287 .to_display_point(&snapshot);
8288
8289 breakpoint_display_points.insert(
8290 position.row(),
8291 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8292 );
8293 }
8294 }
8295
8296 breakpoint_display_points
8297 }
8298
8299 fn breakpoint_context_menu(
8300 &self,
8301 anchor: Anchor,
8302 window: &mut Window,
8303 cx: &mut Context<Self>,
8304 ) -> Entity<ui::ContextMenu> {
8305 let weak_editor = cx.weak_entity();
8306 let focus_handle = self.focus_handle(cx);
8307
8308 let row = self
8309 .buffer
8310 .read(cx)
8311 .snapshot(cx)
8312 .summary_for_anchor::<Point>(&anchor)
8313 .row;
8314
8315 let breakpoint = self
8316 .breakpoint_at_row(row, window, cx)
8317 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8318
8319 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8320 "Edit Log Breakpoint"
8321 } else {
8322 "Set Log Breakpoint"
8323 };
8324
8325 let condition_breakpoint_msg = if breakpoint
8326 .as_ref()
8327 .is_some_and(|bp| bp.1.condition.is_some())
8328 {
8329 "Edit Condition Breakpoint"
8330 } else {
8331 "Set Condition Breakpoint"
8332 };
8333
8334 let hit_condition_breakpoint_msg = if breakpoint
8335 .as_ref()
8336 .is_some_and(|bp| bp.1.hit_condition.is_some())
8337 {
8338 "Edit Hit Condition Breakpoint"
8339 } else {
8340 "Set Hit Condition Breakpoint"
8341 };
8342
8343 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8344 "Unset Breakpoint"
8345 } else {
8346 "Set Breakpoint"
8347 };
8348
8349 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8350
8351 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8352 BreakpointState::Enabled => Some("Disable"),
8353 BreakpointState::Disabled => Some("Enable"),
8354 });
8355
8356 let (anchor, breakpoint) =
8357 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8358
8359 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8360 menu.on_blur_subscription(Subscription::new(|| {}))
8361 .context(focus_handle)
8362 .when(run_to_cursor, |this| {
8363 let weak_editor = weak_editor.clone();
8364 this.entry("Run to cursor", None, move |window, cx| {
8365 weak_editor
8366 .update(cx, |editor, cx| {
8367 editor.change_selections(
8368 SelectionEffects::no_scroll(),
8369 window,
8370 cx,
8371 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8372 );
8373 })
8374 .ok();
8375
8376 window.dispatch_action(Box::new(RunToCursor), cx);
8377 })
8378 .separator()
8379 })
8380 .when_some(toggle_state_msg, |this, msg| {
8381 this.entry(msg, None, {
8382 let weak_editor = weak_editor.clone();
8383 let breakpoint = breakpoint.clone();
8384 move |_window, cx| {
8385 weak_editor
8386 .update(cx, |this, cx| {
8387 this.edit_breakpoint_at_anchor(
8388 anchor,
8389 breakpoint.as_ref().clone(),
8390 BreakpointEditAction::InvertState,
8391 cx,
8392 );
8393 })
8394 .log_err();
8395 }
8396 })
8397 })
8398 .entry(set_breakpoint_msg, None, {
8399 let weak_editor = weak_editor.clone();
8400 let breakpoint = breakpoint.clone();
8401 move |_window, cx| {
8402 weak_editor
8403 .update(cx, |this, cx| {
8404 this.edit_breakpoint_at_anchor(
8405 anchor,
8406 breakpoint.as_ref().clone(),
8407 BreakpointEditAction::Toggle,
8408 cx,
8409 );
8410 })
8411 .log_err();
8412 }
8413 })
8414 .entry(log_breakpoint_msg, None, {
8415 let breakpoint = breakpoint.clone();
8416 let weak_editor = weak_editor.clone();
8417 move |window, cx| {
8418 weak_editor
8419 .update(cx, |this, cx| {
8420 this.add_edit_breakpoint_block(
8421 anchor,
8422 breakpoint.as_ref(),
8423 BreakpointPromptEditAction::Log,
8424 window,
8425 cx,
8426 );
8427 })
8428 .log_err();
8429 }
8430 })
8431 .entry(condition_breakpoint_msg, None, {
8432 let breakpoint = breakpoint.clone();
8433 let weak_editor = weak_editor.clone();
8434 move |window, cx| {
8435 weak_editor
8436 .update(cx, |this, cx| {
8437 this.add_edit_breakpoint_block(
8438 anchor,
8439 breakpoint.as_ref(),
8440 BreakpointPromptEditAction::Condition,
8441 window,
8442 cx,
8443 );
8444 })
8445 .log_err();
8446 }
8447 })
8448 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8449 weak_editor
8450 .update(cx, |this, cx| {
8451 this.add_edit_breakpoint_block(
8452 anchor,
8453 breakpoint.as_ref(),
8454 BreakpointPromptEditAction::HitCondition,
8455 window,
8456 cx,
8457 );
8458 })
8459 .log_err();
8460 })
8461 })
8462 }
8463
8464 fn render_breakpoint(
8465 &self,
8466 position: Anchor,
8467 row: DisplayRow,
8468 breakpoint: &Breakpoint,
8469 state: Option<BreakpointSessionState>,
8470 cx: &mut Context<Self>,
8471 ) -> IconButton {
8472 let is_rejected = state.is_some_and(|s| !s.verified);
8473 // Is it a breakpoint that shows up when hovering over gutter?
8474 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8475 (false, false),
8476 |PhantomBreakpointIndicator {
8477 is_active,
8478 display_row,
8479 collides_with_existing_breakpoint,
8480 }| {
8481 (
8482 is_active && display_row == row,
8483 collides_with_existing_breakpoint,
8484 )
8485 },
8486 );
8487
8488 let (color, icon) = {
8489 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8490 (false, false) => ui::IconName::DebugBreakpoint,
8491 (true, false) => ui::IconName::DebugLogBreakpoint,
8492 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8493 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8494 };
8495
8496 let color = cx.theme().colors();
8497
8498 let color = if is_phantom {
8499 if collides_with_existing {
8500 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8501 } else {
8502 Color::Hint
8503 }
8504 } else if is_rejected {
8505 Color::Disabled
8506 } else {
8507 Color::Debugger
8508 };
8509
8510 (color, icon)
8511 };
8512
8513 let breakpoint = Arc::from(breakpoint.clone());
8514
8515 let alt_as_text = gpui::Keystroke {
8516 modifiers: Modifiers::secondary_key(),
8517 ..Default::default()
8518 };
8519 let primary_action_text = if breakpoint.is_disabled() {
8520 "Enable breakpoint"
8521 } else if is_phantom && !collides_with_existing {
8522 "Set breakpoint"
8523 } else {
8524 "Unset breakpoint"
8525 };
8526 let focus_handle = self.focus_handle.clone();
8527
8528 let meta = if is_rejected {
8529 SharedString::from("No executable code is associated with this line.")
8530 } else if collides_with_existing && !breakpoint.is_disabled() {
8531 SharedString::from(format!(
8532 "{alt_as_text}-click to disable,\nright-click for more options."
8533 ))
8534 } else {
8535 SharedString::from("Right-click for more options.")
8536 };
8537 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8538 .icon_size(IconSize::XSmall)
8539 .size(ui::ButtonSize::None)
8540 .when(is_rejected, |this| {
8541 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8542 })
8543 .icon_color(color)
8544 .style(ButtonStyle::Transparent)
8545 .on_click(cx.listener({
8546 move |editor, event: &ClickEvent, window, cx| {
8547 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8548 BreakpointEditAction::InvertState
8549 } else {
8550 BreakpointEditAction::Toggle
8551 };
8552
8553 window.focus(&editor.focus_handle(cx));
8554 editor.edit_breakpoint_at_anchor(
8555 position,
8556 breakpoint.as_ref().clone(),
8557 edit_action,
8558 cx,
8559 );
8560 }
8561 }))
8562 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8563 editor.set_breakpoint_context_menu(
8564 row,
8565 Some(position),
8566 event.position(),
8567 window,
8568 cx,
8569 );
8570 }))
8571 .tooltip(move |_window, cx| {
8572 Tooltip::with_meta_in(
8573 primary_action_text,
8574 Some(&ToggleBreakpoint),
8575 meta.clone(),
8576 &focus_handle,
8577 cx,
8578 )
8579 })
8580 }
8581
8582 fn build_tasks_context(
8583 project: &Entity<Project>,
8584 buffer: &Entity<Buffer>,
8585 buffer_row: u32,
8586 tasks: &Arc<RunnableTasks>,
8587 cx: &mut Context<Self>,
8588 ) -> Task<Option<task::TaskContext>> {
8589 let position = Point::new(buffer_row, tasks.column);
8590 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8591 let location = Location {
8592 buffer: buffer.clone(),
8593 range: range_start..range_start,
8594 };
8595 // Fill in the environmental variables from the tree-sitter captures
8596 let mut captured_task_variables = TaskVariables::default();
8597 for (capture_name, value) in tasks.extra_variables.clone() {
8598 captured_task_variables.insert(
8599 task::VariableName::Custom(capture_name.into()),
8600 value.clone(),
8601 );
8602 }
8603 project.update(cx, |project, cx| {
8604 project.task_store().update(cx, |task_store, cx| {
8605 task_store.task_context_for_location(captured_task_variables, location, cx)
8606 })
8607 })
8608 }
8609
8610 pub fn spawn_nearest_task(
8611 &mut self,
8612 action: &SpawnNearestTask,
8613 window: &mut Window,
8614 cx: &mut Context<Self>,
8615 ) {
8616 let Some((workspace, _)) = self.workspace.clone() else {
8617 return;
8618 };
8619 let Some(project) = self.project.clone() else {
8620 return;
8621 };
8622
8623 // Try to find a closest, enclosing node using tree-sitter that has a task
8624 let Some((buffer, buffer_row, tasks)) = self
8625 .find_enclosing_node_task(cx)
8626 // Or find the task that's closest in row-distance.
8627 .or_else(|| self.find_closest_task(cx))
8628 else {
8629 return;
8630 };
8631
8632 let reveal_strategy = action.reveal;
8633 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8634 cx.spawn_in(window, async move |_, cx| {
8635 let context = task_context.await?;
8636 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8637
8638 let resolved = &mut resolved_task.resolved;
8639 resolved.reveal = reveal_strategy;
8640
8641 workspace
8642 .update_in(cx, |workspace, window, cx| {
8643 workspace.schedule_resolved_task(
8644 task_source_kind,
8645 resolved_task,
8646 false,
8647 window,
8648 cx,
8649 );
8650 })
8651 .ok()
8652 })
8653 .detach();
8654 }
8655
8656 fn find_closest_task(
8657 &mut self,
8658 cx: &mut Context<Self>,
8659 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8660 let cursor_row = self
8661 .selections
8662 .newest_adjusted(&self.display_snapshot(cx))
8663 .head()
8664 .row;
8665
8666 let ((buffer_id, row), tasks) = self
8667 .tasks
8668 .iter()
8669 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8670
8671 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8672 let tasks = Arc::new(tasks.to_owned());
8673 Some((buffer, *row, tasks))
8674 }
8675
8676 fn find_enclosing_node_task(
8677 &mut self,
8678 cx: &mut Context<Self>,
8679 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8680 let snapshot = self.buffer.read(cx).snapshot(cx);
8681 let offset = self
8682 .selections
8683 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8684 .head();
8685 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8686 let offset = excerpt.map_offset_to_buffer(offset);
8687 let buffer_id = excerpt.buffer().remote_id();
8688
8689 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8690 let mut cursor = layer.node().walk();
8691
8692 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8693 if cursor.node().end_byte() == offset.0 {
8694 cursor.goto_next_sibling();
8695 }
8696 }
8697
8698 // Ascend to the smallest ancestor that contains the range and has a task.
8699 loop {
8700 let node = cursor.node();
8701 let node_range = node.byte_range();
8702 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8703
8704 // Check if this node contains our offset
8705 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8706 // If it contains offset, check for task
8707 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8708 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8709 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8710 }
8711 }
8712
8713 if !cursor.goto_parent() {
8714 break;
8715 }
8716 }
8717 None
8718 }
8719
8720 fn render_run_indicator(
8721 &self,
8722 _style: &EditorStyle,
8723 is_active: bool,
8724 row: DisplayRow,
8725 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8726 cx: &mut Context<Self>,
8727 ) -> IconButton {
8728 let color = Color::Muted;
8729 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8730
8731 IconButton::new(
8732 ("run_indicator", row.0 as usize),
8733 ui::IconName::PlayOutlined,
8734 )
8735 .shape(ui::IconButtonShape::Square)
8736 .icon_size(IconSize::XSmall)
8737 .icon_color(color)
8738 .toggle_state(is_active)
8739 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8740 let quick_launch = match e {
8741 ClickEvent::Keyboard(_) => true,
8742 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8743 };
8744
8745 window.focus(&editor.focus_handle(cx));
8746 editor.toggle_code_actions(
8747 &ToggleCodeActions {
8748 deployed_from: Some(CodeActionSource::RunMenu(row)),
8749 quick_launch,
8750 },
8751 window,
8752 cx,
8753 );
8754 }))
8755 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8756 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8757 }))
8758 }
8759
8760 pub fn context_menu_visible(&self) -> bool {
8761 !self.edit_prediction_preview_is_active()
8762 && self
8763 .context_menu
8764 .borrow()
8765 .as_ref()
8766 .is_some_and(|menu| menu.visible())
8767 }
8768
8769 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8770 self.context_menu
8771 .borrow()
8772 .as_ref()
8773 .map(|menu| menu.origin())
8774 }
8775
8776 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8777 self.context_menu_options = Some(options);
8778 }
8779
8780 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8781 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8782
8783 fn render_edit_prediction_popover(
8784 &mut self,
8785 text_bounds: &Bounds<Pixels>,
8786 content_origin: gpui::Point<Pixels>,
8787 right_margin: Pixels,
8788 editor_snapshot: &EditorSnapshot,
8789 visible_row_range: Range<DisplayRow>,
8790 scroll_top: ScrollOffset,
8791 scroll_bottom: ScrollOffset,
8792 line_layouts: &[LineWithInvisibles],
8793 line_height: Pixels,
8794 scroll_position: gpui::Point<ScrollOffset>,
8795 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8796 newest_selection_head: Option<DisplayPoint>,
8797 editor_width: Pixels,
8798 style: &EditorStyle,
8799 window: &mut Window,
8800 cx: &mut App,
8801 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8802 if self.mode().is_minimap() {
8803 return None;
8804 }
8805 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8806
8807 if self.edit_prediction_visible_in_cursor_popover(true) {
8808 return None;
8809 }
8810
8811 match &active_edit_prediction.completion {
8812 EditPrediction::MoveWithin { target, .. } => {
8813 let target_display_point = target.to_display_point(editor_snapshot);
8814
8815 if self.edit_prediction_requires_modifier() {
8816 if !self.edit_prediction_preview_is_active() {
8817 return None;
8818 }
8819
8820 self.render_edit_prediction_modifier_jump_popover(
8821 text_bounds,
8822 content_origin,
8823 visible_row_range,
8824 line_layouts,
8825 line_height,
8826 scroll_pixel_position,
8827 newest_selection_head,
8828 target_display_point,
8829 window,
8830 cx,
8831 )
8832 } else {
8833 self.render_edit_prediction_eager_jump_popover(
8834 text_bounds,
8835 content_origin,
8836 editor_snapshot,
8837 visible_row_range,
8838 scroll_top,
8839 scroll_bottom,
8840 line_height,
8841 scroll_pixel_position,
8842 target_display_point,
8843 editor_width,
8844 window,
8845 cx,
8846 )
8847 }
8848 }
8849 EditPrediction::Edit {
8850 display_mode: EditDisplayMode::Inline,
8851 ..
8852 } => None,
8853 EditPrediction::Edit {
8854 display_mode: EditDisplayMode::TabAccept,
8855 edits,
8856 ..
8857 } => {
8858 let range = &edits.first()?.0;
8859 let target_display_point = range.end.to_display_point(editor_snapshot);
8860
8861 self.render_edit_prediction_end_of_line_popover(
8862 "Accept",
8863 editor_snapshot,
8864 visible_row_range,
8865 target_display_point,
8866 line_height,
8867 scroll_pixel_position,
8868 content_origin,
8869 editor_width,
8870 window,
8871 cx,
8872 )
8873 }
8874 EditPrediction::Edit {
8875 edits,
8876 edit_preview,
8877 display_mode: EditDisplayMode::DiffPopover,
8878 snapshot,
8879 } => self.render_edit_prediction_diff_popover(
8880 text_bounds,
8881 content_origin,
8882 right_margin,
8883 editor_snapshot,
8884 visible_row_range,
8885 line_layouts,
8886 line_height,
8887 scroll_position,
8888 scroll_pixel_position,
8889 newest_selection_head,
8890 editor_width,
8891 style,
8892 edits,
8893 edit_preview,
8894 snapshot,
8895 window,
8896 cx,
8897 ),
8898 EditPrediction::MoveOutside { snapshot, .. } => {
8899 let mut element = self
8900 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8901 .into_any();
8902
8903 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8904 let origin_x = text_bounds.size.width - size.width - px(30.);
8905 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8906 element.prepaint_at(origin, window, cx);
8907
8908 Some((element, origin))
8909 }
8910 }
8911 }
8912
8913 fn render_edit_prediction_modifier_jump_popover(
8914 &mut self,
8915 text_bounds: &Bounds<Pixels>,
8916 content_origin: gpui::Point<Pixels>,
8917 visible_row_range: Range<DisplayRow>,
8918 line_layouts: &[LineWithInvisibles],
8919 line_height: Pixels,
8920 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8921 newest_selection_head: Option<DisplayPoint>,
8922 target_display_point: DisplayPoint,
8923 window: &mut Window,
8924 cx: &mut App,
8925 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8926 let scrolled_content_origin =
8927 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8928
8929 const SCROLL_PADDING_Y: Pixels = px(12.);
8930
8931 if target_display_point.row() < visible_row_range.start {
8932 return self.render_edit_prediction_scroll_popover(
8933 |_| SCROLL_PADDING_Y,
8934 IconName::ArrowUp,
8935 visible_row_range,
8936 line_layouts,
8937 newest_selection_head,
8938 scrolled_content_origin,
8939 window,
8940 cx,
8941 );
8942 } else if target_display_point.row() >= visible_row_range.end {
8943 return self.render_edit_prediction_scroll_popover(
8944 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8945 IconName::ArrowDown,
8946 visible_row_range,
8947 line_layouts,
8948 newest_selection_head,
8949 scrolled_content_origin,
8950 window,
8951 cx,
8952 );
8953 }
8954
8955 const POLE_WIDTH: Pixels = px(2.);
8956
8957 let line_layout =
8958 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8959 let target_column = target_display_point.column() as usize;
8960
8961 let target_x = line_layout.x_for_index(target_column);
8962 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8963 - scroll_pixel_position.y;
8964
8965 let flag_on_right = target_x < text_bounds.size.width / 2.;
8966
8967 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8968 border_color.l += 0.001;
8969
8970 let mut element = v_flex()
8971 .items_end()
8972 .when(flag_on_right, |el| el.items_start())
8973 .child(if flag_on_right {
8974 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8975 .rounded_bl(px(0.))
8976 .rounded_tl(px(0.))
8977 .border_l_2()
8978 .border_color(border_color)
8979 } else {
8980 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8981 .rounded_br(px(0.))
8982 .rounded_tr(px(0.))
8983 .border_r_2()
8984 .border_color(border_color)
8985 })
8986 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8987 .into_any();
8988
8989 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8990
8991 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8992 - point(
8993 if flag_on_right {
8994 POLE_WIDTH
8995 } else {
8996 size.width - POLE_WIDTH
8997 },
8998 size.height - line_height,
8999 );
9000
9001 origin.x = origin.x.max(content_origin.x);
9002
9003 element.prepaint_at(origin, window, cx);
9004
9005 Some((element, origin))
9006 }
9007
9008 fn render_edit_prediction_scroll_popover(
9009 &mut self,
9010 to_y: impl Fn(Size<Pixels>) -> Pixels,
9011 scroll_icon: IconName,
9012 visible_row_range: Range<DisplayRow>,
9013 line_layouts: &[LineWithInvisibles],
9014 newest_selection_head: Option<DisplayPoint>,
9015 scrolled_content_origin: gpui::Point<Pixels>,
9016 window: &mut Window,
9017 cx: &mut App,
9018 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9019 let mut element = self
9020 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9021 .into_any();
9022
9023 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9024
9025 let cursor = newest_selection_head?;
9026 let cursor_row_layout =
9027 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9028 let cursor_column = cursor.column() as usize;
9029
9030 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9031
9032 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9033
9034 element.prepaint_at(origin, window, cx);
9035 Some((element, origin))
9036 }
9037
9038 fn render_edit_prediction_eager_jump_popover(
9039 &mut self,
9040 text_bounds: &Bounds<Pixels>,
9041 content_origin: gpui::Point<Pixels>,
9042 editor_snapshot: &EditorSnapshot,
9043 visible_row_range: Range<DisplayRow>,
9044 scroll_top: ScrollOffset,
9045 scroll_bottom: ScrollOffset,
9046 line_height: Pixels,
9047 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9048 target_display_point: DisplayPoint,
9049 editor_width: Pixels,
9050 window: &mut Window,
9051 cx: &mut App,
9052 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9053 if target_display_point.row().as_f64() < scroll_top {
9054 let mut element = self
9055 .render_edit_prediction_line_popover(
9056 "Jump to Edit",
9057 Some(IconName::ArrowUp),
9058 window,
9059 cx,
9060 )
9061 .into_any();
9062
9063 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9064 let offset = point(
9065 (text_bounds.size.width - size.width) / 2.,
9066 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9067 );
9068
9069 let origin = text_bounds.origin + offset;
9070 element.prepaint_at(origin, window, cx);
9071 Some((element, origin))
9072 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9073 let mut element = self
9074 .render_edit_prediction_line_popover(
9075 "Jump to Edit",
9076 Some(IconName::ArrowDown),
9077 window,
9078 cx,
9079 )
9080 .into_any();
9081
9082 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9083 let offset = point(
9084 (text_bounds.size.width - size.width) / 2.,
9085 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9086 );
9087
9088 let origin = text_bounds.origin + offset;
9089 element.prepaint_at(origin, window, cx);
9090 Some((element, origin))
9091 } else {
9092 self.render_edit_prediction_end_of_line_popover(
9093 "Jump to Edit",
9094 editor_snapshot,
9095 visible_row_range,
9096 target_display_point,
9097 line_height,
9098 scroll_pixel_position,
9099 content_origin,
9100 editor_width,
9101 window,
9102 cx,
9103 )
9104 }
9105 }
9106
9107 fn render_edit_prediction_end_of_line_popover(
9108 self: &mut Editor,
9109 label: &'static str,
9110 editor_snapshot: &EditorSnapshot,
9111 visible_row_range: Range<DisplayRow>,
9112 target_display_point: DisplayPoint,
9113 line_height: Pixels,
9114 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9115 content_origin: gpui::Point<Pixels>,
9116 editor_width: Pixels,
9117 window: &mut Window,
9118 cx: &mut App,
9119 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9120 let target_line_end = DisplayPoint::new(
9121 target_display_point.row(),
9122 editor_snapshot.line_len(target_display_point.row()),
9123 );
9124
9125 let mut element = self
9126 .render_edit_prediction_line_popover(label, None, window, cx)
9127 .into_any();
9128
9129 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9130
9131 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9132
9133 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9134 let mut origin = start_point
9135 + line_origin
9136 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9137 origin.x = origin.x.max(content_origin.x);
9138
9139 let max_x = content_origin.x + editor_width - size.width;
9140
9141 if origin.x > max_x {
9142 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9143
9144 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9145 origin.y += offset;
9146 IconName::ArrowUp
9147 } else {
9148 origin.y -= offset;
9149 IconName::ArrowDown
9150 };
9151
9152 element = self
9153 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9154 .into_any();
9155
9156 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9157
9158 origin.x = content_origin.x + editor_width - size.width - px(2.);
9159 }
9160
9161 element.prepaint_at(origin, window, cx);
9162 Some((element, origin))
9163 }
9164
9165 fn render_edit_prediction_diff_popover(
9166 self: &Editor,
9167 text_bounds: &Bounds<Pixels>,
9168 content_origin: gpui::Point<Pixels>,
9169 right_margin: Pixels,
9170 editor_snapshot: &EditorSnapshot,
9171 visible_row_range: Range<DisplayRow>,
9172 line_layouts: &[LineWithInvisibles],
9173 line_height: Pixels,
9174 scroll_position: gpui::Point<ScrollOffset>,
9175 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9176 newest_selection_head: Option<DisplayPoint>,
9177 editor_width: Pixels,
9178 style: &EditorStyle,
9179 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9180 edit_preview: &Option<language::EditPreview>,
9181 snapshot: &language::BufferSnapshot,
9182 window: &mut Window,
9183 cx: &mut App,
9184 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9185 let edit_start = edits
9186 .first()
9187 .unwrap()
9188 .0
9189 .start
9190 .to_display_point(editor_snapshot);
9191 let edit_end = edits
9192 .last()
9193 .unwrap()
9194 .0
9195 .end
9196 .to_display_point(editor_snapshot);
9197
9198 let is_visible = visible_row_range.contains(&edit_start.row())
9199 || visible_row_range.contains(&edit_end.row());
9200 if !is_visible {
9201 return None;
9202 }
9203
9204 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9205 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9206 } else {
9207 // Fallback for providers without edit_preview
9208 crate::edit_prediction_fallback_text(edits, cx)
9209 };
9210
9211 let styled_text = highlighted_edits.to_styled_text(&style.text);
9212 let line_count = highlighted_edits.text.lines().count();
9213
9214 const BORDER_WIDTH: Pixels = px(1.);
9215
9216 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9217 let has_keybind = keybind.is_some();
9218
9219 let mut element = h_flex()
9220 .items_start()
9221 .child(
9222 h_flex()
9223 .bg(cx.theme().colors().editor_background)
9224 .border(BORDER_WIDTH)
9225 .shadow_xs()
9226 .border_color(cx.theme().colors().border)
9227 .rounded_l_lg()
9228 .when(line_count > 1, |el| el.rounded_br_lg())
9229 .pr_1()
9230 .child(styled_text),
9231 )
9232 .child(
9233 h_flex()
9234 .h(line_height + BORDER_WIDTH * 2.)
9235 .px_1p5()
9236 .gap_1()
9237 // Workaround: For some reason, there's a gap if we don't do this
9238 .ml(-BORDER_WIDTH)
9239 .shadow(vec![gpui::BoxShadow {
9240 color: gpui::black().opacity(0.05),
9241 offset: point(px(1.), px(1.)),
9242 blur_radius: px(2.),
9243 spread_radius: px(0.),
9244 }])
9245 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9246 .border(BORDER_WIDTH)
9247 .border_color(cx.theme().colors().border)
9248 .rounded_r_lg()
9249 .id("edit_prediction_diff_popover_keybind")
9250 .when(!has_keybind, |el| {
9251 let status_colors = cx.theme().status();
9252
9253 el.bg(status_colors.error_background)
9254 .border_color(status_colors.error.opacity(0.6))
9255 .child(Icon::new(IconName::Info).color(Color::Error))
9256 .cursor_default()
9257 .hoverable_tooltip(move |_window, cx| {
9258 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9259 })
9260 })
9261 .children(keybind),
9262 )
9263 .into_any();
9264
9265 let longest_row =
9266 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9267 let longest_line_width = if visible_row_range.contains(&longest_row) {
9268 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9269 } else {
9270 layout_line(
9271 longest_row,
9272 editor_snapshot,
9273 style,
9274 editor_width,
9275 |_| false,
9276 window,
9277 cx,
9278 )
9279 .width
9280 };
9281
9282 let viewport_bounds =
9283 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9284 right: -right_margin,
9285 ..Default::default()
9286 });
9287
9288 let x_after_longest = Pixels::from(
9289 ScrollPixelOffset::from(
9290 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9291 ) - scroll_pixel_position.x,
9292 );
9293
9294 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9295
9296 // Fully visible if it can be displayed within the window (allow overlapping other
9297 // panes). However, this is only allowed if the popover starts within text_bounds.
9298 let can_position_to_the_right = x_after_longest < text_bounds.right()
9299 && x_after_longest + element_bounds.width < viewport_bounds.right();
9300
9301 let mut origin = if can_position_to_the_right {
9302 point(
9303 x_after_longest,
9304 text_bounds.origin.y
9305 + Pixels::from(
9306 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9307 - scroll_pixel_position.y,
9308 ),
9309 )
9310 } else {
9311 let cursor_row = newest_selection_head.map(|head| head.row());
9312 let above_edit = edit_start
9313 .row()
9314 .0
9315 .checked_sub(line_count as u32)
9316 .map(DisplayRow);
9317 let below_edit = Some(edit_end.row() + 1);
9318 let above_cursor =
9319 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9320 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9321
9322 // Place the edit popover adjacent to the edit if there is a location
9323 // available that is onscreen and does not obscure the cursor. Otherwise,
9324 // place it adjacent to the cursor.
9325 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9326 .into_iter()
9327 .flatten()
9328 .find(|&start_row| {
9329 let end_row = start_row + line_count as u32;
9330 visible_row_range.contains(&start_row)
9331 && visible_row_range.contains(&end_row)
9332 && cursor_row
9333 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9334 })?;
9335
9336 content_origin
9337 + point(
9338 Pixels::from(-scroll_pixel_position.x),
9339 Pixels::from(
9340 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9341 ),
9342 )
9343 };
9344
9345 origin.x -= BORDER_WIDTH;
9346
9347 window.defer_draw(element, origin, 1);
9348
9349 // Do not return an element, since it will already be drawn due to defer_draw.
9350 None
9351 }
9352
9353 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9354 px(30.)
9355 }
9356
9357 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9358 if self.read_only(cx) {
9359 cx.theme().players().read_only()
9360 } else {
9361 self.style.as_ref().unwrap().local_player
9362 }
9363 }
9364
9365 fn render_edit_prediction_accept_keybind(
9366 &self,
9367 window: &mut Window,
9368 cx: &mut App,
9369 ) -> Option<AnyElement> {
9370 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9371 let accept_keystroke = accept_binding.keystroke()?;
9372
9373 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9374
9375 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9376 Color::Accent
9377 } else {
9378 Color::Muted
9379 };
9380
9381 h_flex()
9382 .px_0p5()
9383 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9384 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9385 .text_size(TextSize::XSmall.rems(cx))
9386 .child(h_flex().children(ui::render_modifiers(
9387 accept_keystroke.modifiers(),
9388 PlatformStyle::platform(),
9389 Some(modifiers_color),
9390 Some(IconSize::XSmall.rems().into()),
9391 true,
9392 )))
9393 .when(is_platform_style_mac, |parent| {
9394 parent.child(accept_keystroke.key().to_string())
9395 })
9396 .when(!is_platform_style_mac, |parent| {
9397 parent.child(
9398 Key::new(
9399 util::capitalize(accept_keystroke.key()),
9400 Some(Color::Default),
9401 )
9402 .size(Some(IconSize::XSmall.rems().into())),
9403 )
9404 })
9405 .into_any()
9406 .into()
9407 }
9408
9409 fn render_edit_prediction_line_popover(
9410 &self,
9411 label: impl Into<SharedString>,
9412 icon: Option<IconName>,
9413 window: &mut Window,
9414 cx: &mut App,
9415 ) -> Stateful<Div> {
9416 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9417
9418 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9419 let has_keybind = keybind.is_some();
9420
9421 h_flex()
9422 .id("ep-line-popover")
9423 .py_0p5()
9424 .pl_1()
9425 .pr(padding_right)
9426 .gap_1()
9427 .rounded_md()
9428 .border_1()
9429 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9430 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9431 .shadow_xs()
9432 .when(!has_keybind, |el| {
9433 let status_colors = cx.theme().status();
9434
9435 el.bg(status_colors.error_background)
9436 .border_color(status_colors.error.opacity(0.6))
9437 .pl_2()
9438 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9439 .cursor_default()
9440 .hoverable_tooltip(move |_window, cx| {
9441 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9442 })
9443 })
9444 .children(keybind)
9445 .child(
9446 Label::new(label)
9447 .size(LabelSize::Small)
9448 .when(!has_keybind, |el| {
9449 el.color(cx.theme().status().error.into()).strikethrough()
9450 }),
9451 )
9452 .when(!has_keybind, |el| {
9453 el.child(
9454 h_flex().ml_1().child(
9455 Icon::new(IconName::Info)
9456 .size(IconSize::Small)
9457 .color(cx.theme().status().error.into()),
9458 ),
9459 )
9460 })
9461 .when_some(icon, |element, icon| {
9462 element.child(
9463 div()
9464 .mt(px(1.5))
9465 .child(Icon::new(icon).size(IconSize::Small)),
9466 )
9467 })
9468 }
9469
9470 fn render_edit_prediction_jump_outside_popover(
9471 &self,
9472 snapshot: &BufferSnapshot,
9473 window: &mut Window,
9474 cx: &mut App,
9475 ) -> Stateful<Div> {
9476 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9477 let has_keybind = keybind.is_some();
9478
9479 let file_name = snapshot
9480 .file()
9481 .map(|file| SharedString::new(file.file_name(cx)))
9482 .unwrap_or(SharedString::new_static("untitled"));
9483
9484 h_flex()
9485 .id("ep-jump-outside-popover")
9486 .py_1()
9487 .px_2()
9488 .gap_1()
9489 .rounded_md()
9490 .border_1()
9491 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9492 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9493 .shadow_xs()
9494 .when(!has_keybind, |el| {
9495 let status_colors = cx.theme().status();
9496
9497 el.bg(status_colors.error_background)
9498 .border_color(status_colors.error.opacity(0.6))
9499 .pl_2()
9500 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9501 .cursor_default()
9502 .hoverable_tooltip(move |_window, cx| {
9503 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9504 })
9505 })
9506 .children(keybind)
9507 .child(
9508 Label::new(file_name)
9509 .size(LabelSize::Small)
9510 .buffer_font(cx)
9511 .when(!has_keybind, |el| {
9512 el.color(cx.theme().status().error.into()).strikethrough()
9513 }),
9514 )
9515 .when(!has_keybind, |el| {
9516 el.child(
9517 h_flex().ml_1().child(
9518 Icon::new(IconName::Info)
9519 .size(IconSize::Small)
9520 .color(cx.theme().status().error.into()),
9521 ),
9522 )
9523 })
9524 .child(
9525 div()
9526 .mt(px(1.5))
9527 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9528 )
9529 }
9530
9531 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9532 let accent_color = cx.theme().colors().text_accent;
9533 let editor_bg_color = cx.theme().colors().editor_background;
9534 editor_bg_color.blend(accent_color.opacity(0.1))
9535 }
9536
9537 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9538 let accent_color = cx.theme().colors().text_accent;
9539 let editor_bg_color = cx.theme().colors().editor_background;
9540 editor_bg_color.blend(accent_color.opacity(0.6))
9541 }
9542 fn get_prediction_provider_icon_name(
9543 provider: &Option<RegisteredEditPredictionProvider>,
9544 ) -> IconName {
9545 match provider {
9546 Some(provider) => match provider.provider.name() {
9547 "copilot" => IconName::Copilot,
9548 "supermaven" => IconName::Supermaven,
9549 _ => IconName::ZedPredict,
9550 },
9551 None => IconName::ZedPredict,
9552 }
9553 }
9554
9555 fn render_edit_prediction_cursor_popover(
9556 &self,
9557 min_width: Pixels,
9558 max_width: Pixels,
9559 cursor_point: Point,
9560 style: &EditorStyle,
9561 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9562 _window: &Window,
9563 cx: &mut Context<Editor>,
9564 ) -> Option<AnyElement> {
9565 let provider = self.edit_prediction_provider.as_ref()?;
9566 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9567
9568 let is_refreshing = provider.provider.is_refreshing(cx);
9569
9570 fn pending_completion_container(icon: IconName) -> Div {
9571 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9572 }
9573
9574 let completion = match &self.active_edit_prediction {
9575 Some(prediction) => {
9576 if !self.has_visible_completions_menu() {
9577 const RADIUS: Pixels = px(6.);
9578 const BORDER_WIDTH: Pixels = px(1.);
9579
9580 return Some(
9581 h_flex()
9582 .elevation_2(cx)
9583 .border(BORDER_WIDTH)
9584 .border_color(cx.theme().colors().border)
9585 .when(accept_keystroke.is_none(), |el| {
9586 el.border_color(cx.theme().status().error)
9587 })
9588 .rounded(RADIUS)
9589 .rounded_tl(px(0.))
9590 .overflow_hidden()
9591 .child(div().px_1p5().child(match &prediction.completion {
9592 EditPrediction::MoveWithin { target, snapshot } => {
9593 use text::ToPoint as _;
9594 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9595 {
9596 Icon::new(IconName::ZedPredictDown)
9597 } else {
9598 Icon::new(IconName::ZedPredictUp)
9599 }
9600 }
9601 EditPrediction::MoveOutside { .. } => {
9602 // TODO [zeta2] custom icon for external jump?
9603 Icon::new(provider_icon)
9604 }
9605 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9606 }))
9607 .child(
9608 h_flex()
9609 .gap_1()
9610 .py_1()
9611 .px_2()
9612 .rounded_r(RADIUS - BORDER_WIDTH)
9613 .border_l_1()
9614 .border_color(cx.theme().colors().border)
9615 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9616 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9617 el.child(
9618 Label::new("Hold")
9619 .size(LabelSize::Small)
9620 .when(accept_keystroke.is_none(), |el| {
9621 el.strikethrough()
9622 })
9623 .line_height_style(LineHeightStyle::UiLabel),
9624 )
9625 })
9626 .id("edit_prediction_cursor_popover_keybind")
9627 .when(accept_keystroke.is_none(), |el| {
9628 let status_colors = cx.theme().status();
9629
9630 el.bg(status_colors.error_background)
9631 .border_color(status_colors.error.opacity(0.6))
9632 .child(Icon::new(IconName::Info).color(Color::Error))
9633 .cursor_default()
9634 .hoverable_tooltip(move |_window, cx| {
9635 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9636 .into()
9637 })
9638 })
9639 .when_some(
9640 accept_keystroke.as_ref(),
9641 |el, accept_keystroke| {
9642 el.child(h_flex().children(ui::render_modifiers(
9643 accept_keystroke.modifiers(),
9644 PlatformStyle::platform(),
9645 Some(Color::Default),
9646 Some(IconSize::XSmall.rems().into()),
9647 false,
9648 )))
9649 },
9650 ),
9651 )
9652 .into_any(),
9653 );
9654 }
9655
9656 self.render_edit_prediction_cursor_popover_preview(
9657 prediction,
9658 cursor_point,
9659 style,
9660 cx,
9661 )?
9662 }
9663
9664 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9665 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9666 stale_completion,
9667 cursor_point,
9668 style,
9669 cx,
9670 )?,
9671
9672 None => pending_completion_container(provider_icon)
9673 .child(Label::new("...").size(LabelSize::Small)),
9674 },
9675
9676 None => pending_completion_container(provider_icon)
9677 .child(Label::new("...").size(LabelSize::Small)),
9678 };
9679
9680 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9681 completion
9682 .with_animation(
9683 "loading-completion",
9684 Animation::new(Duration::from_secs(2))
9685 .repeat()
9686 .with_easing(pulsating_between(0.4, 0.8)),
9687 |label, delta| label.opacity(delta),
9688 )
9689 .into_any_element()
9690 } else {
9691 completion.into_any_element()
9692 };
9693
9694 let has_completion = self.active_edit_prediction.is_some();
9695
9696 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9697 Some(
9698 h_flex()
9699 .min_w(min_width)
9700 .max_w(max_width)
9701 .flex_1()
9702 .elevation_2(cx)
9703 .border_color(cx.theme().colors().border)
9704 .child(
9705 div()
9706 .flex_1()
9707 .py_1()
9708 .px_2()
9709 .overflow_hidden()
9710 .child(completion),
9711 )
9712 .when_some(accept_keystroke, |el, accept_keystroke| {
9713 if !accept_keystroke.modifiers().modified() {
9714 return el;
9715 }
9716
9717 el.child(
9718 h_flex()
9719 .h_full()
9720 .border_l_1()
9721 .rounded_r_lg()
9722 .border_color(cx.theme().colors().border)
9723 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9724 .gap_1()
9725 .py_1()
9726 .px_2()
9727 .child(
9728 h_flex()
9729 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9730 .when(is_platform_style_mac, |parent| parent.gap_1())
9731 .child(h_flex().children(ui::render_modifiers(
9732 accept_keystroke.modifiers(),
9733 PlatformStyle::platform(),
9734 Some(if !has_completion {
9735 Color::Muted
9736 } else {
9737 Color::Default
9738 }),
9739 None,
9740 false,
9741 ))),
9742 )
9743 .child(Label::new("Preview").into_any_element())
9744 .opacity(if has_completion { 1.0 } else { 0.4 }),
9745 )
9746 })
9747 .into_any(),
9748 )
9749 }
9750
9751 fn render_edit_prediction_cursor_popover_preview(
9752 &self,
9753 completion: &EditPredictionState,
9754 cursor_point: Point,
9755 style: &EditorStyle,
9756 cx: &mut Context<Editor>,
9757 ) -> Option<Div> {
9758 use text::ToPoint as _;
9759
9760 fn render_relative_row_jump(
9761 prefix: impl Into<String>,
9762 current_row: u32,
9763 target_row: u32,
9764 ) -> Div {
9765 let (row_diff, arrow) = if target_row < current_row {
9766 (current_row - target_row, IconName::ArrowUp)
9767 } else {
9768 (target_row - current_row, IconName::ArrowDown)
9769 };
9770
9771 h_flex()
9772 .child(
9773 Label::new(format!("{}{}", prefix.into(), row_diff))
9774 .color(Color::Muted)
9775 .size(LabelSize::Small),
9776 )
9777 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9778 }
9779
9780 let supports_jump = self
9781 .edit_prediction_provider
9782 .as_ref()
9783 .map(|provider| provider.provider.supports_jump_to_edit())
9784 .unwrap_or(true);
9785
9786 match &completion.completion {
9787 EditPrediction::MoveWithin {
9788 target, snapshot, ..
9789 } => {
9790 if !supports_jump {
9791 return None;
9792 }
9793
9794 Some(
9795 h_flex()
9796 .px_2()
9797 .gap_2()
9798 .flex_1()
9799 .child(
9800 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9801 Icon::new(IconName::ZedPredictDown)
9802 } else {
9803 Icon::new(IconName::ZedPredictUp)
9804 },
9805 )
9806 .child(Label::new("Jump to Edit")),
9807 )
9808 }
9809 EditPrediction::MoveOutside { snapshot, .. } => {
9810 let file_name = snapshot
9811 .file()
9812 .map(|file| file.file_name(cx))
9813 .unwrap_or("untitled");
9814 Some(
9815 h_flex()
9816 .px_2()
9817 .gap_2()
9818 .flex_1()
9819 .child(Icon::new(IconName::ZedPredict))
9820 .child(Label::new(format!("Jump to {file_name}"))),
9821 )
9822 }
9823 EditPrediction::Edit {
9824 edits,
9825 edit_preview,
9826 snapshot,
9827 display_mode: _,
9828 } => {
9829 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9830
9831 let (highlighted_edits, has_more_lines) =
9832 if let Some(edit_preview) = edit_preview.as_ref() {
9833 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9834 .first_line_preview()
9835 } else {
9836 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9837 };
9838
9839 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9840 .with_default_highlights(&style.text, highlighted_edits.highlights);
9841
9842 let preview = h_flex()
9843 .gap_1()
9844 .min_w_16()
9845 .child(styled_text)
9846 .when(has_more_lines, |parent| parent.child("…"));
9847
9848 let left = if supports_jump && first_edit_row != cursor_point.row {
9849 render_relative_row_jump("", cursor_point.row, first_edit_row)
9850 .into_any_element()
9851 } else {
9852 let icon_name =
9853 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9854 Icon::new(icon_name).into_any_element()
9855 };
9856
9857 Some(
9858 h_flex()
9859 .h_full()
9860 .flex_1()
9861 .gap_2()
9862 .pr_1()
9863 .overflow_x_hidden()
9864 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9865 .child(left)
9866 .child(preview),
9867 )
9868 }
9869 }
9870 }
9871
9872 pub fn render_context_menu(
9873 &self,
9874 style: &EditorStyle,
9875 max_height_in_lines: u32,
9876 window: &mut Window,
9877 cx: &mut Context<Editor>,
9878 ) -> Option<AnyElement> {
9879 let menu = self.context_menu.borrow();
9880 let menu = menu.as_ref()?;
9881 if !menu.visible() {
9882 return None;
9883 };
9884 Some(menu.render(style, max_height_in_lines, window, cx))
9885 }
9886
9887 fn render_context_menu_aside(
9888 &mut self,
9889 max_size: Size<Pixels>,
9890 window: &mut Window,
9891 cx: &mut Context<Editor>,
9892 ) -> Option<AnyElement> {
9893 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9894 if menu.visible() {
9895 menu.render_aside(max_size, window, cx)
9896 } else {
9897 None
9898 }
9899 })
9900 }
9901
9902 fn hide_context_menu(
9903 &mut self,
9904 window: &mut Window,
9905 cx: &mut Context<Self>,
9906 ) -> Option<CodeContextMenu> {
9907 cx.notify();
9908 self.completion_tasks.clear();
9909 let context_menu = self.context_menu.borrow_mut().take();
9910 self.stale_edit_prediction_in_menu.take();
9911 self.update_visible_edit_prediction(window, cx);
9912 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9913 && let Some(completion_provider) = &self.completion_provider
9914 {
9915 completion_provider.selection_changed(None, window, cx);
9916 }
9917 context_menu
9918 }
9919
9920 fn show_snippet_choices(
9921 &mut self,
9922 choices: &Vec<String>,
9923 selection: Range<Anchor>,
9924 cx: &mut Context<Self>,
9925 ) {
9926 let Some((_, buffer, _)) = self
9927 .buffer()
9928 .read(cx)
9929 .excerpt_containing(selection.start, cx)
9930 else {
9931 return;
9932 };
9933 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9934 else {
9935 return;
9936 };
9937 if buffer != end_buffer {
9938 log::error!("expected anchor range to have matching buffer IDs");
9939 return;
9940 }
9941
9942 let id = post_inc(&mut self.next_completion_id);
9943 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9944 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9945 CompletionsMenu::new_snippet_choices(
9946 id,
9947 true,
9948 choices,
9949 selection,
9950 buffer,
9951 snippet_sort_order,
9952 ),
9953 ));
9954 }
9955
9956 pub fn insert_snippet(
9957 &mut self,
9958 insertion_ranges: &[Range<MultiBufferOffset>],
9959 snippet: Snippet,
9960 window: &mut Window,
9961 cx: &mut Context<Self>,
9962 ) -> Result<()> {
9963 struct Tabstop<T> {
9964 is_end_tabstop: bool,
9965 ranges: Vec<Range<T>>,
9966 choices: Option<Vec<String>>,
9967 }
9968
9969 let tabstops = self.buffer.update(cx, |buffer, cx| {
9970 let snippet_text: Arc<str> = snippet.text.clone().into();
9971 let edits = insertion_ranges
9972 .iter()
9973 .cloned()
9974 .map(|range| (range, snippet_text.clone()));
9975 let autoindent_mode = AutoindentMode::Block {
9976 original_indent_columns: Vec::new(),
9977 };
9978 buffer.edit(edits, Some(autoindent_mode), cx);
9979
9980 let snapshot = &*buffer.read(cx);
9981 let snippet = &snippet;
9982 snippet
9983 .tabstops
9984 .iter()
9985 .map(|tabstop| {
9986 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9987 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9988 });
9989 let mut tabstop_ranges = tabstop
9990 .ranges
9991 .iter()
9992 .flat_map(|tabstop_range| {
9993 let mut delta = 0_isize;
9994 insertion_ranges.iter().map(move |insertion_range| {
9995 let insertion_start = insertion_range.start + delta;
9996 delta += snippet.text.len() as isize
9997 - (insertion_range.end - insertion_range.start) as isize;
9998
9999 let start =
10000 (insertion_start + tabstop_range.start).min(snapshot.len());
10001 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10002 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10003 })
10004 })
10005 .collect::<Vec<_>>();
10006 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10007
10008 Tabstop {
10009 is_end_tabstop,
10010 ranges: tabstop_ranges,
10011 choices: tabstop.choices.clone(),
10012 }
10013 })
10014 .collect::<Vec<_>>()
10015 });
10016 if let Some(tabstop) = tabstops.first() {
10017 self.change_selections(Default::default(), window, cx, |s| {
10018 // Reverse order so that the first range is the newest created selection.
10019 // Completions will use it and autoscroll will prioritize it.
10020 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10021 });
10022
10023 if let Some(choices) = &tabstop.choices
10024 && let Some(selection) = tabstop.ranges.first()
10025 {
10026 self.show_snippet_choices(choices, selection.clone(), cx)
10027 }
10028
10029 // If we're already at the last tabstop and it's at the end of the snippet,
10030 // we're done, we don't need to keep the state around.
10031 if !tabstop.is_end_tabstop {
10032 let choices = tabstops
10033 .iter()
10034 .map(|tabstop| tabstop.choices.clone())
10035 .collect();
10036
10037 let ranges = tabstops
10038 .into_iter()
10039 .map(|tabstop| tabstop.ranges)
10040 .collect::<Vec<_>>();
10041
10042 self.snippet_stack.push(SnippetState {
10043 active_index: 0,
10044 ranges,
10045 choices,
10046 });
10047 }
10048
10049 // Check whether the just-entered snippet ends with an auto-closable bracket.
10050 if self.autoclose_regions.is_empty() {
10051 let snapshot = self.buffer.read(cx).snapshot(cx);
10052 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10053 let selection_head = selection.head();
10054 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10055 continue;
10056 };
10057
10058 let mut bracket_pair = None;
10059 let max_lookup_length = scope
10060 .brackets()
10061 .map(|(pair, _)| {
10062 pair.start
10063 .as_str()
10064 .chars()
10065 .count()
10066 .max(pair.end.as_str().chars().count())
10067 })
10068 .max();
10069 if let Some(max_lookup_length) = max_lookup_length {
10070 let next_text = snapshot
10071 .chars_at(selection_head)
10072 .take(max_lookup_length)
10073 .collect::<String>();
10074 let prev_text = snapshot
10075 .reversed_chars_at(selection_head)
10076 .take(max_lookup_length)
10077 .collect::<String>();
10078
10079 for (pair, enabled) in scope.brackets() {
10080 if enabled
10081 && pair.close
10082 && prev_text.starts_with(pair.start.as_str())
10083 && next_text.starts_with(pair.end.as_str())
10084 {
10085 bracket_pair = Some(pair.clone());
10086 break;
10087 }
10088 }
10089 }
10090
10091 if let Some(pair) = bracket_pair {
10092 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10093 let autoclose_enabled =
10094 self.use_autoclose && snapshot_settings.use_autoclose;
10095 if autoclose_enabled {
10096 let start = snapshot.anchor_after(selection_head);
10097 let end = snapshot.anchor_after(selection_head);
10098 self.autoclose_regions.push(AutocloseRegion {
10099 selection_id: selection.id,
10100 range: start..end,
10101 pair,
10102 });
10103 }
10104 }
10105 }
10106 }
10107 }
10108 Ok(())
10109 }
10110
10111 pub fn move_to_next_snippet_tabstop(
10112 &mut self,
10113 window: &mut Window,
10114 cx: &mut Context<Self>,
10115 ) -> bool {
10116 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10117 }
10118
10119 pub fn move_to_prev_snippet_tabstop(
10120 &mut self,
10121 window: &mut Window,
10122 cx: &mut Context<Self>,
10123 ) -> bool {
10124 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10125 }
10126
10127 pub fn move_to_snippet_tabstop(
10128 &mut self,
10129 bias: Bias,
10130 window: &mut Window,
10131 cx: &mut Context<Self>,
10132 ) -> bool {
10133 if let Some(mut snippet) = self.snippet_stack.pop() {
10134 match bias {
10135 Bias::Left => {
10136 if snippet.active_index > 0 {
10137 snippet.active_index -= 1;
10138 } else {
10139 self.snippet_stack.push(snippet);
10140 return false;
10141 }
10142 }
10143 Bias::Right => {
10144 if snippet.active_index + 1 < snippet.ranges.len() {
10145 snippet.active_index += 1;
10146 } else {
10147 self.snippet_stack.push(snippet);
10148 return false;
10149 }
10150 }
10151 }
10152 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10153 self.change_selections(Default::default(), window, cx, |s| {
10154 // Reverse order so that the first range is the newest created selection.
10155 // Completions will use it and autoscroll will prioritize it.
10156 s.select_ranges(current_ranges.iter().rev().cloned())
10157 });
10158
10159 if let Some(choices) = &snippet.choices[snippet.active_index]
10160 && let Some(selection) = current_ranges.first()
10161 {
10162 self.show_snippet_choices(choices, selection.clone(), cx);
10163 }
10164
10165 // If snippet state is not at the last tabstop, push it back on the stack
10166 if snippet.active_index + 1 < snippet.ranges.len() {
10167 self.snippet_stack.push(snippet);
10168 }
10169 return true;
10170 }
10171 }
10172
10173 false
10174 }
10175
10176 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10177 self.transact(window, cx, |this, window, cx| {
10178 this.select_all(&SelectAll, window, cx);
10179 this.insert("", window, cx);
10180 });
10181 }
10182
10183 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10184 if self.read_only(cx) {
10185 return;
10186 }
10187 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10188 self.transact(window, cx, |this, window, cx| {
10189 this.select_autoclose_pair(window, cx);
10190
10191 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10192
10193 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10194 if !this.linked_edit_ranges.is_empty() {
10195 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10196 let snapshot = this.buffer.read(cx).snapshot(cx);
10197
10198 for selection in selections.iter() {
10199 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10200 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10201 if selection_start.buffer_id != selection_end.buffer_id {
10202 continue;
10203 }
10204 if let Some(ranges) =
10205 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10206 {
10207 for (buffer, entries) in ranges {
10208 linked_ranges.entry(buffer).or_default().extend(entries);
10209 }
10210 }
10211 }
10212 }
10213
10214 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10215 for selection in &mut selections {
10216 if selection.is_empty() {
10217 let old_head = selection.head();
10218 let mut new_head =
10219 movement::left(&display_map, old_head.to_display_point(&display_map))
10220 .to_point(&display_map);
10221 if let Some((buffer, line_buffer_range)) = display_map
10222 .buffer_snapshot()
10223 .buffer_line_for_row(MultiBufferRow(old_head.row))
10224 {
10225 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10226 let indent_len = match indent_size.kind {
10227 IndentKind::Space => {
10228 buffer.settings_at(line_buffer_range.start, cx).tab_size
10229 }
10230 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10231 };
10232 if old_head.column <= indent_size.len && old_head.column > 0 {
10233 let indent_len = indent_len.get();
10234 new_head = cmp::min(
10235 new_head,
10236 MultiBufferPoint::new(
10237 old_head.row,
10238 ((old_head.column - 1) / indent_len) * indent_len,
10239 ),
10240 );
10241 }
10242 }
10243
10244 selection.set_head(new_head, SelectionGoal::None);
10245 }
10246 }
10247
10248 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10249 this.insert("", window, cx);
10250 let empty_str: Arc<str> = Arc::from("");
10251 for (buffer, edits) in linked_ranges {
10252 let snapshot = buffer.read(cx).snapshot();
10253 use text::ToPoint as TP;
10254
10255 let edits = edits
10256 .into_iter()
10257 .map(|range| {
10258 let end_point = TP::to_point(&range.end, &snapshot);
10259 let mut start_point = TP::to_point(&range.start, &snapshot);
10260
10261 if end_point == start_point {
10262 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10263 .saturating_sub(1);
10264 start_point =
10265 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10266 };
10267
10268 (start_point..end_point, empty_str.clone())
10269 })
10270 .sorted_by_key(|(range, _)| range.start)
10271 .collect::<Vec<_>>();
10272 buffer.update(cx, |this, cx| {
10273 this.edit(edits, None, cx);
10274 })
10275 }
10276 this.refresh_edit_prediction(true, false, window, cx);
10277 refresh_linked_ranges(this, window, cx);
10278 });
10279 }
10280
10281 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10282 if self.read_only(cx) {
10283 return;
10284 }
10285 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10286 self.transact(window, cx, |this, window, cx| {
10287 this.change_selections(Default::default(), window, cx, |s| {
10288 s.move_with(|map, selection| {
10289 if selection.is_empty() {
10290 let cursor = movement::right(map, selection.head());
10291 selection.end = cursor;
10292 selection.reversed = true;
10293 selection.goal = SelectionGoal::None;
10294 }
10295 })
10296 });
10297 this.insert("", window, cx);
10298 this.refresh_edit_prediction(true, false, window, cx);
10299 });
10300 }
10301
10302 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10303 if self.mode.is_single_line() {
10304 cx.propagate();
10305 return;
10306 }
10307
10308 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10309 if self.move_to_prev_snippet_tabstop(window, cx) {
10310 return;
10311 }
10312 self.outdent(&Outdent, window, cx);
10313 }
10314
10315 pub fn next_snippet_tabstop(
10316 &mut self,
10317 _: &NextSnippetTabstop,
10318 window: &mut Window,
10319 cx: &mut Context<Self>,
10320 ) {
10321 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10322 cx.propagate();
10323 return;
10324 }
10325
10326 if self.move_to_next_snippet_tabstop(window, cx) {
10327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10328 return;
10329 }
10330 cx.propagate();
10331 }
10332
10333 pub fn previous_snippet_tabstop(
10334 &mut self,
10335 _: &PreviousSnippetTabstop,
10336 window: &mut Window,
10337 cx: &mut Context<Self>,
10338 ) {
10339 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10340 cx.propagate();
10341 return;
10342 }
10343
10344 if self.move_to_prev_snippet_tabstop(window, cx) {
10345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10346 return;
10347 }
10348 cx.propagate();
10349 }
10350
10351 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10352 if self.mode.is_single_line() {
10353 cx.propagate();
10354 return;
10355 }
10356
10357 if self.move_to_next_snippet_tabstop(window, cx) {
10358 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10359 return;
10360 }
10361 if self.read_only(cx) {
10362 return;
10363 }
10364 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10365 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10366 let buffer = self.buffer.read(cx);
10367 let snapshot = buffer.snapshot(cx);
10368 let rows_iter = selections.iter().map(|s| s.head().row);
10369 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10370
10371 let has_some_cursor_in_whitespace = selections
10372 .iter()
10373 .filter(|selection| selection.is_empty())
10374 .any(|selection| {
10375 let cursor = selection.head();
10376 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10377 cursor.column < current_indent.len
10378 });
10379
10380 let mut edits = Vec::new();
10381 let mut prev_edited_row = 0;
10382 let mut row_delta = 0;
10383 for selection in &mut selections {
10384 if selection.start.row != prev_edited_row {
10385 row_delta = 0;
10386 }
10387 prev_edited_row = selection.end.row;
10388
10389 // If the selection is non-empty, then increase the indentation of the selected lines.
10390 if !selection.is_empty() {
10391 row_delta =
10392 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10393 continue;
10394 }
10395
10396 let cursor = selection.head();
10397 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10398 if let Some(suggested_indent) =
10399 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10400 {
10401 // Don't do anything if already at suggested indent
10402 // and there is any other cursor which is not
10403 if has_some_cursor_in_whitespace
10404 && cursor.column == current_indent.len
10405 && current_indent.len == suggested_indent.len
10406 {
10407 continue;
10408 }
10409
10410 // Adjust line and move cursor to suggested indent
10411 // if cursor is not at suggested indent
10412 if cursor.column < suggested_indent.len
10413 && cursor.column <= current_indent.len
10414 && current_indent.len <= suggested_indent.len
10415 {
10416 selection.start = Point::new(cursor.row, suggested_indent.len);
10417 selection.end = selection.start;
10418 if row_delta == 0 {
10419 edits.extend(Buffer::edit_for_indent_size_adjustment(
10420 cursor.row,
10421 current_indent,
10422 suggested_indent,
10423 ));
10424 row_delta = suggested_indent.len - current_indent.len;
10425 }
10426 continue;
10427 }
10428
10429 // If current indent is more than suggested indent
10430 // only move cursor to current indent and skip indent
10431 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10432 selection.start = Point::new(cursor.row, current_indent.len);
10433 selection.end = selection.start;
10434 continue;
10435 }
10436 }
10437
10438 // Otherwise, insert a hard or soft tab.
10439 let settings = buffer.language_settings_at(cursor, cx);
10440 let tab_size = if settings.hard_tabs {
10441 IndentSize::tab()
10442 } else {
10443 let tab_size = settings.tab_size.get();
10444 let indent_remainder = snapshot
10445 .text_for_range(Point::new(cursor.row, 0)..cursor)
10446 .flat_map(str::chars)
10447 .fold(row_delta % tab_size, |counter: u32, c| {
10448 if c == '\t' {
10449 0
10450 } else {
10451 (counter + 1) % tab_size
10452 }
10453 });
10454
10455 let chars_to_next_tab_stop = tab_size - indent_remainder;
10456 IndentSize::spaces(chars_to_next_tab_stop)
10457 };
10458 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10459 selection.end = selection.start;
10460 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10461 row_delta += tab_size.len;
10462 }
10463
10464 self.transact(window, cx, |this, window, cx| {
10465 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10466 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10467 this.refresh_edit_prediction(true, false, window, cx);
10468 });
10469 }
10470
10471 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10472 if self.read_only(cx) {
10473 return;
10474 }
10475 if self.mode.is_single_line() {
10476 cx.propagate();
10477 return;
10478 }
10479
10480 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10481 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10482 let mut prev_edited_row = 0;
10483 let mut row_delta = 0;
10484 let mut edits = Vec::new();
10485 let buffer = self.buffer.read(cx);
10486 let snapshot = buffer.snapshot(cx);
10487 for selection in &mut selections {
10488 if selection.start.row != prev_edited_row {
10489 row_delta = 0;
10490 }
10491 prev_edited_row = selection.end.row;
10492
10493 row_delta =
10494 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10495 }
10496
10497 self.transact(window, cx, |this, window, cx| {
10498 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10499 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10500 });
10501 }
10502
10503 fn indent_selection(
10504 buffer: &MultiBuffer,
10505 snapshot: &MultiBufferSnapshot,
10506 selection: &mut Selection<Point>,
10507 edits: &mut Vec<(Range<Point>, String)>,
10508 delta_for_start_row: u32,
10509 cx: &App,
10510 ) -> u32 {
10511 let settings = buffer.language_settings_at(selection.start, cx);
10512 let tab_size = settings.tab_size.get();
10513 let indent_kind = if settings.hard_tabs {
10514 IndentKind::Tab
10515 } else {
10516 IndentKind::Space
10517 };
10518 let mut start_row = selection.start.row;
10519 let mut end_row = selection.end.row + 1;
10520
10521 // If a selection ends at the beginning of a line, don't indent
10522 // that last line.
10523 if selection.end.column == 0 && selection.end.row > selection.start.row {
10524 end_row -= 1;
10525 }
10526
10527 // Avoid re-indenting a row that has already been indented by a
10528 // previous selection, but still update this selection's column
10529 // to reflect that indentation.
10530 if delta_for_start_row > 0 {
10531 start_row += 1;
10532 selection.start.column += delta_for_start_row;
10533 if selection.end.row == selection.start.row {
10534 selection.end.column += delta_for_start_row;
10535 }
10536 }
10537
10538 let mut delta_for_end_row = 0;
10539 let has_multiple_rows = start_row + 1 != end_row;
10540 for row in start_row..end_row {
10541 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10542 let indent_delta = match (current_indent.kind, indent_kind) {
10543 (IndentKind::Space, IndentKind::Space) => {
10544 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10545 IndentSize::spaces(columns_to_next_tab_stop)
10546 }
10547 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10548 (_, IndentKind::Tab) => IndentSize::tab(),
10549 };
10550
10551 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10552 0
10553 } else {
10554 selection.start.column
10555 };
10556 let row_start = Point::new(row, start);
10557 edits.push((
10558 row_start..row_start,
10559 indent_delta.chars().collect::<String>(),
10560 ));
10561
10562 // Update this selection's endpoints to reflect the indentation.
10563 if row == selection.start.row {
10564 selection.start.column += indent_delta.len;
10565 }
10566 if row == selection.end.row {
10567 selection.end.column += indent_delta.len;
10568 delta_for_end_row = indent_delta.len;
10569 }
10570 }
10571
10572 if selection.start.row == selection.end.row {
10573 delta_for_start_row + delta_for_end_row
10574 } else {
10575 delta_for_end_row
10576 }
10577 }
10578
10579 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10580 if self.read_only(cx) {
10581 return;
10582 }
10583 if self.mode.is_single_line() {
10584 cx.propagate();
10585 return;
10586 }
10587
10588 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10589 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10590 let selections = self.selections.all::<Point>(&display_map);
10591 let mut deletion_ranges = Vec::new();
10592 let mut last_outdent = None;
10593 {
10594 let buffer = self.buffer.read(cx);
10595 let snapshot = buffer.snapshot(cx);
10596 for selection in &selections {
10597 let settings = buffer.language_settings_at(selection.start, cx);
10598 let tab_size = settings.tab_size.get();
10599 let mut rows = selection.spanned_rows(false, &display_map);
10600
10601 // Avoid re-outdenting a row that has already been outdented by a
10602 // previous selection.
10603 if let Some(last_row) = last_outdent
10604 && last_row == rows.start
10605 {
10606 rows.start = rows.start.next_row();
10607 }
10608 let has_multiple_rows = rows.len() > 1;
10609 for row in rows.iter_rows() {
10610 let indent_size = snapshot.indent_size_for_line(row);
10611 if indent_size.len > 0 {
10612 let deletion_len = match indent_size.kind {
10613 IndentKind::Space => {
10614 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10615 if columns_to_prev_tab_stop == 0 {
10616 tab_size
10617 } else {
10618 columns_to_prev_tab_stop
10619 }
10620 }
10621 IndentKind::Tab => 1,
10622 };
10623 let start = if has_multiple_rows
10624 || deletion_len > selection.start.column
10625 || indent_size.len < selection.start.column
10626 {
10627 0
10628 } else {
10629 selection.start.column - deletion_len
10630 };
10631 deletion_ranges.push(
10632 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10633 );
10634 last_outdent = Some(row);
10635 }
10636 }
10637 }
10638 }
10639
10640 self.transact(window, cx, |this, window, cx| {
10641 this.buffer.update(cx, |buffer, cx| {
10642 let empty_str: Arc<str> = Arc::default();
10643 buffer.edit(
10644 deletion_ranges
10645 .into_iter()
10646 .map(|range| (range, empty_str.clone())),
10647 None,
10648 cx,
10649 );
10650 });
10651 let selections = this
10652 .selections
10653 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10654 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10655 });
10656 }
10657
10658 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10659 if self.read_only(cx) {
10660 return;
10661 }
10662 if self.mode.is_single_line() {
10663 cx.propagate();
10664 return;
10665 }
10666
10667 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10668 let selections = self
10669 .selections
10670 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10671 .into_iter()
10672 .map(|s| s.range());
10673
10674 self.transact(window, cx, |this, window, cx| {
10675 this.buffer.update(cx, |buffer, cx| {
10676 buffer.autoindent_ranges(selections, cx);
10677 });
10678 let selections = this
10679 .selections
10680 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10681 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10682 });
10683 }
10684
10685 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10686 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10687 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10688 let selections = self.selections.all::<Point>(&display_map);
10689
10690 let mut new_cursors = Vec::new();
10691 let mut edit_ranges = Vec::new();
10692 let mut selections = selections.iter().peekable();
10693 while let Some(selection) = selections.next() {
10694 let mut rows = selection.spanned_rows(false, &display_map);
10695
10696 // Accumulate contiguous regions of rows that we want to delete.
10697 while let Some(next_selection) = selections.peek() {
10698 let next_rows = next_selection.spanned_rows(false, &display_map);
10699 if next_rows.start <= rows.end {
10700 rows.end = next_rows.end;
10701 selections.next().unwrap();
10702 } else {
10703 break;
10704 }
10705 }
10706
10707 let buffer = display_map.buffer_snapshot();
10708 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10709 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10710 // If there's a line after the range, delete the \n from the end of the row range
10711 (
10712 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10713 rows.end,
10714 )
10715 } else {
10716 // If there isn't a line after the range, delete the \n from the line before the
10717 // start of the row range
10718 edit_start = edit_start.saturating_sub_usize(1);
10719 (buffer.len(), rows.start.previous_row())
10720 };
10721
10722 let text_layout_details = self.text_layout_details(window);
10723 let x = display_map.x_for_display_point(
10724 selection.head().to_display_point(&display_map),
10725 &text_layout_details,
10726 );
10727 let row = Point::new(target_row.0, 0)
10728 .to_display_point(&display_map)
10729 .row();
10730 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10731
10732 new_cursors.push((
10733 selection.id,
10734 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10735 SelectionGoal::None,
10736 ));
10737 edit_ranges.push(edit_start..edit_end);
10738 }
10739
10740 self.transact(window, cx, |this, window, cx| {
10741 let buffer = this.buffer.update(cx, |buffer, cx| {
10742 let empty_str: Arc<str> = Arc::default();
10743 buffer.edit(
10744 edit_ranges
10745 .into_iter()
10746 .map(|range| (range, empty_str.clone())),
10747 None,
10748 cx,
10749 );
10750 buffer.snapshot(cx)
10751 });
10752 let new_selections = new_cursors
10753 .into_iter()
10754 .map(|(id, cursor, goal)| {
10755 let cursor = cursor.to_point(&buffer);
10756 Selection {
10757 id,
10758 start: cursor,
10759 end: cursor,
10760 reversed: false,
10761 goal,
10762 }
10763 })
10764 .collect();
10765
10766 this.change_selections(Default::default(), window, cx, |s| {
10767 s.select(new_selections);
10768 });
10769 });
10770 }
10771
10772 pub fn join_lines_impl(
10773 &mut self,
10774 insert_whitespace: bool,
10775 window: &mut Window,
10776 cx: &mut Context<Self>,
10777 ) {
10778 if self.read_only(cx) {
10779 return;
10780 }
10781 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10782 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10783 let start = MultiBufferRow(selection.start.row);
10784 // Treat single line selections as if they include the next line. Otherwise this action
10785 // would do nothing for single line selections individual cursors.
10786 let end = if selection.start.row == selection.end.row {
10787 MultiBufferRow(selection.start.row + 1)
10788 } else {
10789 MultiBufferRow(selection.end.row)
10790 };
10791
10792 if let Some(last_row_range) = row_ranges.last_mut()
10793 && start <= last_row_range.end
10794 {
10795 last_row_range.end = end;
10796 continue;
10797 }
10798 row_ranges.push(start..end);
10799 }
10800
10801 let snapshot = self.buffer.read(cx).snapshot(cx);
10802 let mut cursor_positions = Vec::new();
10803 for row_range in &row_ranges {
10804 let anchor = snapshot.anchor_before(Point::new(
10805 row_range.end.previous_row().0,
10806 snapshot.line_len(row_range.end.previous_row()),
10807 ));
10808 cursor_positions.push(anchor..anchor);
10809 }
10810
10811 self.transact(window, cx, |this, window, cx| {
10812 for row_range in row_ranges.into_iter().rev() {
10813 for row in row_range.iter_rows().rev() {
10814 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10815 let next_line_row = row.next_row();
10816 let indent = snapshot.indent_size_for_line(next_line_row);
10817 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10818
10819 let replace =
10820 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10821 " "
10822 } else {
10823 ""
10824 };
10825
10826 this.buffer.update(cx, |buffer, cx| {
10827 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10828 });
10829 }
10830 }
10831
10832 this.change_selections(Default::default(), window, cx, |s| {
10833 s.select_anchor_ranges(cursor_positions)
10834 });
10835 });
10836 }
10837
10838 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10839 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10840 self.join_lines_impl(true, window, cx);
10841 }
10842
10843 pub fn sort_lines_case_sensitive(
10844 &mut self,
10845 _: &SortLinesCaseSensitive,
10846 window: &mut Window,
10847 cx: &mut Context<Self>,
10848 ) {
10849 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10850 }
10851
10852 pub fn sort_lines_by_length(
10853 &mut self,
10854 _: &SortLinesByLength,
10855 window: &mut Window,
10856 cx: &mut Context<Self>,
10857 ) {
10858 self.manipulate_immutable_lines(window, cx, |lines| {
10859 lines.sort_by_key(|&line| line.chars().count())
10860 })
10861 }
10862
10863 pub fn sort_lines_case_insensitive(
10864 &mut self,
10865 _: &SortLinesCaseInsensitive,
10866 window: &mut Window,
10867 cx: &mut Context<Self>,
10868 ) {
10869 self.manipulate_immutable_lines(window, cx, |lines| {
10870 lines.sort_by_key(|line| line.to_lowercase())
10871 })
10872 }
10873
10874 pub fn unique_lines_case_insensitive(
10875 &mut self,
10876 _: &UniqueLinesCaseInsensitive,
10877 window: &mut Window,
10878 cx: &mut Context<Self>,
10879 ) {
10880 self.manipulate_immutable_lines(window, cx, |lines| {
10881 let mut seen = HashSet::default();
10882 lines.retain(|line| seen.insert(line.to_lowercase()));
10883 })
10884 }
10885
10886 pub fn unique_lines_case_sensitive(
10887 &mut self,
10888 _: &UniqueLinesCaseSensitive,
10889 window: &mut Window,
10890 cx: &mut Context<Self>,
10891 ) {
10892 self.manipulate_immutable_lines(window, cx, |lines| {
10893 let mut seen = HashSet::default();
10894 lines.retain(|line| seen.insert(*line));
10895 })
10896 }
10897
10898 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10899 let snapshot = self.buffer.read(cx).snapshot(cx);
10900 for selection in self.selections.disjoint_anchors_arc().iter() {
10901 if snapshot
10902 .language_at(selection.start)
10903 .and_then(|lang| lang.config().wrap_characters.as_ref())
10904 .is_some()
10905 {
10906 return true;
10907 }
10908 }
10909 false
10910 }
10911
10912 fn wrap_selections_in_tag(
10913 &mut self,
10914 _: &WrapSelectionsInTag,
10915 window: &mut Window,
10916 cx: &mut Context<Self>,
10917 ) {
10918 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10919
10920 let snapshot = self.buffer.read(cx).snapshot(cx);
10921
10922 let mut edits = Vec::new();
10923 let mut boundaries = Vec::new();
10924
10925 for selection in self
10926 .selections
10927 .all_adjusted(&self.display_snapshot(cx))
10928 .iter()
10929 {
10930 let Some(wrap_config) = snapshot
10931 .language_at(selection.start)
10932 .and_then(|lang| lang.config().wrap_characters.clone())
10933 else {
10934 continue;
10935 };
10936
10937 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10938 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10939
10940 let start_before = snapshot.anchor_before(selection.start);
10941 let end_after = snapshot.anchor_after(selection.end);
10942
10943 edits.push((start_before..start_before, open_tag));
10944 edits.push((end_after..end_after, close_tag));
10945
10946 boundaries.push((
10947 start_before,
10948 end_after,
10949 wrap_config.start_prefix.len(),
10950 wrap_config.end_suffix.len(),
10951 ));
10952 }
10953
10954 if edits.is_empty() {
10955 return;
10956 }
10957
10958 self.transact(window, cx, |this, window, cx| {
10959 let buffer = this.buffer.update(cx, |buffer, cx| {
10960 buffer.edit(edits, None, cx);
10961 buffer.snapshot(cx)
10962 });
10963
10964 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10965 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10966 boundaries.into_iter()
10967 {
10968 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10969 let close_offset = end_after
10970 .to_offset(&buffer)
10971 .saturating_sub_usize(end_suffix_len);
10972 new_selections.push(open_offset..open_offset);
10973 new_selections.push(close_offset..close_offset);
10974 }
10975
10976 this.change_selections(Default::default(), window, cx, |s| {
10977 s.select_ranges(new_selections);
10978 });
10979
10980 this.request_autoscroll(Autoscroll::fit(), cx);
10981 });
10982 }
10983
10984 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10985 let Some(project) = self.project.clone() else {
10986 return;
10987 };
10988 self.reload(project, window, cx)
10989 .detach_and_notify_err(window, cx);
10990 }
10991
10992 pub fn restore_file(
10993 &mut self,
10994 _: &::git::RestoreFile,
10995 window: &mut Window,
10996 cx: &mut Context<Self>,
10997 ) {
10998 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10999 let mut buffer_ids = HashSet::default();
11000 let snapshot = self.buffer().read(cx).snapshot(cx);
11001 for selection in self
11002 .selections
11003 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11004 {
11005 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11006 }
11007
11008 let buffer = self.buffer().read(cx);
11009 let ranges = buffer_ids
11010 .into_iter()
11011 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11012 .collect::<Vec<_>>();
11013
11014 self.restore_hunks_in_ranges(ranges, window, cx);
11015 }
11016
11017 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11019 let selections = self
11020 .selections
11021 .all(&self.display_snapshot(cx))
11022 .into_iter()
11023 .map(|s| s.range())
11024 .collect();
11025 self.restore_hunks_in_ranges(selections, window, cx);
11026 }
11027
11028 pub fn restore_hunks_in_ranges(
11029 &mut self,
11030 ranges: Vec<Range<Point>>,
11031 window: &mut Window,
11032 cx: &mut Context<Editor>,
11033 ) {
11034 let mut revert_changes = HashMap::default();
11035 let chunk_by = self
11036 .snapshot(window, cx)
11037 .hunks_for_ranges(ranges)
11038 .into_iter()
11039 .chunk_by(|hunk| hunk.buffer_id);
11040 for (buffer_id, hunks) in &chunk_by {
11041 let hunks = hunks.collect::<Vec<_>>();
11042 for hunk in &hunks {
11043 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11044 }
11045 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11046 }
11047 drop(chunk_by);
11048 if !revert_changes.is_empty() {
11049 self.transact(window, cx, |editor, window, cx| {
11050 editor.restore(revert_changes, window, cx);
11051 });
11052 }
11053 }
11054
11055 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11056 if let Some(status) = self
11057 .addons
11058 .iter()
11059 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11060 {
11061 return Some(status);
11062 }
11063 self.project
11064 .as_ref()?
11065 .read(cx)
11066 .status_for_buffer_id(buffer_id, cx)
11067 }
11068
11069 pub fn open_active_item_in_terminal(
11070 &mut self,
11071 _: &OpenInTerminal,
11072 window: &mut Window,
11073 cx: &mut Context<Self>,
11074 ) {
11075 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11076 let project_path = buffer.read(cx).project_path(cx)?;
11077 let project = self.project()?.read(cx);
11078 let entry = project.entry_for_path(&project_path, cx)?;
11079 let parent = match &entry.canonical_path {
11080 Some(canonical_path) => canonical_path.to_path_buf(),
11081 None => project.absolute_path(&project_path, cx)?,
11082 }
11083 .parent()?
11084 .to_path_buf();
11085 Some(parent)
11086 }) {
11087 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11088 }
11089 }
11090
11091 fn set_breakpoint_context_menu(
11092 &mut self,
11093 display_row: DisplayRow,
11094 position: Option<Anchor>,
11095 clicked_point: gpui::Point<Pixels>,
11096 window: &mut Window,
11097 cx: &mut Context<Self>,
11098 ) {
11099 let source = self
11100 .buffer
11101 .read(cx)
11102 .snapshot(cx)
11103 .anchor_before(Point::new(display_row.0, 0u32));
11104
11105 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11106
11107 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11108 self,
11109 source,
11110 clicked_point,
11111 context_menu,
11112 window,
11113 cx,
11114 );
11115 }
11116
11117 fn add_edit_breakpoint_block(
11118 &mut self,
11119 anchor: Anchor,
11120 breakpoint: &Breakpoint,
11121 edit_action: BreakpointPromptEditAction,
11122 window: &mut Window,
11123 cx: &mut Context<Self>,
11124 ) {
11125 let weak_editor = cx.weak_entity();
11126 let bp_prompt = cx.new(|cx| {
11127 BreakpointPromptEditor::new(
11128 weak_editor,
11129 anchor,
11130 breakpoint.clone(),
11131 edit_action,
11132 window,
11133 cx,
11134 )
11135 });
11136
11137 let height = bp_prompt.update(cx, |this, cx| {
11138 this.prompt
11139 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11140 });
11141 let cloned_prompt = bp_prompt.clone();
11142 let blocks = vec![BlockProperties {
11143 style: BlockStyle::Sticky,
11144 placement: BlockPlacement::Above(anchor),
11145 height: Some(height),
11146 render: Arc::new(move |cx| {
11147 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11148 cloned_prompt.clone().into_any_element()
11149 }),
11150 priority: 0,
11151 }];
11152
11153 let focus_handle = bp_prompt.focus_handle(cx);
11154 window.focus(&focus_handle);
11155
11156 let block_ids = self.insert_blocks(blocks, None, cx);
11157 bp_prompt.update(cx, |prompt, _| {
11158 prompt.add_block_ids(block_ids);
11159 });
11160 }
11161
11162 pub(crate) fn breakpoint_at_row(
11163 &self,
11164 row: u32,
11165 window: &mut Window,
11166 cx: &mut Context<Self>,
11167 ) -> Option<(Anchor, Breakpoint)> {
11168 let snapshot = self.snapshot(window, cx);
11169 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11170
11171 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11172 }
11173
11174 pub(crate) fn breakpoint_at_anchor(
11175 &self,
11176 breakpoint_position: Anchor,
11177 snapshot: &EditorSnapshot,
11178 cx: &mut Context<Self>,
11179 ) -> Option<(Anchor, Breakpoint)> {
11180 let buffer = self
11181 .buffer
11182 .read(cx)
11183 .buffer_for_anchor(breakpoint_position, cx)?;
11184
11185 let enclosing_excerpt = breakpoint_position.excerpt_id;
11186 let buffer_snapshot = buffer.read(cx).snapshot();
11187
11188 let row = buffer_snapshot
11189 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11190 .row;
11191
11192 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11193 let anchor_end = snapshot
11194 .buffer_snapshot()
11195 .anchor_after(Point::new(row, line_len));
11196
11197 self.breakpoint_store
11198 .as_ref()?
11199 .read_with(cx, |breakpoint_store, cx| {
11200 breakpoint_store
11201 .breakpoints(
11202 &buffer,
11203 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11204 &buffer_snapshot,
11205 cx,
11206 )
11207 .next()
11208 .and_then(|(bp, _)| {
11209 let breakpoint_row = buffer_snapshot
11210 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11211 .row;
11212
11213 if breakpoint_row == row {
11214 snapshot
11215 .buffer_snapshot()
11216 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11217 .map(|position| (position, bp.bp.clone()))
11218 } else {
11219 None
11220 }
11221 })
11222 })
11223 }
11224
11225 pub fn edit_log_breakpoint(
11226 &mut self,
11227 _: &EditLogBreakpoint,
11228 window: &mut Window,
11229 cx: &mut Context<Self>,
11230 ) {
11231 if self.breakpoint_store.is_none() {
11232 return;
11233 }
11234
11235 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11236 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11237 message: None,
11238 state: BreakpointState::Enabled,
11239 condition: None,
11240 hit_condition: None,
11241 });
11242
11243 self.add_edit_breakpoint_block(
11244 anchor,
11245 &breakpoint,
11246 BreakpointPromptEditAction::Log,
11247 window,
11248 cx,
11249 );
11250 }
11251 }
11252
11253 fn breakpoints_at_cursors(
11254 &self,
11255 window: &mut Window,
11256 cx: &mut Context<Self>,
11257 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11258 let snapshot = self.snapshot(window, cx);
11259 let cursors = self
11260 .selections
11261 .disjoint_anchors_arc()
11262 .iter()
11263 .map(|selection| {
11264 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11265
11266 let breakpoint_position = self
11267 .breakpoint_at_row(cursor_position.row, window, cx)
11268 .map(|bp| bp.0)
11269 .unwrap_or_else(|| {
11270 snapshot
11271 .display_snapshot
11272 .buffer_snapshot()
11273 .anchor_after(Point::new(cursor_position.row, 0))
11274 });
11275
11276 let breakpoint = self
11277 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11278 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11279
11280 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11281 })
11282 // 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.
11283 .collect::<HashMap<Anchor, _>>();
11284
11285 cursors.into_iter().collect()
11286 }
11287
11288 pub fn enable_breakpoint(
11289 &mut self,
11290 _: &crate::actions::EnableBreakpoint,
11291 window: &mut Window,
11292 cx: &mut Context<Self>,
11293 ) {
11294 if self.breakpoint_store.is_none() {
11295 return;
11296 }
11297
11298 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11299 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11300 continue;
11301 };
11302 self.edit_breakpoint_at_anchor(
11303 anchor,
11304 breakpoint,
11305 BreakpointEditAction::InvertState,
11306 cx,
11307 );
11308 }
11309 }
11310
11311 pub fn disable_breakpoint(
11312 &mut self,
11313 _: &crate::actions::DisableBreakpoint,
11314 window: &mut Window,
11315 cx: &mut Context<Self>,
11316 ) {
11317 if self.breakpoint_store.is_none() {
11318 return;
11319 }
11320
11321 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11322 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11323 continue;
11324 };
11325 self.edit_breakpoint_at_anchor(
11326 anchor,
11327 breakpoint,
11328 BreakpointEditAction::InvertState,
11329 cx,
11330 );
11331 }
11332 }
11333
11334 pub fn toggle_breakpoint(
11335 &mut self,
11336 _: &crate::actions::ToggleBreakpoint,
11337 window: &mut Window,
11338 cx: &mut Context<Self>,
11339 ) {
11340 if self.breakpoint_store.is_none() {
11341 return;
11342 }
11343
11344 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11345 if let Some(breakpoint) = breakpoint {
11346 self.edit_breakpoint_at_anchor(
11347 anchor,
11348 breakpoint,
11349 BreakpointEditAction::Toggle,
11350 cx,
11351 );
11352 } else {
11353 self.edit_breakpoint_at_anchor(
11354 anchor,
11355 Breakpoint::new_standard(),
11356 BreakpointEditAction::Toggle,
11357 cx,
11358 );
11359 }
11360 }
11361 }
11362
11363 pub fn edit_breakpoint_at_anchor(
11364 &mut self,
11365 breakpoint_position: Anchor,
11366 breakpoint: Breakpoint,
11367 edit_action: BreakpointEditAction,
11368 cx: &mut Context<Self>,
11369 ) {
11370 let Some(breakpoint_store) = &self.breakpoint_store else {
11371 return;
11372 };
11373
11374 let Some(buffer) = self
11375 .buffer
11376 .read(cx)
11377 .buffer_for_anchor(breakpoint_position, cx)
11378 else {
11379 return;
11380 };
11381
11382 breakpoint_store.update(cx, |breakpoint_store, cx| {
11383 breakpoint_store.toggle_breakpoint(
11384 buffer,
11385 BreakpointWithPosition {
11386 position: breakpoint_position.text_anchor,
11387 bp: breakpoint,
11388 },
11389 edit_action,
11390 cx,
11391 );
11392 });
11393
11394 cx.notify();
11395 }
11396
11397 #[cfg(any(test, feature = "test-support"))]
11398 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11399 self.breakpoint_store.clone()
11400 }
11401
11402 pub fn prepare_restore_change(
11403 &self,
11404 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11405 hunk: &MultiBufferDiffHunk,
11406 cx: &mut App,
11407 ) -> Option<()> {
11408 if hunk.is_created_file() {
11409 return None;
11410 }
11411 let buffer = self.buffer.read(cx);
11412 let diff = buffer.diff_for(hunk.buffer_id)?;
11413 let buffer = buffer.buffer(hunk.buffer_id)?;
11414 let buffer = buffer.read(cx);
11415 let original_text = diff
11416 .read(cx)
11417 .base_text()
11418 .as_rope()
11419 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11420 let buffer_snapshot = buffer.snapshot();
11421 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11422 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11423 probe
11424 .0
11425 .start
11426 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11427 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11428 }) {
11429 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11430 Some(())
11431 } else {
11432 None
11433 }
11434 }
11435
11436 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11437 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11438 }
11439
11440 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11441 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11442 }
11443
11444 fn manipulate_lines<M>(
11445 &mut self,
11446 window: &mut Window,
11447 cx: &mut Context<Self>,
11448 mut manipulate: M,
11449 ) where
11450 M: FnMut(&str) -> LineManipulationResult,
11451 {
11452 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11453
11454 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11455 let buffer = self.buffer.read(cx).snapshot(cx);
11456
11457 let mut edits = Vec::new();
11458
11459 let selections = self.selections.all::<Point>(&display_map);
11460 let mut selections = selections.iter().peekable();
11461 let mut contiguous_row_selections = Vec::new();
11462 let mut new_selections = Vec::new();
11463 let mut added_lines = 0;
11464 let mut removed_lines = 0;
11465
11466 while let Some(selection) = selections.next() {
11467 let (start_row, end_row) = consume_contiguous_rows(
11468 &mut contiguous_row_selections,
11469 selection,
11470 &display_map,
11471 &mut selections,
11472 );
11473
11474 let start_point = Point::new(start_row.0, 0);
11475 let end_point = Point::new(
11476 end_row.previous_row().0,
11477 buffer.line_len(end_row.previous_row()),
11478 );
11479 let text = buffer
11480 .text_for_range(start_point..end_point)
11481 .collect::<String>();
11482
11483 let LineManipulationResult {
11484 new_text,
11485 line_count_before,
11486 line_count_after,
11487 } = manipulate(&text);
11488
11489 edits.push((start_point..end_point, new_text));
11490
11491 // Selections must change based on added and removed line count
11492 let start_row =
11493 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11494 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11495 new_selections.push(Selection {
11496 id: selection.id,
11497 start: start_row,
11498 end: end_row,
11499 goal: SelectionGoal::None,
11500 reversed: selection.reversed,
11501 });
11502
11503 if line_count_after > line_count_before {
11504 added_lines += line_count_after - line_count_before;
11505 } else if line_count_before > line_count_after {
11506 removed_lines += line_count_before - line_count_after;
11507 }
11508 }
11509
11510 self.transact(window, cx, |this, window, cx| {
11511 let buffer = this.buffer.update(cx, |buffer, cx| {
11512 buffer.edit(edits, None, cx);
11513 buffer.snapshot(cx)
11514 });
11515
11516 // Recalculate offsets on newly edited buffer
11517 let new_selections = new_selections
11518 .iter()
11519 .map(|s| {
11520 let start_point = Point::new(s.start.0, 0);
11521 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11522 Selection {
11523 id: s.id,
11524 start: buffer.point_to_offset(start_point),
11525 end: buffer.point_to_offset(end_point),
11526 goal: s.goal,
11527 reversed: s.reversed,
11528 }
11529 })
11530 .collect();
11531
11532 this.change_selections(Default::default(), window, cx, |s| {
11533 s.select(new_selections);
11534 });
11535
11536 this.request_autoscroll(Autoscroll::fit(), cx);
11537 });
11538 }
11539
11540 fn manipulate_immutable_lines<Fn>(
11541 &mut self,
11542 window: &mut Window,
11543 cx: &mut Context<Self>,
11544 mut callback: Fn,
11545 ) where
11546 Fn: FnMut(&mut Vec<&str>),
11547 {
11548 self.manipulate_lines(window, cx, |text| {
11549 let mut lines: Vec<&str> = text.split('\n').collect();
11550 let line_count_before = lines.len();
11551
11552 callback(&mut lines);
11553
11554 LineManipulationResult {
11555 new_text: lines.join("\n"),
11556 line_count_before,
11557 line_count_after: lines.len(),
11558 }
11559 });
11560 }
11561
11562 fn manipulate_mutable_lines<Fn>(
11563 &mut self,
11564 window: &mut Window,
11565 cx: &mut Context<Self>,
11566 mut callback: Fn,
11567 ) where
11568 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11569 {
11570 self.manipulate_lines(window, cx, |text| {
11571 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11572 let line_count_before = lines.len();
11573
11574 callback(&mut lines);
11575
11576 LineManipulationResult {
11577 new_text: lines.join("\n"),
11578 line_count_before,
11579 line_count_after: lines.len(),
11580 }
11581 });
11582 }
11583
11584 pub fn convert_indentation_to_spaces(
11585 &mut self,
11586 _: &ConvertIndentationToSpaces,
11587 window: &mut Window,
11588 cx: &mut Context<Self>,
11589 ) {
11590 let settings = self.buffer.read(cx).language_settings(cx);
11591 let tab_size = settings.tab_size.get() as usize;
11592
11593 self.manipulate_mutable_lines(window, cx, |lines| {
11594 // Allocates a reasonably sized scratch buffer once for the whole loop
11595 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11596 // Avoids recomputing spaces that could be inserted many times
11597 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11598 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11599 .collect();
11600
11601 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11602 let mut chars = line.as_ref().chars();
11603 let mut col = 0;
11604 let mut changed = false;
11605
11606 for ch in chars.by_ref() {
11607 match ch {
11608 ' ' => {
11609 reindented_line.push(' ');
11610 col += 1;
11611 }
11612 '\t' => {
11613 // \t are converted to spaces depending on the current column
11614 let spaces_len = tab_size - (col % tab_size);
11615 reindented_line.extend(&space_cache[spaces_len - 1]);
11616 col += spaces_len;
11617 changed = true;
11618 }
11619 _ => {
11620 // If we dont append before break, the character is consumed
11621 reindented_line.push(ch);
11622 break;
11623 }
11624 }
11625 }
11626
11627 if !changed {
11628 reindented_line.clear();
11629 continue;
11630 }
11631 // Append the rest of the line and replace old reference with new one
11632 reindented_line.extend(chars);
11633 *line = Cow::Owned(reindented_line.clone());
11634 reindented_line.clear();
11635 }
11636 });
11637 }
11638
11639 pub fn convert_indentation_to_tabs(
11640 &mut self,
11641 _: &ConvertIndentationToTabs,
11642 window: &mut Window,
11643 cx: &mut Context<Self>,
11644 ) {
11645 let settings = self.buffer.read(cx).language_settings(cx);
11646 let tab_size = settings.tab_size.get() as usize;
11647
11648 self.manipulate_mutable_lines(window, cx, |lines| {
11649 // Allocates a reasonably sized buffer once for the whole loop
11650 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11651 // Avoids recomputing spaces that could be inserted many times
11652 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11653 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11654 .collect();
11655
11656 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11657 let mut chars = line.chars();
11658 let mut spaces_count = 0;
11659 let mut first_non_indent_char = None;
11660 let mut changed = false;
11661
11662 for ch in chars.by_ref() {
11663 match ch {
11664 ' ' => {
11665 // Keep track of spaces. Append \t when we reach tab_size
11666 spaces_count += 1;
11667 changed = true;
11668 if spaces_count == tab_size {
11669 reindented_line.push('\t');
11670 spaces_count = 0;
11671 }
11672 }
11673 '\t' => {
11674 reindented_line.push('\t');
11675 spaces_count = 0;
11676 }
11677 _ => {
11678 // Dont append it yet, we might have remaining spaces
11679 first_non_indent_char = Some(ch);
11680 break;
11681 }
11682 }
11683 }
11684
11685 if !changed {
11686 reindented_line.clear();
11687 continue;
11688 }
11689 // Remaining spaces that didn't make a full tab stop
11690 if spaces_count > 0 {
11691 reindented_line.extend(&space_cache[spaces_count - 1]);
11692 }
11693 // If we consume an extra character that was not indentation, add it back
11694 if let Some(extra_char) = first_non_indent_char {
11695 reindented_line.push(extra_char);
11696 }
11697 // Append the rest of the line and replace old reference with new one
11698 reindented_line.extend(chars);
11699 *line = Cow::Owned(reindented_line.clone());
11700 reindented_line.clear();
11701 }
11702 });
11703 }
11704
11705 pub fn convert_to_upper_case(
11706 &mut self,
11707 _: &ConvertToUpperCase,
11708 window: &mut Window,
11709 cx: &mut Context<Self>,
11710 ) {
11711 self.manipulate_text(window, cx, |text| text.to_uppercase())
11712 }
11713
11714 pub fn convert_to_lower_case(
11715 &mut self,
11716 _: &ConvertToLowerCase,
11717 window: &mut Window,
11718 cx: &mut Context<Self>,
11719 ) {
11720 self.manipulate_text(window, cx, |text| text.to_lowercase())
11721 }
11722
11723 pub fn convert_to_title_case(
11724 &mut self,
11725 _: &ConvertToTitleCase,
11726 window: &mut Window,
11727 cx: &mut Context<Self>,
11728 ) {
11729 self.manipulate_text(window, cx, |text| {
11730 text.split('\n')
11731 .map(|line| line.to_case(Case::Title))
11732 .join("\n")
11733 })
11734 }
11735
11736 pub fn convert_to_snake_case(
11737 &mut self,
11738 _: &ConvertToSnakeCase,
11739 window: &mut Window,
11740 cx: &mut Context<Self>,
11741 ) {
11742 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11743 }
11744
11745 pub fn convert_to_kebab_case(
11746 &mut self,
11747 _: &ConvertToKebabCase,
11748 window: &mut Window,
11749 cx: &mut Context<Self>,
11750 ) {
11751 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11752 }
11753
11754 pub fn convert_to_upper_camel_case(
11755 &mut self,
11756 _: &ConvertToUpperCamelCase,
11757 window: &mut Window,
11758 cx: &mut Context<Self>,
11759 ) {
11760 self.manipulate_text(window, cx, |text| {
11761 text.split('\n')
11762 .map(|line| line.to_case(Case::UpperCamel))
11763 .join("\n")
11764 })
11765 }
11766
11767 pub fn convert_to_lower_camel_case(
11768 &mut self,
11769 _: &ConvertToLowerCamelCase,
11770 window: &mut Window,
11771 cx: &mut Context<Self>,
11772 ) {
11773 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11774 }
11775
11776 pub fn convert_to_opposite_case(
11777 &mut self,
11778 _: &ConvertToOppositeCase,
11779 window: &mut Window,
11780 cx: &mut Context<Self>,
11781 ) {
11782 self.manipulate_text(window, cx, |text| {
11783 text.chars()
11784 .fold(String::with_capacity(text.len()), |mut t, c| {
11785 if c.is_uppercase() {
11786 t.extend(c.to_lowercase());
11787 } else {
11788 t.extend(c.to_uppercase());
11789 }
11790 t
11791 })
11792 })
11793 }
11794
11795 pub fn convert_to_sentence_case(
11796 &mut self,
11797 _: &ConvertToSentenceCase,
11798 window: &mut Window,
11799 cx: &mut Context<Self>,
11800 ) {
11801 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11802 }
11803
11804 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11805 self.manipulate_text(window, cx, |text| {
11806 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11807 if has_upper_case_characters {
11808 text.to_lowercase()
11809 } else {
11810 text.to_uppercase()
11811 }
11812 })
11813 }
11814
11815 pub fn convert_to_rot13(
11816 &mut self,
11817 _: &ConvertToRot13,
11818 window: &mut Window,
11819 cx: &mut Context<Self>,
11820 ) {
11821 self.manipulate_text(window, cx, |text| {
11822 text.chars()
11823 .map(|c| match c {
11824 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11825 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11826 _ => c,
11827 })
11828 .collect()
11829 })
11830 }
11831
11832 pub fn convert_to_rot47(
11833 &mut self,
11834 _: &ConvertToRot47,
11835 window: &mut Window,
11836 cx: &mut Context<Self>,
11837 ) {
11838 self.manipulate_text(window, cx, |text| {
11839 text.chars()
11840 .map(|c| {
11841 let code_point = c as u32;
11842 if code_point >= 33 && code_point <= 126 {
11843 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11844 }
11845 c
11846 })
11847 .collect()
11848 })
11849 }
11850
11851 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11852 where
11853 Fn: FnMut(&str) -> String,
11854 {
11855 let buffer = self.buffer.read(cx).snapshot(cx);
11856
11857 let mut new_selections = Vec::new();
11858 let mut edits = Vec::new();
11859 let mut selection_adjustment = 0isize;
11860
11861 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11862 let selection_is_empty = selection.is_empty();
11863
11864 let (start, end) = if selection_is_empty {
11865 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11866 (word_range.start, word_range.end)
11867 } else {
11868 (
11869 buffer.point_to_offset(selection.start),
11870 buffer.point_to_offset(selection.end),
11871 )
11872 };
11873
11874 let text = buffer.text_for_range(start..end).collect::<String>();
11875 let old_length = text.len() as isize;
11876 let text = callback(&text);
11877
11878 new_selections.push(Selection {
11879 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11880 end: MultiBufferOffset(
11881 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11882 ),
11883 goal: SelectionGoal::None,
11884 id: selection.id,
11885 reversed: selection.reversed,
11886 });
11887
11888 selection_adjustment += old_length - text.len() as isize;
11889
11890 edits.push((start..end, text));
11891 }
11892
11893 self.transact(window, cx, |this, window, cx| {
11894 this.buffer.update(cx, |buffer, cx| {
11895 buffer.edit(edits, None, cx);
11896 });
11897
11898 this.change_selections(Default::default(), window, cx, |s| {
11899 s.select(new_selections);
11900 });
11901
11902 this.request_autoscroll(Autoscroll::fit(), cx);
11903 });
11904 }
11905
11906 pub fn move_selection_on_drop(
11907 &mut self,
11908 selection: &Selection<Anchor>,
11909 target: DisplayPoint,
11910 is_cut: bool,
11911 window: &mut Window,
11912 cx: &mut Context<Self>,
11913 ) {
11914 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11915 let buffer = display_map.buffer_snapshot();
11916 let mut edits = Vec::new();
11917 let insert_point = display_map
11918 .clip_point(target, Bias::Left)
11919 .to_point(&display_map);
11920 let text = buffer
11921 .text_for_range(selection.start..selection.end)
11922 .collect::<String>();
11923 if is_cut {
11924 edits.push(((selection.start..selection.end), String::new()));
11925 }
11926 let insert_anchor = buffer.anchor_before(insert_point);
11927 edits.push(((insert_anchor..insert_anchor), text));
11928 let last_edit_start = insert_anchor.bias_left(buffer);
11929 let last_edit_end = insert_anchor.bias_right(buffer);
11930 self.transact(window, cx, |this, window, cx| {
11931 this.buffer.update(cx, |buffer, cx| {
11932 buffer.edit(edits, None, cx);
11933 });
11934 this.change_selections(Default::default(), window, cx, |s| {
11935 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11936 });
11937 });
11938 }
11939
11940 pub fn clear_selection_drag_state(&mut self) {
11941 self.selection_drag_state = SelectionDragState::None;
11942 }
11943
11944 pub fn duplicate(
11945 &mut self,
11946 upwards: bool,
11947 whole_lines: bool,
11948 window: &mut Window,
11949 cx: &mut Context<Self>,
11950 ) {
11951 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11952
11953 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11954 let buffer = display_map.buffer_snapshot();
11955 let selections = self.selections.all::<Point>(&display_map);
11956
11957 let mut edits = Vec::new();
11958 let mut selections_iter = selections.iter().peekable();
11959 while let Some(selection) = selections_iter.next() {
11960 let mut rows = selection.spanned_rows(false, &display_map);
11961 // duplicate line-wise
11962 if whole_lines || selection.start == selection.end {
11963 // Avoid duplicating the same lines twice.
11964 while let Some(next_selection) = selections_iter.peek() {
11965 let next_rows = next_selection.spanned_rows(false, &display_map);
11966 if next_rows.start < rows.end {
11967 rows.end = next_rows.end;
11968 selections_iter.next().unwrap();
11969 } else {
11970 break;
11971 }
11972 }
11973
11974 // Copy the text from the selected row region and splice it either at the start
11975 // or end of the region.
11976 let start = Point::new(rows.start.0, 0);
11977 let end = Point::new(
11978 rows.end.previous_row().0,
11979 buffer.line_len(rows.end.previous_row()),
11980 );
11981
11982 let mut text = buffer.text_for_range(start..end).collect::<String>();
11983
11984 let insert_location = if upwards {
11985 // When duplicating upward, we need to insert before the current line.
11986 // If we're on the last line and it doesn't end with a newline,
11987 // we need to add a newline before the duplicated content.
11988 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11989 && buffer.max_point().column > 0
11990 && !text.ends_with('\n');
11991
11992 if needs_leading_newline {
11993 text.insert(0, '\n');
11994 end
11995 } else {
11996 text.push('\n');
11997 Point::new(rows.start.0, 0)
11998 }
11999 } else {
12000 text.push('\n');
12001 start
12002 };
12003 edits.push((insert_location..insert_location, text));
12004 } else {
12005 // duplicate character-wise
12006 let start = selection.start;
12007 let end = selection.end;
12008 let text = buffer.text_for_range(start..end).collect::<String>();
12009 edits.push((selection.end..selection.end, text));
12010 }
12011 }
12012
12013 self.transact(window, cx, |this, window, cx| {
12014 this.buffer.update(cx, |buffer, cx| {
12015 buffer.edit(edits, None, cx);
12016 });
12017
12018 // When duplicating upward with whole lines, move the cursor to the duplicated line
12019 if upwards && whole_lines {
12020 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12021
12022 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12023 let mut new_ranges = Vec::new();
12024 let selections = s.all::<Point>(&display_map);
12025 let mut selections_iter = selections.iter().peekable();
12026
12027 while let Some(first_selection) = selections_iter.next() {
12028 // Group contiguous selections together to find the total row span
12029 let mut group_selections = vec![first_selection];
12030 let mut rows = first_selection.spanned_rows(false, &display_map);
12031
12032 while let Some(next_selection) = selections_iter.peek() {
12033 let next_rows = next_selection.spanned_rows(false, &display_map);
12034 if next_rows.start < rows.end {
12035 rows.end = next_rows.end;
12036 group_selections.push(selections_iter.next().unwrap());
12037 } else {
12038 break;
12039 }
12040 }
12041
12042 let row_count = rows.end.0 - rows.start.0;
12043
12044 // Move all selections in this group up by the total number of duplicated rows
12045 for selection in group_selections {
12046 let new_start = Point::new(
12047 selection.start.row.saturating_sub(row_count),
12048 selection.start.column,
12049 );
12050
12051 let new_end = Point::new(
12052 selection.end.row.saturating_sub(row_count),
12053 selection.end.column,
12054 );
12055
12056 new_ranges.push(new_start..new_end);
12057 }
12058 }
12059
12060 s.select_ranges(new_ranges);
12061 });
12062 }
12063
12064 this.request_autoscroll(Autoscroll::fit(), cx);
12065 });
12066 }
12067
12068 pub fn duplicate_line_up(
12069 &mut self,
12070 _: &DuplicateLineUp,
12071 window: &mut Window,
12072 cx: &mut Context<Self>,
12073 ) {
12074 self.duplicate(true, true, window, cx);
12075 }
12076
12077 pub fn duplicate_line_down(
12078 &mut self,
12079 _: &DuplicateLineDown,
12080 window: &mut Window,
12081 cx: &mut Context<Self>,
12082 ) {
12083 self.duplicate(false, true, window, cx);
12084 }
12085
12086 pub fn duplicate_selection(
12087 &mut self,
12088 _: &DuplicateSelection,
12089 window: &mut Window,
12090 cx: &mut Context<Self>,
12091 ) {
12092 self.duplicate(false, false, window, cx);
12093 }
12094
12095 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12097 if self.mode.is_single_line() {
12098 cx.propagate();
12099 return;
12100 }
12101
12102 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12103 let buffer = self.buffer.read(cx).snapshot(cx);
12104
12105 let mut edits = Vec::new();
12106 let mut unfold_ranges = Vec::new();
12107 let mut refold_creases = Vec::new();
12108
12109 let selections = self.selections.all::<Point>(&display_map);
12110 let mut selections = selections.iter().peekable();
12111 let mut contiguous_row_selections = Vec::new();
12112 let mut new_selections = Vec::new();
12113
12114 while let Some(selection) = selections.next() {
12115 // Find all the selections that span a contiguous row range
12116 let (start_row, end_row) = consume_contiguous_rows(
12117 &mut contiguous_row_selections,
12118 selection,
12119 &display_map,
12120 &mut selections,
12121 );
12122
12123 // Move the text spanned by the row range to be before the line preceding the row range
12124 if start_row.0 > 0 {
12125 let range_to_move = Point::new(
12126 start_row.previous_row().0,
12127 buffer.line_len(start_row.previous_row()),
12128 )
12129 ..Point::new(
12130 end_row.previous_row().0,
12131 buffer.line_len(end_row.previous_row()),
12132 );
12133 let insertion_point = display_map
12134 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12135 .0;
12136
12137 // Don't move lines across excerpts
12138 if buffer
12139 .excerpt_containing(insertion_point..range_to_move.end)
12140 .is_some()
12141 {
12142 let text = buffer
12143 .text_for_range(range_to_move.clone())
12144 .flat_map(|s| s.chars())
12145 .skip(1)
12146 .chain(['\n'])
12147 .collect::<String>();
12148
12149 edits.push((
12150 buffer.anchor_after(range_to_move.start)
12151 ..buffer.anchor_before(range_to_move.end),
12152 String::new(),
12153 ));
12154 let insertion_anchor = buffer.anchor_after(insertion_point);
12155 edits.push((insertion_anchor..insertion_anchor, text));
12156
12157 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12158
12159 // Move selections up
12160 new_selections.extend(contiguous_row_selections.drain(..).map(
12161 |mut selection| {
12162 selection.start.row -= row_delta;
12163 selection.end.row -= row_delta;
12164 selection
12165 },
12166 ));
12167
12168 // Move folds up
12169 unfold_ranges.push(range_to_move.clone());
12170 for fold in display_map.folds_in_range(
12171 buffer.anchor_before(range_to_move.start)
12172 ..buffer.anchor_after(range_to_move.end),
12173 ) {
12174 let mut start = fold.range.start.to_point(&buffer);
12175 let mut end = fold.range.end.to_point(&buffer);
12176 start.row -= row_delta;
12177 end.row -= row_delta;
12178 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12179 }
12180 }
12181 }
12182
12183 // If we didn't move line(s), preserve the existing selections
12184 new_selections.append(&mut contiguous_row_selections);
12185 }
12186
12187 self.transact(window, cx, |this, window, cx| {
12188 this.unfold_ranges(&unfold_ranges, true, true, cx);
12189 this.buffer.update(cx, |buffer, cx| {
12190 for (range, text) in edits {
12191 buffer.edit([(range, text)], None, cx);
12192 }
12193 });
12194 this.fold_creases(refold_creases, true, window, cx);
12195 this.change_selections(Default::default(), window, cx, |s| {
12196 s.select(new_selections);
12197 })
12198 });
12199 }
12200
12201 pub fn move_line_down(
12202 &mut self,
12203 _: &MoveLineDown,
12204 window: &mut Window,
12205 cx: &mut Context<Self>,
12206 ) {
12207 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12208 if self.mode.is_single_line() {
12209 cx.propagate();
12210 return;
12211 }
12212
12213 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12214 let buffer = self.buffer.read(cx).snapshot(cx);
12215
12216 let mut edits = Vec::new();
12217 let mut unfold_ranges = Vec::new();
12218 let mut refold_creases = Vec::new();
12219
12220 let selections = self.selections.all::<Point>(&display_map);
12221 let mut selections = selections.iter().peekable();
12222 let mut contiguous_row_selections = Vec::new();
12223 let mut new_selections = Vec::new();
12224
12225 while let Some(selection) = selections.next() {
12226 // Find all the selections that span a contiguous row range
12227 let (start_row, end_row) = consume_contiguous_rows(
12228 &mut contiguous_row_selections,
12229 selection,
12230 &display_map,
12231 &mut selections,
12232 );
12233
12234 // Move the text spanned by the row range to be after the last line of the row range
12235 if end_row.0 <= buffer.max_point().row {
12236 let range_to_move =
12237 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12238 let insertion_point = display_map
12239 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12240 .0;
12241
12242 // Don't move lines across excerpt boundaries
12243 if buffer
12244 .excerpt_containing(range_to_move.start..insertion_point)
12245 .is_some()
12246 {
12247 let mut text = String::from("\n");
12248 text.extend(buffer.text_for_range(range_to_move.clone()));
12249 text.pop(); // Drop trailing newline
12250 edits.push((
12251 buffer.anchor_after(range_to_move.start)
12252 ..buffer.anchor_before(range_to_move.end),
12253 String::new(),
12254 ));
12255 let insertion_anchor = buffer.anchor_after(insertion_point);
12256 edits.push((insertion_anchor..insertion_anchor, text));
12257
12258 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12259
12260 // Move selections down
12261 new_selections.extend(contiguous_row_selections.drain(..).map(
12262 |mut selection| {
12263 selection.start.row += row_delta;
12264 selection.end.row += row_delta;
12265 selection
12266 },
12267 ));
12268
12269 // Move folds down
12270 unfold_ranges.push(range_to_move.clone());
12271 for fold in display_map.folds_in_range(
12272 buffer.anchor_before(range_to_move.start)
12273 ..buffer.anchor_after(range_to_move.end),
12274 ) {
12275 let mut start = fold.range.start.to_point(&buffer);
12276 let mut end = fold.range.end.to_point(&buffer);
12277 start.row += row_delta;
12278 end.row += row_delta;
12279 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12280 }
12281 }
12282 }
12283
12284 // If we didn't move line(s), preserve the existing selections
12285 new_selections.append(&mut contiguous_row_selections);
12286 }
12287
12288 self.transact(window, cx, |this, window, cx| {
12289 this.unfold_ranges(&unfold_ranges, true, true, cx);
12290 this.buffer.update(cx, |buffer, cx| {
12291 for (range, text) in edits {
12292 buffer.edit([(range, text)], None, cx);
12293 }
12294 });
12295 this.fold_creases(refold_creases, true, window, cx);
12296 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12297 });
12298 }
12299
12300 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12301 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12302 let text_layout_details = &self.text_layout_details(window);
12303 self.transact(window, cx, |this, window, cx| {
12304 let edits = this.change_selections(Default::default(), window, cx, |s| {
12305 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12306 s.move_with(|display_map, selection| {
12307 if !selection.is_empty() {
12308 return;
12309 }
12310
12311 let mut head = selection.head();
12312 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12313 if head.column() == display_map.line_len(head.row()) {
12314 transpose_offset = display_map
12315 .buffer_snapshot()
12316 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12317 }
12318
12319 if transpose_offset == MultiBufferOffset(0) {
12320 return;
12321 }
12322
12323 *head.column_mut() += 1;
12324 head = display_map.clip_point(head, Bias::Right);
12325 let goal = SelectionGoal::HorizontalPosition(
12326 display_map
12327 .x_for_display_point(head, text_layout_details)
12328 .into(),
12329 );
12330 selection.collapse_to(head, goal);
12331
12332 let transpose_start = display_map
12333 .buffer_snapshot()
12334 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12335 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12336 let transpose_end = display_map
12337 .buffer_snapshot()
12338 .clip_offset(transpose_offset + 1usize, Bias::Right);
12339 if let Some(ch) = display_map
12340 .buffer_snapshot()
12341 .chars_at(transpose_start)
12342 .next()
12343 {
12344 edits.push((transpose_start..transpose_offset, String::new()));
12345 edits.push((transpose_end..transpose_end, ch.to_string()));
12346 }
12347 }
12348 });
12349 edits
12350 });
12351 this.buffer
12352 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12353 let selections = this
12354 .selections
12355 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12356 this.change_selections(Default::default(), window, cx, |s| {
12357 s.select(selections);
12358 });
12359 });
12360 }
12361
12362 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12363 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12364 if self.mode.is_single_line() {
12365 cx.propagate();
12366 return;
12367 }
12368
12369 self.rewrap_impl(RewrapOptions::default(), cx)
12370 }
12371
12372 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12373 let buffer = self.buffer.read(cx).snapshot(cx);
12374 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12375
12376 #[derive(Clone, Debug, PartialEq)]
12377 enum CommentFormat {
12378 /// single line comment, with prefix for line
12379 Line(String),
12380 /// single line within a block comment, with prefix for line
12381 BlockLine(String),
12382 /// a single line of a block comment that includes the initial delimiter
12383 BlockCommentWithStart(BlockCommentConfig),
12384 /// a single line of a block comment that includes the ending delimiter
12385 BlockCommentWithEnd(BlockCommentConfig),
12386 }
12387
12388 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12389 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12390 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12391 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12392 .peekable();
12393
12394 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12395 row
12396 } else {
12397 return Vec::new();
12398 };
12399
12400 let language_settings = buffer.language_settings_at(selection.head(), cx);
12401 let language_scope = buffer.language_scope_at(selection.head());
12402
12403 let indent_and_prefix_for_row =
12404 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12405 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12406 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12407 &language_scope
12408 {
12409 let indent_end = Point::new(row, indent.len);
12410 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12411 let line_text_after_indent = buffer
12412 .text_for_range(indent_end..line_end)
12413 .collect::<String>();
12414
12415 let is_within_comment_override = buffer
12416 .language_scope_at(indent_end)
12417 .is_some_and(|scope| scope.override_name() == Some("comment"));
12418 let comment_delimiters = if is_within_comment_override {
12419 // we are within a comment syntax node, but we don't
12420 // yet know what kind of comment: block, doc or line
12421 match (
12422 language_scope.documentation_comment(),
12423 language_scope.block_comment(),
12424 ) {
12425 (Some(config), _) | (_, Some(config))
12426 if buffer.contains_str_at(indent_end, &config.start) =>
12427 {
12428 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12429 }
12430 (Some(config), _) | (_, Some(config))
12431 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12432 {
12433 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12434 }
12435 (Some(config), _) | (_, Some(config))
12436 if buffer.contains_str_at(indent_end, &config.prefix) =>
12437 {
12438 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12439 }
12440 (_, _) => language_scope
12441 .line_comment_prefixes()
12442 .iter()
12443 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12444 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12445 }
12446 } else {
12447 // we not in an overridden comment node, but we may
12448 // be within a non-overridden line comment node
12449 language_scope
12450 .line_comment_prefixes()
12451 .iter()
12452 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12453 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12454 };
12455
12456 let rewrap_prefix = language_scope
12457 .rewrap_prefixes()
12458 .iter()
12459 .find_map(|prefix_regex| {
12460 prefix_regex.find(&line_text_after_indent).map(|mat| {
12461 if mat.start() == 0 {
12462 Some(mat.as_str().to_string())
12463 } else {
12464 None
12465 }
12466 })
12467 })
12468 .flatten();
12469 (comment_delimiters, rewrap_prefix)
12470 } else {
12471 (None, None)
12472 };
12473 (indent, comment_prefix, rewrap_prefix)
12474 };
12475
12476 let mut ranges = Vec::new();
12477 let from_empty_selection = selection.is_empty();
12478
12479 let mut current_range_start = first_row;
12480 let mut prev_row = first_row;
12481 let (
12482 mut current_range_indent,
12483 mut current_range_comment_delimiters,
12484 mut current_range_rewrap_prefix,
12485 ) = indent_and_prefix_for_row(first_row);
12486
12487 for row in non_blank_rows_iter.skip(1) {
12488 let has_paragraph_break = row > prev_row + 1;
12489
12490 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12491 indent_and_prefix_for_row(row);
12492
12493 let has_indent_change = row_indent != current_range_indent;
12494 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12495
12496 let has_boundary_change = has_comment_change
12497 || row_rewrap_prefix.is_some()
12498 || (has_indent_change && current_range_comment_delimiters.is_some());
12499
12500 if has_paragraph_break || has_boundary_change {
12501 ranges.push((
12502 language_settings.clone(),
12503 Point::new(current_range_start, 0)
12504 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12505 current_range_indent,
12506 current_range_comment_delimiters.clone(),
12507 current_range_rewrap_prefix.clone(),
12508 from_empty_selection,
12509 ));
12510 current_range_start = row;
12511 current_range_indent = row_indent;
12512 current_range_comment_delimiters = row_comment_delimiters;
12513 current_range_rewrap_prefix = row_rewrap_prefix;
12514 }
12515 prev_row = row;
12516 }
12517
12518 ranges.push((
12519 language_settings.clone(),
12520 Point::new(current_range_start, 0)
12521 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12522 current_range_indent,
12523 current_range_comment_delimiters,
12524 current_range_rewrap_prefix,
12525 from_empty_selection,
12526 ));
12527
12528 ranges
12529 });
12530
12531 let mut edits = Vec::new();
12532 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12533
12534 for (
12535 language_settings,
12536 wrap_range,
12537 mut indent_size,
12538 comment_prefix,
12539 rewrap_prefix,
12540 from_empty_selection,
12541 ) in wrap_ranges
12542 {
12543 let mut start_row = wrap_range.start.row;
12544 let mut end_row = wrap_range.end.row;
12545
12546 // Skip selections that overlap with a range that has already been rewrapped.
12547 let selection_range = start_row..end_row;
12548 if rewrapped_row_ranges
12549 .iter()
12550 .any(|range| range.overlaps(&selection_range))
12551 {
12552 continue;
12553 }
12554
12555 let tab_size = language_settings.tab_size;
12556
12557 let (line_prefix, inside_comment) = match &comment_prefix {
12558 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12559 (Some(prefix.as_str()), true)
12560 }
12561 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12562 (Some(prefix.as_ref()), true)
12563 }
12564 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12565 start: _,
12566 end: _,
12567 prefix,
12568 tab_size,
12569 })) => {
12570 indent_size.len += tab_size;
12571 (Some(prefix.as_ref()), true)
12572 }
12573 None => (None, false),
12574 };
12575 let indent_prefix = indent_size.chars().collect::<String>();
12576 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12577
12578 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12579 RewrapBehavior::InComments => inside_comment,
12580 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12581 RewrapBehavior::Anywhere => true,
12582 };
12583
12584 let should_rewrap = options.override_language_settings
12585 || allow_rewrap_based_on_language
12586 || self.hard_wrap.is_some();
12587 if !should_rewrap {
12588 continue;
12589 }
12590
12591 if from_empty_selection {
12592 'expand_upwards: while start_row > 0 {
12593 let prev_row = start_row - 1;
12594 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12595 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12596 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12597 {
12598 start_row = prev_row;
12599 } else {
12600 break 'expand_upwards;
12601 }
12602 }
12603
12604 'expand_downwards: while end_row < buffer.max_point().row {
12605 let next_row = end_row + 1;
12606 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12607 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12608 && !buffer.is_line_blank(MultiBufferRow(next_row))
12609 {
12610 end_row = next_row;
12611 } else {
12612 break 'expand_downwards;
12613 }
12614 }
12615 }
12616
12617 let start = Point::new(start_row, 0);
12618 let start_offset = ToOffset::to_offset(&start, &buffer);
12619 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12620 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12621 let mut first_line_delimiter = None;
12622 let mut last_line_delimiter = None;
12623 let Some(lines_without_prefixes) = selection_text
12624 .lines()
12625 .enumerate()
12626 .map(|(ix, line)| {
12627 let line_trimmed = line.trim_start();
12628 if rewrap_prefix.is_some() && ix > 0 {
12629 Ok(line_trimmed)
12630 } else if let Some(
12631 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12632 start,
12633 prefix,
12634 end,
12635 tab_size,
12636 })
12637 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12638 start,
12639 prefix,
12640 end,
12641 tab_size,
12642 }),
12643 ) = &comment_prefix
12644 {
12645 let line_trimmed = line_trimmed
12646 .strip_prefix(start.as_ref())
12647 .map(|s| {
12648 let mut indent_size = indent_size;
12649 indent_size.len -= tab_size;
12650 let indent_prefix: String = indent_size.chars().collect();
12651 first_line_delimiter = Some((indent_prefix, start));
12652 s.trim_start()
12653 })
12654 .unwrap_or(line_trimmed);
12655 let line_trimmed = line_trimmed
12656 .strip_suffix(end.as_ref())
12657 .map(|s| {
12658 last_line_delimiter = Some(end);
12659 s.trim_end()
12660 })
12661 .unwrap_or(line_trimmed);
12662 let line_trimmed = line_trimmed
12663 .strip_prefix(prefix.as_ref())
12664 .unwrap_or(line_trimmed);
12665 Ok(line_trimmed)
12666 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12667 line_trimmed.strip_prefix(prefix).with_context(|| {
12668 format!("line did not start with prefix {prefix:?}: {line:?}")
12669 })
12670 } else {
12671 line_trimmed
12672 .strip_prefix(&line_prefix.trim_start())
12673 .with_context(|| {
12674 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12675 })
12676 }
12677 })
12678 .collect::<Result<Vec<_>, _>>()
12679 .log_err()
12680 else {
12681 continue;
12682 };
12683
12684 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12685 buffer
12686 .language_settings_at(Point::new(start_row, 0), cx)
12687 .preferred_line_length as usize
12688 });
12689
12690 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12691 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12692 } else {
12693 line_prefix.clone()
12694 };
12695
12696 let wrapped_text = {
12697 let mut wrapped_text = wrap_with_prefix(
12698 line_prefix,
12699 subsequent_lines_prefix,
12700 lines_without_prefixes.join("\n"),
12701 wrap_column,
12702 tab_size,
12703 options.preserve_existing_whitespace,
12704 );
12705
12706 if let Some((indent, delimiter)) = first_line_delimiter {
12707 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12708 }
12709 if let Some(last_line) = last_line_delimiter {
12710 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12711 }
12712
12713 wrapped_text
12714 };
12715
12716 // TODO: should always use char-based diff while still supporting cursor behavior that
12717 // matches vim.
12718 let mut diff_options = DiffOptions::default();
12719 if options.override_language_settings {
12720 diff_options.max_word_diff_len = 0;
12721 diff_options.max_word_diff_line_count = 0;
12722 } else {
12723 diff_options.max_word_diff_len = usize::MAX;
12724 diff_options.max_word_diff_line_count = usize::MAX;
12725 }
12726
12727 for (old_range, new_text) in
12728 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12729 {
12730 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12731 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12732 edits.push((edit_start..edit_end, new_text));
12733 }
12734
12735 rewrapped_row_ranges.push(start_row..=end_row);
12736 }
12737
12738 self.buffer
12739 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12740 }
12741
12742 pub fn cut_common(
12743 &mut self,
12744 cut_no_selection_line: bool,
12745 window: &mut Window,
12746 cx: &mut Context<Self>,
12747 ) -> ClipboardItem {
12748 let mut text = String::new();
12749 let buffer = self.buffer.read(cx).snapshot(cx);
12750 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12751 let mut clipboard_selections = Vec::with_capacity(selections.len());
12752 {
12753 let max_point = buffer.max_point();
12754 let mut is_first = true;
12755 let mut prev_selection_was_entire_line = false;
12756 for selection in &mut selections {
12757 let is_entire_line =
12758 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12759 if is_entire_line {
12760 selection.start = Point::new(selection.start.row, 0);
12761 if !selection.is_empty() && selection.end.column == 0 {
12762 selection.end = cmp::min(max_point, selection.end);
12763 } else {
12764 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12765 }
12766 selection.goal = SelectionGoal::None;
12767 }
12768 if is_first {
12769 is_first = false;
12770 } else if !prev_selection_was_entire_line {
12771 text += "\n";
12772 }
12773 prev_selection_was_entire_line = is_entire_line;
12774 let mut len = 0;
12775 for chunk in buffer.text_for_range(selection.start..selection.end) {
12776 text.push_str(chunk);
12777 len += chunk.len();
12778 }
12779 clipboard_selections.push(ClipboardSelection {
12780 len,
12781 is_entire_line,
12782 first_line_indent: buffer
12783 .indent_size_for_line(MultiBufferRow(selection.start.row))
12784 .len,
12785 });
12786 }
12787 }
12788
12789 self.transact(window, cx, |this, window, cx| {
12790 this.change_selections(Default::default(), window, cx, |s| {
12791 s.select(selections);
12792 });
12793 this.insert("", window, cx);
12794 });
12795 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12796 }
12797
12798 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12799 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12800 let item = self.cut_common(true, window, cx);
12801 cx.write_to_clipboard(item);
12802 }
12803
12804 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12805 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12806 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12807 s.move_with(|snapshot, sel| {
12808 if sel.is_empty() {
12809 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12810 }
12811 if sel.is_empty() {
12812 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12813 }
12814 });
12815 });
12816 let item = self.cut_common(false, window, cx);
12817 cx.set_global(KillRing(item))
12818 }
12819
12820 pub fn kill_ring_yank(
12821 &mut self,
12822 _: &KillRingYank,
12823 window: &mut Window,
12824 cx: &mut Context<Self>,
12825 ) {
12826 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12827 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12828 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12829 (kill_ring.text().to_string(), kill_ring.metadata_json())
12830 } else {
12831 return;
12832 }
12833 } else {
12834 return;
12835 };
12836 self.do_paste(&text, metadata, false, window, cx);
12837 }
12838
12839 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12840 self.do_copy(true, cx);
12841 }
12842
12843 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12844 self.do_copy(false, cx);
12845 }
12846
12847 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12848 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12849 let buffer = self.buffer.read(cx).read(cx);
12850 let mut text = String::new();
12851
12852 let mut clipboard_selections = Vec::with_capacity(selections.len());
12853 {
12854 let max_point = buffer.max_point();
12855 let mut is_first = true;
12856 let mut prev_selection_was_entire_line = false;
12857 for selection in &selections {
12858 let mut start = selection.start;
12859 let mut end = selection.end;
12860 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12861 let mut add_trailing_newline = false;
12862 if is_entire_line {
12863 start = Point::new(start.row, 0);
12864 let next_line_start = Point::new(end.row + 1, 0);
12865 if next_line_start <= max_point {
12866 end = next_line_start;
12867 } else {
12868 // We're on the last line without a trailing newline.
12869 // Copy to the end of the line and add a newline afterwards.
12870 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12871 add_trailing_newline = true;
12872 }
12873 }
12874
12875 let mut trimmed_selections = Vec::new();
12876 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12877 let row = MultiBufferRow(start.row);
12878 let first_indent = buffer.indent_size_for_line(row);
12879 if first_indent.len == 0 || start.column > first_indent.len {
12880 trimmed_selections.push(start..end);
12881 } else {
12882 trimmed_selections.push(
12883 Point::new(row.0, first_indent.len)
12884 ..Point::new(row.0, buffer.line_len(row)),
12885 );
12886 for row in start.row + 1..=end.row {
12887 let mut line_len = buffer.line_len(MultiBufferRow(row));
12888 if row == end.row {
12889 line_len = end.column;
12890 }
12891 if line_len == 0 {
12892 trimmed_selections
12893 .push(Point::new(row, 0)..Point::new(row, line_len));
12894 continue;
12895 }
12896 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12897 if row_indent_size.len >= first_indent.len {
12898 trimmed_selections.push(
12899 Point::new(row, first_indent.len)..Point::new(row, line_len),
12900 );
12901 } else {
12902 trimmed_selections.clear();
12903 trimmed_selections.push(start..end);
12904 break;
12905 }
12906 }
12907 }
12908 } else {
12909 trimmed_selections.push(start..end);
12910 }
12911
12912 for trimmed_range in trimmed_selections {
12913 if is_first {
12914 is_first = false;
12915 } else if !prev_selection_was_entire_line {
12916 text += "\n";
12917 }
12918 prev_selection_was_entire_line = is_entire_line;
12919 let mut len = 0;
12920 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12921 text.push_str(chunk);
12922 len += chunk.len();
12923 }
12924 if add_trailing_newline {
12925 text.push('\n');
12926 len += 1;
12927 }
12928 clipboard_selections.push(ClipboardSelection {
12929 len,
12930 is_entire_line,
12931 first_line_indent: buffer
12932 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12933 .len,
12934 });
12935 }
12936 }
12937 }
12938
12939 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12940 text,
12941 clipboard_selections,
12942 ));
12943 }
12944
12945 pub fn do_paste(
12946 &mut self,
12947 text: &String,
12948 clipboard_selections: Option<Vec<ClipboardSelection>>,
12949 handle_entire_lines: bool,
12950 window: &mut Window,
12951 cx: &mut Context<Self>,
12952 ) {
12953 if self.read_only(cx) {
12954 return;
12955 }
12956
12957 let clipboard_text = Cow::Borrowed(text.as_str());
12958
12959 self.transact(window, cx, |this, window, cx| {
12960 let had_active_edit_prediction = this.has_active_edit_prediction();
12961 let display_map = this.display_snapshot(cx);
12962 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12963 let cursor_offset = this
12964 .selections
12965 .last::<MultiBufferOffset>(&display_map)
12966 .head();
12967
12968 if let Some(mut clipboard_selections) = clipboard_selections {
12969 let all_selections_were_entire_line =
12970 clipboard_selections.iter().all(|s| s.is_entire_line);
12971 let first_selection_indent_column =
12972 clipboard_selections.first().map(|s| s.first_line_indent);
12973 if clipboard_selections.len() != old_selections.len() {
12974 clipboard_selections.drain(..);
12975 }
12976 let mut auto_indent_on_paste = true;
12977
12978 this.buffer.update(cx, |buffer, cx| {
12979 let snapshot = buffer.read(cx);
12980 auto_indent_on_paste = snapshot
12981 .language_settings_at(cursor_offset, cx)
12982 .auto_indent_on_paste;
12983
12984 let mut start_offset = 0;
12985 let mut edits = Vec::new();
12986 let mut original_indent_columns = Vec::new();
12987 for (ix, selection) in old_selections.iter().enumerate() {
12988 let to_insert;
12989 let entire_line;
12990 let original_indent_column;
12991 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12992 let end_offset = start_offset + clipboard_selection.len;
12993 to_insert = &clipboard_text[start_offset..end_offset];
12994 entire_line = clipboard_selection.is_entire_line;
12995 start_offset = if entire_line {
12996 end_offset
12997 } else {
12998 end_offset + 1
12999 };
13000 original_indent_column = Some(clipboard_selection.first_line_indent);
13001 } else {
13002 to_insert = &*clipboard_text;
13003 entire_line = all_selections_were_entire_line;
13004 original_indent_column = first_selection_indent_column
13005 }
13006
13007 let (range, to_insert) =
13008 if selection.is_empty() && handle_entire_lines && entire_line {
13009 // If the corresponding selection was empty when this slice of the
13010 // clipboard text was written, then the entire line containing the
13011 // selection was copied. If this selection is also currently empty,
13012 // then paste the line before the current line of the buffer.
13013 let column = selection.start.to_point(&snapshot).column as usize;
13014 let line_start = selection.start - column;
13015 (line_start..line_start, Cow::Borrowed(to_insert))
13016 } else {
13017 let language = snapshot.language_at(selection.head());
13018 let range = selection.range();
13019 if let Some(language) = language
13020 && language.name() == "Markdown".into()
13021 {
13022 edit_for_markdown_paste(
13023 &snapshot,
13024 range,
13025 to_insert,
13026 url::Url::parse(to_insert).ok(),
13027 )
13028 } else {
13029 (range, Cow::Borrowed(to_insert))
13030 }
13031 };
13032
13033 edits.push((range, to_insert));
13034 original_indent_columns.push(original_indent_column);
13035 }
13036 drop(snapshot);
13037
13038 buffer.edit(
13039 edits,
13040 if auto_indent_on_paste {
13041 Some(AutoindentMode::Block {
13042 original_indent_columns,
13043 })
13044 } else {
13045 None
13046 },
13047 cx,
13048 );
13049 });
13050
13051 let selections = this
13052 .selections
13053 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13054 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13055 } else {
13056 let url = url::Url::parse(&clipboard_text).ok();
13057
13058 let auto_indent_mode = if !clipboard_text.is_empty() {
13059 Some(AutoindentMode::Block {
13060 original_indent_columns: Vec::new(),
13061 })
13062 } else {
13063 None
13064 };
13065
13066 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13067 let snapshot = buffer.snapshot(cx);
13068
13069 let anchors = old_selections
13070 .iter()
13071 .map(|s| {
13072 let anchor = snapshot.anchor_after(s.head());
13073 s.map(|_| anchor)
13074 })
13075 .collect::<Vec<_>>();
13076
13077 let mut edits = Vec::new();
13078
13079 for selection in old_selections.iter() {
13080 let language = snapshot.language_at(selection.head());
13081 let range = selection.range();
13082
13083 let (edit_range, edit_text) = if let Some(language) = language
13084 && language.name() == "Markdown".into()
13085 {
13086 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13087 } else {
13088 (range, clipboard_text.clone())
13089 };
13090
13091 edits.push((edit_range, edit_text));
13092 }
13093
13094 drop(snapshot);
13095 buffer.edit(edits, auto_indent_mode, cx);
13096
13097 anchors
13098 });
13099
13100 this.change_selections(Default::default(), window, cx, |s| {
13101 s.select_anchors(selection_anchors);
13102 });
13103 }
13104
13105 // 🤔 | .. | show_in_menu |
13106 // | .. | true true
13107 // | had_edit_prediction | false true
13108
13109 let trigger_in_words =
13110 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13111
13112 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13113 });
13114 }
13115
13116 pub fn diff_clipboard_with_selection(
13117 &mut self,
13118 _: &DiffClipboardWithSelection,
13119 window: &mut Window,
13120 cx: &mut Context<Self>,
13121 ) {
13122 let selections = self
13123 .selections
13124 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13125
13126 if selections.is_empty() {
13127 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13128 return;
13129 };
13130
13131 let clipboard_text = match cx.read_from_clipboard() {
13132 Some(item) => match item.entries().first() {
13133 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13134 _ => None,
13135 },
13136 None => None,
13137 };
13138
13139 let Some(clipboard_text) = clipboard_text else {
13140 log::warn!("Clipboard doesn't contain text.");
13141 return;
13142 };
13143
13144 window.dispatch_action(
13145 Box::new(DiffClipboardWithSelectionData {
13146 clipboard_text,
13147 editor: cx.entity(),
13148 }),
13149 cx,
13150 );
13151 }
13152
13153 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13155 if let Some(item) = cx.read_from_clipboard() {
13156 let entries = item.entries();
13157
13158 match entries.first() {
13159 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13160 // of all the pasted entries.
13161 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13162 .do_paste(
13163 clipboard_string.text(),
13164 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13165 true,
13166 window,
13167 cx,
13168 ),
13169 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13170 }
13171 }
13172 }
13173
13174 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13175 if self.read_only(cx) {
13176 return;
13177 }
13178
13179 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13180
13181 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13182 if let Some((selections, _)) =
13183 self.selection_history.transaction(transaction_id).cloned()
13184 {
13185 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13186 s.select_anchors(selections.to_vec());
13187 });
13188 } else {
13189 log::error!(
13190 "No entry in selection_history found for undo. \
13191 This may correspond to a bug where undo does not update the selection. \
13192 If this is occurring, please add details to \
13193 https://github.com/zed-industries/zed/issues/22692"
13194 );
13195 }
13196 self.request_autoscroll(Autoscroll::fit(), cx);
13197 self.unmark_text(window, cx);
13198 self.refresh_edit_prediction(true, false, window, cx);
13199 cx.emit(EditorEvent::Edited { transaction_id });
13200 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13201 }
13202 }
13203
13204 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13205 if self.read_only(cx) {
13206 return;
13207 }
13208
13209 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13210
13211 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13212 if let Some((_, Some(selections))) =
13213 self.selection_history.transaction(transaction_id).cloned()
13214 {
13215 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13216 s.select_anchors(selections.to_vec());
13217 });
13218 } else {
13219 log::error!(
13220 "No entry in selection_history found for redo. \
13221 This may correspond to a bug where undo does not update the selection. \
13222 If this is occurring, please add details to \
13223 https://github.com/zed-industries/zed/issues/22692"
13224 );
13225 }
13226 self.request_autoscroll(Autoscroll::fit(), cx);
13227 self.unmark_text(window, cx);
13228 self.refresh_edit_prediction(true, false, window, cx);
13229 cx.emit(EditorEvent::Edited { transaction_id });
13230 }
13231 }
13232
13233 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13234 self.buffer
13235 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13236 }
13237
13238 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13239 self.buffer
13240 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13241 }
13242
13243 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13244 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13245 self.change_selections(Default::default(), window, cx, |s| {
13246 s.move_with(|map, selection| {
13247 let cursor = if selection.is_empty() {
13248 movement::left(map, selection.start)
13249 } else {
13250 selection.start
13251 };
13252 selection.collapse_to(cursor, SelectionGoal::None);
13253 });
13254 })
13255 }
13256
13257 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13258 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13259 self.change_selections(Default::default(), window, cx, |s| {
13260 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13261 })
13262 }
13263
13264 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13266 self.change_selections(Default::default(), window, cx, |s| {
13267 s.move_with(|map, selection| {
13268 let cursor = if selection.is_empty() {
13269 movement::right(map, selection.end)
13270 } else {
13271 selection.end
13272 };
13273 selection.collapse_to(cursor, SelectionGoal::None)
13274 });
13275 })
13276 }
13277
13278 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13279 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13280 self.change_selections(Default::default(), window, cx, |s| {
13281 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13282 });
13283 }
13284
13285 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13286 if self.take_rename(true, window, cx).is_some() {
13287 return;
13288 }
13289
13290 if self.mode.is_single_line() {
13291 cx.propagate();
13292 return;
13293 }
13294
13295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13296
13297 let text_layout_details = &self.text_layout_details(window);
13298 let selection_count = self.selections.count();
13299 let first_selection = self.selections.first_anchor();
13300
13301 self.change_selections(Default::default(), window, cx, |s| {
13302 s.move_with(|map, selection| {
13303 if !selection.is_empty() {
13304 selection.goal = SelectionGoal::None;
13305 }
13306 let (cursor, goal) = movement::up(
13307 map,
13308 selection.start,
13309 selection.goal,
13310 false,
13311 text_layout_details,
13312 );
13313 selection.collapse_to(cursor, goal);
13314 });
13315 });
13316
13317 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13318 {
13319 cx.propagate();
13320 }
13321 }
13322
13323 pub fn move_up_by_lines(
13324 &mut self,
13325 action: &MoveUpByLines,
13326 window: &mut Window,
13327 cx: &mut Context<Self>,
13328 ) {
13329 if self.take_rename(true, window, cx).is_some() {
13330 return;
13331 }
13332
13333 if self.mode.is_single_line() {
13334 cx.propagate();
13335 return;
13336 }
13337
13338 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13339
13340 let text_layout_details = &self.text_layout_details(window);
13341
13342 self.change_selections(Default::default(), window, cx, |s| {
13343 s.move_with(|map, selection| {
13344 if !selection.is_empty() {
13345 selection.goal = SelectionGoal::None;
13346 }
13347 let (cursor, goal) = movement::up_by_rows(
13348 map,
13349 selection.start,
13350 action.lines,
13351 selection.goal,
13352 false,
13353 text_layout_details,
13354 );
13355 selection.collapse_to(cursor, goal);
13356 });
13357 })
13358 }
13359
13360 pub fn move_down_by_lines(
13361 &mut self,
13362 action: &MoveDownByLines,
13363 window: &mut Window,
13364 cx: &mut Context<Self>,
13365 ) {
13366 if self.take_rename(true, window, cx).is_some() {
13367 return;
13368 }
13369
13370 if self.mode.is_single_line() {
13371 cx.propagate();
13372 return;
13373 }
13374
13375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13376
13377 let text_layout_details = &self.text_layout_details(window);
13378
13379 self.change_selections(Default::default(), window, cx, |s| {
13380 s.move_with(|map, selection| {
13381 if !selection.is_empty() {
13382 selection.goal = SelectionGoal::None;
13383 }
13384 let (cursor, goal) = movement::down_by_rows(
13385 map,
13386 selection.start,
13387 action.lines,
13388 selection.goal,
13389 false,
13390 text_layout_details,
13391 );
13392 selection.collapse_to(cursor, goal);
13393 });
13394 })
13395 }
13396
13397 pub fn select_down_by_lines(
13398 &mut self,
13399 action: &SelectDownByLines,
13400 window: &mut Window,
13401 cx: &mut Context<Self>,
13402 ) {
13403 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13404 let text_layout_details = &self.text_layout_details(window);
13405 self.change_selections(Default::default(), window, cx, |s| {
13406 s.move_heads_with(|map, head, goal| {
13407 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13408 })
13409 })
13410 }
13411
13412 pub fn select_up_by_lines(
13413 &mut self,
13414 action: &SelectUpByLines,
13415 window: &mut Window,
13416 cx: &mut Context<Self>,
13417 ) {
13418 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13419 let text_layout_details = &self.text_layout_details(window);
13420 self.change_selections(Default::default(), window, cx, |s| {
13421 s.move_heads_with(|map, head, goal| {
13422 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13423 })
13424 })
13425 }
13426
13427 pub fn select_page_up(
13428 &mut self,
13429 _: &SelectPageUp,
13430 window: &mut Window,
13431 cx: &mut Context<Self>,
13432 ) {
13433 let Some(row_count) = self.visible_row_count() else {
13434 return;
13435 };
13436
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13438
13439 let text_layout_details = &self.text_layout_details(window);
13440
13441 self.change_selections(Default::default(), window, cx, |s| {
13442 s.move_heads_with(|map, head, goal| {
13443 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13444 })
13445 })
13446 }
13447
13448 pub fn move_page_up(
13449 &mut self,
13450 action: &MovePageUp,
13451 window: &mut Window,
13452 cx: &mut Context<Self>,
13453 ) {
13454 if self.take_rename(true, window, cx).is_some() {
13455 return;
13456 }
13457
13458 if self
13459 .context_menu
13460 .borrow_mut()
13461 .as_mut()
13462 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13463 .unwrap_or(false)
13464 {
13465 return;
13466 }
13467
13468 if matches!(self.mode, EditorMode::SingleLine) {
13469 cx.propagate();
13470 return;
13471 }
13472
13473 let Some(row_count) = self.visible_row_count() else {
13474 return;
13475 };
13476
13477 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13478
13479 let effects = if action.center_cursor {
13480 SelectionEffects::scroll(Autoscroll::center())
13481 } else {
13482 SelectionEffects::default()
13483 };
13484
13485 let text_layout_details = &self.text_layout_details(window);
13486
13487 self.change_selections(effects, window, cx, |s| {
13488 s.move_with(|map, selection| {
13489 if !selection.is_empty() {
13490 selection.goal = SelectionGoal::None;
13491 }
13492 let (cursor, goal) = movement::up_by_rows(
13493 map,
13494 selection.end,
13495 row_count,
13496 selection.goal,
13497 false,
13498 text_layout_details,
13499 );
13500 selection.collapse_to(cursor, goal);
13501 });
13502 });
13503 }
13504
13505 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13507 let text_layout_details = &self.text_layout_details(window);
13508 self.change_selections(Default::default(), window, cx, |s| {
13509 s.move_heads_with(|map, head, goal| {
13510 movement::up(map, head, goal, false, text_layout_details)
13511 })
13512 })
13513 }
13514
13515 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13516 self.take_rename(true, window, cx);
13517
13518 if self.mode.is_single_line() {
13519 cx.propagate();
13520 return;
13521 }
13522
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13524
13525 let text_layout_details = &self.text_layout_details(window);
13526 let selection_count = self.selections.count();
13527 let first_selection = self.selections.first_anchor();
13528
13529 self.change_selections(Default::default(), window, cx, |s| {
13530 s.move_with(|map, selection| {
13531 if !selection.is_empty() {
13532 selection.goal = SelectionGoal::None;
13533 }
13534 let (cursor, goal) = movement::down(
13535 map,
13536 selection.end,
13537 selection.goal,
13538 false,
13539 text_layout_details,
13540 );
13541 selection.collapse_to(cursor, goal);
13542 });
13543 });
13544
13545 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13546 {
13547 cx.propagate();
13548 }
13549 }
13550
13551 pub fn select_page_down(
13552 &mut self,
13553 _: &SelectPageDown,
13554 window: &mut Window,
13555 cx: &mut Context<Self>,
13556 ) {
13557 let Some(row_count) = self.visible_row_count() else {
13558 return;
13559 };
13560
13561 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13562
13563 let text_layout_details = &self.text_layout_details(window);
13564
13565 self.change_selections(Default::default(), window, cx, |s| {
13566 s.move_heads_with(|map, head, goal| {
13567 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13568 })
13569 })
13570 }
13571
13572 pub fn move_page_down(
13573 &mut self,
13574 action: &MovePageDown,
13575 window: &mut Window,
13576 cx: &mut Context<Self>,
13577 ) {
13578 if self.take_rename(true, window, cx).is_some() {
13579 return;
13580 }
13581
13582 if self
13583 .context_menu
13584 .borrow_mut()
13585 .as_mut()
13586 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13587 .unwrap_or(false)
13588 {
13589 return;
13590 }
13591
13592 if matches!(self.mode, EditorMode::SingleLine) {
13593 cx.propagate();
13594 return;
13595 }
13596
13597 let Some(row_count) = self.visible_row_count() else {
13598 return;
13599 };
13600
13601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13602
13603 let effects = if action.center_cursor {
13604 SelectionEffects::scroll(Autoscroll::center())
13605 } else {
13606 SelectionEffects::default()
13607 };
13608
13609 let text_layout_details = &self.text_layout_details(window);
13610 self.change_selections(effects, window, cx, |s| {
13611 s.move_with(|map, selection| {
13612 if !selection.is_empty() {
13613 selection.goal = SelectionGoal::None;
13614 }
13615 let (cursor, goal) = movement::down_by_rows(
13616 map,
13617 selection.end,
13618 row_count,
13619 selection.goal,
13620 false,
13621 text_layout_details,
13622 );
13623 selection.collapse_to(cursor, goal);
13624 });
13625 });
13626 }
13627
13628 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13630 let text_layout_details = &self.text_layout_details(window);
13631 self.change_selections(Default::default(), window, cx, |s| {
13632 s.move_heads_with(|map, head, goal| {
13633 movement::down(map, head, goal, false, text_layout_details)
13634 })
13635 });
13636 }
13637
13638 pub fn context_menu_first(
13639 &mut self,
13640 _: &ContextMenuFirst,
13641 window: &mut Window,
13642 cx: &mut Context<Self>,
13643 ) {
13644 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13645 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13646 }
13647 }
13648
13649 pub fn context_menu_prev(
13650 &mut self,
13651 _: &ContextMenuPrevious,
13652 window: &mut Window,
13653 cx: &mut Context<Self>,
13654 ) {
13655 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13656 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13657 }
13658 }
13659
13660 pub fn context_menu_next(
13661 &mut self,
13662 _: &ContextMenuNext,
13663 window: &mut Window,
13664 cx: &mut Context<Self>,
13665 ) {
13666 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13667 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13668 }
13669 }
13670
13671 pub fn context_menu_last(
13672 &mut self,
13673 _: &ContextMenuLast,
13674 window: &mut Window,
13675 cx: &mut Context<Self>,
13676 ) {
13677 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13678 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13679 }
13680 }
13681
13682 pub fn signature_help_prev(
13683 &mut self,
13684 _: &SignatureHelpPrevious,
13685 _: &mut Window,
13686 cx: &mut Context<Self>,
13687 ) {
13688 if let Some(popover) = self.signature_help_state.popover_mut() {
13689 if popover.current_signature == 0 {
13690 popover.current_signature = popover.signatures.len() - 1;
13691 } else {
13692 popover.current_signature -= 1;
13693 }
13694 cx.notify();
13695 }
13696 }
13697
13698 pub fn signature_help_next(
13699 &mut self,
13700 _: &SignatureHelpNext,
13701 _: &mut Window,
13702 cx: &mut Context<Self>,
13703 ) {
13704 if let Some(popover) = self.signature_help_state.popover_mut() {
13705 if popover.current_signature + 1 == popover.signatures.len() {
13706 popover.current_signature = 0;
13707 } else {
13708 popover.current_signature += 1;
13709 }
13710 cx.notify();
13711 }
13712 }
13713
13714 pub fn move_to_previous_word_start(
13715 &mut self,
13716 _: &MoveToPreviousWordStart,
13717 window: &mut Window,
13718 cx: &mut Context<Self>,
13719 ) {
13720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13721 self.change_selections(Default::default(), window, cx, |s| {
13722 s.move_cursors_with(|map, head, _| {
13723 (
13724 movement::previous_word_start(map, head),
13725 SelectionGoal::None,
13726 )
13727 });
13728 })
13729 }
13730
13731 pub fn move_to_previous_subword_start(
13732 &mut self,
13733 _: &MoveToPreviousSubwordStart,
13734 window: &mut Window,
13735 cx: &mut Context<Self>,
13736 ) {
13737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13738 self.change_selections(Default::default(), window, cx, |s| {
13739 s.move_cursors_with(|map, head, _| {
13740 (
13741 movement::previous_subword_start(map, head),
13742 SelectionGoal::None,
13743 )
13744 });
13745 })
13746 }
13747
13748 pub fn select_to_previous_word_start(
13749 &mut self,
13750 _: &SelectToPreviousWordStart,
13751 window: &mut Window,
13752 cx: &mut Context<Self>,
13753 ) {
13754 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13755 self.change_selections(Default::default(), window, cx, |s| {
13756 s.move_heads_with(|map, head, _| {
13757 (
13758 movement::previous_word_start(map, head),
13759 SelectionGoal::None,
13760 )
13761 });
13762 })
13763 }
13764
13765 pub fn select_to_previous_subword_start(
13766 &mut self,
13767 _: &SelectToPreviousSubwordStart,
13768 window: &mut Window,
13769 cx: &mut Context<Self>,
13770 ) {
13771 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13772 self.change_selections(Default::default(), window, cx, |s| {
13773 s.move_heads_with(|map, head, _| {
13774 (
13775 movement::previous_subword_start(map, head),
13776 SelectionGoal::None,
13777 )
13778 });
13779 })
13780 }
13781
13782 pub fn delete_to_previous_word_start(
13783 &mut self,
13784 action: &DeleteToPreviousWordStart,
13785 window: &mut Window,
13786 cx: &mut Context<Self>,
13787 ) {
13788 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13789 self.transact(window, cx, |this, window, cx| {
13790 this.select_autoclose_pair(window, cx);
13791 this.change_selections(Default::default(), window, cx, |s| {
13792 s.move_with(|map, selection| {
13793 if selection.is_empty() {
13794 let mut cursor = if action.ignore_newlines {
13795 movement::previous_word_start(map, selection.head())
13796 } else {
13797 movement::previous_word_start_or_newline(map, selection.head())
13798 };
13799 cursor = movement::adjust_greedy_deletion(
13800 map,
13801 selection.head(),
13802 cursor,
13803 action.ignore_brackets,
13804 );
13805 selection.set_head(cursor, SelectionGoal::None);
13806 }
13807 });
13808 });
13809 this.insert("", window, cx);
13810 });
13811 }
13812
13813 pub fn delete_to_previous_subword_start(
13814 &mut self,
13815 _: &DeleteToPreviousSubwordStart,
13816 window: &mut Window,
13817 cx: &mut Context<Self>,
13818 ) {
13819 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13820 self.transact(window, cx, |this, window, cx| {
13821 this.select_autoclose_pair(window, cx);
13822 this.change_selections(Default::default(), window, cx, |s| {
13823 s.move_with(|map, selection| {
13824 if selection.is_empty() {
13825 let mut cursor = movement::previous_subword_start(map, selection.head());
13826 cursor =
13827 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13828 selection.set_head(cursor, SelectionGoal::None);
13829 }
13830 });
13831 });
13832 this.insert("", window, cx);
13833 });
13834 }
13835
13836 pub fn move_to_next_word_end(
13837 &mut self,
13838 _: &MoveToNextWordEnd,
13839 window: &mut Window,
13840 cx: &mut Context<Self>,
13841 ) {
13842 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13843 self.change_selections(Default::default(), window, cx, |s| {
13844 s.move_cursors_with(|map, head, _| {
13845 (movement::next_word_end(map, head), SelectionGoal::None)
13846 });
13847 })
13848 }
13849
13850 pub fn move_to_next_subword_end(
13851 &mut self,
13852 _: &MoveToNextSubwordEnd,
13853 window: &mut Window,
13854 cx: &mut Context<Self>,
13855 ) {
13856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13857 self.change_selections(Default::default(), window, cx, |s| {
13858 s.move_cursors_with(|map, head, _| {
13859 (movement::next_subword_end(map, head), SelectionGoal::None)
13860 });
13861 })
13862 }
13863
13864 pub fn select_to_next_word_end(
13865 &mut self,
13866 _: &SelectToNextWordEnd,
13867 window: &mut Window,
13868 cx: &mut Context<Self>,
13869 ) {
13870 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13871 self.change_selections(Default::default(), window, cx, |s| {
13872 s.move_heads_with(|map, head, _| {
13873 (movement::next_word_end(map, head), SelectionGoal::None)
13874 });
13875 })
13876 }
13877
13878 pub fn select_to_next_subword_end(
13879 &mut self,
13880 _: &SelectToNextSubwordEnd,
13881 window: &mut Window,
13882 cx: &mut Context<Self>,
13883 ) {
13884 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13885 self.change_selections(Default::default(), window, cx, |s| {
13886 s.move_heads_with(|map, head, _| {
13887 (movement::next_subword_end(map, head), SelectionGoal::None)
13888 });
13889 })
13890 }
13891
13892 pub fn delete_to_next_word_end(
13893 &mut self,
13894 action: &DeleteToNextWordEnd,
13895 window: &mut Window,
13896 cx: &mut Context<Self>,
13897 ) {
13898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13899 self.transact(window, cx, |this, window, cx| {
13900 this.change_selections(Default::default(), window, cx, |s| {
13901 s.move_with(|map, selection| {
13902 if selection.is_empty() {
13903 let mut cursor = if action.ignore_newlines {
13904 movement::next_word_end(map, selection.head())
13905 } else {
13906 movement::next_word_end_or_newline(map, selection.head())
13907 };
13908 cursor = movement::adjust_greedy_deletion(
13909 map,
13910 selection.head(),
13911 cursor,
13912 action.ignore_brackets,
13913 );
13914 selection.set_head(cursor, SelectionGoal::None);
13915 }
13916 });
13917 });
13918 this.insert("", window, cx);
13919 });
13920 }
13921
13922 pub fn delete_to_next_subword_end(
13923 &mut self,
13924 _: &DeleteToNextSubwordEnd,
13925 window: &mut Window,
13926 cx: &mut Context<Self>,
13927 ) {
13928 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13929 self.transact(window, cx, |this, window, cx| {
13930 this.change_selections(Default::default(), window, cx, |s| {
13931 s.move_with(|map, selection| {
13932 if selection.is_empty() {
13933 let mut cursor = movement::next_subword_end(map, selection.head());
13934 cursor =
13935 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13936 selection.set_head(cursor, SelectionGoal::None);
13937 }
13938 });
13939 });
13940 this.insert("", window, cx);
13941 });
13942 }
13943
13944 pub fn move_to_beginning_of_line(
13945 &mut self,
13946 action: &MoveToBeginningOfLine,
13947 window: &mut Window,
13948 cx: &mut Context<Self>,
13949 ) {
13950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13951 self.change_selections(Default::default(), window, cx, |s| {
13952 s.move_cursors_with(|map, head, _| {
13953 (
13954 movement::indented_line_beginning(
13955 map,
13956 head,
13957 action.stop_at_soft_wraps,
13958 action.stop_at_indent,
13959 ),
13960 SelectionGoal::None,
13961 )
13962 });
13963 })
13964 }
13965
13966 pub fn select_to_beginning_of_line(
13967 &mut self,
13968 action: &SelectToBeginningOfLine,
13969 window: &mut Window,
13970 cx: &mut Context<Self>,
13971 ) {
13972 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13973 self.change_selections(Default::default(), window, cx, |s| {
13974 s.move_heads_with(|map, head, _| {
13975 (
13976 movement::indented_line_beginning(
13977 map,
13978 head,
13979 action.stop_at_soft_wraps,
13980 action.stop_at_indent,
13981 ),
13982 SelectionGoal::None,
13983 )
13984 });
13985 });
13986 }
13987
13988 pub fn delete_to_beginning_of_line(
13989 &mut self,
13990 action: &DeleteToBeginningOfLine,
13991 window: &mut Window,
13992 cx: &mut Context<Self>,
13993 ) {
13994 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13995 self.transact(window, cx, |this, window, cx| {
13996 this.change_selections(Default::default(), window, cx, |s| {
13997 s.move_with(|_, selection| {
13998 selection.reversed = true;
13999 });
14000 });
14001
14002 this.select_to_beginning_of_line(
14003 &SelectToBeginningOfLine {
14004 stop_at_soft_wraps: false,
14005 stop_at_indent: action.stop_at_indent,
14006 },
14007 window,
14008 cx,
14009 );
14010 this.backspace(&Backspace, window, cx);
14011 });
14012 }
14013
14014 pub fn move_to_end_of_line(
14015 &mut self,
14016 action: &MoveToEndOfLine,
14017 window: &mut Window,
14018 cx: &mut Context<Self>,
14019 ) {
14020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14021 self.change_selections(Default::default(), window, cx, |s| {
14022 s.move_cursors_with(|map, head, _| {
14023 (
14024 movement::line_end(map, head, action.stop_at_soft_wraps),
14025 SelectionGoal::None,
14026 )
14027 });
14028 })
14029 }
14030
14031 pub fn select_to_end_of_line(
14032 &mut self,
14033 action: &SelectToEndOfLine,
14034 window: &mut Window,
14035 cx: &mut Context<Self>,
14036 ) {
14037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14038 self.change_selections(Default::default(), window, cx, |s| {
14039 s.move_heads_with(|map, head, _| {
14040 (
14041 movement::line_end(map, head, action.stop_at_soft_wraps),
14042 SelectionGoal::None,
14043 )
14044 });
14045 })
14046 }
14047
14048 pub fn delete_to_end_of_line(
14049 &mut self,
14050 _: &DeleteToEndOfLine,
14051 window: &mut Window,
14052 cx: &mut Context<Self>,
14053 ) {
14054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14055 self.transact(window, cx, |this, window, cx| {
14056 this.select_to_end_of_line(
14057 &SelectToEndOfLine {
14058 stop_at_soft_wraps: false,
14059 },
14060 window,
14061 cx,
14062 );
14063 this.delete(&Delete, window, cx);
14064 });
14065 }
14066
14067 pub fn cut_to_end_of_line(
14068 &mut self,
14069 action: &CutToEndOfLine,
14070 window: &mut Window,
14071 cx: &mut Context<Self>,
14072 ) {
14073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14074 self.transact(window, cx, |this, window, cx| {
14075 this.select_to_end_of_line(
14076 &SelectToEndOfLine {
14077 stop_at_soft_wraps: false,
14078 },
14079 window,
14080 cx,
14081 );
14082 if !action.stop_at_newlines {
14083 this.change_selections(Default::default(), window, cx, |s| {
14084 s.move_with(|_, sel| {
14085 if sel.is_empty() {
14086 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14087 }
14088 });
14089 });
14090 }
14091 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14092 let item = this.cut_common(false, window, cx);
14093 cx.write_to_clipboard(item);
14094 });
14095 }
14096
14097 pub fn move_to_start_of_paragraph(
14098 &mut self,
14099 _: &MoveToStartOfParagraph,
14100 window: &mut Window,
14101 cx: &mut Context<Self>,
14102 ) {
14103 if matches!(self.mode, EditorMode::SingleLine) {
14104 cx.propagate();
14105 return;
14106 }
14107 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14108 self.change_selections(Default::default(), window, cx, |s| {
14109 s.move_with(|map, selection| {
14110 selection.collapse_to(
14111 movement::start_of_paragraph(map, selection.head(), 1),
14112 SelectionGoal::None,
14113 )
14114 });
14115 })
14116 }
14117
14118 pub fn move_to_end_of_paragraph(
14119 &mut self,
14120 _: &MoveToEndOfParagraph,
14121 window: &mut Window,
14122 cx: &mut Context<Self>,
14123 ) {
14124 if matches!(self.mode, EditorMode::SingleLine) {
14125 cx.propagate();
14126 return;
14127 }
14128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14129 self.change_selections(Default::default(), window, cx, |s| {
14130 s.move_with(|map, selection| {
14131 selection.collapse_to(
14132 movement::end_of_paragraph(map, selection.head(), 1),
14133 SelectionGoal::None,
14134 )
14135 });
14136 })
14137 }
14138
14139 pub fn select_to_start_of_paragraph(
14140 &mut self,
14141 _: &SelectToStartOfParagraph,
14142 window: &mut Window,
14143 cx: &mut Context<Self>,
14144 ) {
14145 if matches!(self.mode, EditorMode::SingleLine) {
14146 cx.propagate();
14147 return;
14148 }
14149 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14150 self.change_selections(Default::default(), window, cx, |s| {
14151 s.move_heads_with(|map, head, _| {
14152 (
14153 movement::start_of_paragraph(map, head, 1),
14154 SelectionGoal::None,
14155 )
14156 });
14157 })
14158 }
14159
14160 pub fn select_to_end_of_paragraph(
14161 &mut self,
14162 _: &SelectToEndOfParagraph,
14163 window: &mut Window,
14164 cx: &mut Context<Self>,
14165 ) {
14166 if matches!(self.mode, EditorMode::SingleLine) {
14167 cx.propagate();
14168 return;
14169 }
14170 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14171 self.change_selections(Default::default(), window, cx, |s| {
14172 s.move_heads_with(|map, head, _| {
14173 (
14174 movement::end_of_paragraph(map, head, 1),
14175 SelectionGoal::None,
14176 )
14177 });
14178 })
14179 }
14180
14181 pub fn move_to_start_of_excerpt(
14182 &mut self,
14183 _: &MoveToStartOfExcerpt,
14184 window: &mut Window,
14185 cx: &mut Context<Self>,
14186 ) {
14187 if matches!(self.mode, EditorMode::SingleLine) {
14188 cx.propagate();
14189 return;
14190 }
14191 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14192 self.change_selections(Default::default(), window, cx, |s| {
14193 s.move_with(|map, selection| {
14194 selection.collapse_to(
14195 movement::start_of_excerpt(
14196 map,
14197 selection.head(),
14198 workspace::searchable::Direction::Prev,
14199 ),
14200 SelectionGoal::None,
14201 )
14202 });
14203 })
14204 }
14205
14206 pub fn move_to_start_of_next_excerpt(
14207 &mut self,
14208 _: &MoveToStartOfNextExcerpt,
14209 window: &mut Window,
14210 cx: &mut Context<Self>,
14211 ) {
14212 if matches!(self.mode, EditorMode::SingleLine) {
14213 cx.propagate();
14214 return;
14215 }
14216
14217 self.change_selections(Default::default(), window, cx, |s| {
14218 s.move_with(|map, selection| {
14219 selection.collapse_to(
14220 movement::start_of_excerpt(
14221 map,
14222 selection.head(),
14223 workspace::searchable::Direction::Next,
14224 ),
14225 SelectionGoal::None,
14226 )
14227 });
14228 })
14229 }
14230
14231 pub fn move_to_end_of_excerpt(
14232 &mut self,
14233 _: &MoveToEndOfExcerpt,
14234 window: &mut Window,
14235 cx: &mut Context<Self>,
14236 ) {
14237 if matches!(self.mode, EditorMode::SingleLine) {
14238 cx.propagate();
14239 return;
14240 }
14241 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14242 self.change_selections(Default::default(), window, cx, |s| {
14243 s.move_with(|map, selection| {
14244 selection.collapse_to(
14245 movement::end_of_excerpt(
14246 map,
14247 selection.head(),
14248 workspace::searchable::Direction::Next,
14249 ),
14250 SelectionGoal::None,
14251 )
14252 });
14253 })
14254 }
14255
14256 pub fn move_to_end_of_previous_excerpt(
14257 &mut self,
14258 _: &MoveToEndOfPreviousExcerpt,
14259 window: &mut Window,
14260 cx: &mut Context<Self>,
14261 ) {
14262 if matches!(self.mode, EditorMode::SingleLine) {
14263 cx.propagate();
14264 return;
14265 }
14266 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14267 self.change_selections(Default::default(), window, cx, |s| {
14268 s.move_with(|map, selection| {
14269 selection.collapse_to(
14270 movement::end_of_excerpt(
14271 map,
14272 selection.head(),
14273 workspace::searchable::Direction::Prev,
14274 ),
14275 SelectionGoal::None,
14276 )
14277 });
14278 })
14279 }
14280
14281 pub fn select_to_start_of_excerpt(
14282 &mut self,
14283 _: &SelectToStartOfExcerpt,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) {
14287 if matches!(self.mode, EditorMode::SingleLine) {
14288 cx.propagate();
14289 return;
14290 }
14291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14292 self.change_selections(Default::default(), window, cx, |s| {
14293 s.move_heads_with(|map, head, _| {
14294 (
14295 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14296 SelectionGoal::None,
14297 )
14298 });
14299 })
14300 }
14301
14302 pub fn select_to_start_of_next_excerpt(
14303 &mut self,
14304 _: &SelectToStartOfNextExcerpt,
14305 window: &mut Window,
14306 cx: &mut Context<Self>,
14307 ) {
14308 if matches!(self.mode, EditorMode::SingleLine) {
14309 cx.propagate();
14310 return;
14311 }
14312 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14313 self.change_selections(Default::default(), window, cx, |s| {
14314 s.move_heads_with(|map, head, _| {
14315 (
14316 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14317 SelectionGoal::None,
14318 )
14319 });
14320 })
14321 }
14322
14323 pub fn select_to_end_of_excerpt(
14324 &mut self,
14325 _: &SelectToEndOfExcerpt,
14326 window: &mut Window,
14327 cx: &mut Context<Self>,
14328 ) {
14329 if matches!(self.mode, EditorMode::SingleLine) {
14330 cx.propagate();
14331 return;
14332 }
14333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14334 self.change_selections(Default::default(), window, cx, |s| {
14335 s.move_heads_with(|map, head, _| {
14336 (
14337 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14338 SelectionGoal::None,
14339 )
14340 });
14341 })
14342 }
14343
14344 pub fn select_to_end_of_previous_excerpt(
14345 &mut self,
14346 _: &SelectToEndOfPreviousExcerpt,
14347 window: &mut Window,
14348 cx: &mut Context<Self>,
14349 ) {
14350 if matches!(self.mode, EditorMode::SingleLine) {
14351 cx.propagate();
14352 return;
14353 }
14354 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14355 self.change_selections(Default::default(), window, cx, |s| {
14356 s.move_heads_with(|map, head, _| {
14357 (
14358 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14359 SelectionGoal::None,
14360 )
14361 });
14362 })
14363 }
14364
14365 pub fn move_to_beginning(
14366 &mut self,
14367 _: &MoveToBeginning,
14368 window: &mut Window,
14369 cx: &mut Context<Self>,
14370 ) {
14371 if matches!(self.mode, EditorMode::SingleLine) {
14372 cx.propagate();
14373 return;
14374 }
14375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14376 self.change_selections(Default::default(), window, cx, |s| {
14377 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14378 });
14379 }
14380
14381 pub fn select_to_beginning(
14382 &mut self,
14383 _: &SelectToBeginning,
14384 window: &mut Window,
14385 cx: &mut Context<Self>,
14386 ) {
14387 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14388 selection.set_head(Point::zero(), SelectionGoal::None);
14389 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14390 self.change_selections(Default::default(), window, cx, |s| {
14391 s.select(vec![selection]);
14392 });
14393 }
14394
14395 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14396 if matches!(self.mode, EditorMode::SingleLine) {
14397 cx.propagate();
14398 return;
14399 }
14400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14401 let cursor = self.buffer.read(cx).read(cx).len();
14402 self.change_selections(Default::default(), window, cx, |s| {
14403 s.select_ranges(vec![cursor..cursor])
14404 });
14405 }
14406
14407 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14408 self.nav_history = nav_history;
14409 }
14410
14411 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14412 self.nav_history.as_ref()
14413 }
14414
14415 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14416 self.push_to_nav_history(
14417 self.selections.newest_anchor().head(),
14418 None,
14419 false,
14420 true,
14421 cx,
14422 );
14423 }
14424
14425 fn push_to_nav_history(
14426 &mut self,
14427 cursor_anchor: Anchor,
14428 new_position: Option<Point>,
14429 is_deactivate: bool,
14430 always: bool,
14431 cx: &mut Context<Self>,
14432 ) {
14433 if let Some(nav_history) = self.nav_history.as_mut() {
14434 let buffer = self.buffer.read(cx).read(cx);
14435 let cursor_position = cursor_anchor.to_point(&buffer);
14436 let scroll_state = self.scroll_manager.anchor();
14437 let scroll_top_row = scroll_state.top_row(&buffer);
14438 drop(buffer);
14439
14440 if let Some(new_position) = new_position {
14441 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14442 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14443 return;
14444 }
14445 }
14446
14447 nav_history.push(
14448 Some(NavigationData {
14449 cursor_anchor,
14450 cursor_position,
14451 scroll_anchor: scroll_state,
14452 scroll_top_row,
14453 }),
14454 cx,
14455 );
14456 cx.emit(EditorEvent::PushedToNavHistory {
14457 anchor: cursor_anchor,
14458 is_deactivate,
14459 })
14460 }
14461 }
14462
14463 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14464 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14465 let buffer = self.buffer.read(cx).snapshot(cx);
14466 let mut selection = self
14467 .selections
14468 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14469 selection.set_head(buffer.len(), SelectionGoal::None);
14470 self.change_selections(Default::default(), window, cx, |s| {
14471 s.select(vec![selection]);
14472 });
14473 }
14474
14475 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14476 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14477 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14478 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14479 });
14480 }
14481
14482 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14483 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14484 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14485 let mut selections = self.selections.all::<Point>(&display_map);
14486 let max_point = display_map.buffer_snapshot().max_point();
14487 for selection in &mut selections {
14488 let rows = selection.spanned_rows(true, &display_map);
14489 selection.start = Point::new(rows.start.0, 0);
14490 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14491 selection.reversed = false;
14492 }
14493 self.change_selections(Default::default(), window, cx, |s| {
14494 s.select(selections);
14495 });
14496 }
14497
14498 pub fn split_selection_into_lines(
14499 &mut self,
14500 action: &SplitSelectionIntoLines,
14501 window: &mut Window,
14502 cx: &mut Context<Self>,
14503 ) {
14504 let selections = self
14505 .selections
14506 .all::<Point>(&self.display_snapshot(cx))
14507 .into_iter()
14508 .map(|selection| selection.start..selection.end)
14509 .collect::<Vec<_>>();
14510 self.unfold_ranges(&selections, true, true, cx);
14511
14512 let mut new_selection_ranges = Vec::new();
14513 {
14514 let buffer = self.buffer.read(cx).read(cx);
14515 for selection in selections {
14516 for row in selection.start.row..selection.end.row {
14517 let line_start = Point::new(row, 0);
14518 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14519
14520 if action.keep_selections {
14521 // Keep the selection range for each line
14522 let selection_start = if row == selection.start.row {
14523 selection.start
14524 } else {
14525 line_start
14526 };
14527 new_selection_ranges.push(selection_start..line_end);
14528 } else {
14529 // Collapse to cursor at end of line
14530 new_selection_ranges.push(line_end..line_end);
14531 }
14532 }
14533
14534 let is_multiline_selection = selection.start.row != selection.end.row;
14535 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14536 // so this action feels more ergonomic when paired with other selection operations
14537 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14538 if !should_skip_last {
14539 if action.keep_selections {
14540 if is_multiline_selection {
14541 let line_start = Point::new(selection.end.row, 0);
14542 new_selection_ranges.push(line_start..selection.end);
14543 } else {
14544 new_selection_ranges.push(selection.start..selection.end);
14545 }
14546 } else {
14547 new_selection_ranges.push(selection.end..selection.end);
14548 }
14549 }
14550 }
14551 }
14552 self.change_selections(Default::default(), window, cx, |s| {
14553 s.select_ranges(new_selection_ranges);
14554 });
14555 }
14556
14557 pub fn add_selection_above(
14558 &mut self,
14559 action: &AddSelectionAbove,
14560 window: &mut Window,
14561 cx: &mut Context<Self>,
14562 ) {
14563 self.add_selection(true, action.skip_soft_wrap, window, cx);
14564 }
14565
14566 pub fn add_selection_below(
14567 &mut self,
14568 action: &AddSelectionBelow,
14569 window: &mut Window,
14570 cx: &mut Context<Self>,
14571 ) {
14572 self.add_selection(false, action.skip_soft_wrap, window, cx);
14573 }
14574
14575 fn add_selection(
14576 &mut self,
14577 above: bool,
14578 skip_soft_wrap: bool,
14579 window: &mut Window,
14580 cx: &mut Context<Self>,
14581 ) {
14582 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14583
14584 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14585 let all_selections = self.selections.all::<Point>(&display_map);
14586 let text_layout_details = self.text_layout_details(window);
14587
14588 let (mut columnar_selections, new_selections_to_columnarize) = {
14589 if let Some(state) = self.add_selections_state.as_ref() {
14590 let columnar_selection_ids: HashSet<_> = state
14591 .groups
14592 .iter()
14593 .flat_map(|group| group.stack.iter())
14594 .copied()
14595 .collect();
14596
14597 all_selections
14598 .into_iter()
14599 .partition(|s| columnar_selection_ids.contains(&s.id))
14600 } else {
14601 (Vec::new(), all_selections)
14602 }
14603 };
14604
14605 let mut state = self
14606 .add_selections_state
14607 .take()
14608 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14609
14610 for selection in new_selections_to_columnarize {
14611 let range = selection.display_range(&display_map).sorted();
14612 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14613 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14614 let positions = start_x.min(end_x)..start_x.max(end_x);
14615 let mut stack = Vec::new();
14616 for row in range.start.row().0..=range.end.row().0 {
14617 if let Some(selection) = self.selections.build_columnar_selection(
14618 &display_map,
14619 DisplayRow(row),
14620 &positions,
14621 selection.reversed,
14622 &text_layout_details,
14623 ) {
14624 stack.push(selection.id);
14625 columnar_selections.push(selection);
14626 }
14627 }
14628 if !stack.is_empty() {
14629 if above {
14630 stack.reverse();
14631 }
14632 state.groups.push(AddSelectionsGroup { above, stack });
14633 }
14634 }
14635
14636 let mut final_selections = Vec::new();
14637 let end_row = if above {
14638 DisplayRow(0)
14639 } else {
14640 display_map.max_point().row()
14641 };
14642
14643 let mut last_added_item_per_group = HashMap::default();
14644 for group in state.groups.iter_mut() {
14645 if let Some(last_id) = group.stack.last() {
14646 last_added_item_per_group.insert(*last_id, group);
14647 }
14648 }
14649
14650 for selection in columnar_selections {
14651 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14652 if above == group.above {
14653 let range = selection.display_range(&display_map).sorted();
14654 debug_assert_eq!(range.start.row(), range.end.row());
14655 let mut row = range.start.row();
14656 let positions =
14657 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14658 Pixels::from(start)..Pixels::from(end)
14659 } else {
14660 let start_x =
14661 display_map.x_for_display_point(range.start, &text_layout_details);
14662 let end_x =
14663 display_map.x_for_display_point(range.end, &text_layout_details);
14664 start_x.min(end_x)..start_x.max(end_x)
14665 };
14666
14667 let mut maybe_new_selection = None;
14668 let direction = if above { -1 } else { 1 };
14669
14670 while row != end_row {
14671 if skip_soft_wrap {
14672 row = display_map
14673 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14674 .row();
14675 } else if above {
14676 row.0 -= 1;
14677 } else {
14678 row.0 += 1;
14679 }
14680
14681 if let Some(new_selection) = self.selections.build_columnar_selection(
14682 &display_map,
14683 row,
14684 &positions,
14685 selection.reversed,
14686 &text_layout_details,
14687 ) {
14688 maybe_new_selection = Some(new_selection);
14689 break;
14690 }
14691 }
14692
14693 if let Some(new_selection) = maybe_new_selection {
14694 group.stack.push(new_selection.id);
14695 if above {
14696 final_selections.push(new_selection);
14697 final_selections.push(selection);
14698 } else {
14699 final_selections.push(selection);
14700 final_selections.push(new_selection);
14701 }
14702 } else {
14703 final_selections.push(selection);
14704 }
14705 } else {
14706 group.stack.pop();
14707 }
14708 } else {
14709 final_selections.push(selection);
14710 }
14711 }
14712
14713 self.change_selections(Default::default(), window, cx, |s| {
14714 s.select(final_selections);
14715 });
14716
14717 let final_selection_ids: HashSet<_> = self
14718 .selections
14719 .all::<Point>(&display_map)
14720 .iter()
14721 .map(|s| s.id)
14722 .collect();
14723 state.groups.retain_mut(|group| {
14724 // selections might get merged above so we remove invalid items from stacks
14725 group.stack.retain(|id| final_selection_ids.contains(id));
14726
14727 // single selection in stack can be treated as initial state
14728 group.stack.len() > 1
14729 });
14730
14731 if !state.groups.is_empty() {
14732 self.add_selections_state = Some(state);
14733 }
14734 }
14735
14736 fn select_match_ranges(
14737 &mut self,
14738 range: Range<MultiBufferOffset>,
14739 reversed: bool,
14740 replace_newest: bool,
14741 auto_scroll: Option<Autoscroll>,
14742 window: &mut Window,
14743 cx: &mut Context<Editor>,
14744 ) {
14745 self.unfold_ranges(
14746 std::slice::from_ref(&range),
14747 false,
14748 auto_scroll.is_some(),
14749 cx,
14750 );
14751 let effects = if let Some(scroll) = auto_scroll {
14752 SelectionEffects::scroll(scroll)
14753 } else {
14754 SelectionEffects::no_scroll()
14755 };
14756 self.change_selections(effects, window, cx, |s| {
14757 if replace_newest {
14758 s.delete(s.newest_anchor().id);
14759 }
14760 if reversed {
14761 s.insert_range(range.end..range.start);
14762 } else {
14763 s.insert_range(range);
14764 }
14765 });
14766 }
14767
14768 pub fn select_next_match_internal(
14769 &mut self,
14770 display_map: &DisplaySnapshot,
14771 replace_newest: bool,
14772 autoscroll: Option<Autoscroll>,
14773 window: &mut Window,
14774 cx: &mut Context<Self>,
14775 ) -> Result<()> {
14776 let buffer = display_map.buffer_snapshot();
14777 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14778 if let Some(mut select_next_state) = self.select_next_state.take() {
14779 let query = &select_next_state.query;
14780 if !select_next_state.done {
14781 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14782 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14783 let mut next_selected_range = None;
14784
14785 let bytes_after_last_selection =
14786 buffer.bytes_in_range(last_selection.end..buffer.len());
14787 let bytes_before_first_selection =
14788 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14789 let query_matches = query
14790 .stream_find_iter(bytes_after_last_selection)
14791 .map(|result| (last_selection.end, result))
14792 .chain(
14793 query
14794 .stream_find_iter(bytes_before_first_selection)
14795 .map(|result| (MultiBufferOffset(0), result)),
14796 );
14797
14798 for (start_offset, query_match) in query_matches {
14799 let query_match = query_match.unwrap(); // can only fail due to I/O
14800 let offset_range =
14801 start_offset + query_match.start()..start_offset + query_match.end();
14802
14803 if !select_next_state.wordwise
14804 || (!buffer.is_inside_word(offset_range.start, None)
14805 && !buffer.is_inside_word(offset_range.end, None))
14806 {
14807 let idx = selections
14808 .partition_point(|selection| selection.end <= offset_range.start);
14809 let overlaps = selections
14810 .get(idx)
14811 .map_or(false, |selection| selection.start < offset_range.end);
14812
14813 if !overlaps {
14814 next_selected_range = Some(offset_range);
14815 break;
14816 }
14817 }
14818 }
14819
14820 if let Some(next_selected_range) = next_selected_range {
14821 self.select_match_ranges(
14822 next_selected_range,
14823 last_selection.reversed,
14824 replace_newest,
14825 autoscroll,
14826 window,
14827 cx,
14828 );
14829 } else {
14830 select_next_state.done = true;
14831 }
14832 }
14833
14834 self.select_next_state = Some(select_next_state);
14835 } else {
14836 let mut only_carets = true;
14837 let mut same_text_selected = true;
14838 let mut selected_text = None;
14839
14840 let mut selections_iter = selections.iter().peekable();
14841 while let Some(selection) = selections_iter.next() {
14842 if selection.start != selection.end {
14843 only_carets = false;
14844 }
14845
14846 if same_text_selected {
14847 if selected_text.is_none() {
14848 selected_text =
14849 Some(buffer.text_for_range(selection.range()).collect::<String>());
14850 }
14851
14852 if let Some(next_selection) = selections_iter.peek() {
14853 if next_selection.len() == selection.len() {
14854 let next_selected_text = buffer
14855 .text_for_range(next_selection.range())
14856 .collect::<String>();
14857 if Some(next_selected_text) != selected_text {
14858 same_text_selected = false;
14859 selected_text = None;
14860 }
14861 } else {
14862 same_text_selected = false;
14863 selected_text = None;
14864 }
14865 }
14866 }
14867 }
14868
14869 if only_carets {
14870 for selection in &mut selections {
14871 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14872 selection.start = word_range.start;
14873 selection.end = word_range.end;
14874 selection.goal = SelectionGoal::None;
14875 selection.reversed = false;
14876 self.select_match_ranges(
14877 selection.start..selection.end,
14878 selection.reversed,
14879 replace_newest,
14880 autoscroll,
14881 window,
14882 cx,
14883 );
14884 }
14885
14886 if selections.len() == 1 {
14887 let selection = selections
14888 .last()
14889 .expect("ensured that there's only one selection");
14890 let query = buffer
14891 .text_for_range(selection.start..selection.end)
14892 .collect::<String>();
14893 let is_empty = query.is_empty();
14894 let select_state = SelectNextState {
14895 query: self.build_query(&[query], cx)?,
14896 wordwise: true,
14897 done: is_empty,
14898 };
14899 self.select_next_state = Some(select_state);
14900 } else {
14901 self.select_next_state = None;
14902 }
14903 } else if let Some(selected_text) = selected_text {
14904 self.select_next_state = Some(SelectNextState {
14905 query: self.build_query(&[selected_text], cx)?,
14906 wordwise: false,
14907 done: false,
14908 });
14909 self.select_next_match_internal(
14910 display_map,
14911 replace_newest,
14912 autoscroll,
14913 window,
14914 cx,
14915 )?;
14916 }
14917 }
14918 Ok(())
14919 }
14920
14921 pub fn select_all_matches(
14922 &mut self,
14923 _action: &SelectAllMatches,
14924 window: &mut Window,
14925 cx: &mut Context<Self>,
14926 ) -> Result<()> {
14927 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14928
14929 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14930
14931 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14932 let Some(select_next_state) = self.select_next_state.as_mut() else {
14933 return Ok(());
14934 };
14935 if select_next_state.done {
14936 return Ok(());
14937 }
14938
14939 let mut new_selections = Vec::new();
14940
14941 let reversed = self
14942 .selections
14943 .oldest::<MultiBufferOffset>(&display_map)
14944 .reversed;
14945 let buffer = display_map.buffer_snapshot();
14946 let query_matches = select_next_state
14947 .query
14948 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14949
14950 for query_match in query_matches.into_iter() {
14951 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14952 let offset_range = if reversed {
14953 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14954 } else {
14955 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14956 };
14957
14958 if !select_next_state.wordwise
14959 || (!buffer.is_inside_word(offset_range.start, None)
14960 && !buffer.is_inside_word(offset_range.end, None))
14961 {
14962 new_selections.push(offset_range.start..offset_range.end);
14963 }
14964 }
14965
14966 select_next_state.done = true;
14967
14968 if new_selections.is_empty() {
14969 log::error!("bug: new_selections is empty in select_all_matches");
14970 return Ok(());
14971 }
14972
14973 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14974 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14975 selections.select_ranges(new_selections)
14976 });
14977
14978 Ok(())
14979 }
14980
14981 pub fn select_next(
14982 &mut self,
14983 action: &SelectNext,
14984 window: &mut Window,
14985 cx: &mut Context<Self>,
14986 ) -> Result<()> {
14987 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14988 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14989 self.select_next_match_internal(
14990 &display_map,
14991 action.replace_newest,
14992 Some(Autoscroll::newest()),
14993 window,
14994 cx,
14995 )?;
14996 Ok(())
14997 }
14998
14999 pub fn select_previous(
15000 &mut self,
15001 action: &SelectPrevious,
15002 window: &mut Window,
15003 cx: &mut Context<Self>,
15004 ) -> Result<()> {
15005 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15006 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15007 let buffer = display_map.buffer_snapshot();
15008 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15009 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15010 let query = &select_prev_state.query;
15011 if !select_prev_state.done {
15012 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15013 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15014 let mut next_selected_range = None;
15015 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15016 let bytes_before_last_selection =
15017 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15018 let bytes_after_first_selection =
15019 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15020 let query_matches = query
15021 .stream_find_iter(bytes_before_last_selection)
15022 .map(|result| (last_selection.start, result))
15023 .chain(
15024 query
15025 .stream_find_iter(bytes_after_first_selection)
15026 .map(|result| (buffer.len(), result)),
15027 );
15028 for (end_offset, query_match) in query_matches {
15029 let query_match = query_match.unwrap(); // can only fail due to I/O
15030 let offset_range =
15031 end_offset - query_match.end()..end_offset - query_match.start();
15032
15033 if !select_prev_state.wordwise
15034 || (!buffer.is_inside_word(offset_range.start, None)
15035 && !buffer.is_inside_word(offset_range.end, None))
15036 {
15037 next_selected_range = Some(offset_range);
15038 break;
15039 }
15040 }
15041
15042 if let Some(next_selected_range) = next_selected_range {
15043 self.select_match_ranges(
15044 next_selected_range,
15045 last_selection.reversed,
15046 action.replace_newest,
15047 Some(Autoscroll::newest()),
15048 window,
15049 cx,
15050 );
15051 } else {
15052 select_prev_state.done = true;
15053 }
15054 }
15055
15056 self.select_prev_state = Some(select_prev_state);
15057 } else {
15058 let mut only_carets = true;
15059 let mut same_text_selected = true;
15060 let mut selected_text = None;
15061
15062 let mut selections_iter = selections.iter().peekable();
15063 while let Some(selection) = selections_iter.next() {
15064 if selection.start != selection.end {
15065 only_carets = false;
15066 }
15067
15068 if same_text_selected {
15069 if selected_text.is_none() {
15070 selected_text =
15071 Some(buffer.text_for_range(selection.range()).collect::<String>());
15072 }
15073
15074 if let Some(next_selection) = selections_iter.peek() {
15075 if next_selection.len() == selection.len() {
15076 let next_selected_text = buffer
15077 .text_for_range(next_selection.range())
15078 .collect::<String>();
15079 if Some(next_selected_text) != selected_text {
15080 same_text_selected = false;
15081 selected_text = None;
15082 }
15083 } else {
15084 same_text_selected = false;
15085 selected_text = None;
15086 }
15087 }
15088 }
15089 }
15090
15091 if only_carets {
15092 for selection in &mut selections {
15093 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15094 selection.start = word_range.start;
15095 selection.end = word_range.end;
15096 selection.goal = SelectionGoal::None;
15097 selection.reversed = false;
15098 self.select_match_ranges(
15099 selection.start..selection.end,
15100 selection.reversed,
15101 action.replace_newest,
15102 Some(Autoscroll::newest()),
15103 window,
15104 cx,
15105 );
15106 }
15107 if selections.len() == 1 {
15108 let selection = selections
15109 .last()
15110 .expect("ensured that there's only one selection");
15111 let query = buffer
15112 .text_for_range(selection.start..selection.end)
15113 .collect::<String>();
15114 let is_empty = query.is_empty();
15115 let select_state = SelectNextState {
15116 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15117 wordwise: true,
15118 done: is_empty,
15119 };
15120 self.select_prev_state = Some(select_state);
15121 } else {
15122 self.select_prev_state = None;
15123 }
15124 } else if let Some(selected_text) = selected_text {
15125 self.select_prev_state = Some(SelectNextState {
15126 query: self
15127 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15128 wordwise: false,
15129 done: false,
15130 });
15131 self.select_previous(action, window, cx)?;
15132 }
15133 }
15134 Ok(())
15135 }
15136
15137 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15138 /// setting the case sensitivity based on the global
15139 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15140 /// editor's settings.
15141 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15142 where
15143 I: IntoIterator<Item = P>,
15144 P: AsRef<[u8]>,
15145 {
15146 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15147 || EditorSettings::get_global(cx).search.case_sensitive,
15148 |value| value,
15149 );
15150
15151 let mut builder = AhoCorasickBuilder::new();
15152 builder.ascii_case_insensitive(!case_sensitive);
15153 builder.build(patterns)
15154 }
15155
15156 pub fn find_next_match(
15157 &mut self,
15158 _: &FindNextMatch,
15159 window: &mut Window,
15160 cx: &mut Context<Self>,
15161 ) -> Result<()> {
15162 let selections = self.selections.disjoint_anchors_arc();
15163 match selections.first() {
15164 Some(first) if selections.len() >= 2 => {
15165 self.change_selections(Default::default(), window, cx, |s| {
15166 s.select_ranges([first.range()]);
15167 });
15168 }
15169 _ => self.select_next(
15170 &SelectNext {
15171 replace_newest: true,
15172 },
15173 window,
15174 cx,
15175 )?,
15176 }
15177 Ok(())
15178 }
15179
15180 pub fn find_previous_match(
15181 &mut self,
15182 _: &FindPreviousMatch,
15183 window: &mut Window,
15184 cx: &mut Context<Self>,
15185 ) -> Result<()> {
15186 let selections = self.selections.disjoint_anchors_arc();
15187 match selections.last() {
15188 Some(last) if selections.len() >= 2 => {
15189 self.change_selections(Default::default(), window, cx, |s| {
15190 s.select_ranges([last.range()]);
15191 });
15192 }
15193 _ => self.select_previous(
15194 &SelectPrevious {
15195 replace_newest: true,
15196 },
15197 window,
15198 cx,
15199 )?,
15200 }
15201 Ok(())
15202 }
15203
15204 pub fn toggle_comments(
15205 &mut self,
15206 action: &ToggleComments,
15207 window: &mut Window,
15208 cx: &mut Context<Self>,
15209 ) {
15210 if self.read_only(cx) {
15211 return;
15212 }
15213 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15214 let text_layout_details = &self.text_layout_details(window);
15215 self.transact(window, cx, |this, window, cx| {
15216 let mut selections = this
15217 .selections
15218 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15219 let mut edits = Vec::new();
15220 let mut selection_edit_ranges = Vec::new();
15221 let mut last_toggled_row = None;
15222 let snapshot = this.buffer.read(cx).read(cx);
15223 let empty_str: Arc<str> = Arc::default();
15224 let mut suffixes_inserted = Vec::new();
15225 let ignore_indent = action.ignore_indent;
15226
15227 fn comment_prefix_range(
15228 snapshot: &MultiBufferSnapshot,
15229 row: MultiBufferRow,
15230 comment_prefix: &str,
15231 comment_prefix_whitespace: &str,
15232 ignore_indent: bool,
15233 ) -> Range<Point> {
15234 let indent_size = if ignore_indent {
15235 0
15236 } else {
15237 snapshot.indent_size_for_line(row).len
15238 };
15239
15240 let start = Point::new(row.0, indent_size);
15241
15242 let mut line_bytes = snapshot
15243 .bytes_in_range(start..snapshot.max_point())
15244 .flatten()
15245 .copied();
15246
15247 // If this line currently begins with the line comment prefix, then record
15248 // the range containing the prefix.
15249 if line_bytes
15250 .by_ref()
15251 .take(comment_prefix.len())
15252 .eq(comment_prefix.bytes())
15253 {
15254 // Include any whitespace that matches the comment prefix.
15255 let matching_whitespace_len = line_bytes
15256 .zip(comment_prefix_whitespace.bytes())
15257 .take_while(|(a, b)| a == b)
15258 .count() as u32;
15259 let end = Point::new(
15260 start.row,
15261 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15262 );
15263 start..end
15264 } else {
15265 start..start
15266 }
15267 }
15268
15269 fn comment_suffix_range(
15270 snapshot: &MultiBufferSnapshot,
15271 row: MultiBufferRow,
15272 comment_suffix: &str,
15273 comment_suffix_has_leading_space: bool,
15274 ) -> Range<Point> {
15275 let end = Point::new(row.0, snapshot.line_len(row));
15276 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15277
15278 let mut line_end_bytes = snapshot
15279 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15280 .flatten()
15281 .copied();
15282
15283 let leading_space_len = if suffix_start_column > 0
15284 && line_end_bytes.next() == Some(b' ')
15285 && comment_suffix_has_leading_space
15286 {
15287 1
15288 } else {
15289 0
15290 };
15291
15292 // If this line currently begins with the line comment prefix, then record
15293 // the range containing the prefix.
15294 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15295 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15296 start..end
15297 } else {
15298 end..end
15299 }
15300 }
15301
15302 // TODO: Handle selections that cross excerpts
15303 for selection in &mut selections {
15304 let start_column = snapshot
15305 .indent_size_for_line(MultiBufferRow(selection.start.row))
15306 .len;
15307 let language = if let Some(language) =
15308 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15309 {
15310 language
15311 } else {
15312 continue;
15313 };
15314
15315 selection_edit_ranges.clear();
15316
15317 // If multiple selections contain a given row, avoid processing that
15318 // row more than once.
15319 let mut start_row = MultiBufferRow(selection.start.row);
15320 if last_toggled_row == Some(start_row) {
15321 start_row = start_row.next_row();
15322 }
15323 let end_row =
15324 if selection.end.row > selection.start.row && selection.end.column == 0 {
15325 MultiBufferRow(selection.end.row - 1)
15326 } else {
15327 MultiBufferRow(selection.end.row)
15328 };
15329 last_toggled_row = Some(end_row);
15330
15331 if start_row > end_row {
15332 continue;
15333 }
15334
15335 // If the language has line comments, toggle those.
15336 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15337
15338 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15339 if ignore_indent {
15340 full_comment_prefixes = full_comment_prefixes
15341 .into_iter()
15342 .map(|s| Arc::from(s.trim_end()))
15343 .collect();
15344 }
15345
15346 if !full_comment_prefixes.is_empty() {
15347 let first_prefix = full_comment_prefixes
15348 .first()
15349 .expect("prefixes is non-empty");
15350 let prefix_trimmed_lengths = full_comment_prefixes
15351 .iter()
15352 .map(|p| p.trim_end_matches(' ').len())
15353 .collect::<SmallVec<[usize; 4]>>();
15354
15355 let mut all_selection_lines_are_comments = true;
15356
15357 for row in start_row.0..=end_row.0 {
15358 let row = MultiBufferRow(row);
15359 if start_row < end_row && snapshot.is_line_blank(row) {
15360 continue;
15361 }
15362
15363 let prefix_range = full_comment_prefixes
15364 .iter()
15365 .zip(prefix_trimmed_lengths.iter().copied())
15366 .map(|(prefix, trimmed_prefix_len)| {
15367 comment_prefix_range(
15368 snapshot.deref(),
15369 row,
15370 &prefix[..trimmed_prefix_len],
15371 &prefix[trimmed_prefix_len..],
15372 ignore_indent,
15373 )
15374 })
15375 .max_by_key(|range| range.end.column - range.start.column)
15376 .expect("prefixes is non-empty");
15377
15378 if prefix_range.is_empty() {
15379 all_selection_lines_are_comments = false;
15380 }
15381
15382 selection_edit_ranges.push(prefix_range);
15383 }
15384
15385 if all_selection_lines_are_comments {
15386 edits.extend(
15387 selection_edit_ranges
15388 .iter()
15389 .cloned()
15390 .map(|range| (range, empty_str.clone())),
15391 );
15392 } else {
15393 let min_column = selection_edit_ranges
15394 .iter()
15395 .map(|range| range.start.column)
15396 .min()
15397 .unwrap_or(0);
15398 edits.extend(selection_edit_ranges.iter().map(|range| {
15399 let position = Point::new(range.start.row, min_column);
15400 (position..position, first_prefix.clone())
15401 }));
15402 }
15403 } else if let Some(BlockCommentConfig {
15404 start: full_comment_prefix,
15405 end: comment_suffix,
15406 ..
15407 }) = language.block_comment()
15408 {
15409 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15410 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15411 let prefix_range = comment_prefix_range(
15412 snapshot.deref(),
15413 start_row,
15414 comment_prefix,
15415 comment_prefix_whitespace,
15416 ignore_indent,
15417 );
15418 let suffix_range = comment_suffix_range(
15419 snapshot.deref(),
15420 end_row,
15421 comment_suffix.trim_start_matches(' '),
15422 comment_suffix.starts_with(' '),
15423 );
15424
15425 if prefix_range.is_empty() || suffix_range.is_empty() {
15426 edits.push((
15427 prefix_range.start..prefix_range.start,
15428 full_comment_prefix.clone(),
15429 ));
15430 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15431 suffixes_inserted.push((end_row, comment_suffix.len()));
15432 } else {
15433 edits.push((prefix_range, empty_str.clone()));
15434 edits.push((suffix_range, empty_str.clone()));
15435 }
15436 } else {
15437 continue;
15438 }
15439 }
15440
15441 drop(snapshot);
15442 this.buffer.update(cx, |buffer, cx| {
15443 buffer.edit(edits, None, cx);
15444 });
15445
15446 // Adjust selections so that they end before any comment suffixes that
15447 // were inserted.
15448 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15449 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15450 let snapshot = this.buffer.read(cx).read(cx);
15451 for selection in &mut selections {
15452 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15453 match row.cmp(&MultiBufferRow(selection.end.row)) {
15454 Ordering::Less => {
15455 suffixes_inserted.next();
15456 continue;
15457 }
15458 Ordering::Greater => break,
15459 Ordering::Equal => {
15460 if selection.end.column == snapshot.line_len(row) {
15461 if selection.is_empty() {
15462 selection.start.column -= suffix_len as u32;
15463 }
15464 selection.end.column -= suffix_len as u32;
15465 }
15466 break;
15467 }
15468 }
15469 }
15470 }
15471
15472 drop(snapshot);
15473 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15474
15475 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15476 let selections_on_single_row = selections.windows(2).all(|selections| {
15477 selections[0].start.row == selections[1].start.row
15478 && selections[0].end.row == selections[1].end.row
15479 && selections[0].start.row == selections[0].end.row
15480 });
15481 let selections_selecting = selections
15482 .iter()
15483 .any(|selection| selection.start != selection.end);
15484 let advance_downwards = action.advance_downwards
15485 && selections_on_single_row
15486 && !selections_selecting
15487 && !matches!(this.mode, EditorMode::SingleLine);
15488
15489 if advance_downwards {
15490 let snapshot = this.buffer.read(cx).snapshot(cx);
15491
15492 this.change_selections(Default::default(), window, cx, |s| {
15493 s.move_cursors_with(|display_snapshot, display_point, _| {
15494 let mut point = display_point.to_point(display_snapshot);
15495 point.row += 1;
15496 point = snapshot.clip_point(point, Bias::Left);
15497 let display_point = point.to_display_point(display_snapshot);
15498 let goal = SelectionGoal::HorizontalPosition(
15499 display_snapshot
15500 .x_for_display_point(display_point, text_layout_details)
15501 .into(),
15502 );
15503 (display_point, goal)
15504 })
15505 });
15506 }
15507 });
15508 }
15509
15510 pub fn select_enclosing_symbol(
15511 &mut self,
15512 _: &SelectEnclosingSymbol,
15513 window: &mut Window,
15514 cx: &mut Context<Self>,
15515 ) {
15516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15517
15518 let buffer = self.buffer.read(cx).snapshot(cx);
15519 let old_selections = self
15520 .selections
15521 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15522 .into_boxed_slice();
15523
15524 fn update_selection(
15525 selection: &Selection<MultiBufferOffset>,
15526 buffer_snap: &MultiBufferSnapshot,
15527 ) -> Option<Selection<MultiBufferOffset>> {
15528 let cursor = selection.head();
15529 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15530 for symbol in symbols.iter().rev() {
15531 let start = symbol.range.start.to_offset(buffer_snap);
15532 let end = symbol.range.end.to_offset(buffer_snap);
15533 let new_range = start..end;
15534 if start < selection.start || end > selection.end {
15535 return Some(Selection {
15536 id: selection.id,
15537 start: new_range.start,
15538 end: new_range.end,
15539 goal: SelectionGoal::None,
15540 reversed: selection.reversed,
15541 });
15542 }
15543 }
15544 None
15545 }
15546
15547 let mut selected_larger_symbol = false;
15548 let new_selections = old_selections
15549 .iter()
15550 .map(|selection| match update_selection(selection, &buffer) {
15551 Some(new_selection) => {
15552 if new_selection.range() != selection.range() {
15553 selected_larger_symbol = true;
15554 }
15555 new_selection
15556 }
15557 None => selection.clone(),
15558 })
15559 .collect::<Vec<_>>();
15560
15561 if selected_larger_symbol {
15562 self.change_selections(Default::default(), window, cx, |s| {
15563 s.select(new_selections);
15564 });
15565 }
15566 }
15567
15568 pub fn select_larger_syntax_node(
15569 &mut self,
15570 _: &SelectLargerSyntaxNode,
15571 window: &mut Window,
15572 cx: &mut Context<Self>,
15573 ) {
15574 let Some(visible_row_count) = self.visible_row_count() else {
15575 return;
15576 };
15577 let old_selections: Box<[_]> = self
15578 .selections
15579 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15580 .into();
15581 if old_selections.is_empty() {
15582 return;
15583 }
15584
15585 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15586
15587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15588 let buffer = self.buffer.read(cx).snapshot(cx);
15589
15590 let mut selected_larger_node = false;
15591 let mut new_selections = old_selections
15592 .iter()
15593 .map(|selection| {
15594 let old_range = selection.start..selection.end;
15595
15596 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15597 // manually select word at selection
15598 if ["string_content", "inline"].contains(&node.kind()) {
15599 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15600 // ignore if word is already selected
15601 if !word_range.is_empty() && old_range != word_range {
15602 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15603 // only select word if start and end point belongs to same word
15604 if word_range == last_word_range {
15605 selected_larger_node = true;
15606 return Selection {
15607 id: selection.id,
15608 start: word_range.start,
15609 end: word_range.end,
15610 goal: SelectionGoal::None,
15611 reversed: selection.reversed,
15612 };
15613 }
15614 }
15615 }
15616 }
15617
15618 let mut new_range = old_range.clone();
15619 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15620 new_range = range;
15621 if !node.is_named() {
15622 continue;
15623 }
15624 if !display_map.intersects_fold(new_range.start)
15625 && !display_map.intersects_fold(new_range.end)
15626 {
15627 break;
15628 }
15629 }
15630
15631 selected_larger_node |= new_range != old_range;
15632 Selection {
15633 id: selection.id,
15634 start: new_range.start,
15635 end: new_range.end,
15636 goal: SelectionGoal::None,
15637 reversed: selection.reversed,
15638 }
15639 })
15640 .collect::<Vec<_>>();
15641
15642 if !selected_larger_node {
15643 return; // don't put this call in the history
15644 }
15645
15646 // scroll based on transformation done to the last selection created by the user
15647 let (last_old, last_new) = old_selections
15648 .last()
15649 .zip(new_selections.last().cloned())
15650 .expect("old_selections isn't empty");
15651
15652 // revert selection
15653 let is_selection_reversed = {
15654 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15655 new_selections.last_mut().expect("checked above").reversed =
15656 should_newest_selection_be_reversed;
15657 should_newest_selection_be_reversed
15658 };
15659
15660 if selected_larger_node {
15661 self.select_syntax_node_history.disable_clearing = true;
15662 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15663 s.select(new_selections.clone());
15664 });
15665 self.select_syntax_node_history.disable_clearing = false;
15666 }
15667
15668 let start_row = last_new.start.to_display_point(&display_map).row().0;
15669 let end_row = last_new.end.to_display_point(&display_map).row().0;
15670 let selection_height = end_row - start_row + 1;
15671 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15672
15673 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15674 let scroll_behavior = if fits_on_the_screen {
15675 self.request_autoscroll(Autoscroll::fit(), cx);
15676 SelectSyntaxNodeScrollBehavior::FitSelection
15677 } else if is_selection_reversed {
15678 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15679 SelectSyntaxNodeScrollBehavior::CursorTop
15680 } else {
15681 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15682 SelectSyntaxNodeScrollBehavior::CursorBottom
15683 };
15684
15685 self.select_syntax_node_history.push((
15686 old_selections,
15687 scroll_behavior,
15688 is_selection_reversed,
15689 ));
15690 }
15691
15692 pub fn select_smaller_syntax_node(
15693 &mut self,
15694 _: &SelectSmallerSyntaxNode,
15695 window: &mut Window,
15696 cx: &mut Context<Self>,
15697 ) {
15698 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15699
15700 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15701 self.select_syntax_node_history.pop()
15702 {
15703 if let Some(selection) = selections.last_mut() {
15704 selection.reversed = is_selection_reversed;
15705 }
15706
15707 self.select_syntax_node_history.disable_clearing = true;
15708 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15709 s.select(selections.to_vec());
15710 });
15711 self.select_syntax_node_history.disable_clearing = false;
15712
15713 match scroll_behavior {
15714 SelectSyntaxNodeScrollBehavior::CursorTop => {
15715 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15716 }
15717 SelectSyntaxNodeScrollBehavior::FitSelection => {
15718 self.request_autoscroll(Autoscroll::fit(), cx);
15719 }
15720 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15721 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15722 }
15723 }
15724 }
15725 }
15726
15727 pub fn unwrap_syntax_node(
15728 &mut self,
15729 _: &UnwrapSyntaxNode,
15730 window: &mut Window,
15731 cx: &mut Context<Self>,
15732 ) {
15733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15734
15735 let buffer = self.buffer.read(cx).snapshot(cx);
15736 let selections = self
15737 .selections
15738 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15739 .into_iter()
15740 // subtracting the offset requires sorting
15741 .sorted_by_key(|i| i.start);
15742
15743 let full_edits = selections
15744 .into_iter()
15745 .filter_map(|selection| {
15746 let child = if selection.is_empty()
15747 && let Some((_, ancestor_range)) =
15748 buffer.syntax_ancestor(selection.start..selection.end)
15749 {
15750 ancestor_range
15751 } else {
15752 selection.range()
15753 };
15754
15755 let mut parent = child.clone();
15756 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15757 parent = ancestor_range;
15758 if parent.start < child.start || parent.end > child.end {
15759 break;
15760 }
15761 }
15762
15763 if parent == child {
15764 return None;
15765 }
15766 let text = buffer.text_for_range(child).collect::<String>();
15767 Some((selection.id, parent, text))
15768 })
15769 .collect::<Vec<_>>();
15770 if full_edits.is_empty() {
15771 return;
15772 }
15773
15774 self.transact(window, cx, |this, window, cx| {
15775 this.buffer.update(cx, |buffer, cx| {
15776 buffer.edit(
15777 full_edits
15778 .iter()
15779 .map(|(_, p, t)| (p.clone(), t.clone()))
15780 .collect::<Vec<_>>(),
15781 None,
15782 cx,
15783 );
15784 });
15785 this.change_selections(Default::default(), window, cx, |s| {
15786 let mut offset = 0;
15787 let mut selections = vec![];
15788 for (id, parent, text) in full_edits {
15789 let start = parent.start - offset;
15790 offset += (parent.end - parent.start) - text.len();
15791 selections.push(Selection {
15792 id,
15793 start,
15794 end: start + text.len(),
15795 reversed: false,
15796 goal: Default::default(),
15797 });
15798 }
15799 s.select(selections);
15800 });
15801 });
15802 }
15803
15804 pub fn select_next_syntax_node(
15805 &mut self,
15806 _: &SelectNextSyntaxNode,
15807 window: &mut Window,
15808 cx: &mut Context<Self>,
15809 ) {
15810 let old_selections: Box<[_]> = self
15811 .selections
15812 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15813 .into();
15814 if old_selections.is_empty() {
15815 return;
15816 }
15817
15818 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15819
15820 let buffer = self.buffer.read(cx).snapshot(cx);
15821 let mut selected_sibling = false;
15822
15823 let new_selections = old_selections
15824 .iter()
15825 .map(|selection| {
15826 let old_range = selection.start..selection.end;
15827
15828 let old_range =
15829 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15830 let excerpt = buffer.excerpt_containing(old_range.clone());
15831
15832 if let Some(mut excerpt) = excerpt
15833 && let Some(node) = excerpt
15834 .buffer()
15835 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15836 {
15837 let new_range = excerpt.map_range_from_buffer(
15838 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15839 );
15840 selected_sibling = true;
15841 Selection {
15842 id: selection.id,
15843 start: new_range.start,
15844 end: new_range.end,
15845 goal: SelectionGoal::None,
15846 reversed: selection.reversed,
15847 }
15848 } else {
15849 selection.clone()
15850 }
15851 })
15852 .collect::<Vec<_>>();
15853
15854 if selected_sibling {
15855 self.change_selections(
15856 SelectionEffects::scroll(Autoscroll::fit()),
15857 window,
15858 cx,
15859 |s| {
15860 s.select(new_selections);
15861 },
15862 );
15863 }
15864 }
15865
15866 pub fn select_prev_syntax_node(
15867 &mut self,
15868 _: &SelectPreviousSyntaxNode,
15869 window: &mut Window,
15870 cx: &mut Context<Self>,
15871 ) {
15872 let old_selections: Box<[_]> = self
15873 .selections
15874 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15875 .into();
15876 if old_selections.is_empty() {
15877 return;
15878 }
15879
15880 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15881
15882 let buffer = self.buffer.read(cx).snapshot(cx);
15883 let mut selected_sibling = false;
15884
15885 let new_selections = old_selections
15886 .iter()
15887 .map(|selection| {
15888 let old_range = selection.start..selection.end;
15889 let old_range =
15890 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15891 let excerpt = buffer.excerpt_containing(old_range.clone());
15892
15893 if let Some(mut excerpt) = excerpt
15894 && let Some(node) = excerpt
15895 .buffer()
15896 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15897 {
15898 let new_range = excerpt.map_range_from_buffer(
15899 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15900 );
15901 selected_sibling = true;
15902 Selection {
15903 id: selection.id,
15904 start: new_range.start,
15905 end: new_range.end,
15906 goal: SelectionGoal::None,
15907 reversed: selection.reversed,
15908 }
15909 } else {
15910 selection.clone()
15911 }
15912 })
15913 .collect::<Vec<_>>();
15914
15915 if selected_sibling {
15916 self.change_selections(
15917 SelectionEffects::scroll(Autoscroll::fit()),
15918 window,
15919 cx,
15920 |s| {
15921 s.select(new_selections);
15922 },
15923 );
15924 }
15925 }
15926
15927 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15928 if !EditorSettings::get_global(cx).gutter.runnables {
15929 self.clear_tasks();
15930 return Task::ready(());
15931 }
15932 let project = self.project().map(Entity::downgrade);
15933 let task_sources = self.lsp_task_sources(cx);
15934 let multi_buffer = self.buffer.downgrade();
15935 cx.spawn_in(window, async move |editor, cx| {
15936 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15937 let Some(project) = project.and_then(|p| p.upgrade()) else {
15938 return;
15939 };
15940 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15941 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15942 }) else {
15943 return;
15944 };
15945
15946 let hide_runnables = project
15947 .update(cx, |project, _| project.is_via_collab())
15948 .unwrap_or(true);
15949 if hide_runnables {
15950 return;
15951 }
15952 let new_rows =
15953 cx.background_spawn({
15954 let snapshot = display_snapshot.clone();
15955 async move {
15956 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15957 }
15958 })
15959 .await;
15960 let Ok(lsp_tasks) =
15961 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15962 else {
15963 return;
15964 };
15965 let lsp_tasks = lsp_tasks.await;
15966
15967 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15968 lsp_tasks
15969 .into_iter()
15970 .flat_map(|(kind, tasks)| {
15971 tasks.into_iter().filter_map(move |(location, task)| {
15972 Some((kind.clone(), location?, task))
15973 })
15974 })
15975 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15976 let buffer = location.target.buffer;
15977 let buffer_snapshot = buffer.read(cx).snapshot();
15978 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15979 |(excerpt_id, snapshot, _)| {
15980 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15981 display_snapshot
15982 .buffer_snapshot()
15983 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15984 } else {
15985 None
15986 }
15987 },
15988 );
15989 if let Some(offset) = offset {
15990 let task_buffer_range =
15991 location.target.range.to_point(&buffer_snapshot);
15992 let context_buffer_range =
15993 task_buffer_range.to_offset(&buffer_snapshot);
15994 let context_range = BufferOffset(context_buffer_range.start)
15995 ..BufferOffset(context_buffer_range.end);
15996
15997 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15998 .or_insert_with(|| RunnableTasks {
15999 templates: Vec::new(),
16000 offset,
16001 column: task_buffer_range.start.column,
16002 extra_variables: HashMap::default(),
16003 context_range,
16004 })
16005 .templates
16006 .push((kind, task.original_task().clone()));
16007 }
16008
16009 acc
16010 })
16011 }) else {
16012 return;
16013 };
16014
16015 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16016 buffer.language_settings(cx).tasks.prefer_lsp
16017 }) else {
16018 return;
16019 };
16020
16021 let rows = Self::runnable_rows(
16022 project,
16023 display_snapshot,
16024 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16025 new_rows,
16026 cx.clone(),
16027 )
16028 .await;
16029 editor
16030 .update(cx, |editor, _| {
16031 editor.clear_tasks();
16032 for (key, mut value) in rows {
16033 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16034 value.templates.extend(lsp_tasks.templates);
16035 }
16036
16037 editor.insert_tasks(key, value);
16038 }
16039 for (key, value) in lsp_tasks_by_rows {
16040 editor.insert_tasks(key, value);
16041 }
16042 })
16043 .ok();
16044 })
16045 }
16046 fn fetch_runnable_ranges(
16047 snapshot: &DisplaySnapshot,
16048 range: Range<Anchor>,
16049 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16050 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16051 }
16052
16053 fn runnable_rows(
16054 project: Entity<Project>,
16055 snapshot: DisplaySnapshot,
16056 prefer_lsp: bool,
16057 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16058 cx: AsyncWindowContext,
16059 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16060 cx.spawn(async move |cx| {
16061 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16062 for (run_range, mut runnable) in runnable_ranges {
16063 let Some(tasks) = cx
16064 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16065 .ok()
16066 else {
16067 continue;
16068 };
16069 let mut tasks = tasks.await;
16070
16071 if prefer_lsp {
16072 tasks.retain(|(task_kind, _)| {
16073 !matches!(task_kind, TaskSourceKind::Language { .. })
16074 });
16075 }
16076 if tasks.is_empty() {
16077 continue;
16078 }
16079
16080 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16081 let Some(row) = snapshot
16082 .buffer_snapshot()
16083 .buffer_line_for_row(MultiBufferRow(point.row))
16084 .map(|(_, range)| range.start.row)
16085 else {
16086 continue;
16087 };
16088
16089 let context_range =
16090 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16091 runnable_rows.push((
16092 (runnable.buffer_id, row),
16093 RunnableTasks {
16094 templates: tasks,
16095 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16096 context_range,
16097 column: point.column,
16098 extra_variables: runnable.extra_captures,
16099 },
16100 ));
16101 }
16102 runnable_rows
16103 })
16104 }
16105
16106 fn templates_with_tags(
16107 project: &Entity<Project>,
16108 runnable: &mut Runnable,
16109 cx: &mut App,
16110 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16111 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16112 let (worktree_id, file) = project
16113 .buffer_for_id(runnable.buffer, cx)
16114 .and_then(|buffer| buffer.read(cx).file())
16115 .map(|file| (file.worktree_id(cx), file.clone()))
16116 .unzip();
16117
16118 (
16119 project.task_store().read(cx).task_inventory().cloned(),
16120 worktree_id,
16121 file,
16122 )
16123 });
16124
16125 let tags = mem::take(&mut runnable.tags);
16126 let language = runnable.language.clone();
16127 cx.spawn(async move |cx| {
16128 let mut templates_with_tags = Vec::new();
16129 if let Some(inventory) = inventory {
16130 for RunnableTag(tag) in tags {
16131 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16132 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16133 }) else {
16134 return templates_with_tags;
16135 };
16136 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16137 move |(_, template)| {
16138 template.tags.iter().any(|source_tag| source_tag == &tag)
16139 },
16140 ));
16141 }
16142 }
16143 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16144
16145 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16146 // Strongest source wins; if we have worktree tag binding, prefer that to
16147 // global and language bindings;
16148 // if we have a global binding, prefer that to language binding.
16149 let first_mismatch = templates_with_tags
16150 .iter()
16151 .position(|(tag_source, _)| tag_source != leading_tag_source);
16152 if let Some(index) = first_mismatch {
16153 templates_with_tags.truncate(index);
16154 }
16155 }
16156
16157 templates_with_tags
16158 })
16159 }
16160
16161 pub fn move_to_enclosing_bracket(
16162 &mut self,
16163 _: &MoveToEnclosingBracket,
16164 window: &mut Window,
16165 cx: &mut Context<Self>,
16166 ) {
16167 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16168 self.change_selections(Default::default(), window, cx, |s| {
16169 s.move_offsets_with(|snapshot, selection| {
16170 let Some(enclosing_bracket_ranges) =
16171 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16172 else {
16173 return;
16174 };
16175
16176 let mut best_length = usize::MAX;
16177 let mut best_inside = false;
16178 let mut best_in_bracket_range = false;
16179 let mut best_destination = None;
16180 for (open, close) in enclosing_bracket_ranges {
16181 let close = close.to_inclusive();
16182 let length = *close.end() - open.start;
16183 let inside = selection.start >= open.end && selection.end <= *close.start();
16184 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16185 || close.contains(&selection.head());
16186
16187 // If best is next to a bracket and current isn't, skip
16188 if !in_bracket_range && best_in_bracket_range {
16189 continue;
16190 }
16191
16192 // Prefer smaller lengths unless best is inside and current isn't
16193 if length > best_length && (best_inside || !inside) {
16194 continue;
16195 }
16196
16197 best_length = length;
16198 best_inside = inside;
16199 best_in_bracket_range = in_bracket_range;
16200 best_destination = Some(
16201 if close.contains(&selection.start) && close.contains(&selection.end) {
16202 if inside { open.end } else { open.start }
16203 } else if inside {
16204 *close.start()
16205 } else {
16206 *close.end()
16207 },
16208 );
16209 }
16210
16211 if let Some(destination) = best_destination {
16212 selection.collapse_to(destination, SelectionGoal::None);
16213 }
16214 })
16215 });
16216 }
16217
16218 pub fn undo_selection(
16219 &mut self,
16220 _: &UndoSelection,
16221 window: &mut Window,
16222 cx: &mut Context<Self>,
16223 ) {
16224 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16225 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16226 self.selection_history.mode = SelectionHistoryMode::Undoing;
16227 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16228 this.end_selection(window, cx);
16229 this.change_selections(
16230 SelectionEffects::scroll(Autoscroll::newest()),
16231 window,
16232 cx,
16233 |s| s.select_anchors(entry.selections.to_vec()),
16234 );
16235 });
16236 self.selection_history.mode = SelectionHistoryMode::Normal;
16237
16238 self.select_next_state = entry.select_next_state;
16239 self.select_prev_state = entry.select_prev_state;
16240 self.add_selections_state = entry.add_selections_state;
16241 }
16242 }
16243
16244 pub fn redo_selection(
16245 &mut self,
16246 _: &RedoSelection,
16247 window: &mut Window,
16248 cx: &mut Context<Self>,
16249 ) {
16250 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16251 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16252 self.selection_history.mode = SelectionHistoryMode::Redoing;
16253 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16254 this.end_selection(window, cx);
16255 this.change_selections(
16256 SelectionEffects::scroll(Autoscroll::newest()),
16257 window,
16258 cx,
16259 |s| s.select_anchors(entry.selections.to_vec()),
16260 );
16261 });
16262 self.selection_history.mode = SelectionHistoryMode::Normal;
16263
16264 self.select_next_state = entry.select_next_state;
16265 self.select_prev_state = entry.select_prev_state;
16266 self.add_selections_state = entry.add_selections_state;
16267 }
16268 }
16269
16270 pub fn expand_excerpts(
16271 &mut self,
16272 action: &ExpandExcerpts,
16273 _: &mut Window,
16274 cx: &mut Context<Self>,
16275 ) {
16276 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16277 }
16278
16279 pub fn expand_excerpts_down(
16280 &mut self,
16281 action: &ExpandExcerptsDown,
16282 _: &mut Window,
16283 cx: &mut Context<Self>,
16284 ) {
16285 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16286 }
16287
16288 pub fn expand_excerpts_up(
16289 &mut self,
16290 action: &ExpandExcerptsUp,
16291 _: &mut Window,
16292 cx: &mut Context<Self>,
16293 ) {
16294 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16295 }
16296
16297 pub fn expand_excerpts_for_direction(
16298 &mut self,
16299 lines: u32,
16300 direction: ExpandExcerptDirection,
16301
16302 cx: &mut Context<Self>,
16303 ) {
16304 let selections = self.selections.disjoint_anchors_arc();
16305
16306 let lines = if lines == 0 {
16307 EditorSettings::get_global(cx).expand_excerpt_lines
16308 } else {
16309 lines
16310 };
16311
16312 self.buffer.update(cx, |buffer, cx| {
16313 let snapshot = buffer.snapshot(cx);
16314 let mut excerpt_ids = selections
16315 .iter()
16316 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16317 .collect::<Vec<_>>();
16318 excerpt_ids.sort();
16319 excerpt_ids.dedup();
16320 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16321 })
16322 }
16323
16324 pub fn expand_excerpt(
16325 &mut self,
16326 excerpt: ExcerptId,
16327 direction: ExpandExcerptDirection,
16328 window: &mut Window,
16329 cx: &mut Context<Self>,
16330 ) {
16331 let current_scroll_position = self.scroll_position(cx);
16332 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16333 let mut scroll = None;
16334
16335 if direction == ExpandExcerptDirection::Down {
16336 let multi_buffer = self.buffer.read(cx);
16337 let snapshot = multi_buffer.snapshot(cx);
16338 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16339 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16340 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16341 {
16342 let buffer_snapshot = buffer.read(cx).snapshot();
16343 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16344 let last_row = buffer_snapshot.max_point().row;
16345 let lines_below = last_row.saturating_sub(excerpt_end_row);
16346 if lines_below >= lines_to_expand {
16347 scroll = Some(
16348 current_scroll_position
16349 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16350 );
16351 }
16352 }
16353 }
16354 if direction == ExpandExcerptDirection::Up
16355 && self
16356 .buffer
16357 .read(cx)
16358 .snapshot(cx)
16359 .excerpt_before(excerpt)
16360 .is_none()
16361 {
16362 scroll = Some(current_scroll_position);
16363 }
16364
16365 self.buffer.update(cx, |buffer, cx| {
16366 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16367 });
16368
16369 if let Some(new_scroll_position) = scroll {
16370 self.set_scroll_position(new_scroll_position, window, cx);
16371 }
16372 }
16373
16374 pub fn go_to_singleton_buffer_point(
16375 &mut self,
16376 point: Point,
16377 window: &mut Window,
16378 cx: &mut Context<Self>,
16379 ) {
16380 self.go_to_singleton_buffer_range(point..point, window, cx);
16381 }
16382
16383 pub fn go_to_singleton_buffer_range(
16384 &mut self,
16385 range: Range<Point>,
16386 window: &mut Window,
16387 cx: &mut Context<Self>,
16388 ) {
16389 let multibuffer = self.buffer().read(cx);
16390 let Some(buffer) = multibuffer.as_singleton() else {
16391 return;
16392 };
16393 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16394 return;
16395 };
16396 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16397 return;
16398 };
16399 self.change_selections(
16400 SelectionEffects::default().nav_history(true),
16401 window,
16402 cx,
16403 |s| s.select_anchor_ranges([start..end]),
16404 );
16405 }
16406
16407 pub fn go_to_diagnostic(
16408 &mut self,
16409 action: &GoToDiagnostic,
16410 window: &mut Window,
16411 cx: &mut Context<Self>,
16412 ) {
16413 if !self.diagnostics_enabled() {
16414 return;
16415 }
16416 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16417 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16418 }
16419
16420 pub fn go_to_prev_diagnostic(
16421 &mut self,
16422 action: &GoToPreviousDiagnostic,
16423 window: &mut Window,
16424 cx: &mut Context<Self>,
16425 ) {
16426 if !self.diagnostics_enabled() {
16427 return;
16428 }
16429 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16430 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16431 }
16432
16433 pub fn go_to_diagnostic_impl(
16434 &mut self,
16435 direction: Direction,
16436 severity: GoToDiagnosticSeverityFilter,
16437 window: &mut Window,
16438 cx: &mut Context<Self>,
16439 ) {
16440 let buffer = self.buffer.read(cx).snapshot(cx);
16441 let selection = self
16442 .selections
16443 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16444
16445 let mut active_group_id = None;
16446 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16447 && active_group.active_range.start.to_offset(&buffer) == selection.start
16448 {
16449 active_group_id = Some(active_group.group_id);
16450 }
16451
16452 fn filtered<'a>(
16453 severity: GoToDiagnosticSeverityFilter,
16454 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16455 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16456 diagnostics
16457 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16458 .filter(|entry| entry.range.start != entry.range.end)
16459 .filter(|entry| !entry.diagnostic.is_unnecessary)
16460 }
16461
16462 let before = filtered(
16463 severity,
16464 buffer
16465 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16466 .filter(|entry| entry.range.start <= selection.start),
16467 );
16468 let after = filtered(
16469 severity,
16470 buffer
16471 .diagnostics_in_range(selection.start..buffer.len())
16472 .filter(|entry| entry.range.start >= selection.start),
16473 );
16474
16475 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16476 if direction == Direction::Prev {
16477 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16478 {
16479 for diagnostic in prev_diagnostics.into_iter().rev() {
16480 if diagnostic.range.start != selection.start
16481 || active_group_id
16482 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16483 {
16484 found = Some(diagnostic);
16485 break 'outer;
16486 }
16487 }
16488 }
16489 } else {
16490 for diagnostic in after.chain(before) {
16491 if diagnostic.range.start != selection.start
16492 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16493 {
16494 found = Some(diagnostic);
16495 break;
16496 }
16497 }
16498 }
16499 let Some(next_diagnostic) = found else {
16500 return;
16501 };
16502
16503 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16504 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16505 return;
16506 };
16507 let snapshot = self.snapshot(window, cx);
16508 if snapshot.intersects_fold(next_diagnostic.range.start) {
16509 self.unfold_ranges(
16510 std::slice::from_ref(&next_diagnostic.range),
16511 true,
16512 false,
16513 cx,
16514 );
16515 }
16516 self.change_selections(Default::default(), window, cx, |s| {
16517 s.select_ranges(vec![
16518 next_diagnostic.range.start..next_diagnostic.range.start,
16519 ])
16520 });
16521 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16522 self.refresh_edit_prediction(false, true, window, cx);
16523 }
16524
16525 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16527 let snapshot = self.snapshot(window, cx);
16528 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16529 self.go_to_hunk_before_or_after_position(
16530 &snapshot,
16531 selection.head(),
16532 Direction::Next,
16533 window,
16534 cx,
16535 );
16536 }
16537
16538 pub fn go_to_hunk_before_or_after_position(
16539 &mut self,
16540 snapshot: &EditorSnapshot,
16541 position: Point,
16542 direction: Direction,
16543 window: &mut Window,
16544 cx: &mut Context<Editor>,
16545 ) {
16546 let row = if direction == Direction::Next {
16547 self.hunk_after_position(snapshot, position)
16548 .map(|hunk| hunk.row_range.start)
16549 } else {
16550 self.hunk_before_position(snapshot, position)
16551 };
16552
16553 if let Some(row) = row {
16554 let destination = Point::new(row.0, 0);
16555 let autoscroll = Autoscroll::center();
16556
16557 self.unfold_ranges(&[destination..destination], false, false, cx);
16558 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16559 s.select_ranges([destination..destination]);
16560 });
16561 }
16562 }
16563
16564 fn hunk_after_position(
16565 &mut self,
16566 snapshot: &EditorSnapshot,
16567 position: Point,
16568 ) -> Option<MultiBufferDiffHunk> {
16569 snapshot
16570 .buffer_snapshot()
16571 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16572 .find(|hunk| hunk.row_range.start.0 > position.row)
16573 .or_else(|| {
16574 snapshot
16575 .buffer_snapshot()
16576 .diff_hunks_in_range(Point::zero()..position)
16577 .find(|hunk| hunk.row_range.end.0 < position.row)
16578 })
16579 }
16580
16581 fn go_to_prev_hunk(
16582 &mut self,
16583 _: &GoToPreviousHunk,
16584 window: &mut Window,
16585 cx: &mut Context<Self>,
16586 ) {
16587 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16588 let snapshot = self.snapshot(window, cx);
16589 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16590 self.go_to_hunk_before_or_after_position(
16591 &snapshot,
16592 selection.head(),
16593 Direction::Prev,
16594 window,
16595 cx,
16596 );
16597 }
16598
16599 fn hunk_before_position(
16600 &mut self,
16601 snapshot: &EditorSnapshot,
16602 position: Point,
16603 ) -> Option<MultiBufferRow> {
16604 snapshot
16605 .buffer_snapshot()
16606 .diff_hunk_before(position)
16607 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16608 }
16609
16610 fn go_to_next_change(
16611 &mut self,
16612 _: &GoToNextChange,
16613 window: &mut Window,
16614 cx: &mut Context<Self>,
16615 ) {
16616 if let Some(selections) = self
16617 .change_list
16618 .next_change(1, Direction::Next)
16619 .map(|s| s.to_vec())
16620 {
16621 self.change_selections(Default::default(), window, cx, |s| {
16622 let map = s.display_snapshot();
16623 s.select_display_ranges(selections.iter().map(|a| {
16624 let point = a.to_display_point(&map);
16625 point..point
16626 }))
16627 })
16628 }
16629 }
16630
16631 fn go_to_previous_change(
16632 &mut self,
16633 _: &GoToPreviousChange,
16634 window: &mut Window,
16635 cx: &mut Context<Self>,
16636 ) {
16637 if let Some(selections) = self
16638 .change_list
16639 .next_change(1, Direction::Prev)
16640 .map(|s| s.to_vec())
16641 {
16642 self.change_selections(Default::default(), window, cx, |s| {
16643 let map = s.display_snapshot();
16644 s.select_display_ranges(selections.iter().map(|a| {
16645 let point = a.to_display_point(&map);
16646 point..point
16647 }))
16648 })
16649 }
16650 }
16651
16652 pub fn go_to_next_document_highlight(
16653 &mut self,
16654 _: &GoToNextDocumentHighlight,
16655 window: &mut Window,
16656 cx: &mut Context<Self>,
16657 ) {
16658 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16659 }
16660
16661 pub fn go_to_prev_document_highlight(
16662 &mut self,
16663 _: &GoToPreviousDocumentHighlight,
16664 window: &mut Window,
16665 cx: &mut Context<Self>,
16666 ) {
16667 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16668 }
16669
16670 pub fn go_to_document_highlight_before_or_after_position(
16671 &mut self,
16672 direction: Direction,
16673 window: &mut Window,
16674 cx: &mut Context<Editor>,
16675 ) {
16676 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16677 let snapshot = self.snapshot(window, cx);
16678 let buffer = &snapshot.buffer_snapshot();
16679 let position = self
16680 .selections
16681 .newest::<Point>(&snapshot.display_snapshot)
16682 .head();
16683 let anchor_position = buffer.anchor_after(position);
16684
16685 // Get all document highlights (both read and write)
16686 let mut all_highlights = Vec::new();
16687
16688 if let Some((_, read_highlights)) = self
16689 .background_highlights
16690 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16691 {
16692 all_highlights.extend(read_highlights.iter());
16693 }
16694
16695 if let Some((_, write_highlights)) = self
16696 .background_highlights
16697 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16698 {
16699 all_highlights.extend(write_highlights.iter());
16700 }
16701
16702 if all_highlights.is_empty() {
16703 return;
16704 }
16705
16706 // Sort highlights by position
16707 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16708
16709 let target_highlight = match direction {
16710 Direction::Next => {
16711 // Find the first highlight after the current position
16712 all_highlights
16713 .iter()
16714 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16715 }
16716 Direction::Prev => {
16717 // Find the last highlight before the current position
16718 all_highlights
16719 .iter()
16720 .rev()
16721 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16722 }
16723 };
16724
16725 if let Some(highlight) = target_highlight {
16726 let destination = highlight.start.to_point(buffer);
16727 let autoscroll = Autoscroll::center();
16728
16729 self.unfold_ranges(&[destination..destination], false, false, cx);
16730 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16731 s.select_ranges([destination..destination]);
16732 });
16733 }
16734 }
16735
16736 fn go_to_line<T: 'static>(
16737 &mut self,
16738 position: Anchor,
16739 highlight_color: Option<Hsla>,
16740 window: &mut Window,
16741 cx: &mut Context<Self>,
16742 ) {
16743 let snapshot = self.snapshot(window, cx).display_snapshot;
16744 let position = position.to_point(&snapshot.buffer_snapshot());
16745 let start = snapshot
16746 .buffer_snapshot()
16747 .clip_point(Point::new(position.row, 0), Bias::Left);
16748 let end = start + Point::new(1, 0);
16749 let start = snapshot.buffer_snapshot().anchor_before(start);
16750 let end = snapshot.buffer_snapshot().anchor_before(end);
16751
16752 self.highlight_rows::<T>(
16753 start..end,
16754 highlight_color
16755 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16756 Default::default(),
16757 cx,
16758 );
16759
16760 if self.buffer.read(cx).is_singleton() {
16761 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16762 }
16763 }
16764
16765 pub fn go_to_definition(
16766 &mut self,
16767 _: &GoToDefinition,
16768 window: &mut Window,
16769 cx: &mut Context<Self>,
16770 ) -> Task<Result<Navigated>> {
16771 let definition =
16772 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16773 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16774 cx.spawn_in(window, async move |editor, cx| {
16775 if definition.await? == Navigated::Yes {
16776 return Ok(Navigated::Yes);
16777 }
16778 match fallback_strategy {
16779 GoToDefinitionFallback::None => Ok(Navigated::No),
16780 GoToDefinitionFallback::FindAllReferences => {
16781 match editor.update_in(cx, |editor, window, cx| {
16782 editor.find_all_references(&FindAllReferences, window, cx)
16783 })? {
16784 Some(references) => references.await,
16785 None => Ok(Navigated::No),
16786 }
16787 }
16788 }
16789 })
16790 }
16791
16792 pub fn go_to_declaration(
16793 &mut self,
16794 _: &GoToDeclaration,
16795 window: &mut Window,
16796 cx: &mut Context<Self>,
16797 ) -> Task<Result<Navigated>> {
16798 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16799 }
16800
16801 pub fn go_to_declaration_split(
16802 &mut self,
16803 _: &GoToDeclaration,
16804 window: &mut Window,
16805 cx: &mut Context<Self>,
16806 ) -> Task<Result<Navigated>> {
16807 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16808 }
16809
16810 pub fn go_to_implementation(
16811 &mut self,
16812 _: &GoToImplementation,
16813 window: &mut Window,
16814 cx: &mut Context<Self>,
16815 ) -> Task<Result<Navigated>> {
16816 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16817 }
16818
16819 pub fn go_to_implementation_split(
16820 &mut self,
16821 _: &GoToImplementationSplit,
16822 window: &mut Window,
16823 cx: &mut Context<Self>,
16824 ) -> Task<Result<Navigated>> {
16825 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16826 }
16827
16828 pub fn go_to_type_definition(
16829 &mut self,
16830 _: &GoToTypeDefinition,
16831 window: &mut Window,
16832 cx: &mut Context<Self>,
16833 ) -> Task<Result<Navigated>> {
16834 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16835 }
16836
16837 pub fn go_to_definition_split(
16838 &mut self,
16839 _: &GoToDefinitionSplit,
16840 window: &mut Window,
16841 cx: &mut Context<Self>,
16842 ) -> Task<Result<Navigated>> {
16843 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16844 }
16845
16846 pub fn go_to_type_definition_split(
16847 &mut self,
16848 _: &GoToTypeDefinitionSplit,
16849 window: &mut Window,
16850 cx: &mut Context<Self>,
16851 ) -> Task<Result<Navigated>> {
16852 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16853 }
16854
16855 fn go_to_definition_of_kind(
16856 &mut self,
16857 kind: GotoDefinitionKind,
16858 split: bool,
16859 window: &mut Window,
16860 cx: &mut Context<Self>,
16861 ) -> Task<Result<Navigated>> {
16862 let Some(provider) = self.semantics_provider.clone() else {
16863 return Task::ready(Ok(Navigated::No));
16864 };
16865 let head = self
16866 .selections
16867 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16868 .head();
16869 let buffer = self.buffer.read(cx);
16870 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16871 return Task::ready(Ok(Navigated::No));
16872 };
16873 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16874 return Task::ready(Ok(Navigated::No));
16875 };
16876
16877 cx.spawn_in(window, async move |editor, cx| {
16878 let Some(definitions) = definitions.await? else {
16879 return Ok(Navigated::No);
16880 };
16881 let navigated = editor
16882 .update_in(cx, |editor, window, cx| {
16883 editor.navigate_to_hover_links(
16884 Some(kind),
16885 definitions
16886 .into_iter()
16887 .filter(|location| {
16888 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16889 })
16890 .map(HoverLink::Text)
16891 .collect::<Vec<_>>(),
16892 split,
16893 window,
16894 cx,
16895 )
16896 })?
16897 .await?;
16898 anyhow::Ok(navigated)
16899 })
16900 }
16901
16902 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16903 let selection = self.selections.newest_anchor();
16904 let head = selection.head();
16905 let tail = selection.tail();
16906
16907 let Some((buffer, start_position)) =
16908 self.buffer.read(cx).text_anchor_for_position(head, cx)
16909 else {
16910 return;
16911 };
16912
16913 let end_position = if head != tail {
16914 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16915 return;
16916 };
16917 Some(pos)
16918 } else {
16919 None
16920 };
16921
16922 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16923 let url = if let Some(end_pos) = end_position {
16924 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16925 } else {
16926 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16927 };
16928
16929 if let Some(url) = url {
16930 cx.update(|window, cx| {
16931 if parse_zed_link(&url, cx).is_some() {
16932 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16933 } else {
16934 cx.open_url(&url);
16935 }
16936 })?;
16937 }
16938
16939 anyhow::Ok(())
16940 });
16941
16942 url_finder.detach();
16943 }
16944
16945 pub fn open_selected_filename(
16946 &mut self,
16947 _: &OpenSelectedFilename,
16948 window: &mut Window,
16949 cx: &mut Context<Self>,
16950 ) {
16951 let Some(workspace) = self.workspace() else {
16952 return;
16953 };
16954
16955 let position = self.selections.newest_anchor().head();
16956
16957 let Some((buffer, buffer_position)) =
16958 self.buffer.read(cx).text_anchor_for_position(position, cx)
16959 else {
16960 return;
16961 };
16962
16963 let project = self.project.clone();
16964
16965 cx.spawn_in(window, async move |_, cx| {
16966 let result = find_file(&buffer, project, buffer_position, cx).await;
16967
16968 if let Some((_, path)) = result {
16969 workspace
16970 .update_in(cx, |workspace, window, cx| {
16971 workspace.open_resolved_path(path, window, cx)
16972 })?
16973 .await?;
16974 }
16975 anyhow::Ok(())
16976 })
16977 .detach();
16978 }
16979
16980 pub(crate) fn navigate_to_hover_links(
16981 &mut self,
16982 kind: Option<GotoDefinitionKind>,
16983 definitions: Vec<HoverLink>,
16984 split: bool,
16985 window: &mut Window,
16986 cx: &mut Context<Editor>,
16987 ) -> Task<Result<Navigated>> {
16988 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16989 let mut first_url_or_file = None;
16990 let definitions: Vec<_> = definitions
16991 .into_iter()
16992 .filter_map(|def| match def {
16993 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16994 HoverLink::InlayHint(lsp_location, server_id) => {
16995 let computation =
16996 self.compute_target_location(lsp_location, server_id, window, cx);
16997 Some(cx.background_spawn(computation))
16998 }
16999 HoverLink::Url(url) => {
17000 first_url_or_file = Some(Either::Left(url));
17001 None
17002 }
17003 HoverLink::File(path) => {
17004 first_url_or_file = Some(Either::Right(path));
17005 None
17006 }
17007 })
17008 .collect();
17009
17010 let workspace = self.workspace();
17011
17012 cx.spawn_in(window, async move |editor, cx| {
17013 let locations: Vec<Location> = future::join_all(definitions)
17014 .await
17015 .into_iter()
17016 .filter_map(|location| location.transpose())
17017 .collect::<Result<_>>()
17018 .context("location tasks")?;
17019 let mut locations = cx.update(|_, cx| {
17020 locations
17021 .into_iter()
17022 .map(|location| {
17023 let buffer = location.buffer.read(cx);
17024 (location.buffer, location.range.to_point(buffer))
17025 })
17026 .into_group_map()
17027 })?;
17028 let mut num_locations = 0;
17029 for ranges in locations.values_mut() {
17030 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17031 ranges.dedup();
17032 num_locations += ranges.len();
17033 }
17034
17035 if num_locations > 1 {
17036 let Some(workspace) = workspace else {
17037 return Ok(Navigated::No);
17038 };
17039
17040 let tab_kind = match kind {
17041 Some(GotoDefinitionKind::Implementation) => "Implementations",
17042 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17043 Some(GotoDefinitionKind::Declaration) => "Declarations",
17044 Some(GotoDefinitionKind::Type) => "Types",
17045 };
17046 let title = editor
17047 .update_in(cx, |_, _, cx| {
17048 let target = locations
17049 .iter()
17050 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17051 .map(|(buffer, location)| {
17052 buffer
17053 .read(cx)
17054 .text_for_range(location.clone())
17055 .collect::<String>()
17056 })
17057 .filter(|text| !text.contains('\n'))
17058 .unique()
17059 .take(3)
17060 .join(", ");
17061 if target.is_empty() {
17062 tab_kind.to_owned()
17063 } else {
17064 format!("{tab_kind} for {target}")
17065 }
17066 })
17067 .context("buffer title")?;
17068
17069 let opened = workspace
17070 .update_in(cx, |workspace, window, cx| {
17071 Self::open_locations_in_multibuffer(
17072 workspace,
17073 locations,
17074 title,
17075 split,
17076 MultibufferSelectionMode::First,
17077 window,
17078 cx,
17079 )
17080 })
17081 .is_ok();
17082
17083 anyhow::Ok(Navigated::from_bool(opened))
17084 } else if num_locations == 0 {
17085 // If there is one url or file, open it directly
17086 match first_url_or_file {
17087 Some(Either::Left(url)) => {
17088 cx.update(|_, cx| cx.open_url(&url))?;
17089 Ok(Navigated::Yes)
17090 }
17091 Some(Either::Right(path)) => {
17092 let Some(workspace) = workspace else {
17093 return Ok(Navigated::No);
17094 };
17095
17096 workspace
17097 .update_in(cx, |workspace, window, cx| {
17098 workspace.open_resolved_path(path, window, cx)
17099 })?
17100 .await?;
17101 Ok(Navigated::Yes)
17102 }
17103 None => Ok(Navigated::No),
17104 }
17105 } else {
17106 let Some(workspace) = workspace else {
17107 return Ok(Navigated::No);
17108 };
17109
17110 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17111 let target_range = target_ranges.first().unwrap().clone();
17112
17113 editor.update_in(cx, |editor, window, cx| {
17114 let range = target_range.to_point(target_buffer.read(cx));
17115 let range = editor.range_for_match(&range);
17116 let range = collapse_multiline_range(range);
17117
17118 if !split
17119 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17120 {
17121 editor.go_to_singleton_buffer_range(range, window, cx);
17122 } else {
17123 let pane = workspace.read(cx).active_pane().clone();
17124 window.defer(cx, move |window, cx| {
17125 let target_editor: Entity<Self> =
17126 workspace.update(cx, |workspace, cx| {
17127 let pane = if split {
17128 workspace.adjacent_pane(window, cx)
17129 } else {
17130 workspace.active_pane().clone()
17131 };
17132
17133 workspace.open_project_item(
17134 pane,
17135 target_buffer.clone(),
17136 true,
17137 true,
17138 window,
17139 cx,
17140 )
17141 });
17142 target_editor.update(cx, |target_editor, cx| {
17143 // When selecting a definition in a different buffer, disable the nav history
17144 // to avoid creating a history entry at the previous cursor location.
17145 pane.update(cx, |pane, _| pane.disable_history());
17146 target_editor.go_to_singleton_buffer_range(range, window, cx);
17147 pane.update(cx, |pane, _| pane.enable_history());
17148 });
17149 });
17150 }
17151 Navigated::Yes
17152 })
17153 }
17154 })
17155 }
17156
17157 fn compute_target_location(
17158 &self,
17159 lsp_location: lsp::Location,
17160 server_id: LanguageServerId,
17161 window: &mut Window,
17162 cx: &mut Context<Self>,
17163 ) -> Task<anyhow::Result<Option<Location>>> {
17164 let Some(project) = self.project.clone() else {
17165 return Task::ready(Ok(None));
17166 };
17167
17168 cx.spawn_in(window, async move |editor, cx| {
17169 let location_task = editor.update(cx, |_, cx| {
17170 project.update(cx, |project, cx| {
17171 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17172 })
17173 })?;
17174 let location = Some({
17175 let target_buffer_handle = location_task.await.context("open local buffer")?;
17176 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17177 let target_start = target_buffer
17178 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17179 let target_end = target_buffer
17180 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17181 target_buffer.anchor_after(target_start)
17182 ..target_buffer.anchor_before(target_end)
17183 })?;
17184 Location {
17185 buffer: target_buffer_handle,
17186 range,
17187 }
17188 });
17189 Ok(location)
17190 })
17191 }
17192
17193 fn go_to_next_reference(
17194 &mut self,
17195 _: &GoToNextReference,
17196 window: &mut Window,
17197 cx: &mut Context<Self>,
17198 ) {
17199 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17200 if let Some(task) = task {
17201 task.detach();
17202 };
17203 }
17204
17205 fn go_to_prev_reference(
17206 &mut self,
17207 _: &GoToPreviousReference,
17208 window: &mut Window,
17209 cx: &mut Context<Self>,
17210 ) {
17211 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17212 if let Some(task) = task {
17213 task.detach();
17214 };
17215 }
17216
17217 pub fn go_to_reference_before_or_after_position(
17218 &mut self,
17219 direction: Direction,
17220 count: usize,
17221 window: &mut Window,
17222 cx: &mut Context<Self>,
17223 ) -> Option<Task<Result<()>>> {
17224 let selection = self.selections.newest_anchor();
17225 let head = selection.head();
17226
17227 let multi_buffer = self.buffer.read(cx);
17228
17229 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17230 let workspace = self.workspace()?;
17231 let project = workspace.read(cx).project().clone();
17232 let references =
17233 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17234 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17235 let Some(locations) = references.await? else {
17236 return Ok(());
17237 };
17238
17239 if locations.is_empty() {
17240 // totally normal - the cursor may be on something which is not
17241 // a symbol (e.g. a keyword)
17242 log::info!("no references found under cursor");
17243 return Ok(());
17244 }
17245
17246 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17247
17248 let (locations, current_location_index) =
17249 multi_buffer.update(cx, |multi_buffer, cx| {
17250 let mut locations = locations
17251 .into_iter()
17252 .filter_map(|loc| {
17253 let start = multi_buffer.buffer_anchor_to_anchor(
17254 &loc.buffer,
17255 loc.range.start,
17256 cx,
17257 )?;
17258 let end = multi_buffer.buffer_anchor_to_anchor(
17259 &loc.buffer,
17260 loc.range.end,
17261 cx,
17262 )?;
17263 Some(start..end)
17264 })
17265 .collect::<Vec<_>>();
17266
17267 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17268 // There is an O(n) implementation, but given this list will be
17269 // small (usually <100 items), the extra O(log(n)) factor isn't
17270 // worth the (surprisingly large amount of) extra complexity.
17271 locations
17272 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17273
17274 let head_offset = head.to_offset(&multi_buffer_snapshot);
17275
17276 let current_location_index = locations.iter().position(|loc| {
17277 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17278 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17279 });
17280
17281 (locations, current_location_index)
17282 })?;
17283
17284 let Some(current_location_index) = current_location_index else {
17285 // This indicates something has gone wrong, because we already
17286 // handle the "no references" case above
17287 log::error!(
17288 "failed to find current reference under cursor. Total references: {}",
17289 locations.len()
17290 );
17291 return Ok(());
17292 };
17293
17294 let destination_location_index = match direction {
17295 Direction::Next => (current_location_index + count) % locations.len(),
17296 Direction::Prev => {
17297 (current_location_index + locations.len() - count % locations.len())
17298 % locations.len()
17299 }
17300 };
17301
17302 // TODO(cameron): is this needed?
17303 // the thinking is to avoid "jumping to the current location" (avoid
17304 // polluting "jumplist" in vim terms)
17305 if current_location_index == destination_location_index {
17306 return Ok(());
17307 }
17308
17309 let Range { start, end } = locations[destination_location_index];
17310
17311 editor.update_in(cx, |editor, window, cx| {
17312 let effects = SelectionEffects::default();
17313
17314 editor.unfold_ranges(&[start..end], false, false, cx);
17315 editor.change_selections(effects, window, cx, |s| {
17316 s.select_ranges([start..start]);
17317 });
17318 })?;
17319
17320 Ok(())
17321 }))
17322 }
17323
17324 pub fn find_all_references(
17325 &mut self,
17326 _: &FindAllReferences,
17327 window: &mut Window,
17328 cx: &mut Context<Self>,
17329 ) -> Option<Task<Result<Navigated>>> {
17330 let selection = self
17331 .selections
17332 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17333 let multi_buffer = self.buffer.read(cx);
17334 let head = selection.head();
17335
17336 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17337 let head_anchor = multi_buffer_snapshot.anchor_at(
17338 head,
17339 if head < selection.tail() {
17340 Bias::Right
17341 } else {
17342 Bias::Left
17343 },
17344 );
17345
17346 match self
17347 .find_all_references_task_sources
17348 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17349 {
17350 Ok(_) => {
17351 log::info!(
17352 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17353 );
17354 return None;
17355 }
17356 Err(i) => {
17357 self.find_all_references_task_sources.insert(i, head_anchor);
17358 }
17359 }
17360
17361 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17362 let workspace = self.workspace()?;
17363 let project = workspace.read(cx).project().clone();
17364 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17365 Some(cx.spawn_in(window, async move |editor, cx| {
17366 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17367 if let Ok(i) = editor
17368 .find_all_references_task_sources
17369 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17370 {
17371 editor.find_all_references_task_sources.remove(i);
17372 }
17373 });
17374
17375 let Some(locations) = references.await? else {
17376 return anyhow::Ok(Navigated::No);
17377 };
17378 let mut locations = cx.update(|_, cx| {
17379 locations
17380 .into_iter()
17381 .map(|location| {
17382 let buffer = location.buffer.read(cx);
17383 (location.buffer, location.range.to_point(buffer))
17384 })
17385 .into_group_map()
17386 })?;
17387 if locations.is_empty() {
17388 return anyhow::Ok(Navigated::No);
17389 }
17390 for ranges in locations.values_mut() {
17391 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17392 ranges.dedup();
17393 }
17394
17395 workspace.update_in(cx, |workspace, window, cx| {
17396 let target = locations
17397 .iter()
17398 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17399 .map(|(buffer, location)| {
17400 buffer
17401 .read(cx)
17402 .text_for_range(location.clone())
17403 .collect::<String>()
17404 })
17405 .filter(|text| !text.contains('\n'))
17406 .unique()
17407 .take(3)
17408 .join(", ");
17409 let title = if target.is_empty() {
17410 "References".to_owned()
17411 } else {
17412 format!("References to {target}")
17413 };
17414 Self::open_locations_in_multibuffer(
17415 workspace,
17416 locations,
17417 title,
17418 false,
17419 MultibufferSelectionMode::First,
17420 window,
17421 cx,
17422 );
17423 Navigated::Yes
17424 })
17425 }))
17426 }
17427
17428 /// Opens a multibuffer with the given project locations in it
17429 pub fn open_locations_in_multibuffer(
17430 workspace: &mut Workspace,
17431 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17432 title: String,
17433 split: bool,
17434 multibuffer_selection_mode: MultibufferSelectionMode,
17435 window: &mut Window,
17436 cx: &mut Context<Workspace>,
17437 ) {
17438 if locations.is_empty() {
17439 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17440 return;
17441 }
17442
17443 let capability = workspace.project().read(cx).capability();
17444 let mut ranges = <Vec<Range<Anchor>>>::new();
17445
17446 // a key to find existing multibuffer editors with the same set of locations
17447 // to prevent us from opening more and more multibuffer tabs for searches and the like
17448 let mut key = (title.clone(), vec![]);
17449 let excerpt_buffer = cx.new(|cx| {
17450 let key = &mut key.1;
17451 let mut multibuffer = MultiBuffer::new(capability);
17452 for (buffer, mut ranges_for_buffer) in locations {
17453 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17454 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17455 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17456 PathKey::for_buffer(&buffer, cx),
17457 buffer.clone(),
17458 ranges_for_buffer,
17459 multibuffer_context_lines(cx),
17460 cx,
17461 );
17462 ranges.extend(new_ranges)
17463 }
17464
17465 multibuffer.with_title(title)
17466 });
17467 let existing = workspace.active_pane().update(cx, |pane, cx| {
17468 pane.items()
17469 .filter_map(|item| item.downcast::<Editor>())
17470 .find(|editor| {
17471 editor
17472 .read(cx)
17473 .lookup_key
17474 .as_ref()
17475 .and_then(|it| {
17476 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17477 })
17478 .is_some_and(|it| *it == key)
17479 })
17480 });
17481 let editor = existing.unwrap_or_else(|| {
17482 cx.new(|cx| {
17483 let mut editor = Editor::for_multibuffer(
17484 excerpt_buffer,
17485 Some(workspace.project().clone()),
17486 window,
17487 cx,
17488 );
17489 editor.lookup_key = Some(Box::new(key));
17490 editor
17491 })
17492 });
17493 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17494 MultibufferSelectionMode::First => {
17495 if let Some(first_range) = ranges.first() {
17496 editor.change_selections(
17497 SelectionEffects::no_scroll(),
17498 window,
17499 cx,
17500 |selections| {
17501 selections.clear_disjoint();
17502 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17503 },
17504 );
17505 }
17506 editor.highlight_background::<Self>(
17507 &ranges,
17508 |theme| theme.colors().editor_highlighted_line_background,
17509 cx,
17510 );
17511 }
17512 MultibufferSelectionMode::All => {
17513 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17514 selections.clear_disjoint();
17515 selections.select_anchor_ranges(ranges);
17516 });
17517 }
17518 });
17519
17520 let item = Box::new(editor);
17521 let item_id = item.item_id();
17522
17523 if split {
17524 let pane = workspace.adjacent_pane(window, cx);
17525 workspace.add_item(pane, item, None, true, true, window, cx);
17526 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17527 let (preview_item_id, preview_item_idx) =
17528 workspace.active_pane().read_with(cx, |pane, _| {
17529 (pane.preview_item_id(), pane.preview_item_idx())
17530 });
17531
17532 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17533
17534 if let Some(preview_item_id) = preview_item_id {
17535 workspace.active_pane().update(cx, |pane, cx| {
17536 pane.remove_item(preview_item_id, false, false, window, cx);
17537 });
17538 }
17539 } else {
17540 workspace.add_item_to_active_pane(item, None, true, window, cx);
17541 }
17542 workspace.active_pane().update(cx, |pane, cx| {
17543 pane.set_preview_item_id(Some(item_id), cx);
17544 });
17545 }
17546
17547 pub fn rename(
17548 &mut self,
17549 _: &Rename,
17550 window: &mut Window,
17551 cx: &mut Context<Self>,
17552 ) -> Option<Task<Result<()>>> {
17553 use language::ToOffset as _;
17554
17555 let provider = self.semantics_provider.clone()?;
17556 let selection = self.selections.newest_anchor().clone();
17557 let (cursor_buffer, cursor_buffer_position) = self
17558 .buffer
17559 .read(cx)
17560 .text_anchor_for_position(selection.head(), cx)?;
17561 let (tail_buffer, cursor_buffer_position_end) = self
17562 .buffer
17563 .read(cx)
17564 .text_anchor_for_position(selection.tail(), cx)?;
17565 if tail_buffer != cursor_buffer {
17566 return None;
17567 }
17568
17569 let snapshot = cursor_buffer.read(cx).snapshot();
17570 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17571 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17572 let prepare_rename = provider
17573 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17574 .unwrap_or_else(|| Task::ready(Ok(None)));
17575 drop(snapshot);
17576
17577 Some(cx.spawn_in(window, async move |this, cx| {
17578 let rename_range = if let Some(range) = prepare_rename.await? {
17579 Some(range)
17580 } else {
17581 this.update(cx, |this, cx| {
17582 let buffer = this.buffer.read(cx).snapshot(cx);
17583 let mut buffer_highlights = this
17584 .document_highlights_for_position(selection.head(), &buffer)
17585 .filter(|highlight| {
17586 highlight.start.excerpt_id == selection.head().excerpt_id
17587 && highlight.end.excerpt_id == selection.head().excerpt_id
17588 });
17589 buffer_highlights
17590 .next()
17591 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17592 })?
17593 };
17594 if let Some(rename_range) = rename_range {
17595 this.update_in(cx, |this, window, cx| {
17596 let snapshot = cursor_buffer.read(cx).snapshot();
17597 let rename_buffer_range = rename_range.to_offset(&snapshot);
17598 let cursor_offset_in_rename_range =
17599 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17600 let cursor_offset_in_rename_range_end =
17601 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17602
17603 this.take_rename(false, window, cx);
17604 let buffer = this.buffer.read(cx).read(cx);
17605 let cursor_offset = selection.head().to_offset(&buffer);
17606 let rename_start =
17607 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17608 let rename_end = rename_start + rename_buffer_range.len();
17609 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17610 let mut old_highlight_id = None;
17611 let old_name: Arc<str> = buffer
17612 .chunks(rename_start..rename_end, true)
17613 .map(|chunk| {
17614 if old_highlight_id.is_none() {
17615 old_highlight_id = chunk.syntax_highlight_id;
17616 }
17617 chunk.text
17618 })
17619 .collect::<String>()
17620 .into();
17621
17622 drop(buffer);
17623
17624 // Position the selection in the rename editor so that it matches the current selection.
17625 this.show_local_selections = false;
17626 let rename_editor = cx.new(|cx| {
17627 let mut editor = Editor::single_line(window, cx);
17628 editor.buffer.update(cx, |buffer, cx| {
17629 buffer.edit(
17630 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17631 None,
17632 cx,
17633 )
17634 });
17635 let cursor_offset_in_rename_range =
17636 MultiBufferOffset(cursor_offset_in_rename_range);
17637 let cursor_offset_in_rename_range_end =
17638 MultiBufferOffset(cursor_offset_in_rename_range_end);
17639 let rename_selection_range = match cursor_offset_in_rename_range
17640 .cmp(&cursor_offset_in_rename_range_end)
17641 {
17642 Ordering::Equal => {
17643 editor.select_all(&SelectAll, window, cx);
17644 return editor;
17645 }
17646 Ordering::Less => {
17647 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17648 }
17649 Ordering::Greater => {
17650 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17651 }
17652 };
17653 if rename_selection_range.end.0 > old_name.len() {
17654 editor.select_all(&SelectAll, window, cx);
17655 } else {
17656 editor.change_selections(Default::default(), window, cx, |s| {
17657 s.select_ranges([rename_selection_range]);
17658 });
17659 }
17660 editor
17661 });
17662 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17663 if e == &EditorEvent::Focused {
17664 cx.emit(EditorEvent::FocusedIn)
17665 }
17666 })
17667 .detach();
17668
17669 let write_highlights =
17670 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17671 let read_highlights =
17672 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17673 let ranges = write_highlights
17674 .iter()
17675 .flat_map(|(_, ranges)| ranges.iter())
17676 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17677 .cloned()
17678 .collect();
17679
17680 this.highlight_text::<Rename>(
17681 ranges,
17682 HighlightStyle {
17683 fade_out: Some(0.6),
17684 ..Default::default()
17685 },
17686 cx,
17687 );
17688 let rename_focus_handle = rename_editor.focus_handle(cx);
17689 window.focus(&rename_focus_handle);
17690 let block_id = this.insert_blocks(
17691 [BlockProperties {
17692 style: BlockStyle::Flex,
17693 placement: BlockPlacement::Below(range.start),
17694 height: Some(1),
17695 render: Arc::new({
17696 let rename_editor = rename_editor.clone();
17697 move |cx: &mut BlockContext| {
17698 let mut text_style = cx.editor_style.text.clone();
17699 if let Some(highlight_style) = old_highlight_id
17700 .and_then(|h| h.style(&cx.editor_style.syntax))
17701 {
17702 text_style = text_style.highlight(highlight_style);
17703 }
17704 div()
17705 .block_mouse_except_scroll()
17706 .pl(cx.anchor_x)
17707 .child(EditorElement::new(
17708 &rename_editor,
17709 EditorStyle {
17710 background: cx.theme().system().transparent,
17711 local_player: cx.editor_style.local_player,
17712 text: text_style,
17713 scrollbar_width: cx.editor_style.scrollbar_width,
17714 syntax: cx.editor_style.syntax.clone(),
17715 status: cx.editor_style.status.clone(),
17716 inlay_hints_style: HighlightStyle {
17717 font_weight: Some(FontWeight::BOLD),
17718 ..make_inlay_hints_style(cx.app)
17719 },
17720 edit_prediction_styles: make_suggestion_styles(
17721 cx.app,
17722 ),
17723 ..EditorStyle::default()
17724 },
17725 ))
17726 .into_any_element()
17727 }
17728 }),
17729 priority: 0,
17730 }],
17731 Some(Autoscroll::fit()),
17732 cx,
17733 )[0];
17734 this.pending_rename = Some(RenameState {
17735 range,
17736 old_name,
17737 editor: rename_editor,
17738 block_id,
17739 });
17740 })?;
17741 }
17742
17743 Ok(())
17744 }))
17745 }
17746
17747 pub fn confirm_rename(
17748 &mut self,
17749 _: &ConfirmRename,
17750 window: &mut Window,
17751 cx: &mut Context<Self>,
17752 ) -> Option<Task<Result<()>>> {
17753 let rename = self.take_rename(false, window, cx)?;
17754 let workspace = self.workspace()?.downgrade();
17755 let (buffer, start) = self
17756 .buffer
17757 .read(cx)
17758 .text_anchor_for_position(rename.range.start, cx)?;
17759 let (end_buffer, _) = self
17760 .buffer
17761 .read(cx)
17762 .text_anchor_for_position(rename.range.end, cx)?;
17763 if buffer != end_buffer {
17764 return None;
17765 }
17766
17767 let old_name = rename.old_name;
17768 let new_name = rename.editor.read(cx).text(cx);
17769
17770 let rename = self.semantics_provider.as_ref()?.perform_rename(
17771 &buffer,
17772 start,
17773 new_name.clone(),
17774 cx,
17775 )?;
17776
17777 Some(cx.spawn_in(window, async move |editor, cx| {
17778 let project_transaction = rename.await?;
17779 Self::open_project_transaction(
17780 &editor,
17781 workspace,
17782 project_transaction,
17783 format!("Rename: {} → {}", old_name, new_name),
17784 cx,
17785 )
17786 .await?;
17787
17788 editor.update(cx, |editor, cx| {
17789 editor.refresh_document_highlights(cx);
17790 })?;
17791 Ok(())
17792 }))
17793 }
17794
17795 fn take_rename(
17796 &mut self,
17797 moving_cursor: bool,
17798 window: &mut Window,
17799 cx: &mut Context<Self>,
17800 ) -> Option<RenameState> {
17801 let rename = self.pending_rename.take()?;
17802 if rename.editor.focus_handle(cx).is_focused(window) {
17803 window.focus(&self.focus_handle);
17804 }
17805
17806 self.remove_blocks(
17807 [rename.block_id].into_iter().collect(),
17808 Some(Autoscroll::fit()),
17809 cx,
17810 );
17811 self.clear_highlights::<Rename>(cx);
17812 self.show_local_selections = true;
17813
17814 if moving_cursor {
17815 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17816 editor
17817 .selections
17818 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17819 .head()
17820 });
17821
17822 // Update the selection to match the position of the selection inside
17823 // the rename editor.
17824 let snapshot = self.buffer.read(cx).read(cx);
17825 let rename_range = rename.range.to_offset(&snapshot);
17826 let cursor_in_editor = snapshot
17827 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17828 .min(rename_range.end);
17829 drop(snapshot);
17830
17831 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17832 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17833 });
17834 } else {
17835 self.refresh_document_highlights(cx);
17836 }
17837
17838 Some(rename)
17839 }
17840
17841 pub fn pending_rename(&self) -> Option<&RenameState> {
17842 self.pending_rename.as_ref()
17843 }
17844
17845 fn format(
17846 &mut self,
17847 _: &Format,
17848 window: &mut Window,
17849 cx: &mut Context<Self>,
17850 ) -> Option<Task<Result<()>>> {
17851 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17852
17853 let project = match &self.project {
17854 Some(project) => project.clone(),
17855 None => return None,
17856 };
17857
17858 Some(self.perform_format(
17859 project,
17860 FormatTrigger::Manual,
17861 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17862 window,
17863 cx,
17864 ))
17865 }
17866
17867 fn format_selections(
17868 &mut self,
17869 _: &FormatSelections,
17870 window: &mut Window,
17871 cx: &mut Context<Self>,
17872 ) -> Option<Task<Result<()>>> {
17873 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17874
17875 let project = match &self.project {
17876 Some(project) => project.clone(),
17877 None => return None,
17878 };
17879
17880 let ranges = self
17881 .selections
17882 .all_adjusted(&self.display_snapshot(cx))
17883 .into_iter()
17884 .map(|selection| selection.range())
17885 .collect_vec();
17886
17887 Some(self.perform_format(
17888 project,
17889 FormatTrigger::Manual,
17890 FormatTarget::Ranges(ranges),
17891 window,
17892 cx,
17893 ))
17894 }
17895
17896 fn perform_format(
17897 &mut self,
17898 project: Entity<Project>,
17899 trigger: FormatTrigger,
17900 target: FormatTarget,
17901 window: &mut Window,
17902 cx: &mut Context<Self>,
17903 ) -> Task<Result<()>> {
17904 let buffer = self.buffer.clone();
17905 let (buffers, target) = match target {
17906 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17907 FormatTarget::Ranges(selection_ranges) => {
17908 let multi_buffer = buffer.read(cx);
17909 let snapshot = multi_buffer.read(cx);
17910 let mut buffers = HashSet::default();
17911 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17912 BTreeMap::new();
17913 for selection_range in selection_ranges {
17914 for (buffer, buffer_range, _) in
17915 snapshot.range_to_buffer_ranges(selection_range)
17916 {
17917 let buffer_id = buffer.remote_id();
17918 let start = buffer.anchor_before(buffer_range.start);
17919 let end = buffer.anchor_after(buffer_range.end);
17920 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17921 buffer_id_to_ranges
17922 .entry(buffer_id)
17923 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17924 .or_insert_with(|| vec![start..end]);
17925 }
17926 }
17927 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17928 }
17929 };
17930
17931 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17932 let selections_prev = transaction_id_prev
17933 .and_then(|transaction_id_prev| {
17934 // default to selections as they were after the last edit, if we have them,
17935 // instead of how they are now.
17936 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17937 // will take you back to where you made the last edit, instead of staying where you scrolled
17938 self.selection_history
17939 .transaction(transaction_id_prev)
17940 .map(|t| t.0.clone())
17941 })
17942 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17943
17944 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17945 let format = project.update(cx, |project, cx| {
17946 project.format(buffers, target, true, trigger, cx)
17947 });
17948
17949 cx.spawn_in(window, async move |editor, cx| {
17950 let transaction = futures::select_biased! {
17951 transaction = format.log_err().fuse() => transaction,
17952 () = timeout => {
17953 log::warn!("timed out waiting for formatting");
17954 None
17955 }
17956 };
17957
17958 buffer
17959 .update(cx, |buffer, cx| {
17960 if let Some(transaction) = transaction
17961 && !buffer.is_singleton()
17962 {
17963 buffer.push_transaction(&transaction.0, cx);
17964 }
17965 cx.notify();
17966 })
17967 .ok();
17968
17969 if let Some(transaction_id_now) =
17970 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17971 {
17972 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17973 if has_new_transaction {
17974 _ = editor.update(cx, |editor, _| {
17975 editor
17976 .selection_history
17977 .insert_transaction(transaction_id_now, selections_prev);
17978 });
17979 }
17980 }
17981
17982 Ok(())
17983 })
17984 }
17985
17986 fn organize_imports(
17987 &mut self,
17988 _: &OrganizeImports,
17989 window: &mut Window,
17990 cx: &mut Context<Self>,
17991 ) -> Option<Task<Result<()>>> {
17992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17993 let project = match &self.project {
17994 Some(project) => project.clone(),
17995 None => return None,
17996 };
17997 Some(self.perform_code_action_kind(
17998 project,
17999 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18000 window,
18001 cx,
18002 ))
18003 }
18004
18005 fn perform_code_action_kind(
18006 &mut self,
18007 project: Entity<Project>,
18008 kind: CodeActionKind,
18009 window: &mut Window,
18010 cx: &mut Context<Self>,
18011 ) -> Task<Result<()>> {
18012 let buffer = self.buffer.clone();
18013 let buffers = buffer.read(cx).all_buffers();
18014 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18015 let apply_action = project.update(cx, |project, cx| {
18016 project.apply_code_action_kind(buffers, kind, true, cx)
18017 });
18018 cx.spawn_in(window, async move |_, cx| {
18019 let transaction = futures::select_biased! {
18020 () = timeout => {
18021 log::warn!("timed out waiting for executing code action");
18022 None
18023 }
18024 transaction = apply_action.log_err().fuse() => transaction,
18025 };
18026 buffer
18027 .update(cx, |buffer, cx| {
18028 // check if we need this
18029 if let Some(transaction) = transaction
18030 && !buffer.is_singleton()
18031 {
18032 buffer.push_transaction(&transaction.0, cx);
18033 }
18034 cx.notify();
18035 })
18036 .ok();
18037 Ok(())
18038 })
18039 }
18040
18041 pub fn restart_language_server(
18042 &mut self,
18043 _: &RestartLanguageServer,
18044 _: &mut Window,
18045 cx: &mut Context<Self>,
18046 ) {
18047 if let Some(project) = self.project.clone() {
18048 self.buffer.update(cx, |multi_buffer, cx| {
18049 project.update(cx, |project, cx| {
18050 project.restart_language_servers_for_buffers(
18051 multi_buffer.all_buffers().into_iter().collect(),
18052 HashSet::default(),
18053 cx,
18054 );
18055 });
18056 })
18057 }
18058 }
18059
18060 pub fn stop_language_server(
18061 &mut self,
18062 _: &StopLanguageServer,
18063 _: &mut Window,
18064 cx: &mut Context<Self>,
18065 ) {
18066 if let Some(project) = self.project.clone() {
18067 self.buffer.update(cx, |multi_buffer, cx| {
18068 project.update(cx, |project, cx| {
18069 project.stop_language_servers_for_buffers(
18070 multi_buffer.all_buffers().into_iter().collect(),
18071 HashSet::default(),
18072 cx,
18073 );
18074 });
18075 });
18076 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18077 }
18078 }
18079
18080 fn cancel_language_server_work(
18081 workspace: &mut Workspace,
18082 _: &actions::CancelLanguageServerWork,
18083 _: &mut Window,
18084 cx: &mut Context<Workspace>,
18085 ) {
18086 let project = workspace.project();
18087 let buffers = workspace
18088 .active_item(cx)
18089 .and_then(|item| item.act_as::<Editor>(cx))
18090 .map_or(HashSet::default(), |editor| {
18091 editor.read(cx).buffer.read(cx).all_buffers()
18092 });
18093 project.update(cx, |project, cx| {
18094 project.cancel_language_server_work_for_buffers(buffers, cx);
18095 });
18096 }
18097
18098 fn show_character_palette(
18099 &mut self,
18100 _: &ShowCharacterPalette,
18101 window: &mut Window,
18102 _: &mut Context<Self>,
18103 ) {
18104 window.show_character_palette();
18105 }
18106
18107 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18108 if !self.diagnostics_enabled() {
18109 return;
18110 }
18111
18112 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18113 let buffer = self.buffer.read(cx).snapshot(cx);
18114 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18115 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18116 let is_valid = buffer
18117 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18118 .any(|entry| {
18119 entry.diagnostic.is_primary
18120 && !entry.range.is_empty()
18121 && entry.range.start == primary_range_start
18122 && entry.diagnostic.message == active_diagnostics.active_message
18123 });
18124
18125 if !is_valid {
18126 self.dismiss_diagnostics(cx);
18127 }
18128 }
18129 }
18130
18131 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18132 match &self.active_diagnostics {
18133 ActiveDiagnostic::Group(group) => Some(group),
18134 _ => None,
18135 }
18136 }
18137
18138 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18139 if !self.diagnostics_enabled() {
18140 return;
18141 }
18142 self.dismiss_diagnostics(cx);
18143 self.active_diagnostics = ActiveDiagnostic::All;
18144 }
18145
18146 fn activate_diagnostics(
18147 &mut self,
18148 buffer_id: BufferId,
18149 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18150 window: &mut Window,
18151 cx: &mut Context<Self>,
18152 ) {
18153 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18154 return;
18155 }
18156 self.dismiss_diagnostics(cx);
18157 let snapshot = self.snapshot(window, cx);
18158 let buffer = self.buffer.read(cx).snapshot(cx);
18159 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18160 return;
18161 };
18162
18163 let diagnostic_group = buffer
18164 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18165 .collect::<Vec<_>>();
18166
18167 let language_registry = self
18168 .project()
18169 .map(|project| project.read(cx).languages().clone());
18170
18171 let blocks = renderer.render_group(
18172 diagnostic_group,
18173 buffer_id,
18174 snapshot,
18175 cx.weak_entity(),
18176 language_registry,
18177 cx,
18178 );
18179
18180 let blocks = self.display_map.update(cx, |display_map, cx| {
18181 display_map.insert_blocks(blocks, cx).into_iter().collect()
18182 });
18183 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18184 active_range: buffer.anchor_before(diagnostic.range.start)
18185 ..buffer.anchor_after(diagnostic.range.end),
18186 active_message: diagnostic.diagnostic.message.clone(),
18187 group_id: diagnostic.diagnostic.group_id,
18188 blocks,
18189 });
18190 cx.notify();
18191 }
18192
18193 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18194 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18195 return;
18196 };
18197
18198 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18199 if let ActiveDiagnostic::Group(group) = prev {
18200 self.display_map.update(cx, |display_map, cx| {
18201 display_map.remove_blocks(group.blocks, cx);
18202 });
18203 cx.notify();
18204 }
18205 }
18206
18207 /// Disable inline diagnostics rendering for this editor.
18208 pub fn disable_inline_diagnostics(&mut self) {
18209 self.inline_diagnostics_enabled = false;
18210 self.inline_diagnostics_update = Task::ready(());
18211 self.inline_diagnostics.clear();
18212 }
18213
18214 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18215 self.diagnostics_enabled = false;
18216 self.dismiss_diagnostics(cx);
18217 self.inline_diagnostics_update = Task::ready(());
18218 self.inline_diagnostics.clear();
18219 }
18220
18221 pub fn disable_word_completions(&mut self) {
18222 self.word_completions_enabled = false;
18223 }
18224
18225 pub fn diagnostics_enabled(&self) -> bool {
18226 self.diagnostics_enabled && self.mode.is_full()
18227 }
18228
18229 pub fn inline_diagnostics_enabled(&self) -> bool {
18230 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18231 }
18232
18233 pub fn show_inline_diagnostics(&self) -> bool {
18234 self.show_inline_diagnostics
18235 }
18236
18237 pub fn toggle_inline_diagnostics(
18238 &mut self,
18239 _: &ToggleInlineDiagnostics,
18240 window: &mut Window,
18241 cx: &mut Context<Editor>,
18242 ) {
18243 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18244 self.refresh_inline_diagnostics(false, window, cx);
18245 }
18246
18247 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18248 self.diagnostics_max_severity = severity;
18249 self.display_map.update(cx, |display_map, _| {
18250 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18251 });
18252 }
18253
18254 pub fn toggle_diagnostics(
18255 &mut self,
18256 _: &ToggleDiagnostics,
18257 window: &mut Window,
18258 cx: &mut Context<Editor>,
18259 ) {
18260 if !self.diagnostics_enabled() {
18261 return;
18262 }
18263
18264 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18265 EditorSettings::get_global(cx)
18266 .diagnostics_max_severity
18267 .filter(|severity| severity != &DiagnosticSeverity::Off)
18268 .unwrap_or(DiagnosticSeverity::Hint)
18269 } else {
18270 DiagnosticSeverity::Off
18271 };
18272 self.set_max_diagnostics_severity(new_severity, cx);
18273 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18274 self.active_diagnostics = ActiveDiagnostic::None;
18275 self.inline_diagnostics_update = Task::ready(());
18276 self.inline_diagnostics.clear();
18277 } else {
18278 self.refresh_inline_diagnostics(false, window, cx);
18279 }
18280
18281 cx.notify();
18282 }
18283
18284 pub fn toggle_minimap(
18285 &mut self,
18286 _: &ToggleMinimap,
18287 window: &mut Window,
18288 cx: &mut Context<Editor>,
18289 ) {
18290 if self.supports_minimap(cx) {
18291 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18292 }
18293 }
18294
18295 fn refresh_inline_diagnostics(
18296 &mut self,
18297 debounce: bool,
18298 window: &mut Window,
18299 cx: &mut Context<Self>,
18300 ) {
18301 let max_severity = ProjectSettings::get_global(cx)
18302 .diagnostics
18303 .inline
18304 .max_severity
18305 .unwrap_or(self.diagnostics_max_severity);
18306
18307 if !self.inline_diagnostics_enabled()
18308 || !self.diagnostics_enabled()
18309 || !self.show_inline_diagnostics
18310 || max_severity == DiagnosticSeverity::Off
18311 {
18312 self.inline_diagnostics_update = Task::ready(());
18313 self.inline_diagnostics.clear();
18314 return;
18315 }
18316
18317 let debounce_ms = ProjectSettings::get_global(cx)
18318 .diagnostics
18319 .inline
18320 .update_debounce_ms;
18321 let debounce = if debounce && debounce_ms > 0 {
18322 Some(Duration::from_millis(debounce_ms))
18323 } else {
18324 None
18325 };
18326 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18327 if let Some(debounce) = debounce {
18328 cx.background_executor().timer(debounce).await;
18329 }
18330 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18331 editor
18332 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18333 .ok()
18334 }) else {
18335 return;
18336 };
18337
18338 let new_inline_diagnostics = cx
18339 .background_spawn(async move {
18340 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18341 for diagnostic_entry in
18342 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18343 {
18344 let message = diagnostic_entry
18345 .diagnostic
18346 .message
18347 .split_once('\n')
18348 .map(|(line, _)| line)
18349 .map(SharedString::new)
18350 .unwrap_or_else(|| {
18351 SharedString::new(&*diagnostic_entry.diagnostic.message)
18352 });
18353 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18354 let (Ok(i) | Err(i)) = inline_diagnostics
18355 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18356 inline_diagnostics.insert(
18357 i,
18358 (
18359 start_anchor,
18360 InlineDiagnostic {
18361 message,
18362 group_id: diagnostic_entry.diagnostic.group_id,
18363 start: diagnostic_entry.range.start.to_point(&snapshot),
18364 is_primary: diagnostic_entry.diagnostic.is_primary,
18365 severity: diagnostic_entry.diagnostic.severity,
18366 },
18367 ),
18368 );
18369 }
18370 inline_diagnostics
18371 })
18372 .await;
18373
18374 editor
18375 .update(cx, |editor, cx| {
18376 editor.inline_diagnostics = new_inline_diagnostics;
18377 cx.notify();
18378 })
18379 .ok();
18380 });
18381 }
18382
18383 fn pull_diagnostics(
18384 &mut self,
18385 buffer_id: Option<BufferId>,
18386 window: &Window,
18387 cx: &mut Context<Self>,
18388 ) -> Option<()> {
18389 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18390 return None;
18391 }
18392 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18393 .diagnostics
18394 .lsp_pull_diagnostics;
18395 if !pull_diagnostics_settings.enabled {
18396 return None;
18397 }
18398 let project = self.project()?.downgrade();
18399 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18400 let mut buffers = self.buffer.read(cx).all_buffers();
18401 buffers.retain(|buffer| {
18402 let buffer_id_to_retain = buffer.read(cx).remote_id();
18403 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18404 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18405 });
18406 if buffers.is_empty() {
18407 self.pull_diagnostics_task = Task::ready(());
18408 return None;
18409 }
18410
18411 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18412 cx.background_executor().timer(debounce).await;
18413
18414 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18415 buffers
18416 .into_iter()
18417 .filter_map(|buffer| {
18418 project
18419 .update(cx, |project, cx| {
18420 project.lsp_store().update(cx, |lsp_store, cx| {
18421 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18422 })
18423 })
18424 .ok()
18425 })
18426 .collect::<FuturesUnordered<_>>()
18427 }) else {
18428 return;
18429 };
18430
18431 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18432 match pull_task {
18433 Ok(()) => {
18434 if editor
18435 .update_in(cx, |editor, window, cx| {
18436 editor.update_diagnostics_state(window, cx);
18437 })
18438 .is_err()
18439 {
18440 return;
18441 }
18442 }
18443 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18444 }
18445 }
18446 });
18447
18448 Some(())
18449 }
18450
18451 pub fn set_selections_from_remote(
18452 &mut self,
18453 selections: Vec<Selection<Anchor>>,
18454 pending_selection: Option<Selection<Anchor>>,
18455 window: &mut Window,
18456 cx: &mut Context<Self>,
18457 ) {
18458 let old_cursor_position = self.selections.newest_anchor().head();
18459 self.selections
18460 .change_with(&self.display_snapshot(cx), |s| {
18461 s.select_anchors(selections);
18462 if let Some(pending_selection) = pending_selection {
18463 s.set_pending(pending_selection, SelectMode::Character);
18464 } else {
18465 s.clear_pending();
18466 }
18467 });
18468 self.selections_did_change(
18469 false,
18470 &old_cursor_position,
18471 SelectionEffects::default(),
18472 window,
18473 cx,
18474 );
18475 }
18476
18477 pub fn transact(
18478 &mut self,
18479 window: &mut Window,
18480 cx: &mut Context<Self>,
18481 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18482 ) -> Option<TransactionId> {
18483 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18484 this.start_transaction_at(Instant::now(), window, cx);
18485 update(this, window, cx);
18486 this.end_transaction_at(Instant::now(), cx)
18487 })
18488 }
18489
18490 pub fn start_transaction_at(
18491 &mut self,
18492 now: Instant,
18493 window: &mut Window,
18494 cx: &mut Context<Self>,
18495 ) -> Option<TransactionId> {
18496 self.end_selection(window, cx);
18497 if let Some(tx_id) = self
18498 .buffer
18499 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18500 {
18501 self.selection_history
18502 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18503 cx.emit(EditorEvent::TransactionBegun {
18504 transaction_id: tx_id,
18505 });
18506 Some(tx_id)
18507 } else {
18508 None
18509 }
18510 }
18511
18512 pub fn end_transaction_at(
18513 &mut self,
18514 now: Instant,
18515 cx: &mut Context<Self>,
18516 ) -> Option<TransactionId> {
18517 if let Some(transaction_id) = self
18518 .buffer
18519 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18520 {
18521 if let Some((_, end_selections)) =
18522 self.selection_history.transaction_mut(transaction_id)
18523 {
18524 *end_selections = Some(self.selections.disjoint_anchors_arc());
18525 } else {
18526 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18527 }
18528
18529 cx.emit(EditorEvent::Edited { transaction_id });
18530 Some(transaction_id)
18531 } else {
18532 None
18533 }
18534 }
18535
18536 pub fn modify_transaction_selection_history(
18537 &mut self,
18538 transaction_id: TransactionId,
18539 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18540 ) -> bool {
18541 self.selection_history
18542 .transaction_mut(transaction_id)
18543 .map(modify)
18544 .is_some()
18545 }
18546
18547 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18548 if self.selection_mark_mode {
18549 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18550 s.move_with(|_, sel| {
18551 sel.collapse_to(sel.head(), SelectionGoal::None);
18552 });
18553 })
18554 }
18555 self.selection_mark_mode = true;
18556 cx.notify();
18557 }
18558
18559 pub fn swap_selection_ends(
18560 &mut self,
18561 _: &actions::SwapSelectionEnds,
18562 window: &mut Window,
18563 cx: &mut Context<Self>,
18564 ) {
18565 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18566 s.move_with(|_, sel| {
18567 if sel.start != sel.end {
18568 sel.reversed = !sel.reversed
18569 }
18570 });
18571 });
18572 self.request_autoscroll(Autoscroll::newest(), cx);
18573 cx.notify();
18574 }
18575
18576 pub fn toggle_focus(
18577 workspace: &mut Workspace,
18578 _: &actions::ToggleFocus,
18579 window: &mut Window,
18580 cx: &mut Context<Workspace>,
18581 ) {
18582 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18583 return;
18584 };
18585 workspace.activate_item(&item, true, true, window, cx);
18586 }
18587
18588 pub fn toggle_fold(
18589 &mut self,
18590 _: &actions::ToggleFold,
18591 window: &mut Window,
18592 cx: &mut Context<Self>,
18593 ) {
18594 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18595 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18596 let selection = self.selections.newest::<Point>(&display_map);
18597
18598 let range = if selection.is_empty() {
18599 let point = selection.head().to_display_point(&display_map);
18600 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18601 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18602 .to_point(&display_map);
18603 start..end
18604 } else {
18605 selection.range()
18606 };
18607 if display_map.folds_in_range(range).next().is_some() {
18608 self.unfold_lines(&Default::default(), window, cx)
18609 } else {
18610 self.fold(&Default::default(), window, cx)
18611 }
18612 } else {
18613 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18614 let buffer_ids: HashSet<_> = self
18615 .selections
18616 .disjoint_anchor_ranges()
18617 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18618 .collect();
18619
18620 let should_unfold = buffer_ids
18621 .iter()
18622 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18623
18624 for buffer_id in buffer_ids {
18625 if should_unfold {
18626 self.unfold_buffer(buffer_id, cx);
18627 } else {
18628 self.fold_buffer(buffer_id, cx);
18629 }
18630 }
18631 }
18632 }
18633
18634 pub fn toggle_fold_recursive(
18635 &mut self,
18636 _: &actions::ToggleFoldRecursive,
18637 window: &mut Window,
18638 cx: &mut Context<Self>,
18639 ) {
18640 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18641
18642 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18643 let range = if selection.is_empty() {
18644 let point = selection.head().to_display_point(&display_map);
18645 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18646 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18647 .to_point(&display_map);
18648 start..end
18649 } else {
18650 selection.range()
18651 };
18652 if display_map.folds_in_range(range).next().is_some() {
18653 self.unfold_recursive(&Default::default(), window, cx)
18654 } else {
18655 self.fold_recursive(&Default::default(), window, cx)
18656 }
18657 }
18658
18659 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18660 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18661 let mut to_fold = Vec::new();
18662 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18663 let selections = self.selections.all_adjusted(&display_map);
18664
18665 for selection in selections {
18666 let range = selection.range().sorted();
18667 let buffer_start_row = range.start.row;
18668
18669 if range.start.row != range.end.row {
18670 let mut found = false;
18671 let mut row = range.start.row;
18672 while row <= range.end.row {
18673 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18674 {
18675 found = true;
18676 row = crease.range().end.row + 1;
18677 to_fold.push(crease);
18678 } else {
18679 row += 1
18680 }
18681 }
18682 if found {
18683 continue;
18684 }
18685 }
18686
18687 for row in (0..=range.start.row).rev() {
18688 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18689 && crease.range().end.row >= buffer_start_row
18690 {
18691 to_fold.push(crease);
18692 if row <= range.start.row {
18693 break;
18694 }
18695 }
18696 }
18697 }
18698
18699 self.fold_creases(to_fold, true, window, cx);
18700 } else {
18701 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18702 let buffer_ids = self
18703 .selections
18704 .disjoint_anchor_ranges()
18705 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18706 .collect::<HashSet<_>>();
18707 for buffer_id in buffer_ids {
18708 self.fold_buffer(buffer_id, cx);
18709 }
18710 }
18711 }
18712
18713 pub fn toggle_fold_all(
18714 &mut self,
18715 _: &actions::ToggleFoldAll,
18716 window: &mut Window,
18717 cx: &mut Context<Self>,
18718 ) {
18719 if self.buffer.read(cx).is_singleton() {
18720 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18721 let has_folds = display_map
18722 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18723 .next()
18724 .is_some();
18725
18726 if has_folds {
18727 self.unfold_all(&actions::UnfoldAll, window, cx);
18728 } else {
18729 self.fold_all(&actions::FoldAll, window, cx);
18730 }
18731 } else {
18732 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18733 let should_unfold = buffer_ids
18734 .iter()
18735 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18736
18737 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18738 editor
18739 .update_in(cx, |editor, _, cx| {
18740 for buffer_id in buffer_ids {
18741 if should_unfold {
18742 editor.unfold_buffer(buffer_id, cx);
18743 } else {
18744 editor.fold_buffer(buffer_id, cx);
18745 }
18746 }
18747 })
18748 .ok();
18749 });
18750 }
18751 }
18752
18753 fn fold_at_level(
18754 &mut self,
18755 fold_at: &FoldAtLevel,
18756 window: &mut Window,
18757 cx: &mut Context<Self>,
18758 ) {
18759 if !self.buffer.read(cx).is_singleton() {
18760 return;
18761 }
18762
18763 let fold_at_level = fold_at.0;
18764 let snapshot = self.buffer.read(cx).snapshot(cx);
18765 let mut to_fold = Vec::new();
18766 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18767
18768 let row_ranges_to_keep: Vec<Range<u32>> = self
18769 .selections
18770 .all::<Point>(&self.display_snapshot(cx))
18771 .into_iter()
18772 .map(|sel| sel.start.row..sel.end.row)
18773 .collect();
18774
18775 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18776 while start_row < end_row {
18777 match self
18778 .snapshot(window, cx)
18779 .crease_for_buffer_row(MultiBufferRow(start_row))
18780 {
18781 Some(crease) => {
18782 let nested_start_row = crease.range().start.row + 1;
18783 let nested_end_row = crease.range().end.row;
18784
18785 if current_level < fold_at_level {
18786 stack.push((nested_start_row, nested_end_row, current_level + 1));
18787 } else if current_level == fold_at_level {
18788 // Fold iff there is no selection completely contained within the fold region
18789 if !row_ranges_to_keep.iter().any(|selection| {
18790 selection.end >= nested_start_row
18791 && selection.start <= nested_end_row
18792 }) {
18793 to_fold.push(crease);
18794 }
18795 }
18796
18797 start_row = nested_end_row + 1;
18798 }
18799 None => start_row += 1,
18800 }
18801 }
18802 }
18803
18804 self.fold_creases(to_fold, true, window, cx);
18805 }
18806
18807 pub fn fold_at_level_1(
18808 &mut self,
18809 _: &actions::FoldAtLevel1,
18810 window: &mut Window,
18811 cx: &mut Context<Self>,
18812 ) {
18813 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18814 }
18815
18816 pub fn fold_at_level_2(
18817 &mut self,
18818 _: &actions::FoldAtLevel2,
18819 window: &mut Window,
18820 cx: &mut Context<Self>,
18821 ) {
18822 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18823 }
18824
18825 pub fn fold_at_level_3(
18826 &mut self,
18827 _: &actions::FoldAtLevel3,
18828 window: &mut Window,
18829 cx: &mut Context<Self>,
18830 ) {
18831 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18832 }
18833
18834 pub fn fold_at_level_4(
18835 &mut self,
18836 _: &actions::FoldAtLevel4,
18837 window: &mut Window,
18838 cx: &mut Context<Self>,
18839 ) {
18840 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18841 }
18842
18843 pub fn fold_at_level_5(
18844 &mut self,
18845 _: &actions::FoldAtLevel5,
18846 window: &mut Window,
18847 cx: &mut Context<Self>,
18848 ) {
18849 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18850 }
18851
18852 pub fn fold_at_level_6(
18853 &mut self,
18854 _: &actions::FoldAtLevel6,
18855 window: &mut Window,
18856 cx: &mut Context<Self>,
18857 ) {
18858 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18859 }
18860
18861 pub fn fold_at_level_7(
18862 &mut self,
18863 _: &actions::FoldAtLevel7,
18864 window: &mut Window,
18865 cx: &mut Context<Self>,
18866 ) {
18867 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18868 }
18869
18870 pub fn fold_at_level_8(
18871 &mut self,
18872 _: &actions::FoldAtLevel8,
18873 window: &mut Window,
18874 cx: &mut Context<Self>,
18875 ) {
18876 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18877 }
18878
18879 pub fn fold_at_level_9(
18880 &mut self,
18881 _: &actions::FoldAtLevel9,
18882 window: &mut Window,
18883 cx: &mut Context<Self>,
18884 ) {
18885 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18886 }
18887
18888 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18889 if self.buffer.read(cx).is_singleton() {
18890 let mut fold_ranges = Vec::new();
18891 let snapshot = self.buffer.read(cx).snapshot(cx);
18892
18893 for row in 0..snapshot.max_row().0 {
18894 if let Some(foldable_range) = self
18895 .snapshot(window, cx)
18896 .crease_for_buffer_row(MultiBufferRow(row))
18897 {
18898 fold_ranges.push(foldable_range);
18899 }
18900 }
18901
18902 self.fold_creases(fold_ranges, true, window, cx);
18903 } else {
18904 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18905 editor
18906 .update_in(cx, |editor, _, cx| {
18907 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18908 editor.fold_buffer(buffer_id, cx);
18909 }
18910 })
18911 .ok();
18912 });
18913 }
18914 }
18915
18916 pub fn fold_function_bodies(
18917 &mut self,
18918 _: &actions::FoldFunctionBodies,
18919 window: &mut Window,
18920 cx: &mut Context<Self>,
18921 ) {
18922 let snapshot = self.buffer.read(cx).snapshot(cx);
18923
18924 let ranges = snapshot
18925 .text_object_ranges(
18926 MultiBufferOffset(0)..snapshot.len(),
18927 TreeSitterOptions::default(),
18928 )
18929 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18930 .collect::<Vec<_>>();
18931
18932 let creases = ranges
18933 .into_iter()
18934 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18935 .collect();
18936
18937 self.fold_creases(creases, true, window, cx);
18938 }
18939
18940 pub fn fold_recursive(
18941 &mut self,
18942 _: &actions::FoldRecursive,
18943 window: &mut Window,
18944 cx: &mut Context<Self>,
18945 ) {
18946 let mut to_fold = Vec::new();
18947 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18948 let selections = self.selections.all_adjusted(&display_map);
18949
18950 for selection in selections {
18951 let range = selection.range().sorted();
18952 let buffer_start_row = range.start.row;
18953
18954 if range.start.row != range.end.row {
18955 let mut found = false;
18956 for row in range.start.row..=range.end.row {
18957 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18958 found = true;
18959 to_fold.push(crease);
18960 }
18961 }
18962 if found {
18963 continue;
18964 }
18965 }
18966
18967 for row in (0..=range.start.row).rev() {
18968 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18969 if crease.range().end.row >= buffer_start_row {
18970 to_fold.push(crease);
18971 } else {
18972 break;
18973 }
18974 }
18975 }
18976 }
18977
18978 self.fold_creases(to_fold, true, window, cx);
18979 }
18980
18981 pub fn fold_at(
18982 &mut self,
18983 buffer_row: MultiBufferRow,
18984 window: &mut Window,
18985 cx: &mut Context<Self>,
18986 ) {
18987 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18988
18989 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18990 let autoscroll = self
18991 .selections
18992 .all::<Point>(&display_map)
18993 .iter()
18994 .any(|selection| crease.range().overlaps(&selection.range()));
18995
18996 self.fold_creases(vec![crease], autoscroll, window, cx);
18997 }
18998 }
18999
19000 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19001 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19002 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19003 let buffer = display_map.buffer_snapshot();
19004 let selections = self.selections.all::<Point>(&display_map);
19005 let ranges = selections
19006 .iter()
19007 .map(|s| {
19008 let range = s.display_range(&display_map).sorted();
19009 let mut start = range.start.to_point(&display_map);
19010 let mut end = range.end.to_point(&display_map);
19011 start.column = 0;
19012 end.column = buffer.line_len(MultiBufferRow(end.row));
19013 start..end
19014 })
19015 .collect::<Vec<_>>();
19016
19017 self.unfold_ranges(&ranges, true, true, cx);
19018 } else {
19019 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19020 let buffer_ids = self
19021 .selections
19022 .disjoint_anchor_ranges()
19023 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19024 .collect::<HashSet<_>>();
19025 for buffer_id in buffer_ids {
19026 self.unfold_buffer(buffer_id, cx);
19027 }
19028 }
19029 }
19030
19031 pub fn unfold_recursive(
19032 &mut self,
19033 _: &UnfoldRecursive,
19034 _window: &mut Window,
19035 cx: &mut Context<Self>,
19036 ) {
19037 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19038 let selections = self.selections.all::<Point>(&display_map);
19039 let ranges = selections
19040 .iter()
19041 .map(|s| {
19042 let mut range = s.display_range(&display_map).sorted();
19043 *range.start.column_mut() = 0;
19044 *range.end.column_mut() = display_map.line_len(range.end.row());
19045 let start = range.start.to_point(&display_map);
19046 let end = range.end.to_point(&display_map);
19047 start..end
19048 })
19049 .collect::<Vec<_>>();
19050
19051 self.unfold_ranges(&ranges, true, true, cx);
19052 }
19053
19054 pub fn unfold_at(
19055 &mut self,
19056 buffer_row: MultiBufferRow,
19057 _window: &mut Window,
19058 cx: &mut Context<Self>,
19059 ) {
19060 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19061
19062 let intersection_range = Point::new(buffer_row.0, 0)
19063 ..Point::new(
19064 buffer_row.0,
19065 display_map.buffer_snapshot().line_len(buffer_row),
19066 );
19067
19068 let autoscroll = self
19069 .selections
19070 .all::<Point>(&display_map)
19071 .iter()
19072 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19073
19074 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19075 }
19076
19077 pub fn unfold_all(
19078 &mut self,
19079 _: &actions::UnfoldAll,
19080 _window: &mut Window,
19081 cx: &mut Context<Self>,
19082 ) {
19083 if self.buffer.read(cx).is_singleton() {
19084 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19085 self.unfold_ranges(
19086 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19087 true,
19088 true,
19089 cx,
19090 );
19091 } else {
19092 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19093 editor
19094 .update(cx, |editor, cx| {
19095 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19096 editor.unfold_buffer(buffer_id, cx);
19097 }
19098 })
19099 .ok();
19100 });
19101 }
19102 }
19103
19104 pub fn fold_selected_ranges(
19105 &mut self,
19106 _: &FoldSelectedRanges,
19107 window: &mut Window,
19108 cx: &mut Context<Self>,
19109 ) {
19110 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19111 let selections = self.selections.all_adjusted(&display_map);
19112 let ranges = selections
19113 .into_iter()
19114 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19115 .collect::<Vec<_>>();
19116 self.fold_creases(ranges, true, window, cx);
19117 }
19118
19119 pub fn fold_ranges<T: ToOffset + Clone>(
19120 &mut self,
19121 ranges: Vec<Range<T>>,
19122 auto_scroll: bool,
19123 window: &mut Window,
19124 cx: &mut Context<Self>,
19125 ) {
19126 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19127 let ranges = ranges
19128 .into_iter()
19129 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19130 .collect::<Vec<_>>();
19131 self.fold_creases(ranges, auto_scroll, window, cx);
19132 }
19133
19134 pub fn fold_creases<T: ToOffset + Clone>(
19135 &mut self,
19136 creases: Vec<Crease<T>>,
19137 auto_scroll: bool,
19138 _window: &mut Window,
19139 cx: &mut Context<Self>,
19140 ) {
19141 if creases.is_empty() {
19142 return;
19143 }
19144
19145 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19146
19147 if auto_scroll {
19148 self.request_autoscroll(Autoscroll::fit(), cx);
19149 }
19150
19151 cx.notify();
19152
19153 self.scrollbar_marker_state.dirty = true;
19154 self.folds_did_change(cx);
19155 }
19156
19157 /// Removes any folds whose ranges intersect any of the given ranges.
19158 pub fn unfold_ranges<T: ToOffset + Clone>(
19159 &mut self,
19160 ranges: &[Range<T>],
19161 inclusive: bool,
19162 auto_scroll: bool,
19163 cx: &mut Context<Self>,
19164 ) {
19165 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19166 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19167 });
19168 self.folds_did_change(cx);
19169 }
19170
19171 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19172 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19173 return;
19174 }
19175
19176 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19177 self.display_map.update(cx, |display_map, cx| {
19178 display_map.fold_buffers([buffer_id], cx)
19179 });
19180
19181 let snapshot = self.display_snapshot(cx);
19182 self.selections.change_with(&snapshot, |selections| {
19183 selections.remove_selections_from_buffer(buffer_id);
19184 });
19185
19186 cx.emit(EditorEvent::BufferFoldToggled {
19187 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19188 folded: true,
19189 });
19190 cx.notify();
19191 }
19192
19193 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19194 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19195 return;
19196 }
19197 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19198 self.display_map.update(cx, |display_map, cx| {
19199 display_map.unfold_buffers([buffer_id], cx);
19200 });
19201 cx.emit(EditorEvent::BufferFoldToggled {
19202 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19203 folded: false,
19204 });
19205 cx.notify();
19206 }
19207
19208 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19209 self.display_map.read(cx).is_buffer_folded(buffer)
19210 }
19211
19212 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19213 self.display_map.read(cx).folded_buffers()
19214 }
19215
19216 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19217 self.display_map.update(cx, |display_map, cx| {
19218 display_map.disable_header_for_buffer(buffer_id, cx);
19219 });
19220 cx.notify();
19221 }
19222
19223 /// Removes any folds with the given ranges.
19224 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19225 &mut self,
19226 ranges: &[Range<T>],
19227 type_id: TypeId,
19228 auto_scroll: bool,
19229 cx: &mut Context<Self>,
19230 ) {
19231 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19232 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19233 });
19234 self.folds_did_change(cx);
19235 }
19236
19237 fn remove_folds_with<T: ToOffset + Clone>(
19238 &mut self,
19239 ranges: &[Range<T>],
19240 auto_scroll: bool,
19241 cx: &mut Context<Self>,
19242 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19243 ) {
19244 if ranges.is_empty() {
19245 return;
19246 }
19247
19248 let mut buffers_affected = HashSet::default();
19249 let multi_buffer = self.buffer().read(cx);
19250 for range in ranges {
19251 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19252 buffers_affected.insert(buffer.read(cx).remote_id());
19253 };
19254 }
19255
19256 self.display_map.update(cx, update);
19257
19258 if auto_scroll {
19259 self.request_autoscroll(Autoscroll::fit(), cx);
19260 }
19261
19262 cx.notify();
19263 self.scrollbar_marker_state.dirty = true;
19264 self.active_indent_guides_state.dirty = true;
19265 }
19266
19267 pub fn update_renderer_widths(
19268 &mut self,
19269 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19270 cx: &mut Context<Self>,
19271 ) -> bool {
19272 self.display_map
19273 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19274 }
19275
19276 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19277 self.display_map.read(cx).fold_placeholder.clone()
19278 }
19279
19280 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19281 self.use_base_text_line_numbers = show;
19282 }
19283
19284 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19285 self.buffer.update(cx, |buffer, cx| {
19286 buffer.set_all_diff_hunks_expanded(cx);
19287 });
19288 }
19289
19290 pub fn expand_all_diff_hunks(
19291 &mut self,
19292 _: &ExpandAllDiffHunks,
19293 _window: &mut Window,
19294 cx: &mut Context<Self>,
19295 ) {
19296 self.buffer.update(cx, |buffer, cx| {
19297 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19298 });
19299 }
19300
19301 pub fn collapse_all_diff_hunks(
19302 &mut self,
19303 _: &CollapseAllDiffHunks,
19304 _window: &mut Window,
19305 cx: &mut Context<Self>,
19306 ) {
19307 self.buffer.update(cx, |buffer, cx| {
19308 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19309 });
19310 }
19311
19312 pub fn toggle_selected_diff_hunks(
19313 &mut self,
19314 _: &ToggleSelectedDiffHunks,
19315 _window: &mut Window,
19316 cx: &mut Context<Self>,
19317 ) {
19318 let ranges: Vec<_> = self
19319 .selections
19320 .disjoint_anchors()
19321 .iter()
19322 .map(|s| s.range())
19323 .collect();
19324 self.toggle_diff_hunks_in_ranges(ranges, cx);
19325 }
19326
19327 pub fn diff_hunks_in_ranges<'a>(
19328 &'a self,
19329 ranges: &'a [Range<Anchor>],
19330 buffer: &'a MultiBufferSnapshot,
19331 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19332 ranges.iter().flat_map(move |range| {
19333 let end_excerpt_id = range.end.excerpt_id;
19334 let range = range.to_point(buffer);
19335 let mut peek_end = range.end;
19336 if range.end.row < buffer.max_row().0 {
19337 peek_end = Point::new(range.end.row + 1, 0);
19338 }
19339 buffer
19340 .diff_hunks_in_range(range.start..peek_end)
19341 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19342 })
19343 }
19344
19345 pub fn has_stageable_diff_hunks_in_ranges(
19346 &self,
19347 ranges: &[Range<Anchor>],
19348 snapshot: &MultiBufferSnapshot,
19349 ) -> bool {
19350 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19351 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19352 }
19353
19354 pub fn toggle_staged_selected_diff_hunks(
19355 &mut self,
19356 _: &::git::ToggleStaged,
19357 _: &mut Window,
19358 cx: &mut Context<Self>,
19359 ) {
19360 let snapshot = self.buffer.read(cx).snapshot(cx);
19361 let ranges: Vec<_> = self
19362 .selections
19363 .disjoint_anchors()
19364 .iter()
19365 .map(|s| s.range())
19366 .collect();
19367 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19368 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19369 }
19370
19371 pub fn set_render_diff_hunk_controls(
19372 &mut self,
19373 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19374 cx: &mut Context<Self>,
19375 ) {
19376 self.render_diff_hunk_controls = render_diff_hunk_controls;
19377 cx.notify();
19378 }
19379
19380 pub fn stage_and_next(
19381 &mut self,
19382 _: &::git::StageAndNext,
19383 window: &mut Window,
19384 cx: &mut Context<Self>,
19385 ) {
19386 self.do_stage_or_unstage_and_next(true, window, cx);
19387 }
19388
19389 pub fn unstage_and_next(
19390 &mut self,
19391 _: &::git::UnstageAndNext,
19392 window: &mut Window,
19393 cx: &mut Context<Self>,
19394 ) {
19395 self.do_stage_or_unstage_and_next(false, window, cx);
19396 }
19397
19398 pub fn stage_or_unstage_diff_hunks(
19399 &mut self,
19400 stage: bool,
19401 ranges: Vec<Range<Anchor>>,
19402 cx: &mut Context<Self>,
19403 ) {
19404 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19405 cx.spawn(async move |this, cx| {
19406 task.await?;
19407 this.update(cx, |this, cx| {
19408 let snapshot = this.buffer.read(cx).snapshot(cx);
19409 let chunk_by = this
19410 .diff_hunks_in_ranges(&ranges, &snapshot)
19411 .chunk_by(|hunk| hunk.buffer_id);
19412 for (buffer_id, hunks) in &chunk_by {
19413 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19414 }
19415 })
19416 })
19417 .detach_and_log_err(cx);
19418 }
19419
19420 fn save_buffers_for_ranges_if_needed(
19421 &mut self,
19422 ranges: &[Range<Anchor>],
19423 cx: &mut Context<Editor>,
19424 ) -> Task<Result<()>> {
19425 let multibuffer = self.buffer.read(cx);
19426 let snapshot = multibuffer.read(cx);
19427 let buffer_ids: HashSet<_> = ranges
19428 .iter()
19429 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19430 .collect();
19431 drop(snapshot);
19432
19433 let mut buffers = HashSet::default();
19434 for buffer_id in buffer_ids {
19435 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19436 let buffer = buffer_entity.read(cx);
19437 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19438 {
19439 buffers.insert(buffer_entity);
19440 }
19441 }
19442 }
19443
19444 if let Some(project) = &self.project {
19445 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19446 } else {
19447 Task::ready(Ok(()))
19448 }
19449 }
19450
19451 fn do_stage_or_unstage_and_next(
19452 &mut self,
19453 stage: bool,
19454 window: &mut Window,
19455 cx: &mut Context<Self>,
19456 ) {
19457 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19458
19459 if ranges.iter().any(|range| range.start != range.end) {
19460 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19461 return;
19462 }
19463
19464 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19465 let snapshot = self.snapshot(window, cx);
19466 let position = self
19467 .selections
19468 .newest::<Point>(&snapshot.display_snapshot)
19469 .head();
19470 let mut row = snapshot
19471 .buffer_snapshot()
19472 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19473 .find(|hunk| hunk.row_range.start.0 > position.row)
19474 .map(|hunk| hunk.row_range.start);
19475
19476 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19477 // Outside of the project diff editor, wrap around to the beginning.
19478 if !all_diff_hunks_expanded {
19479 row = row.or_else(|| {
19480 snapshot
19481 .buffer_snapshot()
19482 .diff_hunks_in_range(Point::zero()..position)
19483 .find(|hunk| hunk.row_range.end.0 < position.row)
19484 .map(|hunk| hunk.row_range.start)
19485 });
19486 }
19487
19488 if let Some(row) = row {
19489 let destination = Point::new(row.0, 0);
19490 let autoscroll = Autoscroll::center();
19491
19492 self.unfold_ranges(&[destination..destination], false, false, cx);
19493 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19494 s.select_ranges([destination..destination]);
19495 });
19496 }
19497 }
19498
19499 fn do_stage_or_unstage(
19500 &self,
19501 stage: bool,
19502 buffer_id: BufferId,
19503 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19504 cx: &mut App,
19505 ) -> Option<()> {
19506 let project = self.project()?;
19507 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19508 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19509 let buffer_snapshot = buffer.read(cx).snapshot();
19510 let file_exists = buffer_snapshot
19511 .file()
19512 .is_some_and(|file| file.disk_state().exists());
19513 diff.update(cx, |diff, cx| {
19514 diff.stage_or_unstage_hunks(
19515 stage,
19516 &hunks
19517 .map(|hunk| buffer_diff::DiffHunk {
19518 buffer_range: hunk.buffer_range,
19519 // We don't need to pass in word diffs here because they're only used for rendering and
19520 // this function changes internal state
19521 base_word_diffs: Vec::default(),
19522 buffer_word_diffs: Vec::default(),
19523 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19524 ..hunk.diff_base_byte_range.end.0,
19525 secondary_status: hunk.secondary_status,
19526 range: Point::zero()..Point::zero(), // unused
19527 })
19528 .collect::<Vec<_>>(),
19529 &buffer_snapshot,
19530 file_exists,
19531 cx,
19532 )
19533 });
19534 None
19535 }
19536
19537 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19538 let ranges: Vec<_> = self
19539 .selections
19540 .disjoint_anchors()
19541 .iter()
19542 .map(|s| s.range())
19543 .collect();
19544 self.buffer
19545 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19546 }
19547
19548 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19549 self.buffer.update(cx, |buffer, cx| {
19550 let ranges = vec![Anchor::min()..Anchor::max()];
19551 if !buffer.all_diff_hunks_expanded()
19552 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19553 {
19554 buffer.collapse_diff_hunks(ranges, cx);
19555 true
19556 } else {
19557 false
19558 }
19559 })
19560 }
19561
19562 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19563 if self.buffer.read(cx).all_diff_hunks_expanded() {
19564 return true;
19565 }
19566 let ranges = vec![Anchor::min()..Anchor::max()];
19567 self.buffer
19568 .read(cx)
19569 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19570 }
19571
19572 fn toggle_diff_hunks_in_ranges(
19573 &mut self,
19574 ranges: Vec<Range<Anchor>>,
19575 cx: &mut Context<Editor>,
19576 ) {
19577 self.buffer.update(cx, |buffer, cx| {
19578 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19579 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19580 })
19581 }
19582
19583 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19584 self.buffer.update(cx, |buffer, cx| {
19585 let snapshot = buffer.snapshot(cx);
19586 let excerpt_id = range.end.excerpt_id;
19587 let point_range = range.to_point(&snapshot);
19588 let expand = !buffer.single_hunk_is_expanded(range, cx);
19589 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19590 })
19591 }
19592
19593 pub(crate) fn apply_all_diff_hunks(
19594 &mut self,
19595 _: &ApplyAllDiffHunks,
19596 window: &mut Window,
19597 cx: &mut Context<Self>,
19598 ) {
19599 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19600
19601 let buffers = self.buffer.read(cx).all_buffers();
19602 for branch_buffer in buffers {
19603 branch_buffer.update(cx, |branch_buffer, cx| {
19604 branch_buffer.merge_into_base(Vec::new(), cx);
19605 });
19606 }
19607
19608 if let Some(project) = self.project.clone() {
19609 self.save(
19610 SaveOptions {
19611 format: true,
19612 autosave: false,
19613 },
19614 project,
19615 window,
19616 cx,
19617 )
19618 .detach_and_log_err(cx);
19619 }
19620 }
19621
19622 pub(crate) fn apply_selected_diff_hunks(
19623 &mut self,
19624 _: &ApplyDiffHunk,
19625 window: &mut Window,
19626 cx: &mut Context<Self>,
19627 ) {
19628 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19629 let snapshot = self.snapshot(window, cx);
19630 let hunks = snapshot.hunks_for_ranges(
19631 self.selections
19632 .all(&snapshot.display_snapshot)
19633 .into_iter()
19634 .map(|selection| selection.range()),
19635 );
19636 let mut ranges_by_buffer = HashMap::default();
19637 self.transact(window, cx, |editor, _window, cx| {
19638 for hunk in hunks {
19639 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19640 ranges_by_buffer
19641 .entry(buffer.clone())
19642 .or_insert_with(Vec::new)
19643 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19644 }
19645 }
19646
19647 for (buffer, ranges) in ranges_by_buffer {
19648 buffer.update(cx, |buffer, cx| {
19649 buffer.merge_into_base(ranges, cx);
19650 });
19651 }
19652 });
19653
19654 if let Some(project) = self.project.clone() {
19655 self.save(
19656 SaveOptions {
19657 format: true,
19658 autosave: false,
19659 },
19660 project,
19661 window,
19662 cx,
19663 )
19664 .detach_and_log_err(cx);
19665 }
19666 }
19667
19668 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19669 if hovered != self.gutter_hovered {
19670 self.gutter_hovered = hovered;
19671 cx.notify();
19672 }
19673 }
19674
19675 pub fn insert_blocks(
19676 &mut self,
19677 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19678 autoscroll: Option<Autoscroll>,
19679 cx: &mut Context<Self>,
19680 ) -> Vec<CustomBlockId> {
19681 let blocks = self
19682 .display_map
19683 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19684 if let Some(autoscroll) = autoscroll {
19685 self.request_autoscroll(autoscroll, cx);
19686 }
19687 cx.notify();
19688 blocks
19689 }
19690
19691 pub fn resize_blocks(
19692 &mut self,
19693 heights: HashMap<CustomBlockId, u32>,
19694 autoscroll: Option<Autoscroll>,
19695 cx: &mut Context<Self>,
19696 ) {
19697 self.display_map
19698 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19699 if let Some(autoscroll) = autoscroll {
19700 self.request_autoscroll(autoscroll, cx);
19701 }
19702 cx.notify();
19703 }
19704
19705 pub fn replace_blocks(
19706 &mut self,
19707 renderers: HashMap<CustomBlockId, RenderBlock>,
19708 autoscroll: Option<Autoscroll>,
19709 cx: &mut Context<Self>,
19710 ) {
19711 self.display_map
19712 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19713 if let Some(autoscroll) = autoscroll {
19714 self.request_autoscroll(autoscroll, cx);
19715 }
19716 cx.notify();
19717 }
19718
19719 pub fn remove_blocks(
19720 &mut self,
19721 block_ids: HashSet<CustomBlockId>,
19722 autoscroll: Option<Autoscroll>,
19723 cx: &mut Context<Self>,
19724 ) {
19725 self.display_map.update(cx, |display_map, cx| {
19726 display_map.remove_blocks(block_ids, cx)
19727 });
19728 if let Some(autoscroll) = autoscroll {
19729 self.request_autoscroll(autoscroll, cx);
19730 }
19731 cx.notify();
19732 }
19733
19734 pub fn row_for_block(
19735 &self,
19736 block_id: CustomBlockId,
19737 cx: &mut Context<Self>,
19738 ) -> Option<DisplayRow> {
19739 self.display_map
19740 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19741 }
19742
19743 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19744 self.focused_block = Some(focused_block);
19745 }
19746
19747 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19748 self.focused_block.take()
19749 }
19750
19751 pub fn insert_creases(
19752 &mut self,
19753 creases: impl IntoIterator<Item = Crease<Anchor>>,
19754 cx: &mut Context<Self>,
19755 ) -> Vec<CreaseId> {
19756 self.display_map
19757 .update(cx, |map, cx| map.insert_creases(creases, cx))
19758 }
19759
19760 pub fn remove_creases(
19761 &mut self,
19762 ids: impl IntoIterator<Item = CreaseId>,
19763 cx: &mut Context<Self>,
19764 ) -> Vec<(CreaseId, Range<Anchor>)> {
19765 self.display_map
19766 .update(cx, |map, cx| map.remove_creases(ids, cx))
19767 }
19768
19769 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19770 self.display_map
19771 .update(cx, |map, cx| map.snapshot(cx))
19772 .longest_row()
19773 }
19774
19775 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19776 self.display_map
19777 .update(cx, |map, cx| map.snapshot(cx))
19778 .max_point()
19779 }
19780
19781 pub fn text(&self, cx: &App) -> String {
19782 self.buffer.read(cx).read(cx).text()
19783 }
19784
19785 pub fn is_empty(&self, cx: &App) -> bool {
19786 self.buffer.read(cx).read(cx).is_empty()
19787 }
19788
19789 pub fn text_option(&self, cx: &App) -> Option<String> {
19790 let text = self.text(cx);
19791 let text = text.trim();
19792
19793 if text.is_empty() {
19794 return None;
19795 }
19796
19797 Some(text.to_string())
19798 }
19799
19800 pub fn set_text(
19801 &mut self,
19802 text: impl Into<Arc<str>>,
19803 window: &mut Window,
19804 cx: &mut Context<Self>,
19805 ) {
19806 self.transact(window, cx, |this, _, cx| {
19807 this.buffer
19808 .read(cx)
19809 .as_singleton()
19810 .expect("you can only call set_text on editors for singleton buffers")
19811 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19812 });
19813 }
19814
19815 pub fn display_text(&self, cx: &mut App) -> String {
19816 self.display_map
19817 .update(cx, |map, cx| map.snapshot(cx))
19818 .text()
19819 }
19820
19821 fn create_minimap(
19822 &self,
19823 minimap_settings: MinimapSettings,
19824 window: &mut Window,
19825 cx: &mut Context<Self>,
19826 ) -> Option<Entity<Self>> {
19827 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19828 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19829 }
19830
19831 fn initialize_new_minimap(
19832 &self,
19833 minimap_settings: MinimapSettings,
19834 window: &mut Window,
19835 cx: &mut Context<Self>,
19836 ) -> Entity<Self> {
19837 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19838
19839 let mut minimap = Editor::new_internal(
19840 EditorMode::Minimap {
19841 parent: cx.weak_entity(),
19842 },
19843 self.buffer.clone(),
19844 None,
19845 Some(self.display_map.clone()),
19846 window,
19847 cx,
19848 );
19849 minimap.scroll_manager.clone_state(&self.scroll_manager);
19850 minimap.set_text_style_refinement(TextStyleRefinement {
19851 font_size: Some(MINIMAP_FONT_SIZE),
19852 font_weight: Some(MINIMAP_FONT_WEIGHT),
19853 ..Default::default()
19854 });
19855 minimap.update_minimap_configuration(minimap_settings, cx);
19856 cx.new(|_| minimap)
19857 }
19858
19859 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19860 let current_line_highlight = minimap_settings
19861 .current_line_highlight
19862 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19863 self.set_current_line_highlight(Some(current_line_highlight));
19864 }
19865
19866 pub fn minimap(&self) -> Option<&Entity<Self>> {
19867 self.minimap
19868 .as_ref()
19869 .filter(|_| self.minimap_visibility.visible())
19870 }
19871
19872 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19873 let mut wrap_guides = smallvec![];
19874
19875 if self.show_wrap_guides == Some(false) {
19876 return wrap_guides;
19877 }
19878
19879 let settings = self.buffer.read(cx).language_settings(cx);
19880 if settings.show_wrap_guides {
19881 match self.soft_wrap_mode(cx) {
19882 SoftWrap::Column(soft_wrap) => {
19883 wrap_guides.push((soft_wrap as usize, true));
19884 }
19885 SoftWrap::Bounded(soft_wrap) => {
19886 wrap_guides.push((soft_wrap as usize, true));
19887 }
19888 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19889 }
19890 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19891 }
19892
19893 wrap_guides
19894 }
19895
19896 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19897 let settings = self.buffer.read(cx).language_settings(cx);
19898 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19899 match mode {
19900 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19901 SoftWrap::None
19902 }
19903 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19904 language_settings::SoftWrap::PreferredLineLength => {
19905 SoftWrap::Column(settings.preferred_line_length)
19906 }
19907 language_settings::SoftWrap::Bounded => {
19908 SoftWrap::Bounded(settings.preferred_line_length)
19909 }
19910 }
19911 }
19912
19913 pub fn set_soft_wrap_mode(
19914 &mut self,
19915 mode: language_settings::SoftWrap,
19916
19917 cx: &mut Context<Self>,
19918 ) {
19919 self.soft_wrap_mode_override = Some(mode);
19920 cx.notify();
19921 }
19922
19923 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19924 self.hard_wrap = hard_wrap;
19925 cx.notify();
19926 }
19927
19928 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19929 self.text_style_refinement = Some(style);
19930 }
19931
19932 /// called by the Element so we know what style we were most recently rendered with.
19933 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19934 // We intentionally do not inform the display map about the minimap style
19935 // so that wrapping is not recalculated and stays consistent for the editor
19936 // and its linked minimap.
19937 if !self.mode.is_minimap() {
19938 let font = style.text.font();
19939 let font_size = style.text.font_size.to_pixels(window.rem_size());
19940 let display_map = self
19941 .placeholder_display_map
19942 .as_ref()
19943 .filter(|_| self.is_empty(cx))
19944 .unwrap_or(&self.display_map);
19945
19946 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19947 }
19948 self.style = Some(style);
19949 }
19950
19951 pub fn style(&self) -> Option<&EditorStyle> {
19952 self.style.as_ref()
19953 }
19954
19955 // Called by the element. This method is not designed to be called outside of the editor
19956 // element's layout code because it does not notify when rewrapping is computed synchronously.
19957 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19958 if self.is_empty(cx) {
19959 self.placeholder_display_map
19960 .as_ref()
19961 .map_or(false, |display_map| {
19962 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19963 })
19964 } else {
19965 self.display_map
19966 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19967 }
19968 }
19969
19970 pub fn set_soft_wrap(&mut self) {
19971 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19972 }
19973
19974 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19975 if self.soft_wrap_mode_override.is_some() {
19976 self.soft_wrap_mode_override.take();
19977 } else {
19978 let soft_wrap = match self.soft_wrap_mode(cx) {
19979 SoftWrap::GitDiff => return,
19980 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19981 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19982 language_settings::SoftWrap::None
19983 }
19984 };
19985 self.soft_wrap_mode_override = Some(soft_wrap);
19986 }
19987 cx.notify();
19988 }
19989
19990 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19991 let Some(workspace) = self.workspace() else {
19992 return;
19993 };
19994 let fs = workspace.read(cx).app_state().fs.clone();
19995 let current_show = TabBarSettings::get_global(cx).show;
19996 update_settings_file(fs, cx, move |setting, _| {
19997 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19998 });
19999 }
20000
20001 pub fn toggle_indent_guides(
20002 &mut self,
20003 _: &ToggleIndentGuides,
20004 _: &mut Window,
20005 cx: &mut Context<Self>,
20006 ) {
20007 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20008 self.buffer
20009 .read(cx)
20010 .language_settings(cx)
20011 .indent_guides
20012 .enabled
20013 });
20014 self.show_indent_guides = Some(!currently_enabled);
20015 cx.notify();
20016 }
20017
20018 fn should_show_indent_guides(&self) -> Option<bool> {
20019 self.show_indent_guides
20020 }
20021
20022 pub fn toggle_line_numbers(
20023 &mut self,
20024 _: &ToggleLineNumbers,
20025 _: &mut Window,
20026 cx: &mut Context<Self>,
20027 ) {
20028 let mut editor_settings = EditorSettings::get_global(cx).clone();
20029 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20030 EditorSettings::override_global(editor_settings, cx);
20031 }
20032
20033 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20034 if let Some(show_line_numbers) = self.show_line_numbers {
20035 return show_line_numbers;
20036 }
20037 EditorSettings::get_global(cx).gutter.line_numbers
20038 }
20039
20040 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20041 match (
20042 self.use_relative_line_numbers,
20043 EditorSettings::get_global(cx).relative_line_numbers,
20044 ) {
20045 (None, setting) => setting,
20046 (Some(false), _) => RelativeLineNumbers::Disabled,
20047 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20048 (Some(true), _) => RelativeLineNumbers::Enabled,
20049 }
20050 }
20051
20052 pub fn toggle_relative_line_numbers(
20053 &mut self,
20054 _: &ToggleRelativeLineNumbers,
20055 _: &mut Window,
20056 cx: &mut Context<Self>,
20057 ) {
20058 let is_relative = self.relative_line_numbers(cx);
20059 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20060 }
20061
20062 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20063 self.use_relative_line_numbers = is_relative;
20064 cx.notify();
20065 }
20066
20067 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20068 self.show_gutter = show_gutter;
20069 cx.notify();
20070 }
20071
20072 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20073 self.show_scrollbars = ScrollbarAxes {
20074 horizontal: show,
20075 vertical: show,
20076 };
20077 cx.notify();
20078 }
20079
20080 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20081 self.show_scrollbars.vertical = show;
20082 cx.notify();
20083 }
20084
20085 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20086 self.show_scrollbars.horizontal = show;
20087 cx.notify();
20088 }
20089
20090 pub fn set_minimap_visibility(
20091 &mut self,
20092 minimap_visibility: MinimapVisibility,
20093 window: &mut Window,
20094 cx: &mut Context<Self>,
20095 ) {
20096 if self.minimap_visibility != minimap_visibility {
20097 if minimap_visibility.visible() && self.minimap.is_none() {
20098 let minimap_settings = EditorSettings::get_global(cx).minimap;
20099 self.minimap =
20100 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20101 }
20102 self.minimap_visibility = minimap_visibility;
20103 cx.notify();
20104 }
20105 }
20106
20107 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20108 self.set_show_scrollbars(false, cx);
20109 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20110 }
20111
20112 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20113 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20114 }
20115
20116 /// Normally the text in full mode and auto height editors is padded on the
20117 /// left side by roughly half a character width for improved hit testing.
20118 ///
20119 /// Use this method to disable this for cases where this is not wanted (e.g.
20120 /// if you want to align the editor text with some other text above or below)
20121 /// or if you want to add this padding to single-line editors.
20122 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20123 self.offset_content = offset_content;
20124 cx.notify();
20125 }
20126
20127 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20128 self.show_line_numbers = Some(show_line_numbers);
20129 cx.notify();
20130 }
20131
20132 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20133 self.disable_expand_excerpt_buttons = true;
20134 cx.notify();
20135 }
20136
20137 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20138 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20139 cx.notify();
20140 }
20141
20142 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20143 self.show_code_actions = Some(show_code_actions);
20144 cx.notify();
20145 }
20146
20147 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20148 self.show_runnables = Some(show_runnables);
20149 cx.notify();
20150 }
20151
20152 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20153 self.show_breakpoints = Some(show_breakpoints);
20154 cx.notify();
20155 }
20156
20157 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20158 if self.display_map.read(cx).masked != masked {
20159 self.display_map.update(cx, |map, _| map.masked = masked);
20160 }
20161 cx.notify()
20162 }
20163
20164 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20165 self.show_wrap_guides = Some(show_wrap_guides);
20166 cx.notify();
20167 }
20168
20169 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20170 self.show_indent_guides = Some(show_indent_guides);
20171 cx.notify();
20172 }
20173
20174 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20175 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20176 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20177 && let Some(dir) = file.abs_path(cx).parent()
20178 {
20179 return Some(dir.to_owned());
20180 }
20181 }
20182
20183 None
20184 }
20185
20186 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20187 self.active_excerpt(cx)?
20188 .1
20189 .read(cx)
20190 .file()
20191 .and_then(|f| f.as_local())
20192 }
20193
20194 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20195 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20196 let buffer = buffer.read(cx);
20197 if let Some(project_path) = buffer.project_path(cx) {
20198 let project = self.project()?.read(cx);
20199 project.absolute_path(&project_path, cx)
20200 } else {
20201 buffer
20202 .file()
20203 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20204 }
20205 })
20206 }
20207
20208 pub fn reveal_in_finder(
20209 &mut self,
20210 _: &RevealInFileManager,
20211 _window: &mut Window,
20212 cx: &mut Context<Self>,
20213 ) {
20214 if let Some(target) = self.target_file(cx) {
20215 cx.reveal_path(&target.abs_path(cx));
20216 }
20217 }
20218
20219 pub fn copy_path(
20220 &mut self,
20221 _: &zed_actions::workspace::CopyPath,
20222 _window: &mut Window,
20223 cx: &mut Context<Self>,
20224 ) {
20225 if let Some(path) = self.target_file_abs_path(cx)
20226 && let Some(path) = path.to_str()
20227 {
20228 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20229 } else {
20230 cx.propagate();
20231 }
20232 }
20233
20234 pub fn copy_relative_path(
20235 &mut self,
20236 _: &zed_actions::workspace::CopyRelativePath,
20237 _window: &mut Window,
20238 cx: &mut Context<Self>,
20239 ) {
20240 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20241 let project = self.project()?.read(cx);
20242 let path = buffer.read(cx).file()?.path();
20243 let path = path.display(project.path_style(cx));
20244 Some(path)
20245 }) {
20246 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20247 } else {
20248 cx.propagate();
20249 }
20250 }
20251
20252 /// Returns the project path for the editor's buffer, if any buffer is
20253 /// opened in the editor.
20254 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20255 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20256 buffer.read(cx).project_path(cx)
20257 } else {
20258 None
20259 }
20260 }
20261
20262 // Returns true if the editor handled a go-to-line request
20263 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20264 maybe!({
20265 let breakpoint_store = self.breakpoint_store.as_ref()?;
20266
20267 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20268 else {
20269 self.clear_row_highlights::<ActiveDebugLine>();
20270 return None;
20271 };
20272
20273 let position = active_stack_frame.position;
20274 let buffer_id = position.buffer_id?;
20275 let snapshot = self
20276 .project
20277 .as_ref()?
20278 .read(cx)
20279 .buffer_for_id(buffer_id, cx)?
20280 .read(cx)
20281 .snapshot();
20282
20283 let mut handled = false;
20284 for (id, ExcerptRange { context, .. }) in
20285 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20286 {
20287 if context.start.cmp(&position, &snapshot).is_ge()
20288 || context.end.cmp(&position, &snapshot).is_lt()
20289 {
20290 continue;
20291 }
20292 let snapshot = self.buffer.read(cx).snapshot(cx);
20293 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20294
20295 handled = true;
20296 self.clear_row_highlights::<ActiveDebugLine>();
20297
20298 self.go_to_line::<ActiveDebugLine>(
20299 multibuffer_anchor,
20300 Some(cx.theme().colors().editor_debugger_active_line_background),
20301 window,
20302 cx,
20303 );
20304
20305 cx.notify();
20306 }
20307
20308 handled.then_some(())
20309 })
20310 .is_some()
20311 }
20312
20313 pub fn copy_file_name_without_extension(
20314 &mut self,
20315 _: &CopyFileNameWithoutExtension,
20316 _: &mut Window,
20317 cx: &mut Context<Self>,
20318 ) {
20319 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20320 let file = buffer.read(cx).file()?;
20321 file.path().file_stem()
20322 }) {
20323 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20324 }
20325 }
20326
20327 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20328 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20329 let file = buffer.read(cx).file()?;
20330 Some(file.file_name(cx))
20331 }) {
20332 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20333 }
20334 }
20335
20336 pub fn toggle_git_blame(
20337 &mut self,
20338 _: &::git::Blame,
20339 window: &mut Window,
20340 cx: &mut Context<Self>,
20341 ) {
20342 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20343
20344 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20345 self.start_git_blame(true, window, cx);
20346 }
20347
20348 cx.notify();
20349 }
20350
20351 pub fn toggle_git_blame_inline(
20352 &mut self,
20353 _: &ToggleGitBlameInline,
20354 window: &mut Window,
20355 cx: &mut Context<Self>,
20356 ) {
20357 self.toggle_git_blame_inline_internal(true, window, cx);
20358 cx.notify();
20359 }
20360
20361 pub fn open_git_blame_commit(
20362 &mut self,
20363 _: &OpenGitBlameCommit,
20364 window: &mut Window,
20365 cx: &mut Context<Self>,
20366 ) {
20367 self.open_git_blame_commit_internal(window, cx);
20368 }
20369
20370 fn open_git_blame_commit_internal(
20371 &mut self,
20372 window: &mut Window,
20373 cx: &mut Context<Self>,
20374 ) -> Option<()> {
20375 let blame = self.blame.as_ref()?;
20376 let snapshot = self.snapshot(window, cx);
20377 let cursor = self
20378 .selections
20379 .newest::<Point>(&snapshot.display_snapshot)
20380 .head();
20381 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20382 let (_, blame_entry) = blame
20383 .update(cx, |blame, cx| {
20384 blame
20385 .blame_for_rows(
20386 &[RowInfo {
20387 buffer_id: Some(buffer.remote_id()),
20388 buffer_row: Some(point.row),
20389 ..Default::default()
20390 }],
20391 cx,
20392 )
20393 .next()
20394 })
20395 .flatten()?;
20396 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20397 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20398 let workspace = self.workspace()?.downgrade();
20399 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20400 None
20401 }
20402
20403 pub fn git_blame_inline_enabled(&self) -> bool {
20404 self.git_blame_inline_enabled
20405 }
20406
20407 pub fn toggle_selection_menu(
20408 &mut self,
20409 _: &ToggleSelectionMenu,
20410 _: &mut Window,
20411 cx: &mut Context<Self>,
20412 ) {
20413 self.show_selection_menu = self
20414 .show_selection_menu
20415 .map(|show_selections_menu| !show_selections_menu)
20416 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20417
20418 cx.notify();
20419 }
20420
20421 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20422 self.show_selection_menu
20423 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20424 }
20425
20426 fn start_git_blame(
20427 &mut self,
20428 user_triggered: bool,
20429 window: &mut Window,
20430 cx: &mut Context<Self>,
20431 ) {
20432 if let Some(project) = self.project() {
20433 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20434 && buffer.read(cx).file().is_none()
20435 {
20436 return;
20437 }
20438
20439 let focused = self.focus_handle(cx).contains_focused(window, cx);
20440
20441 let project = project.clone();
20442 let blame = cx
20443 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20444 self.blame_subscription =
20445 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20446 self.blame = Some(blame);
20447 }
20448 }
20449
20450 fn toggle_git_blame_inline_internal(
20451 &mut self,
20452 user_triggered: bool,
20453 window: &mut Window,
20454 cx: &mut Context<Self>,
20455 ) {
20456 if self.git_blame_inline_enabled {
20457 self.git_blame_inline_enabled = false;
20458 self.show_git_blame_inline = false;
20459 self.show_git_blame_inline_delay_task.take();
20460 } else {
20461 self.git_blame_inline_enabled = true;
20462 self.start_git_blame_inline(user_triggered, window, cx);
20463 }
20464
20465 cx.notify();
20466 }
20467
20468 fn start_git_blame_inline(
20469 &mut self,
20470 user_triggered: bool,
20471 window: &mut Window,
20472 cx: &mut Context<Self>,
20473 ) {
20474 self.start_git_blame(user_triggered, window, cx);
20475
20476 if ProjectSettings::get_global(cx)
20477 .git
20478 .inline_blame_delay()
20479 .is_some()
20480 {
20481 self.start_inline_blame_timer(window, cx);
20482 } else {
20483 self.show_git_blame_inline = true
20484 }
20485 }
20486
20487 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20488 self.blame.as_ref()
20489 }
20490
20491 pub fn show_git_blame_gutter(&self) -> bool {
20492 self.show_git_blame_gutter
20493 }
20494
20495 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20496 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20497 }
20498
20499 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20500 self.show_git_blame_inline
20501 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20502 && !self.newest_selection_head_on_empty_line(cx)
20503 && self.has_blame_entries(cx)
20504 }
20505
20506 fn has_blame_entries(&self, cx: &App) -> bool {
20507 self.blame()
20508 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20509 }
20510
20511 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20512 let cursor_anchor = self.selections.newest_anchor().head();
20513
20514 let snapshot = self.buffer.read(cx).snapshot(cx);
20515 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20516
20517 snapshot.line_len(buffer_row) == 0
20518 }
20519
20520 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20521 let buffer_and_selection = maybe!({
20522 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20523 let selection_range = selection.range();
20524
20525 let multi_buffer = self.buffer().read(cx);
20526 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20527 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20528
20529 let (buffer, range, _) = if selection.reversed {
20530 buffer_ranges.first()
20531 } else {
20532 buffer_ranges.last()
20533 }?;
20534
20535 let selection = text::ToPoint::to_point(&range.start, buffer).row
20536 ..text::ToPoint::to_point(&range.end, buffer).row;
20537 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20538 });
20539
20540 let Some((buffer, selection)) = buffer_and_selection else {
20541 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20542 };
20543
20544 let Some(project) = self.project() else {
20545 return Task::ready(Err(anyhow!("editor does not have project")));
20546 };
20547
20548 project.update(cx, |project, cx| {
20549 project.get_permalink_to_line(&buffer, selection, cx)
20550 })
20551 }
20552
20553 pub fn copy_permalink_to_line(
20554 &mut self,
20555 _: &CopyPermalinkToLine,
20556 window: &mut Window,
20557 cx: &mut Context<Self>,
20558 ) {
20559 let permalink_task = self.get_permalink_to_line(cx);
20560 let workspace = self.workspace();
20561
20562 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20563 Ok(permalink) => {
20564 cx.update(|_, cx| {
20565 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20566 })
20567 .ok();
20568 }
20569 Err(err) => {
20570 let message = format!("Failed to copy permalink: {err}");
20571
20572 anyhow::Result::<()>::Err(err).log_err();
20573
20574 if let Some(workspace) = workspace {
20575 workspace
20576 .update_in(cx, |workspace, _, cx| {
20577 struct CopyPermalinkToLine;
20578
20579 workspace.show_toast(
20580 Toast::new(
20581 NotificationId::unique::<CopyPermalinkToLine>(),
20582 message,
20583 ),
20584 cx,
20585 )
20586 })
20587 .ok();
20588 }
20589 }
20590 })
20591 .detach();
20592 }
20593
20594 pub fn copy_file_location(
20595 &mut self,
20596 _: &CopyFileLocation,
20597 _: &mut Window,
20598 cx: &mut Context<Self>,
20599 ) {
20600 let selection = self
20601 .selections
20602 .newest::<Point>(&self.display_snapshot(cx))
20603 .start
20604 .row
20605 + 1;
20606 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20607 let project = self.project()?.read(cx);
20608 let file = buffer.read(cx).file()?;
20609 let path = file.path().display(project.path_style(cx));
20610
20611 Some(format!("{path}:{selection}"))
20612 }) {
20613 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20614 }
20615 }
20616
20617 pub fn open_permalink_to_line(
20618 &mut self,
20619 _: &OpenPermalinkToLine,
20620 window: &mut Window,
20621 cx: &mut Context<Self>,
20622 ) {
20623 let permalink_task = self.get_permalink_to_line(cx);
20624 let workspace = self.workspace();
20625
20626 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20627 Ok(permalink) => {
20628 cx.update(|_, cx| {
20629 cx.open_url(permalink.as_ref());
20630 })
20631 .ok();
20632 }
20633 Err(err) => {
20634 let message = format!("Failed to open permalink: {err}");
20635
20636 anyhow::Result::<()>::Err(err).log_err();
20637
20638 if let Some(workspace) = workspace {
20639 workspace
20640 .update(cx, |workspace, cx| {
20641 struct OpenPermalinkToLine;
20642
20643 workspace.show_toast(
20644 Toast::new(
20645 NotificationId::unique::<OpenPermalinkToLine>(),
20646 message,
20647 ),
20648 cx,
20649 )
20650 })
20651 .ok();
20652 }
20653 }
20654 })
20655 .detach();
20656 }
20657
20658 pub fn insert_uuid_v4(
20659 &mut self,
20660 _: &InsertUuidV4,
20661 window: &mut Window,
20662 cx: &mut Context<Self>,
20663 ) {
20664 self.insert_uuid(UuidVersion::V4, window, cx);
20665 }
20666
20667 pub fn insert_uuid_v7(
20668 &mut self,
20669 _: &InsertUuidV7,
20670 window: &mut Window,
20671 cx: &mut Context<Self>,
20672 ) {
20673 self.insert_uuid(UuidVersion::V7, window, cx);
20674 }
20675
20676 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20677 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20678 self.transact(window, cx, |this, window, cx| {
20679 let edits = this
20680 .selections
20681 .all::<Point>(&this.display_snapshot(cx))
20682 .into_iter()
20683 .map(|selection| {
20684 let uuid = match version {
20685 UuidVersion::V4 => uuid::Uuid::new_v4(),
20686 UuidVersion::V7 => uuid::Uuid::now_v7(),
20687 };
20688
20689 (selection.range(), uuid.to_string())
20690 });
20691 this.edit(edits, cx);
20692 this.refresh_edit_prediction(true, false, window, cx);
20693 });
20694 }
20695
20696 pub fn open_selections_in_multibuffer(
20697 &mut self,
20698 _: &OpenSelectionsInMultibuffer,
20699 window: &mut Window,
20700 cx: &mut Context<Self>,
20701 ) {
20702 let multibuffer = self.buffer.read(cx);
20703
20704 let Some(buffer) = multibuffer.as_singleton() else {
20705 return;
20706 };
20707
20708 let Some(workspace) = self.workspace() else {
20709 return;
20710 };
20711
20712 let title = multibuffer.title(cx).to_string();
20713
20714 let locations = self
20715 .selections
20716 .all_anchors(&self.display_snapshot(cx))
20717 .iter()
20718 .map(|selection| {
20719 (
20720 buffer.clone(),
20721 (selection.start.text_anchor..selection.end.text_anchor)
20722 .to_point(buffer.read(cx)),
20723 )
20724 })
20725 .into_group_map();
20726
20727 cx.spawn_in(window, async move |_, cx| {
20728 workspace.update_in(cx, |workspace, window, cx| {
20729 Self::open_locations_in_multibuffer(
20730 workspace,
20731 locations,
20732 format!("Selections for '{title}'"),
20733 false,
20734 MultibufferSelectionMode::All,
20735 window,
20736 cx,
20737 );
20738 })
20739 })
20740 .detach();
20741 }
20742
20743 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20744 /// last highlight added will be used.
20745 ///
20746 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20747 pub fn highlight_rows<T: 'static>(
20748 &mut self,
20749 range: Range<Anchor>,
20750 color: Hsla,
20751 options: RowHighlightOptions,
20752 cx: &mut Context<Self>,
20753 ) {
20754 let snapshot = self.buffer().read(cx).snapshot(cx);
20755 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20756 let ix = row_highlights.binary_search_by(|highlight| {
20757 Ordering::Equal
20758 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20759 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20760 });
20761
20762 if let Err(mut ix) = ix {
20763 let index = post_inc(&mut self.highlight_order);
20764
20765 // If this range intersects with the preceding highlight, then merge it with
20766 // the preceding highlight. Otherwise insert a new highlight.
20767 let mut merged = false;
20768 if ix > 0 {
20769 let prev_highlight = &mut row_highlights[ix - 1];
20770 if prev_highlight
20771 .range
20772 .end
20773 .cmp(&range.start, &snapshot)
20774 .is_ge()
20775 {
20776 ix -= 1;
20777 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20778 prev_highlight.range.end = range.end;
20779 }
20780 merged = true;
20781 prev_highlight.index = index;
20782 prev_highlight.color = color;
20783 prev_highlight.options = options;
20784 }
20785 }
20786
20787 if !merged {
20788 row_highlights.insert(
20789 ix,
20790 RowHighlight {
20791 range,
20792 index,
20793 color,
20794 options,
20795 type_id: TypeId::of::<T>(),
20796 },
20797 );
20798 }
20799
20800 // If any of the following highlights intersect with this one, merge them.
20801 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20802 let highlight = &row_highlights[ix];
20803 if next_highlight
20804 .range
20805 .start
20806 .cmp(&highlight.range.end, &snapshot)
20807 .is_le()
20808 {
20809 if next_highlight
20810 .range
20811 .end
20812 .cmp(&highlight.range.end, &snapshot)
20813 .is_gt()
20814 {
20815 row_highlights[ix].range.end = next_highlight.range.end;
20816 }
20817 row_highlights.remove(ix + 1);
20818 } else {
20819 break;
20820 }
20821 }
20822 }
20823 }
20824
20825 /// Remove any highlighted row ranges of the given type that intersect the
20826 /// given ranges.
20827 pub fn remove_highlighted_rows<T: 'static>(
20828 &mut self,
20829 ranges_to_remove: Vec<Range<Anchor>>,
20830 cx: &mut Context<Self>,
20831 ) {
20832 let snapshot = self.buffer().read(cx).snapshot(cx);
20833 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20834 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20835 row_highlights.retain(|highlight| {
20836 while let Some(range_to_remove) = ranges_to_remove.peek() {
20837 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20838 Ordering::Less | Ordering::Equal => {
20839 ranges_to_remove.next();
20840 }
20841 Ordering::Greater => {
20842 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20843 Ordering::Less | Ordering::Equal => {
20844 return false;
20845 }
20846 Ordering::Greater => break,
20847 }
20848 }
20849 }
20850 }
20851
20852 true
20853 })
20854 }
20855
20856 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20857 pub fn clear_row_highlights<T: 'static>(&mut self) {
20858 self.highlighted_rows.remove(&TypeId::of::<T>());
20859 }
20860
20861 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20862 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20863 self.highlighted_rows
20864 .get(&TypeId::of::<T>())
20865 .map_or(&[] as &[_], |vec| vec.as_slice())
20866 .iter()
20867 .map(|highlight| (highlight.range.clone(), highlight.color))
20868 }
20869
20870 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20871 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20872 /// Allows to ignore certain kinds of highlights.
20873 pub fn highlighted_display_rows(
20874 &self,
20875 window: &mut Window,
20876 cx: &mut App,
20877 ) -> BTreeMap<DisplayRow, LineHighlight> {
20878 let snapshot = self.snapshot(window, cx);
20879 let mut used_highlight_orders = HashMap::default();
20880 self.highlighted_rows
20881 .iter()
20882 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20883 .fold(
20884 BTreeMap::<DisplayRow, LineHighlight>::new(),
20885 |mut unique_rows, highlight| {
20886 let start = highlight.range.start.to_display_point(&snapshot);
20887 let end = highlight.range.end.to_display_point(&snapshot);
20888 let start_row = start.row().0;
20889 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20890 {
20891 end.row().0.saturating_sub(1)
20892 } else {
20893 end.row().0
20894 };
20895 for row in start_row..=end_row {
20896 let used_index =
20897 used_highlight_orders.entry(row).or_insert(highlight.index);
20898 if highlight.index >= *used_index {
20899 *used_index = highlight.index;
20900 unique_rows.insert(
20901 DisplayRow(row),
20902 LineHighlight {
20903 include_gutter: highlight.options.include_gutter,
20904 border: None,
20905 background: highlight.color.into(),
20906 type_id: Some(highlight.type_id),
20907 },
20908 );
20909 }
20910 }
20911 unique_rows
20912 },
20913 )
20914 }
20915
20916 pub fn highlighted_display_row_for_autoscroll(
20917 &self,
20918 snapshot: &DisplaySnapshot,
20919 ) -> Option<DisplayRow> {
20920 self.highlighted_rows
20921 .values()
20922 .flat_map(|highlighted_rows| highlighted_rows.iter())
20923 .filter_map(|highlight| {
20924 if highlight.options.autoscroll {
20925 Some(highlight.range.start.to_display_point(snapshot).row())
20926 } else {
20927 None
20928 }
20929 })
20930 .min()
20931 }
20932
20933 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20934 self.highlight_background::<SearchWithinRange>(
20935 ranges,
20936 |colors| colors.colors().editor_document_highlight_read_background,
20937 cx,
20938 )
20939 }
20940
20941 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20942 self.breadcrumb_header = Some(new_header);
20943 }
20944
20945 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20946 self.clear_background_highlights::<SearchWithinRange>(cx);
20947 }
20948
20949 pub fn highlight_background<T: 'static>(
20950 &mut self,
20951 ranges: &[Range<Anchor>],
20952 color_fetcher: fn(&Theme) -> Hsla,
20953 cx: &mut Context<Self>,
20954 ) {
20955 self.background_highlights.insert(
20956 HighlightKey::Type(TypeId::of::<T>()),
20957 (color_fetcher, Arc::from(ranges)),
20958 );
20959 self.scrollbar_marker_state.dirty = true;
20960 cx.notify();
20961 }
20962
20963 pub fn highlight_background_key<T: 'static>(
20964 &mut self,
20965 key: usize,
20966 ranges: &[Range<Anchor>],
20967 color_fetcher: fn(&Theme) -> Hsla,
20968 cx: &mut Context<Self>,
20969 ) {
20970 self.background_highlights.insert(
20971 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20972 (color_fetcher, Arc::from(ranges)),
20973 );
20974 self.scrollbar_marker_state.dirty = true;
20975 cx.notify();
20976 }
20977
20978 pub fn clear_background_highlights<T: 'static>(
20979 &mut self,
20980 cx: &mut Context<Self>,
20981 ) -> Option<BackgroundHighlight> {
20982 let text_highlights = self
20983 .background_highlights
20984 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20985 if !text_highlights.1.is_empty() {
20986 self.scrollbar_marker_state.dirty = true;
20987 cx.notify();
20988 }
20989 Some(text_highlights)
20990 }
20991
20992 pub fn highlight_gutter<T: 'static>(
20993 &mut self,
20994 ranges: impl Into<Vec<Range<Anchor>>>,
20995 color_fetcher: fn(&App) -> Hsla,
20996 cx: &mut Context<Self>,
20997 ) {
20998 self.gutter_highlights
20999 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21000 cx.notify();
21001 }
21002
21003 pub fn clear_gutter_highlights<T: 'static>(
21004 &mut self,
21005 cx: &mut Context<Self>,
21006 ) -> Option<GutterHighlight> {
21007 cx.notify();
21008 self.gutter_highlights.remove(&TypeId::of::<T>())
21009 }
21010
21011 pub fn insert_gutter_highlight<T: 'static>(
21012 &mut self,
21013 range: Range<Anchor>,
21014 color_fetcher: fn(&App) -> Hsla,
21015 cx: &mut Context<Self>,
21016 ) {
21017 let snapshot = self.buffer().read(cx).snapshot(cx);
21018 let mut highlights = self
21019 .gutter_highlights
21020 .remove(&TypeId::of::<T>())
21021 .map(|(_, highlights)| highlights)
21022 .unwrap_or_default();
21023 let ix = highlights.binary_search_by(|highlight| {
21024 Ordering::Equal
21025 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21026 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21027 });
21028 if let Err(ix) = ix {
21029 highlights.insert(ix, range);
21030 }
21031 self.gutter_highlights
21032 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21033 }
21034
21035 pub fn remove_gutter_highlights<T: 'static>(
21036 &mut self,
21037 ranges_to_remove: Vec<Range<Anchor>>,
21038 cx: &mut Context<Self>,
21039 ) {
21040 let snapshot = self.buffer().read(cx).snapshot(cx);
21041 let Some((color_fetcher, mut gutter_highlights)) =
21042 self.gutter_highlights.remove(&TypeId::of::<T>())
21043 else {
21044 return;
21045 };
21046 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21047 gutter_highlights.retain(|highlight| {
21048 while let Some(range_to_remove) = ranges_to_remove.peek() {
21049 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21050 Ordering::Less | Ordering::Equal => {
21051 ranges_to_remove.next();
21052 }
21053 Ordering::Greater => {
21054 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21055 Ordering::Less | Ordering::Equal => {
21056 return false;
21057 }
21058 Ordering::Greater => break,
21059 }
21060 }
21061 }
21062 }
21063
21064 true
21065 });
21066 self.gutter_highlights
21067 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21068 }
21069
21070 #[cfg(feature = "test-support")]
21071 pub fn all_text_highlights(
21072 &self,
21073 window: &mut Window,
21074 cx: &mut Context<Self>,
21075 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21076 let snapshot = self.snapshot(window, cx);
21077 self.display_map.update(cx, |display_map, _| {
21078 display_map
21079 .all_text_highlights()
21080 .map(|highlight| {
21081 let (style, ranges) = highlight.as_ref();
21082 (
21083 *style,
21084 ranges
21085 .iter()
21086 .map(|range| range.clone().to_display_points(&snapshot))
21087 .collect(),
21088 )
21089 })
21090 .collect()
21091 })
21092 }
21093
21094 #[cfg(feature = "test-support")]
21095 pub fn all_text_background_highlights(
21096 &self,
21097 window: &mut Window,
21098 cx: &mut Context<Self>,
21099 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21100 let snapshot = self.snapshot(window, cx);
21101 let buffer = &snapshot.buffer_snapshot();
21102 let start = buffer.anchor_before(MultiBufferOffset(0));
21103 let end = buffer.anchor_after(buffer.len());
21104 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21105 }
21106
21107 #[cfg(any(test, feature = "test-support"))]
21108 pub fn sorted_background_highlights_in_range(
21109 &self,
21110 search_range: Range<Anchor>,
21111 display_snapshot: &DisplaySnapshot,
21112 theme: &Theme,
21113 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21114 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21115 res.sort_by(|a, b| {
21116 a.0.start
21117 .cmp(&b.0.start)
21118 .then_with(|| a.0.end.cmp(&b.0.end))
21119 .then_with(|| a.1.cmp(&b.1))
21120 });
21121 res
21122 }
21123
21124 #[cfg(feature = "test-support")]
21125 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21126 let snapshot = self.buffer().read(cx).snapshot(cx);
21127
21128 let highlights = self
21129 .background_highlights
21130 .get(&HighlightKey::Type(TypeId::of::<
21131 items::BufferSearchHighlights,
21132 >()));
21133
21134 if let Some((_color, ranges)) = highlights {
21135 ranges
21136 .iter()
21137 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21138 .collect_vec()
21139 } else {
21140 vec![]
21141 }
21142 }
21143
21144 fn document_highlights_for_position<'a>(
21145 &'a self,
21146 position: Anchor,
21147 buffer: &'a MultiBufferSnapshot,
21148 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21149 let read_highlights = self
21150 .background_highlights
21151 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21152 .map(|h| &h.1);
21153 let write_highlights = self
21154 .background_highlights
21155 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21156 .map(|h| &h.1);
21157 let left_position = position.bias_left(buffer);
21158 let right_position = position.bias_right(buffer);
21159 read_highlights
21160 .into_iter()
21161 .chain(write_highlights)
21162 .flat_map(move |ranges| {
21163 let start_ix = match ranges.binary_search_by(|probe| {
21164 let cmp = probe.end.cmp(&left_position, buffer);
21165 if cmp.is_ge() {
21166 Ordering::Greater
21167 } else {
21168 Ordering::Less
21169 }
21170 }) {
21171 Ok(i) | Err(i) => i,
21172 };
21173
21174 ranges[start_ix..]
21175 .iter()
21176 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21177 })
21178 }
21179
21180 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21181 self.background_highlights
21182 .get(&HighlightKey::Type(TypeId::of::<T>()))
21183 .is_some_and(|(_, highlights)| !highlights.is_empty())
21184 }
21185
21186 /// Returns all background highlights for a given range.
21187 ///
21188 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21189 pub fn background_highlights_in_range(
21190 &self,
21191 search_range: Range<Anchor>,
21192 display_snapshot: &DisplaySnapshot,
21193 theme: &Theme,
21194 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21195 let mut results = Vec::new();
21196 for (color_fetcher, ranges) in self.background_highlights.values() {
21197 let color = color_fetcher(theme);
21198 let start_ix = match ranges.binary_search_by(|probe| {
21199 let cmp = probe
21200 .end
21201 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21202 if cmp.is_gt() {
21203 Ordering::Greater
21204 } else {
21205 Ordering::Less
21206 }
21207 }) {
21208 Ok(i) | Err(i) => i,
21209 };
21210 for range in &ranges[start_ix..] {
21211 if range
21212 .start
21213 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21214 .is_ge()
21215 {
21216 break;
21217 }
21218
21219 let start = range.start.to_display_point(display_snapshot);
21220 let end = range.end.to_display_point(display_snapshot);
21221 results.push((start..end, color))
21222 }
21223 }
21224 results
21225 }
21226
21227 pub fn gutter_highlights_in_range(
21228 &self,
21229 search_range: Range<Anchor>,
21230 display_snapshot: &DisplaySnapshot,
21231 cx: &App,
21232 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21233 let mut results = Vec::new();
21234 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21235 let color = color_fetcher(cx);
21236 let start_ix = match ranges.binary_search_by(|probe| {
21237 let cmp = probe
21238 .end
21239 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21240 if cmp.is_gt() {
21241 Ordering::Greater
21242 } else {
21243 Ordering::Less
21244 }
21245 }) {
21246 Ok(i) | Err(i) => i,
21247 };
21248 for range in &ranges[start_ix..] {
21249 if range
21250 .start
21251 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21252 .is_ge()
21253 {
21254 break;
21255 }
21256
21257 let start = range.start.to_display_point(display_snapshot);
21258 let end = range.end.to_display_point(display_snapshot);
21259 results.push((start..end, color))
21260 }
21261 }
21262 results
21263 }
21264
21265 /// Get the text ranges corresponding to the redaction query
21266 pub fn redacted_ranges(
21267 &self,
21268 search_range: Range<Anchor>,
21269 display_snapshot: &DisplaySnapshot,
21270 cx: &App,
21271 ) -> Vec<Range<DisplayPoint>> {
21272 display_snapshot
21273 .buffer_snapshot()
21274 .redacted_ranges(search_range, |file| {
21275 if let Some(file) = file {
21276 file.is_private()
21277 && EditorSettings::get(
21278 Some(SettingsLocation {
21279 worktree_id: file.worktree_id(cx),
21280 path: file.path().as_ref(),
21281 }),
21282 cx,
21283 )
21284 .redact_private_values
21285 } else {
21286 false
21287 }
21288 })
21289 .map(|range| {
21290 range.start.to_display_point(display_snapshot)
21291 ..range.end.to_display_point(display_snapshot)
21292 })
21293 .collect()
21294 }
21295
21296 pub fn highlight_text_key<T: 'static>(
21297 &mut self,
21298 key: usize,
21299 ranges: Vec<Range<Anchor>>,
21300 style: HighlightStyle,
21301 merge: bool,
21302 cx: &mut Context<Self>,
21303 ) {
21304 self.display_map.update(cx, |map, cx| {
21305 map.highlight_text(
21306 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21307 ranges,
21308 style,
21309 merge,
21310 cx,
21311 );
21312 });
21313 cx.notify();
21314 }
21315
21316 pub fn highlight_text<T: 'static>(
21317 &mut self,
21318 ranges: Vec<Range<Anchor>>,
21319 style: HighlightStyle,
21320 cx: &mut Context<Self>,
21321 ) {
21322 self.display_map.update(cx, |map, cx| {
21323 map.highlight_text(
21324 HighlightKey::Type(TypeId::of::<T>()),
21325 ranges,
21326 style,
21327 false,
21328 cx,
21329 )
21330 });
21331 cx.notify();
21332 }
21333
21334 pub fn text_highlights<'a, T: 'static>(
21335 &'a self,
21336 cx: &'a App,
21337 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21338 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21339 }
21340
21341 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21342 let cleared = self
21343 .display_map
21344 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21345 if cleared {
21346 cx.notify();
21347 }
21348 }
21349
21350 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21351 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21352 && self.focus_handle.is_focused(window)
21353 }
21354
21355 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21356 self.show_cursor_when_unfocused = is_enabled;
21357 cx.notify();
21358 }
21359
21360 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21361 cx.notify();
21362 }
21363
21364 fn on_debug_session_event(
21365 &mut self,
21366 _session: Entity<Session>,
21367 event: &SessionEvent,
21368 cx: &mut Context<Self>,
21369 ) {
21370 if let SessionEvent::InvalidateInlineValue = event {
21371 self.refresh_inline_values(cx);
21372 }
21373 }
21374
21375 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21376 let Some(project) = self.project.clone() else {
21377 return;
21378 };
21379
21380 if !self.inline_value_cache.enabled {
21381 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21382 self.splice_inlays(&inlays, Vec::new(), cx);
21383 return;
21384 }
21385
21386 let current_execution_position = self
21387 .highlighted_rows
21388 .get(&TypeId::of::<ActiveDebugLine>())
21389 .and_then(|lines| lines.last().map(|line| line.range.end));
21390
21391 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21392 let inline_values = editor
21393 .update(cx, |editor, cx| {
21394 let Some(current_execution_position) = current_execution_position else {
21395 return Some(Task::ready(Ok(Vec::new())));
21396 };
21397
21398 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21399 let snapshot = buffer.snapshot(cx);
21400
21401 let excerpt = snapshot.excerpt_containing(
21402 current_execution_position..current_execution_position,
21403 )?;
21404
21405 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21406 })?;
21407
21408 let range =
21409 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21410
21411 project.inline_values(buffer, range, cx)
21412 })
21413 .ok()
21414 .flatten()?
21415 .await
21416 .context("refreshing debugger inlays")
21417 .log_err()?;
21418
21419 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21420
21421 for (buffer_id, inline_value) in inline_values
21422 .into_iter()
21423 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21424 {
21425 buffer_inline_values
21426 .entry(buffer_id)
21427 .or_default()
21428 .push(inline_value);
21429 }
21430
21431 editor
21432 .update(cx, |editor, cx| {
21433 let snapshot = editor.buffer.read(cx).snapshot(cx);
21434 let mut new_inlays = Vec::default();
21435
21436 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21437 let buffer_id = buffer_snapshot.remote_id();
21438 buffer_inline_values
21439 .get(&buffer_id)
21440 .into_iter()
21441 .flatten()
21442 .for_each(|hint| {
21443 let inlay = Inlay::debugger(
21444 post_inc(&mut editor.next_inlay_id),
21445 Anchor::in_buffer(excerpt_id, hint.position),
21446 hint.text(),
21447 );
21448 if !inlay.text().chars().contains(&'\n') {
21449 new_inlays.push(inlay);
21450 }
21451 });
21452 }
21453
21454 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21455 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21456
21457 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21458 })
21459 .ok()?;
21460 Some(())
21461 });
21462 }
21463
21464 fn on_buffer_event(
21465 &mut self,
21466 multibuffer: &Entity<MultiBuffer>,
21467 event: &multi_buffer::Event,
21468 window: &mut Window,
21469 cx: &mut Context<Self>,
21470 ) {
21471 match event {
21472 multi_buffer::Event::Edited { edited_buffer } => {
21473 self.scrollbar_marker_state.dirty = true;
21474 self.active_indent_guides_state.dirty = true;
21475 self.refresh_active_diagnostics(cx);
21476 self.refresh_code_actions(window, cx);
21477 self.refresh_single_line_folds(window, cx);
21478 self.refresh_matching_bracket_highlights(window, cx);
21479 if self.has_active_edit_prediction() {
21480 self.update_visible_edit_prediction(window, cx);
21481 }
21482
21483 if let Some(buffer) = edited_buffer {
21484 if buffer.read(cx).file().is_none() {
21485 cx.emit(EditorEvent::TitleChanged);
21486 }
21487
21488 if self.project.is_some() {
21489 let buffer_id = buffer.read(cx).remote_id();
21490 self.register_buffer(buffer_id, cx);
21491 self.update_lsp_data(Some(buffer_id), window, cx);
21492 self.refresh_inlay_hints(
21493 InlayHintRefreshReason::BufferEdited(buffer_id),
21494 cx,
21495 );
21496 }
21497 }
21498
21499 cx.emit(EditorEvent::BufferEdited);
21500 cx.emit(SearchEvent::MatchesInvalidated);
21501
21502 let Some(project) = &self.project else { return };
21503 let (telemetry, is_via_ssh) = {
21504 let project = project.read(cx);
21505 let telemetry = project.client().telemetry().clone();
21506 let is_via_ssh = project.is_via_remote_server();
21507 (telemetry, is_via_ssh)
21508 };
21509 telemetry.log_edit_event("editor", is_via_ssh);
21510 }
21511 multi_buffer::Event::ExcerptsAdded {
21512 buffer,
21513 predecessor,
21514 excerpts,
21515 } => {
21516 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21517 let buffer_id = buffer.read(cx).remote_id();
21518 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21519 && let Some(project) = &self.project
21520 {
21521 update_uncommitted_diff_for_buffer(
21522 cx.entity(),
21523 project,
21524 [buffer.clone()],
21525 self.buffer.clone(),
21526 cx,
21527 )
21528 .detach();
21529 }
21530 self.update_lsp_data(Some(buffer_id), window, cx);
21531 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21532 self.colorize_brackets(false, cx);
21533 cx.emit(EditorEvent::ExcerptsAdded {
21534 buffer: buffer.clone(),
21535 predecessor: *predecessor,
21536 excerpts: excerpts.clone(),
21537 });
21538 }
21539 multi_buffer::Event::ExcerptsRemoved {
21540 ids,
21541 removed_buffer_ids,
21542 } => {
21543 if let Some(inlay_hints) = &mut self.inlay_hints {
21544 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21545 }
21546 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21547 for buffer_id in removed_buffer_ids {
21548 self.registered_buffers.remove(buffer_id);
21549 }
21550 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21551 cx.emit(EditorEvent::ExcerptsRemoved {
21552 ids: ids.clone(),
21553 removed_buffer_ids: removed_buffer_ids.clone(),
21554 });
21555 }
21556 multi_buffer::Event::ExcerptsEdited {
21557 excerpt_ids,
21558 buffer_ids,
21559 } => {
21560 self.display_map.update(cx, |map, cx| {
21561 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21562 });
21563 cx.emit(EditorEvent::ExcerptsEdited {
21564 ids: excerpt_ids.clone(),
21565 });
21566 }
21567 multi_buffer::Event::ExcerptsExpanded { ids } => {
21568 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21569 self.refresh_document_highlights(cx);
21570 for id in ids {
21571 self.fetched_tree_sitter_chunks.remove(id);
21572 }
21573 self.colorize_brackets(false, cx);
21574 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21575 }
21576 multi_buffer::Event::Reparsed(buffer_id) => {
21577 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21578 self.refresh_selected_text_highlights(true, window, cx);
21579 self.colorize_brackets(true, cx);
21580 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21581
21582 cx.emit(EditorEvent::Reparsed(*buffer_id));
21583 }
21584 multi_buffer::Event::DiffHunksToggled => {
21585 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21586 }
21587 multi_buffer::Event::LanguageChanged(buffer_id) => {
21588 self.registered_buffers.remove(&buffer_id);
21589 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21590 cx.emit(EditorEvent::Reparsed(*buffer_id));
21591 cx.notify();
21592 }
21593 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21594 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21595 multi_buffer::Event::FileHandleChanged
21596 | multi_buffer::Event::Reloaded
21597 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21598 multi_buffer::Event::DiagnosticsUpdated => {
21599 self.update_diagnostics_state(window, cx);
21600 }
21601 _ => {}
21602 };
21603 }
21604
21605 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21606 if !self.diagnostics_enabled() {
21607 return;
21608 }
21609 self.refresh_active_diagnostics(cx);
21610 self.refresh_inline_diagnostics(true, window, cx);
21611 self.scrollbar_marker_state.dirty = true;
21612 cx.notify();
21613 }
21614
21615 pub fn start_temporary_diff_override(&mut self) {
21616 self.load_diff_task.take();
21617 self.temporary_diff_override = true;
21618 }
21619
21620 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21621 self.temporary_diff_override = false;
21622 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21623 self.buffer.update(cx, |buffer, cx| {
21624 buffer.set_all_diff_hunks_collapsed(cx);
21625 });
21626
21627 if let Some(project) = self.project.clone() {
21628 self.load_diff_task = Some(
21629 update_uncommitted_diff_for_buffer(
21630 cx.entity(),
21631 &project,
21632 self.buffer.read(cx).all_buffers(),
21633 self.buffer.clone(),
21634 cx,
21635 )
21636 .shared(),
21637 );
21638 }
21639 }
21640
21641 fn on_display_map_changed(
21642 &mut self,
21643 _: Entity<DisplayMap>,
21644 _: &mut Window,
21645 cx: &mut Context<Self>,
21646 ) {
21647 cx.notify();
21648 }
21649
21650 fn fetch_accent_overrides(&self, cx: &App) -> Vec<SharedString> {
21651 if !self.mode.is_full() {
21652 return Vec::new();
21653 }
21654
21655 let theme_settings = theme::ThemeSettings::get_global(cx);
21656
21657 theme_settings
21658 .theme_overrides
21659 .get(cx.theme().name.as_ref())
21660 .map(|theme_style| &theme_style.accents)
21661 .into_iter()
21662 .flatten()
21663 .chain(
21664 theme_settings
21665 .experimental_theme_overrides
21666 .as_ref()
21667 .map(|overrides| &overrides.accents)
21668 .into_iter()
21669 .flatten(),
21670 )
21671 .flat_map(|accent| accent.0.clone())
21672 .collect()
21673 }
21674
21675 fn fetch_applicable_language_settings(
21676 &self,
21677 cx: &App,
21678 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21679 if !self.mode.is_full() {
21680 return HashMap::default();
21681 }
21682
21683 self.buffer().read(cx).all_buffers().into_iter().fold(
21684 HashMap::default(),
21685 |mut acc, buffer| {
21686 let buffer = buffer.read(cx);
21687 let language = buffer.language().map(|language| language.name());
21688 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21689 let file = buffer.file();
21690 v.insert(language_settings(language, file, cx).into_owned());
21691 }
21692 acc
21693 },
21694 )
21695 }
21696
21697 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21698 let new_language_settings = self.fetch_applicable_language_settings(cx);
21699 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21700 self.applicable_language_settings = new_language_settings;
21701
21702 let new_accent_overrides = self.fetch_accent_overrides(cx);
21703 let accent_overrides_changed = new_accent_overrides != self.accent_overrides;
21704 self.accent_overrides = new_accent_overrides;
21705
21706 if self.diagnostics_enabled() {
21707 let new_severity = EditorSettings::get_global(cx)
21708 .diagnostics_max_severity
21709 .unwrap_or(DiagnosticSeverity::Hint);
21710 self.set_max_diagnostics_severity(new_severity, cx);
21711 }
21712 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21713 self.update_edit_prediction_settings(cx);
21714 self.refresh_edit_prediction(true, false, window, cx);
21715 self.refresh_inline_values(cx);
21716 self.refresh_inlay_hints(
21717 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21718 self.selections.newest_anchor().head(),
21719 &self.buffer.read(cx).snapshot(cx),
21720 cx,
21721 )),
21722 cx,
21723 );
21724
21725 let old_cursor_shape = self.cursor_shape;
21726 let old_show_breadcrumbs = self.show_breadcrumbs;
21727
21728 {
21729 let editor_settings = EditorSettings::get_global(cx);
21730 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21731 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21732 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21733 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21734 }
21735
21736 if old_cursor_shape != self.cursor_shape {
21737 cx.emit(EditorEvent::CursorShapeChanged);
21738 }
21739
21740 if old_show_breadcrumbs != self.show_breadcrumbs {
21741 cx.emit(EditorEvent::BreadcrumbsChanged);
21742 }
21743
21744 let project_settings = ProjectSettings::get_global(cx);
21745 self.buffer_serialization = self
21746 .should_serialize_buffer()
21747 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21748
21749 if self.mode.is_full() {
21750 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21751 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21752 if self.show_inline_diagnostics != show_inline_diagnostics {
21753 self.show_inline_diagnostics = show_inline_diagnostics;
21754 self.refresh_inline_diagnostics(false, window, cx);
21755 }
21756
21757 if self.git_blame_inline_enabled != inline_blame_enabled {
21758 self.toggle_git_blame_inline_internal(false, window, cx);
21759 }
21760
21761 let minimap_settings = EditorSettings::get_global(cx).minimap;
21762 if self.minimap_visibility != MinimapVisibility::Disabled {
21763 if self.minimap_visibility.settings_visibility()
21764 != minimap_settings.minimap_enabled()
21765 {
21766 self.set_minimap_visibility(
21767 MinimapVisibility::for_mode(self.mode(), cx),
21768 window,
21769 cx,
21770 );
21771 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21772 minimap_entity.update(cx, |minimap_editor, cx| {
21773 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21774 })
21775 }
21776 }
21777
21778 if language_settings_changed || accent_overrides_changed {
21779 self.colorize_brackets(true, cx);
21780 }
21781
21782 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21783 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21784 }) {
21785 if !inlay_splice.is_empty() {
21786 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21787 }
21788 self.refresh_colors_for_visible_range(None, window, cx);
21789 }
21790 }
21791
21792 cx.notify();
21793 }
21794
21795 pub fn set_searchable(&mut self, searchable: bool) {
21796 self.searchable = searchable;
21797 }
21798
21799 pub fn searchable(&self) -> bool {
21800 self.searchable
21801 }
21802
21803 pub fn open_excerpts_in_split(
21804 &mut self,
21805 _: &OpenExcerptsSplit,
21806 window: &mut Window,
21807 cx: &mut Context<Self>,
21808 ) {
21809 self.open_excerpts_common(None, true, window, cx)
21810 }
21811
21812 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21813 self.open_excerpts_common(None, false, window, cx)
21814 }
21815
21816 fn open_excerpts_common(
21817 &mut self,
21818 jump_data: Option<JumpData>,
21819 split: bool,
21820 window: &mut Window,
21821 cx: &mut Context<Self>,
21822 ) {
21823 let Some(workspace) = self.workspace() else {
21824 cx.propagate();
21825 return;
21826 };
21827
21828 if self.buffer.read(cx).is_singleton() {
21829 cx.propagate();
21830 return;
21831 }
21832
21833 let mut new_selections_by_buffer = HashMap::default();
21834 match &jump_data {
21835 Some(JumpData::MultiBufferPoint {
21836 excerpt_id,
21837 position,
21838 anchor,
21839 line_offset_from_top,
21840 }) => {
21841 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21842 if let Some(buffer) = multi_buffer_snapshot
21843 .buffer_id_for_excerpt(*excerpt_id)
21844 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21845 {
21846 let buffer_snapshot = buffer.read(cx).snapshot();
21847 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21848 language::ToPoint::to_point(anchor, &buffer_snapshot)
21849 } else {
21850 buffer_snapshot.clip_point(*position, Bias::Left)
21851 };
21852 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21853 new_selections_by_buffer.insert(
21854 buffer,
21855 (
21856 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21857 Some(*line_offset_from_top),
21858 ),
21859 );
21860 }
21861 }
21862 Some(JumpData::MultiBufferRow {
21863 row,
21864 line_offset_from_top,
21865 }) => {
21866 let point = MultiBufferPoint::new(row.0, 0);
21867 if let Some((buffer, buffer_point, _)) =
21868 self.buffer.read(cx).point_to_buffer_point(point, cx)
21869 {
21870 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21871 new_selections_by_buffer
21872 .entry(buffer)
21873 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21874 .0
21875 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21876 }
21877 }
21878 None => {
21879 let selections = self
21880 .selections
21881 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21882 let multi_buffer = self.buffer.read(cx);
21883 for selection in selections {
21884 for (snapshot, range, _, anchor) in multi_buffer
21885 .snapshot(cx)
21886 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21887 {
21888 if let Some(anchor) = anchor {
21889 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21890 else {
21891 continue;
21892 };
21893 let offset = text::ToOffset::to_offset(
21894 &anchor.text_anchor,
21895 &buffer_handle.read(cx).snapshot(),
21896 );
21897 let range = BufferOffset(offset)..BufferOffset(offset);
21898 new_selections_by_buffer
21899 .entry(buffer_handle)
21900 .or_insert((Vec::new(), None))
21901 .0
21902 .push(range)
21903 } else {
21904 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21905 else {
21906 continue;
21907 };
21908 new_selections_by_buffer
21909 .entry(buffer_handle)
21910 .or_insert((Vec::new(), None))
21911 .0
21912 .push(range)
21913 }
21914 }
21915 }
21916 }
21917 }
21918
21919 new_selections_by_buffer
21920 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21921
21922 if new_selections_by_buffer.is_empty() {
21923 return;
21924 }
21925
21926 // We defer the pane interaction because we ourselves are a workspace item
21927 // and activating a new item causes the pane to call a method on us reentrantly,
21928 // which panics if we're on the stack.
21929 window.defer(cx, move |window, cx| {
21930 workspace.update(cx, |workspace, cx| {
21931 let pane = if split {
21932 workspace.adjacent_pane(window, cx)
21933 } else {
21934 workspace.active_pane().clone()
21935 };
21936
21937 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21938 let editor = buffer
21939 .read(cx)
21940 .file()
21941 .is_none()
21942 .then(|| {
21943 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21944 // so `workspace.open_project_item` will never find them, always opening a new editor.
21945 // Instead, we try to activate the existing editor in the pane first.
21946 let (editor, pane_item_index) =
21947 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21948 let editor = item.downcast::<Editor>()?;
21949 let singleton_buffer =
21950 editor.read(cx).buffer().read(cx).as_singleton()?;
21951 if singleton_buffer == buffer {
21952 Some((editor, i))
21953 } else {
21954 None
21955 }
21956 })?;
21957 pane.update(cx, |pane, cx| {
21958 pane.activate_item(pane_item_index, true, true, window, cx)
21959 });
21960 Some(editor)
21961 })
21962 .flatten()
21963 .unwrap_or_else(|| {
21964 workspace.open_project_item::<Self>(
21965 pane.clone(),
21966 buffer,
21967 true,
21968 true,
21969 window,
21970 cx,
21971 )
21972 });
21973
21974 editor.update(cx, |editor, cx| {
21975 let autoscroll = match scroll_offset {
21976 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21977 None => Autoscroll::newest(),
21978 };
21979 let nav_history = editor.nav_history.take();
21980 editor.change_selections(
21981 SelectionEffects::scroll(autoscroll),
21982 window,
21983 cx,
21984 |s| {
21985 s.select_ranges(ranges.into_iter().map(|range| {
21986 // we checked that the editor is a singleton editor so the offsets are valid
21987 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
21988 }));
21989 },
21990 );
21991 editor.nav_history = nav_history;
21992 });
21993 }
21994 })
21995 });
21996 }
21997
21998 // For now, don't allow opening excerpts in buffers that aren't backed by
21999 // regular project files.
22000 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22001 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
22002 }
22003
22004 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22005 let snapshot = self.buffer.read(cx).read(cx);
22006 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22007 Some(
22008 ranges
22009 .iter()
22010 .map(move |range| {
22011 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22012 })
22013 .collect(),
22014 )
22015 }
22016
22017 fn selection_replacement_ranges(
22018 &self,
22019 range: Range<MultiBufferOffsetUtf16>,
22020 cx: &mut App,
22021 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22022 let selections = self
22023 .selections
22024 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22025 let newest_selection = selections
22026 .iter()
22027 .max_by_key(|selection| selection.id)
22028 .unwrap();
22029 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22030 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22031 let snapshot = self.buffer.read(cx).read(cx);
22032 selections
22033 .into_iter()
22034 .map(|mut selection| {
22035 selection.start.0.0 =
22036 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22037 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22038 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22039 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22040 })
22041 .collect()
22042 }
22043
22044 fn report_editor_event(
22045 &self,
22046 reported_event: ReportEditorEvent,
22047 file_extension: Option<String>,
22048 cx: &App,
22049 ) {
22050 if cfg!(any(test, feature = "test-support")) {
22051 return;
22052 }
22053
22054 let Some(project) = &self.project else { return };
22055
22056 // If None, we are in a file without an extension
22057 let file = self
22058 .buffer
22059 .read(cx)
22060 .as_singleton()
22061 .and_then(|b| b.read(cx).file());
22062 let file_extension = file_extension.or(file
22063 .as_ref()
22064 .and_then(|file| Path::new(file.file_name(cx)).extension())
22065 .and_then(|e| e.to_str())
22066 .map(|a| a.to_string()));
22067
22068 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22069 .map(|vim_mode| vim_mode.0)
22070 .unwrap_or(false);
22071
22072 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22073 let copilot_enabled = edit_predictions_provider
22074 == language::language_settings::EditPredictionProvider::Copilot;
22075 let copilot_enabled_for_language = self
22076 .buffer
22077 .read(cx)
22078 .language_settings(cx)
22079 .show_edit_predictions;
22080
22081 let project = project.read(cx);
22082 let event_type = reported_event.event_type();
22083
22084 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22085 telemetry::event!(
22086 event_type,
22087 type = if auto_saved {"autosave"} else {"manual"},
22088 file_extension,
22089 vim_mode,
22090 copilot_enabled,
22091 copilot_enabled_for_language,
22092 edit_predictions_provider,
22093 is_via_ssh = project.is_via_remote_server(),
22094 );
22095 } else {
22096 telemetry::event!(
22097 event_type,
22098 file_extension,
22099 vim_mode,
22100 copilot_enabled,
22101 copilot_enabled_for_language,
22102 edit_predictions_provider,
22103 is_via_ssh = project.is_via_remote_server(),
22104 );
22105 };
22106 }
22107
22108 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22109 /// with each line being an array of {text, highlight} objects.
22110 fn copy_highlight_json(
22111 &mut self,
22112 _: &CopyHighlightJson,
22113 window: &mut Window,
22114 cx: &mut Context<Self>,
22115 ) {
22116 #[derive(Serialize)]
22117 struct Chunk<'a> {
22118 text: String,
22119 highlight: Option<&'a str>,
22120 }
22121
22122 let snapshot = self.buffer.read(cx).snapshot(cx);
22123 let range = self
22124 .selected_text_range(false, window, cx)
22125 .and_then(|selection| {
22126 if selection.range.is_empty() {
22127 None
22128 } else {
22129 Some(
22130 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22131 selection.range.start,
22132 )))
22133 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22134 selection.range.end,
22135 ))),
22136 )
22137 }
22138 })
22139 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22140
22141 let chunks = snapshot.chunks(range, true);
22142 let mut lines = Vec::new();
22143 let mut line: VecDeque<Chunk> = VecDeque::new();
22144
22145 let Some(style) = self.style.as_ref() else {
22146 return;
22147 };
22148
22149 for chunk in chunks {
22150 let highlight = chunk
22151 .syntax_highlight_id
22152 .and_then(|id| id.name(&style.syntax));
22153 let mut chunk_lines = chunk.text.split('\n').peekable();
22154 while let Some(text) = chunk_lines.next() {
22155 let mut merged_with_last_token = false;
22156 if let Some(last_token) = line.back_mut()
22157 && last_token.highlight == highlight
22158 {
22159 last_token.text.push_str(text);
22160 merged_with_last_token = true;
22161 }
22162
22163 if !merged_with_last_token {
22164 line.push_back(Chunk {
22165 text: text.into(),
22166 highlight,
22167 });
22168 }
22169
22170 if chunk_lines.peek().is_some() {
22171 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22172 line.pop_front();
22173 }
22174 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22175 line.pop_back();
22176 }
22177
22178 lines.push(mem::take(&mut line));
22179 }
22180 }
22181 }
22182
22183 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22184 return;
22185 };
22186 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22187 }
22188
22189 pub fn open_context_menu(
22190 &mut self,
22191 _: &OpenContextMenu,
22192 window: &mut Window,
22193 cx: &mut Context<Self>,
22194 ) {
22195 self.request_autoscroll(Autoscroll::newest(), cx);
22196 let position = self
22197 .selections
22198 .newest_display(&self.display_snapshot(cx))
22199 .start;
22200 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22201 }
22202
22203 pub fn replay_insert_event(
22204 &mut self,
22205 text: &str,
22206 relative_utf16_range: Option<Range<isize>>,
22207 window: &mut Window,
22208 cx: &mut Context<Self>,
22209 ) {
22210 if !self.input_enabled {
22211 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22212 return;
22213 }
22214 if let Some(relative_utf16_range) = relative_utf16_range {
22215 let selections = self
22216 .selections
22217 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22218 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22219 let new_ranges = selections.into_iter().map(|range| {
22220 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22221 range
22222 .head()
22223 .0
22224 .0
22225 .saturating_add_signed(relative_utf16_range.start),
22226 ));
22227 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22228 range
22229 .head()
22230 .0
22231 .0
22232 .saturating_add_signed(relative_utf16_range.end),
22233 ));
22234 start..end
22235 });
22236 s.select_ranges(new_ranges);
22237 });
22238 }
22239
22240 self.handle_input(text, window, cx);
22241 }
22242
22243 pub fn is_focused(&self, window: &Window) -> bool {
22244 self.focus_handle.is_focused(window)
22245 }
22246
22247 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22248 cx.emit(EditorEvent::Focused);
22249
22250 if let Some(descendant) = self
22251 .last_focused_descendant
22252 .take()
22253 .and_then(|descendant| descendant.upgrade())
22254 {
22255 window.focus(&descendant);
22256 } else {
22257 if let Some(blame) = self.blame.as_ref() {
22258 blame.update(cx, GitBlame::focus)
22259 }
22260
22261 self.blink_manager.update(cx, BlinkManager::enable);
22262 self.show_cursor_names(window, cx);
22263 self.buffer.update(cx, |buffer, cx| {
22264 buffer.finalize_last_transaction(cx);
22265 if self.leader_id.is_none() {
22266 buffer.set_active_selections(
22267 &self.selections.disjoint_anchors_arc(),
22268 self.selections.line_mode(),
22269 self.cursor_shape,
22270 cx,
22271 );
22272 }
22273 });
22274
22275 if let Some(position_map) = self.last_position_map.clone() {
22276 EditorElement::mouse_moved(
22277 self,
22278 &MouseMoveEvent {
22279 position: window.mouse_position(),
22280 pressed_button: None,
22281 modifiers: window.modifiers(),
22282 },
22283 &position_map,
22284 window,
22285 cx,
22286 );
22287 }
22288 }
22289 }
22290
22291 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22292 cx.emit(EditorEvent::FocusedIn)
22293 }
22294
22295 fn handle_focus_out(
22296 &mut self,
22297 event: FocusOutEvent,
22298 _window: &mut Window,
22299 cx: &mut Context<Self>,
22300 ) {
22301 if event.blurred != self.focus_handle {
22302 self.last_focused_descendant = Some(event.blurred);
22303 }
22304 self.selection_drag_state = SelectionDragState::None;
22305 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22306 }
22307
22308 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22309 self.blink_manager.update(cx, BlinkManager::disable);
22310 self.buffer
22311 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22312
22313 if let Some(blame) = self.blame.as_ref() {
22314 blame.update(cx, GitBlame::blur)
22315 }
22316 if !self.hover_state.focused(window, cx) {
22317 hide_hover(self, cx);
22318 }
22319 if !self
22320 .context_menu
22321 .borrow()
22322 .as_ref()
22323 .is_some_and(|context_menu| context_menu.focused(window, cx))
22324 {
22325 self.hide_context_menu(window, cx);
22326 }
22327 self.take_active_edit_prediction(cx);
22328 cx.emit(EditorEvent::Blurred);
22329 cx.notify();
22330 }
22331
22332 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22333 let mut pending: String = window
22334 .pending_input_keystrokes()
22335 .into_iter()
22336 .flatten()
22337 .filter_map(|keystroke| keystroke.key_char.clone())
22338 .collect();
22339
22340 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22341 pending = "".to_string();
22342 }
22343
22344 let existing_pending = self
22345 .text_highlights::<PendingInput>(cx)
22346 .map(|(_, ranges)| ranges.to_vec());
22347 if existing_pending.is_none() && pending.is_empty() {
22348 return;
22349 }
22350 let transaction =
22351 self.transact(window, cx, |this, window, cx| {
22352 let selections = this
22353 .selections
22354 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22355 let edits = selections
22356 .iter()
22357 .map(|selection| (selection.end..selection.end, pending.clone()));
22358 this.edit(edits, cx);
22359 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22360 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22361 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22362 }));
22363 });
22364 if let Some(existing_ranges) = existing_pending {
22365 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22366 this.edit(edits, cx);
22367 }
22368 });
22369
22370 let snapshot = self.snapshot(window, cx);
22371 let ranges = self
22372 .selections
22373 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22374 .into_iter()
22375 .map(|selection| {
22376 snapshot.buffer_snapshot().anchor_after(selection.end)
22377 ..snapshot
22378 .buffer_snapshot()
22379 .anchor_before(selection.end + pending.len())
22380 })
22381 .collect();
22382
22383 if pending.is_empty() {
22384 self.clear_highlights::<PendingInput>(cx);
22385 } else {
22386 self.highlight_text::<PendingInput>(
22387 ranges,
22388 HighlightStyle {
22389 underline: Some(UnderlineStyle {
22390 thickness: px(1.),
22391 color: None,
22392 wavy: false,
22393 }),
22394 ..Default::default()
22395 },
22396 cx,
22397 );
22398 }
22399
22400 self.ime_transaction = self.ime_transaction.or(transaction);
22401 if let Some(transaction) = self.ime_transaction {
22402 self.buffer.update(cx, |buffer, cx| {
22403 buffer.group_until_transaction(transaction, cx);
22404 });
22405 }
22406
22407 if self.text_highlights::<PendingInput>(cx).is_none() {
22408 self.ime_transaction.take();
22409 }
22410 }
22411
22412 pub fn register_action_renderer(
22413 &mut self,
22414 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22415 ) -> Subscription {
22416 let id = self.next_editor_action_id.post_inc();
22417 self.editor_actions
22418 .borrow_mut()
22419 .insert(id, Box::new(listener));
22420
22421 let editor_actions = self.editor_actions.clone();
22422 Subscription::new(move || {
22423 editor_actions.borrow_mut().remove(&id);
22424 })
22425 }
22426
22427 pub fn register_action<A: Action>(
22428 &mut self,
22429 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22430 ) -> Subscription {
22431 let id = self.next_editor_action_id.post_inc();
22432 let listener = Arc::new(listener);
22433 self.editor_actions.borrow_mut().insert(
22434 id,
22435 Box::new(move |_, window, _| {
22436 let listener = listener.clone();
22437 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22438 let action = action.downcast_ref().unwrap();
22439 if phase == DispatchPhase::Bubble {
22440 listener(action, window, cx)
22441 }
22442 })
22443 }),
22444 );
22445
22446 let editor_actions = self.editor_actions.clone();
22447 Subscription::new(move || {
22448 editor_actions.borrow_mut().remove(&id);
22449 })
22450 }
22451
22452 pub fn file_header_size(&self) -> u32 {
22453 FILE_HEADER_HEIGHT
22454 }
22455
22456 pub fn restore(
22457 &mut self,
22458 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22459 window: &mut Window,
22460 cx: &mut Context<Self>,
22461 ) {
22462 let workspace = self.workspace();
22463 let project = self.project();
22464 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22465 let mut tasks = Vec::new();
22466 for (buffer_id, changes) in revert_changes {
22467 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22468 buffer.update(cx, |buffer, cx| {
22469 buffer.edit(
22470 changes
22471 .into_iter()
22472 .map(|(range, text)| (range, text.to_string())),
22473 None,
22474 cx,
22475 );
22476 });
22477
22478 if let Some(project) =
22479 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22480 {
22481 project.update(cx, |project, cx| {
22482 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22483 })
22484 }
22485 }
22486 }
22487 tasks
22488 });
22489 cx.spawn_in(window, async move |_, cx| {
22490 for (buffer, task) in save_tasks {
22491 let result = task.await;
22492 if result.is_err() {
22493 let Some(path) = buffer
22494 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22495 .ok()
22496 else {
22497 continue;
22498 };
22499 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22500 let Some(task) = cx
22501 .update_window_entity(workspace, |workspace, window, cx| {
22502 workspace
22503 .open_path_preview(path, None, false, false, false, window, cx)
22504 })
22505 .ok()
22506 else {
22507 continue;
22508 };
22509 task.await.log_err();
22510 }
22511 }
22512 }
22513 })
22514 .detach();
22515 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22516 selections.refresh()
22517 });
22518 }
22519
22520 pub fn to_pixel_point(
22521 &self,
22522 source: multi_buffer::Anchor,
22523 editor_snapshot: &EditorSnapshot,
22524 window: &mut Window,
22525 ) -> Option<gpui::Point<Pixels>> {
22526 let source_point = source.to_display_point(editor_snapshot);
22527 self.display_to_pixel_point(source_point, editor_snapshot, window)
22528 }
22529
22530 pub fn display_to_pixel_point(
22531 &self,
22532 source: DisplayPoint,
22533 editor_snapshot: &EditorSnapshot,
22534 window: &mut Window,
22535 ) -> Option<gpui::Point<Pixels>> {
22536 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22537 let text_layout_details = self.text_layout_details(window);
22538 let scroll_top = text_layout_details
22539 .scroll_anchor
22540 .scroll_position(editor_snapshot)
22541 .y;
22542
22543 if source.row().as_f64() < scroll_top.floor() {
22544 return None;
22545 }
22546 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22547 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22548 Some(gpui::Point::new(source_x, source_y))
22549 }
22550
22551 pub fn has_visible_completions_menu(&self) -> bool {
22552 !self.edit_prediction_preview_is_active()
22553 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22554 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22555 })
22556 }
22557
22558 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22559 if self.mode.is_minimap() {
22560 return;
22561 }
22562 self.addons
22563 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22564 }
22565
22566 pub fn unregister_addon<T: Addon>(&mut self) {
22567 self.addons.remove(&std::any::TypeId::of::<T>());
22568 }
22569
22570 pub fn addon<T: Addon>(&self) -> Option<&T> {
22571 let type_id = std::any::TypeId::of::<T>();
22572 self.addons
22573 .get(&type_id)
22574 .and_then(|item| item.to_any().downcast_ref::<T>())
22575 }
22576
22577 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22578 let type_id = std::any::TypeId::of::<T>();
22579 self.addons
22580 .get_mut(&type_id)
22581 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22582 }
22583
22584 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22585 let text_layout_details = self.text_layout_details(window);
22586 let style = &text_layout_details.editor_style;
22587 let font_id = window.text_system().resolve_font(&style.text.font());
22588 let font_size = style.text.font_size.to_pixels(window.rem_size());
22589 let line_height = style.text.line_height_in_pixels(window.rem_size());
22590 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22591 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22592
22593 CharacterDimensions {
22594 em_width,
22595 em_advance,
22596 line_height,
22597 }
22598 }
22599
22600 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22601 self.load_diff_task.clone()
22602 }
22603
22604 fn read_metadata_from_db(
22605 &mut self,
22606 item_id: u64,
22607 workspace_id: WorkspaceId,
22608 window: &mut Window,
22609 cx: &mut Context<Editor>,
22610 ) {
22611 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22612 && !self.mode.is_minimap()
22613 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22614 {
22615 let buffer_snapshot = OnceCell::new();
22616
22617 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22618 && !folds.is_empty()
22619 {
22620 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22621 self.fold_ranges(
22622 folds
22623 .into_iter()
22624 .map(|(start, end)| {
22625 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22626 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22627 })
22628 .collect(),
22629 false,
22630 window,
22631 cx,
22632 );
22633 }
22634
22635 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22636 && !selections.is_empty()
22637 {
22638 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22639 // skip adding the initial selection to selection history
22640 self.selection_history.mode = SelectionHistoryMode::Skipping;
22641 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22642 s.select_ranges(selections.into_iter().map(|(start, end)| {
22643 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22644 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22645 }));
22646 });
22647 self.selection_history.mode = SelectionHistoryMode::Normal;
22648 };
22649 }
22650
22651 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22652 }
22653
22654 fn update_lsp_data(
22655 &mut self,
22656 for_buffer: Option<BufferId>,
22657 window: &mut Window,
22658 cx: &mut Context<'_, Self>,
22659 ) {
22660 self.pull_diagnostics(for_buffer, window, cx);
22661 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22662 }
22663
22664 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22665 if self.ignore_lsp_data() {
22666 return;
22667 }
22668 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
22669 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22670 }
22671 }
22672
22673 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22674 if self.ignore_lsp_data() {
22675 return;
22676 }
22677
22678 if !self.registered_buffers.contains_key(&buffer_id)
22679 && let Some(project) = self.project.as_ref()
22680 {
22681 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22682 project.update(cx, |project, cx| {
22683 self.registered_buffers.insert(
22684 buffer_id,
22685 project.register_buffer_with_language_servers(&buffer, cx),
22686 );
22687 });
22688 } else {
22689 self.registered_buffers.remove(&buffer_id);
22690 }
22691 }
22692 }
22693
22694 fn ignore_lsp_data(&self) -> bool {
22695 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22696 // skip any LSP updates for it.
22697 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22698 }
22699}
22700
22701fn edit_for_markdown_paste<'a>(
22702 buffer: &MultiBufferSnapshot,
22703 range: Range<MultiBufferOffset>,
22704 to_insert: &'a str,
22705 url: Option<url::Url>,
22706) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22707 if url.is_none() {
22708 return (range, Cow::Borrowed(to_insert));
22709 };
22710
22711 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22712
22713 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22714 Cow::Borrowed(to_insert)
22715 } else {
22716 Cow::Owned(format!("[{old_text}]({to_insert})"))
22717 };
22718 (range, new_text)
22719}
22720
22721fn process_completion_for_edit(
22722 completion: &Completion,
22723 intent: CompletionIntent,
22724 buffer: &Entity<Buffer>,
22725 cursor_position: &text::Anchor,
22726 cx: &mut Context<Editor>,
22727) -> CompletionEdit {
22728 let buffer = buffer.read(cx);
22729 let buffer_snapshot = buffer.snapshot();
22730 let (snippet, new_text) = if completion.is_snippet() {
22731 let mut snippet_source = completion.new_text.clone();
22732 // Workaround for typescript language server issues so that methods don't expand within
22733 // strings and functions with type expressions. The previous point is used because the query
22734 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22735 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22736 let previous_point = if previous_point.column > 0 {
22737 cursor_position.to_previous_offset(&buffer_snapshot)
22738 } else {
22739 cursor_position.to_offset(&buffer_snapshot)
22740 };
22741 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22742 && scope.prefers_label_for_snippet_in_completion()
22743 && let Some(label) = completion.label()
22744 && matches!(
22745 completion.kind(),
22746 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22747 )
22748 {
22749 snippet_source = label;
22750 }
22751 match Snippet::parse(&snippet_source).log_err() {
22752 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22753 None => (None, completion.new_text.clone()),
22754 }
22755 } else {
22756 (None, completion.new_text.clone())
22757 };
22758
22759 let mut range_to_replace = {
22760 let replace_range = &completion.replace_range;
22761 if let CompletionSource::Lsp {
22762 insert_range: Some(insert_range),
22763 ..
22764 } = &completion.source
22765 {
22766 debug_assert_eq!(
22767 insert_range.start, replace_range.start,
22768 "insert_range and replace_range should start at the same position"
22769 );
22770 debug_assert!(
22771 insert_range
22772 .start
22773 .cmp(cursor_position, &buffer_snapshot)
22774 .is_le(),
22775 "insert_range should start before or at cursor position"
22776 );
22777 debug_assert!(
22778 replace_range
22779 .start
22780 .cmp(cursor_position, &buffer_snapshot)
22781 .is_le(),
22782 "replace_range should start before or at cursor position"
22783 );
22784
22785 let should_replace = match intent {
22786 CompletionIntent::CompleteWithInsert => false,
22787 CompletionIntent::CompleteWithReplace => true,
22788 CompletionIntent::Complete | CompletionIntent::Compose => {
22789 let insert_mode =
22790 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22791 .completions
22792 .lsp_insert_mode;
22793 match insert_mode {
22794 LspInsertMode::Insert => false,
22795 LspInsertMode::Replace => true,
22796 LspInsertMode::ReplaceSubsequence => {
22797 let mut text_to_replace = buffer.chars_for_range(
22798 buffer.anchor_before(replace_range.start)
22799 ..buffer.anchor_after(replace_range.end),
22800 );
22801 let mut current_needle = text_to_replace.next();
22802 for haystack_ch in completion.label.text.chars() {
22803 if let Some(needle_ch) = current_needle
22804 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22805 {
22806 current_needle = text_to_replace.next();
22807 }
22808 }
22809 current_needle.is_none()
22810 }
22811 LspInsertMode::ReplaceSuffix => {
22812 if replace_range
22813 .end
22814 .cmp(cursor_position, &buffer_snapshot)
22815 .is_gt()
22816 {
22817 let range_after_cursor = *cursor_position..replace_range.end;
22818 let text_after_cursor = buffer
22819 .text_for_range(
22820 buffer.anchor_before(range_after_cursor.start)
22821 ..buffer.anchor_after(range_after_cursor.end),
22822 )
22823 .collect::<String>()
22824 .to_ascii_lowercase();
22825 completion
22826 .label
22827 .text
22828 .to_ascii_lowercase()
22829 .ends_with(&text_after_cursor)
22830 } else {
22831 true
22832 }
22833 }
22834 }
22835 }
22836 };
22837
22838 if should_replace {
22839 replace_range.clone()
22840 } else {
22841 insert_range.clone()
22842 }
22843 } else {
22844 replace_range.clone()
22845 }
22846 };
22847
22848 if range_to_replace
22849 .end
22850 .cmp(cursor_position, &buffer_snapshot)
22851 .is_lt()
22852 {
22853 range_to_replace.end = *cursor_position;
22854 }
22855
22856 let replace_range = range_to_replace.to_offset(buffer);
22857 CompletionEdit {
22858 new_text,
22859 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22860 snippet,
22861 }
22862}
22863
22864struct CompletionEdit {
22865 new_text: String,
22866 replace_range: Range<BufferOffset>,
22867 snippet: Option<Snippet>,
22868}
22869
22870fn insert_extra_newline_brackets(
22871 buffer: &MultiBufferSnapshot,
22872 range: Range<MultiBufferOffset>,
22873 language: &language::LanguageScope,
22874) -> bool {
22875 let leading_whitespace_len = buffer
22876 .reversed_chars_at(range.start)
22877 .take_while(|c| c.is_whitespace() && *c != '\n')
22878 .map(|c| c.len_utf8())
22879 .sum::<usize>();
22880 let trailing_whitespace_len = buffer
22881 .chars_at(range.end)
22882 .take_while(|c| c.is_whitespace() && *c != '\n')
22883 .map(|c| c.len_utf8())
22884 .sum::<usize>();
22885 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22886
22887 language.brackets().any(|(pair, enabled)| {
22888 let pair_start = pair.start.trim_end();
22889 let pair_end = pair.end.trim_start();
22890
22891 enabled
22892 && pair.newline
22893 && buffer.contains_str_at(range.end, pair_end)
22894 && buffer.contains_str_at(
22895 range.start.saturating_sub_usize(pair_start.len()),
22896 pair_start,
22897 )
22898 })
22899}
22900
22901fn insert_extra_newline_tree_sitter(
22902 buffer: &MultiBufferSnapshot,
22903 range: Range<MultiBufferOffset>,
22904) -> bool {
22905 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22906 [(buffer, range, _)] => (*buffer, range.clone()),
22907 _ => return false,
22908 };
22909 let pair = {
22910 let mut result: Option<BracketMatch<usize>> = None;
22911
22912 for pair in buffer
22913 .all_bracket_ranges(range.start.0..range.end.0)
22914 .filter(move |pair| {
22915 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
22916 })
22917 {
22918 let len = pair.close_range.end - pair.open_range.start;
22919
22920 if let Some(existing) = &result {
22921 let existing_len = existing.close_range.end - existing.open_range.start;
22922 if len > existing_len {
22923 continue;
22924 }
22925 }
22926
22927 result = Some(pair);
22928 }
22929
22930 result
22931 };
22932 let Some(pair) = pair else {
22933 return false;
22934 };
22935 pair.newline_only
22936 && buffer
22937 .chars_for_range(pair.open_range.end..range.start.0)
22938 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
22939 .all(|c| c.is_whitespace() && c != '\n')
22940}
22941
22942fn update_uncommitted_diff_for_buffer(
22943 editor: Entity<Editor>,
22944 project: &Entity<Project>,
22945 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22946 buffer: Entity<MultiBuffer>,
22947 cx: &mut App,
22948) -> Task<()> {
22949 let mut tasks = Vec::new();
22950 project.update(cx, |project, cx| {
22951 for buffer in buffers {
22952 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22953 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22954 }
22955 }
22956 });
22957 cx.spawn(async move |cx| {
22958 let diffs = future::join_all(tasks).await;
22959 if editor
22960 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22961 .unwrap_or(false)
22962 {
22963 return;
22964 }
22965
22966 buffer
22967 .update(cx, |buffer, cx| {
22968 for diff in diffs.into_iter().flatten() {
22969 buffer.add_diff(diff, cx);
22970 }
22971 })
22972 .ok();
22973 })
22974}
22975
22976fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22977 let tab_size = tab_size.get() as usize;
22978 let mut width = offset;
22979
22980 for ch in text.chars() {
22981 width += if ch == '\t' {
22982 tab_size - (width % tab_size)
22983 } else {
22984 1
22985 };
22986 }
22987
22988 width - offset
22989}
22990
22991#[cfg(test)]
22992mod tests {
22993 use super::*;
22994
22995 #[test]
22996 fn test_string_size_with_expanded_tabs() {
22997 let nz = |val| NonZeroU32::new(val).unwrap();
22998 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22999 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23000 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23001 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23002 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23003 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23004 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23005 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23006 }
23007}
23008
23009/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23010struct WordBreakingTokenizer<'a> {
23011 input: &'a str,
23012}
23013
23014impl<'a> WordBreakingTokenizer<'a> {
23015 fn new(input: &'a str) -> Self {
23016 Self { input }
23017 }
23018}
23019
23020fn is_char_ideographic(ch: char) -> bool {
23021 use unicode_script::Script::*;
23022 use unicode_script::UnicodeScript;
23023 matches!(ch.script(), Han | Tangut | Yi)
23024}
23025
23026fn is_grapheme_ideographic(text: &str) -> bool {
23027 text.chars().any(is_char_ideographic)
23028}
23029
23030fn is_grapheme_whitespace(text: &str) -> bool {
23031 text.chars().any(|x| x.is_whitespace())
23032}
23033
23034fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23035 text.chars()
23036 .next()
23037 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23038}
23039
23040#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23041enum WordBreakToken<'a> {
23042 Word { token: &'a str, grapheme_len: usize },
23043 InlineWhitespace { token: &'a str, grapheme_len: usize },
23044 Newline,
23045}
23046
23047impl<'a> Iterator for WordBreakingTokenizer<'a> {
23048 /// Yields a span, the count of graphemes in the token, and whether it was
23049 /// whitespace. Note that it also breaks at word boundaries.
23050 type Item = WordBreakToken<'a>;
23051
23052 fn next(&mut self) -> Option<Self::Item> {
23053 use unicode_segmentation::UnicodeSegmentation;
23054 if self.input.is_empty() {
23055 return None;
23056 }
23057
23058 let mut iter = self.input.graphemes(true).peekable();
23059 let mut offset = 0;
23060 let mut grapheme_len = 0;
23061 if let Some(first_grapheme) = iter.next() {
23062 let is_newline = first_grapheme == "\n";
23063 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23064 offset += first_grapheme.len();
23065 grapheme_len += 1;
23066 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23067 if let Some(grapheme) = iter.peek().copied()
23068 && should_stay_with_preceding_ideograph(grapheme)
23069 {
23070 offset += grapheme.len();
23071 grapheme_len += 1;
23072 }
23073 } else {
23074 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23075 let mut next_word_bound = words.peek().copied();
23076 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23077 next_word_bound = words.next();
23078 }
23079 while let Some(grapheme) = iter.peek().copied() {
23080 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23081 break;
23082 };
23083 if is_grapheme_whitespace(grapheme) != is_whitespace
23084 || (grapheme == "\n") != is_newline
23085 {
23086 break;
23087 };
23088 offset += grapheme.len();
23089 grapheme_len += 1;
23090 iter.next();
23091 }
23092 }
23093 let token = &self.input[..offset];
23094 self.input = &self.input[offset..];
23095 if token == "\n" {
23096 Some(WordBreakToken::Newline)
23097 } else if is_whitespace {
23098 Some(WordBreakToken::InlineWhitespace {
23099 token,
23100 grapheme_len,
23101 })
23102 } else {
23103 Some(WordBreakToken::Word {
23104 token,
23105 grapheme_len,
23106 })
23107 }
23108 } else {
23109 None
23110 }
23111 }
23112}
23113
23114#[test]
23115fn test_word_breaking_tokenizer() {
23116 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23117 ("", &[]),
23118 (" ", &[whitespace(" ", 2)]),
23119 ("Ʒ", &[word("Ʒ", 1)]),
23120 ("Ǽ", &[word("Ǽ", 1)]),
23121 ("⋑", &[word("⋑", 1)]),
23122 ("⋑⋑", &[word("⋑⋑", 2)]),
23123 (
23124 "原理,进而",
23125 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23126 ),
23127 (
23128 "hello world",
23129 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23130 ),
23131 (
23132 "hello, world",
23133 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23134 ),
23135 (
23136 " hello world",
23137 &[
23138 whitespace(" ", 2),
23139 word("hello", 5),
23140 whitespace(" ", 1),
23141 word("world", 5),
23142 ],
23143 ),
23144 (
23145 "这是什么 \n 钢笔",
23146 &[
23147 word("这", 1),
23148 word("是", 1),
23149 word("什", 1),
23150 word("么", 1),
23151 whitespace(" ", 1),
23152 newline(),
23153 whitespace(" ", 1),
23154 word("钢", 1),
23155 word("笔", 1),
23156 ],
23157 ),
23158 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23159 ];
23160
23161 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23162 WordBreakToken::Word {
23163 token,
23164 grapheme_len,
23165 }
23166 }
23167
23168 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23169 WordBreakToken::InlineWhitespace {
23170 token,
23171 grapheme_len,
23172 }
23173 }
23174
23175 fn newline() -> WordBreakToken<'static> {
23176 WordBreakToken::Newline
23177 }
23178
23179 for (input, result) in tests {
23180 assert_eq!(
23181 WordBreakingTokenizer::new(input)
23182 .collect::<Vec<_>>()
23183 .as_slice(),
23184 *result,
23185 );
23186 }
23187}
23188
23189fn wrap_with_prefix(
23190 first_line_prefix: String,
23191 subsequent_lines_prefix: String,
23192 unwrapped_text: String,
23193 wrap_column: usize,
23194 tab_size: NonZeroU32,
23195 preserve_existing_whitespace: bool,
23196) -> String {
23197 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23198 let subsequent_lines_prefix_len =
23199 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23200 let mut wrapped_text = String::new();
23201 let mut current_line = first_line_prefix;
23202 let mut is_first_line = true;
23203
23204 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23205 let mut current_line_len = first_line_prefix_len;
23206 let mut in_whitespace = false;
23207 for token in tokenizer {
23208 let have_preceding_whitespace = in_whitespace;
23209 match token {
23210 WordBreakToken::Word {
23211 token,
23212 grapheme_len,
23213 } => {
23214 in_whitespace = false;
23215 let current_prefix_len = if is_first_line {
23216 first_line_prefix_len
23217 } else {
23218 subsequent_lines_prefix_len
23219 };
23220 if current_line_len + grapheme_len > wrap_column
23221 && current_line_len != current_prefix_len
23222 {
23223 wrapped_text.push_str(current_line.trim_end());
23224 wrapped_text.push('\n');
23225 is_first_line = false;
23226 current_line = subsequent_lines_prefix.clone();
23227 current_line_len = subsequent_lines_prefix_len;
23228 }
23229 current_line.push_str(token);
23230 current_line_len += grapheme_len;
23231 }
23232 WordBreakToken::InlineWhitespace {
23233 mut token,
23234 mut grapheme_len,
23235 } => {
23236 in_whitespace = true;
23237 if have_preceding_whitespace && !preserve_existing_whitespace {
23238 continue;
23239 }
23240 if !preserve_existing_whitespace {
23241 // Keep a single whitespace grapheme as-is
23242 if let Some(first) =
23243 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23244 {
23245 token = first;
23246 } else {
23247 token = " ";
23248 }
23249 grapheme_len = 1;
23250 }
23251 let current_prefix_len = if is_first_line {
23252 first_line_prefix_len
23253 } else {
23254 subsequent_lines_prefix_len
23255 };
23256 if current_line_len + grapheme_len > wrap_column {
23257 wrapped_text.push_str(current_line.trim_end());
23258 wrapped_text.push('\n');
23259 is_first_line = false;
23260 current_line = subsequent_lines_prefix.clone();
23261 current_line_len = subsequent_lines_prefix_len;
23262 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23263 current_line.push_str(token);
23264 current_line_len += grapheme_len;
23265 }
23266 }
23267 WordBreakToken::Newline => {
23268 in_whitespace = true;
23269 let current_prefix_len = if is_first_line {
23270 first_line_prefix_len
23271 } else {
23272 subsequent_lines_prefix_len
23273 };
23274 if preserve_existing_whitespace {
23275 wrapped_text.push_str(current_line.trim_end());
23276 wrapped_text.push('\n');
23277 is_first_line = false;
23278 current_line = subsequent_lines_prefix.clone();
23279 current_line_len = subsequent_lines_prefix_len;
23280 } else if have_preceding_whitespace {
23281 continue;
23282 } else if current_line_len + 1 > wrap_column
23283 && current_line_len != current_prefix_len
23284 {
23285 wrapped_text.push_str(current_line.trim_end());
23286 wrapped_text.push('\n');
23287 is_first_line = false;
23288 current_line = subsequent_lines_prefix.clone();
23289 current_line_len = subsequent_lines_prefix_len;
23290 } else if current_line_len != current_prefix_len {
23291 current_line.push(' ');
23292 current_line_len += 1;
23293 }
23294 }
23295 }
23296 }
23297
23298 if !current_line.is_empty() {
23299 wrapped_text.push_str(¤t_line);
23300 }
23301 wrapped_text
23302}
23303
23304#[test]
23305fn test_wrap_with_prefix() {
23306 assert_eq!(
23307 wrap_with_prefix(
23308 "# ".to_string(),
23309 "# ".to_string(),
23310 "abcdefg".to_string(),
23311 4,
23312 NonZeroU32::new(4).unwrap(),
23313 false,
23314 ),
23315 "# abcdefg"
23316 );
23317 assert_eq!(
23318 wrap_with_prefix(
23319 "".to_string(),
23320 "".to_string(),
23321 "\thello world".to_string(),
23322 8,
23323 NonZeroU32::new(4).unwrap(),
23324 false,
23325 ),
23326 "hello\nworld"
23327 );
23328 assert_eq!(
23329 wrap_with_prefix(
23330 "// ".to_string(),
23331 "// ".to_string(),
23332 "xx \nyy zz aa bb cc".to_string(),
23333 12,
23334 NonZeroU32::new(4).unwrap(),
23335 false,
23336 ),
23337 "// xx yy zz\n// aa bb cc"
23338 );
23339 assert_eq!(
23340 wrap_with_prefix(
23341 String::new(),
23342 String::new(),
23343 "这是什么 \n 钢笔".to_string(),
23344 3,
23345 NonZeroU32::new(4).unwrap(),
23346 false,
23347 ),
23348 "这是什\n么 钢\n笔"
23349 );
23350 assert_eq!(
23351 wrap_with_prefix(
23352 String::new(),
23353 String::new(),
23354 format!("foo{}bar", '\u{2009}'), // thin space
23355 80,
23356 NonZeroU32::new(4).unwrap(),
23357 false,
23358 ),
23359 format!("foo{}bar", '\u{2009}')
23360 );
23361}
23362
23363pub trait CollaborationHub {
23364 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23365 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23366 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23367}
23368
23369impl CollaborationHub for Entity<Project> {
23370 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23371 self.read(cx).collaborators()
23372 }
23373
23374 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23375 self.read(cx).user_store().read(cx).participant_indices()
23376 }
23377
23378 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23379 let this = self.read(cx);
23380 let user_ids = this.collaborators().values().map(|c| c.user_id);
23381 this.user_store().read(cx).participant_names(user_ids, cx)
23382 }
23383}
23384
23385pub trait SemanticsProvider {
23386 fn hover(
23387 &self,
23388 buffer: &Entity<Buffer>,
23389 position: text::Anchor,
23390 cx: &mut App,
23391 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23392
23393 fn inline_values(
23394 &self,
23395 buffer_handle: Entity<Buffer>,
23396 range: Range<text::Anchor>,
23397 cx: &mut App,
23398 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23399
23400 fn applicable_inlay_chunks(
23401 &self,
23402 buffer: &Entity<Buffer>,
23403 ranges: &[Range<text::Anchor>],
23404 cx: &mut App,
23405 ) -> Vec<Range<BufferRow>>;
23406
23407 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23408
23409 fn inlay_hints(
23410 &self,
23411 invalidate: InvalidationStrategy,
23412 buffer: Entity<Buffer>,
23413 ranges: Vec<Range<text::Anchor>>,
23414 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23415 cx: &mut App,
23416 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23417
23418 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23419
23420 fn document_highlights(
23421 &self,
23422 buffer: &Entity<Buffer>,
23423 position: text::Anchor,
23424 cx: &mut App,
23425 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23426
23427 fn definitions(
23428 &self,
23429 buffer: &Entity<Buffer>,
23430 position: text::Anchor,
23431 kind: GotoDefinitionKind,
23432 cx: &mut App,
23433 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23434
23435 fn range_for_rename(
23436 &self,
23437 buffer: &Entity<Buffer>,
23438 position: text::Anchor,
23439 cx: &mut App,
23440 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23441
23442 fn perform_rename(
23443 &self,
23444 buffer: &Entity<Buffer>,
23445 position: text::Anchor,
23446 new_name: String,
23447 cx: &mut App,
23448 ) -> Option<Task<Result<ProjectTransaction>>>;
23449}
23450
23451pub trait CompletionProvider {
23452 fn completions(
23453 &self,
23454 excerpt_id: ExcerptId,
23455 buffer: &Entity<Buffer>,
23456 buffer_position: text::Anchor,
23457 trigger: CompletionContext,
23458 window: &mut Window,
23459 cx: &mut Context<Editor>,
23460 ) -> Task<Result<Vec<CompletionResponse>>>;
23461
23462 fn resolve_completions(
23463 &self,
23464 _buffer: Entity<Buffer>,
23465 _completion_indices: Vec<usize>,
23466 _completions: Rc<RefCell<Box<[Completion]>>>,
23467 _cx: &mut Context<Editor>,
23468 ) -> Task<Result<bool>> {
23469 Task::ready(Ok(false))
23470 }
23471
23472 fn apply_additional_edits_for_completion(
23473 &self,
23474 _buffer: Entity<Buffer>,
23475 _completions: Rc<RefCell<Box<[Completion]>>>,
23476 _completion_index: usize,
23477 _push_to_history: bool,
23478 _cx: &mut Context<Editor>,
23479 ) -> Task<Result<Option<language::Transaction>>> {
23480 Task::ready(Ok(None))
23481 }
23482
23483 fn is_completion_trigger(
23484 &self,
23485 buffer: &Entity<Buffer>,
23486 position: language::Anchor,
23487 text: &str,
23488 trigger_in_words: bool,
23489 menu_is_open: bool,
23490 cx: &mut Context<Editor>,
23491 ) -> bool;
23492
23493 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23494
23495 fn sort_completions(&self) -> bool {
23496 true
23497 }
23498
23499 fn filter_completions(&self) -> bool {
23500 true
23501 }
23502
23503 fn show_snippets(&self) -> bool {
23504 false
23505 }
23506}
23507
23508pub trait CodeActionProvider {
23509 fn id(&self) -> Arc<str>;
23510
23511 fn code_actions(
23512 &self,
23513 buffer: &Entity<Buffer>,
23514 range: Range<text::Anchor>,
23515 window: &mut Window,
23516 cx: &mut App,
23517 ) -> Task<Result<Vec<CodeAction>>>;
23518
23519 fn apply_code_action(
23520 &self,
23521 buffer_handle: Entity<Buffer>,
23522 action: CodeAction,
23523 excerpt_id: ExcerptId,
23524 push_to_history: bool,
23525 window: &mut Window,
23526 cx: &mut App,
23527 ) -> Task<Result<ProjectTransaction>>;
23528}
23529
23530impl CodeActionProvider for Entity<Project> {
23531 fn id(&self) -> Arc<str> {
23532 "project".into()
23533 }
23534
23535 fn code_actions(
23536 &self,
23537 buffer: &Entity<Buffer>,
23538 range: Range<text::Anchor>,
23539 _window: &mut Window,
23540 cx: &mut App,
23541 ) -> Task<Result<Vec<CodeAction>>> {
23542 self.update(cx, |project, cx| {
23543 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23544 let code_actions = project.code_actions(buffer, range, None, cx);
23545 cx.background_spawn(async move {
23546 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23547 Ok(code_lens_actions
23548 .context("code lens fetch")?
23549 .into_iter()
23550 .flatten()
23551 .chain(
23552 code_actions
23553 .context("code action fetch")?
23554 .into_iter()
23555 .flatten(),
23556 )
23557 .collect())
23558 })
23559 })
23560 }
23561
23562 fn apply_code_action(
23563 &self,
23564 buffer_handle: Entity<Buffer>,
23565 action: CodeAction,
23566 _excerpt_id: ExcerptId,
23567 push_to_history: bool,
23568 _window: &mut Window,
23569 cx: &mut App,
23570 ) -> Task<Result<ProjectTransaction>> {
23571 self.update(cx, |project, cx| {
23572 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23573 })
23574 }
23575}
23576
23577fn snippet_completions(
23578 project: &Project,
23579 buffer: &Entity<Buffer>,
23580 buffer_anchor: text::Anchor,
23581 classifier: CharClassifier,
23582 cx: &mut App,
23583) -> Task<Result<CompletionResponse>> {
23584 let languages = buffer.read(cx).languages_at(buffer_anchor);
23585 let snippet_store = project.snippets().read(cx);
23586
23587 let scopes: Vec<_> = languages
23588 .iter()
23589 .filter_map(|language| {
23590 let language_name = language.lsp_id();
23591 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23592
23593 if snippets.is_empty() {
23594 None
23595 } else {
23596 Some((language.default_scope(), snippets))
23597 }
23598 })
23599 .collect();
23600
23601 if scopes.is_empty() {
23602 return Task::ready(Ok(CompletionResponse {
23603 completions: vec![],
23604 display_options: CompletionDisplayOptions::default(),
23605 is_incomplete: false,
23606 }));
23607 }
23608
23609 let snapshot = buffer.read(cx).text_snapshot();
23610 let executor = cx.background_executor().clone();
23611
23612 cx.background_spawn(async move {
23613 let is_word_char = |c| classifier.is_word(c);
23614
23615 let mut is_incomplete = false;
23616 let mut completions: Vec<Completion> = Vec::new();
23617
23618 const MAX_PREFIX_LEN: usize = 128;
23619 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23620 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23621 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23622
23623 let max_buffer_window: String = snapshot
23624 .text_for_range(window_start..buffer_offset)
23625 .collect();
23626
23627 if max_buffer_window.is_empty() {
23628 return Ok(CompletionResponse {
23629 completions: vec![],
23630 display_options: CompletionDisplayOptions::default(),
23631 is_incomplete: true,
23632 });
23633 }
23634
23635 for (_scope, snippets) in scopes.into_iter() {
23636 // Sort snippets by word count to match longer snippet prefixes first.
23637 let mut sorted_snippet_candidates = snippets
23638 .iter()
23639 .enumerate()
23640 .flat_map(|(snippet_ix, snippet)| {
23641 snippet
23642 .prefix
23643 .iter()
23644 .enumerate()
23645 .map(move |(prefix_ix, prefix)| {
23646 let word_count =
23647 snippet_candidate_suffixes(prefix, is_word_char).count();
23648 ((snippet_ix, prefix_ix), prefix, word_count)
23649 })
23650 })
23651 .collect_vec();
23652 sorted_snippet_candidates
23653 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23654
23655 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23656
23657 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23658 .take(
23659 sorted_snippet_candidates
23660 .first()
23661 .map(|(_, _, word_count)| *word_count)
23662 .unwrap_or_default(),
23663 )
23664 .collect_vec();
23665
23666 const MAX_RESULTS: usize = 100;
23667 // Each match also remembers how many characters from the buffer it consumed
23668 let mut matches: Vec<(StringMatch, usize)> = vec![];
23669
23670 let mut snippet_list_cutoff_index = 0;
23671 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23672 let word_count = buffer_index + 1;
23673 // Increase `snippet_list_cutoff_index` until we have all of the
23674 // snippets with sufficiently many words.
23675 while sorted_snippet_candidates
23676 .get(snippet_list_cutoff_index)
23677 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23678 *snippet_word_count >= word_count
23679 })
23680 {
23681 snippet_list_cutoff_index += 1;
23682 }
23683
23684 // Take only the candidates with at least `word_count` many words
23685 let snippet_candidates_at_word_len =
23686 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23687
23688 let candidates = snippet_candidates_at_word_len
23689 .iter()
23690 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23691 .enumerate() // index in `sorted_snippet_candidates`
23692 // First char must match
23693 .filter(|(_ix, prefix)| {
23694 itertools::equal(
23695 prefix
23696 .chars()
23697 .next()
23698 .into_iter()
23699 .flat_map(|c| c.to_lowercase()),
23700 buffer_window
23701 .chars()
23702 .next()
23703 .into_iter()
23704 .flat_map(|c| c.to_lowercase()),
23705 )
23706 })
23707 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23708 .collect::<Vec<StringMatchCandidate>>();
23709
23710 matches.extend(
23711 fuzzy::match_strings(
23712 &candidates,
23713 &buffer_window,
23714 buffer_window.chars().any(|c| c.is_uppercase()),
23715 true,
23716 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23717 &Default::default(),
23718 executor.clone(),
23719 )
23720 .await
23721 .into_iter()
23722 .map(|string_match| (string_match, buffer_window.len())),
23723 );
23724
23725 if matches.len() >= MAX_RESULTS {
23726 break;
23727 }
23728 }
23729
23730 let to_lsp = |point: &text::Anchor| {
23731 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23732 point_to_lsp(end)
23733 };
23734 let lsp_end = to_lsp(&buffer_anchor);
23735
23736 if matches.len() >= MAX_RESULTS {
23737 is_incomplete = true;
23738 }
23739
23740 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23741 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23742 sorted_snippet_candidates[string_match.candidate_id];
23743 let snippet = &snippets[snippet_index];
23744 let start = buffer_offset - buffer_window_len;
23745 let start = snapshot.anchor_before(start);
23746 let range = start..buffer_anchor;
23747 let lsp_start = to_lsp(&start);
23748 let lsp_range = lsp::Range {
23749 start: lsp_start,
23750 end: lsp_end,
23751 };
23752 Completion {
23753 replace_range: range,
23754 new_text: snippet.body.clone(),
23755 source: CompletionSource::Lsp {
23756 insert_range: None,
23757 server_id: LanguageServerId(usize::MAX),
23758 resolved: true,
23759 lsp_completion: Box::new(lsp::CompletionItem {
23760 label: snippet.prefix.first().unwrap().clone(),
23761 kind: Some(CompletionItemKind::SNIPPET),
23762 label_details: snippet.description.as_ref().map(|description| {
23763 lsp::CompletionItemLabelDetails {
23764 detail: Some(description.clone()),
23765 description: None,
23766 }
23767 }),
23768 insert_text_format: Some(InsertTextFormat::SNIPPET),
23769 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23770 lsp::InsertReplaceEdit {
23771 new_text: snippet.body.clone(),
23772 insert: lsp_range,
23773 replace: lsp_range,
23774 },
23775 )),
23776 filter_text: Some(snippet.body.clone()),
23777 sort_text: Some(char::MAX.to_string()),
23778 ..lsp::CompletionItem::default()
23779 }),
23780 lsp_defaults: None,
23781 },
23782 label: CodeLabel {
23783 text: matching_prefix.clone(),
23784 runs: Vec::new(),
23785 filter_range: 0..matching_prefix.len(),
23786 },
23787 icon_path: None,
23788 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23789 single_line: snippet.name.clone().into(),
23790 plain_text: snippet
23791 .description
23792 .clone()
23793 .map(|description| description.into()),
23794 }),
23795 insert_text_mode: None,
23796 confirm: None,
23797 match_start: Some(start),
23798 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23799 }
23800 }));
23801 }
23802
23803 Ok(CompletionResponse {
23804 completions,
23805 display_options: CompletionDisplayOptions::default(),
23806 is_incomplete,
23807 })
23808 })
23809}
23810
23811impl CompletionProvider for Entity<Project> {
23812 fn completions(
23813 &self,
23814 _excerpt_id: ExcerptId,
23815 buffer: &Entity<Buffer>,
23816 buffer_position: text::Anchor,
23817 options: CompletionContext,
23818 _window: &mut Window,
23819 cx: &mut Context<Editor>,
23820 ) -> Task<Result<Vec<CompletionResponse>>> {
23821 self.update(cx, |project, cx| {
23822 let task = project.completions(buffer, buffer_position, options, cx);
23823 cx.background_spawn(task)
23824 })
23825 }
23826
23827 fn resolve_completions(
23828 &self,
23829 buffer: Entity<Buffer>,
23830 completion_indices: Vec<usize>,
23831 completions: Rc<RefCell<Box<[Completion]>>>,
23832 cx: &mut Context<Editor>,
23833 ) -> Task<Result<bool>> {
23834 self.update(cx, |project, cx| {
23835 project.lsp_store().update(cx, |lsp_store, cx| {
23836 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23837 })
23838 })
23839 }
23840
23841 fn apply_additional_edits_for_completion(
23842 &self,
23843 buffer: Entity<Buffer>,
23844 completions: Rc<RefCell<Box<[Completion]>>>,
23845 completion_index: usize,
23846 push_to_history: bool,
23847 cx: &mut Context<Editor>,
23848 ) -> Task<Result<Option<language::Transaction>>> {
23849 self.update(cx, |project, cx| {
23850 project.lsp_store().update(cx, |lsp_store, cx| {
23851 lsp_store.apply_additional_edits_for_completion(
23852 buffer,
23853 completions,
23854 completion_index,
23855 push_to_history,
23856 cx,
23857 )
23858 })
23859 })
23860 }
23861
23862 fn is_completion_trigger(
23863 &self,
23864 buffer: &Entity<Buffer>,
23865 position: language::Anchor,
23866 text: &str,
23867 trigger_in_words: bool,
23868 menu_is_open: bool,
23869 cx: &mut Context<Editor>,
23870 ) -> bool {
23871 let mut chars = text.chars();
23872 let char = if let Some(char) = chars.next() {
23873 char
23874 } else {
23875 return false;
23876 };
23877 if chars.next().is_some() {
23878 return false;
23879 }
23880
23881 let buffer = buffer.read(cx);
23882 let snapshot = buffer.snapshot();
23883 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23884 return false;
23885 }
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}