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::{ControlFlow, 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 AccentColors, 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 = (
730 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
731 Arc<[Range<Anchor>]>,
732);
733type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
734
735#[derive(Default)]
736struct ScrollbarMarkerState {
737 scrollbar_size: Size<Pixels>,
738 dirty: bool,
739 markers: Arc<[PaintQuad]>,
740 pending_refresh: Option<Task<Result<()>>>,
741}
742
743impl ScrollbarMarkerState {
744 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
745 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
746 }
747}
748
749#[derive(Clone, Copy, PartialEq, Eq)]
750pub enum MinimapVisibility {
751 Disabled,
752 Enabled {
753 /// The configuration currently present in the users settings.
754 setting_configuration: bool,
755 /// Whether to override the currently set visibility from the users setting.
756 toggle_override: bool,
757 },
758}
759
760impl MinimapVisibility {
761 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
762 if mode.is_full() {
763 Self::Enabled {
764 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
765 toggle_override: false,
766 }
767 } else {
768 Self::Disabled
769 }
770 }
771
772 fn hidden(&self) -> Self {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 ..
777 } => Self::Enabled {
778 setting_configuration,
779 toggle_override: setting_configuration,
780 },
781 Self::Disabled => Self::Disabled,
782 }
783 }
784
785 fn disabled(&self) -> bool {
786 matches!(*self, Self::Disabled)
787 }
788
789 fn settings_visibility(&self) -> bool {
790 match *self {
791 Self::Enabled {
792 setting_configuration,
793 ..
794 } => setting_configuration,
795 _ => false,
796 }
797 }
798
799 fn visible(&self) -> bool {
800 match *self {
801 Self::Enabled {
802 setting_configuration,
803 toggle_override,
804 } => setting_configuration ^ toggle_override,
805 _ => false,
806 }
807 }
808
809 fn toggle_visibility(&self) -> Self {
810 match *self {
811 Self::Enabled {
812 toggle_override,
813 setting_configuration,
814 } => Self::Enabled {
815 setting_configuration,
816 toggle_override: !toggle_override,
817 },
818 Self::Disabled => Self::Disabled,
819 }
820 }
821}
822
823#[derive(Debug, Clone, Copy, PartialEq, Eq)]
824pub enum BufferSerialization {
825 All,
826 NonDirtyBuffers,
827}
828
829impl BufferSerialization {
830 fn new(restore_unsaved_buffers: bool) -> Self {
831 if restore_unsaved_buffers {
832 Self::All
833 } else {
834 Self::NonDirtyBuffers
835 }
836 }
837}
838
839#[derive(Clone, Debug)]
840struct RunnableTasks {
841 templates: Vec<(TaskSourceKind, TaskTemplate)>,
842 offset: multi_buffer::Anchor,
843 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
844 column: u32,
845 // Values of all named captures, including those starting with '_'
846 extra_variables: HashMap<String, String>,
847 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
848 context_range: Range<BufferOffset>,
849}
850
851impl RunnableTasks {
852 fn resolve<'a>(
853 &'a self,
854 cx: &'a task::TaskContext,
855 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
856 self.templates.iter().filter_map(|(kind, template)| {
857 template
858 .resolve_task(&kind.to_id_base(), cx)
859 .map(|task| (kind.clone(), task))
860 })
861 }
862}
863
864#[derive(Clone)]
865pub struct ResolvedTasks {
866 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
867 position: Anchor,
868}
869
870/// Addons allow storing per-editor state in other crates (e.g. Vim)
871pub trait Addon: 'static {
872 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
873
874 fn render_buffer_header_controls(
875 &self,
876 _: &ExcerptInfo,
877 _: &Window,
878 _: &App,
879 ) -> Option<AnyElement> {
880 None
881 }
882
883 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
884 None
885 }
886
887 fn to_any(&self) -> &dyn std::any::Any;
888
889 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
890 None
891 }
892}
893
894struct ChangeLocation {
895 current: Option<Vec<Anchor>>,
896 original: Vec<Anchor>,
897}
898impl ChangeLocation {
899 fn locations(&self) -> &[Anchor] {
900 self.current.as_ref().unwrap_or(&self.original)
901 }
902}
903
904/// A set of caret positions, registered when the editor was edited.
905pub struct ChangeList {
906 changes: Vec<ChangeLocation>,
907 /// Currently "selected" change.
908 position: Option<usize>,
909}
910
911impl ChangeList {
912 pub fn new() -> Self {
913 Self {
914 changes: Vec::new(),
915 position: None,
916 }
917 }
918
919 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
920 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
921 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
922 if self.changes.is_empty() {
923 return None;
924 }
925
926 let prev = self.position.unwrap_or(self.changes.len());
927 let next = if direction == Direction::Prev {
928 prev.saturating_sub(count)
929 } else {
930 (prev + count).min(self.changes.len() - 1)
931 };
932 self.position = Some(next);
933 self.changes.get(next).map(|change| change.locations())
934 }
935
936 /// Adds a new change to the list, resetting the change list position.
937 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
938 self.position.take();
939 if let Some(last) = self.changes.last_mut()
940 && group
941 {
942 last.current = Some(new_positions)
943 } else {
944 self.changes.push(ChangeLocation {
945 original: new_positions,
946 current: None,
947 });
948 }
949 }
950
951 pub fn last(&self) -> Option<&[Anchor]> {
952 self.changes.last().map(|change| change.locations())
953 }
954
955 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
956 self.changes.last().map(|change| change.original.as_slice())
957 }
958
959 pub fn invert_last_group(&mut self) {
960 if let Some(last) = self.changes.last_mut()
961 && let Some(current) = last.current.as_mut()
962 {
963 mem::swap(&mut last.original, current);
964 }
965 }
966}
967
968#[derive(Clone)]
969struct InlineBlamePopoverState {
970 scroll_handle: ScrollHandle,
971 commit_message: Option<ParsedCommitMessage>,
972 markdown: Entity<Markdown>,
973}
974
975struct InlineBlamePopover {
976 position: gpui::Point<Pixels>,
977 hide_task: Option<Task<()>>,
978 popover_bounds: Option<Bounds<Pixels>>,
979 popover_state: InlineBlamePopoverState,
980 keyboard_grace: bool,
981}
982
983enum SelectionDragState {
984 /// State when no drag related activity is detected.
985 None,
986 /// State when the mouse is down on a selection that is about to be dragged.
987 ReadyToDrag {
988 selection: Selection<Anchor>,
989 click_position: gpui::Point<Pixels>,
990 mouse_down_time: Instant,
991 },
992 /// State when the mouse is dragging the selection in the editor.
993 Dragging {
994 selection: Selection<Anchor>,
995 drop_cursor: Selection<Anchor>,
996 hide_drop_cursor: bool,
997 },
998}
999
1000enum ColumnarSelectionState {
1001 FromMouse {
1002 selection_tail: Anchor,
1003 display_point: Option<DisplayPoint>,
1004 },
1005 FromSelection {
1006 selection_tail: Anchor,
1007 },
1008}
1009
1010/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1011/// a breakpoint on them.
1012#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1013struct PhantomBreakpointIndicator {
1014 display_row: DisplayRow,
1015 /// There's a small debounce between hovering over the line and showing the indicator.
1016 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1017 is_active: bool,
1018 collides_with_existing_breakpoint: bool,
1019}
1020
1021/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1022///
1023/// See the [module level documentation](self) for more information.
1024pub struct Editor {
1025 focus_handle: FocusHandle,
1026 last_focused_descendant: Option<WeakFocusHandle>,
1027 /// The text buffer being edited
1028 buffer: Entity<MultiBuffer>,
1029 /// Map of how text in the buffer should be displayed.
1030 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1031 pub display_map: Entity<DisplayMap>,
1032 placeholder_display_map: Option<Entity<DisplayMap>>,
1033 pub selections: SelectionsCollection,
1034 pub scroll_manager: ScrollManager,
1035 /// When inline assist editors are linked, they all render cursors because
1036 /// typing enters text into each of them, even the ones that aren't focused.
1037 pub(crate) show_cursor_when_unfocused: bool,
1038 columnar_selection_state: Option<ColumnarSelectionState>,
1039 add_selections_state: Option<AddSelectionsState>,
1040 select_next_state: Option<SelectNextState>,
1041 select_prev_state: Option<SelectNextState>,
1042 selection_history: SelectionHistory,
1043 defer_selection_effects: bool,
1044 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1045 autoclose_regions: Vec<AutocloseRegion>,
1046 snippet_stack: InvalidationStack<SnippetState>,
1047 select_syntax_node_history: SelectSyntaxNodeHistory,
1048 ime_transaction: Option<TransactionId>,
1049 pub diagnostics_max_severity: DiagnosticSeverity,
1050 active_diagnostics: ActiveDiagnostic,
1051 show_inline_diagnostics: bool,
1052 inline_diagnostics_update: Task<()>,
1053 inline_diagnostics_enabled: bool,
1054 diagnostics_enabled: bool,
1055 word_completions_enabled: bool,
1056 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1057 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1058 hard_wrap: Option<usize>,
1059 project: Option<Entity<Project>>,
1060 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1061 completion_provider: Option<Rc<dyn CompletionProvider>>,
1062 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1063 blink_manager: Entity<BlinkManager>,
1064 show_cursor_names: bool,
1065 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1066 pub show_local_selections: bool,
1067 mode: EditorMode,
1068 show_breadcrumbs: bool,
1069 show_gutter: bool,
1070 show_scrollbars: ScrollbarAxes,
1071 minimap_visibility: MinimapVisibility,
1072 offset_content: bool,
1073 disable_expand_excerpt_buttons: bool,
1074 show_line_numbers: Option<bool>,
1075 use_relative_line_numbers: Option<bool>,
1076 show_git_diff_gutter: Option<bool>,
1077 show_code_actions: Option<bool>,
1078 show_runnables: Option<bool>,
1079 show_breakpoints: Option<bool>,
1080 show_wrap_guides: Option<bool>,
1081 show_indent_guides: Option<bool>,
1082 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1083 highlight_order: usize,
1084 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1085 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1086 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1087 scrollbar_marker_state: ScrollbarMarkerState,
1088 active_indent_guides_state: ActiveIndentGuidesState,
1089 nav_history: Option<ItemNavHistory>,
1090 context_menu: RefCell<Option<CodeContextMenu>>,
1091 context_menu_options: Option<ContextMenuOptions>,
1092 mouse_context_menu: Option<MouseContextMenu>,
1093 completion_tasks: Vec<(CompletionId, Task<()>)>,
1094 inline_blame_popover: Option<InlineBlamePopover>,
1095 inline_blame_popover_show_task: Option<Task<()>>,
1096 signature_help_state: SignatureHelpState,
1097 auto_signature_help: Option<bool>,
1098 find_all_references_task_sources: Vec<Anchor>,
1099 next_completion_id: CompletionId,
1100 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1101 code_actions_task: Option<Task<Result<()>>>,
1102 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1103 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1104 document_highlights_task: Option<Task<()>>,
1105 linked_editing_range_task: Option<Task<Option<()>>>,
1106 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1107 pending_rename: Option<RenameState>,
1108 searchable: bool,
1109 cursor_shape: CursorShape,
1110 current_line_highlight: Option<CurrentLineHighlight>,
1111 pub collapse_matches: bool,
1112 autoindent_mode: Option<AutoindentMode>,
1113 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1114 input_enabled: bool,
1115 use_modal_editing: bool,
1116 read_only: bool,
1117 leader_id: Option<CollaboratorId>,
1118 remote_id: Option<ViewId>,
1119 pub hover_state: HoverState,
1120 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1121 gutter_hovered: bool,
1122 hovered_link_state: Option<HoveredLinkState>,
1123 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1124 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1125 active_edit_prediction: Option<EditPredictionState>,
1126 /// Used to prevent flickering as the user types while the menu is open
1127 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1128 edit_prediction_settings: EditPredictionSettings,
1129 edit_predictions_hidden_for_vim_mode: bool,
1130 show_edit_predictions_override: Option<bool>,
1131 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1132 edit_prediction_preview: EditPredictionPreview,
1133 edit_prediction_indent_conflict: bool,
1134 edit_prediction_requires_modifier_in_indent_conflict: bool,
1135 next_inlay_id: usize,
1136 next_color_inlay_id: usize,
1137 _subscriptions: Vec<Subscription>,
1138 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1139 gutter_dimensions: GutterDimensions,
1140 style: Option<EditorStyle>,
1141 text_style_refinement: Option<TextStyleRefinement>,
1142 next_editor_action_id: EditorActionId,
1143 editor_actions: Rc<
1144 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1145 >,
1146 use_autoclose: bool,
1147 use_auto_surround: bool,
1148 auto_replace_emoji_shortcode: bool,
1149 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1150 show_git_blame_gutter: bool,
1151 show_git_blame_inline: bool,
1152 show_git_blame_inline_delay_task: Option<Task<()>>,
1153 git_blame_inline_enabled: bool,
1154 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1155 buffer_serialization: Option<BufferSerialization>,
1156 show_selection_menu: Option<bool>,
1157 blame: Option<Entity<GitBlame>>,
1158 blame_subscription: Option<Subscription>,
1159 custom_context_menu: Option<
1160 Box<
1161 dyn 'static
1162 + Fn(
1163 &mut Self,
1164 DisplayPoint,
1165 &mut Window,
1166 &mut Context<Self>,
1167 ) -> Option<Entity<ui::ContextMenu>>,
1168 >,
1169 >,
1170 last_bounds: Option<Bounds<Pixels>>,
1171 last_position_map: Option<Rc<PositionMap>>,
1172 expect_bounds_change: Option<Bounds<Pixels>>,
1173 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1174 tasks_update_task: Option<Task<()>>,
1175 breakpoint_store: Option<Entity<BreakpointStore>>,
1176 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1177 hovered_diff_hunk_row: Option<DisplayRow>,
1178 pull_diagnostics_task: Task<()>,
1179 pull_diagnostics_background_task: Task<()>,
1180 in_project_search: bool,
1181 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1182 breadcrumb_header: Option<String>,
1183 focused_block: Option<FocusedBlock>,
1184 next_scroll_position: NextScrollCursorCenterTopBottom,
1185 addons: HashMap<TypeId, Box<dyn Addon>>,
1186 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1187 load_diff_task: Option<Shared<Task<()>>>,
1188 /// Whether we are temporarily displaying a diff other than git's
1189 temporary_diff_override: bool,
1190 selection_mark_mode: bool,
1191 toggle_fold_multiple_buffers: Task<()>,
1192 _scroll_cursor_center_top_bottom_task: Task<()>,
1193 serialize_selections: Task<()>,
1194 serialize_folds: Task<()>,
1195 mouse_cursor_hidden: bool,
1196 minimap: Option<Entity<Self>>,
1197 hide_mouse_mode: HideMouseMode,
1198 pub change_list: ChangeList,
1199 inline_value_cache: InlineValueCache,
1200
1201 selection_drag_state: SelectionDragState,
1202 colors: Option<LspColorData>,
1203 post_scroll_update: Task<()>,
1204 refresh_colors_task: Task<()>,
1205 inlay_hints: Option<LspInlayHintData>,
1206 folding_newlines: Task<()>,
1207 select_next_is_case_sensitive: Option<bool>,
1208 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1209 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1210 accent_data: Option<AccentData>,
1211 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1212 use_base_text_line_numbers: bool,
1213}
1214
1215#[derive(Debug, PartialEq)]
1216struct AccentData {
1217 colors: AccentColors,
1218 overrides: Vec<SharedString>,
1219}
1220
1221fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1222 if debounce_ms > 0 {
1223 Some(Duration::from_millis(debounce_ms))
1224 } else {
1225 None
1226 }
1227}
1228
1229#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1230enum NextScrollCursorCenterTopBottom {
1231 #[default]
1232 Center,
1233 Top,
1234 Bottom,
1235}
1236
1237impl NextScrollCursorCenterTopBottom {
1238 fn next(&self) -> Self {
1239 match self {
1240 Self::Center => Self::Top,
1241 Self::Top => Self::Bottom,
1242 Self::Bottom => Self::Center,
1243 }
1244 }
1245}
1246
1247#[derive(Clone)]
1248pub struct EditorSnapshot {
1249 pub mode: EditorMode,
1250 show_gutter: bool,
1251 show_line_numbers: Option<bool>,
1252 show_git_diff_gutter: Option<bool>,
1253 show_code_actions: Option<bool>,
1254 show_runnables: Option<bool>,
1255 show_breakpoints: Option<bool>,
1256 git_blame_gutter_max_author_length: Option<usize>,
1257 pub display_snapshot: DisplaySnapshot,
1258 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1259 is_focused: bool,
1260 scroll_anchor: ScrollAnchor,
1261 ongoing_scroll: OngoingScroll,
1262 current_line_highlight: CurrentLineHighlight,
1263 gutter_hovered: bool,
1264}
1265
1266#[derive(Default, Debug, Clone, Copy)]
1267pub struct GutterDimensions {
1268 pub left_padding: Pixels,
1269 pub right_padding: Pixels,
1270 pub width: Pixels,
1271 pub margin: Pixels,
1272 pub git_blame_entries_width: Option<Pixels>,
1273}
1274
1275impl GutterDimensions {
1276 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1277 Self {
1278 margin: Self::default_gutter_margin(font_id, font_size, cx),
1279 ..Default::default()
1280 }
1281 }
1282
1283 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1284 -cx.text_system().descent(font_id, font_size)
1285 }
1286 /// The full width of the space taken up by the gutter.
1287 pub fn full_width(&self) -> Pixels {
1288 self.margin + self.width
1289 }
1290
1291 /// The width of the space reserved for the fold indicators,
1292 /// use alongside 'justify_end' and `gutter_width` to
1293 /// right align content with the line numbers
1294 pub fn fold_area_width(&self) -> Pixels {
1295 self.margin + self.right_padding
1296 }
1297}
1298
1299struct CharacterDimensions {
1300 em_width: Pixels,
1301 em_advance: Pixels,
1302 line_height: Pixels,
1303}
1304
1305#[derive(Debug)]
1306pub struct RemoteSelection {
1307 pub replica_id: ReplicaId,
1308 pub selection: Selection<Anchor>,
1309 pub cursor_shape: CursorShape,
1310 pub collaborator_id: CollaboratorId,
1311 pub line_mode: bool,
1312 pub user_name: Option<SharedString>,
1313 pub color: PlayerColor,
1314}
1315
1316#[derive(Clone, Debug)]
1317struct SelectionHistoryEntry {
1318 selections: Arc<[Selection<Anchor>]>,
1319 select_next_state: Option<SelectNextState>,
1320 select_prev_state: Option<SelectNextState>,
1321 add_selections_state: Option<AddSelectionsState>,
1322}
1323
1324#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1325enum SelectionHistoryMode {
1326 #[default]
1327 Normal,
1328 Undoing,
1329 Redoing,
1330 Skipping,
1331}
1332
1333#[derive(Clone, PartialEq, Eq, Hash)]
1334struct HoveredCursor {
1335 replica_id: ReplicaId,
1336 selection_id: usize,
1337}
1338
1339#[derive(Debug)]
1340/// SelectionEffects controls the side-effects of updating the selection.
1341///
1342/// The default behaviour does "what you mostly want":
1343/// - it pushes to the nav history if the cursor moved by >10 lines
1344/// - it re-triggers completion requests
1345/// - it scrolls to fit
1346///
1347/// You might want to modify these behaviours. For example when doing a "jump"
1348/// like go to definition, we always want to add to nav history; but when scrolling
1349/// in vim mode we never do.
1350///
1351/// Similarly, you might want to disable scrolling if you don't want the viewport to
1352/// move.
1353#[derive(Clone)]
1354pub struct SelectionEffects {
1355 nav_history: Option<bool>,
1356 completions: bool,
1357 scroll: Option<Autoscroll>,
1358}
1359
1360impl Default for SelectionEffects {
1361 fn default() -> Self {
1362 Self {
1363 nav_history: None,
1364 completions: true,
1365 scroll: Some(Autoscroll::fit()),
1366 }
1367 }
1368}
1369impl SelectionEffects {
1370 pub fn scroll(scroll: Autoscroll) -> Self {
1371 Self {
1372 scroll: Some(scroll),
1373 ..Default::default()
1374 }
1375 }
1376
1377 pub fn no_scroll() -> Self {
1378 Self {
1379 scroll: None,
1380 ..Default::default()
1381 }
1382 }
1383
1384 pub fn completions(self, completions: bool) -> Self {
1385 Self {
1386 completions,
1387 ..self
1388 }
1389 }
1390
1391 pub fn nav_history(self, nav_history: bool) -> Self {
1392 Self {
1393 nav_history: Some(nav_history),
1394 ..self
1395 }
1396 }
1397}
1398
1399struct DeferredSelectionEffectsState {
1400 changed: bool,
1401 effects: SelectionEffects,
1402 old_cursor_position: Anchor,
1403 history_entry: SelectionHistoryEntry,
1404}
1405
1406#[derive(Default)]
1407struct SelectionHistory {
1408 #[allow(clippy::type_complexity)]
1409 selections_by_transaction:
1410 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1411 mode: SelectionHistoryMode,
1412 undo_stack: VecDeque<SelectionHistoryEntry>,
1413 redo_stack: VecDeque<SelectionHistoryEntry>,
1414}
1415
1416impl SelectionHistory {
1417 #[track_caller]
1418 fn insert_transaction(
1419 &mut self,
1420 transaction_id: TransactionId,
1421 selections: Arc<[Selection<Anchor>]>,
1422 ) {
1423 if selections.is_empty() {
1424 log::error!(
1425 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1426 std::panic::Location::caller()
1427 );
1428 return;
1429 }
1430 self.selections_by_transaction
1431 .insert(transaction_id, (selections, None));
1432 }
1433
1434 #[allow(clippy::type_complexity)]
1435 fn transaction(
1436 &self,
1437 transaction_id: TransactionId,
1438 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1439 self.selections_by_transaction.get(&transaction_id)
1440 }
1441
1442 #[allow(clippy::type_complexity)]
1443 fn transaction_mut(
1444 &mut self,
1445 transaction_id: TransactionId,
1446 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1447 self.selections_by_transaction.get_mut(&transaction_id)
1448 }
1449
1450 fn push(&mut self, entry: SelectionHistoryEntry) {
1451 if !entry.selections.is_empty() {
1452 match self.mode {
1453 SelectionHistoryMode::Normal => {
1454 self.push_undo(entry);
1455 self.redo_stack.clear();
1456 }
1457 SelectionHistoryMode::Undoing => self.push_redo(entry),
1458 SelectionHistoryMode::Redoing => self.push_undo(entry),
1459 SelectionHistoryMode::Skipping => {}
1460 }
1461 }
1462 }
1463
1464 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1465 if self
1466 .undo_stack
1467 .back()
1468 .is_none_or(|e| e.selections != entry.selections)
1469 {
1470 self.undo_stack.push_back(entry);
1471 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1472 self.undo_stack.pop_front();
1473 }
1474 }
1475 }
1476
1477 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1478 if self
1479 .redo_stack
1480 .back()
1481 .is_none_or(|e| e.selections != entry.selections)
1482 {
1483 self.redo_stack.push_back(entry);
1484 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1485 self.redo_stack.pop_front();
1486 }
1487 }
1488 }
1489}
1490
1491#[derive(Clone, Copy)]
1492pub struct RowHighlightOptions {
1493 pub autoscroll: bool,
1494 pub include_gutter: bool,
1495}
1496
1497impl Default for RowHighlightOptions {
1498 fn default() -> Self {
1499 Self {
1500 autoscroll: Default::default(),
1501 include_gutter: true,
1502 }
1503 }
1504}
1505
1506struct RowHighlight {
1507 index: usize,
1508 range: Range<Anchor>,
1509 color: Hsla,
1510 options: RowHighlightOptions,
1511 type_id: TypeId,
1512}
1513
1514#[derive(Clone, Debug)]
1515struct AddSelectionsState {
1516 groups: Vec<AddSelectionsGroup>,
1517}
1518
1519#[derive(Clone, Debug)]
1520struct AddSelectionsGroup {
1521 above: bool,
1522 stack: Vec<usize>,
1523}
1524
1525#[derive(Clone)]
1526struct SelectNextState {
1527 query: AhoCorasick,
1528 wordwise: bool,
1529 done: bool,
1530}
1531
1532impl std::fmt::Debug for SelectNextState {
1533 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1534 f.debug_struct(std::any::type_name::<Self>())
1535 .field("wordwise", &self.wordwise)
1536 .field("done", &self.done)
1537 .finish()
1538 }
1539}
1540
1541#[derive(Debug)]
1542struct AutocloseRegion {
1543 selection_id: usize,
1544 range: Range<Anchor>,
1545 pair: BracketPair,
1546}
1547
1548#[derive(Debug)]
1549struct SnippetState {
1550 ranges: Vec<Vec<Range<Anchor>>>,
1551 active_index: usize,
1552 choices: Vec<Option<Vec<String>>>,
1553}
1554
1555#[doc(hidden)]
1556pub struct RenameState {
1557 pub range: Range<Anchor>,
1558 pub old_name: Arc<str>,
1559 pub editor: Entity<Editor>,
1560 block_id: CustomBlockId,
1561}
1562
1563struct InvalidationStack<T>(Vec<T>);
1564
1565struct RegisteredEditPredictionProvider {
1566 provider: Arc<dyn EditPredictionProviderHandle>,
1567 _subscription: Subscription,
1568}
1569
1570#[derive(Debug, PartialEq, Eq)]
1571pub struct ActiveDiagnosticGroup {
1572 pub active_range: Range<Anchor>,
1573 pub active_message: String,
1574 pub group_id: usize,
1575 pub blocks: HashSet<CustomBlockId>,
1576}
1577
1578#[derive(Debug, PartialEq, Eq)]
1579
1580pub(crate) enum ActiveDiagnostic {
1581 None,
1582 All,
1583 Group(ActiveDiagnosticGroup),
1584}
1585
1586#[derive(Serialize, Deserialize, Clone, Debug)]
1587pub struct ClipboardSelection {
1588 /// The number of bytes in this selection.
1589 pub len: usize,
1590 /// Whether this was a full-line selection.
1591 pub is_entire_line: bool,
1592 /// The indentation of the first line when this content was originally copied.
1593 pub first_line_indent: u32,
1594}
1595
1596// selections, scroll behavior, was newest selection reversed
1597type SelectSyntaxNodeHistoryState = (
1598 Box<[Selection<MultiBufferOffset>]>,
1599 SelectSyntaxNodeScrollBehavior,
1600 bool,
1601);
1602
1603#[derive(Default)]
1604struct SelectSyntaxNodeHistory {
1605 stack: Vec<SelectSyntaxNodeHistoryState>,
1606 // disable temporarily to allow changing selections without losing the stack
1607 pub disable_clearing: bool,
1608}
1609
1610impl SelectSyntaxNodeHistory {
1611 pub fn try_clear(&mut self) {
1612 if !self.disable_clearing {
1613 self.stack.clear();
1614 }
1615 }
1616
1617 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1618 self.stack.push(selection);
1619 }
1620
1621 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1622 self.stack.pop()
1623 }
1624}
1625
1626enum SelectSyntaxNodeScrollBehavior {
1627 CursorTop,
1628 FitSelection,
1629 CursorBottom,
1630}
1631
1632#[derive(Debug)]
1633pub(crate) struct NavigationData {
1634 cursor_anchor: Anchor,
1635 cursor_position: Point,
1636 scroll_anchor: ScrollAnchor,
1637 scroll_top_row: u32,
1638}
1639
1640#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1641pub enum GotoDefinitionKind {
1642 Symbol,
1643 Declaration,
1644 Type,
1645 Implementation,
1646}
1647
1648pub enum FormatTarget {
1649 Buffers(HashSet<Entity<Buffer>>),
1650 Ranges(Vec<Range<MultiBufferPoint>>),
1651}
1652
1653pub(crate) struct FocusedBlock {
1654 id: BlockId,
1655 focus_handle: WeakFocusHandle,
1656}
1657
1658#[derive(Clone, Debug)]
1659enum JumpData {
1660 MultiBufferRow {
1661 row: MultiBufferRow,
1662 line_offset_from_top: u32,
1663 },
1664 MultiBufferPoint {
1665 excerpt_id: ExcerptId,
1666 position: Point,
1667 anchor: text::Anchor,
1668 line_offset_from_top: u32,
1669 },
1670}
1671
1672pub enum MultibufferSelectionMode {
1673 First,
1674 All,
1675}
1676
1677#[derive(Clone, Copy, Debug, Default)]
1678pub struct RewrapOptions {
1679 pub override_language_settings: bool,
1680 pub preserve_existing_whitespace: bool,
1681}
1682
1683impl Editor {
1684 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1685 let buffer = cx.new(|cx| Buffer::local("", cx));
1686 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1687 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1688 }
1689
1690 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1691 let buffer = cx.new(|cx| Buffer::local("", cx));
1692 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1693 Self::new(EditorMode::full(), buffer, None, window, cx)
1694 }
1695
1696 pub fn auto_height(
1697 min_lines: usize,
1698 max_lines: usize,
1699 window: &mut Window,
1700 cx: &mut Context<Self>,
1701 ) -> Self {
1702 let buffer = cx.new(|cx| Buffer::local("", cx));
1703 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1704 Self::new(
1705 EditorMode::AutoHeight {
1706 min_lines,
1707 max_lines: Some(max_lines),
1708 },
1709 buffer,
1710 None,
1711 window,
1712 cx,
1713 )
1714 }
1715
1716 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1717 /// The editor grows as tall as needed to fit its content.
1718 pub fn auto_height_unbounded(
1719 min_lines: usize,
1720 window: &mut Window,
1721 cx: &mut Context<Self>,
1722 ) -> Self {
1723 let buffer = cx.new(|cx| Buffer::local("", cx));
1724 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1725 Self::new(
1726 EditorMode::AutoHeight {
1727 min_lines,
1728 max_lines: None,
1729 },
1730 buffer,
1731 None,
1732 window,
1733 cx,
1734 )
1735 }
1736
1737 pub fn for_buffer(
1738 buffer: Entity<Buffer>,
1739 project: Option<Entity<Project>>,
1740 window: &mut Window,
1741 cx: &mut Context<Self>,
1742 ) -> Self {
1743 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1744 Self::new(EditorMode::full(), buffer, project, window, cx)
1745 }
1746
1747 pub fn for_multibuffer(
1748 buffer: Entity<MultiBuffer>,
1749 project: Option<Entity<Project>>,
1750 window: &mut Window,
1751 cx: &mut Context<Self>,
1752 ) -> Self {
1753 Self::new(EditorMode::full(), buffer, project, window, cx)
1754 }
1755
1756 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1757 let mut clone = Self::new(
1758 self.mode.clone(),
1759 self.buffer.clone(),
1760 self.project.clone(),
1761 window,
1762 cx,
1763 );
1764 self.display_map.update(cx, |display_map, cx| {
1765 let snapshot = display_map.snapshot(cx);
1766 clone.display_map.update(cx, |display_map, cx| {
1767 display_map.set_state(&snapshot, cx);
1768 });
1769 });
1770 clone.folds_did_change(cx);
1771 clone.selections.clone_state(&self.selections);
1772 clone.scroll_manager.clone_state(&self.scroll_manager);
1773 clone.searchable = self.searchable;
1774 clone.read_only = self.read_only;
1775 clone
1776 }
1777
1778 pub fn new(
1779 mode: EditorMode,
1780 buffer: Entity<MultiBuffer>,
1781 project: Option<Entity<Project>>,
1782 window: &mut Window,
1783 cx: &mut Context<Self>,
1784 ) -> Self {
1785 Editor::new_internal(mode, buffer, project, None, window, cx)
1786 }
1787
1788 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1789 let multi_buffer = self.buffer().read(cx);
1790 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1791 let multi_buffer_visible_start = self
1792 .scroll_manager
1793 .anchor()
1794 .anchor
1795 .to_point(&multi_buffer_snapshot);
1796 let max_row = multi_buffer_snapshot.max_point().row;
1797
1798 let start_row = (multi_buffer_visible_start.row).min(max_row);
1799 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1800
1801 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1802 let outline_items = buffer
1803 .outline_items_containing(
1804 Point::new(start_row, 0)..Point::new(end_row, 0),
1805 true,
1806 self.style().map(|style| style.syntax.as_ref()),
1807 )
1808 .into_iter()
1809 .map(|outline_item| OutlineItem {
1810 depth: outline_item.depth,
1811 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1812 source_range_for_text: Anchor::range_in_buffer(
1813 *excerpt_id,
1814 outline_item.source_range_for_text,
1815 ),
1816 text: outline_item.text,
1817 highlight_ranges: outline_item.highlight_ranges,
1818 name_ranges: outline_item.name_ranges,
1819 body_range: outline_item
1820 .body_range
1821 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1822 annotation_range: outline_item
1823 .annotation_range
1824 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1825 });
1826 return Some(outline_items.collect());
1827 }
1828
1829 None
1830 }
1831
1832 fn new_internal(
1833 mode: EditorMode,
1834 multi_buffer: Entity<MultiBuffer>,
1835 project: Option<Entity<Project>>,
1836 display_map: Option<Entity<DisplayMap>>,
1837 window: &mut Window,
1838 cx: &mut Context<Self>,
1839 ) -> Self {
1840 debug_assert!(
1841 display_map.is_none() || mode.is_minimap(),
1842 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1843 );
1844
1845 let full_mode = mode.is_full();
1846 let is_minimap = mode.is_minimap();
1847 let diagnostics_max_severity = if full_mode {
1848 EditorSettings::get_global(cx)
1849 .diagnostics_max_severity
1850 .unwrap_or(DiagnosticSeverity::Hint)
1851 } else {
1852 DiagnosticSeverity::Off
1853 };
1854 let style = window.text_style();
1855 let font_size = style.font_size.to_pixels(window.rem_size());
1856 let editor = cx.entity().downgrade();
1857 let fold_placeholder = FoldPlaceholder {
1858 constrain_width: false,
1859 render: Arc::new(move |fold_id, fold_range, cx| {
1860 let editor = editor.clone();
1861 div()
1862 .id(fold_id)
1863 .bg(cx.theme().colors().ghost_element_background)
1864 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1865 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1866 .rounded_xs()
1867 .size_full()
1868 .cursor_pointer()
1869 .child("⋯")
1870 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1871 .on_click(move |_, _window, cx| {
1872 editor
1873 .update(cx, |editor, cx| {
1874 editor.unfold_ranges(
1875 &[fold_range.start..fold_range.end],
1876 true,
1877 false,
1878 cx,
1879 );
1880 cx.stop_propagation();
1881 })
1882 .ok();
1883 })
1884 .into_any()
1885 }),
1886 merge_adjacent: true,
1887 ..FoldPlaceholder::default()
1888 };
1889 let display_map = display_map.unwrap_or_else(|| {
1890 cx.new(|cx| {
1891 DisplayMap::new(
1892 multi_buffer.clone(),
1893 style.font(),
1894 font_size,
1895 None,
1896 FILE_HEADER_HEIGHT,
1897 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1898 fold_placeholder,
1899 diagnostics_max_severity,
1900 cx,
1901 )
1902 })
1903 });
1904
1905 let selections = SelectionsCollection::new();
1906
1907 let blink_manager = cx.new(|cx| {
1908 let mut blink_manager = BlinkManager::new(
1909 CURSOR_BLINK_INTERVAL,
1910 |cx| EditorSettings::get_global(cx).cursor_blink,
1911 cx,
1912 );
1913 if is_minimap {
1914 blink_manager.disable(cx);
1915 }
1916 blink_manager
1917 });
1918
1919 let soft_wrap_mode_override =
1920 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1921
1922 let mut project_subscriptions = Vec::new();
1923 if full_mode && let Some(project) = project.as_ref() {
1924 project_subscriptions.push(cx.subscribe_in(
1925 project,
1926 window,
1927 |editor, _, event, window, cx| match event {
1928 project::Event::RefreshCodeLens => {
1929 // we always query lens with actions, without storing them, always refreshing them
1930 }
1931 project::Event::RefreshInlayHints {
1932 server_id,
1933 request_id,
1934 } => {
1935 editor.refresh_inlay_hints(
1936 InlayHintRefreshReason::RefreshRequested {
1937 server_id: *server_id,
1938 request_id: *request_id,
1939 },
1940 cx,
1941 );
1942 }
1943 project::Event::LanguageServerRemoved(..) => {
1944 if editor.tasks_update_task.is_none() {
1945 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1946 }
1947 editor.registered_buffers.clear();
1948 editor.register_visible_buffers(cx);
1949 }
1950 project::Event::LanguageServerAdded(..) => {
1951 if editor.tasks_update_task.is_none() {
1952 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1953 }
1954 }
1955 project::Event::SnippetEdit(id, snippet_edits) => {
1956 // todo(lw): Non singletons
1957 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
1958 let snapshot = buffer.read(cx).snapshot();
1959 let focus_handle = editor.focus_handle(cx);
1960 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
1961 for (range, snippet) in snippet_edits {
1962 let buffer_range =
1963 language::range_from_lsp(*range).to_offset(&snapshot);
1964 editor
1965 .insert_snippet(
1966 &[MultiBufferOffset(buffer_range.start)
1967 ..MultiBufferOffset(buffer_range.end)],
1968 snippet.clone(),
1969 window,
1970 cx,
1971 )
1972 .ok();
1973 }
1974 }
1975 }
1976 }
1977 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1978 let buffer_id = *buffer_id;
1979 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1980 editor.register_buffer(buffer_id, cx);
1981 editor.update_lsp_data(Some(buffer_id), window, cx);
1982 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1983 refresh_linked_ranges(editor, window, cx);
1984 editor.refresh_code_actions(window, cx);
1985 editor.refresh_document_highlights(cx);
1986 }
1987 }
1988
1989 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1990 let Some(workspace) = editor.workspace() else {
1991 return;
1992 };
1993 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1994 else {
1995 return;
1996 };
1997
1998 if active_editor.entity_id() == cx.entity_id() {
1999 let entity_id = cx.entity_id();
2000 workspace.update(cx, |this, cx| {
2001 this.panes_mut()
2002 .iter_mut()
2003 .filter(|pane| pane.entity_id() != entity_id)
2004 .for_each(|p| {
2005 p.update(cx, |pane, _| {
2006 pane.nav_history_mut().rename_item(
2007 entity_id,
2008 project_path.clone(),
2009 abs_path.clone().into(),
2010 );
2011 })
2012 });
2013 });
2014 let edited_buffers_already_open = {
2015 let other_editors: Vec<Entity<Editor>> = workspace
2016 .read(cx)
2017 .panes()
2018 .iter()
2019 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2020 .filter(|editor| editor.entity_id() != cx.entity_id())
2021 .collect();
2022
2023 transaction.0.keys().all(|buffer| {
2024 other_editors.iter().any(|editor| {
2025 let multi_buffer = editor.read(cx).buffer();
2026 multi_buffer.read(cx).is_singleton()
2027 && multi_buffer.read(cx).as_singleton().map_or(
2028 false,
2029 |singleton| {
2030 singleton.entity_id() == buffer.entity_id()
2031 },
2032 )
2033 })
2034 })
2035 };
2036 if !edited_buffers_already_open {
2037 let workspace = workspace.downgrade();
2038 let transaction = transaction.clone();
2039 cx.defer_in(window, move |_, window, cx| {
2040 cx.spawn_in(window, async move |editor, cx| {
2041 Self::open_project_transaction(
2042 &editor,
2043 workspace,
2044 transaction,
2045 "Rename".to_string(),
2046 cx,
2047 )
2048 .await
2049 .ok()
2050 })
2051 .detach();
2052 });
2053 }
2054 }
2055 }
2056
2057 _ => {}
2058 },
2059 ));
2060 if let Some(task_inventory) = project
2061 .read(cx)
2062 .task_store()
2063 .read(cx)
2064 .task_inventory()
2065 .cloned()
2066 {
2067 project_subscriptions.push(cx.observe_in(
2068 &task_inventory,
2069 window,
2070 |editor, _, window, cx| {
2071 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2072 },
2073 ));
2074 };
2075
2076 project_subscriptions.push(cx.subscribe_in(
2077 &project.read(cx).breakpoint_store(),
2078 window,
2079 |editor, _, event, window, cx| match event {
2080 BreakpointStoreEvent::ClearDebugLines => {
2081 editor.clear_row_highlights::<ActiveDebugLine>();
2082 editor.refresh_inline_values(cx);
2083 }
2084 BreakpointStoreEvent::SetDebugLine => {
2085 if editor.go_to_active_debug_line(window, cx) {
2086 cx.stop_propagation();
2087 }
2088
2089 editor.refresh_inline_values(cx);
2090 }
2091 _ => {}
2092 },
2093 ));
2094 let git_store = project.read(cx).git_store().clone();
2095 let project = project.clone();
2096 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2097 if let GitStoreEvent::RepositoryAdded = event {
2098 this.load_diff_task = Some(
2099 update_uncommitted_diff_for_buffer(
2100 cx.entity(),
2101 &project,
2102 this.buffer.read(cx).all_buffers(),
2103 this.buffer.clone(),
2104 cx,
2105 )
2106 .shared(),
2107 );
2108 }
2109 }));
2110 }
2111
2112 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2113
2114 let inlay_hint_settings =
2115 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2116 let focus_handle = cx.focus_handle();
2117 if !is_minimap {
2118 cx.on_focus(&focus_handle, window, Self::handle_focus)
2119 .detach();
2120 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2121 .detach();
2122 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2123 .detach();
2124 cx.on_blur(&focus_handle, window, Self::handle_blur)
2125 .detach();
2126 cx.observe_pending_input(window, Self::observe_pending_input)
2127 .detach();
2128 }
2129
2130 let show_indent_guides =
2131 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2132 Some(false)
2133 } else {
2134 None
2135 };
2136
2137 let breakpoint_store = match (&mode, project.as_ref()) {
2138 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2139 _ => None,
2140 };
2141
2142 let mut code_action_providers = Vec::new();
2143 let mut load_uncommitted_diff = None;
2144 if let Some(project) = project.clone() {
2145 load_uncommitted_diff = Some(
2146 update_uncommitted_diff_for_buffer(
2147 cx.entity(),
2148 &project,
2149 multi_buffer.read(cx).all_buffers(),
2150 multi_buffer.clone(),
2151 cx,
2152 )
2153 .shared(),
2154 );
2155 code_action_providers.push(Rc::new(project) as Rc<_>);
2156 }
2157
2158 let mut editor = Self {
2159 focus_handle,
2160 show_cursor_when_unfocused: false,
2161 last_focused_descendant: None,
2162 buffer: multi_buffer.clone(),
2163 display_map: display_map.clone(),
2164 placeholder_display_map: None,
2165 selections,
2166 scroll_manager: ScrollManager::new(cx),
2167 columnar_selection_state: None,
2168 add_selections_state: None,
2169 select_next_state: None,
2170 select_prev_state: None,
2171 selection_history: SelectionHistory::default(),
2172 defer_selection_effects: false,
2173 deferred_selection_effects_state: None,
2174 autoclose_regions: Vec::new(),
2175 snippet_stack: InvalidationStack::default(),
2176 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2177 ime_transaction: None,
2178 active_diagnostics: ActiveDiagnostic::None,
2179 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2180 inline_diagnostics_update: Task::ready(()),
2181 inline_diagnostics: Vec::new(),
2182 soft_wrap_mode_override,
2183 diagnostics_max_severity,
2184 hard_wrap: None,
2185 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2186 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2187 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2188 project,
2189 blink_manager: blink_manager.clone(),
2190 show_local_selections: true,
2191 show_scrollbars: ScrollbarAxes {
2192 horizontal: full_mode,
2193 vertical: full_mode,
2194 },
2195 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2196 offset_content: !matches!(mode, EditorMode::SingleLine),
2197 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2198 show_gutter: full_mode,
2199 show_line_numbers: (!full_mode).then_some(false),
2200 use_relative_line_numbers: None,
2201 disable_expand_excerpt_buttons: !full_mode,
2202 show_git_diff_gutter: None,
2203 show_code_actions: None,
2204 show_runnables: None,
2205 show_breakpoints: None,
2206 show_wrap_guides: None,
2207 show_indent_guides,
2208 buffers_with_disabled_indent_guides: HashSet::default(),
2209 highlight_order: 0,
2210 highlighted_rows: HashMap::default(),
2211 background_highlights: HashMap::default(),
2212 gutter_highlights: HashMap::default(),
2213 scrollbar_marker_state: ScrollbarMarkerState::default(),
2214 active_indent_guides_state: ActiveIndentGuidesState::default(),
2215 nav_history: None,
2216 context_menu: RefCell::new(None),
2217 context_menu_options: None,
2218 mouse_context_menu: None,
2219 completion_tasks: Vec::new(),
2220 inline_blame_popover: None,
2221 inline_blame_popover_show_task: None,
2222 signature_help_state: SignatureHelpState::default(),
2223 auto_signature_help: None,
2224 find_all_references_task_sources: Vec::new(),
2225 next_completion_id: 0,
2226 next_inlay_id: 0,
2227 code_action_providers,
2228 available_code_actions: None,
2229 code_actions_task: None,
2230 quick_selection_highlight_task: None,
2231 debounced_selection_highlight_task: None,
2232 document_highlights_task: None,
2233 linked_editing_range_task: None,
2234 pending_rename: None,
2235 searchable: !is_minimap,
2236 cursor_shape: EditorSettings::get_global(cx)
2237 .cursor_shape
2238 .unwrap_or_default(),
2239 current_line_highlight: None,
2240 autoindent_mode: Some(AutoindentMode::EachLine),
2241 collapse_matches: false,
2242 workspace: None,
2243 input_enabled: !is_minimap,
2244 use_modal_editing: full_mode,
2245 read_only: is_minimap,
2246 use_autoclose: true,
2247 use_auto_surround: true,
2248 auto_replace_emoji_shortcode: false,
2249 jsx_tag_auto_close_enabled_in_any_buffer: false,
2250 leader_id: None,
2251 remote_id: None,
2252 hover_state: HoverState::default(),
2253 pending_mouse_down: None,
2254 hovered_link_state: None,
2255 edit_prediction_provider: None,
2256 active_edit_prediction: None,
2257 stale_edit_prediction_in_menu: None,
2258 edit_prediction_preview: EditPredictionPreview::Inactive {
2259 released_too_fast: false,
2260 },
2261 inline_diagnostics_enabled: full_mode,
2262 diagnostics_enabled: full_mode,
2263 word_completions_enabled: full_mode,
2264 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2265 gutter_hovered: false,
2266 pixel_position_of_newest_cursor: None,
2267 last_bounds: None,
2268 last_position_map: None,
2269 expect_bounds_change: None,
2270 gutter_dimensions: GutterDimensions::default(),
2271 style: None,
2272 show_cursor_names: false,
2273 hovered_cursors: HashMap::default(),
2274 next_editor_action_id: EditorActionId::default(),
2275 editor_actions: Rc::default(),
2276 edit_predictions_hidden_for_vim_mode: false,
2277 show_edit_predictions_override: None,
2278 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2279 edit_prediction_settings: EditPredictionSettings::Disabled,
2280 edit_prediction_indent_conflict: false,
2281 edit_prediction_requires_modifier_in_indent_conflict: true,
2282 custom_context_menu: None,
2283 show_git_blame_gutter: false,
2284 show_git_blame_inline: false,
2285 show_selection_menu: None,
2286 show_git_blame_inline_delay_task: None,
2287 git_blame_inline_enabled: full_mode
2288 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2289 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2290 buffer_serialization: is_minimap.not().then(|| {
2291 BufferSerialization::new(
2292 ProjectSettings::get_global(cx)
2293 .session
2294 .restore_unsaved_buffers,
2295 )
2296 }),
2297 blame: None,
2298 blame_subscription: None,
2299 tasks: BTreeMap::default(),
2300
2301 breakpoint_store,
2302 gutter_breakpoint_indicator: (None, None),
2303 hovered_diff_hunk_row: None,
2304 _subscriptions: (!is_minimap)
2305 .then(|| {
2306 vec![
2307 cx.observe(&multi_buffer, Self::on_buffer_changed),
2308 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2309 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2310 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2311 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2312 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2313 cx.observe_window_activation(window, |editor, window, cx| {
2314 let active = window.is_window_active();
2315 editor.blink_manager.update(cx, |blink_manager, cx| {
2316 if active {
2317 blink_manager.enable(cx);
2318 } else {
2319 blink_manager.disable(cx);
2320 }
2321 });
2322 if active {
2323 editor.show_mouse_cursor(cx);
2324 }
2325 }),
2326 ]
2327 })
2328 .unwrap_or_default(),
2329 tasks_update_task: None,
2330 pull_diagnostics_task: Task::ready(()),
2331 pull_diagnostics_background_task: Task::ready(()),
2332 colors: None,
2333 refresh_colors_task: Task::ready(()),
2334 inlay_hints: None,
2335 next_color_inlay_id: 0,
2336 post_scroll_update: Task::ready(()),
2337 linked_edit_ranges: Default::default(),
2338 in_project_search: false,
2339 previous_search_ranges: None,
2340 breadcrumb_header: None,
2341 focused_block: None,
2342 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2343 addons: HashMap::default(),
2344 registered_buffers: HashMap::default(),
2345 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2346 selection_mark_mode: false,
2347 toggle_fold_multiple_buffers: Task::ready(()),
2348 serialize_selections: Task::ready(()),
2349 serialize_folds: Task::ready(()),
2350 text_style_refinement: None,
2351 load_diff_task: load_uncommitted_diff,
2352 temporary_diff_override: false,
2353 mouse_cursor_hidden: false,
2354 minimap: None,
2355 hide_mouse_mode: EditorSettings::get_global(cx)
2356 .hide_mouse
2357 .unwrap_or_default(),
2358 change_list: ChangeList::new(),
2359 mode,
2360 selection_drag_state: SelectionDragState::None,
2361 folding_newlines: Task::ready(()),
2362 lookup_key: None,
2363 select_next_is_case_sensitive: None,
2364 applicable_language_settings: HashMap::default(),
2365 accent_data: None,
2366 fetched_tree_sitter_chunks: HashMap::default(),
2367 use_base_text_line_numbers: false,
2368 };
2369
2370 if is_minimap {
2371 return editor;
2372 }
2373
2374 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2375 editor.accent_data = editor.fetch_accent_data(cx);
2376
2377 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2378 editor
2379 ._subscriptions
2380 .push(cx.observe(breakpoints, |_, _, cx| {
2381 cx.notify();
2382 }));
2383 }
2384 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2385 editor._subscriptions.extend(project_subscriptions);
2386
2387 editor._subscriptions.push(cx.subscribe_in(
2388 &cx.entity(),
2389 window,
2390 |editor, _, e: &EditorEvent, window, cx| match e {
2391 EditorEvent::ScrollPositionChanged { local, .. } => {
2392 if *local {
2393 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2394 editor.inline_blame_popover.take();
2395 let new_anchor = editor.scroll_manager.anchor();
2396 let snapshot = editor.snapshot(window, cx);
2397 editor.update_restoration_data(cx, move |data| {
2398 data.scroll_position = (
2399 new_anchor.top_row(snapshot.buffer_snapshot()),
2400 new_anchor.offset,
2401 );
2402 });
2403
2404 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2405 cx.background_executor()
2406 .timer(Duration::from_millis(50))
2407 .await;
2408 editor
2409 .update_in(cx, |editor, window, cx| {
2410 editor.register_visible_buffers(cx);
2411 editor.refresh_colors_for_visible_range(None, window, cx);
2412 editor.refresh_inlay_hints(
2413 InlayHintRefreshReason::NewLinesShown,
2414 cx,
2415 );
2416 editor.colorize_brackets(false, cx);
2417 })
2418 .ok();
2419 });
2420 }
2421 }
2422 EditorEvent::Edited { .. } => {
2423 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2424 .map(|vim_mode| vim_mode.0)
2425 .unwrap_or(false);
2426 if !vim_mode {
2427 let display_map = editor.display_snapshot(cx);
2428 let selections = editor.selections.all_adjusted_display(&display_map);
2429 let pop_state = editor
2430 .change_list
2431 .last()
2432 .map(|previous| {
2433 previous.len() == selections.len()
2434 && previous.iter().enumerate().all(|(ix, p)| {
2435 p.to_display_point(&display_map).row()
2436 == selections[ix].head().row()
2437 })
2438 })
2439 .unwrap_or(false);
2440 let new_positions = selections
2441 .into_iter()
2442 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2443 .collect();
2444 editor
2445 .change_list
2446 .push_to_change_list(pop_state, new_positions);
2447 }
2448 }
2449 _ => (),
2450 },
2451 ));
2452
2453 if let Some(dap_store) = editor
2454 .project
2455 .as_ref()
2456 .map(|project| project.read(cx).dap_store())
2457 {
2458 let weak_editor = cx.weak_entity();
2459
2460 editor
2461 ._subscriptions
2462 .push(
2463 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2464 let session_entity = cx.entity();
2465 weak_editor
2466 .update(cx, |editor, cx| {
2467 editor._subscriptions.push(
2468 cx.subscribe(&session_entity, Self::on_debug_session_event),
2469 );
2470 })
2471 .ok();
2472 }),
2473 );
2474
2475 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2476 editor
2477 ._subscriptions
2478 .push(cx.subscribe(&session, Self::on_debug_session_event));
2479 }
2480 }
2481
2482 // skip adding the initial selection to selection history
2483 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2484 editor.end_selection(window, cx);
2485 editor.selection_history.mode = SelectionHistoryMode::Normal;
2486
2487 editor.scroll_manager.show_scrollbars(window, cx);
2488 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2489
2490 if full_mode {
2491 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2492 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2493
2494 if editor.git_blame_inline_enabled {
2495 editor.start_git_blame_inline(false, window, cx);
2496 }
2497
2498 editor.go_to_active_debug_line(window, cx);
2499
2500 editor.minimap =
2501 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2502 editor.colors = Some(LspColorData::new(cx));
2503 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2504
2505 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2506 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2507 }
2508 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2509 }
2510
2511 editor
2512 }
2513
2514 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2515 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2516 }
2517
2518 pub fn deploy_mouse_context_menu(
2519 &mut self,
2520 position: gpui::Point<Pixels>,
2521 context_menu: Entity<ContextMenu>,
2522 window: &mut Window,
2523 cx: &mut Context<Self>,
2524 ) {
2525 self.mouse_context_menu = Some(MouseContextMenu::new(
2526 self,
2527 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2528 context_menu,
2529 window,
2530 cx,
2531 ));
2532 }
2533
2534 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2535 self.mouse_context_menu
2536 .as_ref()
2537 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2538 }
2539
2540 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2541 if self
2542 .selections
2543 .pending_anchor()
2544 .is_some_and(|pending_selection| {
2545 let snapshot = self.buffer().read(cx).snapshot(cx);
2546 pending_selection.range().includes(range, &snapshot)
2547 })
2548 {
2549 return true;
2550 }
2551
2552 self.selections
2553 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2554 .into_iter()
2555 .any(|selection| {
2556 // This is needed to cover a corner case, if we just check for an existing
2557 // selection in the fold range, having a cursor at the start of the fold
2558 // marks it as selected. Non-empty selections don't cause this.
2559 let length = selection.end - selection.start;
2560 length > 0
2561 })
2562 }
2563
2564 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2565 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2566 }
2567
2568 fn key_context_internal(
2569 &self,
2570 has_active_edit_prediction: bool,
2571 window: &mut Window,
2572 cx: &mut App,
2573 ) -> KeyContext {
2574 let mut key_context = KeyContext::new_with_defaults();
2575 key_context.add("Editor");
2576 let mode = match self.mode {
2577 EditorMode::SingleLine => "single_line",
2578 EditorMode::AutoHeight { .. } => "auto_height",
2579 EditorMode::Minimap { .. } => "minimap",
2580 EditorMode::Full { .. } => "full",
2581 };
2582
2583 if EditorSettings::jupyter_enabled(cx) {
2584 key_context.add("jupyter");
2585 }
2586
2587 key_context.set("mode", mode);
2588 if self.pending_rename.is_some() {
2589 key_context.add("renaming");
2590 }
2591
2592 if let Some(snippet_stack) = self.snippet_stack.last() {
2593 key_context.add("in_snippet");
2594
2595 if snippet_stack.active_index > 0 {
2596 key_context.add("has_previous_tabstop");
2597 }
2598
2599 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2600 key_context.add("has_next_tabstop");
2601 }
2602 }
2603
2604 match self.context_menu.borrow().as_ref() {
2605 Some(CodeContextMenu::Completions(menu)) => {
2606 if menu.visible() {
2607 key_context.add("menu");
2608 key_context.add("showing_completions");
2609 }
2610 }
2611 Some(CodeContextMenu::CodeActions(menu)) => {
2612 if menu.visible() {
2613 key_context.add("menu");
2614 key_context.add("showing_code_actions")
2615 }
2616 }
2617 None => {}
2618 }
2619
2620 if self.signature_help_state.has_multiple_signatures() {
2621 key_context.add("showing_signature_help");
2622 }
2623
2624 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2625 if !self.focus_handle(cx).contains_focused(window, cx)
2626 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2627 {
2628 for addon in self.addons.values() {
2629 addon.extend_key_context(&mut key_context, cx)
2630 }
2631 }
2632
2633 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2634 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2635 Some(
2636 file.full_path(cx)
2637 .extension()?
2638 .to_string_lossy()
2639 .into_owned(),
2640 )
2641 }) {
2642 key_context.set("extension", extension);
2643 }
2644 } else {
2645 key_context.add("multibuffer");
2646 }
2647
2648 if has_active_edit_prediction {
2649 if self.edit_prediction_in_conflict() {
2650 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2651 } else {
2652 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2653 key_context.add("copilot_suggestion");
2654 }
2655 }
2656
2657 if self.selection_mark_mode {
2658 key_context.add("selection_mode");
2659 }
2660
2661 let disjoint = self.selections.disjoint_anchors();
2662 let snapshot = self.snapshot(window, cx);
2663 let snapshot = snapshot.buffer_snapshot();
2664 if self.mode == EditorMode::SingleLine
2665 && let [selection] = disjoint
2666 && selection.start == selection.end
2667 && selection.end.to_offset(snapshot) == snapshot.len()
2668 {
2669 key_context.add("end_of_input");
2670 }
2671
2672 if self.has_any_expanded_diff_hunks(cx) {
2673 key_context.add("diffs_expanded");
2674 }
2675
2676 key_context
2677 }
2678
2679 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2680 self.last_bounds.as_ref()
2681 }
2682
2683 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2684 if self.mouse_cursor_hidden {
2685 self.mouse_cursor_hidden = false;
2686 cx.notify();
2687 }
2688 }
2689
2690 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2691 let hide_mouse_cursor = match origin {
2692 HideMouseCursorOrigin::TypingAction => {
2693 matches!(
2694 self.hide_mouse_mode,
2695 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2696 )
2697 }
2698 HideMouseCursorOrigin::MovementAction => {
2699 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2700 }
2701 };
2702 if self.mouse_cursor_hidden != hide_mouse_cursor {
2703 self.mouse_cursor_hidden = hide_mouse_cursor;
2704 cx.notify();
2705 }
2706 }
2707
2708 pub fn edit_prediction_in_conflict(&self) -> bool {
2709 if !self.show_edit_predictions_in_menu() {
2710 return false;
2711 }
2712
2713 let showing_completions = self
2714 .context_menu
2715 .borrow()
2716 .as_ref()
2717 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2718
2719 showing_completions
2720 || self.edit_prediction_requires_modifier()
2721 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2722 // bindings to insert tab characters.
2723 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2724 }
2725
2726 pub fn accept_edit_prediction_keybind(
2727 &self,
2728 accept_partial: bool,
2729 window: &mut Window,
2730 cx: &mut App,
2731 ) -> AcceptEditPredictionBinding {
2732 let key_context = self.key_context_internal(true, window, cx);
2733 let in_conflict = self.edit_prediction_in_conflict();
2734
2735 let bindings = if accept_partial {
2736 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2737 } else {
2738 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2739 };
2740
2741 // TODO: if the binding contains multiple keystrokes, display all of them, not
2742 // just the first one.
2743 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2744 !in_conflict
2745 || binding
2746 .keystrokes()
2747 .first()
2748 .is_some_and(|keystroke| keystroke.modifiers().modified())
2749 }))
2750 }
2751
2752 pub fn new_file(
2753 workspace: &mut Workspace,
2754 _: &workspace::NewFile,
2755 window: &mut Window,
2756 cx: &mut Context<Workspace>,
2757 ) {
2758 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2759 "Failed to create buffer",
2760 window,
2761 cx,
2762 |e, _, _| match e.error_code() {
2763 ErrorCode::RemoteUpgradeRequired => Some(format!(
2764 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2765 e.error_tag("required").unwrap_or("the latest version")
2766 )),
2767 _ => None,
2768 },
2769 );
2770 }
2771
2772 pub fn new_in_workspace(
2773 workspace: &mut Workspace,
2774 window: &mut Window,
2775 cx: &mut Context<Workspace>,
2776 ) -> Task<Result<Entity<Editor>>> {
2777 let project = workspace.project().clone();
2778 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2779
2780 cx.spawn_in(window, async move |workspace, cx| {
2781 let buffer = create.await?;
2782 workspace.update_in(cx, |workspace, window, cx| {
2783 let editor =
2784 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2785 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2786 editor
2787 })
2788 })
2789 }
2790
2791 fn new_file_vertical(
2792 workspace: &mut Workspace,
2793 _: &workspace::NewFileSplitVertical,
2794 window: &mut Window,
2795 cx: &mut Context<Workspace>,
2796 ) {
2797 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2798 }
2799
2800 fn new_file_horizontal(
2801 workspace: &mut Workspace,
2802 _: &workspace::NewFileSplitHorizontal,
2803 window: &mut Window,
2804 cx: &mut Context<Workspace>,
2805 ) {
2806 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2807 }
2808
2809 fn new_file_split(
2810 workspace: &mut Workspace,
2811 action: &workspace::NewFileSplit,
2812 window: &mut Window,
2813 cx: &mut Context<Workspace>,
2814 ) {
2815 Self::new_file_in_direction(workspace, action.0, window, cx)
2816 }
2817
2818 fn new_file_in_direction(
2819 workspace: &mut Workspace,
2820 direction: SplitDirection,
2821 window: &mut Window,
2822 cx: &mut Context<Workspace>,
2823 ) {
2824 let project = workspace.project().clone();
2825 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2826
2827 cx.spawn_in(window, async move |workspace, cx| {
2828 let buffer = create.await?;
2829 workspace.update_in(cx, move |workspace, window, cx| {
2830 workspace.split_item(
2831 direction,
2832 Box::new(
2833 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2834 ),
2835 window,
2836 cx,
2837 )
2838 })?;
2839 anyhow::Ok(())
2840 })
2841 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2842 match e.error_code() {
2843 ErrorCode::RemoteUpgradeRequired => Some(format!(
2844 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2845 e.error_tag("required").unwrap_or("the latest version")
2846 )),
2847 _ => None,
2848 }
2849 });
2850 }
2851
2852 pub fn leader_id(&self) -> Option<CollaboratorId> {
2853 self.leader_id
2854 }
2855
2856 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2857 &self.buffer
2858 }
2859
2860 pub fn project(&self) -> Option<&Entity<Project>> {
2861 self.project.as_ref()
2862 }
2863
2864 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2865 self.workspace.as_ref()?.0.upgrade()
2866 }
2867
2868 /// Returns the workspace serialization ID if this editor should be serialized.
2869 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2870 self.workspace
2871 .as_ref()
2872 .filter(|_| self.should_serialize_buffer())
2873 .and_then(|workspace| workspace.1)
2874 }
2875
2876 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2877 self.buffer().read(cx).title(cx)
2878 }
2879
2880 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2881 let git_blame_gutter_max_author_length = self
2882 .render_git_blame_gutter(cx)
2883 .then(|| {
2884 if let Some(blame) = self.blame.as_ref() {
2885 let max_author_length =
2886 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2887 Some(max_author_length)
2888 } else {
2889 None
2890 }
2891 })
2892 .flatten();
2893
2894 EditorSnapshot {
2895 mode: self.mode.clone(),
2896 show_gutter: self.show_gutter,
2897 show_line_numbers: self.show_line_numbers,
2898 show_git_diff_gutter: self.show_git_diff_gutter,
2899 show_code_actions: self.show_code_actions,
2900 show_runnables: self.show_runnables,
2901 show_breakpoints: self.show_breakpoints,
2902 git_blame_gutter_max_author_length,
2903 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2904 placeholder_display_snapshot: self
2905 .placeholder_display_map
2906 .as_ref()
2907 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2908 scroll_anchor: self.scroll_manager.anchor(),
2909 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2910 is_focused: self.focus_handle.is_focused(window),
2911 current_line_highlight: self
2912 .current_line_highlight
2913 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2914 gutter_hovered: self.gutter_hovered,
2915 }
2916 }
2917
2918 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2919 self.buffer.read(cx).language_at(point, cx)
2920 }
2921
2922 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2923 self.buffer.read(cx).read(cx).file_at(point).cloned()
2924 }
2925
2926 pub fn active_excerpt(
2927 &self,
2928 cx: &App,
2929 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2930 self.buffer
2931 .read(cx)
2932 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2933 }
2934
2935 pub fn mode(&self) -> &EditorMode {
2936 &self.mode
2937 }
2938
2939 pub fn set_mode(&mut self, mode: EditorMode) {
2940 self.mode = mode;
2941 }
2942
2943 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2944 self.collaboration_hub.as_deref()
2945 }
2946
2947 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2948 self.collaboration_hub = Some(hub);
2949 }
2950
2951 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2952 self.in_project_search = in_project_search;
2953 }
2954
2955 pub fn set_custom_context_menu(
2956 &mut self,
2957 f: impl 'static
2958 + Fn(
2959 &mut Self,
2960 DisplayPoint,
2961 &mut Window,
2962 &mut Context<Self>,
2963 ) -> Option<Entity<ui::ContextMenu>>,
2964 ) {
2965 self.custom_context_menu = Some(Box::new(f))
2966 }
2967
2968 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2969 self.completion_provider = provider;
2970 }
2971
2972 #[cfg(any(test, feature = "test-support"))]
2973 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2974 self.completion_provider.clone()
2975 }
2976
2977 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2978 self.semantics_provider.clone()
2979 }
2980
2981 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2982 self.semantics_provider = provider;
2983 }
2984
2985 pub fn set_edit_prediction_provider<T>(
2986 &mut self,
2987 provider: Option<Entity<T>>,
2988 window: &mut Window,
2989 cx: &mut Context<Self>,
2990 ) where
2991 T: EditPredictionProvider,
2992 {
2993 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2994 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2995 if this.focus_handle.is_focused(window) {
2996 this.update_visible_edit_prediction(window, cx);
2997 }
2998 }),
2999 provider: Arc::new(provider),
3000 });
3001 self.update_edit_prediction_settings(cx);
3002 self.refresh_edit_prediction(false, false, window, cx);
3003 }
3004
3005 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3006 self.placeholder_display_map
3007 .as_ref()
3008 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3009 }
3010
3011 pub fn set_placeholder_text(
3012 &mut self,
3013 placeholder_text: &str,
3014 window: &mut Window,
3015 cx: &mut Context<Self>,
3016 ) {
3017 let multibuffer = cx
3018 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3019
3020 let style = window.text_style();
3021
3022 self.placeholder_display_map = Some(cx.new(|cx| {
3023 DisplayMap::new(
3024 multibuffer,
3025 style.font(),
3026 style.font_size.to_pixels(window.rem_size()),
3027 None,
3028 FILE_HEADER_HEIGHT,
3029 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3030 Default::default(),
3031 DiagnosticSeverity::Off,
3032 cx,
3033 )
3034 }));
3035 cx.notify();
3036 }
3037
3038 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3039 self.cursor_shape = cursor_shape;
3040
3041 // Disrupt blink for immediate user feedback that the cursor shape has changed
3042 self.blink_manager.update(cx, BlinkManager::show_cursor);
3043
3044 cx.notify();
3045 }
3046
3047 pub fn cursor_shape(&self) -> CursorShape {
3048 self.cursor_shape
3049 }
3050
3051 pub fn set_current_line_highlight(
3052 &mut self,
3053 current_line_highlight: Option<CurrentLineHighlight>,
3054 ) {
3055 self.current_line_highlight = current_line_highlight;
3056 }
3057
3058 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3059 self.collapse_matches = collapse_matches;
3060 }
3061
3062 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3063 if self.collapse_matches {
3064 return range.start..range.start;
3065 }
3066 range.clone()
3067 }
3068
3069 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3070 self.display_map.read(cx).clip_at_line_ends
3071 }
3072
3073 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3074 if self.display_map.read(cx).clip_at_line_ends != clip {
3075 self.display_map
3076 .update(cx, |map, _| map.clip_at_line_ends = clip);
3077 }
3078 }
3079
3080 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3081 self.input_enabled = input_enabled;
3082 }
3083
3084 pub fn set_edit_predictions_hidden_for_vim_mode(
3085 &mut self,
3086 hidden: bool,
3087 window: &mut Window,
3088 cx: &mut Context<Self>,
3089 ) {
3090 if hidden != self.edit_predictions_hidden_for_vim_mode {
3091 self.edit_predictions_hidden_for_vim_mode = hidden;
3092 if hidden {
3093 self.update_visible_edit_prediction(window, cx);
3094 } else {
3095 self.refresh_edit_prediction(true, false, window, cx);
3096 }
3097 }
3098 }
3099
3100 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3101 self.menu_edit_predictions_policy = value;
3102 }
3103
3104 pub fn set_autoindent(&mut self, autoindent: bool) {
3105 if autoindent {
3106 self.autoindent_mode = Some(AutoindentMode::EachLine);
3107 } else {
3108 self.autoindent_mode = None;
3109 }
3110 }
3111
3112 pub fn read_only(&self, cx: &App) -> bool {
3113 self.read_only || self.buffer.read(cx).read_only()
3114 }
3115
3116 pub fn set_read_only(&mut self, read_only: bool) {
3117 self.read_only = read_only;
3118 }
3119
3120 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3121 self.use_autoclose = autoclose;
3122 }
3123
3124 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3125 self.use_auto_surround = auto_surround;
3126 }
3127
3128 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3129 self.auto_replace_emoji_shortcode = auto_replace;
3130 }
3131
3132 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3133 self.buffer_serialization = should_serialize.then(|| {
3134 BufferSerialization::new(
3135 ProjectSettings::get_global(cx)
3136 .session
3137 .restore_unsaved_buffers,
3138 )
3139 })
3140 }
3141
3142 fn should_serialize_buffer(&self) -> bool {
3143 self.buffer_serialization.is_some()
3144 }
3145
3146 pub fn toggle_edit_predictions(
3147 &mut self,
3148 _: &ToggleEditPrediction,
3149 window: &mut Window,
3150 cx: &mut Context<Self>,
3151 ) {
3152 if self.show_edit_predictions_override.is_some() {
3153 self.set_show_edit_predictions(None, window, cx);
3154 } else {
3155 let show_edit_predictions = !self.edit_predictions_enabled();
3156 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3157 }
3158 }
3159
3160 pub fn set_show_edit_predictions(
3161 &mut self,
3162 show_edit_predictions: Option<bool>,
3163 window: &mut Window,
3164 cx: &mut Context<Self>,
3165 ) {
3166 self.show_edit_predictions_override = show_edit_predictions;
3167 self.update_edit_prediction_settings(cx);
3168
3169 if let Some(false) = show_edit_predictions {
3170 self.discard_edit_prediction(false, cx);
3171 } else {
3172 self.refresh_edit_prediction(false, true, window, cx);
3173 }
3174 }
3175
3176 fn edit_predictions_disabled_in_scope(
3177 &self,
3178 buffer: &Entity<Buffer>,
3179 buffer_position: language::Anchor,
3180 cx: &App,
3181 ) -> bool {
3182 let snapshot = buffer.read(cx).snapshot();
3183 let settings = snapshot.settings_at(buffer_position, cx);
3184
3185 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3186 return false;
3187 };
3188
3189 scope.override_name().is_some_and(|scope_name| {
3190 settings
3191 .edit_predictions_disabled_in
3192 .iter()
3193 .any(|s| s == scope_name)
3194 })
3195 }
3196
3197 pub fn set_use_modal_editing(&mut self, to: bool) {
3198 self.use_modal_editing = to;
3199 }
3200
3201 pub fn use_modal_editing(&self) -> bool {
3202 self.use_modal_editing
3203 }
3204
3205 fn selections_did_change(
3206 &mut self,
3207 local: bool,
3208 old_cursor_position: &Anchor,
3209 effects: SelectionEffects,
3210 window: &mut Window,
3211 cx: &mut Context<Self>,
3212 ) {
3213 window.invalidate_character_coordinates();
3214
3215 // Copy selections to primary selection buffer
3216 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3217 if local {
3218 let selections = self
3219 .selections
3220 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3221 let buffer_handle = self.buffer.read(cx).read(cx);
3222
3223 let mut text = String::new();
3224 for (index, selection) in selections.iter().enumerate() {
3225 let text_for_selection = buffer_handle
3226 .text_for_range(selection.start..selection.end)
3227 .collect::<String>();
3228
3229 text.push_str(&text_for_selection);
3230 if index != selections.len() - 1 {
3231 text.push('\n');
3232 }
3233 }
3234
3235 if !text.is_empty() {
3236 cx.write_to_primary(ClipboardItem::new_string(text));
3237 }
3238 }
3239
3240 let selection_anchors = self.selections.disjoint_anchors_arc();
3241
3242 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3243 self.buffer.update(cx, |buffer, cx| {
3244 buffer.set_active_selections(
3245 &selection_anchors,
3246 self.selections.line_mode(),
3247 self.cursor_shape,
3248 cx,
3249 )
3250 });
3251 }
3252 let display_map = self
3253 .display_map
3254 .update(cx, |display_map, cx| display_map.snapshot(cx));
3255 let buffer = display_map.buffer_snapshot();
3256 if self.selections.count() == 1 {
3257 self.add_selections_state = None;
3258 }
3259 self.select_next_state = None;
3260 self.select_prev_state = None;
3261 self.select_syntax_node_history.try_clear();
3262 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3263 self.snippet_stack.invalidate(&selection_anchors, buffer);
3264 self.take_rename(false, window, cx);
3265
3266 let newest_selection = self.selections.newest_anchor();
3267 let new_cursor_position = newest_selection.head();
3268 let selection_start = newest_selection.start;
3269
3270 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3271 self.push_to_nav_history(
3272 *old_cursor_position,
3273 Some(new_cursor_position.to_point(buffer)),
3274 false,
3275 effects.nav_history == Some(true),
3276 cx,
3277 );
3278 }
3279
3280 if local {
3281 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3282 self.register_buffer(buffer_id, cx);
3283 }
3284
3285 let mut context_menu = self.context_menu.borrow_mut();
3286 let completion_menu = match context_menu.as_ref() {
3287 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3288 Some(CodeContextMenu::CodeActions(_)) => {
3289 *context_menu = None;
3290 None
3291 }
3292 None => None,
3293 };
3294 let completion_position = completion_menu.map(|menu| menu.initial_position);
3295 drop(context_menu);
3296
3297 if effects.completions
3298 && let Some(completion_position) = completion_position
3299 {
3300 let start_offset = selection_start.to_offset(buffer);
3301 let position_matches = start_offset == completion_position.to_offset(buffer);
3302 let continue_showing = if position_matches {
3303 if self.snippet_stack.is_empty() {
3304 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3305 == Some(CharKind::Word)
3306 } else {
3307 // Snippet choices can be shown even when the cursor is in whitespace.
3308 // Dismissing the menu with actions like backspace is handled by
3309 // invalidation regions.
3310 true
3311 }
3312 } else {
3313 false
3314 };
3315
3316 if continue_showing {
3317 self.open_or_update_completions_menu(None, None, false, window, cx);
3318 } else {
3319 self.hide_context_menu(window, cx);
3320 }
3321 }
3322
3323 hide_hover(self, cx);
3324
3325 if old_cursor_position.to_display_point(&display_map).row()
3326 != new_cursor_position.to_display_point(&display_map).row()
3327 {
3328 self.available_code_actions.take();
3329 }
3330 self.refresh_code_actions(window, cx);
3331 self.refresh_document_highlights(cx);
3332 refresh_linked_ranges(self, window, cx);
3333
3334 self.refresh_selected_text_highlights(false, window, cx);
3335 self.refresh_matching_bracket_highlights(window, cx);
3336 self.update_visible_edit_prediction(window, cx);
3337 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3338 self.inline_blame_popover.take();
3339 if self.git_blame_inline_enabled {
3340 self.start_inline_blame_timer(window, cx);
3341 }
3342 }
3343
3344 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3345 cx.emit(EditorEvent::SelectionsChanged { local });
3346
3347 let selections = &self.selections.disjoint_anchors_arc();
3348 if selections.len() == 1 {
3349 cx.emit(SearchEvent::ActiveMatchChanged)
3350 }
3351 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3352 let inmemory_selections = selections
3353 .iter()
3354 .map(|s| {
3355 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3356 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3357 })
3358 .collect();
3359 self.update_restoration_data(cx, |data| {
3360 data.selections = inmemory_selections;
3361 });
3362
3363 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3364 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3365 {
3366 let snapshot = self.buffer().read(cx).snapshot(cx);
3367 let selections = selections.clone();
3368 let background_executor = cx.background_executor().clone();
3369 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3370 self.serialize_selections = cx.background_spawn(async move {
3371 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3372 let db_selections = selections
3373 .iter()
3374 .map(|selection| {
3375 (
3376 selection.start.to_offset(&snapshot).0,
3377 selection.end.to_offset(&snapshot).0,
3378 )
3379 })
3380 .collect();
3381
3382 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3383 .await
3384 .with_context(|| {
3385 format!(
3386 "persisting editor selections for editor {editor_id}, \
3387 workspace {workspace_id:?}"
3388 )
3389 })
3390 .log_err();
3391 });
3392 }
3393 }
3394
3395 cx.notify();
3396 }
3397
3398 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3399 use text::ToOffset as _;
3400 use text::ToPoint as _;
3401
3402 if self.mode.is_minimap()
3403 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3404 {
3405 return;
3406 }
3407
3408 if !self.buffer().read(cx).is_singleton() {
3409 return;
3410 }
3411
3412 let display_snapshot = self
3413 .display_map
3414 .update(cx, |display_map, cx| display_map.snapshot(cx));
3415 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3416 return;
3417 };
3418 let inmemory_folds = display_snapshot
3419 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3420 .map(|fold| {
3421 fold.range.start.text_anchor.to_point(&snapshot)
3422 ..fold.range.end.text_anchor.to_point(&snapshot)
3423 })
3424 .collect();
3425 self.update_restoration_data(cx, |data| {
3426 data.folds = inmemory_folds;
3427 });
3428
3429 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3430 return;
3431 };
3432 let background_executor = cx.background_executor().clone();
3433 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3434 let db_folds = display_snapshot
3435 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3436 .map(|fold| {
3437 (
3438 fold.range.start.text_anchor.to_offset(&snapshot),
3439 fold.range.end.text_anchor.to_offset(&snapshot),
3440 )
3441 })
3442 .collect();
3443 self.serialize_folds = cx.background_spawn(async move {
3444 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3445 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3446 .await
3447 .with_context(|| {
3448 format!(
3449 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3450 )
3451 })
3452 .log_err();
3453 });
3454 }
3455
3456 pub fn sync_selections(
3457 &mut self,
3458 other: Entity<Editor>,
3459 cx: &mut Context<Self>,
3460 ) -> gpui::Subscription {
3461 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3462 if !other_selections.is_empty() {
3463 self.selections
3464 .change_with(&self.display_snapshot(cx), |selections| {
3465 selections.select_anchors(other_selections);
3466 });
3467 }
3468
3469 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3470 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3471 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3472 if other_selections.is_empty() {
3473 return;
3474 }
3475 let snapshot = this.display_snapshot(cx);
3476 this.selections.change_with(&snapshot, |selections| {
3477 selections.select_anchors(other_selections);
3478 });
3479 }
3480 });
3481
3482 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3483 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3484 let these_selections = this.selections.disjoint_anchors().to_vec();
3485 if these_selections.is_empty() {
3486 return;
3487 }
3488 other.update(cx, |other_editor, cx| {
3489 let snapshot = other_editor.display_snapshot(cx);
3490 other_editor
3491 .selections
3492 .change_with(&snapshot, |selections| {
3493 selections.select_anchors(these_selections);
3494 })
3495 });
3496 }
3497 });
3498
3499 Subscription::join(other_subscription, this_subscription)
3500 }
3501
3502 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3503 if self.buffer().read(cx).is_singleton() {
3504 return;
3505 }
3506 let snapshot = self.buffer.read(cx).snapshot(cx);
3507 let buffer_ids: HashSet<BufferId> = self
3508 .selections
3509 .disjoint_anchor_ranges()
3510 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3511 .collect();
3512 for buffer_id in buffer_ids {
3513 self.unfold_buffer(buffer_id, cx);
3514 }
3515 }
3516
3517 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3518 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3519 /// effects of selection change occur at the end of the transaction.
3520 pub fn change_selections<R>(
3521 &mut self,
3522 effects: SelectionEffects,
3523 window: &mut Window,
3524 cx: &mut Context<Self>,
3525 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3526 ) -> R {
3527 let snapshot = self.display_snapshot(cx);
3528 if let Some(state) = &mut self.deferred_selection_effects_state {
3529 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3530 state.effects.completions = effects.completions;
3531 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3532 let (changed, result) = self.selections.change_with(&snapshot, change);
3533 state.changed |= changed;
3534 return result;
3535 }
3536 let mut state = DeferredSelectionEffectsState {
3537 changed: false,
3538 effects,
3539 old_cursor_position: self.selections.newest_anchor().head(),
3540 history_entry: SelectionHistoryEntry {
3541 selections: self.selections.disjoint_anchors_arc(),
3542 select_next_state: self.select_next_state.clone(),
3543 select_prev_state: self.select_prev_state.clone(),
3544 add_selections_state: self.add_selections_state.clone(),
3545 },
3546 };
3547 let (changed, result) = self.selections.change_with(&snapshot, change);
3548 state.changed = state.changed || changed;
3549 if self.defer_selection_effects {
3550 self.deferred_selection_effects_state = Some(state);
3551 } else {
3552 self.apply_selection_effects(state, window, cx);
3553 }
3554 result
3555 }
3556
3557 /// Defers the effects of selection change, so that the effects of multiple calls to
3558 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3559 /// to selection history and the state of popovers based on selection position aren't
3560 /// erroneously updated.
3561 pub fn with_selection_effects_deferred<R>(
3562 &mut self,
3563 window: &mut Window,
3564 cx: &mut Context<Self>,
3565 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3566 ) -> R {
3567 let already_deferred = self.defer_selection_effects;
3568 self.defer_selection_effects = true;
3569 let result = update(self, window, cx);
3570 if !already_deferred {
3571 self.defer_selection_effects = false;
3572 if let Some(state) = self.deferred_selection_effects_state.take() {
3573 self.apply_selection_effects(state, window, cx);
3574 }
3575 }
3576 result
3577 }
3578
3579 fn apply_selection_effects(
3580 &mut self,
3581 state: DeferredSelectionEffectsState,
3582 window: &mut Window,
3583 cx: &mut Context<Self>,
3584 ) {
3585 if state.changed {
3586 self.selection_history.push(state.history_entry);
3587
3588 if let Some(autoscroll) = state.effects.scroll {
3589 self.request_autoscroll(autoscroll, cx);
3590 }
3591
3592 let old_cursor_position = &state.old_cursor_position;
3593
3594 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3595
3596 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3597 self.show_signature_help(&ShowSignatureHelp, window, cx);
3598 }
3599 }
3600 }
3601
3602 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3603 where
3604 I: IntoIterator<Item = (Range<S>, T)>,
3605 S: ToOffset,
3606 T: Into<Arc<str>>,
3607 {
3608 if self.read_only(cx) {
3609 return;
3610 }
3611
3612 self.buffer
3613 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3614 }
3615
3616 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3617 where
3618 I: IntoIterator<Item = (Range<S>, T)>,
3619 S: ToOffset,
3620 T: Into<Arc<str>>,
3621 {
3622 if self.read_only(cx) {
3623 return;
3624 }
3625
3626 self.buffer.update(cx, |buffer, cx| {
3627 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3628 });
3629 }
3630
3631 pub fn edit_with_block_indent<I, S, T>(
3632 &mut self,
3633 edits: I,
3634 original_indent_columns: Vec<Option<u32>>,
3635 cx: &mut Context<Self>,
3636 ) where
3637 I: IntoIterator<Item = (Range<S>, T)>,
3638 S: ToOffset,
3639 T: Into<Arc<str>>,
3640 {
3641 if self.read_only(cx) {
3642 return;
3643 }
3644
3645 self.buffer.update(cx, |buffer, cx| {
3646 buffer.edit(
3647 edits,
3648 Some(AutoindentMode::Block {
3649 original_indent_columns,
3650 }),
3651 cx,
3652 )
3653 });
3654 }
3655
3656 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3657 self.hide_context_menu(window, cx);
3658
3659 match phase {
3660 SelectPhase::Begin {
3661 position,
3662 add,
3663 click_count,
3664 } => self.begin_selection(position, add, click_count, window, cx),
3665 SelectPhase::BeginColumnar {
3666 position,
3667 goal_column,
3668 reset,
3669 mode,
3670 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3671 SelectPhase::Extend {
3672 position,
3673 click_count,
3674 } => self.extend_selection(position, click_count, window, cx),
3675 SelectPhase::Update {
3676 position,
3677 goal_column,
3678 scroll_delta,
3679 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3680 SelectPhase::End => self.end_selection(window, cx),
3681 }
3682 }
3683
3684 fn extend_selection(
3685 &mut self,
3686 position: DisplayPoint,
3687 click_count: usize,
3688 window: &mut Window,
3689 cx: &mut Context<Self>,
3690 ) {
3691 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3692 let tail = self
3693 .selections
3694 .newest::<MultiBufferOffset>(&display_map)
3695 .tail();
3696 let click_count = click_count.max(match self.selections.select_mode() {
3697 SelectMode::Character => 1,
3698 SelectMode::Word(_) => 2,
3699 SelectMode::Line(_) => 3,
3700 SelectMode::All => 4,
3701 });
3702 self.begin_selection(position, false, click_count, window, cx);
3703
3704 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3705
3706 let current_selection = match self.selections.select_mode() {
3707 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3708 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3709 };
3710
3711 let mut pending_selection = self
3712 .selections
3713 .pending_anchor()
3714 .cloned()
3715 .expect("extend_selection not called with pending selection");
3716
3717 if pending_selection
3718 .start
3719 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3720 == Ordering::Greater
3721 {
3722 pending_selection.start = current_selection.start;
3723 }
3724 if pending_selection
3725 .end
3726 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3727 == Ordering::Less
3728 {
3729 pending_selection.end = current_selection.end;
3730 pending_selection.reversed = true;
3731 }
3732
3733 let mut pending_mode = self.selections.pending_mode().unwrap();
3734 match &mut pending_mode {
3735 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3736 _ => {}
3737 }
3738
3739 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3740 SelectionEffects::scroll(Autoscroll::fit())
3741 } else {
3742 SelectionEffects::no_scroll()
3743 };
3744
3745 self.change_selections(effects, window, cx, |s| {
3746 s.set_pending(pending_selection.clone(), pending_mode);
3747 s.set_is_extending(true);
3748 });
3749 }
3750
3751 fn begin_selection(
3752 &mut self,
3753 position: DisplayPoint,
3754 add: bool,
3755 click_count: usize,
3756 window: &mut Window,
3757 cx: &mut Context<Self>,
3758 ) {
3759 if !self.focus_handle.is_focused(window) {
3760 self.last_focused_descendant = None;
3761 window.focus(&self.focus_handle);
3762 }
3763
3764 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3765 let buffer = display_map.buffer_snapshot();
3766 let position = display_map.clip_point(position, Bias::Left);
3767
3768 let start;
3769 let end;
3770 let mode;
3771 let mut auto_scroll;
3772 match click_count {
3773 1 => {
3774 start = buffer.anchor_before(position.to_point(&display_map));
3775 end = start;
3776 mode = SelectMode::Character;
3777 auto_scroll = true;
3778 }
3779 2 => {
3780 let position = display_map
3781 .clip_point(position, Bias::Left)
3782 .to_offset(&display_map, Bias::Left);
3783 let (range, _) = buffer.surrounding_word(position, None);
3784 start = buffer.anchor_before(range.start);
3785 end = buffer.anchor_before(range.end);
3786 mode = SelectMode::Word(start..end);
3787 auto_scroll = true;
3788 }
3789 3 => {
3790 let position = display_map
3791 .clip_point(position, Bias::Left)
3792 .to_point(&display_map);
3793 let line_start = display_map.prev_line_boundary(position).0;
3794 let next_line_start = buffer.clip_point(
3795 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3796 Bias::Left,
3797 );
3798 start = buffer.anchor_before(line_start);
3799 end = buffer.anchor_before(next_line_start);
3800 mode = SelectMode::Line(start..end);
3801 auto_scroll = true;
3802 }
3803 _ => {
3804 start = buffer.anchor_before(MultiBufferOffset(0));
3805 end = buffer.anchor_before(buffer.len());
3806 mode = SelectMode::All;
3807 auto_scroll = false;
3808 }
3809 }
3810 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3811
3812 let point_to_delete: Option<usize> = {
3813 let selected_points: Vec<Selection<Point>> =
3814 self.selections.disjoint_in_range(start..end, &display_map);
3815
3816 if !add || click_count > 1 {
3817 None
3818 } else if !selected_points.is_empty() {
3819 Some(selected_points[0].id)
3820 } else {
3821 let clicked_point_already_selected =
3822 self.selections.disjoint_anchors().iter().find(|selection| {
3823 selection.start.to_point(buffer) == start.to_point(buffer)
3824 || selection.end.to_point(buffer) == end.to_point(buffer)
3825 });
3826
3827 clicked_point_already_selected.map(|selection| selection.id)
3828 }
3829 };
3830
3831 let selections_count = self.selections.count();
3832 let effects = if auto_scroll {
3833 SelectionEffects::default()
3834 } else {
3835 SelectionEffects::no_scroll()
3836 };
3837
3838 self.change_selections(effects, window, cx, |s| {
3839 if let Some(point_to_delete) = point_to_delete {
3840 s.delete(point_to_delete);
3841
3842 if selections_count == 1 {
3843 s.set_pending_anchor_range(start..end, mode);
3844 }
3845 } else {
3846 if !add {
3847 s.clear_disjoint();
3848 }
3849
3850 s.set_pending_anchor_range(start..end, mode);
3851 }
3852 });
3853 }
3854
3855 fn begin_columnar_selection(
3856 &mut self,
3857 position: DisplayPoint,
3858 goal_column: u32,
3859 reset: bool,
3860 mode: ColumnarMode,
3861 window: &mut Window,
3862 cx: &mut Context<Self>,
3863 ) {
3864 if !self.focus_handle.is_focused(window) {
3865 self.last_focused_descendant = None;
3866 window.focus(&self.focus_handle);
3867 }
3868
3869 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3870
3871 if reset {
3872 let pointer_position = display_map
3873 .buffer_snapshot()
3874 .anchor_before(position.to_point(&display_map));
3875
3876 self.change_selections(
3877 SelectionEffects::scroll(Autoscroll::newest()),
3878 window,
3879 cx,
3880 |s| {
3881 s.clear_disjoint();
3882 s.set_pending_anchor_range(
3883 pointer_position..pointer_position,
3884 SelectMode::Character,
3885 );
3886 },
3887 );
3888 };
3889
3890 let tail = self.selections.newest::<Point>(&display_map).tail();
3891 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3892 self.columnar_selection_state = match mode {
3893 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3894 selection_tail: selection_anchor,
3895 display_point: if reset {
3896 if position.column() != goal_column {
3897 Some(DisplayPoint::new(position.row(), goal_column))
3898 } else {
3899 None
3900 }
3901 } else {
3902 None
3903 },
3904 }),
3905 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3906 selection_tail: selection_anchor,
3907 }),
3908 };
3909
3910 if !reset {
3911 self.select_columns(position, goal_column, &display_map, window, cx);
3912 }
3913 }
3914
3915 fn update_selection(
3916 &mut self,
3917 position: DisplayPoint,
3918 goal_column: u32,
3919 scroll_delta: gpui::Point<f32>,
3920 window: &mut Window,
3921 cx: &mut Context<Self>,
3922 ) {
3923 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3924
3925 if self.columnar_selection_state.is_some() {
3926 self.select_columns(position, goal_column, &display_map, window, cx);
3927 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3928 let buffer = display_map.buffer_snapshot();
3929 let head;
3930 let tail;
3931 let mode = self.selections.pending_mode().unwrap();
3932 match &mode {
3933 SelectMode::Character => {
3934 head = position.to_point(&display_map);
3935 tail = pending.tail().to_point(buffer);
3936 }
3937 SelectMode::Word(original_range) => {
3938 let offset = display_map
3939 .clip_point(position, Bias::Left)
3940 .to_offset(&display_map, Bias::Left);
3941 let original_range = original_range.to_offset(buffer);
3942
3943 let head_offset = if buffer.is_inside_word(offset, None)
3944 || original_range.contains(&offset)
3945 {
3946 let (word_range, _) = buffer.surrounding_word(offset, None);
3947 if word_range.start < original_range.start {
3948 word_range.start
3949 } else {
3950 word_range.end
3951 }
3952 } else {
3953 offset
3954 };
3955
3956 head = head_offset.to_point(buffer);
3957 if head_offset <= original_range.start {
3958 tail = original_range.end.to_point(buffer);
3959 } else {
3960 tail = original_range.start.to_point(buffer);
3961 }
3962 }
3963 SelectMode::Line(original_range) => {
3964 let original_range = original_range.to_point(display_map.buffer_snapshot());
3965
3966 let position = display_map
3967 .clip_point(position, Bias::Left)
3968 .to_point(&display_map);
3969 let line_start = display_map.prev_line_boundary(position).0;
3970 let next_line_start = buffer.clip_point(
3971 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3972 Bias::Left,
3973 );
3974
3975 if line_start < original_range.start {
3976 head = line_start
3977 } else {
3978 head = next_line_start
3979 }
3980
3981 if head <= original_range.start {
3982 tail = original_range.end;
3983 } else {
3984 tail = original_range.start;
3985 }
3986 }
3987 SelectMode::All => {
3988 return;
3989 }
3990 };
3991
3992 if head < tail {
3993 pending.start = buffer.anchor_before(head);
3994 pending.end = buffer.anchor_before(tail);
3995 pending.reversed = true;
3996 } else {
3997 pending.start = buffer.anchor_before(tail);
3998 pending.end = buffer.anchor_before(head);
3999 pending.reversed = false;
4000 }
4001
4002 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4003 s.set_pending(pending.clone(), mode);
4004 });
4005 } else {
4006 log::error!("update_selection dispatched with no pending selection");
4007 return;
4008 }
4009
4010 self.apply_scroll_delta(scroll_delta, window, cx);
4011 cx.notify();
4012 }
4013
4014 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4015 self.columnar_selection_state.take();
4016 if let Some(pending_mode) = self.selections.pending_mode() {
4017 let selections = self
4018 .selections
4019 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4020 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4021 s.select(selections);
4022 s.clear_pending();
4023 if s.is_extending() {
4024 s.set_is_extending(false);
4025 } else {
4026 s.set_select_mode(pending_mode);
4027 }
4028 });
4029 }
4030 }
4031
4032 fn select_columns(
4033 &mut self,
4034 head: DisplayPoint,
4035 goal_column: u32,
4036 display_map: &DisplaySnapshot,
4037 window: &mut Window,
4038 cx: &mut Context<Self>,
4039 ) {
4040 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4041 return;
4042 };
4043
4044 let tail = match columnar_state {
4045 ColumnarSelectionState::FromMouse {
4046 selection_tail,
4047 display_point,
4048 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4049 ColumnarSelectionState::FromSelection { selection_tail } => {
4050 selection_tail.to_display_point(display_map)
4051 }
4052 };
4053
4054 let start_row = cmp::min(tail.row(), head.row());
4055 let end_row = cmp::max(tail.row(), head.row());
4056 let start_column = cmp::min(tail.column(), goal_column);
4057 let end_column = cmp::max(tail.column(), goal_column);
4058 let reversed = start_column < tail.column();
4059
4060 let selection_ranges = (start_row.0..=end_row.0)
4061 .map(DisplayRow)
4062 .filter_map(|row| {
4063 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4064 || start_column <= display_map.line_len(row))
4065 && !display_map.is_block_line(row)
4066 {
4067 let start = display_map
4068 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4069 .to_point(display_map);
4070 let end = display_map
4071 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4072 .to_point(display_map);
4073 if reversed {
4074 Some(end..start)
4075 } else {
4076 Some(start..end)
4077 }
4078 } else {
4079 None
4080 }
4081 })
4082 .collect::<Vec<_>>();
4083 if selection_ranges.is_empty() {
4084 return;
4085 }
4086
4087 let ranges = match columnar_state {
4088 ColumnarSelectionState::FromMouse { .. } => {
4089 let mut non_empty_ranges = selection_ranges
4090 .iter()
4091 .filter(|selection_range| selection_range.start != selection_range.end)
4092 .peekable();
4093 if non_empty_ranges.peek().is_some() {
4094 non_empty_ranges.cloned().collect()
4095 } else {
4096 selection_ranges
4097 }
4098 }
4099 _ => selection_ranges,
4100 };
4101
4102 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4103 s.select_ranges(ranges);
4104 });
4105 cx.notify();
4106 }
4107
4108 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4109 self.selections
4110 .all_adjusted(snapshot)
4111 .iter()
4112 .any(|selection| !selection.is_empty())
4113 }
4114
4115 pub fn has_pending_nonempty_selection(&self) -> bool {
4116 let pending_nonempty_selection = match self.selections.pending_anchor() {
4117 Some(Selection { start, end, .. }) => start != end,
4118 None => false,
4119 };
4120
4121 pending_nonempty_selection
4122 || (self.columnar_selection_state.is_some()
4123 && self.selections.disjoint_anchors().len() > 1)
4124 }
4125
4126 pub fn has_pending_selection(&self) -> bool {
4127 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4128 }
4129
4130 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4131 self.selection_mark_mode = false;
4132 self.selection_drag_state = SelectionDragState::None;
4133
4134 if self.dismiss_menus_and_popups(true, window, cx) {
4135 cx.notify();
4136 return;
4137 }
4138 if self.clear_expanded_diff_hunks(cx) {
4139 cx.notify();
4140 return;
4141 }
4142 if self.show_git_blame_gutter {
4143 self.show_git_blame_gutter = false;
4144 cx.notify();
4145 return;
4146 }
4147
4148 if self.mode.is_full()
4149 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4150 {
4151 cx.notify();
4152 return;
4153 }
4154
4155 cx.propagate();
4156 }
4157
4158 pub fn dismiss_menus_and_popups(
4159 &mut self,
4160 is_user_requested: bool,
4161 window: &mut Window,
4162 cx: &mut Context<Self>,
4163 ) -> bool {
4164 let mut dismissed = false;
4165
4166 dismissed |= self.take_rename(false, window, cx).is_some();
4167 dismissed |= self.hide_blame_popover(true, cx);
4168 dismissed |= hide_hover(self, cx);
4169 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4170 dismissed |= self.hide_context_menu(window, cx).is_some();
4171 dismissed |= self.mouse_context_menu.take().is_some();
4172 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4173 dismissed |= self.snippet_stack.pop().is_some();
4174
4175 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4176 self.dismiss_diagnostics(cx);
4177 dismissed = true;
4178 }
4179
4180 dismissed
4181 }
4182
4183 fn linked_editing_ranges_for(
4184 &self,
4185 selection: Range<text::Anchor>,
4186 cx: &App,
4187 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4188 if self.linked_edit_ranges.is_empty() {
4189 return None;
4190 }
4191 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4192 selection.end.buffer_id.and_then(|end_buffer_id| {
4193 if selection.start.buffer_id != Some(end_buffer_id) {
4194 return None;
4195 }
4196 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4197 let snapshot = buffer.read(cx).snapshot();
4198 self.linked_edit_ranges
4199 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4200 .map(|ranges| (ranges, snapshot, buffer))
4201 })?;
4202 use text::ToOffset as TO;
4203 // find offset from the start of current range to current cursor position
4204 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4205
4206 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4207 let start_difference = start_offset - start_byte_offset;
4208 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4209 let end_difference = end_offset - start_byte_offset;
4210 // Current range has associated linked ranges.
4211 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4212 for range in linked_ranges.iter() {
4213 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4214 let end_offset = start_offset + end_difference;
4215 let start_offset = start_offset + start_difference;
4216 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4217 continue;
4218 }
4219 if self.selections.disjoint_anchor_ranges().any(|s| {
4220 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4221 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4222 {
4223 return false;
4224 }
4225 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4226 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4227 }) {
4228 continue;
4229 }
4230 let start = buffer_snapshot.anchor_after(start_offset);
4231 let end = buffer_snapshot.anchor_after(end_offset);
4232 linked_edits
4233 .entry(buffer.clone())
4234 .or_default()
4235 .push(start..end);
4236 }
4237 Some(linked_edits)
4238 }
4239
4240 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4241 let text: Arc<str> = text.into();
4242
4243 if self.read_only(cx) {
4244 return;
4245 }
4246
4247 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4248
4249 self.unfold_buffers_with_selections(cx);
4250
4251 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4252 let mut bracket_inserted = false;
4253 let mut edits = Vec::new();
4254 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4255 let mut new_selections = Vec::with_capacity(selections.len());
4256 let mut new_autoclose_regions = Vec::new();
4257 let snapshot = self.buffer.read(cx).read(cx);
4258 let mut clear_linked_edit_ranges = false;
4259
4260 for (selection, autoclose_region) in
4261 self.selections_with_autoclose_regions(selections, &snapshot)
4262 {
4263 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4264 // Determine if the inserted text matches the opening or closing
4265 // bracket of any of this language's bracket pairs.
4266 let mut bracket_pair = None;
4267 let mut is_bracket_pair_start = false;
4268 let mut is_bracket_pair_end = false;
4269 if !text.is_empty() {
4270 let mut bracket_pair_matching_end = None;
4271 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4272 // and they are removing the character that triggered IME popup.
4273 for (pair, enabled) in scope.brackets() {
4274 if !pair.close && !pair.surround {
4275 continue;
4276 }
4277
4278 if enabled && pair.start.ends_with(text.as_ref()) {
4279 let prefix_len = pair.start.len() - text.len();
4280 let preceding_text_matches_prefix = prefix_len == 0
4281 || (selection.start.column >= (prefix_len as u32)
4282 && snapshot.contains_str_at(
4283 Point::new(
4284 selection.start.row,
4285 selection.start.column - (prefix_len as u32),
4286 ),
4287 &pair.start[..prefix_len],
4288 ));
4289 if preceding_text_matches_prefix {
4290 bracket_pair = Some(pair.clone());
4291 is_bracket_pair_start = true;
4292 break;
4293 }
4294 }
4295 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4296 {
4297 // take first bracket pair matching end, but don't break in case a later bracket
4298 // pair matches start
4299 bracket_pair_matching_end = Some(pair.clone());
4300 }
4301 }
4302 if let Some(end) = bracket_pair_matching_end
4303 && bracket_pair.is_none()
4304 {
4305 bracket_pair = Some(end);
4306 is_bracket_pair_end = true;
4307 }
4308 }
4309
4310 if let Some(bracket_pair) = bracket_pair {
4311 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4312 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4313 let auto_surround =
4314 self.use_auto_surround && snapshot_settings.use_auto_surround;
4315 if selection.is_empty() {
4316 if is_bracket_pair_start {
4317 // If the inserted text is a suffix of an opening bracket and the
4318 // selection is preceded by the rest of the opening bracket, then
4319 // insert the closing bracket.
4320 let following_text_allows_autoclose = snapshot
4321 .chars_at(selection.start)
4322 .next()
4323 .is_none_or(|c| scope.should_autoclose_before(c));
4324
4325 let preceding_text_allows_autoclose = selection.start.column == 0
4326 || snapshot
4327 .reversed_chars_at(selection.start)
4328 .next()
4329 .is_none_or(|c| {
4330 bracket_pair.start != bracket_pair.end
4331 || !snapshot
4332 .char_classifier_at(selection.start)
4333 .is_word(c)
4334 });
4335
4336 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4337 && bracket_pair.start.len() == 1
4338 {
4339 let target = bracket_pair.start.chars().next().unwrap();
4340 let current_line_count = snapshot
4341 .reversed_chars_at(selection.start)
4342 .take_while(|&c| c != '\n')
4343 .filter(|&c| c == target)
4344 .count();
4345 current_line_count % 2 == 1
4346 } else {
4347 false
4348 };
4349
4350 if autoclose
4351 && bracket_pair.close
4352 && following_text_allows_autoclose
4353 && preceding_text_allows_autoclose
4354 && !is_closing_quote
4355 {
4356 let anchor = snapshot.anchor_before(selection.end);
4357 new_selections.push((selection.map(|_| anchor), text.len()));
4358 new_autoclose_regions.push((
4359 anchor,
4360 text.len(),
4361 selection.id,
4362 bracket_pair.clone(),
4363 ));
4364 edits.push((
4365 selection.range(),
4366 format!("{}{}", text, bracket_pair.end).into(),
4367 ));
4368 bracket_inserted = true;
4369 continue;
4370 }
4371 }
4372
4373 if let Some(region) = autoclose_region {
4374 // If the selection is followed by an auto-inserted closing bracket,
4375 // then don't insert that closing bracket again; just move the selection
4376 // past the closing bracket.
4377 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4378 && text.as_ref() == region.pair.end.as_str()
4379 && snapshot.contains_str_at(region.range.end, text.as_ref());
4380 if should_skip {
4381 let anchor = snapshot.anchor_after(selection.end);
4382 new_selections
4383 .push((selection.map(|_| anchor), region.pair.end.len()));
4384 continue;
4385 }
4386 }
4387
4388 let always_treat_brackets_as_autoclosed = snapshot
4389 .language_settings_at(selection.start, cx)
4390 .always_treat_brackets_as_autoclosed;
4391 if always_treat_brackets_as_autoclosed
4392 && is_bracket_pair_end
4393 && snapshot.contains_str_at(selection.end, text.as_ref())
4394 {
4395 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4396 // and the inserted text is a closing bracket and the selection is followed
4397 // by the closing bracket then move the selection past the closing bracket.
4398 let anchor = snapshot.anchor_after(selection.end);
4399 new_selections.push((selection.map(|_| anchor), text.len()));
4400 continue;
4401 }
4402 }
4403 // If an opening bracket is 1 character long and is typed while
4404 // text is selected, then surround that text with the bracket pair.
4405 else if auto_surround
4406 && bracket_pair.surround
4407 && is_bracket_pair_start
4408 && bracket_pair.start.chars().count() == 1
4409 {
4410 edits.push((selection.start..selection.start, text.clone()));
4411 edits.push((
4412 selection.end..selection.end,
4413 bracket_pair.end.as_str().into(),
4414 ));
4415 bracket_inserted = true;
4416 new_selections.push((
4417 Selection {
4418 id: selection.id,
4419 start: snapshot.anchor_after(selection.start),
4420 end: snapshot.anchor_before(selection.end),
4421 reversed: selection.reversed,
4422 goal: selection.goal,
4423 },
4424 0,
4425 ));
4426 continue;
4427 }
4428 }
4429 }
4430
4431 if self.auto_replace_emoji_shortcode
4432 && selection.is_empty()
4433 && text.as_ref().ends_with(':')
4434 && let Some(possible_emoji_short_code) =
4435 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4436 && !possible_emoji_short_code.is_empty()
4437 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4438 {
4439 let emoji_shortcode_start = Point::new(
4440 selection.start.row,
4441 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4442 );
4443
4444 // Remove shortcode from buffer
4445 edits.push((
4446 emoji_shortcode_start..selection.start,
4447 "".to_string().into(),
4448 ));
4449 new_selections.push((
4450 Selection {
4451 id: selection.id,
4452 start: snapshot.anchor_after(emoji_shortcode_start),
4453 end: snapshot.anchor_before(selection.start),
4454 reversed: selection.reversed,
4455 goal: selection.goal,
4456 },
4457 0,
4458 ));
4459
4460 // Insert emoji
4461 let selection_start_anchor = snapshot.anchor_after(selection.start);
4462 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4463 edits.push((selection.start..selection.end, emoji.to_string().into()));
4464
4465 continue;
4466 }
4467
4468 // If not handling any auto-close operation, then just replace the selected
4469 // text with the given input and move the selection to the end of the
4470 // newly inserted text.
4471 let anchor = snapshot.anchor_after(selection.end);
4472 if !self.linked_edit_ranges.is_empty() {
4473 let start_anchor = snapshot.anchor_before(selection.start);
4474
4475 let is_word_char = text.chars().next().is_none_or(|char| {
4476 let classifier = snapshot
4477 .char_classifier_at(start_anchor.to_offset(&snapshot))
4478 .scope_context(Some(CharScopeContext::LinkedEdit));
4479 classifier.is_word(char)
4480 });
4481
4482 if is_word_char {
4483 if let Some(ranges) = self
4484 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4485 {
4486 for (buffer, edits) in ranges {
4487 linked_edits
4488 .entry(buffer.clone())
4489 .or_default()
4490 .extend(edits.into_iter().map(|range| (range, text.clone())));
4491 }
4492 }
4493 } else {
4494 clear_linked_edit_ranges = true;
4495 }
4496 }
4497
4498 new_selections.push((selection.map(|_| anchor), 0));
4499 edits.push((selection.start..selection.end, text.clone()));
4500 }
4501
4502 drop(snapshot);
4503
4504 self.transact(window, cx, |this, window, cx| {
4505 if clear_linked_edit_ranges {
4506 this.linked_edit_ranges.clear();
4507 }
4508 let initial_buffer_versions =
4509 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4510
4511 this.buffer.update(cx, |buffer, cx| {
4512 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4513 });
4514 for (buffer, edits) in linked_edits {
4515 buffer.update(cx, |buffer, cx| {
4516 let snapshot = buffer.snapshot();
4517 let edits = edits
4518 .into_iter()
4519 .map(|(range, text)| {
4520 use text::ToPoint as TP;
4521 let end_point = TP::to_point(&range.end, &snapshot);
4522 let start_point = TP::to_point(&range.start, &snapshot);
4523 (start_point..end_point, text)
4524 })
4525 .sorted_by_key(|(range, _)| range.start);
4526 buffer.edit(edits, None, cx);
4527 })
4528 }
4529 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4530 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4531 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4532 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4533 new_anchor_selections,
4534 &map,
4535 )
4536 .zip(new_selection_deltas)
4537 .map(|(selection, delta)| Selection {
4538 id: selection.id,
4539 start: selection.start + delta,
4540 end: selection.end + delta,
4541 reversed: selection.reversed,
4542 goal: SelectionGoal::None,
4543 })
4544 .collect::<Vec<_>>();
4545
4546 let mut i = 0;
4547 for (position, delta, selection_id, pair) in new_autoclose_regions {
4548 let position = position.to_offset(map.buffer_snapshot()) + delta;
4549 let start = map.buffer_snapshot().anchor_before(position);
4550 let end = map.buffer_snapshot().anchor_after(position);
4551 while let Some(existing_state) = this.autoclose_regions.get(i) {
4552 match existing_state
4553 .range
4554 .start
4555 .cmp(&start, map.buffer_snapshot())
4556 {
4557 Ordering::Less => i += 1,
4558 Ordering::Greater => break,
4559 Ordering::Equal => {
4560 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4561 Ordering::Less => i += 1,
4562 Ordering::Equal => break,
4563 Ordering::Greater => break,
4564 }
4565 }
4566 }
4567 }
4568 this.autoclose_regions.insert(
4569 i,
4570 AutocloseRegion {
4571 selection_id,
4572 range: start..end,
4573 pair,
4574 },
4575 );
4576 }
4577
4578 let had_active_edit_prediction = this.has_active_edit_prediction();
4579 this.change_selections(
4580 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4581 window,
4582 cx,
4583 |s| s.select(new_selections),
4584 );
4585
4586 if !bracket_inserted
4587 && let Some(on_type_format_task) =
4588 this.trigger_on_type_formatting(text.to_string(), window, cx)
4589 {
4590 on_type_format_task.detach_and_log_err(cx);
4591 }
4592
4593 let editor_settings = EditorSettings::get_global(cx);
4594 if bracket_inserted
4595 && (editor_settings.auto_signature_help
4596 || editor_settings.show_signature_help_after_edits)
4597 {
4598 this.show_signature_help(&ShowSignatureHelp, window, cx);
4599 }
4600
4601 let trigger_in_words =
4602 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4603 if this.hard_wrap.is_some() {
4604 let latest: Range<Point> = this.selections.newest(&map).range();
4605 if latest.is_empty()
4606 && this
4607 .buffer()
4608 .read(cx)
4609 .snapshot(cx)
4610 .line_len(MultiBufferRow(latest.start.row))
4611 == latest.start.column
4612 {
4613 this.rewrap_impl(
4614 RewrapOptions {
4615 override_language_settings: true,
4616 preserve_existing_whitespace: true,
4617 },
4618 cx,
4619 )
4620 }
4621 }
4622 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4623 refresh_linked_ranges(this, window, cx);
4624 this.refresh_edit_prediction(true, false, window, cx);
4625 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4626 });
4627 }
4628
4629 fn find_possible_emoji_shortcode_at_position(
4630 snapshot: &MultiBufferSnapshot,
4631 position: Point,
4632 ) -> Option<String> {
4633 let mut chars = Vec::new();
4634 let mut found_colon = false;
4635 for char in snapshot.reversed_chars_at(position).take(100) {
4636 // Found a possible emoji shortcode in the middle of the buffer
4637 if found_colon {
4638 if char.is_whitespace() {
4639 chars.reverse();
4640 return Some(chars.iter().collect());
4641 }
4642 // If the previous character is not a whitespace, we are in the middle of a word
4643 // and we only want to complete the shortcode if the word is made up of other emojis
4644 let mut containing_word = String::new();
4645 for ch in snapshot
4646 .reversed_chars_at(position)
4647 .skip(chars.len() + 1)
4648 .take(100)
4649 {
4650 if ch.is_whitespace() {
4651 break;
4652 }
4653 containing_word.push(ch);
4654 }
4655 let containing_word = containing_word.chars().rev().collect::<String>();
4656 if util::word_consists_of_emojis(containing_word.as_str()) {
4657 chars.reverse();
4658 return Some(chars.iter().collect());
4659 }
4660 }
4661
4662 if char.is_whitespace() || !char.is_ascii() {
4663 return None;
4664 }
4665 if char == ':' {
4666 found_colon = true;
4667 } else {
4668 chars.push(char);
4669 }
4670 }
4671 // Found a possible emoji shortcode at the beginning of the buffer
4672 chars.reverse();
4673 Some(chars.iter().collect())
4674 }
4675
4676 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4677 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4678 self.transact(window, cx, |this, window, cx| {
4679 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4680 let selections = this
4681 .selections
4682 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4683 let multi_buffer = this.buffer.read(cx);
4684 let buffer = multi_buffer.snapshot(cx);
4685 selections
4686 .iter()
4687 .map(|selection| {
4688 let start_point = selection.start.to_point(&buffer);
4689 let mut existing_indent =
4690 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4691 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4692 let start = selection.start;
4693 let end = selection.end;
4694 let selection_is_empty = start == end;
4695 let language_scope = buffer.language_scope_at(start);
4696 let (
4697 comment_delimiter,
4698 doc_delimiter,
4699 insert_extra_newline,
4700 indent_on_newline,
4701 indent_on_extra_newline,
4702 ) = if let Some(language) = &language_scope {
4703 let mut insert_extra_newline =
4704 insert_extra_newline_brackets(&buffer, start..end, language)
4705 || insert_extra_newline_tree_sitter(&buffer, start..end);
4706
4707 // Comment extension on newline is allowed only for cursor selections
4708 let comment_delimiter = maybe!({
4709 if !selection_is_empty {
4710 return None;
4711 }
4712
4713 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4714 return None;
4715 }
4716
4717 let delimiters = language.line_comment_prefixes();
4718 let max_len_of_delimiter =
4719 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4720 let (snapshot, range) =
4721 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4722
4723 let num_of_whitespaces = snapshot
4724 .chars_for_range(range.clone())
4725 .take_while(|c| c.is_whitespace())
4726 .count();
4727 let comment_candidate = snapshot
4728 .chars_for_range(range.clone())
4729 .skip(num_of_whitespaces)
4730 .take(max_len_of_delimiter)
4731 .collect::<String>();
4732 let (delimiter, trimmed_len) = delimiters
4733 .iter()
4734 .filter_map(|delimiter| {
4735 let prefix = delimiter.trim_end();
4736 if comment_candidate.starts_with(prefix) {
4737 Some((delimiter, prefix.len()))
4738 } else {
4739 None
4740 }
4741 })
4742 .max_by_key(|(_, len)| *len)?;
4743
4744 if let Some(BlockCommentConfig {
4745 start: block_start, ..
4746 }) = language.block_comment()
4747 {
4748 let block_start_trimmed = block_start.trim_end();
4749 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4750 let line_content = snapshot
4751 .chars_for_range(range)
4752 .skip(num_of_whitespaces)
4753 .take(block_start_trimmed.len())
4754 .collect::<String>();
4755
4756 if line_content.starts_with(block_start_trimmed) {
4757 return None;
4758 }
4759 }
4760 }
4761
4762 let cursor_is_placed_after_comment_marker =
4763 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4764 if cursor_is_placed_after_comment_marker {
4765 Some(delimiter.clone())
4766 } else {
4767 None
4768 }
4769 });
4770
4771 let mut indent_on_newline = IndentSize::spaces(0);
4772 let mut indent_on_extra_newline = IndentSize::spaces(0);
4773
4774 let doc_delimiter = maybe!({
4775 if !selection_is_empty {
4776 return None;
4777 }
4778
4779 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4780 return None;
4781 }
4782
4783 let BlockCommentConfig {
4784 start: start_tag,
4785 end: end_tag,
4786 prefix: delimiter,
4787 tab_size: len,
4788 } = language.documentation_comment()?;
4789 let is_within_block_comment = buffer
4790 .language_scope_at(start_point)
4791 .is_some_and(|scope| scope.override_name() == Some("comment"));
4792 if !is_within_block_comment {
4793 return None;
4794 }
4795
4796 let (snapshot, range) =
4797 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4798
4799 let num_of_whitespaces = snapshot
4800 .chars_for_range(range.clone())
4801 .take_while(|c| c.is_whitespace())
4802 .count();
4803
4804 // 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.
4805 let column = start_point.column;
4806 let cursor_is_after_start_tag = {
4807 let start_tag_len = start_tag.len();
4808 let start_tag_line = snapshot
4809 .chars_for_range(range.clone())
4810 .skip(num_of_whitespaces)
4811 .take(start_tag_len)
4812 .collect::<String>();
4813 if start_tag_line.starts_with(start_tag.as_ref()) {
4814 num_of_whitespaces + start_tag_len <= column as usize
4815 } else {
4816 false
4817 }
4818 };
4819
4820 let cursor_is_after_delimiter = {
4821 let delimiter_trim = delimiter.trim_end();
4822 let delimiter_line = snapshot
4823 .chars_for_range(range.clone())
4824 .skip(num_of_whitespaces)
4825 .take(delimiter_trim.len())
4826 .collect::<String>();
4827 if delimiter_line.starts_with(delimiter_trim) {
4828 num_of_whitespaces + delimiter_trim.len() <= column as usize
4829 } else {
4830 false
4831 }
4832 };
4833
4834 let cursor_is_before_end_tag_if_exists = {
4835 let mut char_position = 0u32;
4836 let mut end_tag_offset = None;
4837
4838 'outer: for chunk in snapshot.text_for_range(range) {
4839 if let Some(byte_pos) = chunk.find(&**end_tag) {
4840 let chars_before_match =
4841 chunk[..byte_pos].chars().count() as u32;
4842 end_tag_offset =
4843 Some(char_position + chars_before_match);
4844 break 'outer;
4845 }
4846 char_position += chunk.chars().count() as u32;
4847 }
4848
4849 if let Some(end_tag_offset) = end_tag_offset {
4850 let cursor_is_before_end_tag = column <= end_tag_offset;
4851 if cursor_is_after_start_tag {
4852 if cursor_is_before_end_tag {
4853 insert_extra_newline = true;
4854 }
4855 let cursor_is_at_start_of_end_tag =
4856 column == end_tag_offset;
4857 if cursor_is_at_start_of_end_tag {
4858 indent_on_extra_newline.len = *len;
4859 }
4860 }
4861 cursor_is_before_end_tag
4862 } else {
4863 true
4864 }
4865 };
4866
4867 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4868 && cursor_is_before_end_tag_if_exists
4869 {
4870 if cursor_is_after_start_tag {
4871 indent_on_newline.len = *len;
4872 }
4873 Some(delimiter.clone())
4874 } else {
4875 None
4876 }
4877 });
4878
4879 (
4880 comment_delimiter,
4881 doc_delimiter,
4882 insert_extra_newline,
4883 indent_on_newline,
4884 indent_on_extra_newline,
4885 )
4886 } else {
4887 (
4888 None,
4889 None,
4890 false,
4891 IndentSize::default(),
4892 IndentSize::default(),
4893 )
4894 };
4895
4896 let prevent_auto_indent = doc_delimiter.is_some();
4897 let delimiter = comment_delimiter.or(doc_delimiter);
4898
4899 let capacity_for_delimiter =
4900 delimiter.as_deref().map(str::len).unwrap_or_default();
4901 let mut new_text = String::with_capacity(
4902 1 + capacity_for_delimiter
4903 + existing_indent.len as usize
4904 + indent_on_newline.len as usize
4905 + indent_on_extra_newline.len as usize,
4906 );
4907 new_text.push('\n');
4908 new_text.extend(existing_indent.chars());
4909 new_text.extend(indent_on_newline.chars());
4910
4911 if let Some(delimiter) = &delimiter {
4912 new_text.push_str(delimiter);
4913 }
4914
4915 if insert_extra_newline {
4916 new_text.push('\n');
4917 new_text.extend(existing_indent.chars());
4918 new_text.extend(indent_on_extra_newline.chars());
4919 }
4920
4921 let anchor = buffer.anchor_after(end);
4922 let new_selection = selection.map(|_| anchor);
4923 (
4924 ((start..end, new_text), prevent_auto_indent),
4925 (insert_extra_newline, new_selection),
4926 )
4927 })
4928 .unzip()
4929 };
4930
4931 let mut auto_indent_edits = Vec::new();
4932 let mut edits = Vec::new();
4933 for (edit, prevent_auto_indent) in edits_with_flags {
4934 if prevent_auto_indent {
4935 edits.push(edit);
4936 } else {
4937 auto_indent_edits.push(edit);
4938 }
4939 }
4940 if !edits.is_empty() {
4941 this.edit(edits, cx);
4942 }
4943 if !auto_indent_edits.is_empty() {
4944 this.edit_with_autoindent(auto_indent_edits, cx);
4945 }
4946
4947 let buffer = this.buffer.read(cx).snapshot(cx);
4948 let new_selections = selection_info
4949 .into_iter()
4950 .map(|(extra_newline_inserted, new_selection)| {
4951 let mut cursor = new_selection.end.to_point(&buffer);
4952 if extra_newline_inserted {
4953 cursor.row -= 1;
4954 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4955 }
4956 new_selection.map(|_| cursor)
4957 })
4958 .collect();
4959
4960 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4961 this.refresh_edit_prediction(true, false, window, cx);
4962 });
4963 }
4964
4965 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4967
4968 let buffer = self.buffer.read(cx);
4969 let snapshot = buffer.snapshot(cx);
4970
4971 let mut edits = Vec::new();
4972 let mut rows = Vec::new();
4973
4974 for (rows_inserted, selection) in self
4975 .selections
4976 .all_adjusted(&self.display_snapshot(cx))
4977 .into_iter()
4978 .enumerate()
4979 {
4980 let cursor = selection.head();
4981 let row = cursor.row;
4982
4983 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4984
4985 let newline = "\n".to_string();
4986 edits.push((start_of_line..start_of_line, newline));
4987
4988 rows.push(row + rows_inserted as u32);
4989 }
4990
4991 self.transact(window, cx, |editor, window, cx| {
4992 editor.edit(edits, cx);
4993
4994 editor.change_selections(Default::default(), window, cx, |s| {
4995 let mut index = 0;
4996 s.move_cursors_with(|map, _, _| {
4997 let row = rows[index];
4998 index += 1;
4999
5000 let point = Point::new(row, 0);
5001 let boundary = map.next_line_boundary(point).1;
5002 let clipped = map.clip_point(boundary, Bias::Left);
5003
5004 (clipped, SelectionGoal::None)
5005 });
5006 });
5007
5008 let mut indent_edits = Vec::new();
5009 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5010 for row in rows {
5011 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5012 for (row, indent) in indents {
5013 if indent.len == 0 {
5014 continue;
5015 }
5016
5017 let text = match indent.kind {
5018 IndentKind::Space => " ".repeat(indent.len as usize),
5019 IndentKind::Tab => "\t".repeat(indent.len as usize),
5020 };
5021 let point = Point::new(row.0, 0);
5022 indent_edits.push((point..point, text));
5023 }
5024 }
5025 editor.edit(indent_edits, cx);
5026 });
5027 }
5028
5029 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5030 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5031
5032 let buffer = self.buffer.read(cx);
5033 let snapshot = buffer.snapshot(cx);
5034
5035 let mut edits = Vec::new();
5036 let mut rows = Vec::new();
5037 let mut rows_inserted = 0;
5038
5039 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5040 let cursor = selection.head();
5041 let row = cursor.row;
5042
5043 let point = Point::new(row + 1, 0);
5044 let start_of_line = snapshot.clip_point(point, Bias::Left);
5045
5046 let newline = "\n".to_string();
5047 edits.push((start_of_line..start_of_line, newline));
5048
5049 rows_inserted += 1;
5050 rows.push(row + rows_inserted);
5051 }
5052
5053 self.transact(window, cx, |editor, window, cx| {
5054 editor.edit(edits, cx);
5055
5056 editor.change_selections(Default::default(), window, cx, |s| {
5057 let mut index = 0;
5058 s.move_cursors_with(|map, _, _| {
5059 let row = rows[index];
5060 index += 1;
5061
5062 let point = Point::new(row, 0);
5063 let boundary = map.next_line_boundary(point).1;
5064 let clipped = map.clip_point(boundary, Bias::Left);
5065
5066 (clipped, SelectionGoal::None)
5067 });
5068 });
5069
5070 let mut indent_edits = Vec::new();
5071 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5072 for row in rows {
5073 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5074 for (row, indent) in indents {
5075 if indent.len == 0 {
5076 continue;
5077 }
5078
5079 let text = match indent.kind {
5080 IndentKind::Space => " ".repeat(indent.len as usize),
5081 IndentKind::Tab => "\t".repeat(indent.len as usize),
5082 };
5083 let point = Point::new(row.0, 0);
5084 indent_edits.push((point..point, text));
5085 }
5086 }
5087 editor.edit(indent_edits, cx);
5088 });
5089 }
5090
5091 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5092 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5093 original_indent_columns: Vec::new(),
5094 });
5095 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5096 }
5097
5098 fn insert_with_autoindent_mode(
5099 &mut self,
5100 text: &str,
5101 autoindent_mode: Option<AutoindentMode>,
5102 window: &mut Window,
5103 cx: &mut Context<Self>,
5104 ) {
5105 if self.read_only(cx) {
5106 return;
5107 }
5108
5109 let text: Arc<str> = text.into();
5110 self.transact(window, cx, |this, window, cx| {
5111 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5112 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5113 let anchors = {
5114 let snapshot = buffer.read(cx);
5115 old_selections
5116 .iter()
5117 .map(|s| {
5118 let anchor = snapshot.anchor_after(s.head());
5119 s.map(|_| anchor)
5120 })
5121 .collect::<Vec<_>>()
5122 };
5123 buffer.edit(
5124 old_selections
5125 .iter()
5126 .map(|s| (s.start..s.end, text.clone())),
5127 autoindent_mode,
5128 cx,
5129 );
5130 anchors
5131 });
5132
5133 this.change_selections(Default::default(), window, cx, |s| {
5134 s.select_anchors(selection_anchors);
5135 });
5136
5137 cx.notify();
5138 });
5139 }
5140
5141 fn trigger_completion_on_input(
5142 &mut self,
5143 text: &str,
5144 trigger_in_words: bool,
5145 window: &mut Window,
5146 cx: &mut Context<Self>,
5147 ) {
5148 let completions_source = self
5149 .context_menu
5150 .borrow()
5151 .as_ref()
5152 .and_then(|menu| match menu {
5153 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5154 CodeContextMenu::CodeActions(_) => None,
5155 });
5156
5157 match completions_source {
5158 Some(CompletionsMenuSource::Words { .. }) => {
5159 self.open_or_update_completions_menu(
5160 Some(CompletionsMenuSource::Words {
5161 ignore_threshold: false,
5162 }),
5163 None,
5164 trigger_in_words,
5165 window,
5166 cx,
5167 );
5168 }
5169 _ => self.open_or_update_completions_menu(
5170 None,
5171 Some(text.to_owned()).filter(|x| !x.is_empty()),
5172 true,
5173 window,
5174 cx,
5175 ),
5176 }
5177 }
5178
5179 /// If any empty selections is touching the start of its innermost containing autoclose
5180 /// region, expand it to select the brackets.
5181 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5182 let selections = self
5183 .selections
5184 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5185 let buffer = self.buffer.read(cx).read(cx);
5186 let new_selections = self
5187 .selections_with_autoclose_regions(selections, &buffer)
5188 .map(|(mut selection, region)| {
5189 if !selection.is_empty() {
5190 return selection;
5191 }
5192
5193 if let Some(region) = region {
5194 let mut range = region.range.to_offset(&buffer);
5195 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5196 range.start -= region.pair.start.len();
5197 if buffer.contains_str_at(range.start, ®ion.pair.start)
5198 && buffer.contains_str_at(range.end, ®ion.pair.end)
5199 {
5200 range.end += region.pair.end.len();
5201 selection.start = range.start;
5202 selection.end = range.end;
5203
5204 return selection;
5205 }
5206 }
5207 }
5208
5209 let always_treat_brackets_as_autoclosed = buffer
5210 .language_settings_at(selection.start, cx)
5211 .always_treat_brackets_as_autoclosed;
5212
5213 if !always_treat_brackets_as_autoclosed {
5214 return selection;
5215 }
5216
5217 if let Some(scope) = buffer.language_scope_at(selection.start) {
5218 for (pair, enabled) in scope.brackets() {
5219 if !enabled || !pair.close {
5220 continue;
5221 }
5222
5223 if buffer.contains_str_at(selection.start, &pair.end) {
5224 let pair_start_len = pair.start.len();
5225 if buffer.contains_str_at(
5226 selection.start.saturating_sub_usize(pair_start_len),
5227 &pair.start,
5228 ) {
5229 selection.start -= pair_start_len;
5230 selection.end += pair.end.len();
5231
5232 return selection;
5233 }
5234 }
5235 }
5236 }
5237
5238 selection
5239 })
5240 .collect();
5241
5242 drop(buffer);
5243 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5244 selections.select(new_selections)
5245 });
5246 }
5247
5248 /// Iterate the given selections, and for each one, find the smallest surrounding
5249 /// autoclose region. This uses the ordering of the selections and the autoclose
5250 /// regions to avoid repeated comparisons.
5251 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5252 &'a self,
5253 selections: impl IntoIterator<Item = Selection<D>>,
5254 buffer: &'a MultiBufferSnapshot,
5255 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5256 let mut i = 0;
5257 let mut regions = self.autoclose_regions.as_slice();
5258 selections.into_iter().map(move |selection| {
5259 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5260
5261 let mut enclosing = None;
5262 while let Some(pair_state) = regions.get(i) {
5263 if pair_state.range.end.to_offset(buffer) < range.start {
5264 regions = ®ions[i + 1..];
5265 i = 0;
5266 } else if pair_state.range.start.to_offset(buffer) > range.end {
5267 break;
5268 } else {
5269 if pair_state.selection_id == selection.id {
5270 enclosing = Some(pair_state);
5271 }
5272 i += 1;
5273 }
5274 }
5275
5276 (selection, enclosing)
5277 })
5278 }
5279
5280 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5281 fn invalidate_autoclose_regions(
5282 &mut self,
5283 mut selections: &[Selection<Anchor>],
5284 buffer: &MultiBufferSnapshot,
5285 ) {
5286 self.autoclose_regions.retain(|state| {
5287 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5288 return false;
5289 }
5290
5291 let mut i = 0;
5292 while let Some(selection) = selections.get(i) {
5293 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5294 selections = &selections[1..];
5295 continue;
5296 }
5297 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5298 break;
5299 }
5300 if selection.id == state.selection_id {
5301 return true;
5302 } else {
5303 i += 1;
5304 }
5305 }
5306 false
5307 });
5308 }
5309
5310 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5311 let offset = position.to_offset(buffer);
5312 let (word_range, kind) =
5313 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5314 if offset > word_range.start && kind == Some(CharKind::Word) {
5315 Some(
5316 buffer
5317 .text_for_range(word_range.start..offset)
5318 .collect::<String>(),
5319 )
5320 } else {
5321 None
5322 }
5323 }
5324
5325 pub fn visible_excerpts(
5326 &self,
5327 lsp_related_only: bool,
5328 cx: &mut Context<Editor>,
5329 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5330 let project = self.project().cloned();
5331 let multi_buffer = self.buffer().read(cx);
5332 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5333 let multi_buffer_visible_start = self
5334 .scroll_manager
5335 .anchor()
5336 .anchor
5337 .to_point(&multi_buffer_snapshot);
5338 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5339 multi_buffer_visible_start
5340 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5341 Bias::Left,
5342 );
5343 multi_buffer_snapshot
5344 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5345 .into_iter()
5346 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5347 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5348 if !lsp_related_only {
5349 return Some((
5350 excerpt_id,
5351 (
5352 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5353 buffer.version().clone(),
5354 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5355 ),
5356 ));
5357 }
5358
5359 let project = project.as_ref()?.read(cx);
5360 let buffer_file = project::File::from_dyn(buffer.file())?;
5361 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5362 let worktree_entry = buffer_worktree
5363 .read(cx)
5364 .entry_for_id(buffer_file.project_entry_id()?)?;
5365 if worktree_entry.is_ignored {
5366 None
5367 } else {
5368 Some((
5369 excerpt_id,
5370 (
5371 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5372 buffer.version().clone(),
5373 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5374 ),
5375 ))
5376 }
5377 })
5378 .collect()
5379 }
5380
5381 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5382 TextLayoutDetails {
5383 text_system: window.text_system().clone(),
5384 editor_style: self.style.clone().unwrap(),
5385 rem_size: window.rem_size(),
5386 scroll_anchor: self.scroll_manager.anchor(),
5387 visible_rows: self.visible_line_count(),
5388 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5389 }
5390 }
5391
5392 fn trigger_on_type_formatting(
5393 &self,
5394 input: String,
5395 window: &mut Window,
5396 cx: &mut Context<Self>,
5397 ) -> Option<Task<Result<()>>> {
5398 if input.len() != 1 {
5399 return None;
5400 }
5401
5402 let project = self.project()?;
5403 let position = self.selections.newest_anchor().head();
5404 let (buffer, buffer_position) = self
5405 .buffer
5406 .read(cx)
5407 .text_anchor_for_position(position, cx)?;
5408
5409 let settings = language_settings::language_settings(
5410 buffer
5411 .read(cx)
5412 .language_at(buffer_position)
5413 .map(|l| l.name()),
5414 buffer.read(cx).file(),
5415 cx,
5416 );
5417 if !settings.use_on_type_format {
5418 return None;
5419 }
5420
5421 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5422 // hence we do LSP request & edit on host side only — add formats to host's history.
5423 let push_to_lsp_host_history = true;
5424 // If this is not the host, append its history with new edits.
5425 let push_to_client_history = project.read(cx).is_via_collab();
5426
5427 let on_type_formatting = project.update(cx, |project, cx| {
5428 project.on_type_format(
5429 buffer.clone(),
5430 buffer_position,
5431 input,
5432 push_to_lsp_host_history,
5433 cx,
5434 )
5435 });
5436 Some(cx.spawn_in(window, async move |editor, cx| {
5437 if let Some(transaction) = on_type_formatting.await? {
5438 if push_to_client_history {
5439 buffer
5440 .update(cx, |buffer, _| {
5441 buffer.push_transaction(transaction, Instant::now());
5442 buffer.finalize_last_transaction();
5443 })
5444 .ok();
5445 }
5446 editor.update(cx, |editor, cx| {
5447 editor.refresh_document_highlights(cx);
5448 })?;
5449 }
5450 Ok(())
5451 }))
5452 }
5453
5454 pub fn show_word_completions(
5455 &mut self,
5456 _: &ShowWordCompletions,
5457 window: &mut Window,
5458 cx: &mut Context<Self>,
5459 ) {
5460 self.open_or_update_completions_menu(
5461 Some(CompletionsMenuSource::Words {
5462 ignore_threshold: true,
5463 }),
5464 None,
5465 false,
5466 window,
5467 cx,
5468 );
5469 }
5470
5471 pub fn show_completions(
5472 &mut self,
5473 _: &ShowCompletions,
5474 window: &mut Window,
5475 cx: &mut Context<Self>,
5476 ) {
5477 self.open_or_update_completions_menu(None, None, false, window, cx);
5478 }
5479
5480 fn open_or_update_completions_menu(
5481 &mut self,
5482 requested_source: Option<CompletionsMenuSource>,
5483 trigger: Option<String>,
5484 trigger_in_words: bool,
5485 window: &mut Window,
5486 cx: &mut Context<Self>,
5487 ) {
5488 if self.pending_rename.is_some() {
5489 return;
5490 }
5491
5492 let completions_source = self
5493 .context_menu
5494 .borrow()
5495 .as_ref()
5496 .and_then(|menu| match menu {
5497 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5498 CodeContextMenu::CodeActions(_) => None,
5499 });
5500
5501 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5502
5503 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5504 // inserted and selected. To handle that case, the start of the selection is used so that
5505 // the menu starts with all choices.
5506 let position = self
5507 .selections
5508 .newest_anchor()
5509 .start
5510 .bias_right(&multibuffer_snapshot);
5511 if position.diff_base_anchor.is_some() {
5512 return;
5513 }
5514 let buffer_position = multibuffer_snapshot.anchor_before(position);
5515 let Some(buffer) = buffer_position
5516 .text_anchor
5517 .buffer_id
5518 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5519 else {
5520 return;
5521 };
5522 let buffer_snapshot = buffer.read(cx).snapshot();
5523
5524 let menu_is_open = matches!(
5525 self.context_menu.borrow().as_ref(),
5526 Some(CodeContextMenu::Completions(_))
5527 );
5528
5529 let language = buffer_snapshot
5530 .language_at(buffer_position.text_anchor)
5531 .map(|language| language.name());
5532
5533 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5534 let completion_settings = language_settings.completions.clone();
5535
5536 if !menu_is_open && trigger.is_some() && !language_settings.show_completions_on_input {
5537 return;
5538 }
5539
5540 let query: Option<Arc<String>> =
5541 Self::completion_query(&multibuffer_snapshot, buffer_position)
5542 .map(|query| query.into());
5543
5544 drop(multibuffer_snapshot);
5545
5546 // Hide the current completions menu when query is empty. Without this, cached
5547 // completions from before the trigger char may be reused (#32774).
5548 if query.is_none() && menu_is_open {
5549 self.hide_context_menu(window, cx);
5550 }
5551
5552 let mut ignore_word_threshold = false;
5553 let provider = match requested_source {
5554 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5555 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5556 ignore_word_threshold = ignore_threshold;
5557 None
5558 }
5559 Some(CompletionsMenuSource::SnippetChoices)
5560 | Some(CompletionsMenuSource::SnippetsOnly) => {
5561 log::error!("bug: SnippetChoices requested_source is not handled");
5562 None
5563 }
5564 };
5565
5566 let sort_completions = provider
5567 .as_ref()
5568 .is_some_and(|provider| provider.sort_completions());
5569
5570 let filter_completions = provider
5571 .as_ref()
5572 .is_none_or(|provider| provider.filter_completions());
5573
5574 let was_snippets_only = matches!(
5575 completions_source,
5576 Some(CompletionsMenuSource::SnippetsOnly)
5577 );
5578
5579 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5580 if filter_completions {
5581 menu.filter(
5582 query.clone().unwrap_or_default(),
5583 buffer_position.text_anchor,
5584 &buffer,
5585 provider.clone(),
5586 window,
5587 cx,
5588 );
5589 }
5590 // When `is_incomplete` is false, no need to re-query completions when the current query
5591 // is a suffix of the initial query.
5592 let was_complete = !menu.is_incomplete;
5593 if was_complete && !was_snippets_only {
5594 // If the new query is a suffix of the old query (typing more characters) and
5595 // the previous result was complete, the existing completions can be filtered.
5596 //
5597 // Note that snippet completions are always complete.
5598 let query_matches = match (&menu.initial_query, &query) {
5599 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5600 (None, _) => true,
5601 _ => false,
5602 };
5603 if query_matches {
5604 let position_matches = if menu.initial_position == position {
5605 true
5606 } else {
5607 let snapshot = self.buffer.read(cx).read(cx);
5608 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5609 };
5610 if position_matches {
5611 return;
5612 }
5613 }
5614 }
5615 };
5616
5617 let Anchor {
5618 excerpt_id: buffer_excerpt_id,
5619 text_anchor: buffer_position,
5620 ..
5621 } = buffer_position;
5622
5623 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5624 buffer_snapshot.surrounding_word(buffer_position, None)
5625 {
5626 let word_to_exclude = buffer_snapshot
5627 .text_for_range(word_range.clone())
5628 .collect::<String>();
5629 (
5630 buffer_snapshot.anchor_before(word_range.start)
5631 ..buffer_snapshot.anchor_after(buffer_position),
5632 Some(word_to_exclude),
5633 )
5634 } else {
5635 (buffer_position..buffer_position, None)
5636 };
5637
5638 let show_completion_documentation = buffer_snapshot
5639 .settings_at(buffer_position, cx)
5640 .show_completion_documentation;
5641
5642 // The document can be large, so stay in reasonable bounds when searching for words,
5643 // otherwise completion pop-up might be slow to appear.
5644 const WORD_LOOKUP_ROWS: u32 = 5_000;
5645 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5646 let min_word_search = buffer_snapshot.clip_point(
5647 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5648 Bias::Left,
5649 );
5650 let max_word_search = buffer_snapshot.clip_point(
5651 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5652 Bias::Right,
5653 );
5654 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5655 ..buffer_snapshot.point_to_offset(max_word_search);
5656
5657 let skip_digits = query
5658 .as_ref()
5659 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5660
5661 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5662 trigger.as_ref().is_none_or(|trigger| {
5663 provider.is_completion_trigger(
5664 &buffer,
5665 position.text_anchor,
5666 trigger,
5667 trigger_in_words,
5668 cx,
5669 )
5670 })
5671 });
5672
5673 let provider_responses = if let Some(provider) = &provider
5674 && load_provider_completions
5675 {
5676 let trigger_character =
5677 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5678 let completion_context = CompletionContext {
5679 trigger_kind: match &trigger_character {
5680 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5681 None => CompletionTriggerKind::INVOKED,
5682 },
5683 trigger_character,
5684 };
5685
5686 provider.completions(
5687 buffer_excerpt_id,
5688 &buffer,
5689 buffer_position,
5690 completion_context,
5691 window,
5692 cx,
5693 )
5694 } else {
5695 Task::ready(Ok(Vec::new()))
5696 };
5697
5698 let load_word_completions = if !self.word_completions_enabled {
5699 false
5700 } else if requested_source
5701 == Some(CompletionsMenuSource::Words {
5702 ignore_threshold: true,
5703 })
5704 {
5705 true
5706 } else {
5707 load_provider_completions
5708 && completion_settings.words != WordsCompletionMode::Disabled
5709 && (ignore_word_threshold || {
5710 let words_min_length = completion_settings.words_min_length;
5711 // check whether word has at least `words_min_length` characters
5712 let query_chars = query.iter().flat_map(|q| q.chars());
5713 query_chars.take(words_min_length).count() == words_min_length
5714 })
5715 };
5716
5717 let mut words = if load_word_completions {
5718 cx.background_spawn({
5719 let buffer_snapshot = buffer_snapshot.clone();
5720 async move {
5721 buffer_snapshot.words_in_range(WordsQuery {
5722 fuzzy_contents: None,
5723 range: word_search_range,
5724 skip_digits,
5725 })
5726 }
5727 })
5728 } else {
5729 Task::ready(BTreeMap::default())
5730 };
5731
5732 let snippets = if let Some(provider) = &provider
5733 && provider.show_snippets()
5734 && let Some(project) = self.project()
5735 {
5736 let char_classifier = buffer_snapshot
5737 .char_classifier_at(buffer_position)
5738 .scope_context(Some(CharScopeContext::Completion));
5739 project.update(cx, |project, cx| {
5740 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5741 })
5742 } else {
5743 Task::ready(Ok(CompletionResponse {
5744 completions: Vec::new(),
5745 display_options: Default::default(),
5746 is_incomplete: false,
5747 }))
5748 };
5749
5750 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5751
5752 let id = post_inc(&mut self.next_completion_id);
5753 let task = cx.spawn_in(window, async move |editor, cx| {
5754 let Ok(()) = editor.update(cx, |this, _| {
5755 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5756 }) else {
5757 return;
5758 };
5759
5760 // TODO: Ideally completions from different sources would be selectively re-queried, so
5761 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5762 let mut completions = Vec::new();
5763 let mut is_incomplete = false;
5764 let mut display_options: Option<CompletionDisplayOptions> = None;
5765 if let Some(provider_responses) = provider_responses.await.log_err()
5766 && !provider_responses.is_empty()
5767 {
5768 for response in provider_responses {
5769 completions.extend(response.completions);
5770 is_incomplete = is_incomplete || response.is_incomplete;
5771 match display_options.as_mut() {
5772 None => {
5773 display_options = Some(response.display_options);
5774 }
5775 Some(options) => options.merge(&response.display_options),
5776 }
5777 }
5778 if completion_settings.words == WordsCompletionMode::Fallback {
5779 words = Task::ready(BTreeMap::default());
5780 }
5781 }
5782 let display_options = display_options.unwrap_or_default();
5783
5784 let mut words = words.await;
5785 if let Some(word_to_exclude) = &word_to_exclude {
5786 words.remove(word_to_exclude);
5787 }
5788 for lsp_completion in &completions {
5789 words.remove(&lsp_completion.new_text);
5790 }
5791 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5792 replace_range: word_replace_range.clone(),
5793 new_text: word.clone(),
5794 label: CodeLabel::plain(word, None),
5795 match_start: None,
5796 snippet_deduplication_key: None,
5797 icon_path: None,
5798 documentation: None,
5799 source: CompletionSource::BufferWord {
5800 word_range,
5801 resolved: false,
5802 },
5803 insert_text_mode: Some(InsertTextMode::AS_IS),
5804 confirm: None,
5805 }));
5806
5807 completions.extend(
5808 snippets
5809 .await
5810 .into_iter()
5811 .flat_map(|response| response.completions),
5812 );
5813
5814 let menu = if completions.is_empty() {
5815 None
5816 } else {
5817 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5818 let languages = editor
5819 .workspace
5820 .as_ref()
5821 .and_then(|(workspace, _)| workspace.upgrade())
5822 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5823 let menu = CompletionsMenu::new(
5824 id,
5825 requested_source.unwrap_or(if load_provider_completions {
5826 CompletionsMenuSource::Normal
5827 } else {
5828 CompletionsMenuSource::SnippetsOnly
5829 }),
5830 sort_completions,
5831 show_completion_documentation,
5832 position,
5833 query.clone(),
5834 is_incomplete,
5835 buffer.clone(),
5836 completions.into(),
5837 display_options,
5838 snippet_sort_order,
5839 languages,
5840 language,
5841 cx,
5842 );
5843
5844 let query = if filter_completions { query } else { None };
5845 let matches_task = menu.do_async_filtering(
5846 query.unwrap_or_default(),
5847 buffer_position,
5848 &buffer,
5849 cx,
5850 );
5851 (menu, matches_task)
5852 }) else {
5853 return;
5854 };
5855
5856 let matches = matches_task.await;
5857
5858 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5859 // Newer menu already set, so exit.
5860 if let Some(CodeContextMenu::Completions(prev_menu)) =
5861 editor.context_menu.borrow().as_ref()
5862 && prev_menu.id > id
5863 {
5864 return;
5865 };
5866
5867 // Only valid to take prev_menu because either the new menu is immediately set
5868 // below, or the menu is hidden.
5869 if let Some(CodeContextMenu::Completions(prev_menu)) =
5870 editor.context_menu.borrow_mut().take()
5871 {
5872 let position_matches =
5873 if prev_menu.initial_position == menu.initial_position {
5874 true
5875 } else {
5876 let snapshot = editor.buffer.read(cx).read(cx);
5877 prev_menu.initial_position.to_offset(&snapshot)
5878 == menu.initial_position.to_offset(&snapshot)
5879 };
5880 if position_matches {
5881 // Preserve markdown cache before `set_filter_results` because it will
5882 // try to populate the documentation cache.
5883 menu.preserve_markdown_cache(prev_menu);
5884 }
5885 };
5886
5887 menu.set_filter_results(matches, provider, window, cx);
5888 }) else {
5889 return;
5890 };
5891
5892 menu.visible().then_some(menu)
5893 };
5894
5895 editor
5896 .update_in(cx, |editor, window, cx| {
5897 if editor.focus_handle.is_focused(window)
5898 && let Some(menu) = menu
5899 {
5900 *editor.context_menu.borrow_mut() =
5901 Some(CodeContextMenu::Completions(menu));
5902
5903 crate::hover_popover::hide_hover(editor, cx);
5904 if editor.show_edit_predictions_in_menu() {
5905 editor.update_visible_edit_prediction(window, cx);
5906 } else {
5907 editor.discard_edit_prediction(false, cx);
5908 }
5909
5910 cx.notify();
5911 return;
5912 }
5913
5914 if editor.completion_tasks.len() <= 1 {
5915 // If there are no more completion tasks and the last menu was empty, we should hide it.
5916 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5917 // If it was already hidden and we don't show edit predictions in the menu,
5918 // we should also show the edit prediction when available.
5919 if was_hidden && editor.show_edit_predictions_in_menu() {
5920 editor.update_visible_edit_prediction(window, cx);
5921 }
5922 }
5923 })
5924 .ok();
5925 });
5926
5927 self.completion_tasks.push((id, task));
5928 }
5929
5930 #[cfg(feature = "test-support")]
5931 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5932 let menu = self.context_menu.borrow();
5933 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5934 let completions = menu.completions.borrow();
5935 Some(completions.to_vec())
5936 } else {
5937 None
5938 }
5939 }
5940
5941 pub fn with_completions_menu_matching_id<R>(
5942 &self,
5943 id: CompletionId,
5944 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5945 ) -> R {
5946 let mut context_menu = self.context_menu.borrow_mut();
5947 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5948 return f(None);
5949 };
5950 if completions_menu.id != id {
5951 return f(None);
5952 }
5953 f(Some(completions_menu))
5954 }
5955
5956 pub fn confirm_completion(
5957 &mut self,
5958 action: &ConfirmCompletion,
5959 window: &mut Window,
5960 cx: &mut Context<Self>,
5961 ) -> Option<Task<Result<()>>> {
5962 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5963 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5964 }
5965
5966 pub fn confirm_completion_insert(
5967 &mut self,
5968 _: &ConfirmCompletionInsert,
5969 window: &mut Window,
5970 cx: &mut Context<Self>,
5971 ) -> Option<Task<Result<()>>> {
5972 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5973 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5974 }
5975
5976 pub fn confirm_completion_replace(
5977 &mut self,
5978 _: &ConfirmCompletionReplace,
5979 window: &mut Window,
5980 cx: &mut Context<Self>,
5981 ) -> Option<Task<Result<()>>> {
5982 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5983 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5984 }
5985
5986 pub fn compose_completion(
5987 &mut self,
5988 action: &ComposeCompletion,
5989 window: &mut Window,
5990 cx: &mut Context<Self>,
5991 ) -> Option<Task<Result<()>>> {
5992 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5993 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5994 }
5995
5996 fn do_completion(
5997 &mut self,
5998 item_ix: Option<usize>,
5999 intent: CompletionIntent,
6000 window: &mut Window,
6001 cx: &mut Context<Editor>,
6002 ) -> Option<Task<Result<()>>> {
6003 use language::ToOffset as _;
6004
6005 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6006 else {
6007 return None;
6008 };
6009
6010 let candidate_id = {
6011 let entries = completions_menu.entries.borrow();
6012 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6013 if self.show_edit_predictions_in_menu() {
6014 self.discard_edit_prediction(true, cx);
6015 }
6016 mat.candidate_id
6017 };
6018
6019 let completion = completions_menu
6020 .completions
6021 .borrow()
6022 .get(candidate_id)?
6023 .clone();
6024 cx.stop_propagation();
6025
6026 let buffer_handle = completions_menu.buffer.clone();
6027
6028 let CompletionEdit {
6029 new_text,
6030 snippet,
6031 replace_range,
6032 } = process_completion_for_edit(
6033 &completion,
6034 intent,
6035 &buffer_handle,
6036 &completions_menu.initial_position.text_anchor,
6037 cx,
6038 );
6039
6040 let buffer = buffer_handle.read(cx);
6041 let snapshot = self.buffer.read(cx).snapshot(cx);
6042 let newest_anchor = self.selections.newest_anchor();
6043 let replace_range_multibuffer = {
6044 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6045 excerpt.map_range_from_buffer(replace_range.clone())
6046 };
6047 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6048 return None;
6049 }
6050
6051 let old_text = buffer
6052 .text_for_range(replace_range.clone())
6053 .collect::<String>();
6054 let lookbehind = newest_anchor
6055 .start
6056 .text_anchor
6057 .to_offset(buffer)
6058 .saturating_sub(replace_range.start.0);
6059 let lookahead = replace_range
6060 .end
6061 .0
6062 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6063 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6064 let suffix = &old_text[lookbehind.min(old_text.len())..];
6065
6066 let selections = self
6067 .selections
6068 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6069 let mut ranges = Vec::new();
6070 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6071
6072 for selection in &selections {
6073 let range = if selection.id == newest_anchor.id {
6074 replace_range_multibuffer.clone()
6075 } else {
6076 let mut range = selection.range();
6077
6078 // if prefix is present, don't duplicate it
6079 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6080 range.start = range.start.saturating_sub_usize(lookbehind);
6081
6082 // if suffix is also present, mimic the newest cursor and replace it
6083 if selection.id != newest_anchor.id
6084 && snapshot.contains_str_at(range.end, suffix)
6085 {
6086 range.end += lookahead;
6087 }
6088 }
6089 range
6090 };
6091
6092 ranges.push(range.clone());
6093
6094 if !self.linked_edit_ranges.is_empty() {
6095 let start_anchor = snapshot.anchor_before(range.start);
6096 let end_anchor = snapshot.anchor_after(range.end);
6097 if let Some(ranges) = self
6098 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6099 {
6100 for (buffer, edits) in ranges {
6101 linked_edits
6102 .entry(buffer.clone())
6103 .or_default()
6104 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6105 }
6106 }
6107 }
6108 }
6109
6110 let common_prefix_len = old_text
6111 .chars()
6112 .zip(new_text.chars())
6113 .take_while(|(a, b)| a == b)
6114 .map(|(a, _)| a.len_utf8())
6115 .sum::<usize>();
6116
6117 cx.emit(EditorEvent::InputHandled {
6118 utf16_range_to_replace: None,
6119 text: new_text[common_prefix_len..].into(),
6120 });
6121
6122 self.transact(window, cx, |editor, window, cx| {
6123 if let Some(mut snippet) = snippet {
6124 snippet.text = new_text.to_string();
6125 editor
6126 .insert_snippet(&ranges, snippet, window, cx)
6127 .log_err();
6128 } else {
6129 editor.buffer.update(cx, |multi_buffer, cx| {
6130 let auto_indent = match completion.insert_text_mode {
6131 Some(InsertTextMode::AS_IS) => None,
6132 _ => editor.autoindent_mode.clone(),
6133 };
6134 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6135 multi_buffer.edit(edits, auto_indent, cx);
6136 });
6137 }
6138 for (buffer, edits) in linked_edits {
6139 buffer.update(cx, |buffer, cx| {
6140 let snapshot = buffer.snapshot();
6141 let edits = edits
6142 .into_iter()
6143 .map(|(range, text)| {
6144 use text::ToPoint as TP;
6145 let end_point = TP::to_point(&range.end, &snapshot);
6146 let start_point = TP::to_point(&range.start, &snapshot);
6147 (start_point..end_point, text)
6148 })
6149 .sorted_by_key(|(range, _)| range.start);
6150 buffer.edit(edits, None, cx);
6151 })
6152 }
6153
6154 editor.refresh_edit_prediction(true, false, window, cx);
6155 });
6156 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6157
6158 let show_new_completions_on_confirm = completion
6159 .confirm
6160 .as_ref()
6161 .is_some_and(|confirm| confirm(intent, window, cx));
6162 if show_new_completions_on_confirm {
6163 self.open_or_update_completions_menu(None, None, false, window, cx);
6164 }
6165
6166 let provider = self.completion_provider.as_ref()?;
6167
6168 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6169 let command = lsp_store.as_ref().and_then(|lsp_store| {
6170 let CompletionSource::Lsp {
6171 lsp_completion,
6172 server_id,
6173 ..
6174 } = &completion.source
6175 else {
6176 return None;
6177 };
6178 let lsp_command = lsp_completion.command.as_ref()?;
6179 let available_commands = lsp_store
6180 .read(cx)
6181 .lsp_server_capabilities
6182 .get(server_id)
6183 .and_then(|server_capabilities| {
6184 server_capabilities
6185 .execute_command_provider
6186 .as_ref()
6187 .map(|options| options.commands.as_slice())
6188 })?;
6189 if available_commands.contains(&lsp_command.command) {
6190 Some(CodeAction {
6191 server_id: *server_id,
6192 range: language::Anchor::MIN..language::Anchor::MIN,
6193 lsp_action: LspAction::Command(lsp_command.clone()),
6194 resolved: false,
6195 })
6196 } else {
6197 None
6198 }
6199 });
6200
6201 drop(completion);
6202 let apply_edits = provider.apply_additional_edits_for_completion(
6203 buffer_handle.clone(),
6204 completions_menu.completions.clone(),
6205 candidate_id,
6206 true,
6207 cx,
6208 );
6209
6210 let editor_settings = EditorSettings::get_global(cx);
6211 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6212 // After the code completion is finished, users often want to know what signatures are needed.
6213 // so we should automatically call signature_help
6214 self.show_signature_help(&ShowSignatureHelp, window, cx);
6215 }
6216
6217 Some(cx.spawn_in(window, async move |editor, cx| {
6218 apply_edits.await?;
6219
6220 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6221 let title = command.lsp_action.title().to_owned();
6222 let project_transaction = lsp_store
6223 .update(cx, |lsp_store, cx| {
6224 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6225 })?
6226 .await
6227 .context("applying post-completion command")?;
6228 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6229 Self::open_project_transaction(
6230 &editor,
6231 workspace.downgrade(),
6232 project_transaction,
6233 title,
6234 cx,
6235 )
6236 .await?;
6237 }
6238 }
6239
6240 Ok(())
6241 }))
6242 }
6243
6244 pub fn toggle_code_actions(
6245 &mut self,
6246 action: &ToggleCodeActions,
6247 window: &mut Window,
6248 cx: &mut Context<Self>,
6249 ) {
6250 let quick_launch = action.quick_launch;
6251 let mut context_menu = self.context_menu.borrow_mut();
6252 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6253 if code_actions.deployed_from == action.deployed_from {
6254 // Toggle if we're selecting the same one
6255 *context_menu = None;
6256 cx.notify();
6257 return;
6258 } else {
6259 // Otherwise, clear it and start a new one
6260 *context_menu = None;
6261 cx.notify();
6262 }
6263 }
6264 drop(context_menu);
6265 let snapshot = self.snapshot(window, cx);
6266 let deployed_from = action.deployed_from.clone();
6267 let action = action.clone();
6268 self.completion_tasks.clear();
6269 self.discard_edit_prediction(false, cx);
6270
6271 let multibuffer_point = match &action.deployed_from {
6272 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6273 DisplayPoint::new(*row, 0).to_point(&snapshot)
6274 }
6275 _ => self
6276 .selections
6277 .newest::<Point>(&snapshot.display_snapshot)
6278 .head(),
6279 };
6280 let Some((buffer, buffer_row)) = snapshot
6281 .buffer_snapshot()
6282 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6283 .and_then(|(buffer_snapshot, range)| {
6284 self.buffer()
6285 .read(cx)
6286 .buffer(buffer_snapshot.remote_id())
6287 .map(|buffer| (buffer, range.start.row))
6288 })
6289 else {
6290 return;
6291 };
6292 let buffer_id = buffer.read(cx).remote_id();
6293 let tasks = self
6294 .tasks
6295 .get(&(buffer_id, buffer_row))
6296 .map(|t| Arc::new(t.to_owned()));
6297
6298 if !self.focus_handle.is_focused(window) {
6299 return;
6300 }
6301 let project = self.project.clone();
6302
6303 let code_actions_task = match deployed_from {
6304 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6305 _ => self.code_actions(buffer_row, window, cx),
6306 };
6307
6308 let runnable_task = match deployed_from {
6309 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6310 _ => {
6311 let mut task_context_task = Task::ready(None);
6312 if let Some(tasks) = &tasks
6313 && let Some(project) = project
6314 {
6315 task_context_task =
6316 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6317 }
6318
6319 cx.spawn_in(window, {
6320 let buffer = buffer.clone();
6321 async move |editor, cx| {
6322 let task_context = task_context_task.await;
6323
6324 let resolved_tasks =
6325 tasks
6326 .zip(task_context.clone())
6327 .map(|(tasks, task_context)| ResolvedTasks {
6328 templates: tasks.resolve(&task_context).collect(),
6329 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6330 multibuffer_point.row,
6331 tasks.column,
6332 )),
6333 });
6334 let debug_scenarios = editor
6335 .update(cx, |editor, cx| {
6336 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6337 })?
6338 .await;
6339 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6340 }
6341 })
6342 }
6343 };
6344
6345 cx.spawn_in(window, async move |editor, cx| {
6346 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6347 let code_actions = code_actions_task.await;
6348 let spawn_straight_away = quick_launch
6349 && resolved_tasks
6350 .as_ref()
6351 .is_some_and(|tasks| tasks.templates.len() == 1)
6352 && code_actions
6353 .as_ref()
6354 .is_none_or(|actions| actions.is_empty())
6355 && debug_scenarios.is_empty();
6356
6357 editor.update_in(cx, |editor, window, cx| {
6358 crate::hover_popover::hide_hover(editor, cx);
6359 let actions = CodeActionContents::new(
6360 resolved_tasks,
6361 code_actions,
6362 debug_scenarios,
6363 task_context.unwrap_or_default(),
6364 );
6365
6366 // Don't show the menu if there are no actions available
6367 if actions.is_empty() {
6368 cx.notify();
6369 return Task::ready(Ok(()));
6370 }
6371
6372 *editor.context_menu.borrow_mut() =
6373 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6374 buffer,
6375 actions,
6376 selected_item: Default::default(),
6377 scroll_handle: UniformListScrollHandle::default(),
6378 deployed_from,
6379 }));
6380 cx.notify();
6381 if spawn_straight_away
6382 && let Some(task) = editor.confirm_code_action(
6383 &ConfirmCodeAction { item_ix: Some(0) },
6384 window,
6385 cx,
6386 )
6387 {
6388 return task;
6389 }
6390
6391 Task::ready(Ok(()))
6392 })
6393 })
6394 .detach_and_log_err(cx);
6395 }
6396
6397 fn debug_scenarios(
6398 &mut self,
6399 resolved_tasks: &Option<ResolvedTasks>,
6400 buffer: &Entity<Buffer>,
6401 cx: &mut App,
6402 ) -> Task<Vec<task::DebugScenario>> {
6403 maybe!({
6404 let project = self.project()?;
6405 let dap_store = project.read(cx).dap_store();
6406 let mut scenarios = vec![];
6407 let resolved_tasks = resolved_tasks.as_ref()?;
6408 let buffer = buffer.read(cx);
6409 let language = buffer.language()?;
6410 let file = buffer.file();
6411 let debug_adapter = language_settings(language.name().into(), file, cx)
6412 .debuggers
6413 .first()
6414 .map(SharedString::from)
6415 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6416
6417 dap_store.update(cx, |dap_store, cx| {
6418 for (_, task) in &resolved_tasks.templates {
6419 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6420 task.original_task().clone(),
6421 debug_adapter.clone().into(),
6422 task.display_label().to_owned().into(),
6423 cx,
6424 );
6425 scenarios.push(maybe_scenario);
6426 }
6427 });
6428 Some(cx.background_spawn(async move {
6429 futures::future::join_all(scenarios)
6430 .await
6431 .into_iter()
6432 .flatten()
6433 .collect::<Vec<_>>()
6434 }))
6435 })
6436 .unwrap_or_else(|| Task::ready(vec![]))
6437 }
6438
6439 fn code_actions(
6440 &mut self,
6441 buffer_row: u32,
6442 window: &mut Window,
6443 cx: &mut Context<Self>,
6444 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6445 let mut task = self.code_actions_task.take();
6446 cx.spawn_in(window, async move |editor, cx| {
6447 while let Some(prev_task) = task {
6448 prev_task.await.log_err();
6449 task = editor
6450 .update(cx, |this, _| this.code_actions_task.take())
6451 .ok()?;
6452 }
6453
6454 editor
6455 .update(cx, |editor, cx| {
6456 editor
6457 .available_code_actions
6458 .clone()
6459 .and_then(|(location, code_actions)| {
6460 let snapshot = location.buffer.read(cx).snapshot();
6461 let point_range = location.range.to_point(&snapshot);
6462 let point_range = point_range.start.row..=point_range.end.row;
6463 if point_range.contains(&buffer_row) {
6464 Some(code_actions)
6465 } else {
6466 None
6467 }
6468 })
6469 })
6470 .ok()
6471 .flatten()
6472 })
6473 }
6474
6475 pub fn confirm_code_action(
6476 &mut self,
6477 action: &ConfirmCodeAction,
6478 window: &mut Window,
6479 cx: &mut Context<Self>,
6480 ) -> Option<Task<Result<()>>> {
6481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6482
6483 let actions_menu =
6484 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6485 menu
6486 } else {
6487 return None;
6488 };
6489
6490 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6491 let action = actions_menu.actions.get(action_ix)?;
6492 let title = action.label();
6493 let buffer = actions_menu.buffer;
6494 let workspace = self.workspace()?;
6495
6496 match action {
6497 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6498 workspace.update(cx, |workspace, cx| {
6499 workspace.schedule_resolved_task(
6500 task_source_kind,
6501 resolved_task,
6502 false,
6503 window,
6504 cx,
6505 );
6506
6507 Some(Task::ready(Ok(())))
6508 })
6509 }
6510 CodeActionsItem::CodeAction {
6511 excerpt_id,
6512 action,
6513 provider,
6514 } => {
6515 let apply_code_action =
6516 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6517 let workspace = workspace.downgrade();
6518 Some(cx.spawn_in(window, async move |editor, cx| {
6519 let project_transaction = apply_code_action.await?;
6520 Self::open_project_transaction(
6521 &editor,
6522 workspace,
6523 project_transaction,
6524 title,
6525 cx,
6526 )
6527 .await
6528 }))
6529 }
6530 CodeActionsItem::DebugScenario(scenario) => {
6531 let context = actions_menu.actions.context;
6532
6533 workspace.update(cx, |workspace, cx| {
6534 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6535 workspace.start_debug_session(
6536 scenario,
6537 context,
6538 Some(buffer),
6539 None,
6540 window,
6541 cx,
6542 );
6543 });
6544 Some(Task::ready(Ok(())))
6545 }
6546 }
6547 }
6548
6549 pub async fn open_project_transaction(
6550 editor: &WeakEntity<Editor>,
6551 workspace: WeakEntity<Workspace>,
6552 transaction: ProjectTransaction,
6553 title: String,
6554 cx: &mut AsyncWindowContext,
6555 ) -> Result<()> {
6556 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6557 cx.update(|_, cx| {
6558 entries.sort_unstable_by_key(|(buffer, _)| {
6559 buffer.read(cx).file().map(|f| f.path().clone())
6560 });
6561 })?;
6562 if entries.is_empty() {
6563 return Ok(());
6564 }
6565
6566 // If the project transaction's edits are all contained within this editor, then
6567 // avoid opening a new editor to display them.
6568
6569 if let [(buffer, transaction)] = &*entries {
6570 let excerpt = editor.update(cx, |editor, cx| {
6571 editor
6572 .buffer()
6573 .read(cx)
6574 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6575 })?;
6576 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6577 && excerpted_buffer == *buffer
6578 {
6579 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6580 let excerpt_range = excerpt_range.to_offset(buffer);
6581 buffer
6582 .edited_ranges_for_transaction::<usize>(transaction)
6583 .all(|range| {
6584 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6585 })
6586 })?;
6587
6588 if all_edits_within_excerpt {
6589 return Ok(());
6590 }
6591 }
6592 }
6593
6594 let mut ranges_to_highlight = Vec::new();
6595 let excerpt_buffer = cx.new(|cx| {
6596 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6597 for (buffer_handle, transaction) in &entries {
6598 let edited_ranges = buffer_handle
6599 .read(cx)
6600 .edited_ranges_for_transaction::<Point>(transaction)
6601 .collect::<Vec<_>>();
6602 let (ranges, _) = multibuffer.set_excerpts_for_path(
6603 PathKey::for_buffer(buffer_handle, cx),
6604 buffer_handle.clone(),
6605 edited_ranges,
6606 multibuffer_context_lines(cx),
6607 cx,
6608 );
6609
6610 ranges_to_highlight.extend(ranges);
6611 }
6612 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6613 multibuffer
6614 })?;
6615
6616 workspace.update_in(cx, |workspace, window, cx| {
6617 let project = workspace.project().clone();
6618 let editor =
6619 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6620 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6621 editor.update(cx, |editor, cx| {
6622 editor.highlight_background::<Self>(
6623 &ranges_to_highlight,
6624 |_, theme| theme.colors().editor_highlighted_line_background,
6625 cx,
6626 );
6627 });
6628 })?;
6629
6630 Ok(())
6631 }
6632
6633 pub fn clear_code_action_providers(&mut self) {
6634 self.code_action_providers.clear();
6635 self.available_code_actions.take();
6636 }
6637
6638 pub fn add_code_action_provider(
6639 &mut self,
6640 provider: Rc<dyn CodeActionProvider>,
6641 window: &mut Window,
6642 cx: &mut Context<Self>,
6643 ) {
6644 if self
6645 .code_action_providers
6646 .iter()
6647 .any(|existing_provider| existing_provider.id() == provider.id())
6648 {
6649 return;
6650 }
6651
6652 self.code_action_providers.push(provider);
6653 self.refresh_code_actions(window, cx);
6654 }
6655
6656 pub fn remove_code_action_provider(
6657 &mut self,
6658 id: Arc<str>,
6659 window: &mut Window,
6660 cx: &mut Context<Self>,
6661 ) {
6662 self.code_action_providers
6663 .retain(|provider| provider.id() != id);
6664 self.refresh_code_actions(window, cx);
6665 }
6666
6667 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6668 !self.code_action_providers.is_empty()
6669 && EditorSettings::get_global(cx).toolbar.code_actions
6670 }
6671
6672 pub fn has_available_code_actions(&self) -> bool {
6673 self.available_code_actions
6674 .as_ref()
6675 .is_some_and(|(_, actions)| !actions.is_empty())
6676 }
6677
6678 fn render_inline_code_actions(
6679 &self,
6680 icon_size: ui::IconSize,
6681 display_row: DisplayRow,
6682 is_active: bool,
6683 cx: &mut Context<Self>,
6684 ) -> AnyElement {
6685 let show_tooltip = !self.context_menu_visible();
6686 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6687 .icon_size(icon_size)
6688 .shape(ui::IconButtonShape::Square)
6689 .icon_color(ui::Color::Hidden)
6690 .toggle_state(is_active)
6691 .when(show_tooltip, |this| {
6692 this.tooltip({
6693 let focus_handle = self.focus_handle.clone();
6694 move |_window, cx| {
6695 Tooltip::for_action_in(
6696 "Toggle Code Actions",
6697 &ToggleCodeActions {
6698 deployed_from: None,
6699 quick_launch: false,
6700 },
6701 &focus_handle,
6702 cx,
6703 )
6704 }
6705 })
6706 })
6707 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6708 window.focus(&editor.focus_handle(cx));
6709 editor.toggle_code_actions(
6710 &crate::actions::ToggleCodeActions {
6711 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6712 display_row,
6713 )),
6714 quick_launch: false,
6715 },
6716 window,
6717 cx,
6718 );
6719 }))
6720 .into_any_element()
6721 }
6722
6723 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6724 &self.context_menu
6725 }
6726
6727 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6728 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6729 cx.background_executor()
6730 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6731 .await;
6732
6733 let (start_buffer, start, _, end, newest_selection) = this
6734 .update(cx, |this, cx| {
6735 let newest_selection = this.selections.newest_anchor().clone();
6736 if newest_selection.head().diff_base_anchor.is_some() {
6737 return None;
6738 }
6739 let display_snapshot = this.display_snapshot(cx);
6740 let newest_selection_adjusted =
6741 this.selections.newest_adjusted(&display_snapshot);
6742 let buffer = this.buffer.read(cx);
6743
6744 let (start_buffer, start) =
6745 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6746 let (end_buffer, end) =
6747 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6748
6749 Some((start_buffer, start, end_buffer, end, newest_selection))
6750 })?
6751 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6752 .context(
6753 "Expected selection to lie in a single buffer when refreshing code actions",
6754 )?;
6755 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6756 let providers = this.code_action_providers.clone();
6757 let tasks = this
6758 .code_action_providers
6759 .iter()
6760 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6761 .collect::<Vec<_>>();
6762 (providers, tasks)
6763 })?;
6764
6765 let mut actions = Vec::new();
6766 for (provider, provider_actions) in
6767 providers.into_iter().zip(future::join_all(tasks).await)
6768 {
6769 if let Some(provider_actions) = provider_actions.log_err() {
6770 actions.extend(provider_actions.into_iter().map(|action| {
6771 AvailableCodeAction {
6772 excerpt_id: newest_selection.start.excerpt_id,
6773 action,
6774 provider: provider.clone(),
6775 }
6776 }));
6777 }
6778 }
6779
6780 this.update(cx, |this, cx| {
6781 this.available_code_actions = if actions.is_empty() {
6782 None
6783 } else {
6784 Some((
6785 Location {
6786 buffer: start_buffer,
6787 range: start..end,
6788 },
6789 actions.into(),
6790 ))
6791 };
6792 cx.notify();
6793 })
6794 }));
6795 }
6796
6797 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6798 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6799 self.show_git_blame_inline = false;
6800
6801 self.show_git_blame_inline_delay_task =
6802 Some(cx.spawn_in(window, async move |this, cx| {
6803 cx.background_executor().timer(delay).await;
6804
6805 this.update(cx, |this, cx| {
6806 this.show_git_blame_inline = true;
6807 cx.notify();
6808 })
6809 .log_err();
6810 }));
6811 }
6812 }
6813
6814 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6815 let snapshot = self.snapshot(window, cx);
6816 let cursor = self
6817 .selections
6818 .newest::<Point>(&snapshot.display_snapshot)
6819 .head();
6820 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6821 else {
6822 return;
6823 };
6824
6825 if self.blame.is_none() {
6826 self.start_git_blame(true, window, cx);
6827 }
6828 let Some(blame) = self.blame.as_ref() else {
6829 return;
6830 };
6831
6832 let row_info = RowInfo {
6833 buffer_id: Some(buffer.remote_id()),
6834 buffer_row: Some(point.row),
6835 ..Default::default()
6836 };
6837 let Some((buffer, blame_entry)) = blame
6838 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6839 .flatten()
6840 else {
6841 return;
6842 };
6843
6844 let anchor = self.selections.newest_anchor().head();
6845 let position = self.to_pixel_point(anchor, &snapshot, window);
6846 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6847 self.show_blame_popover(
6848 buffer,
6849 &blame_entry,
6850 position + last_bounds.origin,
6851 true,
6852 cx,
6853 );
6854 };
6855 }
6856
6857 fn show_blame_popover(
6858 &mut self,
6859 buffer: BufferId,
6860 blame_entry: &BlameEntry,
6861 position: gpui::Point<Pixels>,
6862 ignore_timeout: bool,
6863 cx: &mut Context<Self>,
6864 ) {
6865 if let Some(state) = &mut self.inline_blame_popover {
6866 state.hide_task.take();
6867 } else {
6868 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6869 let blame_entry = blame_entry.clone();
6870 let show_task = cx.spawn(async move |editor, cx| {
6871 if !ignore_timeout {
6872 cx.background_executor()
6873 .timer(std::time::Duration::from_millis(blame_popover_delay))
6874 .await;
6875 }
6876 editor
6877 .update(cx, |editor, cx| {
6878 editor.inline_blame_popover_show_task.take();
6879 let Some(blame) = editor.blame.as_ref() else {
6880 return;
6881 };
6882 let blame = blame.read(cx);
6883 let details = blame.details_for_entry(buffer, &blame_entry);
6884 let markdown = cx.new(|cx| {
6885 Markdown::new(
6886 details
6887 .as_ref()
6888 .map(|message| message.message.clone())
6889 .unwrap_or_default(),
6890 None,
6891 None,
6892 cx,
6893 )
6894 });
6895 editor.inline_blame_popover = Some(InlineBlamePopover {
6896 position,
6897 hide_task: None,
6898 popover_bounds: None,
6899 popover_state: InlineBlamePopoverState {
6900 scroll_handle: ScrollHandle::new(),
6901 commit_message: details,
6902 markdown,
6903 },
6904 keyboard_grace: ignore_timeout,
6905 });
6906 cx.notify();
6907 })
6908 .ok();
6909 });
6910 self.inline_blame_popover_show_task = Some(show_task);
6911 }
6912 }
6913
6914 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6915 self.inline_blame_popover_show_task.take();
6916 if let Some(state) = &mut self.inline_blame_popover {
6917 let hide_task = cx.spawn(async move |editor, cx| {
6918 if !ignore_timeout {
6919 cx.background_executor()
6920 .timer(std::time::Duration::from_millis(100))
6921 .await;
6922 }
6923 editor
6924 .update(cx, |editor, cx| {
6925 editor.inline_blame_popover.take();
6926 cx.notify();
6927 })
6928 .ok();
6929 });
6930 state.hide_task = Some(hide_task);
6931 true
6932 } else {
6933 false
6934 }
6935 }
6936
6937 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6938 if self.pending_rename.is_some() {
6939 return None;
6940 }
6941
6942 let provider = self.semantics_provider.clone()?;
6943 let buffer = self.buffer.read(cx);
6944 let newest_selection = self.selections.newest_anchor().clone();
6945 let cursor_position = newest_selection.head();
6946 let (cursor_buffer, cursor_buffer_position) =
6947 buffer.text_anchor_for_position(cursor_position, cx)?;
6948 let (tail_buffer, tail_buffer_position) =
6949 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6950 if cursor_buffer != tail_buffer {
6951 return None;
6952 }
6953
6954 let snapshot = cursor_buffer.read(cx).snapshot();
6955 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6956 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6957 if start_word_range != end_word_range {
6958 self.document_highlights_task.take();
6959 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6960 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6961 return None;
6962 }
6963
6964 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6965 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6966 cx.background_executor()
6967 .timer(Duration::from_millis(debounce))
6968 .await;
6969
6970 let highlights = if let Some(highlights) = cx
6971 .update(|cx| {
6972 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6973 })
6974 .ok()
6975 .flatten()
6976 {
6977 highlights.await.log_err()
6978 } else {
6979 None
6980 };
6981
6982 if let Some(highlights) = highlights {
6983 this.update(cx, |this, cx| {
6984 if this.pending_rename.is_some() {
6985 return;
6986 }
6987
6988 let buffer = this.buffer.read(cx);
6989 if buffer
6990 .text_anchor_for_position(cursor_position, cx)
6991 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6992 {
6993 return;
6994 }
6995
6996 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6997 let mut write_ranges = Vec::new();
6998 let mut read_ranges = Vec::new();
6999 for highlight in highlights {
7000 let buffer_id = cursor_buffer.read(cx).remote_id();
7001 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7002 {
7003 let start = highlight
7004 .range
7005 .start
7006 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7007 let end = highlight
7008 .range
7009 .end
7010 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7011 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7012 continue;
7013 }
7014
7015 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7016 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7017 write_ranges.push(range);
7018 } else {
7019 read_ranges.push(range);
7020 }
7021 }
7022 }
7023
7024 this.highlight_background::<DocumentHighlightRead>(
7025 &read_ranges,
7026 |_, theme| theme.colors().editor_document_highlight_read_background,
7027 cx,
7028 );
7029 this.highlight_background::<DocumentHighlightWrite>(
7030 &write_ranges,
7031 |_, theme| theme.colors().editor_document_highlight_write_background,
7032 cx,
7033 );
7034 cx.notify();
7035 })
7036 .log_err();
7037 }
7038 }));
7039 None
7040 }
7041
7042 fn prepare_highlight_query_from_selection(
7043 &mut self,
7044 window: &Window,
7045 cx: &mut Context<Editor>,
7046 ) -> Option<(String, Range<Anchor>)> {
7047 if matches!(self.mode, EditorMode::SingleLine) {
7048 return None;
7049 }
7050 if !EditorSettings::get_global(cx).selection_highlight {
7051 return None;
7052 }
7053 if self.selections.count() != 1 || self.selections.line_mode() {
7054 return None;
7055 }
7056 let snapshot = self.snapshot(window, cx);
7057 let selection = self.selections.newest::<Point>(&snapshot);
7058 // If the selection spans multiple rows OR it is empty
7059 if selection.start.row != selection.end.row
7060 || selection.start.column == selection.end.column
7061 {
7062 return None;
7063 }
7064 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7065 let query = snapshot
7066 .buffer_snapshot()
7067 .text_for_range(selection_anchor_range.clone())
7068 .collect::<String>();
7069 if query.trim().is_empty() {
7070 return None;
7071 }
7072 Some((query, selection_anchor_range))
7073 }
7074
7075 fn update_selection_occurrence_highlights(
7076 &mut self,
7077 query_text: String,
7078 query_range: Range<Anchor>,
7079 multi_buffer_range_to_query: Range<Point>,
7080 use_debounce: bool,
7081 window: &mut Window,
7082 cx: &mut Context<Editor>,
7083 ) -> Task<()> {
7084 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7085 cx.spawn_in(window, async move |editor, cx| {
7086 if use_debounce {
7087 cx.background_executor()
7088 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7089 .await;
7090 }
7091 let match_task = cx.background_spawn(async move {
7092 let buffer_ranges = multi_buffer_snapshot
7093 .range_to_buffer_ranges(multi_buffer_range_to_query)
7094 .into_iter()
7095 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7096 let mut match_ranges = Vec::new();
7097 let Ok(regex) = project::search::SearchQuery::text(
7098 query_text.clone(),
7099 false,
7100 false,
7101 false,
7102 Default::default(),
7103 Default::default(),
7104 false,
7105 None,
7106 ) else {
7107 return Vec::default();
7108 };
7109 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7110 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7111 match_ranges.extend(
7112 regex
7113 .search(
7114 buffer_snapshot,
7115 Some(search_range.start.0..search_range.end.0),
7116 )
7117 .await
7118 .into_iter()
7119 .filter_map(|match_range| {
7120 let match_start = buffer_snapshot
7121 .anchor_after(search_range.start + match_range.start);
7122 let match_end = buffer_snapshot
7123 .anchor_before(search_range.start + match_range.end);
7124 let match_anchor_range =
7125 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7126 (match_anchor_range != query_range).then_some(match_anchor_range)
7127 }),
7128 );
7129 }
7130 match_ranges
7131 });
7132 let match_ranges = match_task.await;
7133 editor
7134 .update_in(cx, |editor, _, cx| {
7135 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7136 if !match_ranges.is_empty() {
7137 editor.highlight_background::<SelectedTextHighlight>(
7138 &match_ranges,
7139 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7140 cx,
7141 )
7142 }
7143 })
7144 .log_err();
7145 })
7146 }
7147
7148 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7149 struct NewlineFold;
7150 let type_id = std::any::TypeId::of::<NewlineFold>();
7151 if !self.mode.is_single_line() {
7152 return;
7153 }
7154 let snapshot = self.snapshot(window, cx);
7155 if snapshot.buffer_snapshot().max_point().row == 0 {
7156 return;
7157 }
7158 let task = cx.background_spawn(async move {
7159 let new_newlines = snapshot
7160 .buffer_chars_at(MultiBufferOffset(0))
7161 .filter_map(|(c, i)| {
7162 if c == '\n' {
7163 Some(
7164 snapshot.buffer_snapshot().anchor_after(i)
7165 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7166 )
7167 } else {
7168 None
7169 }
7170 })
7171 .collect::<Vec<_>>();
7172 let existing_newlines = snapshot
7173 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7174 .filter_map(|fold| {
7175 if fold.placeholder.type_tag == Some(type_id) {
7176 Some(fold.range.start..fold.range.end)
7177 } else {
7178 None
7179 }
7180 })
7181 .collect::<Vec<_>>();
7182
7183 (new_newlines, existing_newlines)
7184 });
7185 self.folding_newlines = cx.spawn(async move |this, cx| {
7186 let (new_newlines, existing_newlines) = task.await;
7187 if new_newlines == existing_newlines {
7188 return;
7189 }
7190 let placeholder = FoldPlaceholder {
7191 render: Arc::new(move |_, _, cx| {
7192 div()
7193 .bg(cx.theme().status().hint_background)
7194 .border_b_1()
7195 .size_full()
7196 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7197 .border_color(cx.theme().status().hint)
7198 .child("\\n")
7199 .into_any()
7200 }),
7201 constrain_width: false,
7202 merge_adjacent: false,
7203 type_tag: Some(type_id),
7204 };
7205 let creases = new_newlines
7206 .into_iter()
7207 .map(|range| Crease::simple(range, placeholder.clone()))
7208 .collect();
7209 this.update(cx, |this, cx| {
7210 this.display_map.update(cx, |display_map, cx| {
7211 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7212 display_map.fold(creases, cx);
7213 });
7214 })
7215 .ok();
7216 });
7217 }
7218
7219 fn refresh_selected_text_highlights(
7220 &mut self,
7221 on_buffer_edit: bool,
7222 window: &mut Window,
7223 cx: &mut Context<Editor>,
7224 ) {
7225 let Some((query_text, query_range)) =
7226 self.prepare_highlight_query_from_selection(window, cx)
7227 else {
7228 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7229 self.quick_selection_highlight_task.take();
7230 self.debounced_selection_highlight_task.take();
7231 return;
7232 };
7233 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7234 if on_buffer_edit
7235 || self
7236 .quick_selection_highlight_task
7237 .as_ref()
7238 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7239 {
7240 let multi_buffer_visible_start = self
7241 .scroll_manager
7242 .anchor()
7243 .anchor
7244 .to_point(&multi_buffer_snapshot);
7245 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7246 multi_buffer_visible_start
7247 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7248 Bias::Left,
7249 );
7250 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7251 self.quick_selection_highlight_task = Some((
7252 query_range.clone(),
7253 self.update_selection_occurrence_highlights(
7254 query_text.clone(),
7255 query_range.clone(),
7256 multi_buffer_visible_range,
7257 false,
7258 window,
7259 cx,
7260 ),
7261 ));
7262 }
7263 if on_buffer_edit
7264 || self
7265 .debounced_selection_highlight_task
7266 .as_ref()
7267 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7268 {
7269 let multi_buffer_start = multi_buffer_snapshot
7270 .anchor_before(MultiBufferOffset(0))
7271 .to_point(&multi_buffer_snapshot);
7272 let multi_buffer_end = multi_buffer_snapshot
7273 .anchor_after(multi_buffer_snapshot.len())
7274 .to_point(&multi_buffer_snapshot);
7275 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7276 self.debounced_selection_highlight_task = Some((
7277 query_range.clone(),
7278 self.update_selection_occurrence_highlights(
7279 query_text,
7280 query_range,
7281 multi_buffer_full_range,
7282 true,
7283 window,
7284 cx,
7285 ),
7286 ));
7287 }
7288 }
7289
7290 pub fn refresh_edit_prediction(
7291 &mut self,
7292 debounce: bool,
7293 user_requested: bool,
7294 window: &mut Window,
7295 cx: &mut Context<Self>,
7296 ) -> Option<()> {
7297 if DisableAiSettings::get_global(cx).disable_ai {
7298 return None;
7299 }
7300
7301 let provider = self.edit_prediction_provider()?;
7302 let cursor = self.selections.newest_anchor().head();
7303 let (buffer, cursor_buffer_position) =
7304 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7305
7306 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7307 self.discard_edit_prediction(false, cx);
7308 return None;
7309 }
7310
7311 self.update_visible_edit_prediction(window, cx);
7312
7313 if !user_requested
7314 && (!self.should_show_edit_predictions()
7315 || !self.is_focused(window)
7316 || buffer.read(cx).is_empty())
7317 {
7318 self.discard_edit_prediction(false, cx);
7319 return None;
7320 }
7321
7322 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7323 Some(())
7324 }
7325
7326 fn show_edit_predictions_in_menu(&self) -> bool {
7327 match self.edit_prediction_settings {
7328 EditPredictionSettings::Disabled => false,
7329 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7330 }
7331 }
7332
7333 pub fn edit_predictions_enabled(&self) -> bool {
7334 match self.edit_prediction_settings {
7335 EditPredictionSettings::Disabled => false,
7336 EditPredictionSettings::Enabled { .. } => true,
7337 }
7338 }
7339
7340 fn edit_prediction_requires_modifier(&self) -> bool {
7341 match self.edit_prediction_settings {
7342 EditPredictionSettings::Disabled => false,
7343 EditPredictionSettings::Enabled {
7344 preview_requires_modifier,
7345 ..
7346 } => preview_requires_modifier,
7347 }
7348 }
7349
7350 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7351 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7352 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7353 self.discard_edit_prediction(false, cx);
7354 } else {
7355 let selection = self.selections.newest_anchor();
7356 let cursor = selection.head();
7357
7358 if let Some((buffer, cursor_buffer_position)) =
7359 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7360 {
7361 self.edit_prediction_settings =
7362 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7363 }
7364 }
7365 }
7366
7367 fn edit_prediction_settings_at_position(
7368 &self,
7369 buffer: &Entity<Buffer>,
7370 buffer_position: language::Anchor,
7371 cx: &App,
7372 ) -> EditPredictionSettings {
7373 if !self.mode.is_full()
7374 || !self.show_edit_predictions_override.unwrap_or(true)
7375 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7376 {
7377 return EditPredictionSettings::Disabled;
7378 }
7379
7380 let buffer = buffer.read(cx);
7381
7382 let file = buffer.file();
7383
7384 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7385 return EditPredictionSettings::Disabled;
7386 };
7387
7388 let by_provider = matches!(
7389 self.menu_edit_predictions_policy,
7390 MenuEditPredictionsPolicy::ByProvider
7391 );
7392
7393 let show_in_menu = by_provider
7394 && self
7395 .edit_prediction_provider
7396 .as_ref()
7397 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7398
7399 let preview_requires_modifier =
7400 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7401
7402 EditPredictionSettings::Enabled {
7403 show_in_menu,
7404 preview_requires_modifier,
7405 }
7406 }
7407
7408 fn should_show_edit_predictions(&self) -> bool {
7409 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7410 }
7411
7412 pub fn edit_prediction_preview_is_active(&self) -> bool {
7413 matches!(
7414 self.edit_prediction_preview,
7415 EditPredictionPreview::Active { .. }
7416 )
7417 }
7418
7419 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7420 let cursor = self.selections.newest_anchor().head();
7421 if let Some((buffer, cursor_position)) =
7422 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7423 {
7424 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7425 } else {
7426 false
7427 }
7428 }
7429
7430 pub fn supports_minimap(&self, cx: &App) -> bool {
7431 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7432 }
7433
7434 fn edit_predictions_enabled_in_buffer(
7435 &self,
7436 buffer: &Entity<Buffer>,
7437 buffer_position: language::Anchor,
7438 cx: &App,
7439 ) -> bool {
7440 maybe!({
7441 if self.read_only(cx) {
7442 return Some(false);
7443 }
7444 let provider = self.edit_prediction_provider()?;
7445 if !provider.is_enabled(buffer, buffer_position, cx) {
7446 return Some(false);
7447 }
7448 let buffer = buffer.read(cx);
7449 let Some(file) = buffer.file() else {
7450 return Some(true);
7451 };
7452 let settings = all_language_settings(Some(file), cx);
7453 Some(settings.edit_predictions_enabled_for_file(file, cx))
7454 })
7455 .unwrap_or(false)
7456 }
7457
7458 fn cycle_edit_prediction(
7459 &mut self,
7460 direction: Direction,
7461 window: &mut Window,
7462 cx: &mut Context<Self>,
7463 ) -> Option<()> {
7464 let provider = self.edit_prediction_provider()?;
7465 let cursor = self.selections.newest_anchor().head();
7466 let (buffer, cursor_buffer_position) =
7467 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7468 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7469 return None;
7470 }
7471
7472 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7473 self.update_visible_edit_prediction(window, cx);
7474
7475 Some(())
7476 }
7477
7478 pub fn show_edit_prediction(
7479 &mut self,
7480 _: &ShowEditPrediction,
7481 window: &mut Window,
7482 cx: &mut Context<Self>,
7483 ) {
7484 if !self.has_active_edit_prediction() {
7485 self.refresh_edit_prediction(false, true, window, cx);
7486 return;
7487 }
7488
7489 self.update_visible_edit_prediction(window, cx);
7490 }
7491
7492 pub fn display_cursor_names(
7493 &mut self,
7494 _: &DisplayCursorNames,
7495 window: &mut Window,
7496 cx: &mut Context<Self>,
7497 ) {
7498 self.show_cursor_names(window, cx);
7499 }
7500
7501 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7502 self.show_cursor_names = true;
7503 cx.notify();
7504 cx.spawn_in(window, async move |this, cx| {
7505 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7506 this.update(cx, |this, cx| {
7507 this.show_cursor_names = false;
7508 cx.notify()
7509 })
7510 .ok()
7511 })
7512 .detach();
7513 }
7514
7515 pub fn next_edit_prediction(
7516 &mut self,
7517 _: &NextEditPrediction,
7518 window: &mut Window,
7519 cx: &mut Context<Self>,
7520 ) {
7521 if self.has_active_edit_prediction() {
7522 self.cycle_edit_prediction(Direction::Next, window, cx);
7523 } else {
7524 let is_copilot_disabled = self
7525 .refresh_edit_prediction(false, true, window, cx)
7526 .is_none();
7527 if is_copilot_disabled {
7528 cx.propagate();
7529 }
7530 }
7531 }
7532
7533 pub fn previous_edit_prediction(
7534 &mut self,
7535 _: &PreviousEditPrediction,
7536 window: &mut Window,
7537 cx: &mut Context<Self>,
7538 ) {
7539 if self.has_active_edit_prediction() {
7540 self.cycle_edit_prediction(Direction::Prev, window, cx);
7541 } else {
7542 let is_copilot_disabled = self
7543 .refresh_edit_prediction(false, true, window, cx)
7544 .is_none();
7545 if is_copilot_disabled {
7546 cx.propagate();
7547 }
7548 }
7549 }
7550
7551 pub fn accept_edit_prediction(
7552 &mut self,
7553 _: &AcceptEditPrediction,
7554 window: &mut Window,
7555 cx: &mut Context<Self>,
7556 ) {
7557 if self.show_edit_predictions_in_menu() {
7558 self.hide_context_menu(window, cx);
7559 }
7560
7561 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7562 return;
7563 };
7564
7565 match &active_edit_prediction.completion {
7566 EditPrediction::MoveWithin { target, .. } => {
7567 let target = *target;
7568
7569 if let Some(position_map) = &self.last_position_map {
7570 if position_map
7571 .visible_row_range
7572 .contains(&target.to_display_point(&position_map.snapshot).row())
7573 || !self.edit_prediction_requires_modifier()
7574 {
7575 self.unfold_ranges(&[target..target], true, false, cx);
7576 // Note that this is also done in vim's handler of the Tab action.
7577 self.change_selections(
7578 SelectionEffects::scroll(Autoscroll::newest()),
7579 window,
7580 cx,
7581 |selections| {
7582 selections.select_anchor_ranges([target..target]);
7583 },
7584 );
7585 self.clear_row_highlights::<EditPredictionPreview>();
7586
7587 self.edit_prediction_preview
7588 .set_previous_scroll_position(None);
7589 } else {
7590 self.edit_prediction_preview
7591 .set_previous_scroll_position(Some(
7592 position_map.snapshot.scroll_anchor,
7593 ));
7594
7595 self.highlight_rows::<EditPredictionPreview>(
7596 target..target,
7597 cx.theme().colors().editor_highlighted_line_background,
7598 RowHighlightOptions {
7599 autoscroll: true,
7600 ..Default::default()
7601 },
7602 cx,
7603 );
7604 self.request_autoscroll(Autoscroll::fit(), cx);
7605 }
7606 }
7607 }
7608 EditPrediction::MoveOutside { snapshot, target } => {
7609 if let Some(workspace) = self.workspace() {
7610 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7611 .detach_and_log_err(cx);
7612 }
7613 }
7614 EditPrediction::Edit { edits, .. } => {
7615 self.report_edit_prediction_event(
7616 active_edit_prediction.completion_id.clone(),
7617 true,
7618 cx,
7619 );
7620
7621 if let Some(provider) = self.edit_prediction_provider() {
7622 provider.accept(cx);
7623 }
7624
7625 // Store the transaction ID and selections before applying the edit
7626 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7627
7628 let snapshot = self.buffer.read(cx).snapshot(cx);
7629 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7630
7631 self.buffer.update(cx, |buffer, cx| {
7632 buffer.edit(edits.iter().cloned(), None, cx)
7633 });
7634
7635 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7636 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7637 });
7638
7639 let selections = self.selections.disjoint_anchors_arc();
7640 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7641 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7642 if has_new_transaction {
7643 self.selection_history
7644 .insert_transaction(transaction_id_now, selections);
7645 }
7646 }
7647
7648 self.update_visible_edit_prediction(window, cx);
7649 if self.active_edit_prediction.is_none() {
7650 self.refresh_edit_prediction(true, true, window, cx);
7651 }
7652
7653 cx.notify();
7654 }
7655 }
7656
7657 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7658 }
7659
7660 pub fn accept_partial_edit_prediction(
7661 &mut self,
7662 _: &AcceptPartialEditPrediction,
7663 window: &mut Window,
7664 cx: &mut Context<Self>,
7665 ) {
7666 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7667 return;
7668 };
7669 if self.selections.count() != 1 {
7670 return;
7671 }
7672
7673 match &active_edit_prediction.completion {
7674 EditPrediction::MoveWithin { target, .. } => {
7675 let target = *target;
7676 self.change_selections(
7677 SelectionEffects::scroll(Autoscroll::newest()),
7678 window,
7679 cx,
7680 |selections| {
7681 selections.select_anchor_ranges([target..target]);
7682 },
7683 );
7684 }
7685 EditPrediction::MoveOutside { snapshot, target } => {
7686 if let Some(workspace) = self.workspace() {
7687 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7688 .detach_and_log_err(cx);
7689 }
7690 }
7691 EditPrediction::Edit { edits, .. } => {
7692 self.report_edit_prediction_event(
7693 active_edit_prediction.completion_id.clone(),
7694 true,
7695 cx,
7696 );
7697
7698 // Find an insertion that starts at the cursor position.
7699 let snapshot = self.buffer.read(cx).snapshot(cx);
7700 let cursor_offset = self
7701 .selections
7702 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7703 .head();
7704 let insertion = edits.iter().find_map(|(range, text)| {
7705 let range = range.to_offset(&snapshot);
7706 if range.is_empty() && range.start == cursor_offset {
7707 Some(text)
7708 } else {
7709 None
7710 }
7711 });
7712
7713 if let Some(text) = insertion {
7714 let mut partial_completion = text
7715 .chars()
7716 .by_ref()
7717 .take_while(|c| c.is_alphabetic())
7718 .collect::<String>();
7719 if partial_completion.is_empty() {
7720 partial_completion = text
7721 .chars()
7722 .by_ref()
7723 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7724 .collect::<String>();
7725 }
7726
7727 cx.emit(EditorEvent::InputHandled {
7728 utf16_range_to_replace: None,
7729 text: partial_completion.clone().into(),
7730 });
7731
7732 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7733
7734 self.refresh_edit_prediction(true, true, window, cx);
7735 cx.notify();
7736 } else {
7737 self.accept_edit_prediction(&Default::default(), window, cx);
7738 }
7739 }
7740 }
7741 }
7742
7743 fn discard_edit_prediction(
7744 &mut self,
7745 should_report_edit_prediction_event: bool,
7746 cx: &mut Context<Self>,
7747 ) -> bool {
7748 if should_report_edit_prediction_event {
7749 let completion_id = self
7750 .active_edit_prediction
7751 .as_ref()
7752 .and_then(|active_completion| active_completion.completion_id.clone());
7753
7754 self.report_edit_prediction_event(completion_id, false, cx);
7755 }
7756
7757 if let Some(provider) = self.edit_prediction_provider() {
7758 provider.discard(cx);
7759 }
7760
7761 self.take_active_edit_prediction(cx)
7762 }
7763
7764 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7765 let Some(provider) = self.edit_prediction_provider() else {
7766 return;
7767 };
7768
7769 let Some((_, buffer, _)) = self
7770 .buffer
7771 .read(cx)
7772 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7773 else {
7774 return;
7775 };
7776
7777 let extension = buffer
7778 .read(cx)
7779 .file()
7780 .and_then(|file| Some(file.path().extension()?.to_string()));
7781
7782 let event_type = match accepted {
7783 true => "Edit Prediction Accepted",
7784 false => "Edit Prediction Discarded",
7785 };
7786 telemetry::event!(
7787 event_type,
7788 provider = provider.name(),
7789 prediction_id = id,
7790 suggestion_accepted = accepted,
7791 file_extension = extension,
7792 );
7793 }
7794
7795 fn open_editor_at_anchor(
7796 snapshot: &language::BufferSnapshot,
7797 target: language::Anchor,
7798 workspace: &Entity<Workspace>,
7799 window: &mut Window,
7800 cx: &mut App,
7801 ) -> Task<Result<()>> {
7802 workspace.update(cx, |workspace, cx| {
7803 let path = snapshot.file().map(|file| file.full_path(cx));
7804 let Some(path) =
7805 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7806 else {
7807 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7808 };
7809 let target = text::ToPoint::to_point(&target, snapshot);
7810 let item = workspace.open_path(path, None, true, window, cx);
7811 window.spawn(cx, async move |cx| {
7812 let Some(editor) = item.await?.downcast::<Editor>() else {
7813 return Ok(());
7814 };
7815 editor
7816 .update_in(cx, |editor, window, cx| {
7817 editor.go_to_singleton_buffer_point(target, window, cx);
7818 })
7819 .ok();
7820 anyhow::Ok(())
7821 })
7822 })
7823 }
7824
7825 pub fn has_active_edit_prediction(&self) -> bool {
7826 self.active_edit_prediction.is_some()
7827 }
7828
7829 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7830 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7831 return false;
7832 };
7833
7834 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7835 self.clear_highlights::<EditPredictionHighlight>(cx);
7836 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7837 true
7838 }
7839
7840 /// Returns true when we're displaying the edit prediction popover below the cursor
7841 /// like we are not previewing and the LSP autocomplete menu is visible
7842 /// or we are in `when_holding_modifier` mode.
7843 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7844 if self.edit_prediction_preview_is_active()
7845 || !self.show_edit_predictions_in_menu()
7846 || !self.edit_predictions_enabled()
7847 {
7848 return false;
7849 }
7850
7851 if self.has_visible_completions_menu() {
7852 return true;
7853 }
7854
7855 has_completion && self.edit_prediction_requires_modifier()
7856 }
7857
7858 fn handle_modifiers_changed(
7859 &mut self,
7860 modifiers: Modifiers,
7861 position_map: &PositionMap,
7862 window: &mut Window,
7863 cx: &mut Context<Self>,
7864 ) {
7865 // Ensure that the edit prediction preview is updated, even when not
7866 // enabled, if there's an active edit prediction preview.
7867 if self.show_edit_predictions_in_menu()
7868 || matches!(
7869 self.edit_prediction_preview,
7870 EditPredictionPreview::Active { .. }
7871 )
7872 {
7873 self.update_edit_prediction_preview(&modifiers, window, cx);
7874 }
7875
7876 self.update_selection_mode(&modifiers, position_map, window, cx);
7877
7878 let mouse_position = window.mouse_position();
7879 if !position_map.text_hitbox.is_hovered(window) {
7880 return;
7881 }
7882
7883 self.update_hovered_link(
7884 position_map.point_for_position(mouse_position),
7885 &position_map.snapshot,
7886 modifiers,
7887 window,
7888 cx,
7889 )
7890 }
7891
7892 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7893 match EditorSettings::get_global(cx).multi_cursor_modifier {
7894 MultiCursorModifier::Alt => modifiers.secondary(),
7895 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7896 }
7897 }
7898
7899 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7900 match EditorSettings::get_global(cx).multi_cursor_modifier {
7901 MultiCursorModifier::Alt => modifiers.alt,
7902 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7903 }
7904 }
7905
7906 fn columnar_selection_mode(
7907 modifiers: &Modifiers,
7908 cx: &mut Context<Self>,
7909 ) -> Option<ColumnarMode> {
7910 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7911 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7912 Some(ColumnarMode::FromMouse)
7913 } else if Self::is_alt_pressed(modifiers, cx) {
7914 Some(ColumnarMode::FromSelection)
7915 } else {
7916 None
7917 }
7918 } else {
7919 None
7920 }
7921 }
7922
7923 fn update_selection_mode(
7924 &mut self,
7925 modifiers: &Modifiers,
7926 position_map: &PositionMap,
7927 window: &mut Window,
7928 cx: &mut Context<Self>,
7929 ) {
7930 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7931 return;
7932 };
7933 if self.selections.pending_anchor().is_none() {
7934 return;
7935 }
7936
7937 let mouse_position = window.mouse_position();
7938 let point_for_position = position_map.point_for_position(mouse_position);
7939 let position = point_for_position.previous_valid;
7940
7941 self.select(
7942 SelectPhase::BeginColumnar {
7943 position,
7944 reset: false,
7945 mode,
7946 goal_column: point_for_position.exact_unclipped.column(),
7947 },
7948 window,
7949 cx,
7950 );
7951 }
7952
7953 fn update_edit_prediction_preview(
7954 &mut self,
7955 modifiers: &Modifiers,
7956 window: &mut Window,
7957 cx: &mut Context<Self>,
7958 ) {
7959 let mut modifiers_held = false;
7960 if let Some(accept_keystroke) = self
7961 .accept_edit_prediction_keybind(false, window, cx)
7962 .keystroke()
7963 {
7964 modifiers_held = modifiers_held
7965 || (accept_keystroke.modifiers() == modifiers
7966 && accept_keystroke.modifiers().modified());
7967 };
7968 if let Some(accept_partial_keystroke) = self
7969 .accept_edit_prediction_keybind(true, window, cx)
7970 .keystroke()
7971 {
7972 modifiers_held = modifiers_held
7973 || (accept_partial_keystroke.modifiers() == modifiers
7974 && accept_partial_keystroke.modifiers().modified());
7975 }
7976
7977 if modifiers_held {
7978 if matches!(
7979 self.edit_prediction_preview,
7980 EditPredictionPreview::Inactive { .. }
7981 ) {
7982 if let Some(provider) = self.edit_prediction_provider.as_ref() {
7983 provider.provider.did_show(cx)
7984 }
7985
7986 self.edit_prediction_preview = EditPredictionPreview::Active {
7987 previous_scroll_position: None,
7988 since: Instant::now(),
7989 };
7990
7991 self.update_visible_edit_prediction(window, cx);
7992 cx.notify();
7993 }
7994 } else if let EditPredictionPreview::Active {
7995 previous_scroll_position,
7996 since,
7997 } = self.edit_prediction_preview
7998 {
7999 if let (Some(previous_scroll_position), Some(position_map)) =
8000 (previous_scroll_position, self.last_position_map.as_ref())
8001 {
8002 self.set_scroll_position(
8003 previous_scroll_position
8004 .scroll_position(&position_map.snapshot.display_snapshot),
8005 window,
8006 cx,
8007 );
8008 }
8009
8010 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8011 released_too_fast: since.elapsed() < Duration::from_millis(200),
8012 };
8013 self.clear_row_highlights::<EditPredictionPreview>();
8014 self.update_visible_edit_prediction(window, cx);
8015 cx.notify();
8016 }
8017 }
8018
8019 fn update_visible_edit_prediction(
8020 &mut self,
8021 _window: &mut Window,
8022 cx: &mut Context<Self>,
8023 ) -> Option<()> {
8024 if DisableAiSettings::get_global(cx).disable_ai {
8025 return None;
8026 }
8027
8028 if self.ime_transaction.is_some() {
8029 self.discard_edit_prediction(false, cx);
8030 return None;
8031 }
8032
8033 let selection = self.selections.newest_anchor();
8034 let cursor = selection.head();
8035 let multibuffer = self.buffer.read(cx).snapshot(cx);
8036 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8037 let excerpt_id = cursor.excerpt_id;
8038
8039 let show_in_menu = self.show_edit_predictions_in_menu();
8040 let completions_menu_has_precedence = !show_in_menu
8041 && (self.context_menu.borrow().is_some()
8042 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8043
8044 if completions_menu_has_precedence
8045 || !offset_selection.is_empty()
8046 || self
8047 .active_edit_prediction
8048 .as_ref()
8049 .is_some_and(|completion| {
8050 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8051 return false;
8052 };
8053 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8054 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8055 !invalidation_range.contains(&offset_selection.head())
8056 })
8057 {
8058 self.discard_edit_prediction(false, cx);
8059 return None;
8060 }
8061
8062 self.take_active_edit_prediction(cx);
8063 let Some(provider) = self.edit_prediction_provider() else {
8064 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8065 return None;
8066 };
8067
8068 let (buffer, cursor_buffer_position) =
8069 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8070
8071 self.edit_prediction_settings =
8072 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8073
8074 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8075
8076 if self.edit_prediction_indent_conflict {
8077 let cursor_point = cursor.to_point(&multibuffer);
8078 let mut suggested_indent = None;
8079 multibuffer.suggested_indents_callback(
8080 cursor_point.row..cursor_point.row + 1,
8081 |_, indent| {
8082 suggested_indent = Some(indent);
8083 ControlFlow::Break(())
8084 },
8085 cx,
8086 );
8087
8088 if let Some(indent) = suggested_indent
8089 && indent.len == cursor_point.column
8090 {
8091 self.edit_prediction_indent_conflict = false;
8092 }
8093 }
8094
8095 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8096
8097 let (completion_id, edits, edit_preview) = match edit_prediction {
8098 edit_prediction::EditPrediction::Local {
8099 id,
8100 edits,
8101 edit_preview,
8102 } => (id, edits, edit_preview),
8103 edit_prediction::EditPrediction::Jump {
8104 id,
8105 snapshot,
8106 target,
8107 } => {
8108 self.stale_edit_prediction_in_menu = None;
8109 self.active_edit_prediction = Some(EditPredictionState {
8110 inlay_ids: vec![],
8111 completion: EditPrediction::MoveOutside { snapshot, target },
8112 completion_id: id,
8113 invalidation_range: None,
8114 });
8115 cx.notify();
8116 return Some(());
8117 }
8118 };
8119
8120 let edits = edits
8121 .into_iter()
8122 .flat_map(|(range, new_text)| {
8123 Some((
8124 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8125 new_text,
8126 ))
8127 })
8128 .collect::<Vec<_>>();
8129 if edits.is_empty() {
8130 return None;
8131 }
8132
8133 let first_edit_start = edits.first().unwrap().0.start;
8134 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8135 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8136
8137 let last_edit_end = edits.last().unwrap().0.end;
8138 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8139 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8140
8141 let cursor_row = cursor.to_point(&multibuffer).row;
8142
8143 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8144
8145 let mut inlay_ids = Vec::new();
8146 let invalidation_row_range;
8147 let move_invalidation_row_range = if cursor_row < edit_start_row {
8148 Some(cursor_row..edit_end_row)
8149 } else if cursor_row > edit_end_row {
8150 Some(edit_start_row..cursor_row)
8151 } else {
8152 None
8153 };
8154 let supports_jump = self
8155 .edit_prediction_provider
8156 .as_ref()
8157 .map(|provider| provider.provider.supports_jump_to_edit())
8158 .unwrap_or(true);
8159
8160 let is_move = supports_jump
8161 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8162 let completion = if is_move {
8163 invalidation_row_range =
8164 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8165 let target = first_edit_start;
8166 EditPrediction::MoveWithin { target, snapshot }
8167 } else {
8168 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8169 && !self.edit_predictions_hidden_for_vim_mode;
8170
8171 if show_completions_in_buffer {
8172 if let Some(provider) = &self.edit_prediction_provider {
8173 provider.provider.did_show(cx);
8174 }
8175 if edits
8176 .iter()
8177 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8178 {
8179 let mut inlays = Vec::new();
8180 for (range, new_text) in &edits {
8181 let inlay = Inlay::edit_prediction(
8182 post_inc(&mut self.next_inlay_id),
8183 range.start,
8184 new_text.as_ref(),
8185 );
8186 inlay_ids.push(inlay.id);
8187 inlays.push(inlay);
8188 }
8189
8190 self.splice_inlays(&[], inlays, cx);
8191 } else {
8192 let background_color = cx.theme().status().deleted_background;
8193 self.highlight_text::<EditPredictionHighlight>(
8194 edits.iter().map(|(range, _)| range.clone()).collect(),
8195 HighlightStyle {
8196 background_color: Some(background_color),
8197 ..Default::default()
8198 },
8199 cx,
8200 );
8201 }
8202 }
8203
8204 invalidation_row_range = edit_start_row..edit_end_row;
8205
8206 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8207 if provider.show_tab_accept_marker() {
8208 EditDisplayMode::TabAccept
8209 } else {
8210 EditDisplayMode::Inline
8211 }
8212 } else {
8213 EditDisplayMode::DiffPopover
8214 };
8215
8216 EditPrediction::Edit {
8217 edits,
8218 edit_preview,
8219 display_mode,
8220 snapshot,
8221 }
8222 };
8223
8224 let invalidation_range = multibuffer
8225 .anchor_before(Point::new(invalidation_row_range.start, 0))
8226 ..multibuffer.anchor_after(Point::new(
8227 invalidation_row_range.end,
8228 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8229 ));
8230
8231 self.stale_edit_prediction_in_menu = None;
8232 self.active_edit_prediction = Some(EditPredictionState {
8233 inlay_ids,
8234 completion,
8235 completion_id,
8236 invalidation_range: Some(invalidation_range),
8237 });
8238
8239 cx.notify();
8240
8241 Some(())
8242 }
8243
8244 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8245 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8246 }
8247
8248 fn clear_tasks(&mut self) {
8249 self.tasks.clear()
8250 }
8251
8252 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8253 if self.tasks.insert(key, value).is_some() {
8254 // This case should hopefully be rare, but just in case...
8255 log::error!(
8256 "multiple different run targets found on a single line, only the last target will be rendered"
8257 )
8258 }
8259 }
8260
8261 /// Get all display points of breakpoints that will be rendered within editor
8262 ///
8263 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8264 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8265 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8266 fn active_breakpoints(
8267 &self,
8268 range: Range<DisplayRow>,
8269 window: &mut Window,
8270 cx: &mut Context<Self>,
8271 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8272 let mut breakpoint_display_points = HashMap::default();
8273
8274 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8275 return breakpoint_display_points;
8276 };
8277
8278 let snapshot = self.snapshot(window, cx);
8279
8280 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8281 let Some(project) = self.project() else {
8282 return breakpoint_display_points;
8283 };
8284
8285 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8286 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8287
8288 for (buffer_snapshot, range, excerpt_id) in
8289 multi_buffer_snapshot.range_to_buffer_ranges(range)
8290 {
8291 let Some(buffer) = project
8292 .read(cx)
8293 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8294 else {
8295 continue;
8296 };
8297 let breakpoints = breakpoint_store.read(cx).breakpoints(
8298 &buffer,
8299 Some(
8300 buffer_snapshot.anchor_before(range.start)
8301 ..buffer_snapshot.anchor_after(range.end),
8302 ),
8303 buffer_snapshot,
8304 cx,
8305 );
8306 for (breakpoint, state) in breakpoints {
8307 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8308 let position = multi_buffer_anchor
8309 .to_point(&multi_buffer_snapshot)
8310 .to_display_point(&snapshot);
8311
8312 breakpoint_display_points.insert(
8313 position.row(),
8314 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8315 );
8316 }
8317 }
8318
8319 breakpoint_display_points
8320 }
8321
8322 fn breakpoint_context_menu(
8323 &self,
8324 anchor: Anchor,
8325 window: &mut Window,
8326 cx: &mut Context<Self>,
8327 ) -> Entity<ui::ContextMenu> {
8328 let weak_editor = cx.weak_entity();
8329 let focus_handle = self.focus_handle(cx);
8330
8331 let row = self
8332 .buffer
8333 .read(cx)
8334 .snapshot(cx)
8335 .summary_for_anchor::<Point>(&anchor)
8336 .row;
8337
8338 let breakpoint = self
8339 .breakpoint_at_row(row, window, cx)
8340 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8341
8342 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8343 "Edit Log Breakpoint"
8344 } else {
8345 "Set Log Breakpoint"
8346 };
8347
8348 let condition_breakpoint_msg = if breakpoint
8349 .as_ref()
8350 .is_some_and(|bp| bp.1.condition.is_some())
8351 {
8352 "Edit Condition Breakpoint"
8353 } else {
8354 "Set Condition Breakpoint"
8355 };
8356
8357 let hit_condition_breakpoint_msg = if breakpoint
8358 .as_ref()
8359 .is_some_and(|bp| bp.1.hit_condition.is_some())
8360 {
8361 "Edit Hit Condition Breakpoint"
8362 } else {
8363 "Set Hit Condition Breakpoint"
8364 };
8365
8366 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8367 "Unset Breakpoint"
8368 } else {
8369 "Set Breakpoint"
8370 };
8371
8372 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8373
8374 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8375 BreakpointState::Enabled => Some("Disable"),
8376 BreakpointState::Disabled => Some("Enable"),
8377 });
8378
8379 let (anchor, breakpoint) =
8380 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8381
8382 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8383 menu.on_blur_subscription(Subscription::new(|| {}))
8384 .context(focus_handle)
8385 .when(run_to_cursor, |this| {
8386 let weak_editor = weak_editor.clone();
8387 this.entry("Run to cursor", None, move |window, cx| {
8388 weak_editor
8389 .update(cx, |editor, cx| {
8390 editor.change_selections(
8391 SelectionEffects::no_scroll(),
8392 window,
8393 cx,
8394 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8395 );
8396 })
8397 .ok();
8398
8399 window.dispatch_action(Box::new(RunToCursor), cx);
8400 })
8401 .separator()
8402 })
8403 .when_some(toggle_state_msg, |this, msg| {
8404 this.entry(msg, None, {
8405 let weak_editor = weak_editor.clone();
8406 let breakpoint = breakpoint.clone();
8407 move |_window, cx| {
8408 weak_editor
8409 .update(cx, |this, cx| {
8410 this.edit_breakpoint_at_anchor(
8411 anchor,
8412 breakpoint.as_ref().clone(),
8413 BreakpointEditAction::InvertState,
8414 cx,
8415 );
8416 })
8417 .log_err();
8418 }
8419 })
8420 })
8421 .entry(set_breakpoint_msg, None, {
8422 let weak_editor = weak_editor.clone();
8423 let breakpoint = breakpoint.clone();
8424 move |_window, cx| {
8425 weak_editor
8426 .update(cx, |this, cx| {
8427 this.edit_breakpoint_at_anchor(
8428 anchor,
8429 breakpoint.as_ref().clone(),
8430 BreakpointEditAction::Toggle,
8431 cx,
8432 );
8433 })
8434 .log_err();
8435 }
8436 })
8437 .entry(log_breakpoint_msg, None, {
8438 let breakpoint = breakpoint.clone();
8439 let weak_editor = weak_editor.clone();
8440 move |window, cx| {
8441 weak_editor
8442 .update(cx, |this, cx| {
8443 this.add_edit_breakpoint_block(
8444 anchor,
8445 breakpoint.as_ref(),
8446 BreakpointPromptEditAction::Log,
8447 window,
8448 cx,
8449 );
8450 })
8451 .log_err();
8452 }
8453 })
8454 .entry(condition_breakpoint_msg, None, {
8455 let breakpoint = breakpoint.clone();
8456 let weak_editor = weak_editor.clone();
8457 move |window, cx| {
8458 weak_editor
8459 .update(cx, |this, cx| {
8460 this.add_edit_breakpoint_block(
8461 anchor,
8462 breakpoint.as_ref(),
8463 BreakpointPromptEditAction::Condition,
8464 window,
8465 cx,
8466 );
8467 })
8468 .log_err();
8469 }
8470 })
8471 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8472 weak_editor
8473 .update(cx, |this, cx| {
8474 this.add_edit_breakpoint_block(
8475 anchor,
8476 breakpoint.as_ref(),
8477 BreakpointPromptEditAction::HitCondition,
8478 window,
8479 cx,
8480 );
8481 })
8482 .log_err();
8483 })
8484 })
8485 }
8486
8487 fn render_breakpoint(
8488 &self,
8489 position: Anchor,
8490 row: DisplayRow,
8491 breakpoint: &Breakpoint,
8492 state: Option<BreakpointSessionState>,
8493 cx: &mut Context<Self>,
8494 ) -> IconButton {
8495 let is_rejected = state.is_some_and(|s| !s.verified);
8496 // Is it a breakpoint that shows up when hovering over gutter?
8497 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8498 (false, false),
8499 |PhantomBreakpointIndicator {
8500 is_active,
8501 display_row,
8502 collides_with_existing_breakpoint,
8503 }| {
8504 (
8505 is_active && display_row == row,
8506 collides_with_existing_breakpoint,
8507 )
8508 },
8509 );
8510
8511 let (color, icon) = {
8512 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8513 (false, false) => ui::IconName::DebugBreakpoint,
8514 (true, false) => ui::IconName::DebugLogBreakpoint,
8515 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8516 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8517 };
8518
8519 let color = cx.theme().colors();
8520
8521 let color = if is_phantom {
8522 if collides_with_existing {
8523 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8524 } else {
8525 Color::Hint
8526 }
8527 } else if is_rejected {
8528 Color::Disabled
8529 } else {
8530 Color::Debugger
8531 };
8532
8533 (color, icon)
8534 };
8535
8536 let breakpoint = Arc::from(breakpoint.clone());
8537
8538 let alt_as_text = gpui::Keystroke {
8539 modifiers: Modifiers::secondary_key(),
8540 ..Default::default()
8541 };
8542 let primary_action_text = if breakpoint.is_disabled() {
8543 "Enable breakpoint"
8544 } else if is_phantom && !collides_with_existing {
8545 "Set breakpoint"
8546 } else {
8547 "Unset breakpoint"
8548 };
8549 let focus_handle = self.focus_handle.clone();
8550
8551 let meta = if is_rejected {
8552 SharedString::from("No executable code is associated with this line.")
8553 } else if collides_with_existing && !breakpoint.is_disabled() {
8554 SharedString::from(format!(
8555 "{alt_as_text}-click to disable,\nright-click for more options."
8556 ))
8557 } else {
8558 SharedString::from("Right-click for more options.")
8559 };
8560 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8561 .icon_size(IconSize::XSmall)
8562 .size(ui::ButtonSize::None)
8563 .when(is_rejected, |this| {
8564 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8565 })
8566 .icon_color(color)
8567 .style(ButtonStyle::Transparent)
8568 .on_click(cx.listener({
8569 move |editor, event: &ClickEvent, window, cx| {
8570 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8571 BreakpointEditAction::InvertState
8572 } else {
8573 BreakpointEditAction::Toggle
8574 };
8575
8576 window.focus(&editor.focus_handle(cx));
8577 editor.edit_breakpoint_at_anchor(
8578 position,
8579 breakpoint.as_ref().clone(),
8580 edit_action,
8581 cx,
8582 );
8583 }
8584 }))
8585 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8586 editor.set_breakpoint_context_menu(
8587 row,
8588 Some(position),
8589 event.position(),
8590 window,
8591 cx,
8592 );
8593 }))
8594 .tooltip(move |_window, cx| {
8595 Tooltip::with_meta_in(
8596 primary_action_text,
8597 Some(&ToggleBreakpoint),
8598 meta.clone(),
8599 &focus_handle,
8600 cx,
8601 )
8602 })
8603 }
8604
8605 fn build_tasks_context(
8606 project: &Entity<Project>,
8607 buffer: &Entity<Buffer>,
8608 buffer_row: u32,
8609 tasks: &Arc<RunnableTasks>,
8610 cx: &mut Context<Self>,
8611 ) -> Task<Option<task::TaskContext>> {
8612 let position = Point::new(buffer_row, tasks.column);
8613 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8614 let location = Location {
8615 buffer: buffer.clone(),
8616 range: range_start..range_start,
8617 };
8618 // Fill in the environmental variables from the tree-sitter captures
8619 let mut captured_task_variables = TaskVariables::default();
8620 for (capture_name, value) in tasks.extra_variables.clone() {
8621 captured_task_variables.insert(
8622 task::VariableName::Custom(capture_name.into()),
8623 value.clone(),
8624 );
8625 }
8626 project.update(cx, |project, cx| {
8627 project.task_store().update(cx, |task_store, cx| {
8628 task_store.task_context_for_location(captured_task_variables, location, cx)
8629 })
8630 })
8631 }
8632
8633 pub fn spawn_nearest_task(
8634 &mut self,
8635 action: &SpawnNearestTask,
8636 window: &mut Window,
8637 cx: &mut Context<Self>,
8638 ) {
8639 let Some((workspace, _)) = self.workspace.clone() else {
8640 return;
8641 };
8642 let Some(project) = self.project.clone() else {
8643 return;
8644 };
8645
8646 // Try to find a closest, enclosing node using tree-sitter that has a task
8647 let Some((buffer, buffer_row, tasks)) = self
8648 .find_enclosing_node_task(cx)
8649 // Or find the task that's closest in row-distance.
8650 .or_else(|| self.find_closest_task(cx))
8651 else {
8652 return;
8653 };
8654
8655 let reveal_strategy = action.reveal;
8656 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8657 cx.spawn_in(window, async move |_, cx| {
8658 let context = task_context.await?;
8659 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8660
8661 let resolved = &mut resolved_task.resolved;
8662 resolved.reveal = reveal_strategy;
8663
8664 workspace
8665 .update_in(cx, |workspace, window, cx| {
8666 workspace.schedule_resolved_task(
8667 task_source_kind,
8668 resolved_task,
8669 false,
8670 window,
8671 cx,
8672 );
8673 })
8674 .ok()
8675 })
8676 .detach();
8677 }
8678
8679 fn find_closest_task(
8680 &mut self,
8681 cx: &mut Context<Self>,
8682 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8683 let cursor_row = self
8684 .selections
8685 .newest_adjusted(&self.display_snapshot(cx))
8686 .head()
8687 .row;
8688
8689 let ((buffer_id, row), tasks) = self
8690 .tasks
8691 .iter()
8692 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8693
8694 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8695 let tasks = Arc::new(tasks.to_owned());
8696 Some((buffer, *row, tasks))
8697 }
8698
8699 fn find_enclosing_node_task(
8700 &mut self,
8701 cx: &mut Context<Self>,
8702 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8703 let snapshot = self.buffer.read(cx).snapshot(cx);
8704 let offset = self
8705 .selections
8706 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8707 .head();
8708 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8709 let offset = excerpt.map_offset_to_buffer(offset);
8710 let buffer_id = excerpt.buffer().remote_id();
8711
8712 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8713 let mut cursor = layer.node().walk();
8714
8715 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8716 if cursor.node().end_byte() == offset.0 {
8717 cursor.goto_next_sibling();
8718 }
8719 }
8720
8721 // Ascend to the smallest ancestor that contains the range and has a task.
8722 loop {
8723 let node = cursor.node();
8724 let node_range = node.byte_range();
8725 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8726
8727 // Check if this node contains our offset
8728 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8729 // If it contains offset, check for task
8730 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8731 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8732 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8733 }
8734 }
8735
8736 if !cursor.goto_parent() {
8737 break;
8738 }
8739 }
8740 None
8741 }
8742
8743 fn render_run_indicator(
8744 &self,
8745 _style: &EditorStyle,
8746 is_active: bool,
8747 row: DisplayRow,
8748 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8749 cx: &mut Context<Self>,
8750 ) -> IconButton {
8751 let color = Color::Muted;
8752 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8753
8754 IconButton::new(
8755 ("run_indicator", row.0 as usize),
8756 ui::IconName::PlayOutlined,
8757 )
8758 .shape(ui::IconButtonShape::Square)
8759 .icon_size(IconSize::XSmall)
8760 .icon_color(color)
8761 .toggle_state(is_active)
8762 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8763 let quick_launch = match e {
8764 ClickEvent::Keyboard(_) => true,
8765 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8766 };
8767
8768 window.focus(&editor.focus_handle(cx));
8769 editor.toggle_code_actions(
8770 &ToggleCodeActions {
8771 deployed_from: Some(CodeActionSource::RunMenu(row)),
8772 quick_launch,
8773 },
8774 window,
8775 cx,
8776 );
8777 }))
8778 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8779 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8780 }))
8781 }
8782
8783 pub fn context_menu_visible(&self) -> bool {
8784 !self.edit_prediction_preview_is_active()
8785 && self
8786 .context_menu
8787 .borrow()
8788 .as_ref()
8789 .is_some_and(|menu| menu.visible())
8790 }
8791
8792 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8793 self.context_menu
8794 .borrow()
8795 .as_ref()
8796 .map(|menu| menu.origin())
8797 }
8798
8799 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8800 self.context_menu_options = Some(options);
8801 }
8802
8803 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8804 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8805
8806 fn render_edit_prediction_popover(
8807 &mut self,
8808 text_bounds: &Bounds<Pixels>,
8809 content_origin: gpui::Point<Pixels>,
8810 right_margin: Pixels,
8811 editor_snapshot: &EditorSnapshot,
8812 visible_row_range: Range<DisplayRow>,
8813 scroll_top: ScrollOffset,
8814 scroll_bottom: ScrollOffset,
8815 line_layouts: &[LineWithInvisibles],
8816 line_height: Pixels,
8817 scroll_position: gpui::Point<ScrollOffset>,
8818 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8819 newest_selection_head: Option<DisplayPoint>,
8820 editor_width: Pixels,
8821 style: &EditorStyle,
8822 window: &mut Window,
8823 cx: &mut App,
8824 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8825 if self.mode().is_minimap() {
8826 return None;
8827 }
8828 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8829
8830 if self.edit_prediction_visible_in_cursor_popover(true) {
8831 return None;
8832 }
8833
8834 match &active_edit_prediction.completion {
8835 EditPrediction::MoveWithin { target, .. } => {
8836 let target_display_point = target.to_display_point(editor_snapshot);
8837
8838 if self.edit_prediction_requires_modifier() {
8839 if !self.edit_prediction_preview_is_active() {
8840 return None;
8841 }
8842
8843 self.render_edit_prediction_modifier_jump_popover(
8844 text_bounds,
8845 content_origin,
8846 visible_row_range,
8847 line_layouts,
8848 line_height,
8849 scroll_pixel_position,
8850 newest_selection_head,
8851 target_display_point,
8852 window,
8853 cx,
8854 )
8855 } else {
8856 self.render_edit_prediction_eager_jump_popover(
8857 text_bounds,
8858 content_origin,
8859 editor_snapshot,
8860 visible_row_range,
8861 scroll_top,
8862 scroll_bottom,
8863 line_height,
8864 scroll_pixel_position,
8865 target_display_point,
8866 editor_width,
8867 window,
8868 cx,
8869 )
8870 }
8871 }
8872 EditPrediction::Edit {
8873 display_mode: EditDisplayMode::Inline,
8874 ..
8875 } => None,
8876 EditPrediction::Edit {
8877 display_mode: EditDisplayMode::TabAccept,
8878 edits,
8879 ..
8880 } => {
8881 let range = &edits.first()?.0;
8882 let target_display_point = range.end.to_display_point(editor_snapshot);
8883
8884 self.render_edit_prediction_end_of_line_popover(
8885 "Accept",
8886 editor_snapshot,
8887 visible_row_range,
8888 target_display_point,
8889 line_height,
8890 scroll_pixel_position,
8891 content_origin,
8892 editor_width,
8893 window,
8894 cx,
8895 )
8896 }
8897 EditPrediction::Edit {
8898 edits,
8899 edit_preview,
8900 display_mode: EditDisplayMode::DiffPopover,
8901 snapshot,
8902 } => self.render_edit_prediction_diff_popover(
8903 text_bounds,
8904 content_origin,
8905 right_margin,
8906 editor_snapshot,
8907 visible_row_range,
8908 line_layouts,
8909 line_height,
8910 scroll_position,
8911 scroll_pixel_position,
8912 newest_selection_head,
8913 editor_width,
8914 style,
8915 edits,
8916 edit_preview,
8917 snapshot,
8918 window,
8919 cx,
8920 ),
8921 EditPrediction::MoveOutside { snapshot, .. } => {
8922 let mut element = self
8923 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8924 .into_any();
8925
8926 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8927 let origin_x = text_bounds.size.width - size.width - px(30.);
8928 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8929 element.prepaint_at(origin, window, cx);
8930
8931 Some((element, origin))
8932 }
8933 }
8934 }
8935
8936 fn render_edit_prediction_modifier_jump_popover(
8937 &mut self,
8938 text_bounds: &Bounds<Pixels>,
8939 content_origin: gpui::Point<Pixels>,
8940 visible_row_range: Range<DisplayRow>,
8941 line_layouts: &[LineWithInvisibles],
8942 line_height: Pixels,
8943 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8944 newest_selection_head: Option<DisplayPoint>,
8945 target_display_point: DisplayPoint,
8946 window: &mut Window,
8947 cx: &mut App,
8948 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8949 let scrolled_content_origin =
8950 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8951
8952 const SCROLL_PADDING_Y: Pixels = px(12.);
8953
8954 if target_display_point.row() < visible_row_range.start {
8955 return self.render_edit_prediction_scroll_popover(
8956 |_| SCROLL_PADDING_Y,
8957 IconName::ArrowUp,
8958 visible_row_range,
8959 line_layouts,
8960 newest_selection_head,
8961 scrolled_content_origin,
8962 window,
8963 cx,
8964 );
8965 } else if target_display_point.row() >= visible_row_range.end {
8966 return self.render_edit_prediction_scroll_popover(
8967 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8968 IconName::ArrowDown,
8969 visible_row_range,
8970 line_layouts,
8971 newest_selection_head,
8972 scrolled_content_origin,
8973 window,
8974 cx,
8975 );
8976 }
8977
8978 const POLE_WIDTH: Pixels = px(2.);
8979
8980 let line_layout =
8981 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8982 let target_column = target_display_point.column() as usize;
8983
8984 let target_x = line_layout.x_for_index(target_column);
8985 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8986 - scroll_pixel_position.y;
8987
8988 let flag_on_right = target_x < text_bounds.size.width / 2.;
8989
8990 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8991 border_color.l += 0.001;
8992
8993 let mut element = v_flex()
8994 .items_end()
8995 .when(flag_on_right, |el| el.items_start())
8996 .child(if flag_on_right {
8997 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8998 .rounded_bl(px(0.))
8999 .rounded_tl(px(0.))
9000 .border_l_2()
9001 .border_color(border_color)
9002 } else {
9003 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9004 .rounded_br(px(0.))
9005 .rounded_tr(px(0.))
9006 .border_r_2()
9007 .border_color(border_color)
9008 })
9009 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9010 .into_any();
9011
9012 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9013
9014 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9015 - point(
9016 if flag_on_right {
9017 POLE_WIDTH
9018 } else {
9019 size.width - POLE_WIDTH
9020 },
9021 size.height - line_height,
9022 );
9023
9024 origin.x = origin.x.max(content_origin.x);
9025
9026 element.prepaint_at(origin, window, cx);
9027
9028 Some((element, origin))
9029 }
9030
9031 fn render_edit_prediction_scroll_popover(
9032 &mut self,
9033 to_y: impl Fn(Size<Pixels>) -> Pixels,
9034 scroll_icon: IconName,
9035 visible_row_range: Range<DisplayRow>,
9036 line_layouts: &[LineWithInvisibles],
9037 newest_selection_head: Option<DisplayPoint>,
9038 scrolled_content_origin: gpui::Point<Pixels>,
9039 window: &mut Window,
9040 cx: &mut App,
9041 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9042 let mut element = self
9043 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9044 .into_any();
9045
9046 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9047
9048 let cursor = newest_selection_head?;
9049 let cursor_row_layout =
9050 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9051 let cursor_column = cursor.column() as usize;
9052
9053 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9054
9055 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9056
9057 element.prepaint_at(origin, window, cx);
9058 Some((element, origin))
9059 }
9060
9061 fn render_edit_prediction_eager_jump_popover(
9062 &mut self,
9063 text_bounds: &Bounds<Pixels>,
9064 content_origin: gpui::Point<Pixels>,
9065 editor_snapshot: &EditorSnapshot,
9066 visible_row_range: Range<DisplayRow>,
9067 scroll_top: ScrollOffset,
9068 scroll_bottom: ScrollOffset,
9069 line_height: Pixels,
9070 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9071 target_display_point: DisplayPoint,
9072 editor_width: Pixels,
9073 window: &mut Window,
9074 cx: &mut App,
9075 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9076 if target_display_point.row().as_f64() < scroll_top {
9077 let mut element = self
9078 .render_edit_prediction_line_popover(
9079 "Jump to Edit",
9080 Some(IconName::ArrowUp),
9081 window,
9082 cx,
9083 )
9084 .into_any();
9085
9086 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9087 let offset = point(
9088 (text_bounds.size.width - size.width) / 2.,
9089 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9090 );
9091
9092 let origin = text_bounds.origin + offset;
9093 element.prepaint_at(origin, window, cx);
9094 Some((element, origin))
9095 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9096 let mut element = self
9097 .render_edit_prediction_line_popover(
9098 "Jump to Edit",
9099 Some(IconName::ArrowDown),
9100 window,
9101 cx,
9102 )
9103 .into_any();
9104
9105 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9106 let offset = point(
9107 (text_bounds.size.width - size.width) / 2.,
9108 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9109 );
9110
9111 let origin = text_bounds.origin + offset;
9112 element.prepaint_at(origin, window, cx);
9113 Some((element, origin))
9114 } else {
9115 self.render_edit_prediction_end_of_line_popover(
9116 "Jump to Edit",
9117 editor_snapshot,
9118 visible_row_range,
9119 target_display_point,
9120 line_height,
9121 scroll_pixel_position,
9122 content_origin,
9123 editor_width,
9124 window,
9125 cx,
9126 )
9127 }
9128 }
9129
9130 fn render_edit_prediction_end_of_line_popover(
9131 self: &mut Editor,
9132 label: &'static str,
9133 editor_snapshot: &EditorSnapshot,
9134 visible_row_range: Range<DisplayRow>,
9135 target_display_point: DisplayPoint,
9136 line_height: Pixels,
9137 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9138 content_origin: gpui::Point<Pixels>,
9139 editor_width: Pixels,
9140 window: &mut Window,
9141 cx: &mut App,
9142 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9143 let target_line_end = DisplayPoint::new(
9144 target_display_point.row(),
9145 editor_snapshot.line_len(target_display_point.row()),
9146 );
9147
9148 let mut element = self
9149 .render_edit_prediction_line_popover(label, None, window, cx)
9150 .into_any();
9151
9152 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9153
9154 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
9155
9156 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9157 let mut origin = start_point
9158 + line_origin
9159 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9160 origin.x = origin.x.max(content_origin.x);
9161
9162 let max_x = content_origin.x + editor_width - size.width;
9163
9164 if origin.x > max_x {
9165 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9166
9167 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9168 origin.y += offset;
9169 IconName::ArrowUp
9170 } else {
9171 origin.y -= offset;
9172 IconName::ArrowDown
9173 };
9174
9175 element = self
9176 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9177 .into_any();
9178
9179 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9180
9181 origin.x = content_origin.x + editor_width - size.width - px(2.);
9182 }
9183
9184 element.prepaint_at(origin, window, cx);
9185 Some((element, origin))
9186 }
9187
9188 fn render_edit_prediction_diff_popover(
9189 self: &Editor,
9190 text_bounds: &Bounds<Pixels>,
9191 content_origin: gpui::Point<Pixels>,
9192 right_margin: Pixels,
9193 editor_snapshot: &EditorSnapshot,
9194 visible_row_range: Range<DisplayRow>,
9195 line_layouts: &[LineWithInvisibles],
9196 line_height: Pixels,
9197 scroll_position: gpui::Point<ScrollOffset>,
9198 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9199 newest_selection_head: Option<DisplayPoint>,
9200 editor_width: Pixels,
9201 style: &EditorStyle,
9202 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9203 edit_preview: &Option<language::EditPreview>,
9204 snapshot: &language::BufferSnapshot,
9205 window: &mut Window,
9206 cx: &mut App,
9207 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9208 let edit_start = edits
9209 .first()
9210 .unwrap()
9211 .0
9212 .start
9213 .to_display_point(editor_snapshot);
9214 let edit_end = edits
9215 .last()
9216 .unwrap()
9217 .0
9218 .end
9219 .to_display_point(editor_snapshot);
9220
9221 let is_visible = visible_row_range.contains(&edit_start.row())
9222 || visible_row_range.contains(&edit_end.row());
9223 if !is_visible {
9224 return None;
9225 }
9226
9227 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9228 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9229 } else {
9230 // Fallback for providers without edit_preview
9231 crate::edit_prediction_fallback_text(edits, cx)
9232 };
9233
9234 let styled_text = highlighted_edits.to_styled_text(&style.text);
9235 let line_count = highlighted_edits.text.lines().count();
9236
9237 const BORDER_WIDTH: Pixels = px(1.);
9238
9239 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9240 let has_keybind = keybind.is_some();
9241
9242 let mut element = h_flex()
9243 .items_start()
9244 .child(
9245 h_flex()
9246 .bg(cx.theme().colors().editor_background)
9247 .border(BORDER_WIDTH)
9248 .shadow_xs()
9249 .border_color(cx.theme().colors().border)
9250 .rounded_l_lg()
9251 .when(line_count > 1, |el| el.rounded_br_lg())
9252 .pr_1()
9253 .child(styled_text),
9254 )
9255 .child(
9256 h_flex()
9257 .h(line_height + BORDER_WIDTH * 2.)
9258 .px_1p5()
9259 .gap_1()
9260 // Workaround: For some reason, there's a gap if we don't do this
9261 .ml(-BORDER_WIDTH)
9262 .shadow(vec![gpui::BoxShadow {
9263 color: gpui::black().opacity(0.05),
9264 offset: point(px(1.), px(1.)),
9265 blur_radius: px(2.),
9266 spread_radius: px(0.),
9267 }])
9268 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9269 .border(BORDER_WIDTH)
9270 .border_color(cx.theme().colors().border)
9271 .rounded_r_lg()
9272 .id("edit_prediction_diff_popover_keybind")
9273 .when(!has_keybind, |el| {
9274 let status_colors = cx.theme().status();
9275
9276 el.bg(status_colors.error_background)
9277 .border_color(status_colors.error.opacity(0.6))
9278 .child(Icon::new(IconName::Info).color(Color::Error))
9279 .cursor_default()
9280 .hoverable_tooltip(move |_window, cx| {
9281 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9282 })
9283 })
9284 .children(keybind),
9285 )
9286 .into_any();
9287
9288 let longest_row =
9289 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9290 let longest_line_width = if visible_row_range.contains(&longest_row) {
9291 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9292 } else {
9293 layout_line(
9294 longest_row,
9295 editor_snapshot,
9296 style,
9297 editor_width,
9298 |_| false,
9299 window,
9300 cx,
9301 )
9302 .width
9303 };
9304
9305 let viewport_bounds =
9306 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9307 right: -right_margin,
9308 ..Default::default()
9309 });
9310
9311 let x_after_longest = Pixels::from(
9312 ScrollPixelOffset::from(
9313 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9314 ) - scroll_pixel_position.x,
9315 );
9316
9317 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9318
9319 // Fully visible if it can be displayed within the window (allow overlapping other
9320 // panes). However, this is only allowed if the popover starts within text_bounds.
9321 let can_position_to_the_right = x_after_longest < text_bounds.right()
9322 && x_after_longest + element_bounds.width < viewport_bounds.right();
9323
9324 let mut origin = if can_position_to_the_right {
9325 point(
9326 x_after_longest,
9327 text_bounds.origin.y
9328 + Pixels::from(
9329 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9330 - scroll_pixel_position.y,
9331 ),
9332 )
9333 } else {
9334 let cursor_row = newest_selection_head.map(|head| head.row());
9335 let above_edit = edit_start
9336 .row()
9337 .0
9338 .checked_sub(line_count as u32)
9339 .map(DisplayRow);
9340 let below_edit = Some(edit_end.row() + 1);
9341 let above_cursor =
9342 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9343 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9344
9345 // Place the edit popover adjacent to the edit if there is a location
9346 // available that is onscreen and does not obscure the cursor. Otherwise,
9347 // place it adjacent to the cursor.
9348 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9349 .into_iter()
9350 .flatten()
9351 .find(|&start_row| {
9352 let end_row = start_row + line_count as u32;
9353 visible_row_range.contains(&start_row)
9354 && visible_row_range.contains(&end_row)
9355 && cursor_row
9356 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9357 })?;
9358
9359 content_origin
9360 + point(
9361 Pixels::from(-scroll_pixel_position.x),
9362 Pixels::from(
9363 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9364 ),
9365 )
9366 };
9367
9368 origin.x -= BORDER_WIDTH;
9369
9370 window.defer_draw(element, origin, 1);
9371
9372 // Do not return an element, since it will already be drawn due to defer_draw.
9373 None
9374 }
9375
9376 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9377 px(30.)
9378 }
9379
9380 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9381 if self.read_only(cx) {
9382 cx.theme().players().read_only()
9383 } else {
9384 self.style.as_ref().unwrap().local_player
9385 }
9386 }
9387
9388 fn render_edit_prediction_accept_keybind(
9389 &self,
9390 window: &mut Window,
9391 cx: &mut App,
9392 ) -> Option<AnyElement> {
9393 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9394 let accept_keystroke = accept_binding.keystroke()?;
9395
9396 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9397
9398 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9399 Color::Accent
9400 } else {
9401 Color::Muted
9402 };
9403
9404 h_flex()
9405 .px_0p5()
9406 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9407 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9408 .text_size(TextSize::XSmall.rems(cx))
9409 .child(h_flex().children(ui::render_modifiers(
9410 accept_keystroke.modifiers(),
9411 PlatformStyle::platform(),
9412 Some(modifiers_color),
9413 Some(IconSize::XSmall.rems().into()),
9414 true,
9415 )))
9416 .when(is_platform_style_mac, |parent| {
9417 parent.child(accept_keystroke.key().to_string())
9418 })
9419 .when(!is_platform_style_mac, |parent| {
9420 parent.child(
9421 Key::new(
9422 util::capitalize(accept_keystroke.key()),
9423 Some(Color::Default),
9424 )
9425 .size(Some(IconSize::XSmall.rems().into())),
9426 )
9427 })
9428 .into_any()
9429 .into()
9430 }
9431
9432 fn render_edit_prediction_line_popover(
9433 &self,
9434 label: impl Into<SharedString>,
9435 icon: Option<IconName>,
9436 window: &mut Window,
9437 cx: &mut App,
9438 ) -> Stateful<Div> {
9439 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9440
9441 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9442 let has_keybind = keybind.is_some();
9443
9444 h_flex()
9445 .id("ep-line-popover")
9446 .py_0p5()
9447 .pl_1()
9448 .pr(padding_right)
9449 .gap_1()
9450 .rounded_md()
9451 .border_1()
9452 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9453 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9454 .shadow_xs()
9455 .when(!has_keybind, |el| {
9456 let status_colors = cx.theme().status();
9457
9458 el.bg(status_colors.error_background)
9459 .border_color(status_colors.error.opacity(0.6))
9460 .pl_2()
9461 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9462 .cursor_default()
9463 .hoverable_tooltip(move |_window, cx| {
9464 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9465 })
9466 })
9467 .children(keybind)
9468 .child(
9469 Label::new(label)
9470 .size(LabelSize::Small)
9471 .when(!has_keybind, |el| {
9472 el.color(cx.theme().status().error.into()).strikethrough()
9473 }),
9474 )
9475 .when(!has_keybind, |el| {
9476 el.child(
9477 h_flex().ml_1().child(
9478 Icon::new(IconName::Info)
9479 .size(IconSize::Small)
9480 .color(cx.theme().status().error.into()),
9481 ),
9482 )
9483 })
9484 .when_some(icon, |element, icon| {
9485 element.child(
9486 div()
9487 .mt(px(1.5))
9488 .child(Icon::new(icon).size(IconSize::Small)),
9489 )
9490 })
9491 }
9492
9493 fn render_edit_prediction_jump_outside_popover(
9494 &self,
9495 snapshot: &BufferSnapshot,
9496 window: &mut Window,
9497 cx: &mut App,
9498 ) -> Stateful<Div> {
9499 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9500 let has_keybind = keybind.is_some();
9501
9502 let file_name = snapshot
9503 .file()
9504 .map(|file| SharedString::new(file.file_name(cx)))
9505 .unwrap_or(SharedString::new_static("untitled"));
9506
9507 h_flex()
9508 .id("ep-jump-outside-popover")
9509 .py_1()
9510 .px_2()
9511 .gap_1()
9512 .rounded_md()
9513 .border_1()
9514 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9515 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9516 .shadow_xs()
9517 .when(!has_keybind, |el| {
9518 let status_colors = cx.theme().status();
9519
9520 el.bg(status_colors.error_background)
9521 .border_color(status_colors.error.opacity(0.6))
9522 .pl_2()
9523 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9524 .cursor_default()
9525 .hoverable_tooltip(move |_window, cx| {
9526 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9527 })
9528 })
9529 .children(keybind)
9530 .child(
9531 Label::new(file_name)
9532 .size(LabelSize::Small)
9533 .buffer_font(cx)
9534 .when(!has_keybind, |el| {
9535 el.color(cx.theme().status().error.into()).strikethrough()
9536 }),
9537 )
9538 .when(!has_keybind, |el| {
9539 el.child(
9540 h_flex().ml_1().child(
9541 Icon::new(IconName::Info)
9542 .size(IconSize::Small)
9543 .color(cx.theme().status().error.into()),
9544 ),
9545 )
9546 })
9547 .child(
9548 div()
9549 .mt(px(1.5))
9550 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9551 )
9552 }
9553
9554 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9555 let accent_color = cx.theme().colors().text_accent;
9556 let editor_bg_color = cx.theme().colors().editor_background;
9557 editor_bg_color.blend(accent_color.opacity(0.1))
9558 }
9559
9560 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9561 let accent_color = cx.theme().colors().text_accent;
9562 let editor_bg_color = cx.theme().colors().editor_background;
9563 editor_bg_color.blend(accent_color.opacity(0.6))
9564 }
9565 fn get_prediction_provider_icon_name(
9566 provider: &Option<RegisteredEditPredictionProvider>,
9567 ) -> IconName {
9568 match provider {
9569 Some(provider) => match provider.provider.name() {
9570 "copilot" => IconName::Copilot,
9571 "supermaven" => IconName::Supermaven,
9572 _ => IconName::ZedPredict,
9573 },
9574 None => IconName::ZedPredict,
9575 }
9576 }
9577
9578 fn render_edit_prediction_cursor_popover(
9579 &self,
9580 min_width: Pixels,
9581 max_width: Pixels,
9582 cursor_point: Point,
9583 style: &EditorStyle,
9584 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9585 _window: &Window,
9586 cx: &mut Context<Editor>,
9587 ) -> Option<AnyElement> {
9588 let provider = self.edit_prediction_provider.as_ref()?;
9589 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9590
9591 let is_refreshing = provider.provider.is_refreshing(cx);
9592
9593 fn pending_completion_container(icon: IconName) -> Div {
9594 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9595 }
9596
9597 let completion = match &self.active_edit_prediction {
9598 Some(prediction) => {
9599 if !self.has_visible_completions_menu() {
9600 const RADIUS: Pixels = px(6.);
9601 const BORDER_WIDTH: Pixels = px(1.);
9602
9603 return Some(
9604 h_flex()
9605 .elevation_2(cx)
9606 .border(BORDER_WIDTH)
9607 .border_color(cx.theme().colors().border)
9608 .when(accept_keystroke.is_none(), |el| {
9609 el.border_color(cx.theme().status().error)
9610 })
9611 .rounded(RADIUS)
9612 .rounded_tl(px(0.))
9613 .overflow_hidden()
9614 .child(div().px_1p5().child(match &prediction.completion {
9615 EditPrediction::MoveWithin { target, snapshot } => {
9616 use text::ToPoint as _;
9617 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9618 {
9619 Icon::new(IconName::ZedPredictDown)
9620 } else {
9621 Icon::new(IconName::ZedPredictUp)
9622 }
9623 }
9624 EditPrediction::MoveOutside { .. } => {
9625 // TODO [zeta2] custom icon for external jump?
9626 Icon::new(provider_icon)
9627 }
9628 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9629 }))
9630 .child(
9631 h_flex()
9632 .gap_1()
9633 .py_1()
9634 .px_2()
9635 .rounded_r(RADIUS - BORDER_WIDTH)
9636 .border_l_1()
9637 .border_color(cx.theme().colors().border)
9638 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9639 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9640 el.child(
9641 Label::new("Hold")
9642 .size(LabelSize::Small)
9643 .when(accept_keystroke.is_none(), |el| {
9644 el.strikethrough()
9645 })
9646 .line_height_style(LineHeightStyle::UiLabel),
9647 )
9648 })
9649 .id("edit_prediction_cursor_popover_keybind")
9650 .when(accept_keystroke.is_none(), |el| {
9651 let status_colors = cx.theme().status();
9652
9653 el.bg(status_colors.error_background)
9654 .border_color(status_colors.error.opacity(0.6))
9655 .child(Icon::new(IconName::Info).color(Color::Error))
9656 .cursor_default()
9657 .hoverable_tooltip(move |_window, cx| {
9658 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9659 .into()
9660 })
9661 })
9662 .when_some(
9663 accept_keystroke.as_ref(),
9664 |el, accept_keystroke| {
9665 el.child(h_flex().children(ui::render_modifiers(
9666 accept_keystroke.modifiers(),
9667 PlatformStyle::platform(),
9668 Some(Color::Default),
9669 Some(IconSize::XSmall.rems().into()),
9670 false,
9671 )))
9672 },
9673 ),
9674 )
9675 .into_any(),
9676 );
9677 }
9678
9679 self.render_edit_prediction_cursor_popover_preview(
9680 prediction,
9681 cursor_point,
9682 style,
9683 cx,
9684 )?
9685 }
9686
9687 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9688 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9689 stale_completion,
9690 cursor_point,
9691 style,
9692 cx,
9693 )?,
9694
9695 None => pending_completion_container(provider_icon)
9696 .child(Label::new("...").size(LabelSize::Small)),
9697 },
9698
9699 None => pending_completion_container(provider_icon)
9700 .child(Label::new("...").size(LabelSize::Small)),
9701 };
9702
9703 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9704 completion
9705 .with_animation(
9706 "loading-completion",
9707 Animation::new(Duration::from_secs(2))
9708 .repeat()
9709 .with_easing(pulsating_between(0.4, 0.8)),
9710 |label, delta| label.opacity(delta),
9711 )
9712 .into_any_element()
9713 } else {
9714 completion.into_any_element()
9715 };
9716
9717 let has_completion = self.active_edit_prediction.is_some();
9718
9719 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9720 Some(
9721 h_flex()
9722 .min_w(min_width)
9723 .max_w(max_width)
9724 .flex_1()
9725 .elevation_2(cx)
9726 .border_color(cx.theme().colors().border)
9727 .child(
9728 div()
9729 .flex_1()
9730 .py_1()
9731 .px_2()
9732 .overflow_hidden()
9733 .child(completion),
9734 )
9735 .when_some(accept_keystroke, |el, accept_keystroke| {
9736 if !accept_keystroke.modifiers().modified() {
9737 return el;
9738 }
9739
9740 el.child(
9741 h_flex()
9742 .h_full()
9743 .border_l_1()
9744 .rounded_r_lg()
9745 .border_color(cx.theme().colors().border)
9746 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9747 .gap_1()
9748 .py_1()
9749 .px_2()
9750 .child(
9751 h_flex()
9752 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9753 .when(is_platform_style_mac, |parent| parent.gap_1())
9754 .child(h_flex().children(ui::render_modifiers(
9755 accept_keystroke.modifiers(),
9756 PlatformStyle::platform(),
9757 Some(if !has_completion {
9758 Color::Muted
9759 } else {
9760 Color::Default
9761 }),
9762 None,
9763 false,
9764 ))),
9765 )
9766 .child(Label::new("Preview").into_any_element())
9767 .opacity(if has_completion { 1.0 } else { 0.4 }),
9768 )
9769 })
9770 .into_any(),
9771 )
9772 }
9773
9774 fn render_edit_prediction_cursor_popover_preview(
9775 &self,
9776 completion: &EditPredictionState,
9777 cursor_point: Point,
9778 style: &EditorStyle,
9779 cx: &mut Context<Editor>,
9780 ) -> Option<Div> {
9781 use text::ToPoint as _;
9782
9783 fn render_relative_row_jump(
9784 prefix: impl Into<String>,
9785 current_row: u32,
9786 target_row: u32,
9787 ) -> Div {
9788 let (row_diff, arrow) = if target_row < current_row {
9789 (current_row - target_row, IconName::ArrowUp)
9790 } else {
9791 (target_row - current_row, IconName::ArrowDown)
9792 };
9793
9794 h_flex()
9795 .child(
9796 Label::new(format!("{}{}", prefix.into(), row_diff))
9797 .color(Color::Muted)
9798 .size(LabelSize::Small),
9799 )
9800 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9801 }
9802
9803 let supports_jump = self
9804 .edit_prediction_provider
9805 .as_ref()
9806 .map(|provider| provider.provider.supports_jump_to_edit())
9807 .unwrap_or(true);
9808
9809 match &completion.completion {
9810 EditPrediction::MoveWithin {
9811 target, snapshot, ..
9812 } => {
9813 if !supports_jump {
9814 return None;
9815 }
9816
9817 Some(
9818 h_flex()
9819 .px_2()
9820 .gap_2()
9821 .flex_1()
9822 .child(
9823 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9824 Icon::new(IconName::ZedPredictDown)
9825 } else {
9826 Icon::new(IconName::ZedPredictUp)
9827 },
9828 )
9829 .child(Label::new("Jump to Edit")),
9830 )
9831 }
9832 EditPrediction::MoveOutside { snapshot, .. } => {
9833 let file_name = snapshot
9834 .file()
9835 .map(|file| file.file_name(cx))
9836 .unwrap_or("untitled");
9837 Some(
9838 h_flex()
9839 .px_2()
9840 .gap_2()
9841 .flex_1()
9842 .child(Icon::new(IconName::ZedPredict))
9843 .child(Label::new(format!("Jump to {file_name}"))),
9844 )
9845 }
9846 EditPrediction::Edit {
9847 edits,
9848 edit_preview,
9849 snapshot,
9850 display_mode: _,
9851 } => {
9852 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9853
9854 let (highlighted_edits, has_more_lines) =
9855 if let Some(edit_preview) = edit_preview.as_ref() {
9856 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9857 .first_line_preview()
9858 } else {
9859 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9860 };
9861
9862 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9863 .with_default_highlights(&style.text, highlighted_edits.highlights);
9864
9865 let preview = h_flex()
9866 .gap_1()
9867 .min_w_16()
9868 .child(styled_text)
9869 .when(has_more_lines, |parent| parent.child("…"));
9870
9871 let left = if supports_jump && first_edit_row != cursor_point.row {
9872 render_relative_row_jump("", cursor_point.row, first_edit_row)
9873 .into_any_element()
9874 } else {
9875 let icon_name =
9876 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9877 Icon::new(icon_name).into_any_element()
9878 };
9879
9880 Some(
9881 h_flex()
9882 .h_full()
9883 .flex_1()
9884 .gap_2()
9885 .pr_1()
9886 .overflow_x_hidden()
9887 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9888 .child(left)
9889 .child(preview),
9890 )
9891 }
9892 }
9893 }
9894
9895 pub fn render_context_menu(
9896 &self,
9897 style: &EditorStyle,
9898 max_height_in_lines: u32,
9899 window: &mut Window,
9900 cx: &mut Context<Editor>,
9901 ) -> Option<AnyElement> {
9902 let menu = self.context_menu.borrow();
9903 let menu = menu.as_ref()?;
9904 if !menu.visible() {
9905 return None;
9906 };
9907 Some(menu.render(style, max_height_in_lines, window, cx))
9908 }
9909
9910 fn render_context_menu_aside(
9911 &mut self,
9912 max_size: Size<Pixels>,
9913 window: &mut Window,
9914 cx: &mut Context<Editor>,
9915 ) -> Option<AnyElement> {
9916 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9917 if menu.visible() {
9918 menu.render_aside(max_size, window, cx)
9919 } else {
9920 None
9921 }
9922 })
9923 }
9924
9925 fn hide_context_menu(
9926 &mut self,
9927 window: &mut Window,
9928 cx: &mut Context<Self>,
9929 ) -> Option<CodeContextMenu> {
9930 cx.notify();
9931 self.completion_tasks.clear();
9932 let context_menu = self.context_menu.borrow_mut().take();
9933 self.stale_edit_prediction_in_menu.take();
9934 self.update_visible_edit_prediction(window, cx);
9935 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9936 && let Some(completion_provider) = &self.completion_provider
9937 {
9938 completion_provider.selection_changed(None, window, cx);
9939 }
9940 context_menu
9941 }
9942
9943 fn show_snippet_choices(
9944 &mut self,
9945 choices: &Vec<String>,
9946 selection: Range<Anchor>,
9947 cx: &mut Context<Self>,
9948 ) {
9949 let Some((_, buffer, _)) = self
9950 .buffer()
9951 .read(cx)
9952 .excerpt_containing(selection.start, cx)
9953 else {
9954 return;
9955 };
9956 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9957 else {
9958 return;
9959 };
9960 if buffer != end_buffer {
9961 log::error!("expected anchor range to have matching buffer IDs");
9962 return;
9963 }
9964
9965 let id = post_inc(&mut self.next_completion_id);
9966 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9967 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9968 CompletionsMenu::new_snippet_choices(
9969 id,
9970 true,
9971 choices,
9972 selection,
9973 buffer,
9974 snippet_sort_order,
9975 ),
9976 ));
9977 }
9978
9979 pub fn insert_snippet(
9980 &mut self,
9981 insertion_ranges: &[Range<MultiBufferOffset>],
9982 snippet: Snippet,
9983 window: &mut Window,
9984 cx: &mut Context<Self>,
9985 ) -> Result<()> {
9986 struct Tabstop<T> {
9987 is_end_tabstop: bool,
9988 ranges: Vec<Range<T>>,
9989 choices: Option<Vec<String>>,
9990 }
9991
9992 let tabstops = self.buffer.update(cx, |buffer, cx| {
9993 let snippet_text: Arc<str> = snippet.text.clone().into();
9994 let edits = insertion_ranges
9995 .iter()
9996 .cloned()
9997 .map(|range| (range, snippet_text.clone()));
9998 let autoindent_mode = AutoindentMode::Block {
9999 original_indent_columns: Vec::new(),
10000 };
10001 buffer.edit(edits, Some(autoindent_mode), cx);
10002
10003 let snapshot = &*buffer.read(cx);
10004 let snippet = &snippet;
10005 snippet
10006 .tabstops
10007 .iter()
10008 .map(|tabstop| {
10009 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10010 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10011 });
10012 let mut tabstop_ranges = tabstop
10013 .ranges
10014 .iter()
10015 .flat_map(|tabstop_range| {
10016 let mut delta = 0_isize;
10017 insertion_ranges.iter().map(move |insertion_range| {
10018 let insertion_start = insertion_range.start + delta;
10019 delta += snippet.text.len() as isize
10020 - (insertion_range.end - insertion_range.start) as isize;
10021
10022 let start =
10023 (insertion_start + tabstop_range.start).min(snapshot.len());
10024 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10025 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10026 })
10027 })
10028 .collect::<Vec<_>>();
10029 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10030
10031 Tabstop {
10032 is_end_tabstop,
10033 ranges: tabstop_ranges,
10034 choices: tabstop.choices.clone(),
10035 }
10036 })
10037 .collect::<Vec<_>>()
10038 });
10039 if let Some(tabstop) = tabstops.first() {
10040 self.change_selections(Default::default(), window, cx, |s| {
10041 // Reverse order so that the first range is the newest created selection.
10042 // Completions will use it and autoscroll will prioritize it.
10043 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10044 });
10045
10046 if let Some(choices) = &tabstop.choices
10047 && let Some(selection) = tabstop.ranges.first()
10048 {
10049 self.show_snippet_choices(choices, selection.clone(), cx)
10050 }
10051
10052 // If we're already at the last tabstop and it's at the end of the snippet,
10053 // we're done, we don't need to keep the state around.
10054 if !tabstop.is_end_tabstop {
10055 let choices = tabstops
10056 .iter()
10057 .map(|tabstop| tabstop.choices.clone())
10058 .collect();
10059
10060 let ranges = tabstops
10061 .into_iter()
10062 .map(|tabstop| tabstop.ranges)
10063 .collect::<Vec<_>>();
10064
10065 self.snippet_stack.push(SnippetState {
10066 active_index: 0,
10067 ranges,
10068 choices,
10069 });
10070 }
10071
10072 // Check whether the just-entered snippet ends with an auto-closable bracket.
10073 if self.autoclose_regions.is_empty() {
10074 let snapshot = self.buffer.read(cx).snapshot(cx);
10075 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10076 let selection_head = selection.head();
10077 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10078 continue;
10079 };
10080
10081 let mut bracket_pair = None;
10082 let max_lookup_length = scope
10083 .brackets()
10084 .map(|(pair, _)| {
10085 pair.start
10086 .as_str()
10087 .chars()
10088 .count()
10089 .max(pair.end.as_str().chars().count())
10090 })
10091 .max();
10092 if let Some(max_lookup_length) = max_lookup_length {
10093 let next_text = snapshot
10094 .chars_at(selection_head)
10095 .take(max_lookup_length)
10096 .collect::<String>();
10097 let prev_text = snapshot
10098 .reversed_chars_at(selection_head)
10099 .take(max_lookup_length)
10100 .collect::<String>();
10101
10102 for (pair, enabled) in scope.brackets() {
10103 if enabled
10104 && pair.close
10105 && prev_text.starts_with(pair.start.as_str())
10106 && next_text.starts_with(pair.end.as_str())
10107 {
10108 bracket_pair = Some(pair.clone());
10109 break;
10110 }
10111 }
10112 }
10113
10114 if let Some(pair) = bracket_pair {
10115 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10116 let autoclose_enabled =
10117 self.use_autoclose && snapshot_settings.use_autoclose;
10118 if autoclose_enabled {
10119 let start = snapshot.anchor_after(selection_head);
10120 let end = snapshot.anchor_after(selection_head);
10121 self.autoclose_regions.push(AutocloseRegion {
10122 selection_id: selection.id,
10123 range: start..end,
10124 pair,
10125 });
10126 }
10127 }
10128 }
10129 }
10130 }
10131 Ok(())
10132 }
10133
10134 pub fn move_to_next_snippet_tabstop(
10135 &mut self,
10136 window: &mut Window,
10137 cx: &mut Context<Self>,
10138 ) -> bool {
10139 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10140 }
10141
10142 pub fn move_to_prev_snippet_tabstop(
10143 &mut self,
10144 window: &mut Window,
10145 cx: &mut Context<Self>,
10146 ) -> bool {
10147 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10148 }
10149
10150 pub fn move_to_snippet_tabstop(
10151 &mut self,
10152 bias: Bias,
10153 window: &mut Window,
10154 cx: &mut Context<Self>,
10155 ) -> bool {
10156 if let Some(mut snippet) = self.snippet_stack.pop() {
10157 match bias {
10158 Bias::Left => {
10159 if snippet.active_index > 0 {
10160 snippet.active_index -= 1;
10161 } else {
10162 self.snippet_stack.push(snippet);
10163 return false;
10164 }
10165 }
10166 Bias::Right => {
10167 if snippet.active_index + 1 < snippet.ranges.len() {
10168 snippet.active_index += 1;
10169 } else {
10170 self.snippet_stack.push(snippet);
10171 return false;
10172 }
10173 }
10174 }
10175 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10176 self.change_selections(Default::default(), window, cx, |s| {
10177 // Reverse order so that the first range is the newest created selection.
10178 // Completions will use it and autoscroll will prioritize it.
10179 s.select_ranges(current_ranges.iter().rev().cloned())
10180 });
10181
10182 if let Some(choices) = &snippet.choices[snippet.active_index]
10183 && let Some(selection) = current_ranges.first()
10184 {
10185 self.show_snippet_choices(choices, selection.clone(), cx);
10186 }
10187
10188 // If snippet state is not at the last tabstop, push it back on the stack
10189 if snippet.active_index + 1 < snippet.ranges.len() {
10190 self.snippet_stack.push(snippet);
10191 }
10192 return true;
10193 }
10194 }
10195
10196 false
10197 }
10198
10199 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10200 self.transact(window, cx, |this, window, cx| {
10201 this.select_all(&SelectAll, window, cx);
10202 this.insert("", window, cx);
10203 });
10204 }
10205
10206 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10207 if self.read_only(cx) {
10208 return;
10209 }
10210 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10211 self.transact(window, cx, |this, window, cx| {
10212 this.select_autoclose_pair(window, cx);
10213
10214 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10215
10216 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10217 if !this.linked_edit_ranges.is_empty() {
10218 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10219 let snapshot = this.buffer.read(cx).snapshot(cx);
10220
10221 for selection in selections.iter() {
10222 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10223 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10224 if selection_start.buffer_id != selection_end.buffer_id {
10225 continue;
10226 }
10227 if let Some(ranges) =
10228 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10229 {
10230 for (buffer, entries) in ranges {
10231 linked_ranges.entry(buffer).or_default().extend(entries);
10232 }
10233 }
10234 }
10235 }
10236
10237 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10238 for selection in &mut selections {
10239 if selection.is_empty() {
10240 let old_head = selection.head();
10241 let mut new_head =
10242 movement::left(&display_map, old_head.to_display_point(&display_map))
10243 .to_point(&display_map);
10244 if let Some((buffer, line_buffer_range)) = display_map
10245 .buffer_snapshot()
10246 .buffer_line_for_row(MultiBufferRow(old_head.row))
10247 {
10248 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10249 let indent_len = match indent_size.kind {
10250 IndentKind::Space => {
10251 buffer.settings_at(line_buffer_range.start, cx).tab_size
10252 }
10253 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10254 };
10255 if old_head.column <= indent_size.len && old_head.column > 0 {
10256 let indent_len = indent_len.get();
10257 new_head = cmp::min(
10258 new_head,
10259 MultiBufferPoint::new(
10260 old_head.row,
10261 ((old_head.column - 1) / indent_len) * indent_len,
10262 ),
10263 );
10264 }
10265 }
10266
10267 selection.set_head(new_head, SelectionGoal::None);
10268 }
10269 }
10270
10271 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10272 this.insert("", window, cx);
10273 let empty_str: Arc<str> = Arc::from("");
10274 for (buffer, edits) in linked_ranges {
10275 let snapshot = buffer.read(cx).snapshot();
10276 use text::ToPoint as TP;
10277
10278 let edits = edits
10279 .into_iter()
10280 .map(|range| {
10281 let end_point = TP::to_point(&range.end, &snapshot);
10282 let mut start_point = TP::to_point(&range.start, &snapshot);
10283
10284 if end_point == start_point {
10285 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10286 .saturating_sub(1);
10287 start_point =
10288 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10289 };
10290
10291 (start_point..end_point, empty_str.clone())
10292 })
10293 .sorted_by_key(|(range, _)| range.start)
10294 .collect::<Vec<_>>();
10295 buffer.update(cx, |this, cx| {
10296 this.edit(edits, None, cx);
10297 })
10298 }
10299 this.refresh_edit_prediction(true, false, window, cx);
10300 refresh_linked_ranges(this, window, cx);
10301 });
10302 }
10303
10304 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10305 if self.read_only(cx) {
10306 return;
10307 }
10308 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10309 self.transact(window, cx, |this, window, cx| {
10310 this.change_selections(Default::default(), window, cx, |s| {
10311 s.move_with(|map, selection| {
10312 if selection.is_empty() {
10313 let cursor = movement::right(map, selection.head());
10314 selection.end = cursor;
10315 selection.reversed = true;
10316 selection.goal = SelectionGoal::None;
10317 }
10318 })
10319 });
10320 this.insert("", window, cx);
10321 this.refresh_edit_prediction(true, false, window, cx);
10322 });
10323 }
10324
10325 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10326 if self.mode.is_single_line() {
10327 cx.propagate();
10328 return;
10329 }
10330
10331 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10332 if self.move_to_prev_snippet_tabstop(window, cx) {
10333 return;
10334 }
10335 self.outdent(&Outdent, window, cx);
10336 }
10337
10338 pub fn next_snippet_tabstop(
10339 &mut self,
10340 _: &NextSnippetTabstop,
10341 window: &mut Window,
10342 cx: &mut Context<Self>,
10343 ) {
10344 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10345 cx.propagate();
10346 return;
10347 }
10348
10349 if self.move_to_next_snippet_tabstop(window, cx) {
10350 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10351 return;
10352 }
10353 cx.propagate();
10354 }
10355
10356 pub fn previous_snippet_tabstop(
10357 &mut self,
10358 _: &PreviousSnippetTabstop,
10359 window: &mut Window,
10360 cx: &mut Context<Self>,
10361 ) {
10362 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10363 cx.propagate();
10364 return;
10365 }
10366
10367 if self.move_to_prev_snippet_tabstop(window, cx) {
10368 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10369 return;
10370 }
10371 cx.propagate();
10372 }
10373
10374 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10375 if self.mode.is_single_line() {
10376 cx.propagate();
10377 return;
10378 }
10379
10380 if self.move_to_next_snippet_tabstop(window, cx) {
10381 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10382 return;
10383 }
10384 if self.read_only(cx) {
10385 return;
10386 }
10387 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10388 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10389 let buffer = self.buffer.read(cx);
10390 let snapshot = buffer.snapshot(cx);
10391 let rows_iter = selections.iter().map(|s| s.head().row);
10392 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10393
10394 let has_some_cursor_in_whitespace = selections
10395 .iter()
10396 .filter(|selection| selection.is_empty())
10397 .any(|selection| {
10398 let cursor = selection.head();
10399 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10400 cursor.column < current_indent.len
10401 });
10402
10403 let mut edits = Vec::new();
10404 let mut prev_edited_row = 0;
10405 let mut row_delta = 0;
10406 for selection in &mut selections {
10407 if selection.start.row != prev_edited_row {
10408 row_delta = 0;
10409 }
10410 prev_edited_row = selection.end.row;
10411
10412 // If the selection is non-empty, then increase the indentation of the selected lines.
10413 if !selection.is_empty() {
10414 row_delta =
10415 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10416 continue;
10417 }
10418
10419 let cursor = selection.head();
10420 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10421 if let Some(suggested_indent) =
10422 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10423 {
10424 // Don't do anything if already at suggested indent
10425 // and there is any other cursor which is not
10426 if has_some_cursor_in_whitespace
10427 && cursor.column == current_indent.len
10428 && current_indent.len == suggested_indent.len
10429 {
10430 continue;
10431 }
10432
10433 // Adjust line and move cursor to suggested indent
10434 // if cursor is not at suggested indent
10435 if cursor.column < suggested_indent.len
10436 && cursor.column <= current_indent.len
10437 && current_indent.len <= suggested_indent.len
10438 {
10439 selection.start = Point::new(cursor.row, suggested_indent.len);
10440 selection.end = selection.start;
10441 if row_delta == 0 {
10442 edits.extend(Buffer::edit_for_indent_size_adjustment(
10443 cursor.row,
10444 current_indent,
10445 suggested_indent,
10446 ));
10447 row_delta = suggested_indent.len - current_indent.len;
10448 }
10449 continue;
10450 }
10451
10452 // If current indent is more than suggested indent
10453 // only move cursor to current indent and skip indent
10454 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10455 selection.start = Point::new(cursor.row, current_indent.len);
10456 selection.end = selection.start;
10457 continue;
10458 }
10459 }
10460
10461 // Otherwise, insert a hard or soft tab.
10462 let settings = buffer.language_settings_at(cursor, cx);
10463 let tab_size = if settings.hard_tabs {
10464 IndentSize::tab()
10465 } else {
10466 let tab_size = settings.tab_size.get();
10467 let indent_remainder = snapshot
10468 .text_for_range(Point::new(cursor.row, 0)..cursor)
10469 .flat_map(str::chars)
10470 .fold(row_delta % tab_size, |counter: u32, c| {
10471 if c == '\t' {
10472 0
10473 } else {
10474 (counter + 1) % tab_size
10475 }
10476 });
10477
10478 let chars_to_next_tab_stop = tab_size - indent_remainder;
10479 IndentSize::spaces(chars_to_next_tab_stop)
10480 };
10481 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10482 selection.end = selection.start;
10483 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10484 row_delta += tab_size.len;
10485 }
10486
10487 self.transact(window, cx, |this, window, cx| {
10488 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10489 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10490 this.refresh_edit_prediction(true, false, window, cx);
10491 });
10492 }
10493
10494 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10495 if self.read_only(cx) {
10496 return;
10497 }
10498 if self.mode.is_single_line() {
10499 cx.propagate();
10500 return;
10501 }
10502
10503 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10504 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10505 let mut prev_edited_row = 0;
10506 let mut row_delta = 0;
10507 let mut edits = Vec::new();
10508 let buffer = self.buffer.read(cx);
10509 let snapshot = buffer.snapshot(cx);
10510 for selection in &mut selections {
10511 if selection.start.row != prev_edited_row {
10512 row_delta = 0;
10513 }
10514 prev_edited_row = selection.end.row;
10515
10516 row_delta =
10517 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10518 }
10519
10520 self.transact(window, cx, |this, window, cx| {
10521 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10522 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10523 });
10524 }
10525
10526 fn indent_selection(
10527 buffer: &MultiBuffer,
10528 snapshot: &MultiBufferSnapshot,
10529 selection: &mut Selection<Point>,
10530 edits: &mut Vec<(Range<Point>, String)>,
10531 delta_for_start_row: u32,
10532 cx: &App,
10533 ) -> u32 {
10534 let settings = buffer.language_settings_at(selection.start, cx);
10535 let tab_size = settings.tab_size.get();
10536 let indent_kind = if settings.hard_tabs {
10537 IndentKind::Tab
10538 } else {
10539 IndentKind::Space
10540 };
10541 let mut start_row = selection.start.row;
10542 let mut end_row = selection.end.row + 1;
10543
10544 // If a selection ends at the beginning of a line, don't indent
10545 // that last line.
10546 if selection.end.column == 0 && selection.end.row > selection.start.row {
10547 end_row -= 1;
10548 }
10549
10550 // Avoid re-indenting a row that has already been indented by a
10551 // previous selection, but still update this selection's column
10552 // to reflect that indentation.
10553 if delta_for_start_row > 0 {
10554 start_row += 1;
10555 selection.start.column += delta_for_start_row;
10556 if selection.end.row == selection.start.row {
10557 selection.end.column += delta_for_start_row;
10558 }
10559 }
10560
10561 let mut delta_for_end_row = 0;
10562 let has_multiple_rows = start_row + 1 != end_row;
10563 for row in start_row..end_row {
10564 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10565 let indent_delta = match (current_indent.kind, indent_kind) {
10566 (IndentKind::Space, IndentKind::Space) => {
10567 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10568 IndentSize::spaces(columns_to_next_tab_stop)
10569 }
10570 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10571 (_, IndentKind::Tab) => IndentSize::tab(),
10572 };
10573
10574 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10575 0
10576 } else {
10577 selection.start.column
10578 };
10579 let row_start = Point::new(row, start);
10580 edits.push((
10581 row_start..row_start,
10582 indent_delta.chars().collect::<String>(),
10583 ));
10584
10585 // Update this selection's endpoints to reflect the indentation.
10586 if row == selection.start.row {
10587 selection.start.column += indent_delta.len;
10588 }
10589 if row == selection.end.row {
10590 selection.end.column += indent_delta.len;
10591 delta_for_end_row = indent_delta.len;
10592 }
10593 }
10594
10595 if selection.start.row == selection.end.row {
10596 delta_for_start_row + delta_for_end_row
10597 } else {
10598 delta_for_end_row
10599 }
10600 }
10601
10602 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10603 if self.read_only(cx) {
10604 return;
10605 }
10606 if self.mode.is_single_line() {
10607 cx.propagate();
10608 return;
10609 }
10610
10611 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10612 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10613 let selections = self.selections.all::<Point>(&display_map);
10614 let mut deletion_ranges = Vec::new();
10615 let mut last_outdent = None;
10616 {
10617 let buffer = self.buffer.read(cx);
10618 let snapshot = buffer.snapshot(cx);
10619 for selection in &selections {
10620 let settings = buffer.language_settings_at(selection.start, cx);
10621 let tab_size = settings.tab_size.get();
10622 let mut rows = selection.spanned_rows(false, &display_map);
10623
10624 // Avoid re-outdenting a row that has already been outdented by a
10625 // previous selection.
10626 if let Some(last_row) = last_outdent
10627 && last_row == rows.start
10628 {
10629 rows.start = rows.start.next_row();
10630 }
10631 let has_multiple_rows = rows.len() > 1;
10632 for row in rows.iter_rows() {
10633 let indent_size = snapshot.indent_size_for_line(row);
10634 if indent_size.len > 0 {
10635 let deletion_len = match indent_size.kind {
10636 IndentKind::Space => {
10637 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10638 if columns_to_prev_tab_stop == 0 {
10639 tab_size
10640 } else {
10641 columns_to_prev_tab_stop
10642 }
10643 }
10644 IndentKind::Tab => 1,
10645 };
10646 let start = if has_multiple_rows
10647 || deletion_len > selection.start.column
10648 || indent_size.len < selection.start.column
10649 {
10650 0
10651 } else {
10652 selection.start.column - deletion_len
10653 };
10654 deletion_ranges.push(
10655 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10656 );
10657 last_outdent = Some(row);
10658 }
10659 }
10660 }
10661 }
10662
10663 self.transact(window, cx, |this, window, cx| {
10664 this.buffer.update(cx, |buffer, cx| {
10665 let empty_str: Arc<str> = Arc::default();
10666 buffer.edit(
10667 deletion_ranges
10668 .into_iter()
10669 .map(|range| (range, empty_str.clone())),
10670 None,
10671 cx,
10672 );
10673 });
10674 let selections = this
10675 .selections
10676 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10677 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10678 });
10679 }
10680
10681 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10682 if self.read_only(cx) {
10683 return;
10684 }
10685 if self.mode.is_single_line() {
10686 cx.propagate();
10687 return;
10688 }
10689
10690 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10691 let selections = self
10692 .selections
10693 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10694 .into_iter()
10695 .map(|s| s.range());
10696
10697 self.transact(window, cx, |this, window, cx| {
10698 this.buffer.update(cx, |buffer, cx| {
10699 buffer.autoindent_ranges(selections, cx);
10700 });
10701 let selections = this
10702 .selections
10703 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10704 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10705 });
10706 }
10707
10708 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10709 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10711 let selections = self.selections.all::<Point>(&display_map);
10712
10713 let mut new_cursors = Vec::new();
10714 let mut edit_ranges = Vec::new();
10715 let mut selections = selections.iter().peekable();
10716 while let Some(selection) = selections.next() {
10717 let mut rows = selection.spanned_rows(false, &display_map);
10718
10719 // Accumulate contiguous regions of rows that we want to delete.
10720 while let Some(next_selection) = selections.peek() {
10721 let next_rows = next_selection.spanned_rows(false, &display_map);
10722 if next_rows.start <= rows.end {
10723 rows.end = next_rows.end;
10724 selections.next().unwrap();
10725 } else {
10726 break;
10727 }
10728 }
10729
10730 let buffer = display_map.buffer_snapshot();
10731 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10732 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10733 // If there's a line after the range, delete the \n from the end of the row range
10734 (
10735 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10736 rows.end,
10737 )
10738 } else {
10739 // If there isn't a line after the range, delete the \n from the line before the
10740 // start of the row range
10741 edit_start = edit_start.saturating_sub_usize(1);
10742 (buffer.len(), rows.start.previous_row())
10743 };
10744
10745 let text_layout_details = self.text_layout_details(window);
10746 let x = display_map.x_for_display_point(
10747 selection.head().to_display_point(&display_map),
10748 &text_layout_details,
10749 );
10750 let row = Point::new(target_row.0, 0)
10751 .to_display_point(&display_map)
10752 .row();
10753 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10754
10755 new_cursors.push((
10756 selection.id,
10757 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10758 SelectionGoal::None,
10759 ));
10760 edit_ranges.push(edit_start..edit_end);
10761 }
10762
10763 self.transact(window, cx, |this, window, cx| {
10764 let buffer = this.buffer.update(cx, |buffer, cx| {
10765 let empty_str: Arc<str> = Arc::default();
10766 buffer.edit(
10767 edit_ranges
10768 .into_iter()
10769 .map(|range| (range, empty_str.clone())),
10770 None,
10771 cx,
10772 );
10773 buffer.snapshot(cx)
10774 });
10775 let new_selections = new_cursors
10776 .into_iter()
10777 .map(|(id, cursor, goal)| {
10778 let cursor = cursor.to_point(&buffer);
10779 Selection {
10780 id,
10781 start: cursor,
10782 end: cursor,
10783 reversed: false,
10784 goal,
10785 }
10786 })
10787 .collect();
10788
10789 this.change_selections(Default::default(), window, cx, |s| {
10790 s.select(new_selections);
10791 });
10792 });
10793 }
10794
10795 pub fn join_lines_impl(
10796 &mut self,
10797 insert_whitespace: bool,
10798 window: &mut Window,
10799 cx: &mut Context<Self>,
10800 ) {
10801 if self.read_only(cx) {
10802 return;
10803 }
10804 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10805 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10806 let start = MultiBufferRow(selection.start.row);
10807 // Treat single line selections as if they include the next line. Otherwise this action
10808 // would do nothing for single line selections individual cursors.
10809 let end = if selection.start.row == selection.end.row {
10810 MultiBufferRow(selection.start.row + 1)
10811 } else {
10812 MultiBufferRow(selection.end.row)
10813 };
10814
10815 if let Some(last_row_range) = row_ranges.last_mut()
10816 && start <= last_row_range.end
10817 {
10818 last_row_range.end = end;
10819 continue;
10820 }
10821 row_ranges.push(start..end);
10822 }
10823
10824 let snapshot = self.buffer.read(cx).snapshot(cx);
10825 let mut cursor_positions = Vec::new();
10826 for row_range in &row_ranges {
10827 let anchor = snapshot.anchor_before(Point::new(
10828 row_range.end.previous_row().0,
10829 snapshot.line_len(row_range.end.previous_row()),
10830 ));
10831 cursor_positions.push(anchor..anchor);
10832 }
10833
10834 self.transact(window, cx, |this, window, cx| {
10835 for row_range in row_ranges.into_iter().rev() {
10836 for row in row_range.iter_rows().rev() {
10837 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10838 let next_line_row = row.next_row();
10839 let indent = snapshot.indent_size_for_line(next_line_row);
10840 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10841
10842 let replace =
10843 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10844 " "
10845 } else {
10846 ""
10847 };
10848
10849 this.buffer.update(cx, |buffer, cx| {
10850 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10851 });
10852 }
10853 }
10854
10855 this.change_selections(Default::default(), window, cx, |s| {
10856 s.select_anchor_ranges(cursor_positions)
10857 });
10858 });
10859 }
10860
10861 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10862 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10863 self.join_lines_impl(true, window, cx);
10864 }
10865
10866 pub fn sort_lines_case_sensitive(
10867 &mut self,
10868 _: &SortLinesCaseSensitive,
10869 window: &mut Window,
10870 cx: &mut Context<Self>,
10871 ) {
10872 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10873 }
10874
10875 pub fn sort_lines_by_length(
10876 &mut self,
10877 _: &SortLinesByLength,
10878 window: &mut Window,
10879 cx: &mut Context<Self>,
10880 ) {
10881 self.manipulate_immutable_lines(window, cx, |lines| {
10882 lines.sort_by_key(|&line| line.chars().count())
10883 })
10884 }
10885
10886 pub fn sort_lines_case_insensitive(
10887 &mut self,
10888 _: &SortLinesCaseInsensitive,
10889 window: &mut Window,
10890 cx: &mut Context<Self>,
10891 ) {
10892 self.manipulate_immutable_lines(window, cx, |lines| {
10893 lines.sort_by_key(|line| line.to_lowercase())
10894 })
10895 }
10896
10897 pub fn unique_lines_case_insensitive(
10898 &mut self,
10899 _: &UniqueLinesCaseInsensitive,
10900 window: &mut Window,
10901 cx: &mut Context<Self>,
10902 ) {
10903 self.manipulate_immutable_lines(window, cx, |lines| {
10904 let mut seen = HashSet::default();
10905 lines.retain(|line| seen.insert(line.to_lowercase()));
10906 })
10907 }
10908
10909 pub fn unique_lines_case_sensitive(
10910 &mut self,
10911 _: &UniqueLinesCaseSensitive,
10912 window: &mut Window,
10913 cx: &mut Context<Self>,
10914 ) {
10915 self.manipulate_immutable_lines(window, cx, |lines| {
10916 let mut seen = HashSet::default();
10917 lines.retain(|line| seen.insert(*line));
10918 })
10919 }
10920
10921 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10922 let snapshot = self.buffer.read(cx).snapshot(cx);
10923 for selection in self.selections.disjoint_anchors_arc().iter() {
10924 if snapshot
10925 .language_at(selection.start)
10926 .and_then(|lang| lang.config().wrap_characters.as_ref())
10927 .is_some()
10928 {
10929 return true;
10930 }
10931 }
10932 false
10933 }
10934
10935 fn wrap_selections_in_tag(
10936 &mut self,
10937 _: &WrapSelectionsInTag,
10938 window: &mut Window,
10939 cx: &mut Context<Self>,
10940 ) {
10941 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10942
10943 let snapshot = self.buffer.read(cx).snapshot(cx);
10944
10945 let mut edits = Vec::new();
10946 let mut boundaries = Vec::new();
10947
10948 for selection in self
10949 .selections
10950 .all_adjusted(&self.display_snapshot(cx))
10951 .iter()
10952 {
10953 let Some(wrap_config) = snapshot
10954 .language_at(selection.start)
10955 .and_then(|lang| lang.config().wrap_characters.clone())
10956 else {
10957 continue;
10958 };
10959
10960 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10961 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10962
10963 let start_before = snapshot.anchor_before(selection.start);
10964 let end_after = snapshot.anchor_after(selection.end);
10965
10966 edits.push((start_before..start_before, open_tag));
10967 edits.push((end_after..end_after, close_tag));
10968
10969 boundaries.push((
10970 start_before,
10971 end_after,
10972 wrap_config.start_prefix.len(),
10973 wrap_config.end_suffix.len(),
10974 ));
10975 }
10976
10977 if edits.is_empty() {
10978 return;
10979 }
10980
10981 self.transact(window, cx, |this, window, cx| {
10982 let buffer = this.buffer.update(cx, |buffer, cx| {
10983 buffer.edit(edits, None, cx);
10984 buffer.snapshot(cx)
10985 });
10986
10987 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10988 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10989 boundaries.into_iter()
10990 {
10991 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10992 let close_offset = end_after
10993 .to_offset(&buffer)
10994 .saturating_sub_usize(end_suffix_len);
10995 new_selections.push(open_offset..open_offset);
10996 new_selections.push(close_offset..close_offset);
10997 }
10998
10999 this.change_selections(Default::default(), window, cx, |s| {
11000 s.select_ranges(new_selections);
11001 });
11002
11003 this.request_autoscroll(Autoscroll::fit(), cx);
11004 });
11005 }
11006
11007 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11008 let Some(project) = self.project.clone() else {
11009 return;
11010 };
11011 self.reload(project, window, cx)
11012 .detach_and_notify_err(window, cx);
11013 }
11014
11015 pub fn restore_file(
11016 &mut self,
11017 _: &::git::RestoreFile,
11018 window: &mut Window,
11019 cx: &mut Context<Self>,
11020 ) {
11021 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11022 let mut buffer_ids = HashSet::default();
11023 let snapshot = self.buffer().read(cx).snapshot(cx);
11024 for selection in self
11025 .selections
11026 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11027 {
11028 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11029 }
11030
11031 let buffer = self.buffer().read(cx);
11032 let ranges = buffer_ids
11033 .into_iter()
11034 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11035 .collect::<Vec<_>>();
11036
11037 self.restore_hunks_in_ranges(ranges, window, cx);
11038 }
11039
11040 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11042 let selections = self
11043 .selections
11044 .all(&self.display_snapshot(cx))
11045 .into_iter()
11046 .map(|s| s.range())
11047 .collect();
11048 self.restore_hunks_in_ranges(selections, window, cx);
11049 }
11050
11051 pub fn restore_hunks_in_ranges(
11052 &mut self,
11053 ranges: Vec<Range<Point>>,
11054 window: &mut Window,
11055 cx: &mut Context<Editor>,
11056 ) {
11057 let mut revert_changes = HashMap::default();
11058 let chunk_by = self
11059 .snapshot(window, cx)
11060 .hunks_for_ranges(ranges)
11061 .into_iter()
11062 .chunk_by(|hunk| hunk.buffer_id);
11063 for (buffer_id, hunks) in &chunk_by {
11064 let hunks = hunks.collect::<Vec<_>>();
11065 for hunk in &hunks {
11066 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11067 }
11068 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11069 }
11070 drop(chunk_by);
11071 if !revert_changes.is_empty() {
11072 self.transact(window, cx, |editor, window, cx| {
11073 editor.restore(revert_changes, window, cx);
11074 });
11075 }
11076 }
11077
11078 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11079 if let Some(status) = self
11080 .addons
11081 .iter()
11082 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11083 {
11084 return Some(status);
11085 }
11086 self.project
11087 .as_ref()?
11088 .read(cx)
11089 .status_for_buffer_id(buffer_id, cx)
11090 }
11091
11092 pub fn open_active_item_in_terminal(
11093 &mut self,
11094 _: &OpenInTerminal,
11095 window: &mut Window,
11096 cx: &mut Context<Self>,
11097 ) {
11098 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11099 let project_path = buffer.read(cx).project_path(cx)?;
11100 let project = self.project()?.read(cx);
11101 let entry = project.entry_for_path(&project_path, cx)?;
11102 let parent = match &entry.canonical_path {
11103 Some(canonical_path) => canonical_path.to_path_buf(),
11104 None => project.absolute_path(&project_path, cx)?,
11105 }
11106 .parent()?
11107 .to_path_buf();
11108 Some(parent)
11109 }) {
11110 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11111 }
11112 }
11113
11114 fn set_breakpoint_context_menu(
11115 &mut self,
11116 display_row: DisplayRow,
11117 position: Option<Anchor>,
11118 clicked_point: gpui::Point<Pixels>,
11119 window: &mut Window,
11120 cx: &mut Context<Self>,
11121 ) {
11122 let source = self
11123 .buffer
11124 .read(cx)
11125 .snapshot(cx)
11126 .anchor_before(Point::new(display_row.0, 0u32));
11127
11128 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11129
11130 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11131 self,
11132 source,
11133 clicked_point,
11134 context_menu,
11135 window,
11136 cx,
11137 );
11138 }
11139
11140 fn add_edit_breakpoint_block(
11141 &mut self,
11142 anchor: Anchor,
11143 breakpoint: &Breakpoint,
11144 edit_action: BreakpointPromptEditAction,
11145 window: &mut Window,
11146 cx: &mut Context<Self>,
11147 ) {
11148 let weak_editor = cx.weak_entity();
11149 let bp_prompt = cx.new(|cx| {
11150 BreakpointPromptEditor::new(
11151 weak_editor,
11152 anchor,
11153 breakpoint.clone(),
11154 edit_action,
11155 window,
11156 cx,
11157 )
11158 });
11159
11160 let height = bp_prompt.update(cx, |this, cx| {
11161 this.prompt
11162 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11163 });
11164 let cloned_prompt = bp_prompt.clone();
11165 let blocks = vec![BlockProperties {
11166 style: BlockStyle::Sticky,
11167 placement: BlockPlacement::Above(anchor),
11168 height: Some(height),
11169 render: Arc::new(move |cx| {
11170 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11171 cloned_prompt.clone().into_any_element()
11172 }),
11173 priority: 0,
11174 }];
11175
11176 let focus_handle = bp_prompt.focus_handle(cx);
11177 window.focus(&focus_handle);
11178
11179 let block_ids = self.insert_blocks(blocks, None, cx);
11180 bp_prompt.update(cx, |prompt, _| {
11181 prompt.add_block_ids(block_ids);
11182 });
11183 }
11184
11185 pub(crate) fn breakpoint_at_row(
11186 &self,
11187 row: u32,
11188 window: &mut Window,
11189 cx: &mut Context<Self>,
11190 ) -> Option<(Anchor, Breakpoint)> {
11191 let snapshot = self.snapshot(window, cx);
11192 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11193
11194 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11195 }
11196
11197 pub(crate) fn breakpoint_at_anchor(
11198 &self,
11199 breakpoint_position: Anchor,
11200 snapshot: &EditorSnapshot,
11201 cx: &mut Context<Self>,
11202 ) -> Option<(Anchor, Breakpoint)> {
11203 let buffer = self
11204 .buffer
11205 .read(cx)
11206 .buffer_for_anchor(breakpoint_position, cx)?;
11207
11208 let enclosing_excerpt = breakpoint_position.excerpt_id;
11209 let buffer_snapshot = buffer.read(cx).snapshot();
11210
11211 let row = buffer_snapshot
11212 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11213 .row;
11214
11215 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11216 let anchor_end = snapshot
11217 .buffer_snapshot()
11218 .anchor_after(Point::new(row, line_len));
11219
11220 self.breakpoint_store
11221 .as_ref()?
11222 .read_with(cx, |breakpoint_store, cx| {
11223 breakpoint_store
11224 .breakpoints(
11225 &buffer,
11226 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11227 &buffer_snapshot,
11228 cx,
11229 )
11230 .next()
11231 .and_then(|(bp, _)| {
11232 let breakpoint_row = buffer_snapshot
11233 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11234 .row;
11235
11236 if breakpoint_row == row {
11237 snapshot
11238 .buffer_snapshot()
11239 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11240 .map(|position| (position, bp.bp.clone()))
11241 } else {
11242 None
11243 }
11244 })
11245 })
11246 }
11247
11248 pub fn edit_log_breakpoint(
11249 &mut self,
11250 _: &EditLogBreakpoint,
11251 window: &mut Window,
11252 cx: &mut Context<Self>,
11253 ) {
11254 if self.breakpoint_store.is_none() {
11255 return;
11256 }
11257
11258 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11259 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11260 message: None,
11261 state: BreakpointState::Enabled,
11262 condition: None,
11263 hit_condition: None,
11264 });
11265
11266 self.add_edit_breakpoint_block(
11267 anchor,
11268 &breakpoint,
11269 BreakpointPromptEditAction::Log,
11270 window,
11271 cx,
11272 );
11273 }
11274 }
11275
11276 fn breakpoints_at_cursors(
11277 &self,
11278 window: &mut Window,
11279 cx: &mut Context<Self>,
11280 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11281 let snapshot = self.snapshot(window, cx);
11282 let cursors = self
11283 .selections
11284 .disjoint_anchors_arc()
11285 .iter()
11286 .map(|selection| {
11287 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11288
11289 let breakpoint_position = self
11290 .breakpoint_at_row(cursor_position.row, window, cx)
11291 .map(|bp| bp.0)
11292 .unwrap_or_else(|| {
11293 snapshot
11294 .display_snapshot
11295 .buffer_snapshot()
11296 .anchor_after(Point::new(cursor_position.row, 0))
11297 });
11298
11299 let breakpoint = self
11300 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11301 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11302
11303 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11304 })
11305 // 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.
11306 .collect::<HashMap<Anchor, _>>();
11307
11308 cursors.into_iter().collect()
11309 }
11310
11311 pub fn enable_breakpoint(
11312 &mut self,
11313 _: &crate::actions::EnableBreakpoint,
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_disabled()) 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 disable_breakpoint(
11335 &mut self,
11336 _: &crate::actions::DisableBreakpoint,
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 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11346 continue;
11347 };
11348 self.edit_breakpoint_at_anchor(
11349 anchor,
11350 breakpoint,
11351 BreakpointEditAction::InvertState,
11352 cx,
11353 );
11354 }
11355 }
11356
11357 pub fn toggle_breakpoint(
11358 &mut self,
11359 _: &crate::actions::ToggleBreakpoint,
11360 window: &mut Window,
11361 cx: &mut Context<Self>,
11362 ) {
11363 if self.breakpoint_store.is_none() {
11364 return;
11365 }
11366
11367 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11368 if let Some(breakpoint) = breakpoint {
11369 self.edit_breakpoint_at_anchor(
11370 anchor,
11371 breakpoint,
11372 BreakpointEditAction::Toggle,
11373 cx,
11374 );
11375 } else {
11376 self.edit_breakpoint_at_anchor(
11377 anchor,
11378 Breakpoint::new_standard(),
11379 BreakpointEditAction::Toggle,
11380 cx,
11381 );
11382 }
11383 }
11384 }
11385
11386 pub fn edit_breakpoint_at_anchor(
11387 &mut self,
11388 breakpoint_position: Anchor,
11389 breakpoint: Breakpoint,
11390 edit_action: BreakpointEditAction,
11391 cx: &mut Context<Self>,
11392 ) {
11393 let Some(breakpoint_store) = &self.breakpoint_store else {
11394 return;
11395 };
11396
11397 let Some(buffer) = self
11398 .buffer
11399 .read(cx)
11400 .buffer_for_anchor(breakpoint_position, cx)
11401 else {
11402 return;
11403 };
11404
11405 breakpoint_store.update(cx, |breakpoint_store, cx| {
11406 breakpoint_store.toggle_breakpoint(
11407 buffer,
11408 BreakpointWithPosition {
11409 position: breakpoint_position.text_anchor,
11410 bp: breakpoint,
11411 },
11412 edit_action,
11413 cx,
11414 );
11415 });
11416
11417 cx.notify();
11418 }
11419
11420 #[cfg(any(test, feature = "test-support"))]
11421 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11422 self.breakpoint_store.clone()
11423 }
11424
11425 pub fn prepare_restore_change(
11426 &self,
11427 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11428 hunk: &MultiBufferDiffHunk,
11429 cx: &mut App,
11430 ) -> Option<()> {
11431 if hunk.is_created_file() {
11432 return None;
11433 }
11434 let buffer = self.buffer.read(cx);
11435 let diff = buffer.diff_for(hunk.buffer_id)?;
11436 let buffer = buffer.buffer(hunk.buffer_id)?;
11437 let buffer = buffer.read(cx);
11438 let original_text = diff
11439 .read(cx)
11440 .base_text()
11441 .as_rope()
11442 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11443 let buffer_snapshot = buffer.snapshot();
11444 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11445 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11446 probe
11447 .0
11448 .start
11449 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11450 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11451 }) {
11452 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11453 Some(())
11454 } else {
11455 None
11456 }
11457 }
11458
11459 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11460 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11461 }
11462
11463 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11464 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11465 }
11466
11467 fn manipulate_lines<M>(
11468 &mut self,
11469 window: &mut Window,
11470 cx: &mut Context<Self>,
11471 mut manipulate: M,
11472 ) where
11473 M: FnMut(&str) -> LineManipulationResult,
11474 {
11475 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11476
11477 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11478 let buffer = self.buffer.read(cx).snapshot(cx);
11479
11480 let mut edits = Vec::new();
11481
11482 let selections = self.selections.all::<Point>(&display_map);
11483 let mut selections = selections.iter().peekable();
11484 let mut contiguous_row_selections = Vec::new();
11485 let mut new_selections = Vec::new();
11486 let mut added_lines = 0;
11487 let mut removed_lines = 0;
11488
11489 while let Some(selection) = selections.next() {
11490 let (start_row, end_row) = consume_contiguous_rows(
11491 &mut contiguous_row_selections,
11492 selection,
11493 &display_map,
11494 &mut selections,
11495 );
11496
11497 let start_point = Point::new(start_row.0, 0);
11498 let end_point = Point::new(
11499 end_row.previous_row().0,
11500 buffer.line_len(end_row.previous_row()),
11501 );
11502 let text = buffer
11503 .text_for_range(start_point..end_point)
11504 .collect::<String>();
11505
11506 let LineManipulationResult {
11507 new_text,
11508 line_count_before,
11509 line_count_after,
11510 } = manipulate(&text);
11511
11512 edits.push((start_point..end_point, new_text));
11513
11514 // Selections must change based on added and removed line count
11515 let start_row =
11516 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11517 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11518 new_selections.push(Selection {
11519 id: selection.id,
11520 start: start_row,
11521 end: end_row,
11522 goal: SelectionGoal::None,
11523 reversed: selection.reversed,
11524 });
11525
11526 if line_count_after > line_count_before {
11527 added_lines += line_count_after - line_count_before;
11528 } else if line_count_before > line_count_after {
11529 removed_lines += line_count_before - line_count_after;
11530 }
11531 }
11532
11533 self.transact(window, cx, |this, window, cx| {
11534 let buffer = this.buffer.update(cx, |buffer, cx| {
11535 buffer.edit(edits, None, cx);
11536 buffer.snapshot(cx)
11537 });
11538
11539 // Recalculate offsets on newly edited buffer
11540 let new_selections = new_selections
11541 .iter()
11542 .map(|s| {
11543 let start_point = Point::new(s.start.0, 0);
11544 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11545 Selection {
11546 id: s.id,
11547 start: buffer.point_to_offset(start_point),
11548 end: buffer.point_to_offset(end_point),
11549 goal: s.goal,
11550 reversed: s.reversed,
11551 }
11552 })
11553 .collect();
11554
11555 this.change_selections(Default::default(), window, cx, |s| {
11556 s.select(new_selections);
11557 });
11558
11559 this.request_autoscroll(Autoscroll::fit(), cx);
11560 });
11561 }
11562
11563 fn manipulate_immutable_lines<Fn>(
11564 &mut self,
11565 window: &mut Window,
11566 cx: &mut Context<Self>,
11567 mut callback: Fn,
11568 ) where
11569 Fn: FnMut(&mut Vec<&str>),
11570 {
11571 self.manipulate_lines(window, cx, |text| {
11572 let mut lines: Vec<&str> = text.split('\n').collect();
11573 let line_count_before = lines.len();
11574
11575 callback(&mut lines);
11576
11577 LineManipulationResult {
11578 new_text: lines.join("\n"),
11579 line_count_before,
11580 line_count_after: lines.len(),
11581 }
11582 });
11583 }
11584
11585 fn manipulate_mutable_lines<Fn>(
11586 &mut self,
11587 window: &mut Window,
11588 cx: &mut Context<Self>,
11589 mut callback: Fn,
11590 ) where
11591 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11592 {
11593 self.manipulate_lines(window, cx, |text| {
11594 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11595 let line_count_before = lines.len();
11596
11597 callback(&mut lines);
11598
11599 LineManipulationResult {
11600 new_text: lines.join("\n"),
11601 line_count_before,
11602 line_count_after: lines.len(),
11603 }
11604 });
11605 }
11606
11607 pub fn convert_indentation_to_spaces(
11608 &mut self,
11609 _: &ConvertIndentationToSpaces,
11610 window: &mut Window,
11611 cx: &mut Context<Self>,
11612 ) {
11613 let settings = self.buffer.read(cx).language_settings(cx);
11614 let tab_size = settings.tab_size.get() as usize;
11615
11616 self.manipulate_mutable_lines(window, cx, |lines| {
11617 // Allocates a reasonably sized scratch buffer once for the whole loop
11618 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11619 // Avoids recomputing spaces that could be inserted many times
11620 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11621 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11622 .collect();
11623
11624 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11625 let mut chars = line.as_ref().chars();
11626 let mut col = 0;
11627 let mut changed = false;
11628
11629 for ch in chars.by_ref() {
11630 match ch {
11631 ' ' => {
11632 reindented_line.push(' ');
11633 col += 1;
11634 }
11635 '\t' => {
11636 // \t are converted to spaces depending on the current column
11637 let spaces_len = tab_size - (col % tab_size);
11638 reindented_line.extend(&space_cache[spaces_len - 1]);
11639 col += spaces_len;
11640 changed = true;
11641 }
11642 _ => {
11643 // If we dont append before break, the character is consumed
11644 reindented_line.push(ch);
11645 break;
11646 }
11647 }
11648 }
11649
11650 if !changed {
11651 reindented_line.clear();
11652 continue;
11653 }
11654 // Append the rest of the line and replace old reference with new one
11655 reindented_line.extend(chars);
11656 *line = Cow::Owned(reindented_line.clone());
11657 reindented_line.clear();
11658 }
11659 });
11660 }
11661
11662 pub fn convert_indentation_to_tabs(
11663 &mut self,
11664 _: &ConvertIndentationToTabs,
11665 window: &mut Window,
11666 cx: &mut Context<Self>,
11667 ) {
11668 let settings = self.buffer.read(cx).language_settings(cx);
11669 let tab_size = settings.tab_size.get() as usize;
11670
11671 self.manipulate_mutable_lines(window, cx, |lines| {
11672 // Allocates a reasonably sized buffer once for the whole loop
11673 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11674 // Avoids recomputing spaces that could be inserted many times
11675 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11676 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11677 .collect();
11678
11679 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11680 let mut chars = line.chars();
11681 let mut spaces_count = 0;
11682 let mut first_non_indent_char = None;
11683 let mut changed = false;
11684
11685 for ch in chars.by_ref() {
11686 match ch {
11687 ' ' => {
11688 // Keep track of spaces. Append \t when we reach tab_size
11689 spaces_count += 1;
11690 changed = true;
11691 if spaces_count == tab_size {
11692 reindented_line.push('\t');
11693 spaces_count = 0;
11694 }
11695 }
11696 '\t' => {
11697 reindented_line.push('\t');
11698 spaces_count = 0;
11699 }
11700 _ => {
11701 // Dont append it yet, we might have remaining spaces
11702 first_non_indent_char = Some(ch);
11703 break;
11704 }
11705 }
11706 }
11707
11708 if !changed {
11709 reindented_line.clear();
11710 continue;
11711 }
11712 // Remaining spaces that didn't make a full tab stop
11713 if spaces_count > 0 {
11714 reindented_line.extend(&space_cache[spaces_count - 1]);
11715 }
11716 // If we consume an extra character that was not indentation, add it back
11717 if let Some(extra_char) = first_non_indent_char {
11718 reindented_line.push(extra_char);
11719 }
11720 // Append the rest of the line and replace old reference with new one
11721 reindented_line.extend(chars);
11722 *line = Cow::Owned(reindented_line.clone());
11723 reindented_line.clear();
11724 }
11725 });
11726 }
11727
11728 pub fn convert_to_upper_case(
11729 &mut self,
11730 _: &ConvertToUpperCase,
11731 window: &mut Window,
11732 cx: &mut Context<Self>,
11733 ) {
11734 self.manipulate_text(window, cx, |text| text.to_uppercase())
11735 }
11736
11737 pub fn convert_to_lower_case(
11738 &mut self,
11739 _: &ConvertToLowerCase,
11740 window: &mut Window,
11741 cx: &mut Context<Self>,
11742 ) {
11743 self.manipulate_text(window, cx, |text| text.to_lowercase())
11744 }
11745
11746 pub fn convert_to_title_case(
11747 &mut self,
11748 _: &ConvertToTitleCase,
11749 window: &mut Window,
11750 cx: &mut Context<Self>,
11751 ) {
11752 self.manipulate_text(window, cx, |text| {
11753 text.split('\n')
11754 .map(|line| line.to_case(Case::Title))
11755 .join("\n")
11756 })
11757 }
11758
11759 pub fn convert_to_snake_case(
11760 &mut self,
11761 _: &ConvertToSnakeCase,
11762 window: &mut Window,
11763 cx: &mut Context<Self>,
11764 ) {
11765 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11766 }
11767
11768 pub fn convert_to_kebab_case(
11769 &mut self,
11770 _: &ConvertToKebabCase,
11771 window: &mut Window,
11772 cx: &mut Context<Self>,
11773 ) {
11774 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11775 }
11776
11777 pub fn convert_to_upper_camel_case(
11778 &mut self,
11779 _: &ConvertToUpperCamelCase,
11780 window: &mut Window,
11781 cx: &mut Context<Self>,
11782 ) {
11783 self.manipulate_text(window, cx, |text| {
11784 text.split('\n')
11785 .map(|line| line.to_case(Case::UpperCamel))
11786 .join("\n")
11787 })
11788 }
11789
11790 pub fn convert_to_lower_camel_case(
11791 &mut self,
11792 _: &ConvertToLowerCamelCase,
11793 window: &mut Window,
11794 cx: &mut Context<Self>,
11795 ) {
11796 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11797 }
11798
11799 pub fn convert_to_opposite_case(
11800 &mut self,
11801 _: &ConvertToOppositeCase,
11802 window: &mut Window,
11803 cx: &mut Context<Self>,
11804 ) {
11805 self.manipulate_text(window, cx, |text| {
11806 text.chars()
11807 .fold(String::with_capacity(text.len()), |mut t, c| {
11808 if c.is_uppercase() {
11809 t.extend(c.to_lowercase());
11810 } else {
11811 t.extend(c.to_uppercase());
11812 }
11813 t
11814 })
11815 })
11816 }
11817
11818 pub fn convert_to_sentence_case(
11819 &mut self,
11820 _: &ConvertToSentenceCase,
11821 window: &mut Window,
11822 cx: &mut Context<Self>,
11823 ) {
11824 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11825 }
11826
11827 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11828 self.manipulate_text(window, cx, |text| {
11829 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11830 if has_upper_case_characters {
11831 text.to_lowercase()
11832 } else {
11833 text.to_uppercase()
11834 }
11835 })
11836 }
11837
11838 pub fn convert_to_rot13(
11839 &mut self,
11840 _: &ConvertToRot13,
11841 window: &mut Window,
11842 cx: &mut Context<Self>,
11843 ) {
11844 self.manipulate_text(window, cx, |text| {
11845 text.chars()
11846 .map(|c| match c {
11847 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11848 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11849 _ => c,
11850 })
11851 .collect()
11852 })
11853 }
11854
11855 pub fn convert_to_rot47(
11856 &mut self,
11857 _: &ConvertToRot47,
11858 window: &mut Window,
11859 cx: &mut Context<Self>,
11860 ) {
11861 self.manipulate_text(window, cx, |text| {
11862 text.chars()
11863 .map(|c| {
11864 let code_point = c as u32;
11865 if code_point >= 33 && code_point <= 126 {
11866 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11867 }
11868 c
11869 })
11870 .collect()
11871 })
11872 }
11873
11874 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11875 where
11876 Fn: FnMut(&str) -> String,
11877 {
11878 let buffer = self.buffer.read(cx).snapshot(cx);
11879
11880 let mut new_selections = Vec::new();
11881 let mut edits = Vec::new();
11882 let mut selection_adjustment = 0isize;
11883
11884 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11885 let selection_is_empty = selection.is_empty();
11886
11887 let (start, end) = if selection_is_empty {
11888 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11889 (word_range.start, word_range.end)
11890 } else {
11891 (
11892 buffer.point_to_offset(selection.start),
11893 buffer.point_to_offset(selection.end),
11894 )
11895 };
11896
11897 let text = buffer.text_for_range(start..end).collect::<String>();
11898 let old_length = text.len() as isize;
11899 let text = callback(&text);
11900
11901 new_selections.push(Selection {
11902 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
11903 end: MultiBufferOffset(
11904 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
11905 ),
11906 goal: SelectionGoal::None,
11907 id: selection.id,
11908 reversed: selection.reversed,
11909 });
11910
11911 selection_adjustment += old_length - text.len() as isize;
11912
11913 edits.push((start..end, text));
11914 }
11915
11916 self.transact(window, cx, |this, window, cx| {
11917 this.buffer.update(cx, |buffer, cx| {
11918 buffer.edit(edits, None, cx);
11919 });
11920
11921 this.change_selections(Default::default(), window, cx, |s| {
11922 s.select(new_selections);
11923 });
11924
11925 this.request_autoscroll(Autoscroll::fit(), cx);
11926 });
11927 }
11928
11929 pub fn move_selection_on_drop(
11930 &mut self,
11931 selection: &Selection<Anchor>,
11932 target: DisplayPoint,
11933 is_cut: bool,
11934 window: &mut Window,
11935 cx: &mut Context<Self>,
11936 ) {
11937 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11938 let buffer = display_map.buffer_snapshot();
11939 let mut edits = Vec::new();
11940 let insert_point = display_map
11941 .clip_point(target, Bias::Left)
11942 .to_point(&display_map);
11943 let text = buffer
11944 .text_for_range(selection.start..selection.end)
11945 .collect::<String>();
11946 if is_cut {
11947 edits.push(((selection.start..selection.end), String::new()));
11948 }
11949 let insert_anchor = buffer.anchor_before(insert_point);
11950 edits.push(((insert_anchor..insert_anchor), text));
11951 let last_edit_start = insert_anchor.bias_left(buffer);
11952 let last_edit_end = insert_anchor.bias_right(buffer);
11953 self.transact(window, cx, |this, window, cx| {
11954 this.buffer.update(cx, |buffer, cx| {
11955 buffer.edit(edits, None, cx);
11956 });
11957 this.change_selections(Default::default(), window, cx, |s| {
11958 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11959 });
11960 });
11961 }
11962
11963 pub fn clear_selection_drag_state(&mut self) {
11964 self.selection_drag_state = SelectionDragState::None;
11965 }
11966
11967 pub fn duplicate(
11968 &mut self,
11969 upwards: bool,
11970 whole_lines: bool,
11971 window: &mut Window,
11972 cx: &mut Context<Self>,
11973 ) {
11974 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11975
11976 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11977 let buffer = display_map.buffer_snapshot();
11978 let selections = self.selections.all::<Point>(&display_map);
11979
11980 let mut edits = Vec::new();
11981 let mut selections_iter = selections.iter().peekable();
11982 while let Some(selection) = selections_iter.next() {
11983 let mut rows = selection.spanned_rows(false, &display_map);
11984 // duplicate line-wise
11985 if whole_lines || selection.start == selection.end {
11986 // Avoid duplicating the same lines twice.
11987 while let Some(next_selection) = selections_iter.peek() {
11988 let next_rows = next_selection.spanned_rows(false, &display_map);
11989 if next_rows.start < rows.end {
11990 rows.end = next_rows.end;
11991 selections_iter.next().unwrap();
11992 } else {
11993 break;
11994 }
11995 }
11996
11997 // Copy the text from the selected row region and splice it either at the start
11998 // or end of the region.
11999 let start = Point::new(rows.start.0, 0);
12000 let end = Point::new(
12001 rows.end.previous_row().0,
12002 buffer.line_len(rows.end.previous_row()),
12003 );
12004
12005 let mut text = buffer.text_for_range(start..end).collect::<String>();
12006
12007 let insert_location = if upwards {
12008 // When duplicating upward, we need to insert before the current line.
12009 // If we're on the last line and it doesn't end with a newline,
12010 // we need to add a newline before the duplicated content.
12011 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12012 && buffer.max_point().column > 0
12013 && !text.ends_with('\n');
12014
12015 if needs_leading_newline {
12016 text.insert(0, '\n');
12017 end
12018 } else {
12019 text.push('\n');
12020 Point::new(rows.start.0, 0)
12021 }
12022 } else {
12023 text.push('\n');
12024 start
12025 };
12026 edits.push((insert_location..insert_location, text));
12027 } else {
12028 // duplicate character-wise
12029 let start = selection.start;
12030 let end = selection.end;
12031 let text = buffer.text_for_range(start..end).collect::<String>();
12032 edits.push((selection.end..selection.end, text));
12033 }
12034 }
12035
12036 self.transact(window, cx, |this, window, cx| {
12037 this.buffer.update(cx, |buffer, cx| {
12038 buffer.edit(edits, None, cx);
12039 });
12040
12041 // When duplicating upward with whole lines, move the cursor to the duplicated line
12042 if upwards && whole_lines {
12043 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12044
12045 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12046 let mut new_ranges = Vec::new();
12047 let selections = s.all::<Point>(&display_map);
12048 let mut selections_iter = selections.iter().peekable();
12049
12050 while let Some(first_selection) = selections_iter.next() {
12051 // Group contiguous selections together to find the total row span
12052 let mut group_selections = vec![first_selection];
12053 let mut rows = first_selection.spanned_rows(false, &display_map);
12054
12055 while let Some(next_selection) = selections_iter.peek() {
12056 let next_rows = next_selection.spanned_rows(false, &display_map);
12057 if next_rows.start < rows.end {
12058 rows.end = next_rows.end;
12059 group_selections.push(selections_iter.next().unwrap());
12060 } else {
12061 break;
12062 }
12063 }
12064
12065 let row_count = rows.end.0 - rows.start.0;
12066
12067 // Move all selections in this group up by the total number of duplicated rows
12068 for selection in group_selections {
12069 let new_start = Point::new(
12070 selection.start.row.saturating_sub(row_count),
12071 selection.start.column,
12072 );
12073
12074 let new_end = Point::new(
12075 selection.end.row.saturating_sub(row_count),
12076 selection.end.column,
12077 );
12078
12079 new_ranges.push(new_start..new_end);
12080 }
12081 }
12082
12083 s.select_ranges(new_ranges);
12084 });
12085 }
12086
12087 this.request_autoscroll(Autoscroll::fit(), cx);
12088 });
12089 }
12090
12091 pub fn duplicate_line_up(
12092 &mut self,
12093 _: &DuplicateLineUp,
12094 window: &mut Window,
12095 cx: &mut Context<Self>,
12096 ) {
12097 self.duplicate(true, true, window, cx);
12098 }
12099
12100 pub fn duplicate_line_down(
12101 &mut self,
12102 _: &DuplicateLineDown,
12103 window: &mut Window,
12104 cx: &mut Context<Self>,
12105 ) {
12106 self.duplicate(false, true, window, cx);
12107 }
12108
12109 pub fn duplicate_selection(
12110 &mut self,
12111 _: &DuplicateSelection,
12112 window: &mut Window,
12113 cx: &mut Context<Self>,
12114 ) {
12115 self.duplicate(false, false, window, cx);
12116 }
12117
12118 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12119 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12120 if self.mode.is_single_line() {
12121 cx.propagate();
12122 return;
12123 }
12124
12125 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12126 let buffer = self.buffer.read(cx).snapshot(cx);
12127
12128 let mut edits = Vec::new();
12129 let mut unfold_ranges = Vec::new();
12130 let mut refold_creases = Vec::new();
12131
12132 let selections = self.selections.all::<Point>(&display_map);
12133 let mut selections = selections.iter().peekable();
12134 let mut contiguous_row_selections = Vec::new();
12135 let mut new_selections = Vec::new();
12136
12137 while let Some(selection) = selections.next() {
12138 // Find all the selections that span a contiguous row range
12139 let (start_row, end_row) = consume_contiguous_rows(
12140 &mut contiguous_row_selections,
12141 selection,
12142 &display_map,
12143 &mut selections,
12144 );
12145
12146 // Move the text spanned by the row range to be before the line preceding the row range
12147 if start_row.0 > 0 {
12148 let range_to_move = Point::new(
12149 start_row.previous_row().0,
12150 buffer.line_len(start_row.previous_row()),
12151 )
12152 ..Point::new(
12153 end_row.previous_row().0,
12154 buffer.line_len(end_row.previous_row()),
12155 );
12156 let insertion_point = display_map
12157 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12158 .0;
12159
12160 // Don't move lines across excerpts
12161 if buffer
12162 .excerpt_containing(insertion_point..range_to_move.end)
12163 .is_some()
12164 {
12165 let text = buffer
12166 .text_for_range(range_to_move.clone())
12167 .flat_map(|s| s.chars())
12168 .skip(1)
12169 .chain(['\n'])
12170 .collect::<String>();
12171
12172 edits.push((
12173 buffer.anchor_after(range_to_move.start)
12174 ..buffer.anchor_before(range_to_move.end),
12175 String::new(),
12176 ));
12177 let insertion_anchor = buffer.anchor_after(insertion_point);
12178 edits.push((insertion_anchor..insertion_anchor, text));
12179
12180 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12181
12182 // Move selections up
12183 new_selections.extend(contiguous_row_selections.drain(..).map(
12184 |mut selection| {
12185 selection.start.row -= row_delta;
12186 selection.end.row -= row_delta;
12187 selection
12188 },
12189 ));
12190
12191 // Move folds up
12192 unfold_ranges.push(range_to_move.clone());
12193 for fold in display_map.folds_in_range(
12194 buffer.anchor_before(range_to_move.start)
12195 ..buffer.anchor_after(range_to_move.end),
12196 ) {
12197 let mut start = fold.range.start.to_point(&buffer);
12198 let mut end = fold.range.end.to_point(&buffer);
12199 start.row -= row_delta;
12200 end.row -= row_delta;
12201 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12202 }
12203 }
12204 }
12205
12206 // If we didn't move line(s), preserve the existing selections
12207 new_selections.append(&mut contiguous_row_selections);
12208 }
12209
12210 self.transact(window, cx, |this, window, cx| {
12211 this.unfold_ranges(&unfold_ranges, true, true, cx);
12212 this.buffer.update(cx, |buffer, cx| {
12213 for (range, text) in edits {
12214 buffer.edit([(range, text)], None, cx);
12215 }
12216 });
12217 this.fold_creases(refold_creases, true, window, cx);
12218 this.change_selections(Default::default(), window, cx, |s| {
12219 s.select(new_selections);
12220 })
12221 });
12222 }
12223
12224 pub fn move_line_down(
12225 &mut self,
12226 _: &MoveLineDown,
12227 window: &mut Window,
12228 cx: &mut Context<Self>,
12229 ) {
12230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12231 if self.mode.is_single_line() {
12232 cx.propagate();
12233 return;
12234 }
12235
12236 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12237 let buffer = self.buffer.read(cx).snapshot(cx);
12238
12239 let mut edits = Vec::new();
12240 let mut unfold_ranges = Vec::new();
12241 let mut refold_creases = Vec::new();
12242
12243 let selections = self.selections.all::<Point>(&display_map);
12244 let mut selections = selections.iter().peekable();
12245 let mut contiguous_row_selections = Vec::new();
12246 let mut new_selections = Vec::new();
12247
12248 while let Some(selection) = selections.next() {
12249 // Find all the selections that span a contiguous row range
12250 let (start_row, end_row) = consume_contiguous_rows(
12251 &mut contiguous_row_selections,
12252 selection,
12253 &display_map,
12254 &mut selections,
12255 );
12256
12257 // Move the text spanned by the row range to be after the last line of the row range
12258 if end_row.0 <= buffer.max_point().row {
12259 let range_to_move =
12260 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12261 let insertion_point = display_map
12262 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12263 .0;
12264
12265 // Don't move lines across excerpt boundaries
12266 if buffer
12267 .excerpt_containing(range_to_move.start..insertion_point)
12268 .is_some()
12269 {
12270 let mut text = String::from("\n");
12271 text.extend(buffer.text_for_range(range_to_move.clone()));
12272 text.pop(); // Drop trailing newline
12273 edits.push((
12274 buffer.anchor_after(range_to_move.start)
12275 ..buffer.anchor_before(range_to_move.end),
12276 String::new(),
12277 ));
12278 let insertion_anchor = buffer.anchor_after(insertion_point);
12279 edits.push((insertion_anchor..insertion_anchor, text));
12280
12281 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12282
12283 // Move selections down
12284 new_selections.extend(contiguous_row_selections.drain(..).map(
12285 |mut selection| {
12286 selection.start.row += row_delta;
12287 selection.end.row += row_delta;
12288 selection
12289 },
12290 ));
12291
12292 // Move folds down
12293 unfold_ranges.push(range_to_move.clone());
12294 for fold in display_map.folds_in_range(
12295 buffer.anchor_before(range_to_move.start)
12296 ..buffer.anchor_after(range_to_move.end),
12297 ) {
12298 let mut start = fold.range.start.to_point(&buffer);
12299 let mut end = fold.range.end.to_point(&buffer);
12300 start.row += row_delta;
12301 end.row += row_delta;
12302 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12303 }
12304 }
12305 }
12306
12307 // If we didn't move line(s), preserve the existing selections
12308 new_selections.append(&mut contiguous_row_selections);
12309 }
12310
12311 self.transact(window, cx, |this, window, cx| {
12312 this.unfold_ranges(&unfold_ranges, true, true, cx);
12313 this.buffer.update(cx, |buffer, cx| {
12314 for (range, text) in edits {
12315 buffer.edit([(range, text)], None, cx);
12316 }
12317 });
12318 this.fold_creases(refold_creases, true, window, cx);
12319 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12320 });
12321 }
12322
12323 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12324 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12325 let text_layout_details = &self.text_layout_details(window);
12326 self.transact(window, cx, |this, window, cx| {
12327 let edits = this.change_selections(Default::default(), window, cx, |s| {
12328 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12329 s.move_with(|display_map, selection| {
12330 if !selection.is_empty() {
12331 return;
12332 }
12333
12334 let mut head = selection.head();
12335 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12336 if head.column() == display_map.line_len(head.row()) {
12337 transpose_offset = display_map
12338 .buffer_snapshot()
12339 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12340 }
12341
12342 if transpose_offset == MultiBufferOffset(0) {
12343 return;
12344 }
12345
12346 *head.column_mut() += 1;
12347 head = display_map.clip_point(head, Bias::Right);
12348 let goal = SelectionGoal::HorizontalPosition(
12349 display_map
12350 .x_for_display_point(head, text_layout_details)
12351 .into(),
12352 );
12353 selection.collapse_to(head, goal);
12354
12355 let transpose_start = display_map
12356 .buffer_snapshot()
12357 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12358 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12359 let transpose_end = display_map
12360 .buffer_snapshot()
12361 .clip_offset(transpose_offset + 1usize, Bias::Right);
12362 if let Some(ch) = display_map
12363 .buffer_snapshot()
12364 .chars_at(transpose_start)
12365 .next()
12366 {
12367 edits.push((transpose_start..transpose_offset, String::new()));
12368 edits.push((transpose_end..transpose_end, ch.to_string()));
12369 }
12370 }
12371 });
12372 edits
12373 });
12374 this.buffer
12375 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12376 let selections = this
12377 .selections
12378 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12379 this.change_selections(Default::default(), window, cx, |s| {
12380 s.select(selections);
12381 });
12382 });
12383 }
12384
12385 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12386 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12387 if self.mode.is_single_line() {
12388 cx.propagate();
12389 return;
12390 }
12391
12392 self.rewrap_impl(RewrapOptions::default(), cx)
12393 }
12394
12395 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12396 let buffer = self.buffer.read(cx).snapshot(cx);
12397 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12398
12399 #[derive(Clone, Debug, PartialEq)]
12400 enum CommentFormat {
12401 /// single line comment, with prefix for line
12402 Line(String),
12403 /// single line within a block comment, with prefix for line
12404 BlockLine(String),
12405 /// a single line of a block comment that includes the initial delimiter
12406 BlockCommentWithStart(BlockCommentConfig),
12407 /// a single line of a block comment that includes the ending delimiter
12408 BlockCommentWithEnd(BlockCommentConfig),
12409 }
12410
12411 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12412 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12413 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12414 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12415 .peekable();
12416
12417 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12418 row
12419 } else {
12420 return Vec::new();
12421 };
12422
12423 let language_settings = buffer.language_settings_at(selection.head(), cx);
12424 let language_scope = buffer.language_scope_at(selection.head());
12425
12426 let indent_and_prefix_for_row =
12427 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12428 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12429 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12430 &language_scope
12431 {
12432 let indent_end = Point::new(row, indent.len);
12433 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12434 let line_text_after_indent = buffer
12435 .text_for_range(indent_end..line_end)
12436 .collect::<String>();
12437
12438 let is_within_comment_override = buffer
12439 .language_scope_at(indent_end)
12440 .is_some_and(|scope| scope.override_name() == Some("comment"));
12441 let comment_delimiters = if is_within_comment_override {
12442 // we are within a comment syntax node, but we don't
12443 // yet know what kind of comment: block, doc or line
12444 match (
12445 language_scope.documentation_comment(),
12446 language_scope.block_comment(),
12447 ) {
12448 (Some(config), _) | (_, Some(config))
12449 if buffer.contains_str_at(indent_end, &config.start) =>
12450 {
12451 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12452 }
12453 (Some(config), _) | (_, Some(config))
12454 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12455 {
12456 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12457 }
12458 (Some(config), _) | (_, Some(config))
12459 if buffer.contains_str_at(indent_end, &config.prefix) =>
12460 {
12461 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12462 }
12463 (_, _) => language_scope
12464 .line_comment_prefixes()
12465 .iter()
12466 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12467 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12468 }
12469 } else {
12470 // we not in an overridden comment node, but we may
12471 // be within a non-overridden line comment node
12472 language_scope
12473 .line_comment_prefixes()
12474 .iter()
12475 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12476 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12477 };
12478
12479 let rewrap_prefix = language_scope
12480 .rewrap_prefixes()
12481 .iter()
12482 .find_map(|prefix_regex| {
12483 prefix_regex.find(&line_text_after_indent).map(|mat| {
12484 if mat.start() == 0 {
12485 Some(mat.as_str().to_string())
12486 } else {
12487 None
12488 }
12489 })
12490 })
12491 .flatten();
12492 (comment_delimiters, rewrap_prefix)
12493 } else {
12494 (None, None)
12495 };
12496 (indent, comment_prefix, rewrap_prefix)
12497 };
12498
12499 let mut ranges = Vec::new();
12500 let from_empty_selection = selection.is_empty();
12501
12502 let mut current_range_start = first_row;
12503 let mut prev_row = first_row;
12504 let (
12505 mut current_range_indent,
12506 mut current_range_comment_delimiters,
12507 mut current_range_rewrap_prefix,
12508 ) = indent_and_prefix_for_row(first_row);
12509
12510 for row in non_blank_rows_iter.skip(1) {
12511 let has_paragraph_break = row > prev_row + 1;
12512
12513 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12514 indent_and_prefix_for_row(row);
12515
12516 let has_indent_change = row_indent != current_range_indent;
12517 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12518
12519 let has_boundary_change = has_comment_change
12520 || row_rewrap_prefix.is_some()
12521 || (has_indent_change && current_range_comment_delimiters.is_some());
12522
12523 if has_paragraph_break || has_boundary_change {
12524 ranges.push((
12525 language_settings.clone(),
12526 Point::new(current_range_start, 0)
12527 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12528 current_range_indent,
12529 current_range_comment_delimiters.clone(),
12530 current_range_rewrap_prefix.clone(),
12531 from_empty_selection,
12532 ));
12533 current_range_start = row;
12534 current_range_indent = row_indent;
12535 current_range_comment_delimiters = row_comment_delimiters;
12536 current_range_rewrap_prefix = row_rewrap_prefix;
12537 }
12538 prev_row = row;
12539 }
12540
12541 ranges.push((
12542 language_settings.clone(),
12543 Point::new(current_range_start, 0)
12544 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12545 current_range_indent,
12546 current_range_comment_delimiters,
12547 current_range_rewrap_prefix,
12548 from_empty_selection,
12549 ));
12550
12551 ranges
12552 });
12553
12554 let mut edits = Vec::new();
12555 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12556
12557 for (
12558 language_settings,
12559 wrap_range,
12560 mut indent_size,
12561 comment_prefix,
12562 rewrap_prefix,
12563 from_empty_selection,
12564 ) in wrap_ranges
12565 {
12566 let mut start_row = wrap_range.start.row;
12567 let mut end_row = wrap_range.end.row;
12568
12569 // Skip selections that overlap with a range that has already been rewrapped.
12570 let selection_range = start_row..end_row;
12571 if rewrapped_row_ranges
12572 .iter()
12573 .any(|range| range.overlaps(&selection_range))
12574 {
12575 continue;
12576 }
12577
12578 let tab_size = language_settings.tab_size;
12579
12580 let (line_prefix, inside_comment) = match &comment_prefix {
12581 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12582 (Some(prefix.as_str()), true)
12583 }
12584 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12585 (Some(prefix.as_ref()), true)
12586 }
12587 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12588 start: _,
12589 end: _,
12590 prefix,
12591 tab_size,
12592 })) => {
12593 indent_size.len += tab_size;
12594 (Some(prefix.as_ref()), true)
12595 }
12596 None => (None, false),
12597 };
12598 let indent_prefix = indent_size.chars().collect::<String>();
12599 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12600
12601 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12602 RewrapBehavior::InComments => inside_comment,
12603 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12604 RewrapBehavior::Anywhere => true,
12605 };
12606
12607 let should_rewrap = options.override_language_settings
12608 || allow_rewrap_based_on_language
12609 || self.hard_wrap.is_some();
12610 if !should_rewrap {
12611 continue;
12612 }
12613
12614 if from_empty_selection {
12615 'expand_upwards: while start_row > 0 {
12616 let prev_row = start_row - 1;
12617 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12618 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12619 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12620 {
12621 start_row = prev_row;
12622 } else {
12623 break 'expand_upwards;
12624 }
12625 }
12626
12627 'expand_downwards: while end_row < buffer.max_point().row {
12628 let next_row = end_row + 1;
12629 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12630 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12631 && !buffer.is_line_blank(MultiBufferRow(next_row))
12632 {
12633 end_row = next_row;
12634 } else {
12635 break 'expand_downwards;
12636 }
12637 }
12638 }
12639
12640 let start = Point::new(start_row, 0);
12641 let start_offset = ToOffset::to_offset(&start, &buffer);
12642 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12643 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12644 let mut first_line_delimiter = None;
12645 let mut last_line_delimiter = None;
12646 let Some(lines_without_prefixes) = selection_text
12647 .lines()
12648 .enumerate()
12649 .map(|(ix, line)| {
12650 let line_trimmed = line.trim_start();
12651 if rewrap_prefix.is_some() && ix > 0 {
12652 Ok(line_trimmed)
12653 } else if let Some(
12654 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12655 start,
12656 prefix,
12657 end,
12658 tab_size,
12659 })
12660 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12661 start,
12662 prefix,
12663 end,
12664 tab_size,
12665 }),
12666 ) = &comment_prefix
12667 {
12668 let line_trimmed = line_trimmed
12669 .strip_prefix(start.as_ref())
12670 .map(|s| {
12671 let mut indent_size = indent_size;
12672 indent_size.len -= tab_size;
12673 let indent_prefix: String = indent_size.chars().collect();
12674 first_line_delimiter = Some((indent_prefix, start));
12675 s.trim_start()
12676 })
12677 .unwrap_or(line_trimmed);
12678 let line_trimmed = line_trimmed
12679 .strip_suffix(end.as_ref())
12680 .map(|s| {
12681 last_line_delimiter = Some(end);
12682 s.trim_end()
12683 })
12684 .unwrap_or(line_trimmed);
12685 let line_trimmed = line_trimmed
12686 .strip_prefix(prefix.as_ref())
12687 .unwrap_or(line_trimmed);
12688 Ok(line_trimmed)
12689 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12690 line_trimmed.strip_prefix(prefix).with_context(|| {
12691 format!("line did not start with prefix {prefix:?}: {line:?}")
12692 })
12693 } else {
12694 line_trimmed
12695 .strip_prefix(&line_prefix.trim_start())
12696 .with_context(|| {
12697 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12698 })
12699 }
12700 })
12701 .collect::<Result<Vec<_>, _>>()
12702 .log_err()
12703 else {
12704 continue;
12705 };
12706
12707 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12708 buffer
12709 .language_settings_at(Point::new(start_row, 0), cx)
12710 .preferred_line_length as usize
12711 });
12712
12713 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12714 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12715 } else {
12716 line_prefix.clone()
12717 };
12718
12719 let wrapped_text = {
12720 let mut wrapped_text = wrap_with_prefix(
12721 line_prefix,
12722 subsequent_lines_prefix,
12723 lines_without_prefixes.join("\n"),
12724 wrap_column,
12725 tab_size,
12726 options.preserve_existing_whitespace,
12727 );
12728
12729 if let Some((indent, delimiter)) = first_line_delimiter {
12730 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12731 }
12732 if let Some(last_line) = last_line_delimiter {
12733 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12734 }
12735
12736 wrapped_text
12737 };
12738
12739 // TODO: should always use char-based diff while still supporting cursor behavior that
12740 // matches vim.
12741 let mut diff_options = DiffOptions::default();
12742 if options.override_language_settings {
12743 diff_options.max_word_diff_len = 0;
12744 diff_options.max_word_diff_line_count = 0;
12745 } else {
12746 diff_options.max_word_diff_len = usize::MAX;
12747 diff_options.max_word_diff_line_count = usize::MAX;
12748 }
12749
12750 for (old_range, new_text) in
12751 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12752 {
12753 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12754 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12755 edits.push((edit_start..edit_end, new_text));
12756 }
12757
12758 rewrapped_row_ranges.push(start_row..=end_row);
12759 }
12760
12761 self.buffer
12762 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12763 }
12764
12765 pub fn cut_common(
12766 &mut self,
12767 cut_no_selection_line: bool,
12768 window: &mut Window,
12769 cx: &mut Context<Self>,
12770 ) -> ClipboardItem {
12771 let mut text = String::new();
12772 let buffer = self.buffer.read(cx).snapshot(cx);
12773 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12774 let mut clipboard_selections = Vec::with_capacity(selections.len());
12775 {
12776 let max_point = buffer.max_point();
12777 let mut is_first = true;
12778 let mut prev_selection_was_entire_line = false;
12779 for selection in &mut selections {
12780 let is_entire_line =
12781 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12782 if is_entire_line {
12783 selection.start = Point::new(selection.start.row, 0);
12784 if !selection.is_empty() && selection.end.column == 0 {
12785 selection.end = cmp::min(max_point, selection.end);
12786 } else {
12787 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12788 }
12789 selection.goal = SelectionGoal::None;
12790 }
12791 if is_first {
12792 is_first = false;
12793 } else if !prev_selection_was_entire_line {
12794 text += "\n";
12795 }
12796 prev_selection_was_entire_line = is_entire_line;
12797 let mut len = 0;
12798 for chunk in buffer.text_for_range(selection.start..selection.end) {
12799 text.push_str(chunk);
12800 len += chunk.len();
12801 }
12802 clipboard_selections.push(ClipboardSelection {
12803 len,
12804 is_entire_line,
12805 first_line_indent: buffer
12806 .indent_size_for_line(MultiBufferRow(selection.start.row))
12807 .len,
12808 });
12809 }
12810 }
12811
12812 self.transact(window, cx, |this, window, cx| {
12813 this.change_selections(Default::default(), window, cx, |s| {
12814 s.select(selections);
12815 });
12816 this.insert("", window, cx);
12817 });
12818 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12819 }
12820
12821 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12822 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12823 let item = self.cut_common(true, window, cx);
12824 cx.write_to_clipboard(item);
12825 }
12826
12827 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12829 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12830 s.move_with(|snapshot, sel| {
12831 if sel.is_empty() {
12832 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12833 }
12834 if sel.is_empty() {
12835 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12836 }
12837 });
12838 });
12839 let item = self.cut_common(false, window, cx);
12840 cx.set_global(KillRing(item))
12841 }
12842
12843 pub fn kill_ring_yank(
12844 &mut self,
12845 _: &KillRingYank,
12846 window: &mut Window,
12847 cx: &mut Context<Self>,
12848 ) {
12849 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12850 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12851 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12852 (kill_ring.text().to_string(), kill_ring.metadata_json())
12853 } else {
12854 return;
12855 }
12856 } else {
12857 return;
12858 };
12859 self.do_paste(&text, metadata, false, window, cx);
12860 }
12861
12862 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12863 self.do_copy(true, cx);
12864 }
12865
12866 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12867 self.do_copy(false, cx);
12868 }
12869
12870 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12871 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12872 let buffer = self.buffer.read(cx).read(cx);
12873 let mut text = String::new();
12874
12875 let mut clipboard_selections = Vec::with_capacity(selections.len());
12876 {
12877 let max_point = buffer.max_point();
12878 let mut is_first = true;
12879 let mut prev_selection_was_entire_line = false;
12880 for selection in &selections {
12881 let mut start = selection.start;
12882 let mut end = selection.end;
12883 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12884 let mut add_trailing_newline = false;
12885 if is_entire_line {
12886 start = Point::new(start.row, 0);
12887 let next_line_start = Point::new(end.row + 1, 0);
12888 if next_line_start <= max_point {
12889 end = next_line_start;
12890 } else {
12891 // We're on the last line without a trailing newline.
12892 // Copy to the end of the line and add a newline afterwards.
12893 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12894 add_trailing_newline = true;
12895 }
12896 }
12897
12898 let mut trimmed_selections = Vec::new();
12899 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12900 let row = MultiBufferRow(start.row);
12901 let first_indent = buffer.indent_size_for_line(row);
12902 if first_indent.len == 0 || start.column > first_indent.len {
12903 trimmed_selections.push(start..end);
12904 } else {
12905 trimmed_selections.push(
12906 Point::new(row.0, first_indent.len)
12907 ..Point::new(row.0, buffer.line_len(row)),
12908 );
12909 for row in start.row + 1..=end.row {
12910 let mut line_len = buffer.line_len(MultiBufferRow(row));
12911 if row == end.row {
12912 line_len = end.column;
12913 }
12914 if line_len == 0 {
12915 trimmed_selections
12916 .push(Point::new(row, 0)..Point::new(row, line_len));
12917 continue;
12918 }
12919 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12920 if row_indent_size.len >= first_indent.len {
12921 trimmed_selections.push(
12922 Point::new(row, first_indent.len)..Point::new(row, line_len),
12923 );
12924 } else {
12925 trimmed_selections.clear();
12926 trimmed_selections.push(start..end);
12927 break;
12928 }
12929 }
12930 }
12931 } else {
12932 trimmed_selections.push(start..end);
12933 }
12934
12935 for trimmed_range in trimmed_selections {
12936 if is_first {
12937 is_first = false;
12938 } else if !prev_selection_was_entire_line {
12939 text += "\n";
12940 }
12941 prev_selection_was_entire_line = is_entire_line;
12942 let mut len = 0;
12943 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12944 text.push_str(chunk);
12945 len += chunk.len();
12946 }
12947 if add_trailing_newline {
12948 text.push('\n');
12949 len += 1;
12950 }
12951 clipboard_selections.push(ClipboardSelection {
12952 len,
12953 is_entire_line,
12954 first_line_indent: buffer
12955 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12956 .len,
12957 });
12958 }
12959 }
12960 }
12961
12962 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12963 text,
12964 clipboard_selections,
12965 ));
12966 }
12967
12968 pub fn do_paste(
12969 &mut self,
12970 text: &String,
12971 clipboard_selections: Option<Vec<ClipboardSelection>>,
12972 handle_entire_lines: bool,
12973 window: &mut Window,
12974 cx: &mut Context<Self>,
12975 ) {
12976 if self.read_only(cx) {
12977 return;
12978 }
12979
12980 let clipboard_text = Cow::Borrowed(text.as_str());
12981
12982 self.transact(window, cx, |this, window, cx| {
12983 let had_active_edit_prediction = this.has_active_edit_prediction();
12984 let display_map = this.display_snapshot(cx);
12985 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
12986 let cursor_offset = this
12987 .selections
12988 .last::<MultiBufferOffset>(&display_map)
12989 .head();
12990
12991 if let Some(mut clipboard_selections) = clipboard_selections {
12992 let all_selections_were_entire_line =
12993 clipboard_selections.iter().all(|s| s.is_entire_line);
12994 let first_selection_indent_column =
12995 clipboard_selections.first().map(|s| s.first_line_indent);
12996 if clipboard_selections.len() != old_selections.len() {
12997 clipboard_selections.drain(..);
12998 }
12999 let mut auto_indent_on_paste = true;
13000
13001 this.buffer.update(cx, |buffer, cx| {
13002 let snapshot = buffer.read(cx);
13003 auto_indent_on_paste = snapshot
13004 .language_settings_at(cursor_offset, cx)
13005 .auto_indent_on_paste;
13006
13007 let mut start_offset = 0;
13008 let mut edits = Vec::new();
13009 let mut original_indent_columns = Vec::new();
13010 for (ix, selection) in old_selections.iter().enumerate() {
13011 let to_insert;
13012 let entire_line;
13013 let original_indent_column;
13014 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13015 let end_offset = start_offset + clipboard_selection.len;
13016 to_insert = &clipboard_text[start_offset..end_offset];
13017 entire_line = clipboard_selection.is_entire_line;
13018 start_offset = if entire_line {
13019 end_offset
13020 } else {
13021 end_offset + 1
13022 };
13023 original_indent_column = Some(clipboard_selection.first_line_indent);
13024 } else {
13025 to_insert = &*clipboard_text;
13026 entire_line = all_selections_were_entire_line;
13027 original_indent_column = first_selection_indent_column
13028 }
13029
13030 let (range, to_insert) =
13031 if selection.is_empty() && handle_entire_lines && entire_line {
13032 // If the corresponding selection was empty when this slice of the
13033 // clipboard text was written, then the entire line containing the
13034 // selection was copied. If this selection is also currently empty,
13035 // then paste the line before the current line of the buffer.
13036 let column = selection.start.to_point(&snapshot).column as usize;
13037 let line_start = selection.start - column;
13038 (line_start..line_start, Cow::Borrowed(to_insert))
13039 } else {
13040 let language = snapshot.language_at(selection.head());
13041 let range = selection.range();
13042 if let Some(language) = language
13043 && language.name() == "Markdown".into()
13044 {
13045 edit_for_markdown_paste(
13046 &snapshot,
13047 range,
13048 to_insert,
13049 url::Url::parse(to_insert).ok(),
13050 )
13051 } else {
13052 (range, Cow::Borrowed(to_insert))
13053 }
13054 };
13055
13056 edits.push((range, to_insert));
13057 original_indent_columns.push(original_indent_column);
13058 }
13059 drop(snapshot);
13060
13061 buffer.edit(
13062 edits,
13063 if auto_indent_on_paste {
13064 Some(AutoindentMode::Block {
13065 original_indent_columns,
13066 })
13067 } else {
13068 None
13069 },
13070 cx,
13071 );
13072 });
13073
13074 let selections = this
13075 .selections
13076 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13077 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13078 } else {
13079 let url = url::Url::parse(&clipboard_text).ok();
13080
13081 let auto_indent_mode = if !clipboard_text.is_empty() {
13082 Some(AutoindentMode::Block {
13083 original_indent_columns: Vec::new(),
13084 })
13085 } else {
13086 None
13087 };
13088
13089 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13090 let snapshot = buffer.snapshot(cx);
13091
13092 let anchors = old_selections
13093 .iter()
13094 .map(|s| {
13095 let anchor = snapshot.anchor_after(s.head());
13096 s.map(|_| anchor)
13097 })
13098 .collect::<Vec<_>>();
13099
13100 let mut edits = Vec::new();
13101
13102 for selection in old_selections.iter() {
13103 let language = snapshot.language_at(selection.head());
13104 let range = selection.range();
13105
13106 let (edit_range, edit_text) = if let Some(language) = language
13107 && language.name() == "Markdown".into()
13108 {
13109 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13110 } else {
13111 (range, clipboard_text.clone())
13112 };
13113
13114 edits.push((edit_range, edit_text));
13115 }
13116
13117 drop(snapshot);
13118 buffer.edit(edits, auto_indent_mode, cx);
13119
13120 anchors
13121 });
13122
13123 this.change_selections(Default::default(), window, cx, |s| {
13124 s.select_anchors(selection_anchors);
13125 });
13126 }
13127
13128 // 🤔 | .. | show_in_menu |
13129 // | .. | true true
13130 // | had_edit_prediction | false true
13131
13132 let trigger_in_words =
13133 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13134
13135 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13136 });
13137 }
13138
13139 pub fn diff_clipboard_with_selection(
13140 &mut self,
13141 _: &DiffClipboardWithSelection,
13142 window: &mut Window,
13143 cx: &mut Context<Self>,
13144 ) {
13145 let selections = self
13146 .selections
13147 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13148
13149 if selections.is_empty() {
13150 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13151 return;
13152 };
13153
13154 let clipboard_text = match cx.read_from_clipboard() {
13155 Some(item) => match item.entries().first() {
13156 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13157 _ => None,
13158 },
13159 None => None,
13160 };
13161
13162 let Some(clipboard_text) = clipboard_text else {
13163 log::warn!("Clipboard doesn't contain text.");
13164 return;
13165 };
13166
13167 window.dispatch_action(
13168 Box::new(DiffClipboardWithSelectionData {
13169 clipboard_text,
13170 editor: cx.entity(),
13171 }),
13172 cx,
13173 );
13174 }
13175
13176 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13177 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13178 if let Some(item) = cx.read_from_clipboard() {
13179 let entries = item.entries();
13180
13181 match entries.first() {
13182 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13183 // of all the pasted entries.
13184 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13185 .do_paste(
13186 clipboard_string.text(),
13187 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13188 true,
13189 window,
13190 cx,
13191 ),
13192 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13193 }
13194 }
13195 }
13196
13197 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13198 if self.read_only(cx) {
13199 return;
13200 }
13201
13202 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13203
13204 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13205 if let Some((selections, _)) =
13206 self.selection_history.transaction(transaction_id).cloned()
13207 {
13208 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13209 s.select_anchors(selections.to_vec());
13210 });
13211 } else {
13212 log::error!(
13213 "No entry in selection_history found for undo. \
13214 This may correspond to a bug where undo does not update the selection. \
13215 If this is occurring, please add details to \
13216 https://github.com/zed-industries/zed/issues/22692"
13217 );
13218 }
13219 self.request_autoscroll(Autoscroll::fit(), cx);
13220 self.unmark_text(window, cx);
13221 self.refresh_edit_prediction(true, false, window, cx);
13222 cx.emit(EditorEvent::Edited { transaction_id });
13223 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13224 }
13225 }
13226
13227 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13228 if self.read_only(cx) {
13229 return;
13230 }
13231
13232 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13233
13234 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13235 if let Some((_, Some(selections))) =
13236 self.selection_history.transaction(transaction_id).cloned()
13237 {
13238 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13239 s.select_anchors(selections.to_vec());
13240 });
13241 } else {
13242 log::error!(
13243 "No entry in selection_history found for redo. \
13244 This may correspond to a bug where undo does not update the selection. \
13245 If this is occurring, please add details to \
13246 https://github.com/zed-industries/zed/issues/22692"
13247 );
13248 }
13249 self.request_autoscroll(Autoscroll::fit(), cx);
13250 self.unmark_text(window, cx);
13251 self.refresh_edit_prediction(true, false, window, cx);
13252 cx.emit(EditorEvent::Edited { transaction_id });
13253 }
13254 }
13255
13256 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13257 self.buffer
13258 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13259 }
13260
13261 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13262 self.buffer
13263 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13264 }
13265
13266 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13267 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13268 self.change_selections(Default::default(), window, cx, |s| {
13269 s.move_with(|map, selection| {
13270 let cursor = if selection.is_empty() {
13271 movement::left(map, selection.start)
13272 } else {
13273 selection.start
13274 };
13275 selection.collapse_to(cursor, SelectionGoal::None);
13276 });
13277 })
13278 }
13279
13280 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13281 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13282 self.change_selections(Default::default(), window, cx, |s| {
13283 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13284 })
13285 }
13286
13287 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13288 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13289 self.change_selections(Default::default(), window, cx, |s| {
13290 s.move_with(|map, selection| {
13291 let cursor = if selection.is_empty() {
13292 movement::right(map, selection.end)
13293 } else {
13294 selection.end
13295 };
13296 selection.collapse_to(cursor, SelectionGoal::None)
13297 });
13298 })
13299 }
13300
13301 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13302 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13303 self.change_selections(Default::default(), window, cx, |s| {
13304 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13305 });
13306 }
13307
13308 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13309 if self.take_rename(true, window, cx).is_some() {
13310 return;
13311 }
13312
13313 if self.mode.is_single_line() {
13314 cx.propagate();
13315 return;
13316 }
13317
13318 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13319
13320 let text_layout_details = &self.text_layout_details(window);
13321 let selection_count = self.selections.count();
13322 let first_selection = self.selections.first_anchor();
13323
13324 self.change_selections(Default::default(), window, cx, |s| {
13325 s.move_with(|map, selection| {
13326 if !selection.is_empty() {
13327 selection.goal = SelectionGoal::None;
13328 }
13329 let (cursor, goal) = movement::up(
13330 map,
13331 selection.start,
13332 selection.goal,
13333 false,
13334 text_layout_details,
13335 );
13336 selection.collapse_to(cursor, goal);
13337 });
13338 });
13339
13340 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13341 {
13342 cx.propagate();
13343 }
13344 }
13345
13346 pub fn move_up_by_lines(
13347 &mut self,
13348 action: &MoveUpByLines,
13349 window: &mut Window,
13350 cx: &mut Context<Self>,
13351 ) {
13352 if self.take_rename(true, window, cx).is_some() {
13353 return;
13354 }
13355
13356 if self.mode.is_single_line() {
13357 cx.propagate();
13358 return;
13359 }
13360
13361 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13362
13363 let text_layout_details = &self.text_layout_details(window);
13364
13365 self.change_selections(Default::default(), window, cx, |s| {
13366 s.move_with(|map, selection| {
13367 if !selection.is_empty() {
13368 selection.goal = SelectionGoal::None;
13369 }
13370 let (cursor, goal) = movement::up_by_rows(
13371 map,
13372 selection.start,
13373 action.lines,
13374 selection.goal,
13375 false,
13376 text_layout_details,
13377 );
13378 selection.collapse_to(cursor, goal);
13379 });
13380 })
13381 }
13382
13383 pub fn move_down_by_lines(
13384 &mut self,
13385 action: &MoveDownByLines,
13386 window: &mut Window,
13387 cx: &mut Context<Self>,
13388 ) {
13389 if self.take_rename(true, window, cx).is_some() {
13390 return;
13391 }
13392
13393 if self.mode.is_single_line() {
13394 cx.propagate();
13395 return;
13396 }
13397
13398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13399
13400 let text_layout_details = &self.text_layout_details(window);
13401
13402 self.change_selections(Default::default(), window, cx, |s| {
13403 s.move_with(|map, selection| {
13404 if !selection.is_empty() {
13405 selection.goal = SelectionGoal::None;
13406 }
13407 let (cursor, goal) = movement::down_by_rows(
13408 map,
13409 selection.start,
13410 action.lines,
13411 selection.goal,
13412 false,
13413 text_layout_details,
13414 );
13415 selection.collapse_to(cursor, goal);
13416 });
13417 })
13418 }
13419
13420 pub fn select_down_by_lines(
13421 &mut self,
13422 action: &SelectDownByLines,
13423 window: &mut Window,
13424 cx: &mut Context<Self>,
13425 ) {
13426 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13427 let text_layout_details = &self.text_layout_details(window);
13428 self.change_selections(Default::default(), window, cx, |s| {
13429 s.move_heads_with(|map, head, goal| {
13430 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13431 })
13432 })
13433 }
13434
13435 pub fn select_up_by_lines(
13436 &mut self,
13437 action: &SelectUpByLines,
13438 window: &mut Window,
13439 cx: &mut Context<Self>,
13440 ) {
13441 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13442 let text_layout_details = &self.text_layout_details(window);
13443 self.change_selections(Default::default(), window, cx, |s| {
13444 s.move_heads_with(|map, head, goal| {
13445 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13446 })
13447 })
13448 }
13449
13450 pub fn select_page_up(
13451 &mut self,
13452 _: &SelectPageUp,
13453 window: &mut Window,
13454 cx: &mut Context<Self>,
13455 ) {
13456 let Some(row_count) = self.visible_row_count() else {
13457 return;
13458 };
13459
13460 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13461
13462 let text_layout_details = &self.text_layout_details(window);
13463
13464 self.change_selections(Default::default(), window, cx, |s| {
13465 s.move_heads_with(|map, head, goal| {
13466 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13467 })
13468 })
13469 }
13470
13471 pub fn move_page_up(
13472 &mut self,
13473 action: &MovePageUp,
13474 window: &mut Window,
13475 cx: &mut Context<Self>,
13476 ) {
13477 if self.take_rename(true, window, cx).is_some() {
13478 return;
13479 }
13480
13481 if self
13482 .context_menu
13483 .borrow_mut()
13484 .as_mut()
13485 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13486 .unwrap_or(false)
13487 {
13488 return;
13489 }
13490
13491 if matches!(self.mode, EditorMode::SingleLine) {
13492 cx.propagate();
13493 return;
13494 }
13495
13496 let Some(row_count) = self.visible_row_count() else {
13497 return;
13498 };
13499
13500 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13501
13502 let effects = if action.center_cursor {
13503 SelectionEffects::scroll(Autoscroll::center())
13504 } else {
13505 SelectionEffects::default()
13506 };
13507
13508 let text_layout_details = &self.text_layout_details(window);
13509
13510 self.change_selections(effects, window, cx, |s| {
13511 s.move_with(|map, selection| {
13512 if !selection.is_empty() {
13513 selection.goal = SelectionGoal::None;
13514 }
13515 let (cursor, goal) = movement::up_by_rows(
13516 map,
13517 selection.end,
13518 row_count,
13519 selection.goal,
13520 false,
13521 text_layout_details,
13522 );
13523 selection.collapse_to(cursor, goal);
13524 });
13525 });
13526 }
13527
13528 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13529 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13530 let text_layout_details = &self.text_layout_details(window);
13531 self.change_selections(Default::default(), window, cx, |s| {
13532 s.move_heads_with(|map, head, goal| {
13533 movement::up(map, head, goal, false, text_layout_details)
13534 })
13535 })
13536 }
13537
13538 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13539 self.take_rename(true, window, cx);
13540
13541 if self.mode.is_single_line() {
13542 cx.propagate();
13543 return;
13544 }
13545
13546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13547
13548 let text_layout_details = &self.text_layout_details(window);
13549 let selection_count = self.selections.count();
13550 let first_selection = self.selections.first_anchor();
13551
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.move_with(|map, selection| {
13554 if !selection.is_empty() {
13555 selection.goal = SelectionGoal::None;
13556 }
13557 let (cursor, goal) = movement::down(
13558 map,
13559 selection.end,
13560 selection.goal,
13561 false,
13562 text_layout_details,
13563 );
13564 selection.collapse_to(cursor, goal);
13565 });
13566 });
13567
13568 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13569 {
13570 cx.propagate();
13571 }
13572 }
13573
13574 pub fn select_page_down(
13575 &mut self,
13576 _: &SelectPageDown,
13577 window: &mut Window,
13578 cx: &mut Context<Self>,
13579 ) {
13580 let Some(row_count) = self.visible_row_count() else {
13581 return;
13582 };
13583
13584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13585
13586 let text_layout_details = &self.text_layout_details(window);
13587
13588 self.change_selections(Default::default(), window, cx, |s| {
13589 s.move_heads_with(|map, head, goal| {
13590 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13591 })
13592 })
13593 }
13594
13595 pub fn move_page_down(
13596 &mut self,
13597 action: &MovePageDown,
13598 window: &mut Window,
13599 cx: &mut Context<Self>,
13600 ) {
13601 if self.take_rename(true, window, cx).is_some() {
13602 return;
13603 }
13604
13605 if self
13606 .context_menu
13607 .borrow_mut()
13608 .as_mut()
13609 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13610 .unwrap_or(false)
13611 {
13612 return;
13613 }
13614
13615 if matches!(self.mode, EditorMode::SingleLine) {
13616 cx.propagate();
13617 return;
13618 }
13619
13620 let Some(row_count) = self.visible_row_count() else {
13621 return;
13622 };
13623
13624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13625
13626 let effects = if action.center_cursor {
13627 SelectionEffects::scroll(Autoscroll::center())
13628 } else {
13629 SelectionEffects::default()
13630 };
13631
13632 let text_layout_details = &self.text_layout_details(window);
13633 self.change_selections(effects, window, cx, |s| {
13634 s.move_with(|map, selection| {
13635 if !selection.is_empty() {
13636 selection.goal = SelectionGoal::None;
13637 }
13638 let (cursor, goal) = movement::down_by_rows(
13639 map,
13640 selection.end,
13641 row_count,
13642 selection.goal,
13643 false,
13644 text_layout_details,
13645 );
13646 selection.collapse_to(cursor, goal);
13647 });
13648 });
13649 }
13650
13651 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13652 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13653 let text_layout_details = &self.text_layout_details(window);
13654 self.change_selections(Default::default(), window, cx, |s| {
13655 s.move_heads_with(|map, head, goal| {
13656 movement::down(map, head, goal, false, text_layout_details)
13657 })
13658 });
13659 }
13660
13661 pub fn context_menu_first(
13662 &mut self,
13663 _: &ContextMenuFirst,
13664 window: &mut Window,
13665 cx: &mut Context<Self>,
13666 ) {
13667 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13668 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13669 }
13670 }
13671
13672 pub fn context_menu_prev(
13673 &mut self,
13674 _: &ContextMenuPrevious,
13675 window: &mut Window,
13676 cx: &mut Context<Self>,
13677 ) {
13678 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13679 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13680 }
13681 }
13682
13683 pub fn context_menu_next(
13684 &mut self,
13685 _: &ContextMenuNext,
13686 window: &mut Window,
13687 cx: &mut Context<Self>,
13688 ) {
13689 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13690 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13691 }
13692 }
13693
13694 pub fn context_menu_last(
13695 &mut self,
13696 _: &ContextMenuLast,
13697 window: &mut Window,
13698 cx: &mut Context<Self>,
13699 ) {
13700 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13701 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13702 }
13703 }
13704
13705 pub fn signature_help_prev(
13706 &mut self,
13707 _: &SignatureHelpPrevious,
13708 _: &mut Window,
13709 cx: &mut Context<Self>,
13710 ) {
13711 if let Some(popover) = self.signature_help_state.popover_mut() {
13712 if popover.current_signature == 0 {
13713 popover.current_signature = popover.signatures.len() - 1;
13714 } else {
13715 popover.current_signature -= 1;
13716 }
13717 cx.notify();
13718 }
13719 }
13720
13721 pub fn signature_help_next(
13722 &mut self,
13723 _: &SignatureHelpNext,
13724 _: &mut Window,
13725 cx: &mut Context<Self>,
13726 ) {
13727 if let Some(popover) = self.signature_help_state.popover_mut() {
13728 if popover.current_signature + 1 == popover.signatures.len() {
13729 popover.current_signature = 0;
13730 } else {
13731 popover.current_signature += 1;
13732 }
13733 cx.notify();
13734 }
13735 }
13736
13737 pub fn move_to_previous_word_start(
13738 &mut self,
13739 _: &MoveToPreviousWordStart,
13740 window: &mut Window,
13741 cx: &mut Context<Self>,
13742 ) {
13743 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13744 self.change_selections(Default::default(), window, cx, |s| {
13745 s.move_cursors_with(|map, head, _| {
13746 (
13747 movement::previous_word_start(map, head),
13748 SelectionGoal::None,
13749 )
13750 });
13751 })
13752 }
13753
13754 pub fn move_to_previous_subword_start(
13755 &mut self,
13756 _: &MoveToPreviousSubwordStart,
13757 window: &mut Window,
13758 cx: &mut Context<Self>,
13759 ) {
13760 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13761 self.change_selections(Default::default(), window, cx, |s| {
13762 s.move_cursors_with(|map, head, _| {
13763 (
13764 movement::previous_subword_start(map, head),
13765 SelectionGoal::None,
13766 )
13767 });
13768 })
13769 }
13770
13771 pub fn select_to_previous_word_start(
13772 &mut self,
13773 _: &SelectToPreviousWordStart,
13774 window: &mut Window,
13775 cx: &mut Context<Self>,
13776 ) {
13777 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13778 self.change_selections(Default::default(), window, cx, |s| {
13779 s.move_heads_with(|map, head, _| {
13780 (
13781 movement::previous_word_start(map, head),
13782 SelectionGoal::None,
13783 )
13784 });
13785 })
13786 }
13787
13788 pub fn select_to_previous_subword_start(
13789 &mut self,
13790 _: &SelectToPreviousSubwordStart,
13791 window: &mut Window,
13792 cx: &mut Context<Self>,
13793 ) {
13794 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13795 self.change_selections(Default::default(), window, cx, |s| {
13796 s.move_heads_with(|map, head, _| {
13797 (
13798 movement::previous_subword_start(map, head),
13799 SelectionGoal::None,
13800 )
13801 });
13802 })
13803 }
13804
13805 pub fn delete_to_previous_word_start(
13806 &mut self,
13807 action: &DeleteToPreviousWordStart,
13808 window: &mut Window,
13809 cx: &mut Context<Self>,
13810 ) {
13811 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13812 self.transact(window, cx, |this, window, cx| {
13813 this.select_autoclose_pair(window, cx);
13814 this.change_selections(Default::default(), window, cx, |s| {
13815 s.move_with(|map, selection| {
13816 if selection.is_empty() {
13817 let mut cursor = if action.ignore_newlines {
13818 movement::previous_word_start(map, selection.head())
13819 } else {
13820 movement::previous_word_start_or_newline(map, selection.head())
13821 };
13822 cursor = movement::adjust_greedy_deletion(
13823 map,
13824 selection.head(),
13825 cursor,
13826 action.ignore_brackets,
13827 );
13828 selection.set_head(cursor, SelectionGoal::None);
13829 }
13830 });
13831 });
13832 this.insert("", window, cx);
13833 });
13834 }
13835
13836 pub fn delete_to_previous_subword_start(
13837 &mut self,
13838 _: &DeleteToPreviousSubwordStart,
13839 window: &mut Window,
13840 cx: &mut Context<Self>,
13841 ) {
13842 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13843 self.transact(window, cx, |this, window, cx| {
13844 this.select_autoclose_pair(window, cx);
13845 this.change_selections(Default::default(), window, cx, |s| {
13846 s.move_with(|map, selection| {
13847 if selection.is_empty() {
13848 let mut cursor = movement::previous_subword_start(map, selection.head());
13849 cursor =
13850 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13851 selection.set_head(cursor, SelectionGoal::None);
13852 }
13853 });
13854 });
13855 this.insert("", window, cx);
13856 });
13857 }
13858
13859 pub fn move_to_next_word_end(
13860 &mut self,
13861 _: &MoveToNextWordEnd,
13862 window: &mut Window,
13863 cx: &mut Context<Self>,
13864 ) {
13865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13866 self.change_selections(Default::default(), window, cx, |s| {
13867 s.move_cursors_with(|map, head, _| {
13868 (movement::next_word_end(map, head), SelectionGoal::None)
13869 });
13870 })
13871 }
13872
13873 pub fn move_to_next_subword_end(
13874 &mut self,
13875 _: &MoveToNextSubwordEnd,
13876 window: &mut Window,
13877 cx: &mut Context<Self>,
13878 ) {
13879 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13880 self.change_selections(Default::default(), window, cx, |s| {
13881 s.move_cursors_with(|map, head, _| {
13882 (movement::next_subword_end(map, head), SelectionGoal::None)
13883 });
13884 })
13885 }
13886
13887 pub fn select_to_next_word_end(
13888 &mut self,
13889 _: &SelectToNextWordEnd,
13890 window: &mut Window,
13891 cx: &mut Context<Self>,
13892 ) {
13893 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13894 self.change_selections(Default::default(), window, cx, |s| {
13895 s.move_heads_with(|map, head, _| {
13896 (movement::next_word_end(map, head), SelectionGoal::None)
13897 });
13898 })
13899 }
13900
13901 pub fn select_to_next_subword_end(
13902 &mut self,
13903 _: &SelectToNextSubwordEnd,
13904 window: &mut Window,
13905 cx: &mut Context<Self>,
13906 ) {
13907 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13908 self.change_selections(Default::default(), window, cx, |s| {
13909 s.move_heads_with(|map, head, _| {
13910 (movement::next_subword_end(map, head), SelectionGoal::None)
13911 });
13912 })
13913 }
13914
13915 pub fn delete_to_next_word_end(
13916 &mut self,
13917 action: &DeleteToNextWordEnd,
13918 window: &mut Window,
13919 cx: &mut Context<Self>,
13920 ) {
13921 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13922 self.transact(window, cx, |this, window, cx| {
13923 this.change_selections(Default::default(), window, cx, |s| {
13924 s.move_with(|map, selection| {
13925 if selection.is_empty() {
13926 let mut cursor = if action.ignore_newlines {
13927 movement::next_word_end(map, selection.head())
13928 } else {
13929 movement::next_word_end_or_newline(map, selection.head())
13930 };
13931 cursor = movement::adjust_greedy_deletion(
13932 map,
13933 selection.head(),
13934 cursor,
13935 action.ignore_brackets,
13936 );
13937 selection.set_head(cursor, SelectionGoal::None);
13938 }
13939 });
13940 });
13941 this.insert("", window, cx);
13942 });
13943 }
13944
13945 pub fn delete_to_next_subword_end(
13946 &mut self,
13947 _: &DeleteToNextSubwordEnd,
13948 window: &mut Window,
13949 cx: &mut Context<Self>,
13950 ) {
13951 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13952 self.transact(window, cx, |this, window, cx| {
13953 this.change_selections(Default::default(), window, cx, |s| {
13954 s.move_with(|map, selection| {
13955 if selection.is_empty() {
13956 let mut cursor = movement::next_subword_end(map, selection.head());
13957 cursor =
13958 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13959 selection.set_head(cursor, SelectionGoal::None);
13960 }
13961 });
13962 });
13963 this.insert("", window, cx);
13964 });
13965 }
13966
13967 pub fn move_to_beginning_of_line(
13968 &mut self,
13969 action: &MoveToBeginningOfLine,
13970 window: &mut Window,
13971 cx: &mut Context<Self>,
13972 ) {
13973 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13974 self.change_selections(Default::default(), window, cx, |s| {
13975 s.move_cursors_with(|map, head, _| {
13976 (
13977 movement::indented_line_beginning(
13978 map,
13979 head,
13980 action.stop_at_soft_wraps,
13981 action.stop_at_indent,
13982 ),
13983 SelectionGoal::None,
13984 )
13985 });
13986 })
13987 }
13988
13989 pub fn select_to_beginning_of_line(
13990 &mut self,
13991 action: &SelectToBeginningOfLine,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) {
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 self.change_selections(Default::default(), window, cx, |s| {
13997 s.move_heads_with(|map, head, _| {
13998 (
13999 movement::indented_line_beginning(
14000 map,
14001 head,
14002 action.stop_at_soft_wraps,
14003 action.stop_at_indent,
14004 ),
14005 SelectionGoal::None,
14006 )
14007 });
14008 });
14009 }
14010
14011 pub fn delete_to_beginning_of_line(
14012 &mut self,
14013 action: &DeleteToBeginningOfLine,
14014 window: &mut Window,
14015 cx: &mut Context<Self>,
14016 ) {
14017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14018 self.transact(window, cx, |this, window, cx| {
14019 this.change_selections(Default::default(), window, cx, |s| {
14020 s.move_with(|_, selection| {
14021 selection.reversed = true;
14022 });
14023 });
14024
14025 this.select_to_beginning_of_line(
14026 &SelectToBeginningOfLine {
14027 stop_at_soft_wraps: false,
14028 stop_at_indent: action.stop_at_indent,
14029 },
14030 window,
14031 cx,
14032 );
14033 this.backspace(&Backspace, window, cx);
14034 });
14035 }
14036
14037 pub fn move_to_end_of_line(
14038 &mut self,
14039 action: &MoveToEndOfLine,
14040 window: &mut Window,
14041 cx: &mut Context<Self>,
14042 ) {
14043 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14044 self.change_selections(Default::default(), window, cx, |s| {
14045 s.move_cursors_with(|map, head, _| {
14046 (
14047 movement::line_end(map, head, action.stop_at_soft_wraps),
14048 SelectionGoal::None,
14049 )
14050 });
14051 })
14052 }
14053
14054 pub fn select_to_end_of_line(
14055 &mut self,
14056 action: &SelectToEndOfLine,
14057 window: &mut Window,
14058 cx: &mut Context<Self>,
14059 ) {
14060 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14061 self.change_selections(Default::default(), window, cx, |s| {
14062 s.move_heads_with(|map, head, _| {
14063 (
14064 movement::line_end(map, head, action.stop_at_soft_wraps),
14065 SelectionGoal::None,
14066 )
14067 });
14068 })
14069 }
14070
14071 pub fn delete_to_end_of_line(
14072 &mut self,
14073 _: &DeleteToEndOfLine,
14074 window: &mut Window,
14075 cx: &mut Context<Self>,
14076 ) {
14077 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14078 self.transact(window, cx, |this, window, cx| {
14079 this.select_to_end_of_line(
14080 &SelectToEndOfLine {
14081 stop_at_soft_wraps: false,
14082 },
14083 window,
14084 cx,
14085 );
14086 this.delete(&Delete, window, cx);
14087 });
14088 }
14089
14090 pub fn cut_to_end_of_line(
14091 &mut self,
14092 action: &CutToEndOfLine,
14093 window: &mut Window,
14094 cx: &mut Context<Self>,
14095 ) {
14096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14097 self.transact(window, cx, |this, window, cx| {
14098 this.select_to_end_of_line(
14099 &SelectToEndOfLine {
14100 stop_at_soft_wraps: false,
14101 },
14102 window,
14103 cx,
14104 );
14105 if !action.stop_at_newlines {
14106 this.change_selections(Default::default(), window, cx, |s| {
14107 s.move_with(|_, sel| {
14108 if sel.is_empty() {
14109 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14110 }
14111 });
14112 });
14113 }
14114 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14115 let item = this.cut_common(false, window, cx);
14116 cx.write_to_clipboard(item);
14117 });
14118 }
14119
14120 pub fn move_to_start_of_paragraph(
14121 &mut self,
14122 _: &MoveToStartOfParagraph,
14123 window: &mut Window,
14124 cx: &mut Context<Self>,
14125 ) {
14126 if matches!(self.mode, EditorMode::SingleLine) {
14127 cx.propagate();
14128 return;
14129 }
14130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14131 self.change_selections(Default::default(), window, cx, |s| {
14132 s.move_with(|map, selection| {
14133 selection.collapse_to(
14134 movement::start_of_paragraph(map, selection.head(), 1),
14135 SelectionGoal::None,
14136 )
14137 });
14138 })
14139 }
14140
14141 pub fn move_to_end_of_paragraph(
14142 &mut self,
14143 _: &MoveToEndOfParagraph,
14144 window: &mut Window,
14145 cx: &mut Context<Self>,
14146 ) {
14147 if matches!(self.mode, EditorMode::SingleLine) {
14148 cx.propagate();
14149 return;
14150 }
14151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14152 self.change_selections(Default::default(), window, cx, |s| {
14153 s.move_with(|map, selection| {
14154 selection.collapse_to(
14155 movement::end_of_paragraph(map, selection.head(), 1),
14156 SelectionGoal::None,
14157 )
14158 });
14159 })
14160 }
14161
14162 pub fn select_to_start_of_paragraph(
14163 &mut self,
14164 _: &SelectToStartOfParagraph,
14165 window: &mut Window,
14166 cx: &mut Context<Self>,
14167 ) {
14168 if matches!(self.mode, EditorMode::SingleLine) {
14169 cx.propagate();
14170 return;
14171 }
14172 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14173 self.change_selections(Default::default(), window, cx, |s| {
14174 s.move_heads_with(|map, head, _| {
14175 (
14176 movement::start_of_paragraph(map, head, 1),
14177 SelectionGoal::None,
14178 )
14179 });
14180 })
14181 }
14182
14183 pub fn select_to_end_of_paragraph(
14184 &mut self,
14185 _: &SelectToEndOfParagraph,
14186 window: &mut Window,
14187 cx: &mut Context<Self>,
14188 ) {
14189 if matches!(self.mode, EditorMode::SingleLine) {
14190 cx.propagate();
14191 return;
14192 }
14193 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14194 self.change_selections(Default::default(), window, cx, |s| {
14195 s.move_heads_with(|map, head, _| {
14196 (
14197 movement::end_of_paragraph(map, head, 1),
14198 SelectionGoal::None,
14199 )
14200 });
14201 })
14202 }
14203
14204 pub fn move_to_start_of_excerpt(
14205 &mut self,
14206 _: &MoveToStartOfExcerpt,
14207 window: &mut Window,
14208 cx: &mut Context<Self>,
14209 ) {
14210 if matches!(self.mode, EditorMode::SingleLine) {
14211 cx.propagate();
14212 return;
14213 }
14214 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14215 self.change_selections(Default::default(), window, cx, |s| {
14216 s.move_with(|map, selection| {
14217 selection.collapse_to(
14218 movement::start_of_excerpt(
14219 map,
14220 selection.head(),
14221 workspace::searchable::Direction::Prev,
14222 ),
14223 SelectionGoal::None,
14224 )
14225 });
14226 })
14227 }
14228
14229 pub fn move_to_start_of_next_excerpt(
14230 &mut self,
14231 _: &MoveToStartOfNextExcerpt,
14232 window: &mut Window,
14233 cx: &mut Context<Self>,
14234 ) {
14235 if matches!(self.mode, EditorMode::SingleLine) {
14236 cx.propagate();
14237 return;
14238 }
14239
14240 self.change_selections(Default::default(), window, cx, |s| {
14241 s.move_with(|map, selection| {
14242 selection.collapse_to(
14243 movement::start_of_excerpt(
14244 map,
14245 selection.head(),
14246 workspace::searchable::Direction::Next,
14247 ),
14248 SelectionGoal::None,
14249 )
14250 });
14251 })
14252 }
14253
14254 pub fn move_to_end_of_excerpt(
14255 &mut self,
14256 _: &MoveToEndOfExcerpt,
14257 window: &mut Window,
14258 cx: &mut Context<Self>,
14259 ) {
14260 if matches!(self.mode, EditorMode::SingleLine) {
14261 cx.propagate();
14262 return;
14263 }
14264 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14265 self.change_selections(Default::default(), window, cx, |s| {
14266 s.move_with(|map, selection| {
14267 selection.collapse_to(
14268 movement::end_of_excerpt(
14269 map,
14270 selection.head(),
14271 workspace::searchable::Direction::Next,
14272 ),
14273 SelectionGoal::None,
14274 )
14275 });
14276 })
14277 }
14278
14279 pub fn move_to_end_of_previous_excerpt(
14280 &mut self,
14281 _: &MoveToEndOfPreviousExcerpt,
14282 window: &mut Window,
14283 cx: &mut Context<Self>,
14284 ) {
14285 if matches!(self.mode, EditorMode::SingleLine) {
14286 cx.propagate();
14287 return;
14288 }
14289 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14290 self.change_selections(Default::default(), window, cx, |s| {
14291 s.move_with(|map, selection| {
14292 selection.collapse_to(
14293 movement::end_of_excerpt(
14294 map,
14295 selection.head(),
14296 workspace::searchable::Direction::Prev,
14297 ),
14298 SelectionGoal::None,
14299 )
14300 });
14301 })
14302 }
14303
14304 pub fn select_to_start_of_excerpt(
14305 &mut self,
14306 _: &SelectToStartOfExcerpt,
14307 window: &mut Window,
14308 cx: &mut Context<Self>,
14309 ) {
14310 if matches!(self.mode, EditorMode::SingleLine) {
14311 cx.propagate();
14312 return;
14313 }
14314 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14315 self.change_selections(Default::default(), window, cx, |s| {
14316 s.move_heads_with(|map, head, _| {
14317 (
14318 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14319 SelectionGoal::None,
14320 )
14321 });
14322 })
14323 }
14324
14325 pub fn select_to_start_of_next_excerpt(
14326 &mut self,
14327 _: &SelectToStartOfNextExcerpt,
14328 window: &mut Window,
14329 cx: &mut Context<Self>,
14330 ) {
14331 if matches!(self.mode, EditorMode::SingleLine) {
14332 cx.propagate();
14333 return;
14334 }
14335 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14336 self.change_selections(Default::default(), window, cx, |s| {
14337 s.move_heads_with(|map, head, _| {
14338 (
14339 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14340 SelectionGoal::None,
14341 )
14342 });
14343 })
14344 }
14345
14346 pub fn select_to_end_of_excerpt(
14347 &mut self,
14348 _: &SelectToEndOfExcerpt,
14349 window: &mut Window,
14350 cx: &mut Context<Self>,
14351 ) {
14352 if matches!(self.mode, EditorMode::SingleLine) {
14353 cx.propagate();
14354 return;
14355 }
14356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14357 self.change_selections(Default::default(), window, cx, |s| {
14358 s.move_heads_with(|map, head, _| {
14359 (
14360 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14361 SelectionGoal::None,
14362 )
14363 });
14364 })
14365 }
14366
14367 pub fn select_to_end_of_previous_excerpt(
14368 &mut self,
14369 _: &SelectToEndOfPreviousExcerpt,
14370 window: &mut Window,
14371 cx: &mut Context<Self>,
14372 ) {
14373 if matches!(self.mode, EditorMode::SingleLine) {
14374 cx.propagate();
14375 return;
14376 }
14377 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14378 self.change_selections(Default::default(), window, cx, |s| {
14379 s.move_heads_with(|map, head, _| {
14380 (
14381 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14382 SelectionGoal::None,
14383 )
14384 });
14385 })
14386 }
14387
14388 pub fn move_to_beginning(
14389 &mut self,
14390 _: &MoveToBeginning,
14391 window: &mut Window,
14392 cx: &mut Context<Self>,
14393 ) {
14394 if matches!(self.mode, EditorMode::SingleLine) {
14395 cx.propagate();
14396 return;
14397 }
14398 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14399 self.change_selections(Default::default(), window, cx, |s| {
14400 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14401 });
14402 }
14403
14404 pub fn select_to_beginning(
14405 &mut self,
14406 _: &SelectToBeginning,
14407 window: &mut Window,
14408 cx: &mut Context<Self>,
14409 ) {
14410 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14411 selection.set_head(Point::zero(), SelectionGoal::None);
14412 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14413 self.change_selections(Default::default(), window, cx, |s| {
14414 s.select(vec![selection]);
14415 });
14416 }
14417
14418 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14419 if matches!(self.mode, EditorMode::SingleLine) {
14420 cx.propagate();
14421 return;
14422 }
14423 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14424 let cursor = self.buffer.read(cx).read(cx).len();
14425 self.change_selections(Default::default(), window, cx, |s| {
14426 s.select_ranges(vec![cursor..cursor])
14427 });
14428 }
14429
14430 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14431 self.nav_history = nav_history;
14432 }
14433
14434 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14435 self.nav_history.as_ref()
14436 }
14437
14438 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14439 self.push_to_nav_history(
14440 self.selections.newest_anchor().head(),
14441 None,
14442 false,
14443 true,
14444 cx,
14445 );
14446 }
14447
14448 fn push_to_nav_history(
14449 &mut self,
14450 cursor_anchor: Anchor,
14451 new_position: Option<Point>,
14452 is_deactivate: bool,
14453 always: bool,
14454 cx: &mut Context<Self>,
14455 ) {
14456 if let Some(nav_history) = self.nav_history.as_mut() {
14457 let buffer = self.buffer.read(cx).read(cx);
14458 let cursor_position = cursor_anchor.to_point(&buffer);
14459 let scroll_state = self.scroll_manager.anchor();
14460 let scroll_top_row = scroll_state.top_row(&buffer);
14461 drop(buffer);
14462
14463 if let Some(new_position) = new_position {
14464 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14465 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14466 return;
14467 }
14468 }
14469
14470 nav_history.push(
14471 Some(NavigationData {
14472 cursor_anchor,
14473 cursor_position,
14474 scroll_anchor: scroll_state,
14475 scroll_top_row,
14476 }),
14477 cx,
14478 );
14479 cx.emit(EditorEvent::PushedToNavHistory {
14480 anchor: cursor_anchor,
14481 is_deactivate,
14482 })
14483 }
14484 }
14485
14486 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14487 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14488 let buffer = self.buffer.read(cx).snapshot(cx);
14489 let mut selection = self
14490 .selections
14491 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14492 selection.set_head(buffer.len(), SelectionGoal::None);
14493 self.change_selections(Default::default(), window, cx, |s| {
14494 s.select(vec![selection]);
14495 });
14496 }
14497
14498 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14500 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14501 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14502 });
14503 }
14504
14505 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14507 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14508 let mut selections = self.selections.all::<Point>(&display_map);
14509 let max_point = display_map.buffer_snapshot().max_point();
14510 for selection in &mut selections {
14511 let rows = selection.spanned_rows(true, &display_map);
14512 selection.start = Point::new(rows.start.0, 0);
14513 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14514 selection.reversed = false;
14515 }
14516 self.change_selections(Default::default(), window, cx, |s| {
14517 s.select(selections);
14518 });
14519 }
14520
14521 pub fn split_selection_into_lines(
14522 &mut self,
14523 action: &SplitSelectionIntoLines,
14524 window: &mut Window,
14525 cx: &mut Context<Self>,
14526 ) {
14527 let selections = self
14528 .selections
14529 .all::<Point>(&self.display_snapshot(cx))
14530 .into_iter()
14531 .map(|selection| selection.start..selection.end)
14532 .collect::<Vec<_>>();
14533 self.unfold_ranges(&selections, true, true, cx);
14534
14535 let mut new_selection_ranges = Vec::new();
14536 {
14537 let buffer = self.buffer.read(cx).read(cx);
14538 for selection in selections {
14539 for row in selection.start.row..selection.end.row {
14540 let line_start = Point::new(row, 0);
14541 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14542
14543 if action.keep_selections {
14544 // Keep the selection range for each line
14545 let selection_start = if row == selection.start.row {
14546 selection.start
14547 } else {
14548 line_start
14549 };
14550 new_selection_ranges.push(selection_start..line_end);
14551 } else {
14552 // Collapse to cursor at end of line
14553 new_selection_ranges.push(line_end..line_end);
14554 }
14555 }
14556
14557 let is_multiline_selection = selection.start.row != selection.end.row;
14558 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14559 // so this action feels more ergonomic when paired with other selection operations
14560 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14561 if !should_skip_last {
14562 if action.keep_selections {
14563 if is_multiline_selection {
14564 let line_start = Point::new(selection.end.row, 0);
14565 new_selection_ranges.push(line_start..selection.end);
14566 } else {
14567 new_selection_ranges.push(selection.start..selection.end);
14568 }
14569 } else {
14570 new_selection_ranges.push(selection.end..selection.end);
14571 }
14572 }
14573 }
14574 }
14575 self.change_selections(Default::default(), window, cx, |s| {
14576 s.select_ranges(new_selection_ranges);
14577 });
14578 }
14579
14580 pub fn add_selection_above(
14581 &mut self,
14582 action: &AddSelectionAbove,
14583 window: &mut Window,
14584 cx: &mut Context<Self>,
14585 ) {
14586 self.add_selection(true, action.skip_soft_wrap, window, cx);
14587 }
14588
14589 pub fn add_selection_below(
14590 &mut self,
14591 action: &AddSelectionBelow,
14592 window: &mut Window,
14593 cx: &mut Context<Self>,
14594 ) {
14595 self.add_selection(false, action.skip_soft_wrap, window, cx);
14596 }
14597
14598 fn add_selection(
14599 &mut self,
14600 above: bool,
14601 skip_soft_wrap: bool,
14602 window: &mut Window,
14603 cx: &mut Context<Self>,
14604 ) {
14605 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14606
14607 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14608 let all_selections = self.selections.all::<Point>(&display_map);
14609 let text_layout_details = self.text_layout_details(window);
14610
14611 let (mut columnar_selections, new_selections_to_columnarize) = {
14612 if let Some(state) = self.add_selections_state.as_ref() {
14613 let columnar_selection_ids: HashSet<_> = state
14614 .groups
14615 .iter()
14616 .flat_map(|group| group.stack.iter())
14617 .copied()
14618 .collect();
14619
14620 all_selections
14621 .into_iter()
14622 .partition(|s| columnar_selection_ids.contains(&s.id))
14623 } else {
14624 (Vec::new(), all_selections)
14625 }
14626 };
14627
14628 let mut state = self
14629 .add_selections_state
14630 .take()
14631 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14632
14633 for selection in new_selections_to_columnarize {
14634 let range = selection.display_range(&display_map).sorted();
14635 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14636 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14637 let positions = start_x.min(end_x)..start_x.max(end_x);
14638 let mut stack = Vec::new();
14639 for row in range.start.row().0..=range.end.row().0 {
14640 if let Some(selection) = self.selections.build_columnar_selection(
14641 &display_map,
14642 DisplayRow(row),
14643 &positions,
14644 selection.reversed,
14645 &text_layout_details,
14646 ) {
14647 stack.push(selection.id);
14648 columnar_selections.push(selection);
14649 }
14650 }
14651 if !stack.is_empty() {
14652 if above {
14653 stack.reverse();
14654 }
14655 state.groups.push(AddSelectionsGroup { above, stack });
14656 }
14657 }
14658
14659 let mut final_selections = Vec::new();
14660 let end_row = if above {
14661 DisplayRow(0)
14662 } else {
14663 display_map.max_point().row()
14664 };
14665
14666 let mut last_added_item_per_group = HashMap::default();
14667 for group in state.groups.iter_mut() {
14668 if let Some(last_id) = group.stack.last() {
14669 last_added_item_per_group.insert(*last_id, group);
14670 }
14671 }
14672
14673 for selection in columnar_selections {
14674 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14675 if above == group.above {
14676 let range = selection.display_range(&display_map).sorted();
14677 debug_assert_eq!(range.start.row(), range.end.row());
14678 let mut row = range.start.row();
14679 let positions =
14680 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14681 Pixels::from(start)..Pixels::from(end)
14682 } else {
14683 let start_x =
14684 display_map.x_for_display_point(range.start, &text_layout_details);
14685 let end_x =
14686 display_map.x_for_display_point(range.end, &text_layout_details);
14687 start_x.min(end_x)..start_x.max(end_x)
14688 };
14689
14690 let mut maybe_new_selection = None;
14691 let direction = if above { -1 } else { 1 };
14692
14693 while row != end_row {
14694 if skip_soft_wrap {
14695 row = display_map
14696 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14697 .row();
14698 } else if above {
14699 row.0 -= 1;
14700 } else {
14701 row.0 += 1;
14702 }
14703
14704 if let Some(new_selection) = self.selections.build_columnar_selection(
14705 &display_map,
14706 row,
14707 &positions,
14708 selection.reversed,
14709 &text_layout_details,
14710 ) {
14711 maybe_new_selection = Some(new_selection);
14712 break;
14713 }
14714 }
14715
14716 if let Some(new_selection) = maybe_new_selection {
14717 group.stack.push(new_selection.id);
14718 if above {
14719 final_selections.push(new_selection);
14720 final_selections.push(selection);
14721 } else {
14722 final_selections.push(selection);
14723 final_selections.push(new_selection);
14724 }
14725 } else {
14726 final_selections.push(selection);
14727 }
14728 } else {
14729 group.stack.pop();
14730 }
14731 } else {
14732 final_selections.push(selection);
14733 }
14734 }
14735
14736 self.change_selections(Default::default(), window, cx, |s| {
14737 s.select(final_selections);
14738 });
14739
14740 let final_selection_ids: HashSet<_> = self
14741 .selections
14742 .all::<Point>(&display_map)
14743 .iter()
14744 .map(|s| s.id)
14745 .collect();
14746 state.groups.retain_mut(|group| {
14747 // selections might get merged above so we remove invalid items from stacks
14748 group.stack.retain(|id| final_selection_ids.contains(id));
14749
14750 // single selection in stack can be treated as initial state
14751 group.stack.len() > 1
14752 });
14753
14754 if !state.groups.is_empty() {
14755 self.add_selections_state = Some(state);
14756 }
14757 }
14758
14759 fn select_match_ranges(
14760 &mut self,
14761 range: Range<MultiBufferOffset>,
14762 reversed: bool,
14763 replace_newest: bool,
14764 auto_scroll: Option<Autoscroll>,
14765 window: &mut Window,
14766 cx: &mut Context<Editor>,
14767 ) {
14768 self.unfold_ranges(
14769 std::slice::from_ref(&range),
14770 false,
14771 auto_scroll.is_some(),
14772 cx,
14773 );
14774 let effects = if let Some(scroll) = auto_scroll {
14775 SelectionEffects::scroll(scroll)
14776 } else {
14777 SelectionEffects::no_scroll()
14778 };
14779 self.change_selections(effects, window, cx, |s| {
14780 if replace_newest {
14781 s.delete(s.newest_anchor().id);
14782 }
14783 if reversed {
14784 s.insert_range(range.end..range.start);
14785 } else {
14786 s.insert_range(range);
14787 }
14788 });
14789 }
14790
14791 pub fn select_next_match_internal(
14792 &mut self,
14793 display_map: &DisplaySnapshot,
14794 replace_newest: bool,
14795 autoscroll: Option<Autoscroll>,
14796 window: &mut Window,
14797 cx: &mut Context<Self>,
14798 ) -> Result<()> {
14799 let buffer = display_map.buffer_snapshot();
14800 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
14801 if let Some(mut select_next_state) = self.select_next_state.take() {
14802 let query = &select_next_state.query;
14803 if !select_next_state.done {
14804 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14805 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14806 let mut next_selected_range = None;
14807
14808 let bytes_after_last_selection =
14809 buffer.bytes_in_range(last_selection.end..buffer.len());
14810 let bytes_before_first_selection =
14811 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
14812 let query_matches = query
14813 .stream_find_iter(bytes_after_last_selection)
14814 .map(|result| (last_selection.end, result))
14815 .chain(
14816 query
14817 .stream_find_iter(bytes_before_first_selection)
14818 .map(|result| (MultiBufferOffset(0), result)),
14819 );
14820
14821 for (start_offset, query_match) in query_matches {
14822 let query_match = query_match.unwrap(); // can only fail due to I/O
14823 let offset_range =
14824 start_offset + query_match.start()..start_offset + query_match.end();
14825
14826 if !select_next_state.wordwise
14827 || (!buffer.is_inside_word(offset_range.start, None)
14828 && !buffer.is_inside_word(offset_range.end, None))
14829 {
14830 let idx = selections
14831 .partition_point(|selection| selection.end <= offset_range.start);
14832 let overlaps = selections
14833 .get(idx)
14834 .map_or(false, |selection| selection.start < offset_range.end);
14835
14836 if !overlaps {
14837 next_selected_range = Some(offset_range);
14838 break;
14839 }
14840 }
14841 }
14842
14843 if let Some(next_selected_range) = next_selected_range {
14844 self.select_match_ranges(
14845 next_selected_range,
14846 last_selection.reversed,
14847 replace_newest,
14848 autoscroll,
14849 window,
14850 cx,
14851 );
14852 } else {
14853 select_next_state.done = true;
14854 }
14855 }
14856
14857 self.select_next_state = Some(select_next_state);
14858 } else {
14859 let mut only_carets = true;
14860 let mut same_text_selected = true;
14861 let mut selected_text = None;
14862
14863 let mut selections_iter = selections.iter().peekable();
14864 while let Some(selection) = selections_iter.next() {
14865 if selection.start != selection.end {
14866 only_carets = false;
14867 }
14868
14869 if same_text_selected {
14870 if selected_text.is_none() {
14871 selected_text =
14872 Some(buffer.text_for_range(selection.range()).collect::<String>());
14873 }
14874
14875 if let Some(next_selection) = selections_iter.peek() {
14876 if next_selection.len() == selection.len() {
14877 let next_selected_text = buffer
14878 .text_for_range(next_selection.range())
14879 .collect::<String>();
14880 if Some(next_selected_text) != selected_text {
14881 same_text_selected = false;
14882 selected_text = None;
14883 }
14884 } else {
14885 same_text_selected = false;
14886 selected_text = None;
14887 }
14888 }
14889 }
14890 }
14891
14892 if only_carets {
14893 for selection in &mut selections {
14894 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14895 selection.start = word_range.start;
14896 selection.end = word_range.end;
14897 selection.goal = SelectionGoal::None;
14898 selection.reversed = false;
14899 self.select_match_ranges(
14900 selection.start..selection.end,
14901 selection.reversed,
14902 replace_newest,
14903 autoscroll,
14904 window,
14905 cx,
14906 );
14907 }
14908
14909 if selections.len() == 1 {
14910 let selection = selections
14911 .last()
14912 .expect("ensured that there's only one selection");
14913 let query = buffer
14914 .text_for_range(selection.start..selection.end)
14915 .collect::<String>();
14916 let is_empty = query.is_empty();
14917 let select_state = SelectNextState {
14918 query: self.build_query(&[query], cx)?,
14919 wordwise: true,
14920 done: is_empty,
14921 };
14922 self.select_next_state = Some(select_state);
14923 } else {
14924 self.select_next_state = None;
14925 }
14926 } else if let Some(selected_text) = selected_text {
14927 self.select_next_state = Some(SelectNextState {
14928 query: self.build_query(&[selected_text], cx)?,
14929 wordwise: false,
14930 done: false,
14931 });
14932 self.select_next_match_internal(
14933 display_map,
14934 replace_newest,
14935 autoscroll,
14936 window,
14937 cx,
14938 )?;
14939 }
14940 }
14941 Ok(())
14942 }
14943
14944 pub fn select_all_matches(
14945 &mut self,
14946 _action: &SelectAllMatches,
14947 window: &mut Window,
14948 cx: &mut Context<Self>,
14949 ) -> Result<()> {
14950 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14951
14952 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14953
14954 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14955 let Some(select_next_state) = self.select_next_state.as_mut() else {
14956 return Ok(());
14957 };
14958 if select_next_state.done {
14959 return Ok(());
14960 }
14961
14962 let mut new_selections = Vec::new();
14963
14964 let reversed = self
14965 .selections
14966 .oldest::<MultiBufferOffset>(&display_map)
14967 .reversed;
14968 let buffer = display_map.buffer_snapshot();
14969 let query_matches = select_next_state
14970 .query
14971 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
14972
14973 for query_match in query_matches.into_iter() {
14974 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14975 let offset_range = if reversed {
14976 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
14977 } else {
14978 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
14979 };
14980
14981 if !select_next_state.wordwise
14982 || (!buffer.is_inside_word(offset_range.start, None)
14983 && !buffer.is_inside_word(offset_range.end, None))
14984 {
14985 new_selections.push(offset_range.start..offset_range.end);
14986 }
14987 }
14988
14989 select_next_state.done = true;
14990
14991 if new_selections.is_empty() {
14992 log::error!("bug: new_selections is empty in select_all_matches");
14993 return Ok(());
14994 }
14995
14996 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14997 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14998 selections.select_ranges(new_selections)
14999 });
15000
15001 Ok(())
15002 }
15003
15004 pub fn select_next(
15005 &mut self,
15006 action: &SelectNext,
15007 window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) -> Result<()> {
15010 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15011 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15012 self.select_next_match_internal(
15013 &display_map,
15014 action.replace_newest,
15015 Some(Autoscroll::newest()),
15016 window,
15017 cx,
15018 )?;
15019 Ok(())
15020 }
15021
15022 pub fn select_previous(
15023 &mut self,
15024 action: &SelectPrevious,
15025 window: &mut Window,
15026 cx: &mut Context<Self>,
15027 ) -> Result<()> {
15028 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15029 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15030 let buffer = display_map.buffer_snapshot();
15031 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15032 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15033 let query = &select_prev_state.query;
15034 if !select_prev_state.done {
15035 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15036 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15037 let mut next_selected_range = None;
15038 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15039 let bytes_before_last_selection =
15040 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15041 let bytes_after_first_selection =
15042 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15043 let query_matches = query
15044 .stream_find_iter(bytes_before_last_selection)
15045 .map(|result| (last_selection.start, result))
15046 .chain(
15047 query
15048 .stream_find_iter(bytes_after_first_selection)
15049 .map(|result| (buffer.len(), result)),
15050 );
15051 for (end_offset, query_match) in query_matches {
15052 let query_match = query_match.unwrap(); // can only fail due to I/O
15053 let offset_range =
15054 end_offset - query_match.end()..end_offset - query_match.start();
15055
15056 if !select_prev_state.wordwise
15057 || (!buffer.is_inside_word(offset_range.start, None)
15058 && !buffer.is_inside_word(offset_range.end, None))
15059 {
15060 next_selected_range = Some(offset_range);
15061 break;
15062 }
15063 }
15064
15065 if let Some(next_selected_range) = next_selected_range {
15066 self.select_match_ranges(
15067 next_selected_range,
15068 last_selection.reversed,
15069 action.replace_newest,
15070 Some(Autoscroll::newest()),
15071 window,
15072 cx,
15073 );
15074 } else {
15075 select_prev_state.done = true;
15076 }
15077 }
15078
15079 self.select_prev_state = Some(select_prev_state);
15080 } else {
15081 let mut only_carets = true;
15082 let mut same_text_selected = true;
15083 let mut selected_text = None;
15084
15085 let mut selections_iter = selections.iter().peekable();
15086 while let Some(selection) = selections_iter.next() {
15087 if selection.start != selection.end {
15088 only_carets = false;
15089 }
15090
15091 if same_text_selected {
15092 if selected_text.is_none() {
15093 selected_text =
15094 Some(buffer.text_for_range(selection.range()).collect::<String>());
15095 }
15096
15097 if let Some(next_selection) = selections_iter.peek() {
15098 if next_selection.len() == selection.len() {
15099 let next_selected_text = buffer
15100 .text_for_range(next_selection.range())
15101 .collect::<String>();
15102 if Some(next_selected_text) != selected_text {
15103 same_text_selected = false;
15104 selected_text = None;
15105 }
15106 } else {
15107 same_text_selected = false;
15108 selected_text = None;
15109 }
15110 }
15111 }
15112 }
15113
15114 if only_carets {
15115 for selection in &mut selections {
15116 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15117 selection.start = word_range.start;
15118 selection.end = word_range.end;
15119 selection.goal = SelectionGoal::None;
15120 selection.reversed = false;
15121 self.select_match_ranges(
15122 selection.start..selection.end,
15123 selection.reversed,
15124 action.replace_newest,
15125 Some(Autoscroll::newest()),
15126 window,
15127 cx,
15128 );
15129 }
15130 if selections.len() == 1 {
15131 let selection = selections
15132 .last()
15133 .expect("ensured that there's only one selection");
15134 let query = buffer
15135 .text_for_range(selection.start..selection.end)
15136 .collect::<String>();
15137 let is_empty = query.is_empty();
15138 let select_state = SelectNextState {
15139 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15140 wordwise: true,
15141 done: is_empty,
15142 };
15143 self.select_prev_state = Some(select_state);
15144 } else {
15145 self.select_prev_state = None;
15146 }
15147 } else if let Some(selected_text) = selected_text {
15148 self.select_prev_state = Some(SelectNextState {
15149 query: self
15150 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15151 wordwise: false,
15152 done: false,
15153 });
15154 self.select_previous(action, window, cx)?;
15155 }
15156 }
15157 Ok(())
15158 }
15159
15160 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15161 /// setting the case sensitivity based on the global
15162 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15163 /// editor's settings.
15164 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15165 where
15166 I: IntoIterator<Item = P>,
15167 P: AsRef<[u8]>,
15168 {
15169 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15170 || EditorSettings::get_global(cx).search.case_sensitive,
15171 |value| value,
15172 );
15173
15174 let mut builder = AhoCorasickBuilder::new();
15175 builder.ascii_case_insensitive(!case_sensitive);
15176 builder.build(patterns)
15177 }
15178
15179 pub fn find_next_match(
15180 &mut self,
15181 _: &FindNextMatch,
15182 window: &mut Window,
15183 cx: &mut Context<Self>,
15184 ) -> Result<()> {
15185 let selections = self.selections.disjoint_anchors_arc();
15186 match selections.first() {
15187 Some(first) if selections.len() >= 2 => {
15188 self.change_selections(Default::default(), window, cx, |s| {
15189 s.select_ranges([first.range()]);
15190 });
15191 }
15192 _ => self.select_next(
15193 &SelectNext {
15194 replace_newest: true,
15195 },
15196 window,
15197 cx,
15198 )?,
15199 }
15200 Ok(())
15201 }
15202
15203 pub fn find_previous_match(
15204 &mut self,
15205 _: &FindPreviousMatch,
15206 window: &mut Window,
15207 cx: &mut Context<Self>,
15208 ) -> Result<()> {
15209 let selections = self.selections.disjoint_anchors_arc();
15210 match selections.last() {
15211 Some(last) if selections.len() >= 2 => {
15212 self.change_selections(Default::default(), window, cx, |s| {
15213 s.select_ranges([last.range()]);
15214 });
15215 }
15216 _ => self.select_previous(
15217 &SelectPrevious {
15218 replace_newest: true,
15219 },
15220 window,
15221 cx,
15222 )?,
15223 }
15224 Ok(())
15225 }
15226
15227 pub fn toggle_comments(
15228 &mut self,
15229 action: &ToggleComments,
15230 window: &mut Window,
15231 cx: &mut Context<Self>,
15232 ) {
15233 if self.read_only(cx) {
15234 return;
15235 }
15236 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15237 let text_layout_details = &self.text_layout_details(window);
15238 self.transact(window, cx, |this, window, cx| {
15239 let mut selections = this
15240 .selections
15241 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15242 let mut edits = Vec::new();
15243 let mut selection_edit_ranges = Vec::new();
15244 let mut last_toggled_row = None;
15245 let snapshot = this.buffer.read(cx).read(cx);
15246 let empty_str: Arc<str> = Arc::default();
15247 let mut suffixes_inserted = Vec::new();
15248 let ignore_indent = action.ignore_indent;
15249
15250 fn comment_prefix_range(
15251 snapshot: &MultiBufferSnapshot,
15252 row: MultiBufferRow,
15253 comment_prefix: &str,
15254 comment_prefix_whitespace: &str,
15255 ignore_indent: bool,
15256 ) -> Range<Point> {
15257 let indent_size = if ignore_indent {
15258 0
15259 } else {
15260 snapshot.indent_size_for_line(row).len
15261 };
15262
15263 let start = Point::new(row.0, indent_size);
15264
15265 let mut line_bytes = snapshot
15266 .bytes_in_range(start..snapshot.max_point())
15267 .flatten()
15268 .copied();
15269
15270 // If this line currently begins with the line comment prefix, then record
15271 // the range containing the prefix.
15272 if line_bytes
15273 .by_ref()
15274 .take(comment_prefix.len())
15275 .eq(comment_prefix.bytes())
15276 {
15277 // Include any whitespace that matches the comment prefix.
15278 let matching_whitespace_len = line_bytes
15279 .zip(comment_prefix_whitespace.bytes())
15280 .take_while(|(a, b)| a == b)
15281 .count() as u32;
15282 let end = Point::new(
15283 start.row,
15284 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15285 );
15286 start..end
15287 } else {
15288 start..start
15289 }
15290 }
15291
15292 fn comment_suffix_range(
15293 snapshot: &MultiBufferSnapshot,
15294 row: MultiBufferRow,
15295 comment_suffix: &str,
15296 comment_suffix_has_leading_space: bool,
15297 ) -> Range<Point> {
15298 let end = Point::new(row.0, snapshot.line_len(row));
15299 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15300
15301 let mut line_end_bytes = snapshot
15302 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15303 .flatten()
15304 .copied();
15305
15306 let leading_space_len = if suffix_start_column > 0
15307 && line_end_bytes.next() == Some(b' ')
15308 && comment_suffix_has_leading_space
15309 {
15310 1
15311 } else {
15312 0
15313 };
15314
15315 // If this line currently begins with the line comment prefix, then record
15316 // the range containing the prefix.
15317 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15318 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15319 start..end
15320 } else {
15321 end..end
15322 }
15323 }
15324
15325 // TODO: Handle selections that cross excerpts
15326 for selection in &mut selections {
15327 let start_column = snapshot
15328 .indent_size_for_line(MultiBufferRow(selection.start.row))
15329 .len;
15330 let language = if let Some(language) =
15331 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15332 {
15333 language
15334 } else {
15335 continue;
15336 };
15337
15338 selection_edit_ranges.clear();
15339
15340 // If multiple selections contain a given row, avoid processing that
15341 // row more than once.
15342 let mut start_row = MultiBufferRow(selection.start.row);
15343 if last_toggled_row == Some(start_row) {
15344 start_row = start_row.next_row();
15345 }
15346 let end_row =
15347 if selection.end.row > selection.start.row && selection.end.column == 0 {
15348 MultiBufferRow(selection.end.row - 1)
15349 } else {
15350 MultiBufferRow(selection.end.row)
15351 };
15352 last_toggled_row = Some(end_row);
15353
15354 if start_row > end_row {
15355 continue;
15356 }
15357
15358 // If the language has line comments, toggle those.
15359 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15360
15361 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15362 if ignore_indent {
15363 full_comment_prefixes = full_comment_prefixes
15364 .into_iter()
15365 .map(|s| Arc::from(s.trim_end()))
15366 .collect();
15367 }
15368
15369 if !full_comment_prefixes.is_empty() {
15370 let first_prefix = full_comment_prefixes
15371 .first()
15372 .expect("prefixes is non-empty");
15373 let prefix_trimmed_lengths = full_comment_prefixes
15374 .iter()
15375 .map(|p| p.trim_end_matches(' ').len())
15376 .collect::<SmallVec<[usize; 4]>>();
15377
15378 let mut all_selection_lines_are_comments = true;
15379
15380 for row in start_row.0..=end_row.0 {
15381 let row = MultiBufferRow(row);
15382 if start_row < end_row && snapshot.is_line_blank(row) {
15383 continue;
15384 }
15385
15386 let prefix_range = full_comment_prefixes
15387 .iter()
15388 .zip(prefix_trimmed_lengths.iter().copied())
15389 .map(|(prefix, trimmed_prefix_len)| {
15390 comment_prefix_range(
15391 snapshot.deref(),
15392 row,
15393 &prefix[..trimmed_prefix_len],
15394 &prefix[trimmed_prefix_len..],
15395 ignore_indent,
15396 )
15397 })
15398 .max_by_key(|range| range.end.column - range.start.column)
15399 .expect("prefixes is non-empty");
15400
15401 if prefix_range.is_empty() {
15402 all_selection_lines_are_comments = false;
15403 }
15404
15405 selection_edit_ranges.push(prefix_range);
15406 }
15407
15408 if all_selection_lines_are_comments {
15409 edits.extend(
15410 selection_edit_ranges
15411 .iter()
15412 .cloned()
15413 .map(|range| (range, empty_str.clone())),
15414 );
15415 } else {
15416 let min_column = selection_edit_ranges
15417 .iter()
15418 .map(|range| range.start.column)
15419 .min()
15420 .unwrap_or(0);
15421 edits.extend(selection_edit_ranges.iter().map(|range| {
15422 let position = Point::new(range.start.row, min_column);
15423 (position..position, first_prefix.clone())
15424 }));
15425 }
15426 } else if let Some(BlockCommentConfig {
15427 start: full_comment_prefix,
15428 end: comment_suffix,
15429 ..
15430 }) = language.block_comment()
15431 {
15432 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15433 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15434 let prefix_range = comment_prefix_range(
15435 snapshot.deref(),
15436 start_row,
15437 comment_prefix,
15438 comment_prefix_whitespace,
15439 ignore_indent,
15440 );
15441 let suffix_range = comment_suffix_range(
15442 snapshot.deref(),
15443 end_row,
15444 comment_suffix.trim_start_matches(' '),
15445 comment_suffix.starts_with(' '),
15446 );
15447
15448 if prefix_range.is_empty() || suffix_range.is_empty() {
15449 edits.push((
15450 prefix_range.start..prefix_range.start,
15451 full_comment_prefix.clone(),
15452 ));
15453 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15454 suffixes_inserted.push((end_row, comment_suffix.len()));
15455 } else {
15456 edits.push((prefix_range, empty_str.clone()));
15457 edits.push((suffix_range, empty_str.clone()));
15458 }
15459 } else {
15460 continue;
15461 }
15462 }
15463
15464 drop(snapshot);
15465 this.buffer.update(cx, |buffer, cx| {
15466 buffer.edit(edits, None, cx);
15467 });
15468
15469 // Adjust selections so that they end before any comment suffixes that
15470 // were inserted.
15471 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15472 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15473 let snapshot = this.buffer.read(cx).read(cx);
15474 for selection in &mut selections {
15475 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15476 match row.cmp(&MultiBufferRow(selection.end.row)) {
15477 Ordering::Less => {
15478 suffixes_inserted.next();
15479 continue;
15480 }
15481 Ordering::Greater => break,
15482 Ordering::Equal => {
15483 if selection.end.column == snapshot.line_len(row) {
15484 if selection.is_empty() {
15485 selection.start.column -= suffix_len as u32;
15486 }
15487 selection.end.column -= suffix_len as u32;
15488 }
15489 break;
15490 }
15491 }
15492 }
15493 }
15494
15495 drop(snapshot);
15496 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15497
15498 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15499 let selections_on_single_row = selections.windows(2).all(|selections| {
15500 selections[0].start.row == selections[1].start.row
15501 && selections[0].end.row == selections[1].end.row
15502 && selections[0].start.row == selections[0].end.row
15503 });
15504 let selections_selecting = selections
15505 .iter()
15506 .any(|selection| selection.start != selection.end);
15507 let advance_downwards = action.advance_downwards
15508 && selections_on_single_row
15509 && !selections_selecting
15510 && !matches!(this.mode, EditorMode::SingleLine);
15511
15512 if advance_downwards {
15513 let snapshot = this.buffer.read(cx).snapshot(cx);
15514
15515 this.change_selections(Default::default(), window, cx, |s| {
15516 s.move_cursors_with(|display_snapshot, display_point, _| {
15517 let mut point = display_point.to_point(display_snapshot);
15518 point.row += 1;
15519 point = snapshot.clip_point(point, Bias::Left);
15520 let display_point = point.to_display_point(display_snapshot);
15521 let goal = SelectionGoal::HorizontalPosition(
15522 display_snapshot
15523 .x_for_display_point(display_point, text_layout_details)
15524 .into(),
15525 );
15526 (display_point, goal)
15527 })
15528 });
15529 }
15530 });
15531 }
15532
15533 pub fn select_enclosing_symbol(
15534 &mut self,
15535 _: &SelectEnclosingSymbol,
15536 window: &mut Window,
15537 cx: &mut Context<Self>,
15538 ) {
15539 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15540
15541 let buffer = self.buffer.read(cx).snapshot(cx);
15542 let old_selections = self
15543 .selections
15544 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15545 .into_boxed_slice();
15546
15547 fn update_selection(
15548 selection: &Selection<MultiBufferOffset>,
15549 buffer_snap: &MultiBufferSnapshot,
15550 ) -> Option<Selection<MultiBufferOffset>> {
15551 let cursor = selection.head();
15552 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15553 for symbol in symbols.iter().rev() {
15554 let start = symbol.range.start.to_offset(buffer_snap);
15555 let end = symbol.range.end.to_offset(buffer_snap);
15556 let new_range = start..end;
15557 if start < selection.start || end > selection.end {
15558 return Some(Selection {
15559 id: selection.id,
15560 start: new_range.start,
15561 end: new_range.end,
15562 goal: SelectionGoal::None,
15563 reversed: selection.reversed,
15564 });
15565 }
15566 }
15567 None
15568 }
15569
15570 let mut selected_larger_symbol = false;
15571 let new_selections = old_selections
15572 .iter()
15573 .map(|selection| match update_selection(selection, &buffer) {
15574 Some(new_selection) => {
15575 if new_selection.range() != selection.range() {
15576 selected_larger_symbol = true;
15577 }
15578 new_selection
15579 }
15580 None => selection.clone(),
15581 })
15582 .collect::<Vec<_>>();
15583
15584 if selected_larger_symbol {
15585 self.change_selections(Default::default(), window, cx, |s| {
15586 s.select(new_selections);
15587 });
15588 }
15589 }
15590
15591 pub fn select_larger_syntax_node(
15592 &mut self,
15593 _: &SelectLargerSyntaxNode,
15594 window: &mut Window,
15595 cx: &mut Context<Self>,
15596 ) {
15597 let Some(visible_row_count) = self.visible_row_count() else {
15598 return;
15599 };
15600 let old_selections: Box<[_]> = self
15601 .selections
15602 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15603 .into();
15604 if old_selections.is_empty() {
15605 return;
15606 }
15607
15608 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15609
15610 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15611 let buffer = self.buffer.read(cx).snapshot(cx);
15612
15613 let mut selected_larger_node = false;
15614 let mut new_selections = old_selections
15615 .iter()
15616 .map(|selection| {
15617 let old_range = selection.start..selection.end;
15618
15619 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15620 // manually select word at selection
15621 if ["string_content", "inline"].contains(&node.kind()) {
15622 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15623 // ignore if word is already selected
15624 if !word_range.is_empty() && old_range != word_range {
15625 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15626 // only select word if start and end point belongs to same word
15627 if word_range == last_word_range {
15628 selected_larger_node = true;
15629 return Selection {
15630 id: selection.id,
15631 start: word_range.start,
15632 end: word_range.end,
15633 goal: SelectionGoal::None,
15634 reversed: selection.reversed,
15635 };
15636 }
15637 }
15638 }
15639 }
15640
15641 let mut new_range = old_range.clone();
15642 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15643 new_range = range;
15644 if !node.is_named() {
15645 continue;
15646 }
15647 if !display_map.intersects_fold(new_range.start)
15648 && !display_map.intersects_fold(new_range.end)
15649 {
15650 break;
15651 }
15652 }
15653
15654 selected_larger_node |= new_range != old_range;
15655 Selection {
15656 id: selection.id,
15657 start: new_range.start,
15658 end: new_range.end,
15659 goal: SelectionGoal::None,
15660 reversed: selection.reversed,
15661 }
15662 })
15663 .collect::<Vec<_>>();
15664
15665 if !selected_larger_node {
15666 return; // don't put this call in the history
15667 }
15668
15669 // scroll based on transformation done to the last selection created by the user
15670 let (last_old, last_new) = old_selections
15671 .last()
15672 .zip(new_selections.last().cloned())
15673 .expect("old_selections isn't empty");
15674
15675 // revert selection
15676 let is_selection_reversed = {
15677 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15678 new_selections.last_mut().expect("checked above").reversed =
15679 should_newest_selection_be_reversed;
15680 should_newest_selection_be_reversed
15681 };
15682
15683 if selected_larger_node {
15684 self.select_syntax_node_history.disable_clearing = true;
15685 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15686 s.select(new_selections.clone());
15687 });
15688 self.select_syntax_node_history.disable_clearing = false;
15689 }
15690
15691 let start_row = last_new.start.to_display_point(&display_map).row().0;
15692 let end_row = last_new.end.to_display_point(&display_map).row().0;
15693 let selection_height = end_row - start_row + 1;
15694 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15695
15696 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15697 let scroll_behavior = if fits_on_the_screen {
15698 self.request_autoscroll(Autoscroll::fit(), cx);
15699 SelectSyntaxNodeScrollBehavior::FitSelection
15700 } else if is_selection_reversed {
15701 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15702 SelectSyntaxNodeScrollBehavior::CursorTop
15703 } else {
15704 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15705 SelectSyntaxNodeScrollBehavior::CursorBottom
15706 };
15707
15708 self.select_syntax_node_history.push((
15709 old_selections,
15710 scroll_behavior,
15711 is_selection_reversed,
15712 ));
15713 }
15714
15715 pub fn select_smaller_syntax_node(
15716 &mut self,
15717 _: &SelectSmallerSyntaxNode,
15718 window: &mut Window,
15719 cx: &mut Context<Self>,
15720 ) {
15721 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15722
15723 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15724 self.select_syntax_node_history.pop()
15725 {
15726 if let Some(selection) = selections.last_mut() {
15727 selection.reversed = is_selection_reversed;
15728 }
15729
15730 self.select_syntax_node_history.disable_clearing = true;
15731 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15732 s.select(selections.to_vec());
15733 });
15734 self.select_syntax_node_history.disable_clearing = false;
15735
15736 match scroll_behavior {
15737 SelectSyntaxNodeScrollBehavior::CursorTop => {
15738 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15739 }
15740 SelectSyntaxNodeScrollBehavior::FitSelection => {
15741 self.request_autoscroll(Autoscroll::fit(), cx);
15742 }
15743 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15744 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15745 }
15746 }
15747 }
15748 }
15749
15750 pub fn unwrap_syntax_node(
15751 &mut self,
15752 _: &UnwrapSyntaxNode,
15753 window: &mut Window,
15754 cx: &mut Context<Self>,
15755 ) {
15756 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15757
15758 let buffer = self.buffer.read(cx).snapshot(cx);
15759 let selections = self
15760 .selections
15761 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15762 .into_iter()
15763 // subtracting the offset requires sorting
15764 .sorted_by_key(|i| i.start);
15765
15766 let full_edits = selections
15767 .into_iter()
15768 .filter_map(|selection| {
15769 let child = if selection.is_empty()
15770 && let Some((_, ancestor_range)) =
15771 buffer.syntax_ancestor(selection.start..selection.end)
15772 {
15773 ancestor_range
15774 } else {
15775 selection.range()
15776 };
15777
15778 let mut parent = child.clone();
15779 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15780 parent = ancestor_range;
15781 if parent.start < child.start || parent.end > child.end {
15782 break;
15783 }
15784 }
15785
15786 if parent == child {
15787 return None;
15788 }
15789 let text = buffer.text_for_range(child).collect::<String>();
15790 Some((selection.id, parent, text))
15791 })
15792 .collect::<Vec<_>>();
15793 if full_edits.is_empty() {
15794 return;
15795 }
15796
15797 self.transact(window, cx, |this, window, cx| {
15798 this.buffer.update(cx, |buffer, cx| {
15799 buffer.edit(
15800 full_edits
15801 .iter()
15802 .map(|(_, p, t)| (p.clone(), t.clone()))
15803 .collect::<Vec<_>>(),
15804 None,
15805 cx,
15806 );
15807 });
15808 this.change_selections(Default::default(), window, cx, |s| {
15809 let mut offset = 0;
15810 let mut selections = vec![];
15811 for (id, parent, text) in full_edits {
15812 let start = parent.start - offset;
15813 offset += (parent.end - parent.start) - text.len();
15814 selections.push(Selection {
15815 id,
15816 start,
15817 end: start + text.len(),
15818 reversed: false,
15819 goal: Default::default(),
15820 });
15821 }
15822 s.select(selections);
15823 });
15824 });
15825 }
15826
15827 pub fn select_next_syntax_node(
15828 &mut self,
15829 _: &SelectNextSyntaxNode,
15830 window: &mut Window,
15831 cx: &mut Context<Self>,
15832 ) {
15833 let old_selections: Box<[_]> = self
15834 .selections
15835 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15836 .into();
15837 if old_selections.is_empty() {
15838 return;
15839 }
15840
15841 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15842
15843 let buffer = self.buffer.read(cx).snapshot(cx);
15844 let mut selected_sibling = false;
15845
15846 let new_selections = old_selections
15847 .iter()
15848 .map(|selection| {
15849 let old_range = selection.start..selection.end;
15850
15851 let old_range =
15852 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15853 let excerpt = buffer.excerpt_containing(old_range.clone());
15854
15855 if let Some(mut excerpt) = excerpt
15856 && let Some(node) = excerpt
15857 .buffer()
15858 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
15859 {
15860 let new_range = excerpt.map_range_from_buffer(
15861 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15862 );
15863 selected_sibling = true;
15864 Selection {
15865 id: selection.id,
15866 start: new_range.start,
15867 end: new_range.end,
15868 goal: SelectionGoal::None,
15869 reversed: selection.reversed,
15870 }
15871 } else {
15872 selection.clone()
15873 }
15874 })
15875 .collect::<Vec<_>>();
15876
15877 if selected_sibling {
15878 self.change_selections(
15879 SelectionEffects::scroll(Autoscroll::fit()),
15880 window,
15881 cx,
15882 |s| {
15883 s.select(new_selections);
15884 },
15885 );
15886 }
15887 }
15888
15889 pub fn select_prev_syntax_node(
15890 &mut self,
15891 _: &SelectPreviousSyntaxNode,
15892 window: &mut Window,
15893 cx: &mut Context<Self>,
15894 ) {
15895 let old_selections: Box<[_]> = self
15896 .selections
15897 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15898 .into();
15899 if old_selections.is_empty() {
15900 return;
15901 }
15902
15903 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15904
15905 let buffer = self.buffer.read(cx).snapshot(cx);
15906 let mut selected_sibling = false;
15907
15908 let new_selections = old_selections
15909 .iter()
15910 .map(|selection| {
15911 let old_range = selection.start..selection.end;
15912 let old_range =
15913 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
15914 let excerpt = buffer.excerpt_containing(old_range.clone());
15915
15916 if let Some(mut excerpt) = excerpt
15917 && let Some(node) = excerpt
15918 .buffer()
15919 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
15920 {
15921 let new_range = excerpt.map_range_from_buffer(
15922 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
15923 );
15924 selected_sibling = true;
15925 Selection {
15926 id: selection.id,
15927 start: new_range.start,
15928 end: new_range.end,
15929 goal: SelectionGoal::None,
15930 reversed: selection.reversed,
15931 }
15932 } else {
15933 selection.clone()
15934 }
15935 })
15936 .collect::<Vec<_>>();
15937
15938 if selected_sibling {
15939 self.change_selections(
15940 SelectionEffects::scroll(Autoscroll::fit()),
15941 window,
15942 cx,
15943 |s| {
15944 s.select(new_selections);
15945 },
15946 );
15947 }
15948 }
15949
15950 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15951 if !EditorSettings::get_global(cx).gutter.runnables {
15952 self.clear_tasks();
15953 return Task::ready(());
15954 }
15955 let project = self.project().map(Entity::downgrade);
15956 let task_sources = self.lsp_task_sources(cx);
15957 let multi_buffer = self.buffer.downgrade();
15958 cx.spawn_in(window, async move |editor, cx| {
15959 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15960 let Some(project) = project.and_then(|p| p.upgrade()) else {
15961 return;
15962 };
15963 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15964 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15965 }) else {
15966 return;
15967 };
15968
15969 let hide_runnables = project
15970 .update(cx, |project, _| project.is_via_collab())
15971 .unwrap_or(true);
15972 if hide_runnables {
15973 return;
15974 }
15975 let new_rows =
15976 cx.background_spawn({
15977 let snapshot = display_snapshot.clone();
15978 async move {
15979 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15980 }
15981 })
15982 .await;
15983 let Ok(lsp_tasks) =
15984 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15985 else {
15986 return;
15987 };
15988 let lsp_tasks = lsp_tasks.await;
15989
15990 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15991 lsp_tasks
15992 .into_iter()
15993 .flat_map(|(kind, tasks)| {
15994 tasks.into_iter().filter_map(move |(location, task)| {
15995 Some((kind.clone(), location?, task))
15996 })
15997 })
15998 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15999 let buffer = location.target.buffer;
16000 let buffer_snapshot = buffer.read(cx).snapshot();
16001 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16002 |(excerpt_id, snapshot, _)| {
16003 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16004 display_snapshot
16005 .buffer_snapshot()
16006 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16007 } else {
16008 None
16009 }
16010 },
16011 );
16012 if let Some(offset) = offset {
16013 let task_buffer_range =
16014 location.target.range.to_point(&buffer_snapshot);
16015 let context_buffer_range =
16016 task_buffer_range.to_offset(&buffer_snapshot);
16017 let context_range = BufferOffset(context_buffer_range.start)
16018 ..BufferOffset(context_buffer_range.end);
16019
16020 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16021 .or_insert_with(|| RunnableTasks {
16022 templates: Vec::new(),
16023 offset,
16024 column: task_buffer_range.start.column,
16025 extra_variables: HashMap::default(),
16026 context_range,
16027 })
16028 .templates
16029 .push((kind, task.original_task().clone()));
16030 }
16031
16032 acc
16033 })
16034 }) else {
16035 return;
16036 };
16037
16038 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16039 buffer.language_settings(cx).tasks.prefer_lsp
16040 }) else {
16041 return;
16042 };
16043
16044 let rows = Self::runnable_rows(
16045 project,
16046 display_snapshot,
16047 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16048 new_rows,
16049 cx.clone(),
16050 )
16051 .await;
16052 editor
16053 .update(cx, |editor, _| {
16054 editor.clear_tasks();
16055 for (key, mut value) in rows {
16056 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16057 value.templates.extend(lsp_tasks.templates);
16058 }
16059
16060 editor.insert_tasks(key, value);
16061 }
16062 for (key, value) in lsp_tasks_by_rows {
16063 editor.insert_tasks(key, value);
16064 }
16065 })
16066 .ok();
16067 })
16068 }
16069 fn fetch_runnable_ranges(
16070 snapshot: &DisplaySnapshot,
16071 range: Range<Anchor>,
16072 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16073 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16074 }
16075
16076 fn runnable_rows(
16077 project: Entity<Project>,
16078 snapshot: DisplaySnapshot,
16079 prefer_lsp: bool,
16080 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16081 cx: AsyncWindowContext,
16082 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16083 cx.spawn(async move |cx| {
16084 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16085 for (run_range, mut runnable) in runnable_ranges {
16086 let Some(tasks) = cx
16087 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16088 .ok()
16089 else {
16090 continue;
16091 };
16092 let mut tasks = tasks.await;
16093
16094 if prefer_lsp {
16095 tasks.retain(|(task_kind, _)| {
16096 !matches!(task_kind, TaskSourceKind::Language { .. })
16097 });
16098 }
16099 if tasks.is_empty() {
16100 continue;
16101 }
16102
16103 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16104 let Some(row) = snapshot
16105 .buffer_snapshot()
16106 .buffer_line_for_row(MultiBufferRow(point.row))
16107 .map(|(_, range)| range.start.row)
16108 else {
16109 continue;
16110 };
16111
16112 let context_range =
16113 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16114 runnable_rows.push((
16115 (runnable.buffer_id, row),
16116 RunnableTasks {
16117 templates: tasks,
16118 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16119 context_range,
16120 column: point.column,
16121 extra_variables: runnable.extra_captures,
16122 },
16123 ));
16124 }
16125 runnable_rows
16126 })
16127 }
16128
16129 fn templates_with_tags(
16130 project: &Entity<Project>,
16131 runnable: &mut Runnable,
16132 cx: &mut App,
16133 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16134 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16135 let (worktree_id, file) = project
16136 .buffer_for_id(runnable.buffer, cx)
16137 .and_then(|buffer| buffer.read(cx).file())
16138 .map(|file| (file.worktree_id(cx), file.clone()))
16139 .unzip();
16140
16141 (
16142 project.task_store().read(cx).task_inventory().cloned(),
16143 worktree_id,
16144 file,
16145 )
16146 });
16147
16148 let tags = mem::take(&mut runnable.tags);
16149 let language = runnable.language.clone();
16150 cx.spawn(async move |cx| {
16151 let mut templates_with_tags = Vec::new();
16152 if let Some(inventory) = inventory {
16153 for RunnableTag(tag) in tags {
16154 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16155 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16156 }) else {
16157 return templates_with_tags;
16158 };
16159 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16160 move |(_, template)| {
16161 template.tags.iter().any(|source_tag| source_tag == &tag)
16162 },
16163 ));
16164 }
16165 }
16166 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16167
16168 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16169 // Strongest source wins; if we have worktree tag binding, prefer that to
16170 // global and language bindings;
16171 // if we have a global binding, prefer that to language binding.
16172 let first_mismatch = templates_with_tags
16173 .iter()
16174 .position(|(tag_source, _)| tag_source != leading_tag_source);
16175 if let Some(index) = first_mismatch {
16176 templates_with_tags.truncate(index);
16177 }
16178 }
16179
16180 templates_with_tags
16181 })
16182 }
16183
16184 pub fn move_to_enclosing_bracket(
16185 &mut self,
16186 _: &MoveToEnclosingBracket,
16187 window: &mut Window,
16188 cx: &mut Context<Self>,
16189 ) {
16190 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16191 self.change_selections(Default::default(), window, cx, |s| {
16192 s.move_offsets_with(|snapshot, selection| {
16193 let Some(enclosing_bracket_ranges) =
16194 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16195 else {
16196 return;
16197 };
16198
16199 let mut best_length = usize::MAX;
16200 let mut best_inside = false;
16201 let mut best_in_bracket_range = false;
16202 let mut best_destination = None;
16203 for (open, close) in enclosing_bracket_ranges {
16204 let close = close.to_inclusive();
16205 let length = *close.end() - open.start;
16206 let inside = selection.start >= open.end && selection.end <= *close.start();
16207 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16208 || close.contains(&selection.head());
16209
16210 // If best is next to a bracket and current isn't, skip
16211 if !in_bracket_range && best_in_bracket_range {
16212 continue;
16213 }
16214
16215 // Prefer smaller lengths unless best is inside and current isn't
16216 if length > best_length && (best_inside || !inside) {
16217 continue;
16218 }
16219
16220 best_length = length;
16221 best_inside = inside;
16222 best_in_bracket_range = in_bracket_range;
16223 best_destination = Some(
16224 if close.contains(&selection.start) && close.contains(&selection.end) {
16225 if inside { open.end } else { open.start }
16226 } else if inside {
16227 *close.start()
16228 } else {
16229 *close.end()
16230 },
16231 );
16232 }
16233
16234 if let Some(destination) = best_destination {
16235 selection.collapse_to(destination, SelectionGoal::None);
16236 }
16237 })
16238 });
16239 }
16240
16241 pub fn undo_selection(
16242 &mut self,
16243 _: &UndoSelection,
16244 window: &mut Window,
16245 cx: &mut Context<Self>,
16246 ) {
16247 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16248 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16249 self.selection_history.mode = SelectionHistoryMode::Undoing;
16250 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16251 this.end_selection(window, cx);
16252 this.change_selections(
16253 SelectionEffects::scroll(Autoscroll::newest()),
16254 window,
16255 cx,
16256 |s| s.select_anchors(entry.selections.to_vec()),
16257 );
16258 });
16259 self.selection_history.mode = SelectionHistoryMode::Normal;
16260
16261 self.select_next_state = entry.select_next_state;
16262 self.select_prev_state = entry.select_prev_state;
16263 self.add_selections_state = entry.add_selections_state;
16264 }
16265 }
16266
16267 pub fn redo_selection(
16268 &mut self,
16269 _: &RedoSelection,
16270 window: &mut Window,
16271 cx: &mut Context<Self>,
16272 ) {
16273 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16274 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16275 self.selection_history.mode = SelectionHistoryMode::Redoing;
16276 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16277 this.end_selection(window, cx);
16278 this.change_selections(
16279 SelectionEffects::scroll(Autoscroll::newest()),
16280 window,
16281 cx,
16282 |s| s.select_anchors(entry.selections.to_vec()),
16283 );
16284 });
16285 self.selection_history.mode = SelectionHistoryMode::Normal;
16286
16287 self.select_next_state = entry.select_next_state;
16288 self.select_prev_state = entry.select_prev_state;
16289 self.add_selections_state = entry.add_selections_state;
16290 }
16291 }
16292
16293 pub fn expand_excerpts(
16294 &mut self,
16295 action: &ExpandExcerpts,
16296 _: &mut Window,
16297 cx: &mut Context<Self>,
16298 ) {
16299 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16300 }
16301
16302 pub fn expand_excerpts_down(
16303 &mut self,
16304 action: &ExpandExcerptsDown,
16305 _: &mut Window,
16306 cx: &mut Context<Self>,
16307 ) {
16308 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16309 }
16310
16311 pub fn expand_excerpts_up(
16312 &mut self,
16313 action: &ExpandExcerptsUp,
16314 _: &mut Window,
16315 cx: &mut Context<Self>,
16316 ) {
16317 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16318 }
16319
16320 pub fn expand_excerpts_for_direction(
16321 &mut self,
16322 lines: u32,
16323 direction: ExpandExcerptDirection,
16324
16325 cx: &mut Context<Self>,
16326 ) {
16327 let selections = self.selections.disjoint_anchors_arc();
16328
16329 let lines = if lines == 0 {
16330 EditorSettings::get_global(cx).expand_excerpt_lines
16331 } else {
16332 lines
16333 };
16334
16335 self.buffer.update(cx, |buffer, cx| {
16336 let snapshot = buffer.snapshot(cx);
16337 let mut excerpt_ids = selections
16338 .iter()
16339 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16340 .collect::<Vec<_>>();
16341 excerpt_ids.sort();
16342 excerpt_ids.dedup();
16343 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16344 })
16345 }
16346
16347 pub fn expand_excerpt(
16348 &mut self,
16349 excerpt: ExcerptId,
16350 direction: ExpandExcerptDirection,
16351 window: &mut Window,
16352 cx: &mut Context<Self>,
16353 ) {
16354 let current_scroll_position = self.scroll_position(cx);
16355 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16356 let mut scroll = None;
16357
16358 if direction == ExpandExcerptDirection::Down {
16359 let multi_buffer = self.buffer.read(cx);
16360 let snapshot = multi_buffer.snapshot(cx);
16361 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16362 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16363 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16364 {
16365 let buffer_snapshot = buffer.read(cx).snapshot();
16366 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16367 let last_row = buffer_snapshot.max_point().row;
16368 let lines_below = last_row.saturating_sub(excerpt_end_row);
16369 if lines_below >= lines_to_expand {
16370 scroll = Some(
16371 current_scroll_position
16372 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16373 );
16374 }
16375 }
16376 }
16377 if direction == ExpandExcerptDirection::Up
16378 && self
16379 .buffer
16380 .read(cx)
16381 .snapshot(cx)
16382 .excerpt_before(excerpt)
16383 .is_none()
16384 {
16385 scroll = Some(current_scroll_position);
16386 }
16387
16388 self.buffer.update(cx, |buffer, cx| {
16389 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16390 });
16391
16392 if let Some(new_scroll_position) = scroll {
16393 self.set_scroll_position(new_scroll_position, window, cx);
16394 }
16395 }
16396
16397 pub fn go_to_singleton_buffer_point(
16398 &mut self,
16399 point: Point,
16400 window: &mut Window,
16401 cx: &mut Context<Self>,
16402 ) {
16403 self.go_to_singleton_buffer_range(point..point, window, cx);
16404 }
16405
16406 pub fn go_to_singleton_buffer_range(
16407 &mut self,
16408 range: Range<Point>,
16409 window: &mut Window,
16410 cx: &mut Context<Self>,
16411 ) {
16412 let multibuffer = self.buffer().read(cx);
16413 let Some(buffer) = multibuffer.as_singleton() else {
16414 return;
16415 };
16416 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16417 return;
16418 };
16419 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16420 return;
16421 };
16422 self.change_selections(
16423 SelectionEffects::default().nav_history(true),
16424 window,
16425 cx,
16426 |s| s.select_anchor_ranges([start..end]),
16427 );
16428 }
16429
16430 pub fn go_to_diagnostic(
16431 &mut self,
16432 action: &GoToDiagnostic,
16433 window: &mut Window,
16434 cx: &mut Context<Self>,
16435 ) {
16436 if !self.diagnostics_enabled() {
16437 return;
16438 }
16439 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16440 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16441 }
16442
16443 pub fn go_to_prev_diagnostic(
16444 &mut self,
16445 action: &GoToPreviousDiagnostic,
16446 window: &mut Window,
16447 cx: &mut Context<Self>,
16448 ) {
16449 if !self.diagnostics_enabled() {
16450 return;
16451 }
16452 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16453 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16454 }
16455
16456 pub fn go_to_diagnostic_impl(
16457 &mut self,
16458 direction: Direction,
16459 severity: GoToDiagnosticSeverityFilter,
16460 window: &mut Window,
16461 cx: &mut Context<Self>,
16462 ) {
16463 let buffer = self.buffer.read(cx).snapshot(cx);
16464 let selection = self
16465 .selections
16466 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16467
16468 let mut active_group_id = None;
16469 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16470 && active_group.active_range.start.to_offset(&buffer) == selection.start
16471 {
16472 active_group_id = Some(active_group.group_id);
16473 }
16474
16475 fn filtered<'a>(
16476 severity: GoToDiagnosticSeverityFilter,
16477 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16478 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16479 diagnostics
16480 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16481 .filter(|entry| entry.range.start != entry.range.end)
16482 .filter(|entry| !entry.diagnostic.is_unnecessary)
16483 }
16484
16485 let before = filtered(
16486 severity,
16487 buffer
16488 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16489 .filter(|entry| entry.range.start <= selection.start),
16490 );
16491 let after = filtered(
16492 severity,
16493 buffer
16494 .diagnostics_in_range(selection.start..buffer.len())
16495 .filter(|entry| entry.range.start >= selection.start),
16496 );
16497
16498 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16499 if direction == Direction::Prev {
16500 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16501 {
16502 for diagnostic in prev_diagnostics.into_iter().rev() {
16503 if diagnostic.range.start != selection.start
16504 || active_group_id
16505 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16506 {
16507 found = Some(diagnostic);
16508 break 'outer;
16509 }
16510 }
16511 }
16512 } else {
16513 for diagnostic in after.chain(before) {
16514 if diagnostic.range.start != selection.start
16515 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16516 {
16517 found = Some(diagnostic);
16518 break;
16519 }
16520 }
16521 }
16522 let Some(next_diagnostic) = found else {
16523 return;
16524 };
16525
16526 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16527 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16528 return;
16529 };
16530 let snapshot = self.snapshot(window, cx);
16531 if snapshot.intersects_fold(next_diagnostic.range.start) {
16532 self.unfold_ranges(
16533 std::slice::from_ref(&next_diagnostic.range),
16534 true,
16535 false,
16536 cx,
16537 );
16538 }
16539 self.change_selections(Default::default(), window, cx, |s| {
16540 s.select_ranges(vec![
16541 next_diagnostic.range.start..next_diagnostic.range.start,
16542 ])
16543 });
16544 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16545 self.refresh_edit_prediction(false, true, window, cx);
16546 }
16547
16548 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16550 let snapshot = self.snapshot(window, cx);
16551 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16552 self.go_to_hunk_before_or_after_position(
16553 &snapshot,
16554 selection.head(),
16555 Direction::Next,
16556 window,
16557 cx,
16558 );
16559 }
16560
16561 pub fn go_to_hunk_before_or_after_position(
16562 &mut self,
16563 snapshot: &EditorSnapshot,
16564 position: Point,
16565 direction: Direction,
16566 window: &mut Window,
16567 cx: &mut Context<Editor>,
16568 ) {
16569 let row = if direction == Direction::Next {
16570 self.hunk_after_position(snapshot, position)
16571 .map(|hunk| hunk.row_range.start)
16572 } else {
16573 self.hunk_before_position(snapshot, position)
16574 };
16575
16576 if let Some(row) = row {
16577 let destination = Point::new(row.0, 0);
16578 let autoscroll = Autoscroll::center();
16579
16580 self.unfold_ranges(&[destination..destination], false, false, cx);
16581 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16582 s.select_ranges([destination..destination]);
16583 });
16584 }
16585 }
16586
16587 fn hunk_after_position(
16588 &mut self,
16589 snapshot: &EditorSnapshot,
16590 position: Point,
16591 ) -> Option<MultiBufferDiffHunk> {
16592 snapshot
16593 .buffer_snapshot()
16594 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16595 .find(|hunk| hunk.row_range.start.0 > position.row)
16596 .or_else(|| {
16597 snapshot
16598 .buffer_snapshot()
16599 .diff_hunks_in_range(Point::zero()..position)
16600 .find(|hunk| hunk.row_range.end.0 < position.row)
16601 })
16602 }
16603
16604 fn go_to_prev_hunk(
16605 &mut self,
16606 _: &GoToPreviousHunk,
16607 window: &mut Window,
16608 cx: &mut Context<Self>,
16609 ) {
16610 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16611 let snapshot = self.snapshot(window, cx);
16612 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16613 self.go_to_hunk_before_or_after_position(
16614 &snapshot,
16615 selection.head(),
16616 Direction::Prev,
16617 window,
16618 cx,
16619 );
16620 }
16621
16622 fn hunk_before_position(
16623 &mut self,
16624 snapshot: &EditorSnapshot,
16625 position: Point,
16626 ) -> Option<MultiBufferRow> {
16627 snapshot
16628 .buffer_snapshot()
16629 .diff_hunk_before(position)
16630 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16631 }
16632
16633 fn go_to_next_change(
16634 &mut self,
16635 _: &GoToNextChange,
16636 window: &mut Window,
16637 cx: &mut Context<Self>,
16638 ) {
16639 if let Some(selections) = self
16640 .change_list
16641 .next_change(1, Direction::Next)
16642 .map(|s| s.to_vec())
16643 {
16644 self.change_selections(Default::default(), window, cx, |s| {
16645 let map = s.display_snapshot();
16646 s.select_display_ranges(selections.iter().map(|a| {
16647 let point = a.to_display_point(&map);
16648 point..point
16649 }))
16650 })
16651 }
16652 }
16653
16654 fn go_to_previous_change(
16655 &mut self,
16656 _: &GoToPreviousChange,
16657 window: &mut Window,
16658 cx: &mut Context<Self>,
16659 ) {
16660 if let Some(selections) = self
16661 .change_list
16662 .next_change(1, Direction::Prev)
16663 .map(|s| s.to_vec())
16664 {
16665 self.change_selections(Default::default(), window, cx, |s| {
16666 let map = s.display_snapshot();
16667 s.select_display_ranges(selections.iter().map(|a| {
16668 let point = a.to_display_point(&map);
16669 point..point
16670 }))
16671 })
16672 }
16673 }
16674
16675 pub fn go_to_next_document_highlight(
16676 &mut self,
16677 _: &GoToNextDocumentHighlight,
16678 window: &mut Window,
16679 cx: &mut Context<Self>,
16680 ) {
16681 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16682 }
16683
16684 pub fn go_to_prev_document_highlight(
16685 &mut self,
16686 _: &GoToPreviousDocumentHighlight,
16687 window: &mut Window,
16688 cx: &mut Context<Self>,
16689 ) {
16690 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16691 }
16692
16693 pub fn go_to_document_highlight_before_or_after_position(
16694 &mut self,
16695 direction: Direction,
16696 window: &mut Window,
16697 cx: &mut Context<Editor>,
16698 ) {
16699 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16700 let snapshot = self.snapshot(window, cx);
16701 let buffer = &snapshot.buffer_snapshot();
16702 let position = self
16703 .selections
16704 .newest::<Point>(&snapshot.display_snapshot)
16705 .head();
16706 let anchor_position = buffer.anchor_after(position);
16707
16708 // Get all document highlights (both read and write)
16709 let mut all_highlights = Vec::new();
16710
16711 if let Some((_, read_highlights)) = self
16712 .background_highlights
16713 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16714 {
16715 all_highlights.extend(read_highlights.iter());
16716 }
16717
16718 if let Some((_, write_highlights)) = self
16719 .background_highlights
16720 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16721 {
16722 all_highlights.extend(write_highlights.iter());
16723 }
16724
16725 if all_highlights.is_empty() {
16726 return;
16727 }
16728
16729 // Sort highlights by position
16730 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16731
16732 let target_highlight = match direction {
16733 Direction::Next => {
16734 // Find the first highlight after the current position
16735 all_highlights
16736 .iter()
16737 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16738 }
16739 Direction::Prev => {
16740 // Find the last highlight before the current position
16741 all_highlights
16742 .iter()
16743 .rev()
16744 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16745 }
16746 };
16747
16748 if let Some(highlight) = target_highlight {
16749 let destination = highlight.start.to_point(buffer);
16750 let autoscroll = Autoscroll::center();
16751
16752 self.unfold_ranges(&[destination..destination], false, false, cx);
16753 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16754 s.select_ranges([destination..destination]);
16755 });
16756 }
16757 }
16758
16759 fn go_to_line<T: 'static>(
16760 &mut self,
16761 position: Anchor,
16762 highlight_color: Option<Hsla>,
16763 window: &mut Window,
16764 cx: &mut Context<Self>,
16765 ) {
16766 let snapshot = self.snapshot(window, cx).display_snapshot;
16767 let position = position.to_point(&snapshot.buffer_snapshot());
16768 let start = snapshot
16769 .buffer_snapshot()
16770 .clip_point(Point::new(position.row, 0), Bias::Left);
16771 let end = start + Point::new(1, 0);
16772 let start = snapshot.buffer_snapshot().anchor_before(start);
16773 let end = snapshot.buffer_snapshot().anchor_before(end);
16774
16775 self.highlight_rows::<T>(
16776 start..end,
16777 highlight_color
16778 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16779 Default::default(),
16780 cx,
16781 );
16782
16783 if self.buffer.read(cx).is_singleton() {
16784 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16785 }
16786 }
16787
16788 pub fn go_to_definition(
16789 &mut self,
16790 _: &GoToDefinition,
16791 window: &mut Window,
16792 cx: &mut Context<Self>,
16793 ) -> Task<Result<Navigated>> {
16794 let definition =
16795 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16796 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16797 cx.spawn_in(window, async move |editor, cx| {
16798 if definition.await? == Navigated::Yes {
16799 return Ok(Navigated::Yes);
16800 }
16801 match fallback_strategy {
16802 GoToDefinitionFallback::None => Ok(Navigated::No),
16803 GoToDefinitionFallback::FindAllReferences => {
16804 match editor.update_in(cx, |editor, window, cx| {
16805 editor.find_all_references(&FindAllReferences, window, cx)
16806 })? {
16807 Some(references) => references.await,
16808 None => Ok(Navigated::No),
16809 }
16810 }
16811 }
16812 })
16813 }
16814
16815 pub fn go_to_declaration(
16816 &mut self,
16817 _: &GoToDeclaration,
16818 window: &mut Window,
16819 cx: &mut Context<Self>,
16820 ) -> Task<Result<Navigated>> {
16821 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16822 }
16823
16824 pub fn go_to_declaration_split(
16825 &mut self,
16826 _: &GoToDeclaration,
16827 window: &mut Window,
16828 cx: &mut Context<Self>,
16829 ) -> Task<Result<Navigated>> {
16830 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16831 }
16832
16833 pub fn go_to_implementation(
16834 &mut self,
16835 _: &GoToImplementation,
16836 window: &mut Window,
16837 cx: &mut Context<Self>,
16838 ) -> Task<Result<Navigated>> {
16839 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16840 }
16841
16842 pub fn go_to_implementation_split(
16843 &mut self,
16844 _: &GoToImplementationSplit,
16845 window: &mut Window,
16846 cx: &mut Context<Self>,
16847 ) -> Task<Result<Navigated>> {
16848 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16849 }
16850
16851 pub fn go_to_type_definition(
16852 &mut self,
16853 _: &GoToTypeDefinition,
16854 window: &mut Window,
16855 cx: &mut Context<Self>,
16856 ) -> Task<Result<Navigated>> {
16857 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16858 }
16859
16860 pub fn go_to_definition_split(
16861 &mut self,
16862 _: &GoToDefinitionSplit,
16863 window: &mut Window,
16864 cx: &mut Context<Self>,
16865 ) -> Task<Result<Navigated>> {
16866 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16867 }
16868
16869 pub fn go_to_type_definition_split(
16870 &mut self,
16871 _: &GoToTypeDefinitionSplit,
16872 window: &mut Window,
16873 cx: &mut Context<Self>,
16874 ) -> Task<Result<Navigated>> {
16875 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16876 }
16877
16878 fn go_to_definition_of_kind(
16879 &mut self,
16880 kind: GotoDefinitionKind,
16881 split: bool,
16882 window: &mut Window,
16883 cx: &mut Context<Self>,
16884 ) -> Task<Result<Navigated>> {
16885 let Some(provider) = self.semantics_provider.clone() else {
16886 return Task::ready(Ok(Navigated::No));
16887 };
16888 let head = self
16889 .selections
16890 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
16891 .head();
16892 let buffer = self.buffer.read(cx);
16893 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16894 return Task::ready(Ok(Navigated::No));
16895 };
16896 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16897 return Task::ready(Ok(Navigated::No));
16898 };
16899
16900 cx.spawn_in(window, async move |editor, cx| {
16901 let Some(definitions) = definitions.await? else {
16902 return Ok(Navigated::No);
16903 };
16904 let navigated = editor
16905 .update_in(cx, |editor, window, cx| {
16906 editor.navigate_to_hover_links(
16907 Some(kind),
16908 definitions
16909 .into_iter()
16910 .filter(|location| {
16911 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16912 })
16913 .map(HoverLink::Text)
16914 .collect::<Vec<_>>(),
16915 split,
16916 window,
16917 cx,
16918 )
16919 })?
16920 .await?;
16921 anyhow::Ok(navigated)
16922 })
16923 }
16924
16925 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16926 let selection = self.selections.newest_anchor();
16927 let head = selection.head();
16928 let tail = selection.tail();
16929
16930 let Some((buffer, start_position)) =
16931 self.buffer.read(cx).text_anchor_for_position(head, cx)
16932 else {
16933 return;
16934 };
16935
16936 let end_position = if head != tail {
16937 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16938 return;
16939 };
16940 Some(pos)
16941 } else {
16942 None
16943 };
16944
16945 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16946 let url = if let Some(end_pos) = end_position {
16947 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16948 } else {
16949 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16950 };
16951
16952 if let Some(url) = url {
16953 cx.update(|window, cx| {
16954 if parse_zed_link(&url, cx).is_some() {
16955 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16956 } else {
16957 cx.open_url(&url);
16958 }
16959 })?;
16960 }
16961
16962 anyhow::Ok(())
16963 });
16964
16965 url_finder.detach();
16966 }
16967
16968 pub fn open_selected_filename(
16969 &mut self,
16970 _: &OpenSelectedFilename,
16971 window: &mut Window,
16972 cx: &mut Context<Self>,
16973 ) {
16974 let Some(workspace) = self.workspace() else {
16975 return;
16976 };
16977
16978 let position = self.selections.newest_anchor().head();
16979
16980 let Some((buffer, buffer_position)) =
16981 self.buffer.read(cx).text_anchor_for_position(position, cx)
16982 else {
16983 return;
16984 };
16985
16986 let project = self.project.clone();
16987
16988 cx.spawn_in(window, async move |_, cx| {
16989 let result = find_file(&buffer, project, buffer_position, cx).await;
16990
16991 if let Some((_, path)) = result {
16992 workspace
16993 .update_in(cx, |workspace, window, cx| {
16994 workspace.open_resolved_path(path, window, cx)
16995 })?
16996 .await?;
16997 }
16998 anyhow::Ok(())
16999 })
17000 .detach();
17001 }
17002
17003 pub(crate) fn navigate_to_hover_links(
17004 &mut self,
17005 kind: Option<GotoDefinitionKind>,
17006 definitions: Vec<HoverLink>,
17007 split: bool,
17008 window: &mut Window,
17009 cx: &mut Context<Editor>,
17010 ) -> Task<Result<Navigated>> {
17011 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17012 let mut first_url_or_file = None;
17013 let definitions: Vec<_> = definitions
17014 .into_iter()
17015 .filter_map(|def| match def {
17016 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17017 HoverLink::InlayHint(lsp_location, server_id) => {
17018 let computation =
17019 self.compute_target_location(lsp_location, server_id, window, cx);
17020 Some(cx.background_spawn(computation))
17021 }
17022 HoverLink::Url(url) => {
17023 first_url_or_file = Some(Either::Left(url));
17024 None
17025 }
17026 HoverLink::File(path) => {
17027 first_url_or_file = Some(Either::Right(path));
17028 None
17029 }
17030 })
17031 .collect();
17032
17033 let Some(workspace) = self.workspace() else {
17034 return Task::ready(Ok(Navigated::No));
17035 };
17036
17037 cx.spawn_in(window, async move |editor, cx| {
17038 let locations: Vec<Location> = future::join_all(definitions)
17039 .await
17040 .into_iter()
17041 .filter_map(|location| location.transpose())
17042 .collect::<Result<_>>()
17043 .context("location tasks")?;
17044 let mut locations = cx.update(|_, cx| {
17045 locations
17046 .into_iter()
17047 .map(|location| {
17048 let buffer = location.buffer.read(cx);
17049 (location.buffer, location.range.to_point(buffer))
17050 })
17051 .into_group_map()
17052 })?;
17053 let mut num_locations = 0;
17054 for ranges in locations.values_mut() {
17055 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17056 ranges.dedup();
17057 num_locations += ranges.len();
17058 }
17059
17060 if num_locations > 1 {
17061 let tab_kind = match kind {
17062 Some(GotoDefinitionKind::Implementation) => "Implementations",
17063 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17064 Some(GotoDefinitionKind::Declaration) => "Declarations",
17065 Some(GotoDefinitionKind::Type) => "Types",
17066 };
17067 let title = editor
17068 .update_in(cx, |_, _, cx| {
17069 let target = locations
17070 .iter()
17071 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17072 .map(|(buffer, location)| {
17073 buffer
17074 .read(cx)
17075 .text_for_range(location.clone())
17076 .collect::<String>()
17077 })
17078 .filter(|text| !text.contains('\n'))
17079 .unique()
17080 .take(3)
17081 .join(", ");
17082 if target.is_empty() {
17083 tab_kind.to_owned()
17084 } else {
17085 format!("{tab_kind} for {target}")
17086 }
17087 })
17088 .context("buffer title")?;
17089
17090 let opened = workspace
17091 .update_in(cx, |workspace, window, cx| {
17092 let allow_preview = PreviewTabsSettings::get_global(cx)
17093 .enable_preview_multibuffer_from_code_navigation;
17094 Self::open_locations_in_multibuffer(
17095 workspace,
17096 locations,
17097 title,
17098 split,
17099 allow_preview,
17100 MultibufferSelectionMode::First,
17101 window,
17102 cx,
17103 )
17104 })
17105 .is_ok();
17106
17107 anyhow::Ok(Navigated::from_bool(opened))
17108 } else if num_locations == 0 {
17109 // If there is one url or file, open it directly
17110 match first_url_or_file {
17111 Some(Either::Left(url)) => {
17112 cx.update(|_, cx| cx.open_url(&url))?;
17113 Ok(Navigated::Yes)
17114 }
17115 Some(Either::Right(path)) => {
17116 // TODO(andrew): respect preview tab settings
17117 // `enable_keep_preview_on_code_navigation` and
17118 // `enable_preview_file_from_code_navigation`
17119 workspace
17120 .update_in(cx, |workspace, window, cx| {
17121 workspace.open_resolved_path(path, window, cx)
17122 })?
17123 .await?;
17124 Ok(Navigated::Yes)
17125 }
17126 None => Ok(Navigated::No),
17127 }
17128 } else {
17129 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17130 let target_range = target_ranges.first().unwrap().clone();
17131
17132 editor.update_in(cx, |editor, window, cx| {
17133 let range = target_range.to_point(target_buffer.read(cx));
17134 let range = editor.range_for_match(&range);
17135 let range = collapse_multiline_range(range);
17136
17137 if !split
17138 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17139 {
17140 editor.go_to_singleton_buffer_range(range, window, cx);
17141 } else {
17142 let pane = workspace.read(cx).active_pane().clone();
17143 window.defer(cx, move |window, cx| {
17144 let target_editor: Entity<Self> =
17145 workspace.update(cx, |workspace, cx| {
17146 let pane = if split {
17147 workspace.adjacent_pane(window, cx)
17148 } else {
17149 workspace.active_pane().clone()
17150 };
17151
17152 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17153 let keep_old_preview = preview_tabs_settings
17154 .enable_keep_preview_on_code_navigation;
17155 let allow_new_preview = preview_tabs_settings
17156 .enable_preview_file_from_code_navigation;
17157
17158 workspace.open_project_item(
17159 pane,
17160 target_buffer.clone(),
17161 true,
17162 true,
17163 keep_old_preview,
17164 allow_new_preview,
17165 window,
17166 cx,
17167 )
17168 });
17169 target_editor.update(cx, |target_editor, cx| {
17170 // When selecting a definition in a different buffer, disable the nav history
17171 // to avoid creating a history entry at the previous cursor location.
17172 pane.update(cx, |pane, _| pane.disable_history());
17173 target_editor.go_to_singleton_buffer_range(range, window, cx);
17174 pane.update(cx, |pane, _| pane.enable_history());
17175 });
17176 });
17177 }
17178 Navigated::Yes
17179 })
17180 }
17181 })
17182 }
17183
17184 fn compute_target_location(
17185 &self,
17186 lsp_location: lsp::Location,
17187 server_id: LanguageServerId,
17188 window: &mut Window,
17189 cx: &mut Context<Self>,
17190 ) -> Task<anyhow::Result<Option<Location>>> {
17191 let Some(project) = self.project.clone() else {
17192 return Task::ready(Ok(None));
17193 };
17194
17195 cx.spawn_in(window, async move |editor, cx| {
17196 let location_task = editor.update(cx, |_, cx| {
17197 project.update(cx, |project, cx| {
17198 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17199 })
17200 })?;
17201 let location = Some({
17202 let target_buffer_handle = location_task.await.context("open local buffer")?;
17203 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17204 let target_start = target_buffer
17205 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17206 let target_end = target_buffer
17207 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17208 target_buffer.anchor_after(target_start)
17209 ..target_buffer.anchor_before(target_end)
17210 })?;
17211 Location {
17212 buffer: target_buffer_handle,
17213 range,
17214 }
17215 });
17216 Ok(location)
17217 })
17218 }
17219
17220 fn go_to_next_reference(
17221 &mut self,
17222 _: &GoToNextReference,
17223 window: &mut Window,
17224 cx: &mut Context<Self>,
17225 ) {
17226 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17227 if let Some(task) = task {
17228 task.detach();
17229 };
17230 }
17231
17232 fn go_to_prev_reference(
17233 &mut self,
17234 _: &GoToPreviousReference,
17235 window: &mut Window,
17236 cx: &mut Context<Self>,
17237 ) {
17238 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17239 if let Some(task) = task {
17240 task.detach();
17241 };
17242 }
17243
17244 pub fn go_to_reference_before_or_after_position(
17245 &mut self,
17246 direction: Direction,
17247 count: usize,
17248 window: &mut Window,
17249 cx: &mut Context<Self>,
17250 ) -> Option<Task<Result<()>>> {
17251 let selection = self.selections.newest_anchor();
17252 let head = selection.head();
17253
17254 let multi_buffer = self.buffer.read(cx);
17255
17256 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17257 let workspace = self.workspace()?;
17258 let project = workspace.read(cx).project().clone();
17259 let references =
17260 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17261 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17262 let Some(locations) = references.await? else {
17263 return Ok(());
17264 };
17265
17266 if locations.is_empty() {
17267 // totally normal - the cursor may be on something which is not
17268 // a symbol (e.g. a keyword)
17269 log::info!("no references found under cursor");
17270 return Ok(());
17271 }
17272
17273 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17274
17275 let (locations, current_location_index) =
17276 multi_buffer.update(cx, |multi_buffer, cx| {
17277 let mut locations = locations
17278 .into_iter()
17279 .filter_map(|loc| {
17280 let start = multi_buffer.buffer_anchor_to_anchor(
17281 &loc.buffer,
17282 loc.range.start,
17283 cx,
17284 )?;
17285 let end = multi_buffer.buffer_anchor_to_anchor(
17286 &loc.buffer,
17287 loc.range.end,
17288 cx,
17289 )?;
17290 Some(start..end)
17291 })
17292 .collect::<Vec<_>>();
17293
17294 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17295 // There is an O(n) implementation, but given this list will be
17296 // small (usually <100 items), the extra O(log(n)) factor isn't
17297 // worth the (surprisingly large amount of) extra complexity.
17298 locations
17299 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17300
17301 let head_offset = head.to_offset(&multi_buffer_snapshot);
17302
17303 let current_location_index = locations.iter().position(|loc| {
17304 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17305 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17306 });
17307
17308 (locations, current_location_index)
17309 })?;
17310
17311 let Some(current_location_index) = current_location_index else {
17312 // This indicates something has gone wrong, because we already
17313 // handle the "no references" case above
17314 log::error!(
17315 "failed to find current reference under cursor. Total references: {}",
17316 locations.len()
17317 );
17318 return Ok(());
17319 };
17320
17321 let destination_location_index = match direction {
17322 Direction::Next => (current_location_index + count) % locations.len(),
17323 Direction::Prev => {
17324 (current_location_index + locations.len() - count % locations.len())
17325 % locations.len()
17326 }
17327 };
17328
17329 // TODO(cameron): is this needed?
17330 // the thinking is to avoid "jumping to the current location" (avoid
17331 // polluting "jumplist" in vim terms)
17332 if current_location_index == destination_location_index {
17333 return Ok(());
17334 }
17335
17336 let Range { start, end } = locations[destination_location_index];
17337
17338 editor.update_in(cx, |editor, window, cx| {
17339 let effects = SelectionEffects::default();
17340
17341 editor.unfold_ranges(&[start..end], false, false, cx);
17342 editor.change_selections(effects, window, cx, |s| {
17343 s.select_ranges([start..start]);
17344 });
17345 })?;
17346
17347 Ok(())
17348 }))
17349 }
17350
17351 pub fn find_all_references(
17352 &mut self,
17353 _: &FindAllReferences,
17354 window: &mut Window,
17355 cx: &mut Context<Self>,
17356 ) -> Option<Task<Result<Navigated>>> {
17357 let selection = self
17358 .selections
17359 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
17360 let multi_buffer = self.buffer.read(cx);
17361 let head = selection.head();
17362
17363 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17364 let head_anchor = multi_buffer_snapshot.anchor_at(
17365 head,
17366 if head < selection.tail() {
17367 Bias::Right
17368 } else {
17369 Bias::Left
17370 },
17371 );
17372
17373 match self
17374 .find_all_references_task_sources
17375 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17376 {
17377 Ok(_) => {
17378 log::info!(
17379 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17380 );
17381 return None;
17382 }
17383 Err(i) => {
17384 self.find_all_references_task_sources.insert(i, head_anchor);
17385 }
17386 }
17387
17388 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17389 let workspace = self.workspace()?;
17390 let project = workspace.read(cx).project().clone();
17391 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17392 Some(cx.spawn_in(window, async move |editor, cx| {
17393 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17394 if let Ok(i) = editor
17395 .find_all_references_task_sources
17396 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17397 {
17398 editor.find_all_references_task_sources.remove(i);
17399 }
17400 });
17401
17402 let Some(locations) = references.await? else {
17403 return anyhow::Ok(Navigated::No);
17404 };
17405 let mut locations = cx.update(|_, cx| {
17406 locations
17407 .into_iter()
17408 .map(|location| {
17409 let buffer = location.buffer.read(cx);
17410 (location.buffer, location.range.to_point(buffer))
17411 })
17412 .into_group_map()
17413 })?;
17414 if locations.is_empty() {
17415 return anyhow::Ok(Navigated::No);
17416 }
17417 for ranges in locations.values_mut() {
17418 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17419 ranges.dedup();
17420 }
17421
17422 workspace.update_in(cx, |workspace, window, cx| {
17423 let target = locations
17424 .iter()
17425 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17426 .map(|(buffer, location)| {
17427 buffer
17428 .read(cx)
17429 .text_for_range(location.clone())
17430 .collect::<String>()
17431 })
17432 .filter(|text| !text.contains('\n'))
17433 .unique()
17434 .take(3)
17435 .join(", ");
17436 let title = if target.is_empty() {
17437 "References".to_owned()
17438 } else {
17439 format!("References to {target}")
17440 };
17441 let allow_preview = PreviewTabsSettings::get_global(cx)
17442 .enable_preview_multibuffer_from_code_navigation;
17443 Self::open_locations_in_multibuffer(
17444 workspace,
17445 locations,
17446 title,
17447 false,
17448 allow_preview,
17449 MultibufferSelectionMode::First,
17450 window,
17451 cx,
17452 );
17453 Navigated::Yes
17454 })
17455 }))
17456 }
17457
17458 /// Opens a multibuffer with the given project locations in it
17459 pub fn open_locations_in_multibuffer(
17460 workspace: &mut Workspace,
17461 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17462 title: String,
17463 split: bool,
17464 allow_preview: bool,
17465 multibuffer_selection_mode: MultibufferSelectionMode,
17466 window: &mut Window,
17467 cx: &mut Context<Workspace>,
17468 ) {
17469 if locations.is_empty() {
17470 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17471 return;
17472 }
17473
17474 let capability = workspace.project().read(cx).capability();
17475 let mut ranges = <Vec<Range<Anchor>>>::new();
17476
17477 // a key to find existing multibuffer editors with the same set of locations
17478 // to prevent us from opening more and more multibuffer tabs for searches and the like
17479 let mut key = (title.clone(), vec![]);
17480 let excerpt_buffer = cx.new(|cx| {
17481 let key = &mut key.1;
17482 let mut multibuffer = MultiBuffer::new(capability);
17483 for (buffer, mut ranges_for_buffer) in locations {
17484 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17485 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17486 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17487 PathKey::for_buffer(&buffer, cx),
17488 buffer.clone(),
17489 ranges_for_buffer,
17490 multibuffer_context_lines(cx),
17491 cx,
17492 );
17493 ranges.extend(new_ranges)
17494 }
17495
17496 multibuffer.with_title(title)
17497 });
17498 let existing = workspace.active_pane().update(cx, |pane, cx| {
17499 pane.items()
17500 .filter_map(|item| item.downcast::<Editor>())
17501 .find(|editor| {
17502 editor
17503 .read(cx)
17504 .lookup_key
17505 .as_ref()
17506 .and_then(|it| {
17507 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17508 })
17509 .is_some_and(|it| *it == key)
17510 })
17511 });
17512 let was_existing = existing.is_some();
17513 let editor = existing.unwrap_or_else(|| {
17514 cx.new(|cx| {
17515 let mut editor = Editor::for_multibuffer(
17516 excerpt_buffer,
17517 Some(workspace.project().clone()),
17518 window,
17519 cx,
17520 );
17521 editor.lookup_key = Some(Box::new(key));
17522 editor
17523 })
17524 });
17525 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17526 MultibufferSelectionMode::First => {
17527 if let Some(first_range) = ranges.first() {
17528 editor.change_selections(
17529 SelectionEffects::no_scroll(),
17530 window,
17531 cx,
17532 |selections| {
17533 selections.clear_disjoint();
17534 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17535 },
17536 );
17537 }
17538 editor.highlight_background::<Self>(
17539 &ranges,
17540 |_, theme| theme.colors().editor_highlighted_line_background,
17541 cx,
17542 );
17543 }
17544 MultibufferSelectionMode::All => {
17545 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17546 selections.clear_disjoint();
17547 selections.select_anchor_ranges(ranges);
17548 });
17549 }
17550 });
17551
17552 let item = Box::new(editor);
17553
17554 let pane = if split {
17555 workspace.adjacent_pane(window, cx)
17556 } else {
17557 workspace.active_pane().clone()
17558 };
17559 let activate_pane = split;
17560
17561 let mut destination_index = None;
17562 pane.update(cx, |pane, cx| {
17563 if allow_preview && !was_existing {
17564 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17565 }
17566 if was_existing && !allow_preview {
17567 pane.unpreview_item_if_preview(item.item_id());
17568 }
17569 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17570 });
17571 }
17572
17573 pub fn rename(
17574 &mut self,
17575 _: &Rename,
17576 window: &mut Window,
17577 cx: &mut Context<Self>,
17578 ) -> Option<Task<Result<()>>> {
17579 use language::ToOffset as _;
17580
17581 let provider = self.semantics_provider.clone()?;
17582 let selection = self.selections.newest_anchor().clone();
17583 let (cursor_buffer, cursor_buffer_position) = self
17584 .buffer
17585 .read(cx)
17586 .text_anchor_for_position(selection.head(), cx)?;
17587 let (tail_buffer, cursor_buffer_position_end) = self
17588 .buffer
17589 .read(cx)
17590 .text_anchor_for_position(selection.tail(), cx)?;
17591 if tail_buffer != cursor_buffer {
17592 return None;
17593 }
17594
17595 let snapshot = cursor_buffer.read(cx).snapshot();
17596 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17597 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17598 let prepare_rename = provider
17599 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17600 .unwrap_or_else(|| Task::ready(Ok(None)));
17601 drop(snapshot);
17602
17603 Some(cx.spawn_in(window, async move |this, cx| {
17604 let rename_range = if let Some(range) = prepare_rename.await? {
17605 Some(range)
17606 } else {
17607 this.update(cx, |this, cx| {
17608 let buffer = this.buffer.read(cx).snapshot(cx);
17609 let mut buffer_highlights = this
17610 .document_highlights_for_position(selection.head(), &buffer)
17611 .filter(|highlight| {
17612 highlight.start.excerpt_id == selection.head().excerpt_id
17613 && highlight.end.excerpt_id == selection.head().excerpt_id
17614 });
17615 buffer_highlights
17616 .next()
17617 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17618 })?
17619 };
17620 if let Some(rename_range) = rename_range {
17621 this.update_in(cx, |this, window, cx| {
17622 let snapshot = cursor_buffer.read(cx).snapshot();
17623 let rename_buffer_range = rename_range.to_offset(&snapshot);
17624 let cursor_offset_in_rename_range =
17625 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17626 let cursor_offset_in_rename_range_end =
17627 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17628
17629 this.take_rename(false, window, cx);
17630 let buffer = this.buffer.read(cx).read(cx);
17631 let cursor_offset = selection.head().to_offset(&buffer);
17632 let rename_start =
17633 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17634 let rename_end = rename_start + rename_buffer_range.len();
17635 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17636 let mut old_highlight_id = None;
17637 let old_name: Arc<str> = buffer
17638 .chunks(rename_start..rename_end, true)
17639 .map(|chunk| {
17640 if old_highlight_id.is_none() {
17641 old_highlight_id = chunk.syntax_highlight_id;
17642 }
17643 chunk.text
17644 })
17645 .collect::<String>()
17646 .into();
17647
17648 drop(buffer);
17649
17650 // Position the selection in the rename editor so that it matches the current selection.
17651 this.show_local_selections = false;
17652 let rename_editor = cx.new(|cx| {
17653 let mut editor = Editor::single_line(window, cx);
17654 editor.buffer.update(cx, |buffer, cx| {
17655 buffer.edit(
17656 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17657 None,
17658 cx,
17659 )
17660 });
17661 let cursor_offset_in_rename_range =
17662 MultiBufferOffset(cursor_offset_in_rename_range);
17663 let cursor_offset_in_rename_range_end =
17664 MultiBufferOffset(cursor_offset_in_rename_range_end);
17665 let rename_selection_range = match cursor_offset_in_rename_range
17666 .cmp(&cursor_offset_in_rename_range_end)
17667 {
17668 Ordering::Equal => {
17669 editor.select_all(&SelectAll, window, cx);
17670 return editor;
17671 }
17672 Ordering::Less => {
17673 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17674 }
17675 Ordering::Greater => {
17676 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17677 }
17678 };
17679 if rename_selection_range.end.0 > old_name.len() {
17680 editor.select_all(&SelectAll, window, cx);
17681 } else {
17682 editor.change_selections(Default::default(), window, cx, |s| {
17683 s.select_ranges([rename_selection_range]);
17684 });
17685 }
17686 editor
17687 });
17688 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17689 if e == &EditorEvent::Focused {
17690 cx.emit(EditorEvent::FocusedIn)
17691 }
17692 })
17693 .detach();
17694
17695 let write_highlights =
17696 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17697 let read_highlights =
17698 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17699 let ranges = write_highlights
17700 .iter()
17701 .flat_map(|(_, ranges)| ranges.iter())
17702 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17703 .cloned()
17704 .collect();
17705
17706 this.highlight_text::<Rename>(
17707 ranges,
17708 HighlightStyle {
17709 fade_out: Some(0.6),
17710 ..Default::default()
17711 },
17712 cx,
17713 );
17714 let rename_focus_handle = rename_editor.focus_handle(cx);
17715 window.focus(&rename_focus_handle);
17716 let block_id = this.insert_blocks(
17717 [BlockProperties {
17718 style: BlockStyle::Flex,
17719 placement: BlockPlacement::Below(range.start),
17720 height: Some(1),
17721 render: Arc::new({
17722 let rename_editor = rename_editor.clone();
17723 move |cx: &mut BlockContext| {
17724 let mut text_style = cx.editor_style.text.clone();
17725 if let Some(highlight_style) = old_highlight_id
17726 .and_then(|h| h.style(&cx.editor_style.syntax))
17727 {
17728 text_style = text_style.highlight(highlight_style);
17729 }
17730 div()
17731 .block_mouse_except_scroll()
17732 .pl(cx.anchor_x)
17733 .child(EditorElement::new(
17734 &rename_editor,
17735 EditorStyle {
17736 background: cx.theme().system().transparent,
17737 local_player: cx.editor_style.local_player,
17738 text: text_style,
17739 scrollbar_width: cx.editor_style.scrollbar_width,
17740 syntax: cx.editor_style.syntax.clone(),
17741 status: cx.editor_style.status.clone(),
17742 inlay_hints_style: HighlightStyle {
17743 font_weight: Some(FontWeight::BOLD),
17744 ..make_inlay_hints_style(cx.app)
17745 },
17746 edit_prediction_styles: make_suggestion_styles(
17747 cx.app,
17748 ),
17749 ..EditorStyle::default()
17750 },
17751 ))
17752 .into_any_element()
17753 }
17754 }),
17755 priority: 0,
17756 }],
17757 Some(Autoscroll::fit()),
17758 cx,
17759 )[0];
17760 this.pending_rename = Some(RenameState {
17761 range,
17762 old_name,
17763 editor: rename_editor,
17764 block_id,
17765 });
17766 })?;
17767 }
17768
17769 Ok(())
17770 }))
17771 }
17772
17773 pub fn confirm_rename(
17774 &mut self,
17775 _: &ConfirmRename,
17776 window: &mut Window,
17777 cx: &mut Context<Self>,
17778 ) -> Option<Task<Result<()>>> {
17779 let rename = self.take_rename(false, window, cx)?;
17780 let workspace = self.workspace()?.downgrade();
17781 let (buffer, start) = self
17782 .buffer
17783 .read(cx)
17784 .text_anchor_for_position(rename.range.start, cx)?;
17785 let (end_buffer, _) = self
17786 .buffer
17787 .read(cx)
17788 .text_anchor_for_position(rename.range.end, cx)?;
17789 if buffer != end_buffer {
17790 return None;
17791 }
17792
17793 let old_name = rename.old_name;
17794 let new_name = rename.editor.read(cx).text(cx);
17795
17796 let rename = self.semantics_provider.as_ref()?.perform_rename(
17797 &buffer,
17798 start,
17799 new_name.clone(),
17800 cx,
17801 )?;
17802
17803 Some(cx.spawn_in(window, async move |editor, cx| {
17804 let project_transaction = rename.await?;
17805 Self::open_project_transaction(
17806 &editor,
17807 workspace,
17808 project_transaction,
17809 format!("Rename: {} → {}", old_name, new_name),
17810 cx,
17811 )
17812 .await?;
17813
17814 editor.update(cx, |editor, cx| {
17815 editor.refresh_document_highlights(cx);
17816 })?;
17817 Ok(())
17818 }))
17819 }
17820
17821 fn take_rename(
17822 &mut self,
17823 moving_cursor: bool,
17824 window: &mut Window,
17825 cx: &mut Context<Self>,
17826 ) -> Option<RenameState> {
17827 let rename = self.pending_rename.take()?;
17828 if rename.editor.focus_handle(cx).is_focused(window) {
17829 window.focus(&self.focus_handle);
17830 }
17831
17832 self.remove_blocks(
17833 [rename.block_id].into_iter().collect(),
17834 Some(Autoscroll::fit()),
17835 cx,
17836 );
17837 self.clear_highlights::<Rename>(cx);
17838 self.show_local_selections = true;
17839
17840 if moving_cursor {
17841 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17842 editor
17843 .selections
17844 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
17845 .head()
17846 });
17847
17848 // Update the selection to match the position of the selection inside
17849 // the rename editor.
17850 let snapshot = self.buffer.read(cx).read(cx);
17851 let rename_range = rename.range.to_offset(&snapshot);
17852 let cursor_in_editor = snapshot
17853 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17854 .min(rename_range.end);
17855 drop(snapshot);
17856
17857 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17858 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17859 });
17860 } else {
17861 self.refresh_document_highlights(cx);
17862 }
17863
17864 Some(rename)
17865 }
17866
17867 pub fn pending_rename(&self) -> Option<&RenameState> {
17868 self.pending_rename.as_ref()
17869 }
17870
17871 fn format(
17872 &mut self,
17873 _: &Format,
17874 window: &mut Window,
17875 cx: &mut Context<Self>,
17876 ) -> Option<Task<Result<()>>> {
17877 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17878
17879 let project = match &self.project {
17880 Some(project) => project.clone(),
17881 None => return None,
17882 };
17883
17884 Some(self.perform_format(
17885 project,
17886 FormatTrigger::Manual,
17887 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17888 window,
17889 cx,
17890 ))
17891 }
17892
17893 fn format_selections(
17894 &mut self,
17895 _: &FormatSelections,
17896 window: &mut Window,
17897 cx: &mut Context<Self>,
17898 ) -> Option<Task<Result<()>>> {
17899 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17900
17901 let project = match &self.project {
17902 Some(project) => project.clone(),
17903 None => return None,
17904 };
17905
17906 let ranges = self
17907 .selections
17908 .all_adjusted(&self.display_snapshot(cx))
17909 .into_iter()
17910 .map(|selection| selection.range())
17911 .collect_vec();
17912
17913 Some(self.perform_format(
17914 project,
17915 FormatTrigger::Manual,
17916 FormatTarget::Ranges(ranges),
17917 window,
17918 cx,
17919 ))
17920 }
17921
17922 fn perform_format(
17923 &mut self,
17924 project: Entity<Project>,
17925 trigger: FormatTrigger,
17926 target: FormatTarget,
17927 window: &mut Window,
17928 cx: &mut Context<Self>,
17929 ) -> Task<Result<()>> {
17930 let buffer = self.buffer.clone();
17931 let (buffers, target) = match target {
17932 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17933 FormatTarget::Ranges(selection_ranges) => {
17934 let multi_buffer = buffer.read(cx);
17935 let snapshot = multi_buffer.read(cx);
17936 let mut buffers = HashSet::default();
17937 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17938 BTreeMap::new();
17939 for selection_range in selection_ranges {
17940 for (buffer, buffer_range, _) in
17941 snapshot.range_to_buffer_ranges(selection_range)
17942 {
17943 let buffer_id = buffer.remote_id();
17944 let start = buffer.anchor_before(buffer_range.start);
17945 let end = buffer.anchor_after(buffer_range.end);
17946 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17947 buffer_id_to_ranges
17948 .entry(buffer_id)
17949 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17950 .or_insert_with(|| vec![start..end]);
17951 }
17952 }
17953 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17954 }
17955 };
17956
17957 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17958 let selections_prev = transaction_id_prev
17959 .and_then(|transaction_id_prev| {
17960 // default to selections as they were after the last edit, if we have them,
17961 // instead of how they are now.
17962 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17963 // will take you back to where you made the last edit, instead of staying where you scrolled
17964 self.selection_history
17965 .transaction(transaction_id_prev)
17966 .map(|t| t.0.clone())
17967 })
17968 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17969
17970 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17971 let format = project.update(cx, |project, cx| {
17972 project.format(buffers, target, true, trigger, cx)
17973 });
17974
17975 cx.spawn_in(window, async move |editor, cx| {
17976 let transaction = futures::select_biased! {
17977 transaction = format.log_err().fuse() => transaction,
17978 () = timeout => {
17979 log::warn!("timed out waiting for formatting");
17980 None
17981 }
17982 };
17983
17984 buffer
17985 .update(cx, |buffer, cx| {
17986 if let Some(transaction) = transaction
17987 && !buffer.is_singleton()
17988 {
17989 buffer.push_transaction(&transaction.0, cx);
17990 }
17991 cx.notify();
17992 })
17993 .ok();
17994
17995 if let Some(transaction_id_now) =
17996 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17997 {
17998 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17999 if has_new_transaction {
18000 _ = editor.update(cx, |editor, _| {
18001 editor
18002 .selection_history
18003 .insert_transaction(transaction_id_now, selections_prev);
18004 });
18005 }
18006 }
18007
18008 Ok(())
18009 })
18010 }
18011
18012 fn organize_imports(
18013 &mut self,
18014 _: &OrganizeImports,
18015 window: &mut Window,
18016 cx: &mut Context<Self>,
18017 ) -> Option<Task<Result<()>>> {
18018 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18019 let project = match &self.project {
18020 Some(project) => project.clone(),
18021 None => return None,
18022 };
18023 Some(self.perform_code_action_kind(
18024 project,
18025 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18026 window,
18027 cx,
18028 ))
18029 }
18030
18031 fn perform_code_action_kind(
18032 &mut self,
18033 project: Entity<Project>,
18034 kind: CodeActionKind,
18035 window: &mut Window,
18036 cx: &mut Context<Self>,
18037 ) -> Task<Result<()>> {
18038 let buffer = self.buffer.clone();
18039 let buffers = buffer.read(cx).all_buffers();
18040 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18041 let apply_action = project.update(cx, |project, cx| {
18042 project.apply_code_action_kind(buffers, kind, true, cx)
18043 });
18044 cx.spawn_in(window, async move |_, cx| {
18045 let transaction = futures::select_biased! {
18046 () = timeout => {
18047 log::warn!("timed out waiting for executing code action");
18048 None
18049 }
18050 transaction = apply_action.log_err().fuse() => transaction,
18051 };
18052 buffer
18053 .update(cx, |buffer, cx| {
18054 // check if we need this
18055 if let Some(transaction) = transaction
18056 && !buffer.is_singleton()
18057 {
18058 buffer.push_transaction(&transaction.0, cx);
18059 }
18060 cx.notify();
18061 })
18062 .ok();
18063 Ok(())
18064 })
18065 }
18066
18067 pub fn restart_language_server(
18068 &mut self,
18069 _: &RestartLanguageServer,
18070 _: &mut Window,
18071 cx: &mut Context<Self>,
18072 ) {
18073 if let Some(project) = self.project.clone() {
18074 self.buffer.update(cx, |multi_buffer, cx| {
18075 project.update(cx, |project, cx| {
18076 project.restart_language_servers_for_buffers(
18077 multi_buffer.all_buffers().into_iter().collect(),
18078 HashSet::default(),
18079 cx,
18080 );
18081 });
18082 })
18083 }
18084 }
18085
18086 pub fn stop_language_server(
18087 &mut self,
18088 _: &StopLanguageServer,
18089 _: &mut Window,
18090 cx: &mut Context<Self>,
18091 ) {
18092 if let Some(project) = self.project.clone() {
18093 self.buffer.update(cx, |multi_buffer, cx| {
18094 project.update(cx, |project, cx| {
18095 project.stop_language_servers_for_buffers(
18096 multi_buffer.all_buffers().into_iter().collect(),
18097 HashSet::default(),
18098 cx,
18099 );
18100 });
18101 });
18102 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18103 }
18104 }
18105
18106 fn cancel_language_server_work(
18107 workspace: &mut Workspace,
18108 _: &actions::CancelLanguageServerWork,
18109 _: &mut Window,
18110 cx: &mut Context<Workspace>,
18111 ) {
18112 let project = workspace.project();
18113 let buffers = workspace
18114 .active_item(cx)
18115 .and_then(|item| item.act_as::<Editor>(cx))
18116 .map_or(HashSet::default(), |editor| {
18117 editor.read(cx).buffer.read(cx).all_buffers()
18118 });
18119 project.update(cx, |project, cx| {
18120 project.cancel_language_server_work_for_buffers(buffers, cx);
18121 });
18122 }
18123
18124 fn show_character_palette(
18125 &mut self,
18126 _: &ShowCharacterPalette,
18127 window: &mut Window,
18128 _: &mut Context<Self>,
18129 ) {
18130 window.show_character_palette();
18131 }
18132
18133 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18134 if !self.diagnostics_enabled() {
18135 return;
18136 }
18137
18138 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18139 let buffer = self.buffer.read(cx).snapshot(cx);
18140 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18141 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18142 let is_valid = buffer
18143 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18144 .any(|entry| {
18145 entry.diagnostic.is_primary
18146 && !entry.range.is_empty()
18147 && entry.range.start == primary_range_start
18148 && entry.diagnostic.message == active_diagnostics.active_message
18149 });
18150
18151 if !is_valid {
18152 self.dismiss_diagnostics(cx);
18153 }
18154 }
18155 }
18156
18157 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18158 match &self.active_diagnostics {
18159 ActiveDiagnostic::Group(group) => Some(group),
18160 _ => None,
18161 }
18162 }
18163
18164 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18165 if !self.diagnostics_enabled() {
18166 return;
18167 }
18168 self.dismiss_diagnostics(cx);
18169 self.active_diagnostics = ActiveDiagnostic::All;
18170 }
18171
18172 fn activate_diagnostics(
18173 &mut self,
18174 buffer_id: BufferId,
18175 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18176 window: &mut Window,
18177 cx: &mut Context<Self>,
18178 ) {
18179 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18180 return;
18181 }
18182 self.dismiss_diagnostics(cx);
18183 let snapshot = self.snapshot(window, cx);
18184 let buffer = self.buffer.read(cx).snapshot(cx);
18185 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18186 return;
18187 };
18188
18189 let diagnostic_group = buffer
18190 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18191 .collect::<Vec<_>>();
18192
18193 let language_registry = self
18194 .project()
18195 .map(|project| project.read(cx).languages().clone());
18196
18197 let blocks = renderer.render_group(
18198 diagnostic_group,
18199 buffer_id,
18200 snapshot,
18201 cx.weak_entity(),
18202 language_registry,
18203 cx,
18204 );
18205
18206 let blocks = self.display_map.update(cx, |display_map, cx| {
18207 display_map.insert_blocks(blocks, cx).into_iter().collect()
18208 });
18209 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18210 active_range: buffer.anchor_before(diagnostic.range.start)
18211 ..buffer.anchor_after(diagnostic.range.end),
18212 active_message: diagnostic.diagnostic.message.clone(),
18213 group_id: diagnostic.diagnostic.group_id,
18214 blocks,
18215 });
18216 cx.notify();
18217 }
18218
18219 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18220 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18221 return;
18222 };
18223
18224 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18225 if let ActiveDiagnostic::Group(group) = prev {
18226 self.display_map.update(cx, |display_map, cx| {
18227 display_map.remove_blocks(group.blocks, cx);
18228 });
18229 cx.notify();
18230 }
18231 }
18232
18233 /// Disable inline diagnostics rendering for this editor.
18234 pub fn disable_inline_diagnostics(&mut self) {
18235 self.inline_diagnostics_enabled = false;
18236 self.inline_diagnostics_update = Task::ready(());
18237 self.inline_diagnostics.clear();
18238 }
18239
18240 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18241 self.diagnostics_enabled = false;
18242 self.dismiss_diagnostics(cx);
18243 self.inline_diagnostics_update = Task::ready(());
18244 self.inline_diagnostics.clear();
18245 }
18246
18247 pub fn disable_word_completions(&mut self) {
18248 self.word_completions_enabled = false;
18249 }
18250
18251 pub fn diagnostics_enabled(&self) -> bool {
18252 self.diagnostics_enabled && self.mode.is_full()
18253 }
18254
18255 pub fn inline_diagnostics_enabled(&self) -> bool {
18256 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18257 }
18258
18259 pub fn show_inline_diagnostics(&self) -> bool {
18260 self.show_inline_diagnostics
18261 }
18262
18263 pub fn toggle_inline_diagnostics(
18264 &mut self,
18265 _: &ToggleInlineDiagnostics,
18266 window: &mut Window,
18267 cx: &mut Context<Editor>,
18268 ) {
18269 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18270 self.refresh_inline_diagnostics(false, window, cx);
18271 }
18272
18273 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18274 self.diagnostics_max_severity = severity;
18275 self.display_map.update(cx, |display_map, _| {
18276 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18277 });
18278 }
18279
18280 pub fn toggle_diagnostics(
18281 &mut self,
18282 _: &ToggleDiagnostics,
18283 window: &mut Window,
18284 cx: &mut Context<Editor>,
18285 ) {
18286 if !self.diagnostics_enabled() {
18287 return;
18288 }
18289
18290 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18291 EditorSettings::get_global(cx)
18292 .diagnostics_max_severity
18293 .filter(|severity| severity != &DiagnosticSeverity::Off)
18294 .unwrap_or(DiagnosticSeverity::Hint)
18295 } else {
18296 DiagnosticSeverity::Off
18297 };
18298 self.set_max_diagnostics_severity(new_severity, cx);
18299 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18300 self.active_diagnostics = ActiveDiagnostic::None;
18301 self.inline_diagnostics_update = Task::ready(());
18302 self.inline_diagnostics.clear();
18303 } else {
18304 self.refresh_inline_diagnostics(false, window, cx);
18305 }
18306
18307 cx.notify();
18308 }
18309
18310 pub fn toggle_minimap(
18311 &mut self,
18312 _: &ToggleMinimap,
18313 window: &mut Window,
18314 cx: &mut Context<Editor>,
18315 ) {
18316 if self.supports_minimap(cx) {
18317 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18318 }
18319 }
18320
18321 fn refresh_inline_diagnostics(
18322 &mut self,
18323 debounce: bool,
18324 window: &mut Window,
18325 cx: &mut Context<Self>,
18326 ) {
18327 let max_severity = ProjectSettings::get_global(cx)
18328 .diagnostics
18329 .inline
18330 .max_severity
18331 .unwrap_or(self.diagnostics_max_severity);
18332
18333 if !self.inline_diagnostics_enabled()
18334 || !self.diagnostics_enabled()
18335 || !self.show_inline_diagnostics
18336 || max_severity == DiagnosticSeverity::Off
18337 {
18338 self.inline_diagnostics_update = Task::ready(());
18339 self.inline_diagnostics.clear();
18340 return;
18341 }
18342
18343 let debounce_ms = ProjectSettings::get_global(cx)
18344 .diagnostics
18345 .inline
18346 .update_debounce_ms;
18347 let debounce = if debounce && debounce_ms > 0 {
18348 Some(Duration::from_millis(debounce_ms))
18349 } else {
18350 None
18351 };
18352 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18353 if let Some(debounce) = debounce {
18354 cx.background_executor().timer(debounce).await;
18355 }
18356 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18357 editor
18358 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18359 .ok()
18360 }) else {
18361 return;
18362 };
18363
18364 let new_inline_diagnostics = cx
18365 .background_spawn(async move {
18366 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18367 for diagnostic_entry in
18368 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18369 {
18370 let message = diagnostic_entry
18371 .diagnostic
18372 .message
18373 .split_once('\n')
18374 .map(|(line, _)| line)
18375 .map(SharedString::new)
18376 .unwrap_or_else(|| {
18377 SharedString::new(&*diagnostic_entry.diagnostic.message)
18378 });
18379 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18380 let (Ok(i) | Err(i)) = inline_diagnostics
18381 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18382 inline_diagnostics.insert(
18383 i,
18384 (
18385 start_anchor,
18386 InlineDiagnostic {
18387 message,
18388 group_id: diagnostic_entry.diagnostic.group_id,
18389 start: diagnostic_entry.range.start.to_point(&snapshot),
18390 is_primary: diagnostic_entry.diagnostic.is_primary,
18391 severity: diagnostic_entry.diagnostic.severity,
18392 },
18393 ),
18394 );
18395 }
18396 inline_diagnostics
18397 })
18398 .await;
18399
18400 editor
18401 .update(cx, |editor, cx| {
18402 editor.inline_diagnostics = new_inline_diagnostics;
18403 cx.notify();
18404 })
18405 .ok();
18406 });
18407 }
18408
18409 fn pull_diagnostics(
18410 &mut self,
18411 buffer_id: Option<BufferId>,
18412 window: &Window,
18413 cx: &mut Context<Self>,
18414 ) -> Option<()> {
18415 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18416 return None;
18417 }
18418 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18419 .diagnostics
18420 .lsp_pull_diagnostics;
18421 if !pull_diagnostics_settings.enabled {
18422 return None;
18423 }
18424 let project = self.project()?.downgrade();
18425
18426 let mut edited_buffer_ids = HashSet::default();
18427 let mut edited_worktree_ids = HashSet::default();
18428 let edited_buffers = match buffer_id {
18429 Some(buffer_id) => {
18430 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18431 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18432 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18433 edited_worktree_ids.insert(worktree_id);
18434 vec![buffer]
18435 }
18436 None => self
18437 .buffer()
18438 .read(cx)
18439 .all_buffers()
18440 .into_iter()
18441 .filter(|buffer| {
18442 let buffer = buffer.read(cx);
18443 match buffer.file().map(|f| f.worktree_id(cx)) {
18444 Some(worktree_id) => {
18445 edited_buffer_ids.insert(buffer.remote_id());
18446 edited_worktree_ids.insert(worktree_id);
18447 true
18448 }
18449 None => false,
18450 }
18451 })
18452 .collect::<Vec<_>>(),
18453 };
18454
18455 if edited_buffers.is_empty() {
18456 self.pull_diagnostics_task = Task::ready(());
18457 self.pull_diagnostics_background_task = Task::ready(());
18458 return None;
18459 }
18460
18461 let mut already_used_buffers = HashSet::default();
18462 let related_open_buffers = self
18463 .workspace
18464 .as_ref()
18465 .and_then(|(workspace, _)| workspace.upgrade())
18466 .into_iter()
18467 .flat_map(|workspace| workspace.read(cx).panes())
18468 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18469 .filter(|editor| editor != &cx.entity())
18470 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18471 .filter(|buffer| {
18472 let buffer = buffer.read(cx);
18473 let buffer_id = buffer.remote_id();
18474 if already_used_buffers.insert(buffer_id) {
18475 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18476 return !edited_buffer_ids.contains(&buffer_id)
18477 && !edited_worktree_ids.contains(&worktree_id);
18478 }
18479 }
18480 false
18481 })
18482 .collect::<Vec<_>>();
18483
18484 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18485 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18486 if buffers.is_empty() {
18487 return Task::ready(());
18488 }
18489 let project_weak = project.clone();
18490 cx.spawn_in(window, async move |_, cx| {
18491 cx.background_executor().timer(delay).await;
18492
18493 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18494 buffers
18495 .into_iter()
18496 .filter_map(|buffer| {
18497 project_weak
18498 .update(cx, |project, cx| {
18499 project.lsp_store().update(cx, |lsp_store, cx| {
18500 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18501 })
18502 })
18503 .ok()
18504 })
18505 .collect::<FuturesUnordered<_>>()
18506 }) else {
18507 return;
18508 };
18509
18510 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18511 if let Err(e) = pull_task {
18512 log::error!("Failed to update project diagnostics: {e:#}");
18513 }
18514 }
18515 })
18516 };
18517
18518 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18519 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18520
18521 Some(())
18522 }
18523
18524 pub fn set_selections_from_remote(
18525 &mut self,
18526 selections: Vec<Selection<Anchor>>,
18527 pending_selection: Option<Selection<Anchor>>,
18528 window: &mut Window,
18529 cx: &mut Context<Self>,
18530 ) {
18531 let old_cursor_position = self.selections.newest_anchor().head();
18532 self.selections
18533 .change_with(&self.display_snapshot(cx), |s| {
18534 s.select_anchors(selections);
18535 if let Some(pending_selection) = pending_selection {
18536 s.set_pending(pending_selection, SelectMode::Character);
18537 } else {
18538 s.clear_pending();
18539 }
18540 });
18541 self.selections_did_change(
18542 false,
18543 &old_cursor_position,
18544 SelectionEffects::default(),
18545 window,
18546 cx,
18547 );
18548 }
18549
18550 pub fn transact(
18551 &mut self,
18552 window: &mut Window,
18553 cx: &mut Context<Self>,
18554 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18555 ) -> Option<TransactionId> {
18556 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18557 this.start_transaction_at(Instant::now(), window, cx);
18558 update(this, window, cx);
18559 this.end_transaction_at(Instant::now(), cx)
18560 })
18561 }
18562
18563 pub fn start_transaction_at(
18564 &mut self,
18565 now: Instant,
18566 window: &mut Window,
18567 cx: &mut Context<Self>,
18568 ) -> Option<TransactionId> {
18569 self.end_selection(window, cx);
18570 if let Some(tx_id) = self
18571 .buffer
18572 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18573 {
18574 self.selection_history
18575 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18576 cx.emit(EditorEvent::TransactionBegun {
18577 transaction_id: tx_id,
18578 });
18579 Some(tx_id)
18580 } else {
18581 None
18582 }
18583 }
18584
18585 pub fn end_transaction_at(
18586 &mut self,
18587 now: Instant,
18588 cx: &mut Context<Self>,
18589 ) -> Option<TransactionId> {
18590 if let Some(transaction_id) = self
18591 .buffer
18592 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18593 {
18594 if let Some((_, end_selections)) =
18595 self.selection_history.transaction_mut(transaction_id)
18596 {
18597 *end_selections = Some(self.selections.disjoint_anchors_arc());
18598 } else {
18599 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18600 }
18601
18602 cx.emit(EditorEvent::Edited { transaction_id });
18603 Some(transaction_id)
18604 } else {
18605 None
18606 }
18607 }
18608
18609 pub fn modify_transaction_selection_history(
18610 &mut self,
18611 transaction_id: TransactionId,
18612 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18613 ) -> bool {
18614 self.selection_history
18615 .transaction_mut(transaction_id)
18616 .map(modify)
18617 .is_some()
18618 }
18619
18620 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18621 if self.selection_mark_mode {
18622 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18623 s.move_with(|_, sel| {
18624 sel.collapse_to(sel.head(), SelectionGoal::None);
18625 });
18626 })
18627 }
18628 self.selection_mark_mode = true;
18629 cx.notify();
18630 }
18631
18632 pub fn swap_selection_ends(
18633 &mut self,
18634 _: &actions::SwapSelectionEnds,
18635 window: &mut Window,
18636 cx: &mut Context<Self>,
18637 ) {
18638 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18639 s.move_with(|_, sel| {
18640 if sel.start != sel.end {
18641 sel.reversed = !sel.reversed
18642 }
18643 });
18644 });
18645 self.request_autoscroll(Autoscroll::newest(), cx);
18646 cx.notify();
18647 }
18648
18649 pub fn toggle_focus(
18650 workspace: &mut Workspace,
18651 _: &actions::ToggleFocus,
18652 window: &mut Window,
18653 cx: &mut Context<Workspace>,
18654 ) {
18655 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18656 return;
18657 };
18658 workspace.activate_item(&item, true, true, window, cx);
18659 }
18660
18661 pub fn toggle_fold(
18662 &mut self,
18663 _: &actions::ToggleFold,
18664 window: &mut Window,
18665 cx: &mut Context<Self>,
18666 ) {
18667 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18668 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18669 let selection = self.selections.newest::<Point>(&display_map);
18670
18671 let range = if selection.is_empty() {
18672 let point = selection.head().to_display_point(&display_map);
18673 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18674 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18675 .to_point(&display_map);
18676 start..end
18677 } else {
18678 selection.range()
18679 };
18680 if display_map.folds_in_range(range).next().is_some() {
18681 self.unfold_lines(&Default::default(), window, cx)
18682 } else {
18683 self.fold(&Default::default(), window, cx)
18684 }
18685 } else {
18686 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18687 let buffer_ids: HashSet<_> = self
18688 .selections
18689 .disjoint_anchor_ranges()
18690 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18691 .collect();
18692
18693 let should_unfold = buffer_ids
18694 .iter()
18695 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18696
18697 for buffer_id in buffer_ids {
18698 if should_unfold {
18699 self.unfold_buffer(buffer_id, cx);
18700 } else {
18701 self.fold_buffer(buffer_id, cx);
18702 }
18703 }
18704 }
18705 }
18706
18707 pub fn toggle_fold_recursive(
18708 &mut self,
18709 _: &actions::ToggleFoldRecursive,
18710 window: &mut Window,
18711 cx: &mut Context<Self>,
18712 ) {
18713 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18714
18715 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18716 let range = if selection.is_empty() {
18717 let point = selection.head().to_display_point(&display_map);
18718 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18719 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18720 .to_point(&display_map);
18721 start..end
18722 } else {
18723 selection.range()
18724 };
18725 if display_map.folds_in_range(range).next().is_some() {
18726 self.unfold_recursive(&Default::default(), window, cx)
18727 } else {
18728 self.fold_recursive(&Default::default(), window, cx)
18729 }
18730 }
18731
18732 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18733 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18734 let mut to_fold = Vec::new();
18735 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18736 let selections = self.selections.all_adjusted(&display_map);
18737
18738 for selection in selections {
18739 let range = selection.range().sorted();
18740 let buffer_start_row = range.start.row;
18741
18742 if range.start.row != range.end.row {
18743 let mut found = false;
18744 let mut row = range.start.row;
18745 while row <= range.end.row {
18746 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18747 {
18748 found = true;
18749 row = crease.range().end.row + 1;
18750 to_fold.push(crease);
18751 } else {
18752 row += 1
18753 }
18754 }
18755 if found {
18756 continue;
18757 }
18758 }
18759
18760 for row in (0..=range.start.row).rev() {
18761 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18762 && crease.range().end.row >= buffer_start_row
18763 {
18764 to_fold.push(crease);
18765 if row <= range.start.row {
18766 break;
18767 }
18768 }
18769 }
18770 }
18771
18772 self.fold_creases(to_fold, true, window, cx);
18773 } else {
18774 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18775 let buffer_ids = self
18776 .selections
18777 .disjoint_anchor_ranges()
18778 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18779 .collect::<HashSet<_>>();
18780 for buffer_id in buffer_ids {
18781 self.fold_buffer(buffer_id, cx);
18782 }
18783 }
18784 }
18785
18786 pub fn toggle_fold_all(
18787 &mut self,
18788 _: &actions::ToggleFoldAll,
18789 window: &mut Window,
18790 cx: &mut Context<Self>,
18791 ) {
18792 if self.buffer.read(cx).is_singleton() {
18793 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18794 let has_folds = display_map
18795 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
18796 .next()
18797 .is_some();
18798
18799 if has_folds {
18800 self.unfold_all(&actions::UnfoldAll, window, cx);
18801 } else {
18802 self.fold_all(&actions::FoldAll, window, cx);
18803 }
18804 } else {
18805 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18806 let should_unfold = buffer_ids
18807 .iter()
18808 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18809
18810 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18811 editor
18812 .update_in(cx, |editor, _, cx| {
18813 for buffer_id in buffer_ids {
18814 if should_unfold {
18815 editor.unfold_buffer(buffer_id, cx);
18816 } else {
18817 editor.fold_buffer(buffer_id, cx);
18818 }
18819 }
18820 })
18821 .ok();
18822 });
18823 }
18824 }
18825
18826 fn fold_at_level(
18827 &mut self,
18828 fold_at: &FoldAtLevel,
18829 window: &mut Window,
18830 cx: &mut Context<Self>,
18831 ) {
18832 if !self.buffer.read(cx).is_singleton() {
18833 return;
18834 }
18835
18836 let fold_at_level = fold_at.0;
18837 let snapshot = self.buffer.read(cx).snapshot(cx);
18838 let mut to_fold = Vec::new();
18839 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18840
18841 let row_ranges_to_keep: Vec<Range<u32>> = self
18842 .selections
18843 .all::<Point>(&self.display_snapshot(cx))
18844 .into_iter()
18845 .map(|sel| sel.start.row..sel.end.row)
18846 .collect();
18847
18848 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18849 while start_row < end_row {
18850 match self
18851 .snapshot(window, cx)
18852 .crease_for_buffer_row(MultiBufferRow(start_row))
18853 {
18854 Some(crease) => {
18855 let nested_start_row = crease.range().start.row + 1;
18856 let nested_end_row = crease.range().end.row;
18857
18858 if current_level < fold_at_level {
18859 stack.push((nested_start_row, nested_end_row, current_level + 1));
18860 } else if current_level == fold_at_level {
18861 // Fold iff there is no selection completely contained within the fold region
18862 if !row_ranges_to_keep.iter().any(|selection| {
18863 selection.end >= nested_start_row
18864 && selection.start <= nested_end_row
18865 }) {
18866 to_fold.push(crease);
18867 }
18868 }
18869
18870 start_row = nested_end_row + 1;
18871 }
18872 None => start_row += 1,
18873 }
18874 }
18875 }
18876
18877 self.fold_creases(to_fold, true, window, cx);
18878 }
18879
18880 pub fn fold_at_level_1(
18881 &mut self,
18882 _: &actions::FoldAtLevel1,
18883 window: &mut Window,
18884 cx: &mut Context<Self>,
18885 ) {
18886 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18887 }
18888
18889 pub fn fold_at_level_2(
18890 &mut self,
18891 _: &actions::FoldAtLevel2,
18892 window: &mut Window,
18893 cx: &mut Context<Self>,
18894 ) {
18895 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18896 }
18897
18898 pub fn fold_at_level_3(
18899 &mut self,
18900 _: &actions::FoldAtLevel3,
18901 window: &mut Window,
18902 cx: &mut Context<Self>,
18903 ) {
18904 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18905 }
18906
18907 pub fn fold_at_level_4(
18908 &mut self,
18909 _: &actions::FoldAtLevel4,
18910 window: &mut Window,
18911 cx: &mut Context<Self>,
18912 ) {
18913 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18914 }
18915
18916 pub fn fold_at_level_5(
18917 &mut self,
18918 _: &actions::FoldAtLevel5,
18919 window: &mut Window,
18920 cx: &mut Context<Self>,
18921 ) {
18922 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18923 }
18924
18925 pub fn fold_at_level_6(
18926 &mut self,
18927 _: &actions::FoldAtLevel6,
18928 window: &mut Window,
18929 cx: &mut Context<Self>,
18930 ) {
18931 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18932 }
18933
18934 pub fn fold_at_level_7(
18935 &mut self,
18936 _: &actions::FoldAtLevel7,
18937 window: &mut Window,
18938 cx: &mut Context<Self>,
18939 ) {
18940 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18941 }
18942
18943 pub fn fold_at_level_8(
18944 &mut self,
18945 _: &actions::FoldAtLevel8,
18946 window: &mut Window,
18947 cx: &mut Context<Self>,
18948 ) {
18949 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18950 }
18951
18952 pub fn fold_at_level_9(
18953 &mut self,
18954 _: &actions::FoldAtLevel9,
18955 window: &mut Window,
18956 cx: &mut Context<Self>,
18957 ) {
18958 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18959 }
18960
18961 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18962 if self.buffer.read(cx).is_singleton() {
18963 let mut fold_ranges = Vec::new();
18964 let snapshot = self.buffer.read(cx).snapshot(cx);
18965
18966 for row in 0..snapshot.max_row().0 {
18967 if let Some(foldable_range) = self
18968 .snapshot(window, cx)
18969 .crease_for_buffer_row(MultiBufferRow(row))
18970 {
18971 fold_ranges.push(foldable_range);
18972 }
18973 }
18974
18975 self.fold_creases(fold_ranges, true, window, cx);
18976 } else {
18977 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18978 editor
18979 .update_in(cx, |editor, _, cx| {
18980 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18981 editor.fold_buffer(buffer_id, cx);
18982 }
18983 })
18984 .ok();
18985 });
18986 }
18987 }
18988
18989 pub fn fold_function_bodies(
18990 &mut self,
18991 _: &actions::FoldFunctionBodies,
18992 window: &mut Window,
18993 cx: &mut Context<Self>,
18994 ) {
18995 let snapshot = self.buffer.read(cx).snapshot(cx);
18996
18997 let ranges = snapshot
18998 .text_object_ranges(
18999 MultiBufferOffset(0)..snapshot.len(),
19000 TreeSitterOptions::default(),
19001 )
19002 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19003 .collect::<Vec<_>>();
19004
19005 let creases = ranges
19006 .into_iter()
19007 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19008 .collect();
19009
19010 self.fold_creases(creases, true, window, cx);
19011 }
19012
19013 pub fn fold_recursive(
19014 &mut self,
19015 _: &actions::FoldRecursive,
19016 window: &mut Window,
19017 cx: &mut Context<Self>,
19018 ) {
19019 let mut to_fold = Vec::new();
19020 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19021 let selections = self.selections.all_adjusted(&display_map);
19022
19023 for selection in selections {
19024 let range = selection.range().sorted();
19025 let buffer_start_row = range.start.row;
19026
19027 if range.start.row != range.end.row {
19028 let mut found = false;
19029 for row in range.start.row..=range.end.row {
19030 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19031 found = true;
19032 to_fold.push(crease);
19033 }
19034 }
19035 if found {
19036 continue;
19037 }
19038 }
19039
19040 for row in (0..=range.start.row).rev() {
19041 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19042 if crease.range().end.row >= buffer_start_row {
19043 to_fold.push(crease);
19044 } else {
19045 break;
19046 }
19047 }
19048 }
19049 }
19050
19051 self.fold_creases(to_fold, true, window, cx);
19052 }
19053
19054 pub fn fold_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 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19063 let autoscroll = self
19064 .selections
19065 .all::<Point>(&display_map)
19066 .iter()
19067 .any(|selection| crease.range().overlaps(&selection.range()));
19068
19069 self.fold_creases(vec![crease], autoscroll, window, cx);
19070 }
19071 }
19072
19073 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19074 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19075 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19076 let buffer = display_map.buffer_snapshot();
19077 let selections = self.selections.all::<Point>(&display_map);
19078 let ranges = selections
19079 .iter()
19080 .map(|s| {
19081 let range = s.display_range(&display_map).sorted();
19082 let mut start = range.start.to_point(&display_map);
19083 let mut end = range.end.to_point(&display_map);
19084 start.column = 0;
19085 end.column = buffer.line_len(MultiBufferRow(end.row));
19086 start..end
19087 })
19088 .collect::<Vec<_>>();
19089
19090 self.unfold_ranges(&ranges, true, true, cx);
19091 } else {
19092 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19093 let buffer_ids = self
19094 .selections
19095 .disjoint_anchor_ranges()
19096 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19097 .collect::<HashSet<_>>();
19098 for buffer_id in buffer_ids {
19099 self.unfold_buffer(buffer_id, cx);
19100 }
19101 }
19102 }
19103
19104 pub fn unfold_recursive(
19105 &mut self,
19106 _: &UnfoldRecursive,
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::<Point>(&display_map);
19112 let ranges = selections
19113 .iter()
19114 .map(|s| {
19115 let mut range = s.display_range(&display_map).sorted();
19116 *range.start.column_mut() = 0;
19117 *range.end.column_mut() = display_map.line_len(range.end.row());
19118 let start = range.start.to_point(&display_map);
19119 let end = range.end.to_point(&display_map);
19120 start..end
19121 })
19122 .collect::<Vec<_>>();
19123
19124 self.unfold_ranges(&ranges, true, true, cx);
19125 }
19126
19127 pub fn unfold_at(
19128 &mut self,
19129 buffer_row: MultiBufferRow,
19130 _window: &mut Window,
19131 cx: &mut Context<Self>,
19132 ) {
19133 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19134
19135 let intersection_range = Point::new(buffer_row.0, 0)
19136 ..Point::new(
19137 buffer_row.0,
19138 display_map.buffer_snapshot().line_len(buffer_row),
19139 );
19140
19141 let autoscroll = self
19142 .selections
19143 .all::<Point>(&display_map)
19144 .iter()
19145 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19146
19147 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19148 }
19149
19150 pub fn unfold_all(
19151 &mut self,
19152 _: &actions::UnfoldAll,
19153 _window: &mut Window,
19154 cx: &mut Context<Self>,
19155 ) {
19156 if self.buffer.read(cx).is_singleton() {
19157 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19158 self.unfold_ranges(
19159 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19160 true,
19161 true,
19162 cx,
19163 );
19164 } else {
19165 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19166 editor
19167 .update(cx, |editor, cx| {
19168 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19169 editor.unfold_buffer(buffer_id, cx);
19170 }
19171 })
19172 .ok();
19173 });
19174 }
19175 }
19176
19177 pub fn fold_selected_ranges(
19178 &mut self,
19179 _: &FoldSelectedRanges,
19180 window: &mut Window,
19181 cx: &mut Context<Self>,
19182 ) {
19183 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19184 let selections = self.selections.all_adjusted(&display_map);
19185 let ranges = selections
19186 .into_iter()
19187 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19188 .collect::<Vec<_>>();
19189 self.fold_creases(ranges, true, window, cx);
19190 }
19191
19192 pub fn fold_ranges<T: ToOffset + Clone>(
19193 &mut self,
19194 ranges: Vec<Range<T>>,
19195 auto_scroll: bool,
19196 window: &mut Window,
19197 cx: &mut Context<Self>,
19198 ) {
19199 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19200 let ranges = ranges
19201 .into_iter()
19202 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19203 .collect::<Vec<_>>();
19204 self.fold_creases(ranges, auto_scroll, window, cx);
19205 }
19206
19207 pub fn fold_creases<T: ToOffset + Clone>(
19208 &mut self,
19209 creases: Vec<Crease<T>>,
19210 auto_scroll: bool,
19211 _window: &mut Window,
19212 cx: &mut Context<Self>,
19213 ) {
19214 if creases.is_empty() {
19215 return;
19216 }
19217
19218 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19219
19220 if auto_scroll {
19221 self.request_autoscroll(Autoscroll::fit(), cx);
19222 }
19223
19224 cx.notify();
19225
19226 self.scrollbar_marker_state.dirty = true;
19227 self.folds_did_change(cx);
19228 }
19229
19230 /// Removes any folds whose ranges intersect any of the given ranges.
19231 pub fn unfold_ranges<T: ToOffset + Clone>(
19232 &mut self,
19233 ranges: &[Range<T>],
19234 inclusive: bool,
19235 auto_scroll: bool,
19236 cx: &mut Context<Self>,
19237 ) {
19238 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19239 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19240 });
19241 self.folds_did_change(cx);
19242 }
19243
19244 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19245 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19246 return;
19247 }
19248
19249 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19250 self.display_map.update(cx, |display_map, cx| {
19251 display_map.fold_buffers([buffer_id], cx)
19252 });
19253
19254 let snapshot = self.display_snapshot(cx);
19255 self.selections.change_with(&snapshot, |selections| {
19256 selections.remove_selections_from_buffer(buffer_id);
19257 });
19258
19259 cx.emit(EditorEvent::BufferFoldToggled {
19260 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19261 folded: true,
19262 });
19263 cx.notify();
19264 }
19265
19266 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19267 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19268 return;
19269 }
19270 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19271 self.display_map.update(cx, |display_map, cx| {
19272 display_map.unfold_buffers([buffer_id], cx);
19273 });
19274 cx.emit(EditorEvent::BufferFoldToggled {
19275 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19276 folded: false,
19277 });
19278 cx.notify();
19279 }
19280
19281 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19282 self.display_map.read(cx).is_buffer_folded(buffer)
19283 }
19284
19285 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19286 self.display_map.read(cx).folded_buffers()
19287 }
19288
19289 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19290 self.display_map.update(cx, |display_map, cx| {
19291 display_map.disable_header_for_buffer(buffer_id, cx);
19292 });
19293 cx.notify();
19294 }
19295
19296 /// Removes any folds with the given ranges.
19297 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19298 &mut self,
19299 ranges: &[Range<T>],
19300 type_id: TypeId,
19301 auto_scroll: bool,
19302 cx: &mut Context<Self>,
19303 ) {
19304 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19305 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19306 });
19307 self.folds_did_change(cx);
19308 }
19309
19310 fn remove_folds_with<T: ToOffset + Clone>(
19311 &mut self,
19312 ranges: &[Range<T>],
19313 auto_scroll: bool,
19314 cx: &mut Context<Self>,
19315 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19316 ) {
19317 if ranges.is_empty() {
19318 return;
19319 }
19320
19321 let mut buffers_affected = HashSet::default();
19322 let multi_buffer = self.buffer().read(cx);
19323 for range in ranges {
19324 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19325 buffers_affected.insert(buffer.read(cx).remote_id());
19326 };
19327 }
19328
19329 self.display_map.update(cx, update);
19330
19331 if auto_scroll {
19332 self.request_autoscroll(Autoscroll::fit(), cx);
19333 }
19334
19335 cx.notify();
19336 self.scrollbar_marker_state.dirty = true;
19337 self.active_indent_guides_state.dirty = true;
19338 }
19339
19340 pub fn update_renderer_widths(
19341 &mut self,
19342 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19343 cx: &mut Context<Self>,
19344 ) -> bool {
19345 self.display_map
19346 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19347 }
19348
19349 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19350 self.display_map.read(cx).fold_placeholder.clone()
19351 }
19352
19353 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19354 self.use_base_text_line_numbers = show;
19355 }
19356
19357 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19358 self.buffer.update(cx, |buffer, cx| {
19359 buffer.set_all_diff_hunks_expanded(cx);
19360 });
19361 }
19362
19363 pub fn expand_all_diff_hunks(
19364 &mut self,
19365 _: &ExpandAllDiffHunks,
19366 _window: &mut Window,
19367 cx: &mut Context<Self>,
19368 ) {
19369 self.buffer.update(cx, |buffer, cx| {
19370 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19371 });
19372 }
19373
19374 pub fn collapse_all_diff_hunks(
19375 &mut self,
19376 _: &CollapseAllDiffHunks,
19377 _window: &mut Window,
19378 cx: &mut Context<Self>,
19379 ) {
19380 self.buffer.update(cx, |buffer, cx| {
19381 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19382 });
19383 }
19384
19385 pub fn toggle_selected_diff_hunks(
19386 &mut self,
19387 _: &ToggleSelectedDiffHunks,
19388 _window: &mut Window,
19389 cx: &mut Context<Self>,
19390 ) {
19391 let ranges: Vec<_> = self
19392 .selections
19393 .disjoint_anchors()
19394 .iter()
19395 .map(|s| s.range())
19396 .collect();
19397 self.toggle_diff_hunks_in_ranges(ranges, cx);
19398 }
19399
19400 pub fn diff_hunks_in_ranges<'a>(
19401 &'a self,
19402 ranges: &'a [Range<Anchor>],
19403 buffer: &'a MultiBufferSnapshot,
19404 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19405 ranges.iter().flat_map(move |range| {
19406 let end_excerpt_id = range.end.excerpt_id;
19407 let range = range.to_point(buffer);
19408 let mut peek_end = range.end;
19409 if range.end.row < buffer.max_row().0 {
19410 peek_end = Point::new(range.end.row + 1, 0);
19411 }
19412 buffer
19413 .diff_hunks_in_range(range.start..peek_end)
19414 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19415 })
19416 }
19417
19418 pub fn has_stageable_diff_hunks_in_ranges(
19419 &self,
19420 ranges: &[Range<Anchor>],
19421 snapshot: &MultiBufferSnapshot,
19422 ) -> bool {
19423 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19424 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19425 }
19426
19427 pub fn toggle_staged_selected_diff_hunks(
19428 &mut self,
19429 _: &::git::ToggleStaged,
19430 _: &mut Window,
19431 cx: &mut Context<Self>,
19432 ) {
19433 let snapshot = self.buffer.read(cx).snapshot(cx);
19434 let ranges: Vec<_> = self
19435 .selections
19436 .disjoint_anchors()
19437 .iter()
19438 .map(|s| s.range())
19439 .collect();
19440 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19441 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19442 }
19443
19444 pub fn set_render_diff_hunk_controls(
19445 &mut self,
19446 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19447 cx: &mut Context<Self>,
19448 ) {
19449 self.render_diff_hunk_controls = render_diff_hunk_controls;
19450 cx.notify();
19451 }
19452
19453 pub fn stage_and_next(
19454 &mut self,
19455 _: &::git::StageAndNext,
19456 window: &mut Window,
19457 cx: &mut Context<Self>,
19458 ) {
19459 self.do_stage_or_unstage_and_next(true, window, cx);
19460 }
19461
19462 pub fn unstage_and_next(
19463 &mut self,
19464 _: &::git::UnstageAndNext,
19465 window: &mut Window,
19466 cx: &mut Context<Self>,
19467 ) {
19468 self.do_stage_or_unstage_and_next(false, window, cx);
19469 }
19470
19471 pub fn stage_or_unstage_diff_hunks(
19472 &mut self,
19473 stage: bool,
19474 ranges: Vec<Range<Anchor>>,
19475 cx: &mut Context<Self>,
19476 ) {
19477 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19478 cx.spawn(async move |this, cx| {
19479 task.await?;
19480 this.update(cx, |this, cx| {
19481 let snapshot = this.buffer.read(cx).snapshot(cx);
19482 let chunk_by = this
19483 .diff_hunks_in_ranges(&ranges, &snapshot)
19484 .chunk_by(|hunk| hunk.buffer_id);
19485 for (buffer_id, hunks) in &chunk_by {
19486 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19487 }
19488 })
19489 })
19490 .detach_and_log_err(cx);
19491 }
19492
19493 fn save_buffers_for_ranges_if_needed(
19494 &mut self,
19495 ranges: &[Range<Anchor>],
19496 cx: &mut Context<Editor>,
19497 ) -> Task<Result<()>> {
19498 let multibuffer = self.buffer.read(cx);
19499 let snapshot = multibuffer.read(cx);
19500 let buffer_ids: HashSet<_> = ranges
19501 .iter()
19502 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19503 .collect();
19504 drop(snapshot);
19505
19506 let mut buffers = HashSet::default();
19507 for buffer_id in buffer_ids {
19508 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19509 let buffer = buffer_entity.read(cx);
19510 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19511 {
19512 buffers.insert(buffer_entity);
19513 }
19514 }
19515 }
19516
19517 if let Some(project) = &self.project {
19518 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19519 } else {
19520 Task::ready(Ok(()))
19521 }
19522 }
19523
19524 fn do_stage_or_unstage_and_next(
19525 &mut self,
19526 stage: bool,
19527 window: &mut Window,
19528 cx: &mut Context<Self>,
19529 ) {
19530 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19531
19532 if ranges.iter().any(|range| range.start != range.end) {
19533 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19534 return;
19535 }
19536
19537 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19538 let snapshot = self.snapshot(window, cx);
19539 let position = self
19540 .selections
19541 .newest::<Point>(&snapshot.display_snapshot)
19542 .head();
19543 let mut row = snapshot
19544 .buffer_snapshot()
19545 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19546 .find(|hunk| hunk.row_range.start.0 > position.row)
19547 .map(|hunk| hunk.row_range.start);
19548
19549 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19550 // Outside of the project diff editor, wrap around to the beginning.
19551 if !all_diff_hunks_expanded {
19552 row = row.or_else(|| {
19553 snapshot
19554 .buffer_snapshot()
19555 .diff_hunks_in_range(Point::zero()..position)
19556 .find(|hunk| hunk.row_range.end.0 < position.row)
19557 .map(|hunk| hunk.row_range.start)
19558 });
19559 }
19560
19561 if let Some(row) = row {
19562 let destination = Point::new(row.0, 0);
19563 let autoscroll = Autoscroll::center();
19564
19565 self.unfold_ranges(&[destination..destination], false, false, cx);
19566 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19567 s.select_ranges([destination..destination]);
19568 });
19569 }
19570 }
19571
19572 fn do_stage_or_unstage(
19573 &self,
19574 stage: bool,
19575 buffer_id: BufferId,
19576 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19577 cx: &mut App,
19578 ) -> Option<()> {
19579 let project = self.project()?;
19580 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19581 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19582 let buffer_snapshot = buffer.read(cx).snapshot();
19583 let file_exists = buffer_snapshot
19584 .file()
19585 .is_some_and(|file| file.disk_state().exists());
19586 diff.update(cx, |diff, cx| {
19587 diff.stage_or_unstage_hunks(
19588 stage,
19589 &hunks
19590 .map(|hunk| buffer_diff::DiffHunk {
19591 buffer_range: hunk.buffer_range,
19592 // We don't need to pass in word diffs here because they're only used for rendering and
19593 // this function changes internal state
19594 base_word_diffs: Vec::default(),
19595 buffer_word_diffs: Vec::default(),
19596 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19597 ..hunk.diff_base_byte_range.end.0,
19598 secondary_status: hunk.secondary_status,
19599 range: Point::zero()..Point::zero(), // unused
19600 })
19601 .collect::<Vec<_>>(),
19602 &buffer_snapshot,
19603 file_exists,
19604 cx,
19605 )
19606 });
19607 None
19608 }
19609
19610 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19611 let ranges: Vec<_> = self
19612 .selections
19613 .disjoint_anchors()
19614 .iter()
19615 .map(|s| s.range())
19616 .collect();
19617 self.buffer
19618 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19619 }
19620
19621 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19622 self.buffer.update(cx, |buffer, cx| {
19623 let ranges = vec![Anchor::min()..Anchor::max()];
19624 if !buffer.all_diff_hunks_expanded()
19625 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19626 {
19627 buffer.collapse_diff_hunks(ranges, cx);
19628 true
19629 } else {
19630 false
19631 }
19632 })
19633 }
19634
19635 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19636 if self.buffer.read(cx).all_diff_hunks_expanded() {
19637 return true;
19638 }
19639 let ranges = vec![Anchor::min()..Anchor::max()];
19640 self.buffer
19641 .read(cx)
19642 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19643 }
19644
19645 fn toggle_diff_hunks_in_ranges(
19646 &mut self,
19647 ranges: Vec<Range<Anchor>>,
19648 cx: &mut Context<Editor>,
19649 ) {
19650 self.buffer.update(cx, |buffer, cx| {
19651 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19652 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19653 })
19654 }
19655
19656 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19657 self.buffer.update(cx, |buffer, cx| {
19658 let snapshot = buffer.snapshot(cx);
19659 let excerpt_id = range.end.excerpt_id;
19660 let point_range = range.to_point(&snapshot);
19661 let expand = !buffer.single_hunk_is_expanded(range, cx);
19662 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19663 })
19664 }
19665
19666 pub(crate) fn apply_all_diff_hunks(
19667 &mut self,
19668 _: &ApplyAllDiffHunks,
19669 window: &mut Window,
19670 cx: &mut Context<Self>,
19671 ) {
19672 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19673
19674 let buffers = self.buffer.read(cx).all_buffers();
19675 for branch_buffer in buffers {
19676 branch_buffer.update(cx, |branch_buffer, cx| {
19677 branch_buffer.merge_into_base(Vec::new(), cx);
19678 });
19679 }
19680
19681 if let Some(project) = self.project.clone() {
19682 self.save(
19683 SaveOptions {
19684 format: true,
19685 autosave: false,
19686 },
19687 project,
19688 window,
19689 cx,
19690 )
19691 .detach_and_log_err(cx);
19692 }
19693 }
19694
19695 pub(crate) fn apply_selected_diff_hunks(
19696 &mut self,
19697 _: &ApplyDiffHunk,
19698 window: &mut Window,
19699 cx: &mut Context<Self>,
19700 ) {
19701 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19702 let snapshot = self.snapshot(window, cx);
19703 let hunks = snapshot.hunks_for_ranges(
19704 self.selections
19705 .all(&snapshot.display_snapshot)
19706 .into_iter()
19707 .map(|selection| selection.range()),
19708 );
19709 let mut ranges_by_buffer = HashMap::default();
19710 self.transact(window, cx, |editor, _window, cx| {
19711 for hunk in hunks {
19712 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19713 ranges_by_buffer
19714 .entry(buffer.clone())
19715 .or_insert_with(Vec::new)
19716 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19717 }
19718 }
19719
19720 for (buffer, ranges) in ranges_by_buffer {
19721 buffer.update(cx, |buffer, cx| {
19722 buffer.merge_into_base(ranges, cx);
19723 });
19724 }
19725 });
19726
19727 if let Some(project) = self.project.clone() {
19728 self.save(
19729 SaveOptions {
19730 format: true,
19731 autosave: false,
19732 },
19733 project,
19734 window,
19735 cx,
19736 )
19737 .detach_and_log_err(cx);
19738 }
19739 }
19740
19741 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19742 if hovered != self.gutter_hovered {
19743 self.gutter_hovered = hovered;
19744 cx.notify();
19745 }
19746 }
19747
19748 pub fn insert_blocks(
19749 &mut self,
19750 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19751 autoscroll: Option<Autoscroll>,
19752 cx: &mut Context<Self>,
19753 ) -> Vec<CustomBlockId> {
19754 let blocks = self
19755 .display_map
19756 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19757 if let Some(autoscroll) = autoscroll {
19758 self.request_autoscroll(autoscroll, cx);
19759 }
19760 cx.notify();
19761 blocks
19762 }
19763
19764 pub fn resize_blocks(
19765 &mut self,
19766 heights: HashMap<CustomBlockId, u32>,
19767 autoscroll: Option<Autoscroll>,
19768 cx: &mut Context<Self>,
19769 ) {
19770 self.display_map
19771 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19772 if let Some(autoscroll) = autoscroll {
19773 self.request_autoscroll(autoscroll, cx);
19774 }
19775 cx.notify();
19776 }
19777
19778 pub fn replace_blocks(
19779 &mut self,
19780 renderers: HashMap<CustomBlockId, RenderBlock>,
19781 autoscroll: Option<Autoscroll>,
19782 cx: &mut Context<Self>,
19783 ) {
19784 self.display_map
19785 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19786 if let Some(autoscroll) = autoscroll {
19787 self.request_autoscroll(autoscroll, cx);
19788 }
19789 cx.notify();
19790 }
19791
19792 pub fn remove_blocks(
19793 &mut self,
19794 block_ids: HashSet<CustomBlockId>,
19795 autoscroll: Option<Autoscroll>,
19796 cx: &mut Context<Self>,
19797 ) {
19798 self.display_map.update(cx, |display_map, cx| {
19799 display_map.remove_blocks(block_ids, cx)
19800 });
19801 if let Some(autoscroll) = autoscroll {
19802 self.request_autoscroll(autoscroll, cx);
19803 }
19804 cx.notify();
19805 }
19806
19807 pub fn row_for_block(
19808 &self,
19809 block_id: CustomBlockId,
19810 cx: &mut Context<Self>,
19811 ) -> Option<DisplayRow> {
19812 self.display_map
19813 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19814 }
19815
19816 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19817 self.focused_block = Some(focused_block);
19818 }
19819
19820 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19821 self.focused_block.take()
19822 }
19823
19824 pub fn insert_creases(
19825 &mut self,
19826 creases: impl IntoIterator<Item = Crease<Anchor>>,
19827 cx: &mut Context<Self>,
19828 ) -> Vec<CreaseId> {
19829 self.display_map
19830 .update(cx, |map, cx| map.insert_creases(creases, cx))
19831 }
19832
19833 pub fn remove_creases(
19834 &mut self,
19835 ids: impl IntoIterator<Item = CreaseId>,
19836 cx: &mut Context<Self>,
19837 ) -> Vec<(CreaseId, Range<Anchor>)> {
19838 self.display_map
19839 .update(cx, |map, cx| map.remove_creases(ids, cx))
19840 }
19841
19842 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19843 self.display_map
19844 .update(cx, |map, cx| map.snapshot(cx))
19845 .longest_row()
19846 }
19847
19848 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19849 self.display_map
19850 .update(cx, |map, cx| map.snapshot(cx))
19851 .max_point()
19852 }
19853
19854 pub fn text(&self, cx: &App) -> String {
19855 self.buffer.read(cx).read(cx).text()
19856 }
19857
19858 pub fn is_empty(&self, cx: &App) -> bool {
19859 self.buffer.read(cx).read(cx).is_empty()
19860 }
19861
19862 pub fn text_option(&self, cx: &App) -> Option<String> {
19863 let text = self.text(cx);
19864 let text = text.trim();
19865
19866 if text.is_empty() {
19867 return None;
19868 }
19869
19870 Some(text.to_string())
19871 }
19872
19873 pub fn set_text(
19874 &mut self,
19875 text: impl Into<Arc<str>>,
19876 window: &mut Window,
19877 cx: &mut Context<Self>,
19878 ) {
19879 self.transact(window, cx, |this, _, cx| {
19880 this.buffer
19881 .read(cx)
19882 .as_singleton()
19883 .expect("you can only call set_text on editors for singleton buffers")
19884 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19885 });
19886 }
19887
19888 pub fn display_text(&self, cx: &mut App) -> String {
19889 self.display_map
19890 .update(cx, |map, cx| map.snapshot(cx))
19891 .text()
19892 }
19893
19894 fn create_minimap(
19895 &self,
19896 minimap_settings: MinimapSettings,
19897 window: &mut Window,
19898 cx: &mut Context<Self>,
19899 ) -> Option<Entity<Self>> {
19900 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19901 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19902 }
19903
19904 fn initialize_new_minimap(
19905 &self,
19906 minimap_settings: MinimapSettings,
19907 window: &mut Window,
19908 cx: &mut Context<Self>,
19909 ) -> Entity<Self> {
19910 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19911
19912 let mut minimap = Editor::new_internal(
19913 EditorMode::Minimap {
19914 parent: cx.weak_entity(),
19915 },
19916 self.buffer.clone(),
19917 None,
19918 Some(self.display_map.clone()),
19919 window,
19920 cx,
19921 );
19922 minimap.scroll_manager.clone_state(&self.scroll_manager);
19923 minimap.set_text_style_refinement(TextStyleRefinement {
19924 font_size: Some(MINIMAP_FONT_SIZE),
19925 font_weight: Some(MINIMAP_FONT_WEIGHT),
19926 ..Default::default()
19927 });
19928 minimap.update_minimap_configuration(minimap_settings, cx);
19929 cx.new(|_| minimap)
19930 }
19931
19932 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19933 let current_line_highlight = minimap_settings
19934 .current_line_highlight
19935 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19936 self.set_current_line_highlight(Some(current_line_highlight));
19937 }
19938
19939 pub fn minimap(&self) -> Option<&Entity<Self>> {
19940 self.minimap
19941 .as_ref()
19942 .filter(|_| self.minimap_visibility.visible())
19943 }
19944
19945 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19946 let mut wrap_guides = smallvec![];
19947
19948 if self.show_wrap_guides == Some(false) {
19949 return wrap_guides;
19950 }
19951
19952 let settings = self.buffer.read(cx).language_settings(cx);
19953 if settings.show_wrap_guides {
19954 match self.soft_wrap_mode(cx) {
19955 SoftWrap::Column(soft_wrap) => {
19956 wrap_guides.push((soft_wrap as usize, true));
19957 }
19958 SoftWrap::Bounded(soft_wrap) => {
19959 wrap_guides.push((soft_wrap as usize, true));
19960 }
19961 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19962 }
19963 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19964 }
19965
19966 wrap_guides
19967 }
19968
19969 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19970 let settings = self.buffer.read(cx).language_settings(cx);
19971 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19972 match mode {
19973 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19974 SoftWrap::None
19975 }
19976 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19977 language_settings::SoftWrap::PreferredLineLength => {
19978 SoftWrap::Column(settings.preferred_line_length)
19979 }
19980 language_settings::SoftWrap::Bounded => {
19981 SoftWrap::Bounded(settings.preferred_line_length)
19982 }
19983 }
19984 }
19985
19986 pub fn set_soft_wrap_mode(
19987 &mut self,
19988 mode: language_settings::SoftWrap,
19989
19990 cx: &mut Context<Self>,
19991 ) {
19992 self.soft_wrap_mode_override = Some(mode);
19993 cx.notify();
19994 }
19995
19996 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19997 self.hard_wrap = hard_wrap;
19998 cx.notify();
19999 }
20000
20001 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20002 self.text_style_refinement = Some(style);
20003 }
20004
20005 /// called by the Element so we know what style we were most recently rendered with.
20006 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20007 // We intentionally do not inform the display map about the minimap style
20008 // so that wrapping is not recalculated and stays consistent for the editor
20009 // and its linked minimap.
20010 if !self.mode.is_minimap() {
20011 let font = style.text.font();
20012 let font_size = style.text.font_size.to_pixels(window.rem_size());
20013 let display_map = self
20014 .placeholder_display_map
20015 .as_ref()
20016 .filter(|_| self.is_empty(cx))
20017 .unwrap_or(&self.display_map);
20018
20019 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20020 }
20021 self.style = Some(style);
20022 }
20023
20024 pub fn style(&self) -> Option<&EditorStyle> {
20025 self.style.as_ref()
20026 }
20027
20028 // Called by the element. This method is not designed to be called outside of the editor
20029 // element's layout code because it does not notify when rewrapping is computed synchronously.
20030 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20031 if self.is_empty(cx) {
20032 self.placeholder_display_map
20033 .as_ref()
20034 .map_or(false, |display_map| {
20035 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20036 })
20037 } else {
20038 self.display_map
20039 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20040 }
20041 }
20042
20043 pub fn set_soft_wrap(&mut self) {
20044 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20045 }
20046
20047 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20048 if self.soft_wrap_mode_override.is_some() {
20049 self.soft_wrap_mode_override.take();
20050 } else {
20051 let soft_wrap = match self.soft_wrap_mode(cx) {
20052 SoftWrap::GitDiff => return,
20053 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20054 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20055 language_settings::SoftWrap::None
20056 }
20057 };
20058 self.soft_wrap_mode_override = Some(soft_wrap);
20059 }
20060 cx.notify();
20061 }
20062
20063 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20064 let Some(workspace) = self.workspace() else {
20065 return;
20066 };
20067 let fs = workspace.read(cx).app_state().fs.clone();
20068 let current_show = TabBarSettings::get_global(cx).show;
20069 update_settings_file(fs, cx, move |setting, _| {
20070 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20071 });
20072 }
20073
20074 pub fn toggle_indent_guides(
20075 &mut self,
20076 _: &ToggleIndentGuides,
20077 _: &mut Window,
20078 cx: &mut Context<Self>,
20079 ) {
20080 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20081 self.buffer
20082 .read(cx)
20083 .language_settings(cx)
20084 .indent_guides
20085 .enabled
20086 });
20087 self.show_indent_guides = Some(!currently_enabled);
20088 cx.notify();
20089 }
20090
20091 fn should_show_indent_guides(&self) -> Option<bool> {
20092 self.show_indent_guides
20093 }
20094
20095 pub fn disable_indent_guides_for_buffer(
20096 &mut self,
20097 buffer_id: BufferId,
20098 cx: &mut Context<Self>,
20099 ) {
20100 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20101 cx.notify();
20102 }
20103
20104 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20105 self.buffers_with_disabled_indent_guides
20106 .contains(&buffer_id)
20107 }
20108
20109 pub fn toggle_line_numbers(
20110 &mut self,
20111 _: &ToggleLineNumbers,
20112 _: &mut Window,
20113 cx: &mut Context<Self>,
20114 ) {
20115 let mut editor_settings = EditorSettings::get_global(cx).clone();
20116 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20117 EditorSettings::override_global(editor_settings, cx);
20118 }
20119
20120 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20121 if let Some(show_line_numbers) = self.show_line_numbers {
20122 return show_line_numbers;
20123 }
20124 EditorSettings::get_global(cx).gutter.line_numbers
20125 }
20126
20127 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20128 match (
20129 self.use_relative_line_numbers,
20130 EditorSettings::get_global(cx).relative_line_numbers,
20131 ) {
20132 (None, setting) => setting,
20133 (Some(false), _) => RelativeLineNumbers::Disabled,
20134 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20135 (Some(true), _) => RelativeLineNumbers::Enabled,
20136 }
20137 }
20138
20139 pub fn toggle_relative_line_numbers(
20140 &mut self,
20141 _: &ToggleRelativeLineNumbers,
20142 _: &mut Window,
20143 cx: &mut Context<Self>,
20144 ) {
20145 let is_relative = self.relative_line_numbers(cx);
20146 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20147 }
20148
20149 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20150 self.use_relative_line_numbers = is_relative;
20151 cx.notify();
20152 }
20153
20154 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20155 self.show_gutter = show_gutter;
20156 cx.notify();
20157 }
20158
20159 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20160 self.show_scrollbars = ScrollbarAxes {
20161 horizontal: show,
20162 vertical: show,
20163 };
20164 cx.notify();
20165 }
20166
20167 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20168 self.show_scrollbars.vertical = show;
20169 cx.notify();
20170 }
20171
20172 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20173 self.show_scrollbars.horizontal = show;
20174 cx.notify();
20175 }
20176
20177 pub fn set_minimap_visibility(
20178 &mut self,
20179 minimap_visibility: MinimapVisibility,
20180 window: &mut Window,
20181 cx: &mut Context<Self>,
20182 ) {
20183 if self.minimap_visibility != minimap_visibility {
20184 if minimap_visibility.visible() && self.minimap.is_none() {
20185 let minimap_settings = EditorSettings::get_global(cx).minimap;
20186 self.minimap =
20187 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20188 }
20189 self.minimap_visibility = minimap_visibility;
20190 cx.notify();
20191 }
20192 }
20193
20194 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20195 self.set_show_scrollbars(false, cx);
20196 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20197 }
20198
20199 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20200 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20201 }
20202
20203 /// Normally the text in full mode and auto height editors is padded on the
20204 /// left side by roughly half a character width for improved hit testing.
20205 ///
20206 /// Use this method to disable this for cases where this is not wanted (e.g.
20207 /// if you want to align the editor text with some other text above or below)
20208 /// or if you want to add this padding to single-line editors.
20209 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20210 self.offset_content = offset_content;
20211 cx.notify();
20212 }
20213
20214 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20215 self.show_line_numbers = Some(show_line_numbers);
20216 cx.notify();
20217 }
20218
20219 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20220 self.disable_expand_excerpt_buttons = true;
20221 cx.notify();
20222 }
20223
20224 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20225 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20226 cx.notify();
20227 }
20228
20229 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20230 self.show_code_actions = Some(show_code_actions);
20231 cx.notify();
20232 }
20233
20234 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20235 self.show_runnables = Some(show_runnables);
20236 cx.notify();
20237 }
20238
20239 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20240 self.show_breakpoints = Some(show_breakpoints);
20241 cx.notify();
20242 }
20243
20244 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20245 if self.display_map.read(cx).masked != masked {
20246 self.display_map.update(cx, |map, _| map.masked = masked);
20247 }
20248 cx.notify()
20249 }
20250
20251 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20252 self.show_wrap_guides = Some(show_wrap_guides);
20253 cx.notify();
20254 }
20255
20256 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20257 self.show_indent_guides = Some(show_indent_guides);
20258 cx.notify();
20259 }
20260
20261 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20262 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20263 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20264 && let Some(dir) = file.abs_path(cx).parent()
20265 {
20266 return Some(dir.to_owned());
20267 }
20268 }
20269
20270 None
20271 }
20272
20273 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20274 self.active_excerpt(cx)?
20275 .1
20276 .read(cx)
20277 .file()
20278 .and_then(|f| f.as_local())
20279 }
20280
20281 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20282 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20283 let buffer = buffer.read(cx);
20284 if let Some(project_path) = buffer.project_path(cx) {
20285 let project = self.project()?.read(cx);
20286 project.absolute_path(&project_path, cx)
20287 } else {
20288 buffer
20289 .file()
20290 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20291 }
20292 })
20293 }
20294
20295 pub fn reveal_in_finder(
20296 &mut self,
20297 _: &RevealInFileManager,
20298 _window: &mut Window,
20299 cx: &mut Context<Self>,
20300 ) {
20301 if let Some(target) = self.target_file(cx) {
20302 cx.reveal_path(&target.abs_path(cx));
20303 }
20304 }
20305
20306 pub fn copy_path(
20307 &mut self,
20308 _: &zed_actions::workspace::CopyPath,
20309 _window: &mut Window,
20310 cx: &mut Context<Self>,
20311 ) {
20312 if let Some(path) = self.target_file_abs_path(cx)
20313 && let Some(path) = path.to_str()
20314 {
20315 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20316 } else {
20317 cx.propagate();
20318 }
20319 }
20320
20321 pub fn copy_relative_path(
20322 &mut self,
20323 _: &zed_actions::workspace::CopyRelativePath,
20324 _window: &mut Window,
20325 cx: &mut Context<Self>,
20326 ) {
20327 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20328 let project = self.project()?.read(cx);
20329 let path = buffer.read(cx).file()?.path();
20330 let path = path.display(project.path_style(cx));
20331 Some(path)
20332 }) {
20333 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20334 } else {
20335 cx.propagate();
20336 }
20337 }
20338
20339 /// Returns the project path for the editor's buffer, if any buffer is
20340 /// opened in the editor.
20341 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20342 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20343 buffer.read(cx).project_path(cx)
20344 } else {
20345 None
20346 }
20347 }
20348
20349 // Returns true if the editor handled a go-to-line request
20350 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20351 maybe!({
20352 let breakpoint_store = self.breakpoint_store.as_ref()?;
20353
20354 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20355 else {
20356 self.clear_row_highlights::<ActiveDebugLine>();
20357 return None;
20358 };
20359
20360 let position = active_stack_frame.position;
20361 let buffer_id = position.buffer_id?;
20362 let snapshot = self
20363 .project
20364 .as_ref()?
20365 .read(cx)
20366 .buffer_for_id(buffer_id, cx)?
20367 .read(cx)
20368 .snapshot();
20369
20370 let mut handled = false;
20371 for (id, ExcerptRange { context, .. }) in
20372 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20373 {
20374 if context.start.cmp(&position, &snapshot).is_ge()
20375 || context.end.cmp(&position, &snapshot).is_lt()
20376 {
20377 continue;
20378 }
20379 let snapshot = self.buffer.read(cx).snapshot(cx);
20380 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20381
20382 handled = true;
20383 self.clear_row_highlights::<ActiveDebugLine>();
20384
20385 self.go_to_line::<ActiveDebugLine>(
20386 multibuffer_anchor,
20387 Some(cx.theme().colors().editor_debugger_active_line_background),
20388 window,
20389 cx,
20390 );
20391
20392 cx.notify();
20393 }
20394
20395 handled.then_some(())
20396 })
20397 .is_some()
20398 }
20399
20400 pub fn copy_file_name_without_extension(
20401 &mut self,
20402 _: &CopyFileNameWithoutExtension,
20403 _: &mut Window,
20404 cx: &mut Context<Self>,
20405 ) {
20406 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20407 let file = buffer.read(cx).file()?;
20408 file.path().file_stem()
20409 }) {
20410 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20411 }
20412 }
20413
20414 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20415 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20416 let file = buffer.read(cx).file()?;
20417 Some(file.file_name(cx))
20418 }) {
20419 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20420 }
20421 }
20422
20423 pub fn toggle_git_blame(
20424 &mut self,
20425 _: &::git::Blame,
20426 window: &mut Window,
20427 cx: &mut Context<Self>,
20428 ) {
20429 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20430
20431 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20432 self.start_git_blame(true, window, cx);
20433 }
20434
20435 cx.notify();
20436 }
20437
20438 pub fn toggle_git_blame_inline(
20439 &mut self,
20440 _: &ToggleGitBlameInline,
20441 window: &mut Window,
20442 cx: &mut Context<Self>,
20443 ) {
20444 self.toggle_git_blame_inline_internal(true, window, cx);
20445 cx.notify();
20446 }
20447
20448 pub fn open_git_blame_commit(
20449 &mut self,
20450 _: &OpenGitBlameCommit,
20451 window: &mut Window,
20452 cx: &mut Context<Self>,
20453 ) {
20454 self.open_git_blame_commit_internal(window, cx);
20455 }
20456
20457 fn open_git_blame_commit_internal(
20458 &mut self,
20459 window: &mut Window,
20460 cx: &mut Context<Self>,
20461 ) -> Option<()> {
20462 let blame = self.blame.as_ref()?;
20463 let snapshot = self.snapshot(window, cx);
20464 let cursor = self
20465 .selections
20466 .newest::<Point>(&snapshot.display_snapshot)
20467 .head();
20468 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20469 let (_, blame_entry) = blame
20470 .update(cx, |blame, cx| {
20471 blame
20472 .blame_for_rows(
20473 &[RowInfo {
20474 buffer_id: Some(buffer.remote_id()),
20475 buffer_row: Some(point.row),
20476 ..Default::default()
20477 }],
20478 cx,
20479 )
20480 .next()
20481 })
20482 .flatten()?;
20483 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20484 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20485 let workspace = self.workspace()?.downgrade();
20486 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20487 None
20488 }
20489
20490 pub fn git_blame_inline_enabled(&self) -> bool {
20491 self.git_blame_inline_enabled
20492 }
20493
20494 pub fn toggle_selection_menu(
20495 &mut self,
20496 _: &ToggleSelectionMenu,
20497 _: &mut Window,
20498 cx: &mut Context<Self>,
20499 ) {
20500 self.show_selection_menu = self
20501 .show_selection_menu
20502 .map(|show_selections_menu| !show_selections_menu)
20503 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20504
20505 cx.notify();
20506 }
20507
20508 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20509 self.show_selection_menu
20510 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20511 }
20512
20513 fn start_git_blame(
20514 &mut self,
20515 user_triggered: bool,
20516 window: &mut Window,
20517 cx: &mut Context<Self>,
20518 ) {
20519 if let Some(project) = self.project() {
20520 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20521 && buffer.read(cx).file().is_none()
20522 {
20523 return;
20524 }
20525
20526 let focused = self.focus_handle(cx).contains_focused(window, cx);
20527
20528 let project = project.clone();
20529 let blame = cx
20530 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20531 self.blame_subscription =
20532 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20533 self.blame = Some(blame);
20534 }
20535 }
20536
20537 fn toggle_git_blame_inline_internal(
20538 &mut self,
20539 user_triggered: bool,
20540 window: &mut Window,
20541 cx: &mut Context<Self>,
20542 ) {
20543 if self.git_blame_inline_enabled {
20544 self.git_blame_inline_enabled = false;
20545 self.show_git_blame_inline = false;
20546 self.show_git_blame_inline_delay_task.take();
20547 } else {
20548 self.git_blame_inline_enabled = true;
20549 self.start_git_blame_inline(user_triggered, window, cx);
20550 }
20551
20552 cx.notify();
20553 }
20554
20555 fn start_git_blame_inline(
20556 &mut self,
20557 user_triggered: bool,
20558 window: &mut Window,
20559 cx: &mut Context<Self>,
20560 ) {
20561 self.start_git_blame(user_triggered, window, cx);
20562
20563 if ProjectSettings::get_global(cx)
20564 .git
20565 .inline_blame_delay()
20566 .is_some()
20567 {
20568 self.start_inline_blame_timer(window, cx);
20569 } else {
20570 self.show_git_blame_inline = true
20571 }
20572 }
20573
20574 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20575 self.blame.as_ref()
20576 }
20577
20578 pub fn show_git_blame_gutter(&self) -> bool {
20579 self.show_git_blame_gutter
20580 }
20581
20582 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20583 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20584 }
20585
20586 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20587 self.show_git_blame_inline
20588 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20589 && !self.newest_selection_head_on_empty_line(cx)
20590 && self.has_blame_entries(cx)
20591 }
20592
20593 fn has_blame_entries(&self, cx: &App) -> bool {
20594 self.blame()
20595 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20596 }
20597
20598 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20599 let cursor_anchor = self.selections.newest_anchor().head();
20600
20601 let snapshot = self.buffer.read(cx).snapshot(cx);
20602 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20603
20604 snapshot.line_len(buffer_row) == 0
20605 }
20606
20607 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20608 let buffer_and_selection = maybe!({
20609 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20610 let selection_range = selection.range();
20611
20612 let multi_buffer = self.buffer().read(cx);
20613 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20614 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20615
20616 let (buffer, range, _) = if selection.reversed {
20617 buffer_ranges.first()
20618 } else {
20619 buffer_ranges.last()
20620 }?;
20621
20622 let selection = text::ToPoint::to_point(&range.start, buffer).row
20623 ..text::ToPoint::to_point(&range.end, buffer).row;
20624 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20625 });
20626
20627 let Some((buffer, selection)) = buffer_and_selection else {
20628 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20629 };
20630
20631 let Some(project) = self.project() else {
20632 return Task::ready(Err(anyhow!("editor does not have project")));
20633 };
20634
20635 project.update(cx, |project, cx| {
20636 project.get_permalink_to_line(&buffer, selection, cx)
20637 })
20638 }
20639
20640 pub fn copy_permalink_to_line(
20641 &mut self,
20642 _: &CopyPermalinkToLine,
20643 window: &mut Window,
20644 cx: &mut Context<Self>,
20645 ) {
20646 let permalink_task = self.get_permalink_to_line(cx);
20647 let workspace = self.workspace();
20648
20649 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20650 Ok(permalink) => {
20651 cx.update(|_, cx| {
20652 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20653 })
20654 .ok();
20655 }
20656 Err(err) => {
20657 let message = format!("Failed to copy permalink: {err}");
20658
20659 anyhow::Result::<()>::Err(err).log_err();
20660
20661 if let Some(workspace) = workspace {
20662 workspace
20663 .update_in(cx, |workspace, _, cx| {
20664 struct CopyPermalinkToLine;
20665
20666 workspace.show_toast(
20667 Toast::new(
20668 NotificationId::unique::<CopyPermalinkToLine>(),
20669 message,
20670 ),
20671 cx,
20672 )
20673 })
20674 .ok();
20675 }
20676 }
20677 })
20678 .detach();
20679 }
20680
20681 pub fn copy_file_location(
20682 &mut self,
20683 _: &CopyFileLocation,
20684 _: &mut Window,
20685 cx: &mut Context<Self>,
20686 ) {
20687 let selection = self
20688 .selections
20689 .newest::<Point>(&self.display_snapshot(cx))
20690 .start
20691 .row
20692 + 1;
20693 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20694 let project = self.project()?.read(cx);
20695 let file = buffer.read(cx).file()?;
20696 let path = file.path().display(project.path_style(cx));
20697
20698 Some(format!("{path}:{selection}"))
20699 }) {
20700 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
20701 }
20702 }
20703
20704 pub fn open_permalink_to_line(
20705 &mut self,
20706 _: &OpenPermalinkToLine,
20707 window: &mut Window,
20708 cx: &mut Context<Self>,
20709 ) {
20710 let permalink_task = self.get_permalink_to_line(cx);
20711 let workspace = self.workspace();
20712
20713 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20714 Ok(permalink) => {
20715 cx.update(|_, cx| {
20716 cx.open_url(permalink.as_ref());
20717 })
20718 .ok();
20719 }
20720 Err(err) => {
20721 let message = format!("Failed to open permalink: {err}");
20722
20723 anyhow::Result::<()>::Err(err).log_err();
20724
20725 if let Some(workspace) = workspace {
20726 workspace
20727 .update(cx, |workspace, cx| {
20728 struct OpenPermalinkToLine;
20729
20730 workspace.show_toast(
20731 Toast::new(
20732 NotificationId::unique::<OpenPermalinkToLine>(),
20733 message,
20734 ),
20735 cx,
20736 )
20737 })
20738 .ok();
20739 }
20740 }
20741 })
20742 .detach();
20743 }
20744
20745 pub fn insert_uuid_v4(
20746 &mut self,
20747 _: &InsertUuidV4,
20748 window: &mut Window,
20749 cx: &mut Context<Self>,
20750 ) {
20751 self.insert_uuid(UuidVersion::V4, window, cx);
20752 }
20753
20754 pub fn insert_uuid_v7(
20755 &mut self,
20756 _: &InsertUuidV7,
20757 window: &mut Window,
20758 cx: &mut Context<Self>,
20759 ) {
20760 self.insert_uuid(UuidVersion::V7, window, cx);
20761 }
20762
20763 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20764 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20765 self.transact(window, cx, |this, window, cx| {
20766 let edits = this
20767 .selections
20768 .all::<Point>(&this.display_snapshot(cx))
20769 .into_iter()
20770 .map(|selection| {
20771 let uuid = match version {
20772 UuidVersion::V4 => uuid::Uuid::new_v4(),
20773 UuidVersion::V7 => uuid::Uuid::now_v7(),
20774 };
20775
20776 (selection.range(), uuid.to_string())
20777 });
20778 this.edit(edits, cx);
20779 this.refresh_edit_prediction(true, false, window, cx);
20780 });
20781 }
20782
20783 pub fn open_selections_in_multibuffer(
20784 &mut self,
20785 _: &OpenSelectionsInMultibuffer,
20786 window: &mut Window,
20787 cx: &mut Context<Self>,
20788 ) {
20789 let multibuffer = self.buffer.read(cx);
20790
20791 let Some(buffer) = multibuffer.as_singleton() else {
20792 return;
20793 };
20794
20795 let Some(workspace) = self.workspace() else {
20796 return;
20797 };
20798
20799 let title = multibuffer.title(cx).to_string();
20800
20801 let locations = self
20802 .selections
20803 .all_anchors(&self.display_snapshot(cx))
20804 .iter()
20805 .map(|selection| {
20806 (
20807 buffer.clone(),
20808 (selection.start.text_anchor..selection.end.text_anchor)
20809 .to_point(buffer.read(cx)),
20810 )
20811 })
20812 .into_group_map();
20813
20814 cx.spawn_in(window, async move |_, cx| {
20815 workspace.update_in(cx, |workspace, window, cx| {
20816 Self::open_locations_in_multibuffer(
20817 workspace,
20818 locations,
20819 format!("Selections for '{title}'"),
20820 false,
20821 false,
20822 MultibufferSelectionMode::All,
20823 window,
20824 cx,
20825 );
20826 })
20827 })
20828 .detach();
20829 }
20830
20831 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20832 /// last highlight added will be used.
20833 ///
20834 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20835 pub fn highlight_rows<T: 'static>(
20836 &mut self,
20837 range: Range<Anchor>,
20838 color: Hsla,
20839 options: RowHighlightOptions,
20840 cx: &mut Context<Self>,
20841 ) {
20842 let snapshot = self.buffer().read(cx).snapshot(cx);
20843 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20844 let ix = row_highlights.binary_search_by(|highlight| {
20845 Ordering::Equal
20846 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20847 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20848 });
20849
20850 if let Err(mut ix) = ix {
20851 let index = post_inc(&mut self.highlight_order);
20852
20853 // If this range intersects with the preceding highlight, then merge it with
20854 // the preceding highlight. Otherwise insert a new highlight.
20855 let mut merged = false;
20856 if ix > 0 {
20857 let prev_highlight = &mut row_highlights[ix - 1];
20858 if prev_highlight
20859 .range
20860 .end
20861 .cmp(&range.start, &snapshot)
20862 .is_ge()
20863 {
20864 ix -= 1;
20865 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20866 prev_highlight.range.end = range.end;
20867 }
20868 merged = true;
20869 prev_highlight.index = index;
20870 prev_highlight.color = color;
20871 prev_highlight.options = options;
20872 }
20873 }
20874
20875 if !merged {
20876 row_highlights.insert(
20877 ix,
20878 RowHighlight {
20879 range,
20880 index,
20881 color,
20882 options,
20883 type_id: TypeId::of::<T>(),
20884 },
20885 );
20886 }
20887
20888 // If any of the following highlights intersect with this one, merge them.
20889 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20890 let highlight = &row_highlights[ix];
20891 if next_highlight
20892 .range
20893 .start
20894 .cmp(&highlight.range.end, &snapshot)
20895 .is_le()
20896 {
20897 if next_highlight
20898 .range
20899 .end
20900 .cmp(&highlight.range.end, &snapshot)
20901 .is_gt()
20902 {
20903 row_highlights[ix].range.end = next_highlight.range.end;
20904 }
20905 row_highlights.remove(ix + 1);
20906 } else {
20907 break;
20908 }
20909 }
20910 }
20911 }
20912
20913 /// Remove any highlighted row ranges of the given type that intersect the
20914 /// given ranges.
20915 pub fn remove_highlighted_rows<T: 'static>(
20916 &mut self,
20917 ranges_to_remove: Vec<Range<Anchor>>,
20918 cx: &mut Context<Self>,
20919 ) {
20920 let snapshot = self.buffer().read(cx).snapshot(cx);
20921 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20922 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20923 row_highlights.retain(|highlight| {
20924 while let Some(range_to_remove) = ranges_to_remove.peek() {
20925 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20926 Ordering::Less | Ordering::Equal => {
20927 ranges_to_remove.next();
20928 }
20929 Ordering::Greater => {
20930 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20931 Ordering::Less | Ordering::Equal => {
20932 return false;
20933 }
20934 Ordering::Greater => break,
20935 }
20936 }
20937 }
20938 }
20939
20940 true
20941 })
20942 }
20943
20944 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20945 pub fn clear_row_highlights<T: 'static>(&mut self) {
20946 self.highlighted_rows.remove(&TypeId::of::<T>());
20947 }
20948
20949 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20950 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20951 self.highlighted_rows
20952 .get(&TypeId::of::<T>())
20953 .map_or(&[] as &[_], |vec| vec.as_slice())
20954 .iter()
20955 .map(|highlight| (highlight.range.clone(), highlight.color))
20956 }
20957
20958 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20959 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20960 /// Allows to ignore certain kinds of highlights.
20961 pub fn highlighted_display_rows(
20962 &self,
20963 window: &mut Window,
20964 cx: &mut App,
20965 ) -> BTreeMap<DisplayRow, LineHighlight> {
20966 let snapshot = self.snapshot(window, cx);
20967 let mut used_highlight_orders = HashMap::default();
20968 self.highlighted_rows
20969 .iter()
20970 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20971 .fold(
20972 BTreeMap::<DisplayRow, LineHighlight>::new(),
20973 |mut unique_rows, highlight| {
20974 let start = highlight.range.start.to_display_point(&snapshot);
20975 let end = highlight.range.end.to_display_point(&snapshot);
20976 let start_row = start.row().0;
20977 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
20978 {
20979 end.row().0.saturating_sub(1)
20980 } else {
20981 end.row().0
20982 };
20983 for row in start_row..=end_row {
20984 let used_index =
20985 used_highlight_orders.entry(row).or_insert(highlight.index);
20986 if highlight.index >= *used_index {
20987 *used_index = highlight.index;
20988 unique_rows.insert(
20989 DisplayRow(row),
20990 LineHighlight {
20991 include_gutter: highlight.options.include_gutter,
20992 border: None,
20993 background: highlight.color.into(),
20994 type_id: Some(highlight.type_id),
20995 },
20996 );
20997 }
20998 }
20999 unique_rows
21000 },
21001 )
21002 }
21003
21004 pub fn highlighted_display_row_for_autoscroll(
21005 &self,
21006 snapshot: &DisplaySnapshot,
21007 ) -> Option<DisplayRow> {
21008 self.highlighted_rows
21009 .values()
21010 .flat_map(|highlighted_rows| highlighted_rows.iter())
21011 .filter_map(|highlight| {
21012 if highlight.options.autoscroll {
21013 Some(highlight.range.start.to_display_point(snapshot).row())
21014 } else {
21015 None
21016 }
21017 })
21018 .min()
21019 }
21020
21021 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21022 self.highlight_background::<SearchWithinRange>(
21023 ranges,
21024 |_, colors| colors.colors().editor_document_highlight_read_background,
21025 cx,
21026 )
21027 }
21028
21029 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21030 self.breadcrumb_header = Some(new_header);
21031 }
21032
21033 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21034 self.clear_background_highlights::<SearchWithinRange>(cx);
21035 }
21036
21037 pub fn highlight_background<T: 'static>(
21038 &mut self,
21039 ranges: &[Range<Anchor>],
21040 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21041 cx: &mut Context<Self>,
21042 ) {
21043 self.background_highlights.insert(
21044 HighlightKey::Type(TypeId::of::<T>()),
21045 (Arc::new(color_fetcher), Arc::from(ranges)),
21046 );
21047 self.scrollbar_marker_state.dirty = true;
21048 cx.notify();
21049 }
21050
21051 pub fn highlight_background_key<T: 'static>(
21052 &mut self,
21053 key: usize,
21054 ranges: &[Range<Anchor>],
21055 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21056 cx: &mut Context<Self>,
21057 ) {
21058 self.background_highlights.insert(
21059 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21060 (Arc::new(color_fetcher), Arc::from(ranges)),
21061 );
21062 self.scrollbar_marker_state.dirty = true;
21063 cx.notify();
21064 }
21065
21066 pub fn clear_background_highlights<T: 'static>(
21067 &mut self,
21068 cx: &mut Context<Self>,
21069 ) -> Option<BackgroundHighlight> {
21070 let text_highlights = self
21071 .background_highlights
21072 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21073 if !text_highlights.1.is_empty() {
21074 self.scrollbar_marker_state.dirty = true;
21075 cx.notify();
21076 }
21077 Some(text_highlights)
21078 }
21079
21080 pub fn highlight_gutter<T: 'static>(
21081 &mut self,
21082 ranges: impl Into<Vec<Range<Anchor>>>,
21083 color_fetcher: fn(&App) -> Hsla,
21084 cx: &mut Context<Self>,
21085 ) {
21086 self.gutter_highlights
21087 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21088 cx.notify();
21089 }
21090
21091 pub fn clear_gutter_highlights<T: 'static>(
21092 &mut self,
21093 cx: &mut Context<Self>,
21094 ) -> Option<GutterHighlight> {
21095 cx.notify();
21096 self.gutter_highlights.remove(&TypeId::of::<T>())
21097 }
21098
21099 pub fn insert_gutter_highlight<T: 'static>(
21100 &mut self,
21101 range: Range<Anchor>,
21102 color_fetcher: fn(&App) -> Hsla,
21103 cx: &mut Context<Self>,
21104 ) {
21105 let snapshot = self.buffer().read(cx).snapshot(cx);
21106 let mut highlights = self
21107 .gutter_highlights
21108 .remove(&TypeId::of::<T>())
21109 .map(|(_, highlights)| highlights)
21110 .unwrap_or_default();
21111 let ix = highlights.binary_search_by(|highlight| {
21112 Ordering::Equal
21113 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21114 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21115 });
21116 if let Err(ix) = ix {
21117 highlights.insert(ix, range);
21118 }
21119 self.gutter_highlights
21120 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21121 }
21122
21123 pub fn remove_gutter_highlights<T: 'static>(
21124 &mut self,
21125 ranges_to_remove: Vec<Range<Anchor>>,
21126 cx: &mut Context<Self>,
21127 ) {
21128 let snapshot = self.buffer().read(cx).snapshot(cx);
21129 let Some((color_fetcher, mut gutter_highlights)) =
21130 self.gutter_highlights.remove(&TypeId::of::<T>())
21131 else {
21132 return;
21133 };
21134 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21135 gutter_highlights.retain(|highlight| {
21136 while let Some(range_to_remove) = ranges_to_remove.peek() {
21137 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21138 Ordering::Less | Ordering::Equal => {
21139 ranges_to_remove.next();
21140 }
21141 Ordering::Greater => {
21142 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21143 Ordering::Less | Ordering::Equal => {
21144 return false;
21145 }
21146 Ordering::Greater => break,
21147 }
21148 }
21149 }
21150 }
21151
21152 true
21153 });
21154 self.gutter_highlights
21155 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21156 }
21157
21158 #[cfg(feature = "test-support")]
21159 pub fn all_text_highlights(
21160 &self,
21161 window: &mut Window,
21162 cx: &mut Context<Self>,
21163 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21164 let snapshot = self.snapshot(window, cx);
21165 self.display_map.update(cx, |display_map, _| {
21166 display_map
21167 .all_text_highlights()
21168 .map(|highlight| {
21169 let (style, ranges) = highlight.as_ref();
21170 (
21171 *style,
21172 ranges
21173 .iter()
21174 .map(|range| range.clone().to_display_points(&snapshot))
21175 .collect(),
21176 )
21177 })
21178 .collect()
21179 })
21180 }
21181
21182 #[cfg(feature = "test-support")]
21183 pub fn all_text_background_highlights(
21184 &self,
21185 window: &mut Window,
21186 cx: &mut Context<Self>,
21187 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21188 let snapshot = self.snapshot(window, cx);
21189 let buffer = &snapshot.buffer_snapshot();
21190 let start = buffer.anchor_before(MultiBufferOffset(0));
21191 let end = buffer.anchor_after(buffer.len());
21192 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21193 }
21194
21195 #[cfg(any(test, feature = "test-support"))]
21196 pub fn sorted_background_highlights_in_range(
21197 &self,
21198 search_range: Range<Anchor>,
21199 display_snapshot: &DisplaySnapshot,
21200 theme: &Theme,
21201 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21202 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21203 res.sort_by(|a, b| {
21204 a.0.start
21205 .cmp(&b.0.start)
21206 .then_with(|| a.0.end.cmp(&b.0.end))
21207 .then_with(|| a.1.cmp(&b.1))
21208 });
21209 res
21210 }
21211
21212 #[cfg(feature = "test-support")]
21213 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21214 let snapshot = self.buffer().read(cx).snapshot(cx);
21215
21216 let highlights = self
21217 .background_highlights
21218 .get(&HighlightKey::Type(TypeId::of::<
21219 items::BufferSearchHighlights,
21220 >()));
21221
21222 if let Some((_color, ranges)) = highlights {
21223 ranges
21224 .iter()
21225 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21226 .collect_vec()
21227 } else {
21228 vec![]
21229 }
21230 }
21231
21232 fn document_highlights_for_position<'a>(
21233 &'a self,
21234 position: Anchor,
21235 buffer: &'a MultiBufferSnapshot,
21236 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21237 let read_highlights = self
21238 .background_highlights
21239 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21240 .map(|h| &h.1);
21241 let write_highlights = self
21242 .background_highlights
21243 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21244 .map(|h| &h.1);
21245 let left_position = position.bias_left(buffer);
21246 let right_position = position.bias_right(buffer);
21247 read_highlights
21248 .into_iter()
21249 .chain(write_highlights)
21250 .flat_map(move |ranges| {
21251 let start_ix = match ranges.binary_search_by(|probe| {
21252 let cmp = probe.end.cmp(&left_position, buffer);
21253 if cmp.is_ge() {
21254 Ordering::Greater
21255 } else {
21256 Ordering::Less
21257 }
21258 }) {
21259 Ok(i) | Err(i) => i,
21260 };
21261
21262 ranges[start_ix..]
21263 .iter()
21264 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21265 })
21266 }
21267
21268 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21269 self.background_highlights
21270 .get(&HighlightKey::Type(TypeId::of::<T>()))
21271 .is_some_and(|(_, highlights)| !highlights.is_empty())
21272 }
21273
21274 /// Returns all background highlights for a given range.
21275 ///
21276 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21277 pub fn background_highlights_in_range(
21278 &self,
21279 search_range: Range<Anchor>,
21280 display_snapshot: &DisplaySnapshot,
21281 theme: &Theme,
21282 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21283 let mut results = Vec::new();
21284 for (color_fetcher, ranges) in self.background_highlights.values() {
21285 let start_ix = match ranges.binary_search_by(|probe| {
21286 let cmp = probe
21287 .end
21288 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21289 if cmp.is_gt() {
21290 Ordering::Greater
21291 } else {
21292 Ordering::Less
21293 }
21294 }) {
21295 Ok(i) | Err(i) => i,
21296 };
21297 for (index, range) in ranges[start_ix..].iter().enumerate() {
21298 if range
21299 .start
21300 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21301 .is_ge()
21302 {
21303 break;
21304 }
21305
21306 let color = color_fetcher(&(start_ix + index), theme);
21307 let start = range.start.to_display_point(display_snapshot);
21308 let end = range.end.to_display_point(display_snapshot);
21309 results.push((start..end, color))
21310 }
21311 }
21312 results
21313 }
21314
21315 pub fn gutter_highlights_in_range(
21316 &self,
21317 search_range: Range<Anchor>,
21318 display_snapshot: &DisplaySnapshot,
21319 cx: &App,
21320 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21321 let mut results = Vec::new();
21322 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21323 let color = color_fetcher(cx);
21324 let start_ix = match ranges.binary_search_by(|probe| {
21325 let cmp = probe
21326 .end
21327 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21328 if cmp.is_gt() {
21329 Ordering::Greater
21330 } else {
21331 Ordering::Less
21332 }
21333 }) {
21334 Ok(i) | Err(i) => i,
21335 };
21336 for range in &ranges[start_ix..] {
21337 if range
21338 .start
21339 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21340 .is_ge()
21341 {
21342 break;
21343 }
21344
21345 let start = range.start.to_display_point(display_snapshot);
21346 let end = range.end.to_display_point(display_snapshot);
21347 results.push((start..end, color))
21348 }
21349 }
21350 results
21351 }
21352
21353 /// Get the text ranges corresponding to the redaction query
21354 pub fn redacted_ranges(
21355 &self,
21356 search_range: Range<Anchor>,
21357 display_snapshot: &DisplaySnapshot,
21358 cx: &App,
21359 ) -> Vec<Range<DisplayPoint>> {
21360 display_snapshot
21361 .buffer_snapshot()
21362 .redacted_ranges(search_range, |file| {
21363 if let Some(file) = file {
21364 file.is_private()
21365 && EditorSettings::get(
21366 Some(SettingsLocation {
21367 worktree_id: file.worktree_id(cx),
21368 path: file.path().as_ref(),
21369 }),
21370 cx,
21371 )
21372 .redact_private_values
21373 } else {
21374 false
21375 }
21376 })
21377 .map(|range| {
21378 range.start.to_display_point(display_snapshot)
21379 ..range.end.to_display_point(display_snapshot)
21380 })
21381 .collect()
21382 }
21383
21384 pub fn highlight_text_key<T: 'static>(
21385 &mut self,
21386 key: usize,
21387 ranges: Vec<Range<Anchor>>,
21388 style: HighlightStyle,
21389 merge: bool,
21390 cx: &mut Context<Self>,
21391 ) {
21392 self.display_map.update(cx, |map, cx| {
21393 map.highlight_text(
21394 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21395 ranges,
21396 style,
21397 merge,
21398 cx,
21399 );
21400 });
21401 cx.notify();
21402 }
21403
21404 pub fn highlight_text<T: 'static>(
21405 &mut self,
21406 ranges: Vec<Range<Anchor>>,
21407 style: HighlightStyle,
21408 cx: &mut Context<Self>,
21409 ) {
21410 self.display_map.update(cx, |map, cx| {
21411 map.highlight_text(
21412 HighlightKey::Type(TypeId::of::<T>()),
21413 ranges,
21414 style,
21415 false,
21416 cx,
21417 )
21418 });
21419 cx.notify();
21420 }
21421
21422 pub fn text_highlights<'a, T: 'static>(
21423 &'a self,
21424 cx: &'a App,
21425 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21426 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21427 }
21428
21429 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21430 let cleared = self
21431 .display_map
21432 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21433 if cleared {
21434 cx.notify();
21435 }
21436 }
21437
21438 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21439 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21440 && self.focus_handle.is_focused(window)
21441 }
21442
21443 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21444 self.show_cursor_when_unfocused = is_enabled;
21445 cx.notify();
21446 }
21447
21448 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21449 cx.notify();
21450 }
21451
21452 fn on_debug_session_event(
21453 &mut self,
21454 _session: Entity<Session>,
21455 event: &SessionEvent,
21456 cx: &mut Context<Self>,
21457 ) {
21458 if let SessionEvent::InvalidateInlineValue = event {
21459 self.refresh_inline_values(cx);
21460 }
21461 }
21462
21463 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21464 let Some(project) = self.project.clone() else {
21465 return;
21466 };
21467
21468 if !self.inline_value_cache.enabled {
21469 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21470 self.splice_inlays(&inlays, Vec::new(), cx);
21471 return;
21472 }
21473
21474 let current_execution_position = self
21475 .highlighted_rows
21476 .get(&TypeId::of::<ActiveDebugLine>())
21477 .and_then(|lines| lines.last().map(|line| line.range.end));
21478
21479 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21480 let inline_values = editor
21481 .update(cx, |editor, cx| {
21482 let Some(current_execution_position) = current_execution_position else {
21483 return Some(Task::ready(Ok(Vec::new())));
21484 };
21485
21486 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21487 let snapshot = buffer.snapshot(cx);
21488
21489 let excerpt = snapshot.excerpt_containing(
21490 current_execution_position..current_execution_position,
21491 )?;
21492
21493 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21494 })?;
21495
21496 let range =
21497 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21498
21499 project.inline_values(buffer, range, cx)
21500 })
21501 .ok()
21502 .flatten()?
21503 .await
21504 .context("refreshing debugger inlays")
21505 .log_err()?;
21506
21507 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21508
21509 for (buffer_id, inline_value) in inline_values
21510 .into_iter()
21511 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21512 {
21513 buffer_inline_values
21514 .entry(buffer_id)
21515 .or_default()
21516 .push(inline_value);
21517 }
21518
21519 editor
21520 .update(cx, |editor, cx| {
21521 let snapshot = editor.buffer.read(cx).snapshot(cx);
21522 let mut new_inlays = Vec::default();
21523
21524 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21525 let buffer_id = buffer_snapshot.remote_id();
21526 buffer_inline_values
21527 .get(&buffer_id)
21528 .into_iter()
21529 .flatten()
21530 .for_each(|hint| {
21531 let inlay = Inlay::debugger(
21532 post_inc(&mut editor.next_inlay_id),
21533 Anchor::in_buffer(excerpt_id, hint.position),
21534 hint.text(),
21535 );
21536 if !inlay.text().chars().contains(&'\n') {
21537 new_inlays.push(inlay);
21538 }
21539 });
21540 }
21541
21542 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21543 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21544
21545 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21546 })
21547 .ok()?;
21548 Some(())
21549 });
21550 }
21551
21552 fn on_buffer_event(
21553 &mut self,
21554 multibuffer: &Entity<MultiBuffer>,
21555 event: &multi_buffer::Event,
21556 window: &mut Window,
21557 cx: &mut Context<Self>,
21558 ) {
21559 match event {
21560 multi_buffer::Event::Edited { edited_buffer } => {
21561 self.scrollbar_marker_state.dirty = true;
21562 self.active_indent_guides_state.dirty = true;
21563 self.refresh_active_diagnostics(cx);
21564 self.refresh_code_actions(window, cx);
21565 self.refresh_single_line_folds(window, cx);
21566 self.refresh_matching_bracket_highlights(window, cx);
21567 if self.has_active_edit_prediction() {
21568 self.update_visible_edit_prediction(window, cx);
21569 }
21570
21571 if let Some(buffer) = edited_buffer {
21572 if buffer.read(cx).file().is_none() {
21573 cx.emit(EditorEvent::TitleChanged);
21574 }
21575
21576 if self.project.is_some() {
21577 let buffer_id = buffer.read(cx).remote_id();
21578 self.register_buffer(buffer_id, cx);
21579 self.update_lsp_data(Some(buffer_id), window, cx);
21580 self.refresh_inlay_hints(
21581 InlayHintRefreshReason::BufferEdited(buffer_id),
21582 cx,
21583 );
21584 }
21585 }
21586
21587 cx.emit(EditorEvent::BufferEdited);
21588 cx.emit(SearchEvent::MatchesInvalidated);
21589
21590 let Some(project) = &self.project else { return };
21591 let (telemetry, is_via_ssh) = {
21592 let project = project.read(cx);
21593 let telemetry = project.client().telemetry().clone();
21594 let is_via_ssh = project.is_via_remote_server();
21595 (telemetry, is_via_ssh)
21596 };
21597 telemetry.log_edit_event("editor", is_via_ssh);
21598 }
21599 multi_buffer::Event::ExcerptsAdded {
21600 buffer,
21601 predecessor,
21602 excerpts,
21603 } => {
21604 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21605 let buffer_id = buffer.read(cx).remote_id();
21606 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21607 && let Some(project) = &self.project
21608 {
21609 update_uncommitted_diff_for_buffer(
21610 cx.entity(),
21611 project,
21612 [buffer.clone()],
21613 self.buffer.clone(),
21614 cx,
21615 )
21616 .detach();
21617 }
21618 self.update_lsp_data(Some(buffer_id), window, cx);
21619 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21620 self.colorize_brackets(false, cx);
21621 cx.emit(EditorEvent::ExcerptsAdded {
21622 buffer: buffer.clone(),
21623 predecessor: *predecessor,
21624 excerpts: excerpts.clone(),
21625 });
21626 }
21627 multi_buffer::Event::ExcerptsRemoved {
21628 ids,
21629 removed_buffer_ids,
21630 } => {
21631 if let Some(inlay_hints) = &mut self.inlay_hints {
21632 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21633 }
21634 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21635 for buffer_id in removed_buffer_ids {
21636 self.registered_buffers.remove(buffer_id);
21637 }
21638 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21639 cx.emit(EditorEvent::ExcerptsRemoved {
21640 ids: ids.clone(),
21641 removed_buffer_ids: removed_buffer_ids.clone(),
21642 });
21643 }
21644 multi_buffer::Event::ExcerptsEdited {
21645 excerpt_ids,
21646 buffer_ids,
21647 } => {
21648 self.display_map.update(cx, |map, cx| {
21649 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21650 });
21651 cx.emit(EditorEvent::ExcerptsEdited {
21652 ids: excerpt_ids.clone(),
21653 });
21654 }
21655 multi_buffer::Event::ExcerptsExpanded { ids } => {
21656 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21657 self.refresh_document_highlights(cx);
21658 for id in ids {
21659 self.fetched_tree_sitter_chunks.remove(id);
21660 }
21661 self.colorize_brackets(false, cx);
21662 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21663 }
21664 multi_buffer::Event::Reparsed(buffer_id) => {
21665 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21666 self.refresh_selected_text_highlights(true, window, cx);
21667 self.colorize_brackets(true, cx);
21668 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21669
21670 cx.emit(EditorEvent::Reparsed(*buffer_id));
21671 }
21672 multi_buffer::Event::DiffHunksToggled => {
21673 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21674 }
21675 multi_buffer::Event::LanguageChanged(buffer_id) => {
21676 self.registered_buffers.remove(&buffer_id);
21677 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21678 cx.emit(EditorEvent::Reparsed(*buffer_id));
21679 cx.notify();
21680 }
21681 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21682 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21683 multi_buffer::Event::FileHandleChanged
21684 | multi_buffer::Event::Reloaded
21685 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21686 multi_buffer::Event::DiagnosticsUpdated => {
21687 self.update_diagnostics_state(window, cx);
21688 }
21689 _ => {}
21690 };
21691 }
21692
21693 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21694 if !self.diagnostics_enabled() {
21695 return;
21696 }
21697 self.refresh_active_diagnostics(cx);
21698 self.refresh_inline_diagnostics(true, window, cx);
21699 self.scrollbar_marker_state.dirty = true;
21700 cx.notify();
21701 }
21702
21703 pub fn start_temporary_diff_override(&mut self) {
21704 self.load_diff_task.take();
21705 self.temporary_diff_override = true;
21706 }
21707
21708 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21709 self.temporary_diff_override = false;
21710 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21711 self.buffer.update(cx, |buffer, cx| {
21712 buffer.set_all_diff_hunks_collapsed(cx);
21713 });
21714
21715 if let Some(project) = self.project.clone() {
21716 self.load_diff_task = Some(
21717 update_uncommitted_diff_for_buffer(
21718 cx.entity(),
21719 &project,
21720 self.buffer.read(cx).all_buffers(),
21721 self.buffer.clone(),
21722 cx,
21723 )
21724 .shared(),
21725 );
21726 }
21727 }
21728
21729 fn on_display_map_changed(
21730 &mut self,
21731 _: Entity<DisplayMap>,
21732 _: &mut Window,
21733 cx: &mut Context<Self>,
21734 ) {
21735 cx.notify();
21736 }
21737
21738 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
21739 if !self.mode.is_full() {
21740 return None;
21741 }
21742
21743 let theme_settings = theme::ThemeSettings::get_global(cx);
21744 let theme = cx.theme();
21745 let accent_colors = theme.accents().clone();
21746
21747 let accent_overrides = theme_settings
21748 .theme_overrides
21749 .get(theme.name.as_ref())
21750 .map(|theme_style| &theme_style.accents)
21751 .into_iter()
21752 .flatten()
21753 .chain(
21754 theme_settings
21755 .experimental_theme_overrides
21756 .as_ref()
21757 .map(|overrides| &overrides.accents)
21758 .into_iter()
21759 .flatten(),
21760 )
21761 .flat_map(|accent| accent.0.clone())
21762 .collect();
21763
21764 Some(AccentData {
21765 colors: accent_colors,
21766 overrides: accent_overrides,
21767 })
21768 }
21769
21770 fn fetch_applicable_language_settings(
21771 &self,
21772 cx: &App,
21773 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
21774 if !self.mode.is_full() {
21775 return HashMap::default();
21776 }
21777
21778 self.buffer().read(cx).all_buffers().into_iter().fold(
21779 HashMap::default(),
21780 |mut acc, buffer| {
21781 let buffer = buffer.read(cx);
21782 let language = buffer.language().map(|language| language.name());
21783 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
21784 let file = buffer.file();
21785 v.insert(language_settings(language, file, cx).into_owned());
21786 }
21787 acc
21788 },
21789 )
21790 }
21791
21792 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21793 let new_language_settings = self.fetch_applicable_language_settings(cx);
21794 let language_settings_changed = new_language_settings != self.applicable_language_settings;
21795 self.applicable_language_settings = new_language_settings;
21796
21797 let new_accents = self.fetch_accent_data(cx);
21798 let accents_changed = new_accents != self.accent_data;
21799 self.accent_data = new_accents;
21800
21801 if self.diagnostics_enabled() {
21802 let new_severity = EditorSettings::get_global(cx)
21803 .diagnostics_max_severity
21804 .unwrap_or(DiagnosticSeverity::Hint);
21805 self.set_max_diagnostics_severity(new_severity, cx);
21806 }
21807 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21808 self.update_edit_prediction_settings(cx);
21809 self.refresh_edit_prediction(true, false, window, cx);
21810 self.refresh_inline_values(cx);
21811 self.refresh_inlay_hints(
21812 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21813 self.selections.newest_anchor().head(),
21814 &self.buffer.read(cx).snapshot(cx),
21815 cx,
21816 )),
21817 cx,
21818 );
21819
21820 let old_cursor_shape = self.cursor_shape;
21821 let old_show_breadcrumbs = self.show_breadcrumbs;
21822
21823 {
21824 let editor_settings = EditorSettings::get_global(cx);
21825 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21826 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21827 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21828 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21829 }
21830
21831 if old_cursor_shape != self.cursor_shape {
21832 cx.emit(EditorEvent::CursorShapeChanged);
21833 }
21834
21835 if old_show_breadcrumbs != self.show_breadcrumbs {
21836 cx.emit(EditorEvent::BreadcrumbsChanged);
21837 }
21838
21839 let project_settings = ProjectSettings::get_global(cx);
21840 self.buffer_serialization = self
21841 .should_serialize_buffer()
21842 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21843
21844 if self.mode.is_full() {
21845 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21846 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21847 if self.show_inline_diagnostics != show_inline_diagnostics {
21848 self.show_inline_diagnostics = show_inline_diagnostics;
21849 self.refresh_inline_diagnostics(false, window, cx);
21850 }
21851
21852 if self.git_blame_inline_enabled != inline_blame_enabled {
21853 self.toggle_git_blame_inline_internal(false, window, cx);
21854 }
21855
21856 let minimap_settings = EditorSettings::get_global(cx).minimap;
21857 if self.minimap_visibility != MinimapVisibility::Disabled {
21858 if self.minimap_visibility.settings_visibility()
21859 != minimap_settings.minimap_enabled()
21860 {
21861 self.set_minimap_visibility(
21862 MinimapVisibility::for_mode(self.mode(), cx),
21863 window,
21864 cx,
21865 );
21866 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21867 minimap_entity.update(cx, |minimap_editor, cx| {
21868 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21869 })
21870 }
21871 }
21872
21873 if language_settings_changed || accents_changed {
21874 self.colorize_brackets(true, cx);
21875 }
21876
21877 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21878 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21879 }) {
21880 if !inlay_splice.is_empty() {
21881 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21882 }
21883 self.refresh_colors_for_visible_range(None, window, cx);
21884 }
21885 }
21886
21887 cx.notify();
21888 }
21889
21890 pub fn set_searchable(&mut self, searchable: bool) {
21891 self.searchable = searchable;
21892 }
21893
21894 pub fn searchable(&self) -> bool {
21895 self.searchable
21896 }
21897
21898 pub fn open_excerpts_in_split(
21899 &mut self,
21900 _: &OpenExcerptsSplit,
21901 window: &mut Window,
21902 cx: &mut Context<Self>,
21903 ) {
21904 self.open_excerpts_common(None, true, window, cx)
21905 }
21906
21907 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21908 self.open_excerpts_common(None, false, window, cx)
21909 }
21910
21911 fn open_excerpts_common(
21912 &mut self,
21913 jump_data: Option<JumpData>,
21914 split: bool,
21915 window: &mut Window,
21916 cx: &mut Context<Self>,
21917 ) {
21918 let Some(workspace) = self.workspace() else {
21919 cx.propagate();
21920 return;
21921 };
21922
21923 if self.buffer.read(cx).is_singleton() {
21924 cx.propagate();
21925 return;
21926 }
21927
21928 let mut new_selections_by_buffer = HashMap::default();
21929 match &jump_data {
21930 Some(JumpData::MultiBufferPoint {
21931 excerpt_id,
21932 position,
21933 anchor,
21934 line_offset_from_top,
21935 }) => {
21936 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21937 if let Some(buffer) = multi_buffer_snapshot
21938 .buffer_id_for_excerpt(*excerpt_id)
21939 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21940 {
21941 let buffer_snapshot = buffer.read(cx).snapshot();
21942 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21943 language::ToPoint::to_point(anchor, &buffer_snapshot)
21944 } else {
21945 buffer_snapshot.clip_point(*position, Bias::Left)
21946 };
21947 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21948 new_selections_by_buffer.insert(
21949 buffer,
21950 (
21951 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
21952 Some(*line_offset_from_top),
21953 ),
21954 );
21955 }
21956 }
21957 Some(JumpData::MultiBufferRow {
21958 row,
21959 line_offset_from_top,
21960 }) => {
21961 let point = MultiBufferPoint::new(row.0, 0);
21962 if let Some((buffer, buffer_point, _)) =
21963 self.buffer.read(cx).point_to_buffer_point(point, cx)
21964 {
21965 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21966 new_selections_by_buffer
21967 .entry(buffer)
21968 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21969 .0
21970 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
21971 }
21972 }
21973 None => {
21974 let selections = self
21975 .selections
21976 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
21977 let multi_buffer = self.buffer.read(cx);
21978 for selection in selections {
21979 for (snapshot, range, _, anchor) in multi_buffer
21980 .snapshot(cx)
21981 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21982 {
21983 if let Some(anchor) = anchor {
21984 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21985 else {
21986 continue;
21987 };
21988 let offset = text::ToOffset::to_offset(
21989 &anchor.text_anchor,
21990 &buffer_handle.read(cx).snapshot(),
21991 );
21992 let range = BufferOffset(offset)..BufferOffset(offset);
21993 new_selections_by_buffer
21994 .entry(buffer_handle)
21995 .or_insert((Vec::new(), None))
21996 .0
21997 .push(range)
21998 } else {
21999 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22000 else {
22001 continue;
22002 };
22003 new_selections_by_buffer
22004 .entry(buffer_handle)
22005 .or_insert((Vec::new(), None))
22006 .0
22007 .push(range)
22008 }
22009 }
22010 }
22011 }
22012 }
22013
22014 new_selections_by_buffer
22015 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22016
22017 if new_selections_by_buffer.is_empty() {
22018 return;
22019 }
22020
22021 // We defer the pane interaction because we ourselves are a workspace item
22022 // and activating a new item causes the pane to call a method on us reentrantly,
22023 // which panics if we're on the stack.
22024 window.defer(cx, move |window, cx| {
22025 workspace.update(cx, |workspace, cx| {
22026 let pane = if split {
22027 workspace.adjacent_pane(window, cx)
22028 } else {
22029 workspace.active_pane().clone()
22030 };
22031
22032 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22033 let buffer_read = buffer.read(cx);
22034 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22035 (true, project::File::from_dyn(Some(file)).is_some())
22036 } else {
22037 (false, false)
22038 };
22039
22040 // If project file is none workspace.open_project_item will fail to open the excerpt
22041 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22042 // so we check if there's a tab match in that case first
22043 let editor = (!has_file || !is_project_file)
22044 .then(|| {
22045 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22046 // so `workspace.open_project_item` will never find them, always opening a new editor.
22047 // Instead, we try to activate the existing editor in the pane first.
22048 let (editor, pane_item_index, pane_item_id) =
22049 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22050 let editor = item.downcast::<Editor>()?;
22051 let singleton_buffer =
22052 editor.read(cx).buffer().read(cx).as_singleton()?;
22053 if singleton_buffer == buffer {
22054 Some((editor, i, item.item_id()))
22055 } else {
22056 None
22057 }
22058 })?;
22059 pane.update(cx, |pane, cx| {
22060 pane.activate_item(pane_item_index, true, true, window, cx);
22061 if !PreviewTabsSettings::get_global(cx)
22062 .enable_preview_from_multibuffer
22063 {
22064 pane.unpreview_item_if_preview(pane_item_id);
22065 }
22066 });
22067 Some(editor)
22068 })
22069 .flatten()
22070 .unwrap_or_else(|| {
22071 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22072 .enable_keep_preview_on_code_navigation;
22073 let allow_new_preview =
22074 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22075 workspace.open_project_item::<Self>(
22076 pane.clone(),
22077 buffer,
22078 true,
22079 true,
22080 keep_old_preview,
22081 allow_new_preview,
22082 window,
22083 cx,
22084 )
22085 });
22086
22087 editor.update(cx, |editor, cx| {
22088 if has_file && !is_project_file {
22089 editor.set_read_only(true);
22090 }
22091 let autoscroll = match scroll_offset {
22092 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22093 None => Autoscroll::newest(),
22094 };
22095 let nav_history = editor.nav_history.take();
22096 editor.change_selections(
22097 SelectionEffects::scroll(autoscroll),
22098 window,
22099 cx,
22100 |s| {
22101 s.select_ranges(ranges.into_iter().map(|range| {
22102 // we checked that the editor is a singleton editor so the offsets are valid
22103 MultiBufferOffset(range.start.0)..MultiBufferOffset(range.end.0)
22104 }));
22105 },
22106 );
22107 editor.nav_history = nav_history;
22108 });
22109 }
22110 })
22111 });
22112 }
22113
22114 // Allow opening excerpts for buffers that either belong to the current project
22115 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22116 // are also supported so tests and other in-memory views keep working.
22117 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22118 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22119 }
22120
22121 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22122 let snapshot = self.buffer.read(cx).read(cx);
22123 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22124 Some(
22125 ranges
22126 .iter()
22127 .map(move |range| {
22128 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22129 })
22130 .collect(),
22131 )
22132 }
22133
22134 fn selection_replacement_ranges(
22135 &self,
22136 range: Range<MultiBufferOffsetUtf16>,
22137 cx: &mut App,
22138 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22139 let selections = self
22140 .selections
22141 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22142 let newest_selection = selections
22143 .iter()
22144 .max_by_key(|selection| selection.id)
22145 .unwrap();
22146 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22147 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22148 let snapshot = self.buffer.read(cx).read(cx);
22149 selections
22150 .into_iter()
22151 .map(|mut selection| {
22152 selection.start.0.0 =
22153 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22154 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22155 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22156 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22157 })
22158 .collect()
22159 }
22160
22161 fn report_editor_event(
22162 &self,
22163 reported_event: ReportEditorEvent,
22164 file_extension: Option<String>,
22165 cx: &App,
22166 ) {
22167 if cfg!(any(test, feature = "test-support")) {
22168 return;
22169 }
22170
22171 let Some(project) = &self.project else { return };
22172
22173 // If None, we are in a file without an extension
22174 let file = self
22175 .buffer
22176 .read(cx)
22177 .as_singleton()
22178 .and_then(|b| b.read(cx).file());
22179 let file_extension = file_extension.or(file
22180 .as_ref()
22181 .and_then(|file| Path::new(file.file_name(cx)).extension())
22182 .and_then(|e| e.to_str())
22183 .map(|a| a.to_string()));
22184
22185 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22186 .map(|vim_mode| vim_mode.0)
22187 .unwrap_or(false);
22188
22189 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22190 let copilot_enabled = edit_predictions_provider
22191 == language::language_settings::EditPredictionProvider::Copilot;
22192 let copilot_enabled_for_language = self
22193 .buffer
22194 .read(cx)
22195 .language_settings(cx)
22196 .show_edit_predictions;
22197
22198 let project = project.read(cx);
22199 let event_type = reported_event.event_type();
22200
22201 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22202 telemetry::event!(
22203 event_type,
22204 type = if auto_saved {"autosave"} else {"manual"},
22205 file_extension,
22206 vim_mode,
22207 copilot_enabled,
22208 copilot_enabled_for_language,
22209 edit_predictions_provider,
22210 is_via_ssh = project.is_via_remote_server(),
22211 );
22212 } else {
22213 telemetry::event!(
22214 event_type,
22215 file_extension,
22216 vim_mode,
22217 copilot_enabled,
22218 copilot_enabled_for_language,
22219 edit_predictions_provider,
22220 is_via_ssh = project.is_via_remote_server(),
22221 );
22222 };
22223 }
22224
22225 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22226 /// with each line being an array of {text, highlight} objects.
22227 fn copy_highlight_json(
22228 &mut self,
22229 _: &CopyHighlightJson,
22230 window: &mut Window,
22231 cx: &mut Context<Self>,
22232 ) {
22233 #[derive(Serialize)]
22234 struct Chunk<'a> {
22235 text: String,
22236 highlight: Option<&'a str>,
22237 }
22238
22239 let snapshot = self.buffer.read(cx).snapshot(cx);
22240 let range = self
22241 .selected_text_range(false, window, cx)
22242 .and_then(|selection| {
22243 if selection.range.is_empty() {
22244 None
22245 } else {
22246 Some(
22247 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22248 selection.range.start,
22249 )))
22250 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22251 selection.range.end,
22252 ))),
22253 )
22254 }
22255 })
22256 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22257
22258 let chunks = snapshot.chunks(range, true);
22259 let mut lines = Vec::new();
22260 let mut line: VecDeque<Chunk> = VecDeque::new();
22261
22262 let Some(style) = self.style.as_ref() else {
22263 return;
22264 };
22265
22266 for chunk in chunks {
22267 let highlight = chunk
22268 .syntax_highlight_id
22269 .and_then(|id| id.name(&style.syntax));
22270 let mut chunk_lines = chunk.text.split('\n').peekable();
22271 while let Some(text) = chunk_lines.next() {
22272 let mut merged_with_last_token = false;
22273 if let Some(last_token) = line.back_mut()
22274 && last_token.highlight == highlight
22275 {
22276 last_token.text.push_str(text);
22277 merged_with_last_token = true;
22278 }
22279
22280 if !merged_with_last_token {
22281 line.push_back(Chunk {
22282 text: text.into(),
22283 highlight,
22284 });
22285 }
22286
22287 if chunk_lines.peek().is_some() {
22288 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22289 line.pop_front();
22290 }
22291 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22292 line.pop_back();
22293 }
22294
22295 lines.push(mem::take(&mut line));
22296 }
22297 }
22298 }
22299
22300 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22301 return;
22302 };
22303 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22304 }
22305
22306 pub fn open_context_menu(
22307 &mut self,
22308 _: &OpenContextMenu,
22309 window: &mut Window,
22310 cx: &mut Context<Self>,
22311 ) {
22312 self.request_autoscroll(Autoscroll::newest(), cx);
22313 let position = self
22314 .selections
22315 .newest_display(&self.display_snapshot(cx))
22316 .start;
22317 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22318 }
22319
22320 pub fn replay_insert_event(
22321 &mut self,
22322 text: &str,
22323 relative_utf16_range: Option<Range<isize>>,
22324 window: &mut Window,
22325 cx: &mut Context<Self>,
22326 ) {
22327 if !self.input_enabled {
22328 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22329 return;
22330 }
22331 if let Some(relative_utf16_range) = relative_utf16_range {
22332 let selections = self
22333 .selections
22334 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22335 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22336 let new_ranges = selections.into_iter().map(|range| {
22337 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22338 range
22339 .head()
22340 .0
22341 .0
22342 .saturating_add_signed(relative_utf16_range.start),
22343 ));
22344 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22345 range
22346 .head()
22347 .0
22348 .0
22349 .saturating_add_signed(relative_utf16_range.end),
22350 ));
22351 start..end
22352 });
22353 s.select_ranges(new_ranges);
22354 });
22355 }
22356
22357 self.handle_input(text, window, cx);
22358 }
22359
22360 pub fn is_focused(&self, window: &Window) -> bool {
22361 self.focus_handle.is_focused(window)
22362 }
22363
22364 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22365 cx.emit(EditorEvent::Focused);
22366
22367 if let Some(descendant) = self
22368 .last_focused_descendant
22369 .take()
22370 .and_then(|descendant| descendant.upgrade())
22371 {
22372 window.focus(&descendant);
22373 } else {
22374 if let Some(blame) = self.blame.as_ref() {
22375 blame.update(cx, GitBlame::focus)
22376 }
22377
22378 self.blink_manager.update(cx, BlinkManager::enable);
22379 self.show_cursor_names(window, cx);
22380 self.buffer.update(cx, |buffer, cx| {
22381 buffer.finalize_last_transaction(cx);
22382 if self.leader_id.is_none() {
22383 buffer.set_active_selections(
22384 &self.selections.disjoint_anchors_arc(),
22385 self.selections.line_mode(),
22386 self.cursor_shape,
22387 cx,
22388 );
22389 }
22390 });
22391
22392 if let Some(position_map) = self.last_position_map.clone() {
22393 EditorElement::mouse_moved(
22394 self,
22395 &MouseMoveEvent {
22396 position: window.mouse_position(),
22397 pressed_button: None,
22398 modifiers: window.modifiers(),
22399 },
22400 &position_map,
22401 window,
22402 cx,
22403 );
22404 }
22405 }
22406 }
22407
22408 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22409 cx.emit(EditorEvent::FocusedIn)
22410 }
22411
22412 fn handle_focus_out(
22413 &mut self,
22414 event: FocusOutEvent,
22415 _window: &mut Window,
22416 cx: &mut Context<Self>,
22417 ) {
22418 if event.blurred != self.focus_handle {
22419 self.last_focused_descendant = Some(event.blurred);
22420 }
22421 self.selection_drag_state = SelectionDragState::None;
22422 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22423 }
22424
22425 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22426 self.blink_manager.update(cx, BlinkManager::disable);
22427 self.buffer
22428 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22429
22430 if let Some(blame) = self.blame.as_ref() {
22431 blame.update(cx, GitBlame::blur)
22432 }
22433 if !self.hover_state.focused(window, cx) {
22434 hide_hover(self, cx);
22435 }
22436 if !self
22437 .context_menu
22438 .borrow()
22439 .as_ref()
22440 .is_some_and(|context_menu| context_menu.focused(window, cx))
22441 {
22442 self.hide_context_menu(window, cx);
22443 }
22444 self.take_active_edit_prediction(cx);
22445 cx.emit(EditorEvent::Blurred);
22446 cx.notify();
22447 }
22448
22449 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22450 let mut pending: String = window
22451 .pending_input_keystrokes()
22452 .into_iter()
22453 .flatten()
22454 .filter_map(|keystroke| keystroke.key_char.clone())
22455 .collect();
22456
22457 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22458 pending = "".to_string();
22459 }
22460
22461 let existing_pending = self
22462 .text_highlights::<PendingInput>(cx)
22463 .map(|(_, ranges)| ranges.to_vec());
22464 if existing_pending.is_none() && pending.is_empty() {
22465 return;
22466 }
22467 let transaction =
22468 self.transact(window, cx, |this, window, cx| {
22469 let selections = this
22470 .selections
22471 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22472 let edits = selections
22473 .iter()
22474 .map(|selection| (selection.end..selection.end, pending.clone()));
22475 this.edit(edits, cx);
22476 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22477 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22478 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22479 }));
22480 });
22481 if let Some(existing_ranges) = existing_pending {
22482 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22483 this.edit(edits, cx);
22484 }
22485 });
22486
22487 let snapshot = self.snapshot(window, cx);
22488 let ranges = self
22489 .selections
22490 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22491 .into_iter()
22492 .map(|selection| {
22493 snapshot.buffer_snapshot().anchor_after(selection.end)
22494 ..snapshot
22495 .buffer_snapshot()
22496 .anchor_before(selection.end + pending.len())
22497 })
22498 .collect();
22499
22500 if pending.is_empty() {
22501 self.clear_highlights::<PendingInput>(cx);
22502 } else {
22503 self.highlight_text::<PendingInput>(
22504 ranges,
22505 HighlightStyle {
22506 underline: Some(UnderlineStyle {
22507 thickness: px(1.),
22508 color: None,
22509 wavy: false,
22510 }),
22511 ..Default::default()
22512 },
22513 cx,
22514 );
22515 }
22516
22517 self.ime_transaction = self.ime_transaction.or(transaction);
22518 if let Some(transaction) = self.ime_transaction {
22519 self.buffer.update(cx, |buffer, cx| {
22520 buffer.group_until_transaction(transaction, cx);
22521 });
22522 }
22523
22524 if self.text_highlights::<PendingInput>(cx).is_none() {
22525 self.ime_transaction.take();
22526 }
22527 }
22528
22529 pub fn register_action_renderer(
22530 &mut self,
22531 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22532 ) -> Subscription {
22533 let id = self.next_editor_action_id.post_inc();
22534 self.editor_actions
22535 .borrow_mut()
22536 .insert(id, Box::new(listener));
22537
22538 let editor_actions = self.editor_actions.clone();
22539 Subscription::new(move || {
22540 editor_actions.borrow_mut().remove(&id);
22541 })
22542 }
22543
22544 pub fn register_action<A: Action>(
22545 &mut self,
22546 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22547 ) -> Subscription {
22548 let id = self.next_editor_action_id.post_inc();
22549 let listener = Arc::new(listener);
22550 self.editor_actions.borrow_mut().insert(
22551 id,
22552 Box::new(move |_, window, _| {
22553 let listener = listener.clone();
22554 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22555 let action = action.downcast_ref().unwrap();
22556 if phase == DispatchPhase::Bubble {
22557 listener(action, window, cx)
22558 }
22559 })
22560 }),
22561 );
22562
22563 let editor_actions = self.editor_actions.clone();
22564 Subscription::new(move || {
22565 editor_actions.borrow_mut().remove(&id);
22566 })
22567 }
22568
22569 pub fn file_header_size(&self) -> u32 {
22570 FILE_HEADER_HEIGHT
22571 }
22572
22573 pub fn restore(
22574 &mut self,
22575 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22576 window: &mut Window,
22577 cx: &mut Context<Self>,
22578 ) {
22579 let workspace = self.workspace();
22580 let project = self.project();
22581 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22582 let mut tasks = Vec::new();
22583 for (buffer_id, changes) in revert_changes {
22584 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22585 buffer.update(cx, |buffer, cx| {
22586 buffer.edit(
22587 changes
22588 .into_iter()
22589 .map(|(range, text)| (range, text.to_string())),
22590 None,
22591 cx,
22592 );
22593 });
22594
22595 if let Some(project) =
22596 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22597 {
22598 project.update(cx, |project, cx| {
22599 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22600 })
22601 }
22602 }
22603 }
22604 tasks
22605 });
22606 cx.spawn_in(window, async move |_, cx| {
22607 for (buffer, task) in save_tasks {
22608 let result = task.await;
22609 if result.is_err() {
22610 let Some(path) = buffer
22611 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22612 .ok()
22613 else {
22614 continue;
22615 };
22616 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22617 let Some(task) = cx
22618 .update_window_entity(workspace, |workspace, window, cx| {
22619 workspace
22620 .open_path_preview(path, None, false, false, false, window, cx)
22621 })
22622 .ok()
22623 else {
22624 continue;
22625 };
22626 task.await.log_err();
22627 }
22628 }
22629 }
22630 })
22631 .detach();
22632 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22633 selections.refresh()
22634 });
22635 }
22636
22637 pub fn to_pixel_point(
22638 &self,
22639 source: multi_buffer::Anchor,
22640 editor_snapshot: &EditorSnapshot,
22641 window: &mut Window,
22642 ) -> Option<gpui::Point<Pixels>> {
22643 let source_point = source.to_display_point(editor_snapshot);
22644 self.display_to_pixel_point(source_point, editor_snapshot, window)
22645 }
22646
22647 pub fn display_to_pixel_point(
22648 &self,
22649 source: DisplayPoint,
22650 editor_snapshot: &EditorSnapshot,
22651 window: &mut Window,
22652 ) -> Option<gpui::Point<Pixels>> {
22653 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22654 let text_layout_details = self.text_layout_details(window);
22655 let scroll_top = text_layout_details
22656 .scroll_anchor
22657 .scroll_position(editor_snapshot)
22658 .y;
22659
22660 if source.row().as_f64() < scroll_top.floor() {
22661 return None;
22662 }
22663 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22664 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22665 Some(gpui::Point::new(source_x, source_y))
22666 }
22667
22668 pub fn has_visible_completions_menu(&self) -> bool {
22669 !self.edit_prediction_preview_is_active()
22670 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22671 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22672 })
22673 }
22674
22675 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22676 if self.mode.is_minimap() {
22677 return;
22678 }
22679 self.addons
22680 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22681 }
22682
22683 pub fn unregister_addon<T: Addon>(&mut self) {
22684 self.addons.remove(&std::any::TypeId::of::<T>());
22685 }
22686
22687 pub fn addon<T: Addon>(&self) -> Option<&T> {
22688 let type_id = std::any::TypeId::of::<T>();
22689 self.addons
22690 .get(&type_id)
22691 .and_then(|item| item.to_any().downcast_ref::<T>())
22692 }
22693
22694 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22695 let type_id = std::any::TypeId::of::<T>();
22696 self.addons
22697 .get_mut(&type_id)
22698 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22699 }
22700
22701 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22702 let text_layout_details = self.text_layout_details(window);
22703 let style = &text_layout_details.editor_style;
22704 let font_id = window.text_system().resolve_font(&style.text.font());
22705 let font_size = style.text.font_size.to_pixels(window.rem_size());
22706 let line_height = style.text.line_height_in_pixels(window.rem_size());
22707 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22708 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22709
22710 CharacterDimensions {
22711 em_width,
22712 em_advance,
22713 line_height,
22714 }
22715 }
22716
22717 pub fn last_gutter_dimensions(&self) -> &GutterDimensions {
22718 &self.gutter_dimensions
22719 }
22720
22721 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22722 self.load_diff_task.clone()
22723 }
22724
22725 fn read_metadata_from_db(
22726 &mut self,
22727 item_id: u64,
22728 workspace_id: WorkspaceId,
22729 window: &mut Window,
22730 cx: &mut Context<Editor>,
22731 ) {
22732 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22733 && !self.mode.is_minimap()
22734 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22735 {
22736 let buffer_snapshot = OnceCell::new();
22737
22738 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22739 && !folds.is_empty()
22740 {
22741 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22742 self.fold_ranges(
22743 folds
22744 .into_iter()
22745 .map(|(start, end)| {
22746 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22747 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22748 })
22749 .collect(),
22750 false,
22751 window,
22752 cx,
22753 );
22754 }
22755
22756 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22757 && !selections.is_empty()
22758 {
22759 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22760 // skip adding the initial selection to selection history
22761 self.selection_history.mode = SelectionHistoryMode::Skipping;
22762 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22763 s.select_ranges(selections.into_iter().map(|(start, end)| {
22764 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
22765 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
22766 }));
22767 });
22768 self.selection_history.mode = SelectionHistoryMode::Normal;
22769 };
22770 }
22771
22772 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22773 }
22774
22775 fn update_lsp_data(
22776 &mut self,
22777 for_buffer: Option<BufferId>,
22778 window: &mut Window,
22779 cx: &mut Context<'_, Self>,
22780 ) {
22781 self.pull_diagnostics(for_buffer, window, cx);
22782 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22783 }
22784
22785 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22786 if self.ignore_lsp_data() {
22787 return;
22788 }
22789 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
22790 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22791 }
22792 }
22793
22794 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22795 if self.ignore_lsp_data() {
22796 return;
22797 }
22798
22799 if !self.registered_buffers.contains_key(&buffer_id)
22800 && let Some(project) = self.project.as_ref()
22801 {
22802 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22803 project.update(cx, |project, cx| {
22804 self.registered_buffers.insert(
22805 buffer_id,
22806 project.register_buffer_with_language_servers(&buffer, cx),
22807 );
22808 });
22809 } else {
22810 self.registered_buffers.remove(&buffer_id);
22811 }
22812 }
22813 }
22814
22815 fn ignore_lsp_data(&self) -> bool {
22816 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22817 // skip any LSP updates for it.
22818 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22819 }
22820}
22821
22822fn edit_for_markdown_paste<'a>(
22823 buffer: &MultiBufferSnapshot,
22824 range: Range<MultiBufferOffset>,
22825 to_insert: &'a str,
22826 url: Option<url::Url>,
22827) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
22828 if url.is_none() {
22829 return (range, Cow::Borrowed(to_insert));
22830 };
22831
22832 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22833
22834 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22835 Cow::Borrowed(to_insert)
22836 } else {
22837 Cow::Owned(format!("[{old_text}]({to_insert})"))
22838 };
22839 (range, new_text)
22840}
22841
22842fn process_completion_for_edit(
22843 completion: &Completion,
22844 intent: CompletionIntent,
22845 buffer: &Entity<Buffer>,
22846 cursor_position: &text::Anchor,
22847 cx: &mut Context<Editor>,
22848) -> CompletionEdit {
22849 let buffer = buffer.read(cx);
22850 let buffer_snapshot = buffer.snapshot();
22851 let (snippet, new_text) = if completion.is_snippet() {
22852 let mut snippet_source = completion.new_text.clone();
22853 // Workaround for typescript language server issues so that methods don't expand within
22854 // strings and functions with type expressions. The previous point is used because the query
22855 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22856 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22857 let previous_point = if previous_point.column > 0 {
22858 cursor_position.to_previous_offset(&buffer_snapshot)
22859 } else {
22860 cursor_position.to_offset(&buffer_snapshot)
22861 };
22862 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22863 && scope.prefers_label_for_snippet_in_completion()
22864 && let Some(label) = completion.label()
22865 && matches!(
22866 completion.kind(),
22867 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22868 )
22869 {
22870 snippet_source = label;
22871 }
22872 match Snippet::parse(&snippet_source).log_err() {
22873 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22874 None => (None, completion.new_text.clone()),
22875 }
22876 } else {
22877 (None, completion.new_text.clone())
22878 };
22879
22880 let mut range_to_replace = {
22881 let replace_range = &completion.replace_range;
22882 if let CompletionSource::Lsp {
22883 insert_range: Some(insert_range),
22884 ..
22885 } = &completion.source
22886 {
22887 debug_assert_eq!(
22888 insert_range.start, replace_range.start,
22889 "insert_range and replace_range should start at the same position"
22890 );
22891 debug_assert!(
22892 insert_range
22893 .start
22894 .cmp(cursor_position, &buffer_snapshot)
22895 .is_le(),
22896 "insert_range should start before or at cursor position"
22897 );
22898 debug_assert!(
22899 replace_range
22900 .start
22901 .cmp(cursor_position, &buffer_snapshot)
22902 .is_le(),
22903 "replace_range should start before or at cursor position"
22904 );
22905
22906 let should_replace = match intent {
22907 CompletionIntent::CompleteWithInsert => false,
22908 CompletionIntent::CompleteWithReplace => true,
22909 CompletionIntent::Complete | CompletionIntent::Compose => {
22910 let insert_mode =
22911 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22912 .completions
22913 .lsp_insert_mode;
22914 match insert_mode {
22915 LspInsertMode::Insert => false,
22916 LspInsertMode::Replace => true,
22917 LspInsertMode::ReplaceSubsequence => {
22918 let mut text_to_replace = buffer.chars_for_range(
22919 buffer.anchor_before(replace_range.start)
22920 ..buffer.anchor_after(replace_range.end),
22921 );
22922 let mut current_needle = text_to_replace.next();
22923 for haystack_ch in completion.label.text.chars() {
22924 if let Some(needle_ch) = current_needle
22925 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22926 {
22927 current_needle = text_to_replace.next();
22928 }
22929 }
22930 current_needle.is_none()
22931 }
22932 LspInsertMode::ReplaceSuffix => {
22933 if replace_range
22934 .end
22935 .cmp(cursor_position, &buffer_snapshot)
22936 .is_gt()
22937 {
22938 let range_after_cursor = *cursor_position..replace_range.end;
22939 let text_after_cursor = buffer
22940 .text_for_range(
22941 buffer.anchor_before(range_after_cursor.start)
22942 ..buffer.anchor_after(range_after_cursor.end),
22943 )
22944 .collect::<String>()
22945 .to_ascii_lowercase();
22946 completion
22947 .label
22948 .text
22949 .to_ascii_lowercase()
22950 .ends_with(&text_after_cursor)
22951 } else {
22952 true
22953 }
22954 }
22955 }
22956 }
22957 };
22958
22959 if should_replace {
22960 replace_range.clone()
22961 } else {
22962 insert_range.clone()
22963 }
22964 } else {
22965 replace_range.clone()
22966 }
22967 };
22968
22969 if range_to_replace
22970 .end
22971 .cmp(cursor_position, &buffer_snapshot)
22972 .is_lt()
22973 {
22974 range_to_replace.end = *cursor_position;
22975 }
22976
22977 let replace_range = range_to_replace.to_offset(buffer);
22978 CompletionEdit {
22979 new_text,
22980 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
22981 snippet,
22982 }
22983}
22984
22985struct CompletionEdit {
22986 new_text: String,
22987 replace_range: Range<BufferOffset>,
22988 snippet: Option<Snippet>,
22989}
22990
22991fn insert_extra_newline_brackets(
22992 buffer: &MultiBufferSnapshot,
22993 range: Range<MultiBufferOffset>,
22994 language: &language::LanguageScope,
22995) -> bool {
22996 let leading_whitespace_len = buffer
22997 .reversed_chars_at(range.start)
22998 .take_while(|c| c.is_whitespace() && *c != '\n')
22999 .map(|c| c.len_utf8())
23000 .sum::<usize>();
23001 let trailing_whitespace_len = buffer
23002 .chars_at(range.end)
23003 .take_while(|c| c.is_whitespace() && *c != '\n')
23004 .map(|c| c.len_utf8())
23005 .sum::<usize>();
23006 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23007
23008 language.brackets().any(|(pair, enabled)| {
23009 let pair_start = pair.start.trim_end();
23010 let pair_end = pair.end.trim_start();
23011
23012 enabled
23013 && pair.newline
23014 && buffer.contains_str_at(range.end, pair_end)
23015 && buffer.contains_str_at(
23016 range.start.saturating_sub_usize(pair_start.len()),
23017 pair_start,
23018 )
23019 })
23020}
23021
23022fn insert_extra_newline_tree_sitter(
23023 buffer: &MultiBufferSnapshot,
23024 range: Range<MultiBufferOffset>,
23025) -> bool {
23026 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23027 [(buffer, range, _)] => (*buffer, range.clone()),
23028 _ => return false,
23029 };
23030 let pair = {
23031 let mut result: Option<BracketMatch<usize>> = None;
23032
23033 for pair in buffer
23034 .all_bracket_ranges(range.start.0..range.end.0)
23035 .filter(move |pair| {
23036 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23037 })
23038 {
23039 let len = pair.close_range.end - pair.open_range.start;
23040
23041 if let Some(existing) = &result {
23042 let existing_len = existing.close_range.end - existing.open_range.start;
23043 if len > existing_len {
23044 continue;
23045 }
23046 }
23047
23048 result = Some(pair);
23049 }
23050
23051 result
23052 };
23053 let Some(pair) = pair else {
23054 return false;
23055 };
23056 pair.newline_only
23057 && buffer
23058 .chars_for_range(pair.open_range.end..range.start.0)
23059 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23060 .all(|c| c.is_whitespace() && c != '\n')
23061}
23062
23063fn update_uncommitted_diff_for_buffer(
23064 editor: Entity<Editor>,
23065 project: &Entity<Project>,
23066 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23067 buffer: Entity<MultiBuffer>,
23068 cx: &mut App,
23069) -> Task<()> {
23070 let mut tasks = Vec::new();
23071 project.update(cx, |project, cx| {
23072 for buffer in buffers {
23073 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23074 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23075 }
23076 }
23077 });
23078 cx.spawn(async move |cx| {
23079 let diffs = future::join_all(tasks).await;
23080 if editor
23081 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23082 .unwrap_or(false)
23083 {
23084 return;
23085 }
23086
23087 buffer
23088 .update(cx, |buffer, cx| {
23089 for diff in diffs.into_iter().flatten() {
23090 buffer.add_diff(diff, cx);
23091 }
23092 })
23093 .ok();
23094 })
23095}
23096
23097fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23098 let tab_size = tab_size.get() as usize;
23099 let mut width = offset;
23100
23101 for ch in text.chars() {
23102 width += if ch == '\t' {
23103 tab_size - (width % tab_size)
23104 } else {
23105 1
23106 };
23107 }
23108
23109 width - offset
23110}
23111
23112#[cfg(test)]
23113mod tests {
23114 use super::*;
23115
23116 #[test]
23117 fn test_string_size_with_expanded_tabs() {
23118 let nz = |val| NonZeroU32::new(val).unwrap();
23119 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23120 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23121 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23122 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23123 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23124 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23125 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23126 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23127 }
23128}
23129
23130/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23131struct WordBreakingTokenizer<'a> {
23132 input: &'a str,
23133}
23134
23135impl<'a> WordBreakingTokenizer<'a> {
23136 fn new(input: &'a str) -> Self {
23137 Self { input }
23138 }
23139}
23140
23141fn is_char_ideographic(ch: char) -> bool {
23142 use unicode_script::Script::*;
23143 use unicode_script::UnicodeScript;
23144 matches!(ch.script(), Han | Tangut | Yi)
23145}
23146
23147fn is_grapheme_ideographic(text: &str) -> bool {
23148 text.chars().any(is_char_ideographic)
23149}
23150
23151fn is_grapheme_whitespace(text: &str) -> bool {
23152 text.chars().any(|x| x.is_whitespace())
23153}
23154
23155fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23156 text.chars()
23157 .next()
23158 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23159}
23160
23161#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23162enum WordBreakToken<'a> {
23163 Word { token: &'a str, grapheme_len: usize },
23164 InlineWhitespace { token: &'a str, grapheme_len: usize },
23165 Newline,
23166}
23167
23168impl<'a> Iterator for WordBreakingTokenizer<'a> {
23169 /// Yields a span, the count of graphemes in the token, and whether it was
23170 /// whitespace. Note that it also breaks at word boundaries.
23171 type Item = WordBreakToken<'a>;
23172
23173 fn next(&mut self) -> Option<Self::Item> {
23174 use unicode_segmentation::UnicodeSegmentation;
23175 if self.input.is_empty() {
23176 return None;
23177 }
23178
23179 let mut iter = self.input.graphemes(true).peekable();
23180 let mut offset = 0;
23181 let mut grapheme_len = 0;
23182 if let Some(first_grapheme) = iter.next() {
23183 let is_newline = first_grapheme == "\n";
23184 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23185 offset += first_grapheme.len();
23186 grapheme_len += 1;
23187 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23188 if let Some(grapheme) = iter.peek().copied()
23189 && should_stay_with_preceding_ideograph(grapheme)
23190 {
23191 offset += grapheme.len();
23192 grapheme_len += 1;
23193 }
23194 } else {
23195 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23196 let mut next_word_bound = words.peek().copied();
23197 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23198 next_word_bound = words.next();
23199 }
23200 while let Some(grapheme) = iter.peek().copied() {
23201 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23202 break;
23203 };
23204 if is_grapheme_whitespace(grapheme) != is_whitespace
23205 || (grapheme == "\n") != is_newline
23206 {
23207 break;
23208 };
23209 offset += grapheme.len();
23210 grapheme_len += 1;
23211 iter.next();
23212 }
23213 }
23214 let token = &self.input[..offset];
23215 self.input = &self.input[offset..];
23216 if token == "\n" {
23217 Some(WordBreakToken::Newline)
23218 } else if is_whitespace {
23219 Some(WordBreakToken::InlineWhitespace {
23220 token,
23221 grapheme_len,
23222 })
23223 } else {
23224 Some(WordBreakToken::Word {
23225 token,
23226 grapheme_len,
23227 })
23228 }
23229 } else {
23230 None
23231 }
23232 }
23233}
23234
23235#[test]
23236fn test_word_breaking_tokenizer() {
23237 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23238 ("", &[]),
23239 (" ", &[whitespace(" ", 2)]),
23240 ("Ʒ", &[word("Ʒ", 1)]),
23241 ("Ǽ", &[word("Ǽ", 1)]),
23242 ("⋑", &[word("⋑", 1)]),
23243 ("⋑⋑", &[word("⋑⋑", 2)]),
23244 (
23245 "原理,进而",
23246 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23247 ),
23248 (
23249 "hello world",
23250 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23251 ),
23252 (
23253 "hello, world",
23254 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23255 ),
23256 (
23257 " hello world",
23258 &[
23259 whitespace(" ", 2),
23260 word("hello", 5),
23261 whitespace(" ", 1),
23262 word("world", 5),
23263 ],
23264 ),
23265 (
23266 "这是什么 \n 钢笔",
23267 &[
23268 word("这", 1),
23269 word("是", 1),
23270 word("什", 1),
23271 word("么", 1),
23272 whitespace(" ", 1),
23273 newline(),
23274 whitespace(" ", 1),
23275 word("钢", 1),
23276 word("笔", 1),
23277 ],
23278 ),
23279 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23280 ];
23281
23282 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23283 WordBreakToken::Word {
23284 token,
23285 grapheme_len,
23286 }
23287 }
23288
23289 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23290 WordBreakToken::InlineWhitespace {
23291 token,
23292 grapheme_len,
23293 }
23294 }
23295
23296 fn newline() -> WordBreakToken<'static> {
23297 WordBreakToken::Newline
23298 }
23299
23300 for (input, result) in tests {
23301 assert_eq!(
23302 WordBreakingTokenizer::new(input)
23303 .collect::<Vec<_>>()
23304 .as_slice(),
23305 *result,
23306 );
23307 }
23308}
23309
23310fn wrap_with_prefix(
23311 first_line_prefix: String,
23312 subsequent_lines_prefix: String,
23313 unwrapped_text: String,
23314 wrap_column: usize,
23315 tab_size: NonZeroU32,
23316 preserve_existing_whitespace: bool,
23317) -> String {
23318 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23319 let subsequent_lines_prefix_len =
23320 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23321 let mut wrapped_text = String::new();
23322 let mut current_line = first_line_prefix;
23323 let mut is_first_line = true;
23324
23325 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23326 let mut current_line_len = first_line_prefix_len;
23327 let mut in_whitespace = false;
23328 for token in tokenizer {
23329 let have_preceding_whitespace = in_whitespace;
23330 match token {
23331 WordBreakToken::Word {
23332 token,
23333 grapheme_len,
23334 } => {
23335 in_whitespace = false;
23336 let current_prefix_len = if is_first_line {
23337 first_line_prefix_len
23338 } else {
23339 subsequent_lines_prefix_len
23340 };
23341 if current_line_len + grapheme_len > wrap_column
23342 && current_line_len != current_prefix_len
23343 {
23344 wrapped_text.push_str(current_line.trim_end());
23345 wrapped_text.push('\n');
23346 is_first_line = false;
23347 current_line = subsequent_lines_prefix.clone();
23348 current_line_len = subsequent_lines_prefix_len;
23349 }
23350 current_line.push_str(token);
23351 current_line_len += grapheme_len;
23352 }
23353 WordBreakToken::InlineWhitespace {
23354 mut token,
23355 mut grapheme_len,
23356 } => {
23357 in_whitespace = true;
23358 if have_preceding_whitespace && !preserve_existing_whitespace {
23359 continue;
23360 }
23361 if !preserve_existing_whitespace {
23362 // Keep a single whitespace grapheme as-is
23363 if let Some(first) =
23364 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23365 {
23366 token = first;
23367 } else {
23368 token = " ";
23369 }
23370 grapheme_len = 1;
23371 }
23372 let current_prefix_len = if is_first_line {
23373 first_line_prefix_len
23374 } else {
23375 subsequent_lines_prefix_len
23376 };
23377 if current_line_len + grapheme_len > wrap_column {
23378 wrapped_text.push_str(current_line.trim_end());
23379 wrapped_text.push('\n');
23380 is_first_line = false;
23381 current_line = subsequent_lines_prefix.clone();
23382 current_line_len = subsequent_lines_prefix_len;
23383 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23384 current_line.push_str(token);
23385 current_line_len += grapheme_len;
23386 }
23387 }
23388 WordBreakToken::Newline => {
23389 in_whitespace = true;
23390 let current_prefix_len = if is_first_line {
23391 first_line_prefix_len
23392 } else {
23393 subsequent_lines_prefix_len
23394 };
23395 if preserve_existing_whitespace {
23396 wrapped_text.push_str(current_line.trim_end());
23397 wrapped_text.push('\n');
23398 is_first_line = false;
23399 current_line = subsequent_lines_prefix.clone();
23400 current_line_len = subsequent_lines_prefix_len;
23401 } else if have_preceding_whitespace {
23402 continue;
23403 } else if current_line_len + 1 > wrap_column
23404 && current_line_len != current_prefix_len
23405 {
23406 wrapped_text.push_str(current_line.trim_end());
23407 wrapped_text.push('\n');
23408 is_first_line = false;
23409 current_line = subsequent_lines_prefix.clone();
23410 current_line_len = subsequent_lines_prefix_len;
23411 } else if current_line_len != current_prefix_len {
23412 current_line.push(' ');
23413 current_line_len += 1;
23414 }
23415 }
23416 }
23417 }
23418
23419 if !current_line.is_empty() {
23420 wrapped_text.push_str(¤t_line);
23421 }
23422 wrapped_text
23423}
23424
23425#[test]
23426fn test_wrap_with_prefix() {
23427 assert_eq!(
23428 wrap_with_prefix(
23429 "# ".to_string(),
23430 "# ".to_string(),
23431 "abcdefg".to_string(),
23432 4,
23433 NonZeroU32::new(4).unwrap(),
23434 false,
23435 ),
23436 "# abcdefg"
23437 );
23438 assert_eq!(
23439 wrap_with_prefix(
23440 "".to_string(),
23441 "".to_string(),
23442 "\thello world".to_string(),
23443 8,
23444 NonZeroU32::new(4).unwrap(),
23445 false,
23446 ),
23447 "hello\nworld"
23448 );
23449 assert_eq!(
23450 wrap_with_prefix(
23451 "// ".to_string(),
23452 "// ".to_string(),
23453 "xx \nyy zz aa bb cc".to_string(),
23454 12,
23455 NonZeroU32::new(4).unwrap(),
23456 false,
23457 ),
23458 "// xx yy zz\n// aa bb cc"
23459 );
23460 assert_eq!(
23461 wrap_with_prefix(
23462 String::new(),
23463 String::new(),
23464 "这是什么 \n 钢笔".to_string(),
23465 3,
23466 NonZeroU32::new(4).unwrap(),
23467 false,
23468 ),
23469 "这是什\n么 钢\n笔"
23470 );
23471 assert_eq!(
23472 wrap_with_prefix(
23473 String::new(),
23474 String::new(),
23475 format!("foo{}bar", '\u{2009}'), // thin space
23476 80,
23477 NonZeroU32::new(4).unwrap(),
23478 false,
23479 ),
23480 format!("foo{}bar", '\u{2009}')
23481 );
23482}
23483
23484pub trait CollaborationHub {
23485 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23486 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23487 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23488}
23489
23490impl CollaborationHub for Entity<Project> {
23491 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23492 self.read(cx).collaborators()
23493 }
23494
23495 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23496 self.read(cx).user_store().read(cx).participant_indices()
23497 }
23498
23499 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23500 let this = self.read(cx);
23501 let user_ids = this.collaborators().values().map(|c| c.user_id);
23502 this.user_store().read(cx).participant_names(user_ids, cx)
23503 }
23504}
23505
23506pub trait SemanticsProvider {
23507 fn hover(
23508 &self,
23509 buffer: &Entity<Buffer>,
23510 position: text::Anchor,
23511 cx: &mut App,
23512 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23513
23514 fn inline_values(
23515 &self,
23516 buffer_handle: Entity<Buffer>,
23517 range: Range<text::Anchor>,
23518 cx: &mut App,
23519 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23520
23521 fn applicable_inlay_chunks(
23522 &self,
23523 buffer: &Entity<Buffer>,
23524 ranges: &[Range<text::Anchor>],
23525 cx: &mut App,
23526 ) -> Vec<Range<BufferRow>>;
23527
23528 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23529
23530 fn inlay_hints(
23531 &self,
23532 invalidate: InvalidationStrategy,
23533 buffer: Entity<Buffer>,
23534 ranges: Vec<Range<text::Anchor>>,
23535 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23536 cx: &mut App,
23537 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23538
23539 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23540
23541 fn document_highlights(
23542 &self,
23543 buffer: &Entity<Buffer>,
23544 position: text::Anchor,
23545 cx: &mut App,
23546 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23547
23548 fn definitions(
23549 &self,
23550 buffer: &Entity<Buffer>,
23551 position: text::Anchor,
23552 kind: GotoDefinitionKind,
23553 cx: &mut App,
23554 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23555
23556 fn range_for_rename(
23557 &self,
23558 buffer: &Entity<Buffer>,
23559 position: text::Anchor,
23560 cx: &mut App,
23561 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23562
23563 fn perform_rename(
23564 &self,
23565 buffer: &Entity<Buffer>,
23566 position: text::Anchor,
23567 new_name: String,
23568 cx: &mut App,
23569 ) -> Option<Task<Result<ProjectTransaction>>>;
23570}
23571
23572pub trait CompletionProvider {
23573 fn completions(
23574 &self,
23575 excerpt_id: ExcerptId,
23576 buffer: &Entity<Buffer>,
23577 buffer_position: text::Anchor,
23578 trigger: CompletionContext,
23579 window: &mut Window,
23580 cx: &mut Context<Editor>,
23581 ) -> Task<Result<Vec<CompletionResponse>>>;
23582
23583 fn resolve_completions(
23584 &self,
23585 _buffer: Entity<Buffer>,
23586 _completion_indices: Vec<usize>,
23587 _completions: Rc<RefCell<Box<[Completion]>>>,
23588 _cx: &mut Context<Editor>,
23589 ) -> Task<Result<bool>> {
23590 Task::ready(Ok(false))
23591 }
23592
23593 fn apply_additional_edits_for_completion(
23594 &self,
23595 _buffer: Entity<Buffer>,
23596 _completions: Rc<RefCell<Box<[Completion]>>>,
23597 _completion_index: usize,
23598 _push_to_history: bool,
23599 _cx: &mut Context<Editor>,
23600 ) -> Task<Result<Option<language::Transaction>>> {
23601 Task::ready(Ok(None))
23602 }
23603
23604 fn is_completion_trigger(
23605 &self,
23606 buffer: &Entity<Buffer>,
23607 position: language::Anchor,
23608 text: &str,
23609 trigger_in_words: bool,
23610 cx: &mut Context<Editor>,
23611 ) -> bool;
23612
23613 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23614
23615 fn sort_completions(&self) -> bool {
23616 true
23617 }
23618
23619 fn filter_completions(&self) -> bool {
23620 true
23621 }
23622
23623 fn show_snippets(&self) -> bool {
23624 false
23625 }
23626}
23627
23628pub trait CodeActionProvider {
23629 fn id(&self) -> Arc<str>;
23630
23631 fn code_actions(
23632 &self,
23633 buffer: &Entity<Buffer>,
23634 range: Range<text::Anchor>,
23635 window: &mut Window,
23636 cx: &mut App,
23637 ) -> Task<Result<Vec<CodeAction>>>;
23638
23639 fn apply_code_action(
23640 &self,
23641 buffer_handle: Entity<Buffer>,
23642 action: CodeAction,
23643 excerpt_id: ExcerptId,
23644 push_to_history: bool,
23645 window: &mut Window,
23646 cx: &mut App,
23647 ) -> Task<Result<ProjectTransaction>>;
23648}
23649
23650impl CodeActionProvider for Entity<Project> {
23651 fn id(&self) -> Arc<str> {
23652 "project".into()
23653 }
23654
23655 fn code_actions(
23656 &self,
23657 buffer: &Entity<Buffer>,
23658 range: Range<text::Anchor>,
23659 _window: &mut Window,
23660 cx: &mut App,
23661 ) -> Task<Result<Vec<CodeAction>>> {
23662 self.update(cx, |project, cx| {
23663 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23664 let code_actions = project.code_actions(buffer, range, None, cx);
23665 cx.background_spawn(async move {
23666 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23667 Ok(code_lens_actions
23668 .context("code lens fetch")?
23669 .into_iter()
23670 .flatten()
23671 .chain(
23672 code_actions
23673 .context("code action fetch")?
23674 .into_iter()
23675 .flatten(),
23676 )
23677 .collect())
23678 })
23679 })
23680 }
23681
23682 fn apply_code_action(
23683 &self,
23684 buffer_handle: Entity<Buffer>,
23685 action: CodeAction,
23686 _excerpt_id: ExcerptId,
23687 push_to_history: bool,
23688 _window: &mut Window,
23689 cx: &mut App,
23690 ) -> Task<Result<ProjectTransaction>> {
23691 self.update(cx, |project, cx| {
23692 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23693 })
23694 }
23695}
23696
23697fn snippet_completions(
23698 project: &Project,
23699 buffer: &Entity<Buffer>,
23700 buffer_anchor: text::Anchor,
23701 classifier: CharClassifier,
23702 cx: &mut App,
23703) -> Task<Result<CompletionResponse>> {
23704 let languages = buffer.read(cx).languages_at(buffer_anchor);
23705 let snippet_store = project.snippets().read(cx);
23706
23707 let scopes: Vec<_> = languages
23708 .iter()
23709 .filter_map(|language| {
23710 let language_name = language.lsp_id();
23711 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23712
23713 if snippets.is_empty() {
23714 None
23715 } else {
23716 Some((language.default_scope(), snippets))
23717 }
23718 })
23719 .collect();
23720
23721 if scopes.is_empty() {
23722 return Task::ready(Ok(CompletionResponse {
23723 completions: vec![],
23724 display_options: CompletionDisplayOptions::default(),
23725 is_incomplete: false,
23726 }));
23727 }
23728
23729 let snapshot = buffer.read(cx).text_snapshot();
23730 let executor = cx.background_executor().clone();
23731
23732 cx.background_spawn(async move {
23733 let is_word_char = |c| classifier.is_word(c);
23734
23735 let mut is_incomplete = false;
23736 let mut completions: Vec<Completion> = Vec::new();
23737
23738 const MAX_PREFIX_LEN: usize = 128;
23739 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
23740 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
23741 let window_start = snapshot.clip_offset(window_start, Bias::Left);
23742
23743 let max_buffer_window: String = snapshot
23744 .text_for_range(window_start..buffer_offset)
23745 .collect();
23746
23747 if max_buffer_window.is_empty() {
23748 return Ok(CompletionResponse {
23749 completions: vec![],
23750 display_options: CompletionDisplayOptions::default(),
23751 is_incomplete: true,
23752 });
23753 }
23754
23755 for (_scope, snippets) in scopes.into_iter() {
23756 // Sort snippets by word count to match longer snippet prefixes first.
23757 let mut sorted_snippet_candidates = snippets
23758 .iter()
23759 .enumerate()
23760 .flat_map(|(snippet_ix, snippet)| {
23761 snippet
23762 .prefix
23763 .iter()
23764 .enumerate()
23765 .map(move |(prefix_ix, prefix)| {
23766 let word_count =
23767 snippet_candidate_suffixes(prefix, is_word_char).count();
23768 ((snippet_ix, prefix_ix), prefix, word_count)
23769 })
23770 })
23771 .collect_vec();
23772 sorted_snippet_candidates
23773 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
23774
23775 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
23776
23777 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
23778 .take(
23779 sorted_snippet_candidates
23780 .first()
23781 .map(|(_, _, word_count)| *word_count)
23782 .unwrap_or_default(),
23783 )
23784 .collect_vec();
23785
23786 const MAX_RESULTS: usize = 100;
23787 // Each match also remembers how many characters from the buffer it consumed
23788 let mut matches: Vec<(StringMatch, usize)> = vec![];
23789
23790 let mut snippet_list_cutoff_index = 0;
23791 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
23792 let word_count = buffer_index + 1;
23793 // Increase `snippet_list_cutoff_index` until we have all of the
23794 // snippets with sufficiently many words.
23795 while sorted_snippet_candidates
23796 .get(snippet_list_cutoff_index)
23797 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
23798 *snippet_word_count >= word_count
23799 })
23800 {
23801 snippet_list_cutoff_index += 1;
23802 }
23803
23804 // Take only the candidates with at least `word_count` many words
23805 let snippet_candidates_at_word_len =
23806 &sorted_snippet_candidates[..snippet_list_cutoff_index];
23807
23808 let candidates = snippet_candidates_at_word_len
23809 .iter()
23810 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
23811 .enumerate() // index in `sorted_snippet_candidates`
23812 // First char must match
23813 .filter(|(_ix, prefix)| {
23814 itertools::equal(
23815 prefix
23816 .chars()
23817 .next()
23818 .into_iter()
23819 .flat_map(|c| c.to_lowercase()),
23820 buffer_window
23821 .chars()
23822 .next()
23823 .into_iter()
23824 .flat_map(|c| c.to_lowercase()),
23825 )
23826 })
23827 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
23828 .collect::<Vec<StringMatchCandidate>>();
23829
23830 matches.extend(
23831 fuzzy::match_strings(
23832 &candidates,
23833 &buffer_window,
23834 buffer_window.chars().any(|c| c.is_uppercase()),
23835 true,
23836 MAX_RESULTS - matches.len(), // always prioritize longer snippets
23837 &Default::default(),
23838 executor.clone(),
23839 )
23840 .await
23841 .into_iter()
23842 .map(|string_match| (string_match, buffer_window.len())),
23843 );
23844
23845 if matches.len() >= MAX_RESULTS {
23846 break;
23847 }
23848 }
23849
23850 let to_lsp = |point: &text::Anchor| {
23851 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23852 point_to_lsp(end)
23853 };
23854 let lsp_end = to_lsp(&buffer_anchor);
23855
23856 if matches.len() >= MAX_RESULTS {
23857 is_incomplete = true;
23858 }
23859
23860 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
23861 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
23862 sorted_snippet_candidates[string_match.candidate_id];
23863 let snippet = &snippets[snippet_index];
23864 let start = buffer_offset - buffer_window_len;
23865 let start = snapshot.anchor_before(start);
23866 let range = start..buffer_anchor;
23867 let lsp_start = to_lsp(&start);
23868 let lsp_range = lsp::Range {
23869 start: lsp_start,
23870 end: lsp_end,
23871 };
23872 Completion {
23873 replace_range: range,
23874 new_text: snippet.body.clone(),
23875 source: CompletionSource::Lsp {
23876 insert_range: None,
23877 server_id: LanguageServerId(usize::MAX),
23878 resolved: true,
23879 lsp_completion: Box::new(lsp::CompletionItem {
23880 label: snippet.prefix.first().unwrap().clone(),
23881 kind: Some(CompletionItemKind::SNIPPET),
23882 label_details: snippet.description.as_ref().map(|description| {
23883 lsp::CompletionItemLabelDetails {
23884 detail: Some(description.clone()),
23885 description: None,
23886 }
23887 }),
23888 insert_text_format: Some(InsertTextFormat::SNIPPET),
23889 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23890 lsp::InsertReplaceEdit {
23891 new_text: snippet.body.clone(),
23892 insert: lsp_range,
23893 replace: lsp_range,
23894 },
23895 )),
23896 filter_text: Some(snippet.body.clone()),
23897 sort_text: Some(char::MAX.to_string()),
23898 ..lsp::CompletionItem::default()
23899 }),
23900 lsp_defaults: None,
23901 },
23902 label: CodeLabel {
23903 text: matching_prefix.clone(),
23904 runs: Vec::new(),
23905 filter_range: 0..matching_prefix.len(),
23906 },
23907 icon_path: None,
23908 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23909 single_line: snippet.name.clone().into(),
23910 plain_text: snippet
23911 .description
23912 .clone()
23913 .map(|description| description.into()),
23914 }),
23915 insert_text_mode: None,
23916 confirm: None,
23917 match_start: Some(start),
23918 snippet_deduplication_key: Some((snippet_index, prefix_index)),
23919 }
23920 }));
23921 }
23922
23923 Ok(CompletionResponse {
23924 completions,
23925 display_options: CompletionDisplayOptions::default(),
23926 is_incomplete,
23927 })
23928 })
23929}
23930
23931impl CompletionProvider for Entity<Project> {
23932 fn completions(
23933 &self,
23934 _excerpt_id: ExcerptId,
23935 buffer: &Entity<Buffer>,
23936 buffer_position: text::Anchor,
23937 options: CompletionContext,
23938 _window: &mut Window,
23939 cx: &mut Context<Editor>,
23940 ) -> Task<Result<Vec<CompletionResponse>>> {
23941 self.update(cx, |project, cx| {
23942 let task = project.completions(buffer, buffer_position, options, cx);
23943 cx.background_spawn(task)
23944 })
23945 }
23946
23947 fn resolve_completions(
23948 &self,
23949 buffer: Entity<Buffer>,
23950 completion_indices: Vec<usize>,
23951 completions: Rc<RefCell<Box<[Completion]>>>,
23952 cx: &mut Context<Editor>,
23953 ) -> Task<Result<bool>> {
23954 self.update(cx, |project, cx| {
23955 project.lsp_store().update(cx, |lsp_store, cx| {
23956 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23957 })
23958 })
23959 }
23960
23961 fn apply_additional_edits_for_completion(
23962 &self,
23963 buffer: Entity<Buffer>,
23964 completions: Rc<RefCell<Box<[Completion]>>>,
23965 completion_index: usize,
23966 push_to_history: bool,
23967 cx: &mut Context<Editor>,
23968 ) -> Task<Result<Option<language::Transaction>>> {
23969 self.update(cx, |project, cx| {
23970 project.lsp_store().update(cx, |lsp_store, cx| {
23971 lsp_store.apply_additional_edits_for_completion(
23972 buffer,
23973 completions,
23974 completion_index,
23975 push_to_history,
23976 cx,
23977 )
23978 })
23979 })
23980 }
23981
23982 fn is_completion_trigger(
23983 &self,
23984 buffer: &Entity<Buffer>,
23985 position: language::Anchor,
23986 text: &str,
23987 trigger_in_words: bool,
23988 cx: &mut Context<Editor>,
23989 ) -> bool {
23990 let mut chars = text.chars();
23991 let char = if let Some(char) = chars.next() {
23992 char
23993 } else {
23994 return false;
23995 };
23996 if chars.next().is_some() {
23997 return false;
23998 }
23999
24000 let buffer = buffer.read(cx);
24001 let snapshot = buffer.snapshot();
24002 let classifier = snapshot
24003 .char_classifier_at(position)
24004 .scope_context(Some(CharScopeContext::Completion));
24005 if trigger_in_words && classifier.is_word(char) {
24006 return true;
24007 }
24008
24009 buffer.completion_triggers().contains(text)
24010 }
24011
24012 fn show_snippets(&self) -> bool {
24013 true
24014 }
24015}
24016
24017impl SemanticsProvider for Entity<Project> {
24018 fn hover(
24019 &self,
24020 buffer: &Entity<Buffer>,
24021 position: text::Anchor,
24022 cx: &mut App,
24023 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24024 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24025 }
24026
24027 fn document_highlights(
24028 &self,
24029 buffer: &Entity<Buffer>,
24030 position: text::Anchor,
24031 cx: &mut App,
24032 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24033 Some(self.update(cx, |project, cx| {
24034 project.document_highlights(buffer, position, cx)
24035 }))
24036 }
24037
24038 fn definitions(
24039 &self,
24040 buffer: &Entity<Buffer>,
24041 position: text::Anchor,
24042 kind: GotoDefinitionKind,
24043 cx: &mut App,
24044 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24045 Some(self.update(cx, |project, cx| match kind {
24046 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24047 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24048 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24049 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24050 }))
24051 }
24052
24053 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24054 self.update(cx, |project, cx| {
24055 if project
24056 .active_debug_session(cx)
24057 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24058 {
24059 return true;
24060 }
24061
24062 buffer.update(cx, |buffer, cx| {
24063 project.any_language_server_supports_inlay_hints(buffer, cx)
24064 })
24065 })
24066 }
24067
24068 fn inline_values(
24069 &self,
24070 buffer_handle: Entity<Buffer>,
24071 range: Range<text::Anchor>,
24072 cx: &mut App,
24073 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24074 self.update(cx, |project, cx| {
24075 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24076
24077 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24078 })
24079 }
24080
24081 fn applicable_inlay_chunks(
24082 &self,
24083 buffer: &Entity<Buffer>,
24084 ranges: &[Range<text::Anchor>],
24085 cx: &mut App,
24086 ) -> Vec<Range<BufferRow>> {
24087 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24088 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24089 })
24090 }
24091
24092 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24093 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24094 lsp_store.invalidate_inlay_hints(for_buffers)
24095 });
24096 }
24097
24098 fn inlay_hints(
24099 &self,
24100 invalidate: InvalidationStrategy,
24101 buffer: Entity<Buffer>,
24102 ranges: Vec<Range<text::Anchor>>,
24103 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24104 cx: &mut App,
24105 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24106 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24107 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24108 }))
24109 }
24110
24111 fn range_for_rename(
24112 &self,
24113 buffer: &Entity<Buffer>,
24114 position: text::Anchor,
24115 cx: &mut App,
24116 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24117 Some(self.update(cx, |project, cx| {
24118 let buffer = buffer.clone();
24119 let task = project.prepare_rename(buffer.clone(), position, cx);
24120 cx.spawn(async move |_, cx| {
24121 Ok(match task.await? {
24122 PrepareRenameResponse::Success(range) => Some(range),
24123 PrepareRenameResponse::InvalidPosition => None,
24124 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24125 // Fallback on using TreeSitter info to determine identifier range
24126 buffer.read_with(cx, |buffer, _| {
24127 let snapshot = buffer.snapshot();
24128 let (range, kind) = snapshot.surrounding_word(position, None);
24129 if kind != Some(CharKind::Word) {
24130 return None;
24131 }
24132 Some(
24133 snapshot.anchor_before(range.start)
24134 ..snapshot.anchor_after(range.end),
24135 )
24136 })?
24137 }
24138 })
24139 })
24140 }))
24141 }
24142
24143 fn perform_rename(
24144 &self,
24145 buffer: &Entity<Buffer>,
24146 position: text::Anchor,
24147 new_name: String,
24148 cx: &mut App,
24149 ) -> Option<Task<Result<ProjectTransaction>>> {
24150 Some(self.update(cx, |project, cx| {
24151 project.perform_rename(buffer.clone(), position, new_name, cx)
24152 }))
24153 }
24154}
24155
24156fn consume_contiguous_rows(
24157 contiguous_row_selections: &mut Vec<Selection<Point>>,
24158 selection: &Selection<Point>,
24159 display_map: &DisplaySnapshot,
24160 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24161) -> (MultiBufferRow, MultiBufferRow) {
24162 contiguous_row_selections.push(selection.clone());
24163 let start_row = starting_row(selection, display_map);
24164 let mut end_row = ending_row(selection, display_map);
24165
24166 while let Some(next_selection) = selections.peek() {
24167 if next_selection.start.row <= end_row.0 {
24168 end_row = ending_row(next_selection, display_map);
24169 contiguous_row_selections.push(selections.next().unwrap().clone());
24170 } else {
24171 break;
24172 }
24173 }
24174 (start_row, end_row)
24175}
24176
24177fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24178 if selection.start.column > 0 {
24179 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24180 } else {
24181 MultiBufferRow(selection.start.row)
24182 }
24183}
24184
24185fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24186 if next_selection.end.column > 0 || next_selection.is_empty() {
24187 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24188 } else {
24189 MultiBufferRow(next_selection.end.row)
24190 }
24191}
24192
24193impl EditorSnapshot {
24194 pub fn remote_selections_in_range<'a>(
24195 &'a self,
24196 range: &'a Range<Anchor>,
24197 collaboration_hub: &dyn CollaborationHub,
24198 cx: &'a App,
24199 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24200 let participant_names = collaboration_hub.user_names(cx);
24201 let participant_indices = collaboration_hub.user_participant_indices(cx);
24202 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24203 let collaborators_by_replica_id = collaborators_by_peer_id
24204 .values()
24205 .map(|collaborator| (collaborator.replica_id, collaborator))
24206 .collect::<HashMap<_, _>>();
24207 self.buffer_snapshot()
24208 .selections_in_range(range, false)
24209 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24210 if replica_id == ReplicaId::AGENT {
24211 Some(RemoteSelection {
24212 replica_id,
24213 selection,
24214 cursor_shape,
24215 line_mode,
24216 collaborator_id: CollaboratorId::Agent,
24217 user_name: Some("Agent".into()),
24218 color: cx.theme().players().agent(),
24219 })
24220 } else {
24221 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24222 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24223 let user_name = participant_names.get(&collaborator.user_id).cloned();
24224 Some(RemoteSelection {
24225 replica_id,
24226 selection,
24227 cursor_shape,
24228 line_mode,
24229 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24230 user_name,
24231 color: if let Some(index) = participant_index {
24232 cx.theme().players().color_for_participant(index.0)
24233 } else {
24234 cx.theme().players().absent()
24235 },
24236 })
24237 }
24238 })
24239 }
24240
24241 pub fn hunks_for_ranges(
24242 &self,
24243 ranges: impl IntoIterator<Item = Range<Point>>,
24244 ) -> Vec<MultiBufferDiffHunk> {
24245 let mut hunks = Vec::new();
24246 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24247 HashMap::default();
24248 for query_range in ranges {
24249 let query_rows =
24250 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24251 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24252 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24253 ) {
24254 // Include deleted hunks that are adjacent to the query range, because
24255 // otherwise they would be missed.
24256 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24257 if hunk.status().is_deleted() {
24258 intersects_range |= hunk.row_range.start == query_rows.end;
24259 intersects_range |= hunk.row_range.end == query_rows.start;
24260 }
24261 if intersects_range {
24262 if !processed_buffer_rows
24263 .entry(hunk.buffer_id)
24264 .or_default()
24265 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24266 {
24267 continue;
24268 }
24269 hunks.push(hunk);
24270 }
24271 }
24272 }
24273
24274 hunks
24275 }
24276
24277 fn display_diff_hunks_for_rows<'a>(
24278 &'a self,
24279 display_rows: Range<DisplayRow>,
24280 folded_buffers: &'a HashSet<BufferId>,
24281 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24282 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24283 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24284
24285 self.buffer_snapshot()
24286 .diff_hunks_in_range(buffer_start..buffer_end)
24287 .filter_map(|hunk| {
24288 if folded_buffers.contains(&hunk.buffer_id) {
24289 return None;
24290 }
24291
24292 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24293 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24294
24295 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24296 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24297
24298 let display_hunk = if hunk_display_start.column() != 0 {
24299 DisplayDiffHunk::Folded {
24300 display_row: hunk_display_start.row(),
24301 }
24302 } else {
24303 let mut end_row = hunk_display_end.row();
24304 if hunk_display_end.column() > 0 {
24305 end_row.0 += 1;
24306 }
24307 let is_created_file = hunk.is_created_file();
24308
24309 DisplayDiffHunk::Unfolded {
24310 status: hunk.status(),
24311 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24312 ..hunk.diff_base_byte_range.end.0,
24313 word_diffs: hunk.word_diffs,
24314 display_row_range: hunk_display_start.row()..end_row,
24315 multi_buffer_range: Anchor::range_in_buffer(
24316 hunk.excerpt_id,
24317 hunk.buffer_range,
24318 ),
24319 is_created_file,
24320 }
24321 };
24322
24323 Some(display_hunk)
24324 })
24325 }
24326
24327 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24328 self.display_snapshot
24329 .buffer_snapshot()
24330 .language_at(position)
24331 }
24332
24333 pub fn is_focused(&self) -> bool {
24334 self.is_focused
24335 }
24336
24337 pub fn placeholder_text(&self) -> Option<String> {
24338 self.placeholder_display_snapshot
24339 .as_ref()
24340 .map(|display_map| display_map.text())
24341 }
24342
24343 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24344 self.scroll_anchor.scroll_position(&self.display_snapshot)
24345 }
24346
24347 fn gutter_dimensions(
24348 &self,
24349 font_id: FontId,
24350 font_size: Pixels,
24351 max_line_number_width: Pixels,
24352 cx: &App,
24353 ) -> Option<GutterDimensions> {
24354 if !self.show_gutter {
24355 return None;
24356 }
24357
24358 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
24359 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
24360
24361 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24362 matches!(
24363 ProjectSettings::get_global(cx).git.git_gutter,
24364 GitGutterSetting::TrackedFiles
24365 )
24366 });
24367 let gutter_settings = EditorSettings::get_global(cx).gutter;
24368 let show_line_numbers = self
24369 .show_line_numbers
24370 .unwrap_or(gutter_settings.line_numbers);
24371 let line_gutter_width = if show_line_numbers {
24372 // Avoid flicker-like gutter resizes when the line number gains another digit by
24373 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24374 let min_width_for_number_on_gutter =
24375 ch_advance * gutter_settings.min_line_number_digits as f32;
24376 max_line_number_width.max(min_width_for_number_on_gutter)
24377 } else {
24378 0.0.into()
24379 };
24380
24381 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24382 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24383
24384 let git_blame_entries_width =
24385 self.git_blame_gutter_max_author_length
24386 .map(|max_author_length| {
24387 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24388 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24389
24390 /// The number of characters to dedicate to gaps and margins.
24391 const SPACING_WIDTH: usize = 4;
24392
24393 let max_char_count = max_author_length.min(renderer.max_author_length())
24394 + ::git::SHORT_SHA_LENGTH
24395 + MAX_RELATIVE_TIMESTAMP.len()
24396 + SPACING_WIDTH;
24397
24398 ch_advance * max_char_count
24399 });
24400
24401 let is_singleton = self.buffer_snapshot().is_singleton();
24402
24403 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24404 left_padding += if !is_singleton {
24405 ch_width * 4.0
24406 } else if show_runnables || show_breakpoints {
24407 ch_width * 3.0
24408 } else if show_git_gutter && show_line_numbers {
24409 ch_width * 2.0
24410 } else if show_git_gutter || show_line_numbers {
24411 ch_width
24412 } else {
24413 px(0.)
24414 };
24415
24416 let shows_folds = is_singleton && gutter_settings.folds;
24417
24418 let right_padding = if shows_folds && show_line_numbers {
24419 ch_width * 4.0
24420 } else if shows_folds || (!is_singleton && show_line_numbers) {
24421 ch_width * 3.0
24422 } else if show_line_numbers {
24423 ch_width
24424 } else {
24425 px(0.)
24426 };
24427
24428 Some(GutterDimensions {
24429 left_padding,
24430 right_padding,
24431 width: line_gutter_width + left_padding + right_padding,
24432 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24433 git_blame_entries_width,
24434 })
24435 }
24436
24437 pub fn render_crease_toggle(
24438 &self,
24439 buffer_row: MultiBufferRow,
24440 row_contains_cursor: bool,
24441 editor: Entity<Editor>,
24442 window: &mut Window,
24443 cx: &mut App,
24444 ) -> Option<AnyElement> {
24445 let folded = self.is_line_folded(buffer_row);
24446 let mut is_foldable = false;
24447
24448 if let Some(crease) = self
24449 .crease_snapshot
24450 .query_row(buffer_row, self.buffer_snapshot())
24451 {
24452 is_foldable = true;
24453 match crease {
24454 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24455 if let Some(render_toggle) = render_toggle {
24456 let toggle_callback =
24457 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24458 if folded {
24459 editor.update(cx, |editor, cx| {
24460 editor.fold_at(buffer_row, window, cx)
24461 });
24462 } else {
24463 editor.update(cx, |editor, cx| {
24464 editor.unfold_at(buffer_row, window, cx)
24465 });
24466 }
24467 });
24468 return Some((render_toggle)(
24469 buffer_row,
24470 folded,
24471 toggle_callback,
24472 window,
24473 cx,
24474 ));
24475 }
24476 }
24477 }
24478 }
24479
24480 is_foldable |= self.starts_indent(buffer_row);
24481
24482 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24483 Some(
24484 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24485 .toggle_state(folded)
24486 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24487 if folded {
24488 this.unfold_at(buffer_row, window, cx);
24489 } else {
24490 this.fold_at(buffer_row, window, cx);
24491 }
24492 }))
24493 .into_any_element(),
24494 )
24495 } else {
24496 None
24497 }
24498 }
24499
24500 pub fn render_crease_trailer(
24501 &self,
24502 buffer_row: MultiBufferRow,
24503 window: &mut Window,
24504 cx: &mut App,
24505 ) -> Option<AnyElement> {
24506 let folded = self.is_line_folded(buffer_row);
24507 if let Crease::Inline { render_trailer, .. } = self
24508 .crease_snapshot
24509 .query_row(buffer_row, self.buffer_snapshot())?
24510 {
24511 let render_trailer = render_trailer.as_ref()?;
24512 Some(render_trailer(buffer_row, folded, window, cx))
24513 } else {
24514 None
24515 }
24516 }
24517}
24518
24519impl Deref for EditorSnapshot {
24520 type Target = DisplaySnapshot;
24521
24522 fn deref(&self) -> &Self::Target {
24523 &self.display_snapshot
24524 }
24525}
24526
24527#[derive(Clone, Debug, PartialEq, Eq)]
24528pub enum EditorEvent {
24529 InputIgnored {
24530 text: Arc<str>,
24531 },
24532 InputHandled {
24533 utf16_range_to_replace: Option<Range<isize>>,
24534 text: Arc<str>,
24535 },
24536 ExcerptsAdded {
24537 buffer: Entity<Buffer>,
24538 predecessor: ExcerptId,
24539 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24540 },
24541 ExcerptsRemoved {
24542 ids: Vec<ExcerptId>,
24543 removed_buffer_ids: Vec<BufferId>,
24544 },
24545 BufferFoldToggled {
24546 ids: Vec<ExcerptId>,
24547 folded: bool,
24548 },
24549 ExcerptsEdited {
24550 ids: Vec<ExcerptId>,
24551 },
24552 ExcerptsExpanded {
24553 ids: Vec<ExcerptId>,
24554 },
24555 BufferEdited,
24556 Edited {
24557 transaction_id: clock::Lamport,
24558 },
24559 Reparsed(BufferId),
24560 Focused,
24561 FocusedIn,
24562 Blurred,
24563 DirtyChanged,
24564 Saved,
24565 TitleChanged,
24566 SelectionsChanged {
24567 local: bool,
24568 },
24569 ScrollPositionChanged {
24570 local: bool,
24571 autoscroll: bool,
24572 },
24573 TransactionUndone {
24574 transaction_id: clock::Lamport,
24575 },
24576 TransactionBegun {
24577 transaction_id: clock::Lamport,
24578 },
24579 CursorShapeChanged,
24580 BreadcrumbsChanged,
24581 PushedToNavHistory {
24582 anchor: Anchor,
24583 is_deactivate: bool,
24584 },
24585}
24586
24587impl EventEmitter<EditorEvent> for Editor {}
24588
24589impl Focusable for Editor {
24590 fn focus_handle(&self, _cx: &App) -> FocusHandle {
24591 self.focus_handle.clone()
24592 }
24593}
24594
24595impl Render for Editor {
24596 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24597 let settings = ThemeSettings::get_global(cx);
24598
24599 let mut text_style = match self.mode {
24600 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
24601 color: cx.theme().colors().editor_foreground,
24602 font_family: settings.ui_font.family.clone(),
24603 font_features: settings.ui_font.features.clone(),
24604 font_fallbacks: settings.ui_font.fallbacks.clone(),
24605 font_size: rems(0.875).into(),
24606 font_weight: settings.ui_font.weight,
24607 line_height: relative(settings.buffer_line_height.value()),
24608 ..Default::default()
24609 },
24610 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24611 color: cx.theme().colors().editor_foreground,
24612 font_family: settings.buffer_font.family.clone(),
24613 font_features: settings.buffer_font.features.clone(),
24614 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24615 font_size: settings.buffer_font_size(cx).into(),
24616 font_weight: settings.buffer_font.weight,
24617 line_height: relative(settings.buffer_line_height.value()),
24618 ..Default::default()
24619 },
24620 };
24621 if let Some(text_style_refinement) = &self.text_style_refinement {
24622 text_style.refine(text_style_refinement)
24623 }
24624
24625 let background = match self.mode {
24626 EditorMode::SingleLine => cx.theme().system().transparent,
24627 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24628 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24629 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24630 };
24631
24632 EditorElement::new(
24633 &cx.entity(),
24634 EditorStyle {
24635 background,
24636 border: cx.theme().colors().border,
24637 local_player: cx.theme().players().local(),
24638 text: text_style,
24639 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24640 syntax: cx.theme().syntax().clone(),
24641 status: cx.theme().status().clone(),
24642 inlay_hints_style: make_inlay_hints_style(cx),
24643 edit_prediction_styles: make_suggestion_styles(cx),
24644 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24645 show_underlines: self.diagnostics_enabled(),
24646 },
24647 )
24648 }
24649}
24650
24651impl EntityInputHandler for Editor {
24652 fn text_for_range(
24653 &mut self,
24654 range_utf16: Range<usize>,
24655 adjusted_range: &mut Option<Range<usize>>,
24656 _: &mut Window,
24657 cx: &mut Context<Self>,
24658 ) -> Option<String> {
24659 let snapshot = self.buffer.read(cx).read(cx);
24660 let start = snapshot.clip_offset_utf16(
24661 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
24662 Bias::Left,
24663 );
24664 let end = snapshot.clip_offset_utf16(
24665 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
24666 Bias::Right,
24667 );
24668 if (start.0.0..end.0.0) != range_utf16 {
24669 adjusted_range.replace(start.0.0..end.0.0);
24670 }
24671 Some(snapshot.text_for_range(start..end).collect())
24672 }
24673
24674 fn selected_text_range(
24675 &mut self,
24676 ignore_disabled_input: bool,
24677 _: &mut Window,
24678 cx: &mut Context<Self>,
24679 ) -> Option<UTF16Selection> {
24680 // Prevent the IME menu from appearing when holding down an alphabetic key
24681 // while input is disabled.
24682 if !ignore_disabled_input && !self.input_enabled {
24683 return None;
24684 }
24685
24686 let selection = self
24687 .selections
24688 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
24689 let range = selection.range();
24690
24691 Some(UTF16Selection {
24692 range: range.start.0.0..range.end.0.0,
24693 reversed: selection.reversed,
24694 })
24695 }
24696
24697 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24698 let snapshot = self.buffer.read(cx).read(cx);
24699 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24700 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
24701 }
24702
24703 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24704 self.clear_highlights::<InputComposition>(cx);
24705 self.ime_transaction.take();
24706 }
24707
24708 fn replace_text_in_range(
24709 &mut self,
24710 range_utf16: Option<Range<usize>>,
24711 text: &str,
24712 window: &mut Window,
24713 cx: &mut Context<Self>,
24714 ) {
24715 if !self.input_enabled {
24716 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24717 return;
24718 }
24719
24720 self.transact(window, cx, |this, window, cx| {
24721 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24722 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24723 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24724 Some(this.selection_replacement_ranges(range_utf16, cx))
24725 } else {
24726 this.marked_text_ranges(cx)
24727 };
24728
24729 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24730 let newest_selection_id = this.selections.newest_anchor().id;
24731 this.selections
24732 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24733 .iter()
24734 .zip(ranges_to_replace.iter())
24735 .find_map(|(selection, range)| {
24736 if selection.id == newest_selection_id {
24737 Some(
24738 (range.start.0.0 as isize - selection.head().0.0 as isize)
24739 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24740 )
24741 } else {
24742 None
24743 }
24744 })
24745 });
24746
24747 cx.emit(EditorEvent::InputHandled {
24748 utf16_range_to_replace: range_to_replace,
24749 text: text.into(),
24750 });
24751
24752 if let Some(new_selected_ranges) = new_selected_ranges {
24753 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24754 selections.select_ranges(new_selected_ranges)
24755 });
24756 this.backspace(&Default::default(), window, cx);
24757 }
24758
24759 this.handle_input(text, window, cx);
24760 });
24761
24762 if let Some(transaction) = self.ime_transaction {
24763 self.buffer.update(cx, |buffer, cx| {
24764 buffer.group_until_transaction(transaction, cx);
24765 });
24766 }
24767
24768 self.unmark_text(window, cx);
24769 }
24770
24771 fn replace_and_mark_text_in_range(
24772 &mut self,
24773 range_utf16: Option<Range<usize>>,
24774 text: &str,
24775 new_selected_range_utf16: Option<Range<usize>>,
24776 window: &mut Window,
24777 cx: &mut Context<Self>,
24778 ) {
24779 if !self.input_enabled {
24780 return;
24781 }
24782
24783 let transaction = self.transact(window, cx, |this, window, cx| {
24784 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24785 let snapshot = this.buffer.read(cx).read(cx);
24786 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24787 for marked_range in &mut marked_ranges {
24788 marked_range.end = marked_range.start + relative_range_utf16.end;
24789 marked_range.start += relative_range_utf16.start;
24790 marked_range.start =
24791 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24792 marked_range.end =
24793 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24794 }
24795 }
24796 Some(marked_ranges)
24797 } else if let Some(range_utf16) = range_utf16 {
24798 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
24799 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
24800 Some(this.selection_replacement_ranges(range_utf16, cx))
24801 } else {
24802 None
24803 };
24804
24805 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24806 let newest_selection_id = this.selections.newest_anchor().id;
24807 this.selections
24808 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
24809 .iter()
24810 .zip(ranges_to_replace.iter())
24811 .find_map(|(selection, range)| {
24812 if selection.id == newest_selection_id {
24813 Some(
24814 (range.start.0.0 as isize - selection.head().0.0 as isize)
24815 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
24816 )
24817 } else {
24818 None
24819 }
24820 })
24821 });
24822
24823 cx.emit(EditorEvent::InputHandled {
24824 utf16_range_to_replace: range_to_replace,
24825 text: text.into(),
24826 });
24827
24828 if let Some(ranges) = ranges_to_replace {
24829 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24830 s.select_ranges(ranges)
24831 });
24832 }
24833
24834 let marked_ranges = {
24835 let snapshot = this.buffer.read(cx).read(cx);
24836 this.selections
24837 .disjoint_anchors_arc()
24838 .iter()
24839 .map(|selection| {
24840 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24841 })
24842 .collect::<Vec<_>>()
24843 };
24844
24845 if text.is_empty() {
24846 this.unmark_text(window, cx);
24847 } else {
24848 this.highlight_text::<InputComposition>(
24849 marked_ranges.clone(),
24850 HighlightStyle {
24851 underline: Some(UnderlineStyle {
24852 thickness: px(1.),
24853 color: None,
24854 wavy: false,
24855 }),
24856 ..Default::default()
24857 },
24858 cx,
24859 );
24860 }
24861
24862 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24863 let use_autoclose = this.use_autoclose;
24864 let use_auto_surround = this.use_auto_surround;
24865 this.set_use_autoclose(false);
24866 this.set_use_auto_surround(false);
24867 this.handle_input(text, window, cx);
24868 this.set_use_autoclose(use_autoclose);
24869 this.set_use_auto_surround(use_auto_surround);
24870
24871 if let Some(new_selected_range) = new_selected_range_utf16 {
24872 let snapshot = this.buffer.read(cx).read(cx);
24873 let new_selected_ranges = marked_ranges
24874 .into_iter()
24875 .map(|marked_range| {
24876 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24877 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
24878 insertion_start.0 + new_selected_range.start,
24879 ));
24880 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
24881 insertion_start.0 + new_selected_range.end,
24882 ));
24883 snapshot.clip_offset_utf16(new_start, Bias::Left)
24884 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24885 })
24886 .collect::<Vec<_>>();
24887
24888 drop(snapshot);
24889 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24890 selections.select_ranges(new_selected_ranges)
24891 });
24892 }
24893 });
24894
24895 self.ime_transaction = self.ime_transaction.or(transaction);
24896 if let Some(transaction) = self.ime_transaction {
24897 self.buffer.update(cx, |buffer, cx| {
24898 buffer.group_until_transaction(transaction, cx);
24899 });
24900 }
24901
24902 if self.text_highlights::<InputComposition>(cx).is_none() {
24903 self.ime_transaction.take();
24904 }
24905 }
24906
24907 fn bounds_for_range(
24908 &mut self,
24909 range_utf16: Range<usize>,
24910 element_bounds: gpui::Bounds<Pixels>,
24911 window: &mut Window,
24912 cx: &mut Context<Self>,
24913 ) -> Option<gpui::Bounds<Pixels>> {
24914 let text_layout_details = self.text_layout_details(window);
24915 let CharacterDimensions {
24916 em_width,
24917 em_advance,
24918 line_height,
24919 } = self.character_dimensions(window);
24920
24921 let snapshot = self.snapshot(window, cx);
24922 let scroll_position = snapshot.scroll_position();
24923 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24924
24925 let start =
24926 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
24927 let x = Pixels::from(
24928 ScrollOffset::from(
24929 snapshot.x_for_display_point(start, &text_layout_details)
24930 + self.gutter_dimensions.full_width(),
24931 ) - scroll_left,
24932 );
24933 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24934
24935 Some(Bounds {
24936 origin: element_bounds.origin + point(x, y),
24937 size: size(em_width, line_height),
24938 })
24939 }
24940
24941 fn character_index_for_point(
24942 &mut self,
24943 point: gpui::Point<Pixels>,
24944 _window: &mut Window,
24945 _cx: &mut Context<Self>,
24946 ) -> Option<usize> {
24947 let position_map = self.last_position_map.as_ref()?;
24948 if !position_map.text_hitbox.contains(&point) {
24949 return None;
24950 }
24951 let display_point = position_map.point_for_position(point).previous_valid;
24952 let anchor = position_map
24953 .snapshot
24954 .display_point_to_anchor(display_point, Bias::Left);
24955 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24956 Some(utf16_offset.0.0)
24957 }
24958
24959 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24960 self.input_enabled
24961 }
24962}
24963
24964trait SelectionExt {
24965 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24966 fn spanned_rows(
24967 &self,
24968 include_end_if_at_line_start: bool,
24969 map: &DisplaySnapshot,
24970 ) -> Range<MultiBufferRow>;
24971}
24972
24973impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24974 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24975 let start = self
24976 .start
24977 .to_point(map.buffer_snapshot())
24978 .to_display_point(map);
24979 let end = self
24980 .end
24981 .to_point(map.buffer_snapshot())
24982 .to_display_point(map);
24983 if self.reversed {
24984 end..start
24985 } else {
24986 start..end
24987 }
24988 }
24989
24990 fn spanned_rows(
24991 &self,
24992 include_end_if_at_line_start: bool,
24993 map: &DisplaySnapshot,
24994 ) -> Range<MultiBufferRow> {
24995 let start = self.start.to_point(map.buffer_snapshot());
24996 let mut end = self.end.to_point(map.buffer_snapshot());
24997 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24998 end.row -= 1;
24999 }
25000
25001 let buffer_start = map.prev_line_boundary(start).0;
25002 let buffer_end = map.next_line_boundary(end).0;
25003 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25004 }
25005}
25006
25007impl<T: InvalidationRegion> InvalidationStack<T> {
25008 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25009 where
25010 S: Clone + ToOffset,
25011 {
25012 while let Some(region) = self.last() {
25013 let all_selections_inside_invalidation_ranges =
25014 if selections.len() == region.ranges().len() {
25015 selections
25016 .iter()
25017 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25018 .all(|(selection, invalidation_range)| {
25019 let head = selection.head().to_offset(buffer);
25020 invalidation_range.start <= head && invalidation_range.end >= head
25021 })
25022 } else {
25023 false
25024 };
25025
25026 if all_selections_inside_invalidation_ranges {
25027 break;
25028 } else {
25029 self.pop();
25030 }
25031 }
25032 }
25033}
25034
25035impl<T> Default for InvalidationStack<T> {
25036 fn default() -> Self {
25037 Self(Default::default())
25038 }
25039}
25040
25041impl<T> Deref for InvalidationStack<T> {
25042 type Target = Vec<T>;
25043
25044 fn deref(&self) -> &Self::Target {
25045 &self.0
25046 }
25047}
25048
25049impl<T> DerefMut for InvalidationStack<T> {
25050 fn deref_mut(&mut self) -> &mut Self::Target {
25051 &mut self.0
25052 }
25053}
25054
25055impl InvalidationRegion for SnippetState {
25056 fn ranges(&self) -> &[Range<Anchor>] {
25057 &self.ranges[self.active_index]
25058 }
25059}
25060
25061fn edit_prediction_edit_text(
25062 current_snapshot: &BufferSnapshot,
25063 edits: &[(Range<Anchor>, impl AsRef<str>)],
25064 edit_preview: &EditPreview,
25065 include_deletions: bool,
25066 cx: &App,
25067) -> HighlightedText {
25068 let edits = edits
25069 .iter()
25070 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25071 .collect::<Vec<_>>();
25072
25073 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25074}
25075
25076fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25077 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25078 // Just show the raw edit text with basic styling
25079 let mut text = String::new();
25080 let mut highlights = Vec::new();
25081
25082 let insertion_highlight_style = HighlightStyle {
25083 color: Some(cx.theme().colors().text),
25084 ..Default::default()
25085 };
25086
25087 for (_, edit_text) in edits {
25088 let start_offset = text.len();
25089 text.push_str(edit_text);
25090 let end_offset = text.len();
25091
25092 if start_offset < end_offset {
25093 highlights.push((start_offset..end_offset, insertion_highlight_style));
25094 }
25095 }
25096
25097 HighlightedText {
25098 text: text.into(),
25099 highlights,
25100 }
25101}
25102
25103pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25104 match severity {
25105 lsp::DiagnosticSeverity::ERROR => colors.error,
25106 lsp::DiagnosticSeverity::WARNING => colors.warning,
25107 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25108 lsp::DiagnosticSeverity::HINT => colors.info,
25109 _ => colors.ignored,
25110 }
25111}
25112
25113pub fn styled_runs_for_code_label<'a>(
25114 label: &'a CodeLabel,
25115 syntax_theme: &'a theme::SyntaxTheme,
25116 local_player: &'a theme::PlayerColor,
25117) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25118 let fade_out = HighlightStyle {
25119 fade_out: Some(0.35),
25120 ..Default::default()
25121 };
25122
25123 let mut prev_end = label.filter_range.end;
25124 label
25125 .runs
25126 .iter()
25127 .enumerate()
25128 .flat_map(move |(ix, (range, highlight_id))| {
25129 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25130 HighlightStyle {
25131 color: Some(local_player.cursor),
25132 ..Default::default()
25133 }
25134 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25135 HighlightStyle {
25136 background_color: Some(local_player.selection),
25137 ..Default::default()
25138 }
25139 } else if let Some(style) = highlight_id.style(syntax_theme) {
25140 style
25141 } else {
25142 return Default::default();
25143 };
25144 let muted_style = style.highlight(fade_out);
25145
25146 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25147 if range.start >= label.filter_range.end {
25148 if range.start > prev_end {
25149 runs.push((prev_end..range.start, fade_out));
25150 }
25151 runs.push((range.clone(), muted_style));
25152 } else if range.end <= label.filter_range.end {
25153 runs.push((range.clone(), style));
25154 } else {
25155 runs.push((range.start..label.filter_range.end, style));
25156 runs.push((label.filter_range.end..range.end, muted_style));
25157 }
25158 prev_end = cmp::max(prev_end, range.end);
25159
25160 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25161 runs.push((prev_end..label.text.len(), fade_out));
25162 }
25163
25164 runs
25165 })
25166}
25167
25168pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25169 let mut prev_index = 0;
25170 let mut prev_codepoint: Option<char> = None;
25171 text.char_indices()
25172 .chain([(text.len(), '\0')])
25173 .filter_map(move |(index, codepoint)| {
25174 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25175 let is_boundary = index == text.len()
25176 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25177 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25178 if is_boundary {
25179 let chunk = &text[prev_index..index];
25180 prev_index = index;
25181 Some(chunk)
25182 } else {
25183 None
25184 }
25185 })
25186}
25187
25188/// Given a string of text immediately before the cursor, iterates over possible
25189/// strings a snippet could match to. More precisely: returns an iterator over
25190/// suffixes of `text` created by splitting at word boundaries (before & after
25191/// every non-word character).
25192///
25193/// Shorter suffixes are returned first.
25194pub(crate) fn snippet_candidate_suffixes(
25195 text: &str,
25196 is_word_char: impl Fn(char) -> bool,
25197) -> impl std::iter::Iterator<Item = &str> {
25198 let mut prev_index = text.len();
25199 let mut prev_codepoint = None;
25200 text.char_indices()
25201 .rev()
25202 .chain([(0, '\0')])
25203 .filter_map(move |(index, codepoint)| {
25204 let prev_index = std::mem::replace(&mut prev_index, index);
25205 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25206 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25207 None
25208 } else {
25209 let chunk = &text[prev_index..]; // go to end of string
25210 Some(chunk)
25211 }
25212 })
25213}
25214
25215pub trait RangeToAnchorExt: Sized {
25216 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25217
25218 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25219 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25220 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25221 }
25222}
25223
25224impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25225 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25226 let start_offset = self.start.to_offset(snapshot);
25227 let end_offset = self.end.to_offset(snapshot);
25228 if start_offset == end_offset {
25229 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25230 } else {
25231 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25232 }
25233 }
25234}
25235
25236pub trait RowExt {
25237 fn as_f64(&self) -> f64;
25238
25239 fn next_row(&self) -> Self;
25240
25241 fn previous_row(&self) -> Self;
25242
25243 fn minus(&self, other: Self) -> u32;
25244}
25245
25246impl RowExt for DisplayRow {
25247 fn as_f64(&self) -> f64 {
25248 self.0 as _
25249 }
25250
25251 fn next_row(&self) -> Self {
25252 Self(self.0 + 1)
25253 }
25254
25255 fn previous_row(&self) -> Self {
25256 Self(self.0.saturating_sub(1))
25257 }
25258
25259 fn minus(&self, other: Self) -> u32 {
25260 self.0 - other.0
25261 }
25262}
25263
25264impl RowExt for MultiBufferRow {
25265 fn as_f64(&self) -> f64 {
25266 self.0 as _
25267 }
25268
25269 fn next_row(&self) -> Self {
25270 Self(self.0 + 1)
25271 }
25272
25273 fn previous_row(&self) -> Self {
25274 Self(self.0.saturating_sub(1))
25275 }
25276
25277 fn minus(&self, other: Self) -> u32 {
25278 self.0 - other.0
25279 }
25280}
25281
25282trait RowRangeExt {
25283 type Row;
25284
25285 fn len(&self) -> usize;
25286
25287 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25288}
25289
25290impl RowRangeExt for Range<MultiBufferRow> {
25291 type Row = MultiBufferRow;
25292
25293 fn len(&self) -> usize {
25294 (self.end.0 - self.start.0) as usize
25295 }
25296
25297 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25298 (self.start.0..self.end.0).map(MultiBufferRow)
25299 }
25300}
25301
25302impl RowRangeExt for Range<DisplayRow> {
25303 type Row = DisplayRow;
25304
25305 fn len(&self) -> usize {
25306 (self.end.0 - self.start.0) as usize
25307 }
25308
25309 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25310 (self.start.0..self.end.0).map(DisplayRow)
25311 }
25312}
25313
25314/// If select range has more than one line, we
25315/// just point the cursor to range.start.
25316fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25317 if range.start.row == range.end.row {
25318 range
25319 } else {
25320 range.start..range.start
25321 }
25322}
25323pub struct KillRing(ClipboardItem);
25324impl Global for KillRing {}
25325
25326const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25327
25328enum BreakpointPromptEditAction {
25329 Log,
25330 Condition,
25331 HitCondition,
25332}
25333
25334struct BreakpointPromptEditor {
25335 pub(crate) prompt: Entity<Editor>,
25336 editor: WeakEntity<Editor>,
25337 breakpoint_anchor: Anchor,
25338 breakpoint: Breakpoint,
25339 edit_action: BreakpointPromptEditAction,
25340 block_ids: HashSet<CustomBlockId>,
25341 editor_margins: Arc<Mutex<EditorMargins>>,
25342 _subscriptions: Vec<Subscription>,
25343}
25344
25345impl BreakpointPromptEditor {
25346 const MAX_LINES: u8 = 4;
25347
25348 fn new(
25349 editor: WeakEntity<Editor>,
25350 breakpoint_anchor: Anchor,
25351 breakpoint: Breakpoint,
25352 edit_action: BreakpointPromptEditAction,
25353 window: &mut Window,
25354 cx: &mut Context<Self>,
25355 ) -> Self {
25356 let base_text = match edit_action {
25357 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25358 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25359 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25360 }
25361 .map(|msg| msg.to_string())
25362 .unwrap_or_default();
25363
25364 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25365 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25366
25367 let prompt = cx.new(|cx| {
25368 let mut prompt = Editor::new(
25369 EditorMode::AutoHeight {
25370 min_lines: 1,
25371 max_lines: Some(Self::MAX_LINES as usize),
25372 },
25373 buffer,
25374 None,
25375 window,
25376 cx,
25377 );
25378 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25379 prompt.set_show_cursor_when_unfocused(false, cx);
25380 prompt.set_placeholder_text(
25381 match edit_action {
25382 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25383 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25384 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25385 },
25386 window,
25387 cx,
25388 );
25389
25390 prompt
25391 });
25392
25393 Self {
25394 prompt,
25395 editor,
25396 breakpoint_anchor,
25397 breakpoint,
25398 edit_action,
25399 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25400 block_ids: Default::default(),
25401 _subscriptions: vec![],
25402 }
25403 }
25404
25405 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25406 self.block_ids.extend(block_ids)
25407 }
25408
25409 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25410 if let Some(editor) = self.editor.upgrade() {
25411 let message = self
25412 .prompt
25413 .read(cx)
25414 .buffer
25415 .read(cx)
25416 .as_singleton()
25417 .expect("A multi buffer in breakpoint prompt isn't possible")
25418 .read(cx)
25419 .as_rope()
25420 .to_string();
25421
25422 editor.update(cx, |editor, cx| {
25423 editor.edit_breakpoint_at_anchor(
25424 self.breakpoint_anchor,
25425 self.breakpoint.clone(),
25426 match self.edit_action {
25427 BreakpointPromptEditAction::Log => {
25428 BreakpointEditAction::EditLogMessage(message.into())
25429 }
25430 BreakpointPromptEditAction::Condition => {
25431 BreakpointEditAction::EditCondition(message.into())
25432 }
25433 BreakpointPromptEditAction::HitCondition => {
25434 BreakpointEditAction::EditHitCondition(message.into())
25435 }
25436 },
25437 cx,
25438 );
25439
25440 editor.remove_blocks(self.block_ids.clone(), None, cx);
25441 cx.focus_self(window);
25442 });
25443 }
25444 }
25445
25446 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25447 self.editor
25448 .update(cx, |editor, cx| {
25449 editor.remove_blocks(self.block_ids.clone(), None, cx);
25450 window.focus(&editor.focus_handle);
25451 })
25452 .log_err();
25453 }
25454
25455 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25456 let settings = ThemeSettings::get_global(cx);
25457 let text_style = TextStyle {
25458 color: if self.prompt.read(cx).read_only(cx) {
25459 cx.theme().colors().text_disabled
25460 } else {
25461 cx.theme().colors().text
25462 },
25463 font_family: settings.buffer_font.family.clone(),
25464 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25465 font_size: settings.buffer_font_size(cx).into(),
25466 font_weight: settings.buffer_font.weight,
25467 line_height: relative(settings.buffer_line_height.value()),
25468 ..Default::default()
25469 };
25470 EditorElement::new(
25471 &self.prompt,
25472 EditorStyle {
25473 background: cx.theme().colors().editor_background,
25474 local_player: cx.theme().players().local(),
25475 text: text_style,
25476 ..Default::default()
25477 },
25478 )
25479 }
25480}
25481
25482impl Render for BreakpointPromptEditor {
25483 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25484 let editor_margins = *self.editor_margins.lock();
25485 let gutter_dimensions = editor_margins.gutter;
25486 h_flex()
25487 .key_context("Editor")
25488 .bg(cx.theme().colors().editor_background)
25489 .border_y_1()
25490 .border_color(cx.theme().status().info_border)
25491 .size_full()
25492 .py(window.line_height() / 2.5)
25493 .on_action(cx.listener(Self::confirm))
25494 .on_action(cx.listener(Self::cancel))
25495 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25496 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25497 }
25498}
25499
25500impl Focusable for BreakpointPromptEditor {
25501 fn focus_handle(&self, cx: &App) -> FocusHandle {
25502 self.prompt.focus_handle(cx)
25503 }
25504}
25505
25506fn all_edits_insertions_or_deletions(
25507 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25508 snapshot: &MultiBufferSnapshot,
25509) -> bool {
25510 let mut all_insertions = true;
25511 let mut all_deletions = true;
25512
25513 for (range, new_text) in edits.iter() {
25514 let range_is_empty = range.to_offset(snapshot).is_empty();
25515 let text_is_empty = new_text.is_empty();
25516
25517 if range_is_empty != text_is_empty {
25518 if range_is_empty {
25519 all_deletions = false;
25520 } else {
25521 all_insertions = false;
25522 }
25523 } else {
25524 return false;
25525 }
25526
25527 if !all_insertions && !all_deletions {
25528 return false;
25529 }
25530 }
25531 all_insertions || all_deletions
25532}
25533
25534struct MissingEditPredictionKeybindingTooltip;
25535
25536impl Render for MissingEditPredictionKeybindingTooltip {
25537 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25538 ui::tooltip_container(cx, |container, cx| {
25539 container
25540 .flex_shrink_0()
25541 .max_w_80()
25542 .min_h(rems_from_px(124.))
25543 .justify_between()
25544 .child(
25545 v_flex()
25546 .flex_1()
25547 .text_ui_sm(cx)
25548 .child(Label::new("Conflict with Accept Keybinding"))
25549 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25550 )
25551 .child(
25552 h_flex()
25553 .pb_1()
25554 .gap_1()
25555 .items_end()
25556 .w_full()
25557 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25558 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25559 }))
25560 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25561 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25562 })),
25563 )
25564 })
25565 }
25566}
25567
25568#[derive(Debug, Clone, Copy, PartialEq)]
25569pub struct LineHighlight {
25570 pub background: Background,
25571 pub border: Option<gpui::Hsla>,
25572 pub include_gutter: bool,
25573 pub type_id: Option<TypeId>,
25574}
25575
25576struct LineManipulationResult {
25577 pub new_text: String,
25578 pub line_count_before: usize,
25579 pub line_count_after: usize,
25580}
25581
25582fn render_diff_hunk_controls(
25583 row: u32,
25584 status: &DiffHunkStatus,
25585 hunk_range: Range<Anchor>,
25586 is_created_file: bool,
25587 line_height: Pixels,
25588 editor: &Entity<Editor>,
25589 _window: &mut Window,
25590 cx: &mut App,
25591) -> AnyElement {
25592 h_flex()
25593 .h(line_height)
25594 .mr_1()
25595 .gap_1()
25596 .px_0p5()
25597 .pb_1()
25598 .border_x_1()
25599 .border_b_1()
25600 .border_color(cx.theme().colors().border_variant)
25601 .rounded_b_lg()
25602 .bg(cx.theme().colors().editor_background)
25603 .gap_1()
25604 .block_mouse_except_scroll()
25605 .shadow_md()
25606 .child(if status.has_secondary_hunk() {
25607 Button::new(("stage", row as u64), "Stage")
25608 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25609 .tooltip({
25610 let focus_handle = editor.focus_handle(cx);
25611 move |_window, cx| {
25612 Tooltip::for_action_in(
25613 "Stage Hunk",
25614 &::git::ToggleStaged,
25615 &focus_handle,
25616 cx,
25617 )
25618 }
25619 })
25620 .on_click({
25621 let editor = editor.clone();
25622 move |_event, _window, cx| {
25623 editor.update(cx, |editor, cx| {
25624 editor.stage_or_unstage_diff_hunks(
25625 true,
25626 vec![hunk_range.start..hunk_range.start],
25627 cx,
25628 );
25629 });
25630 }
25631 })
25632 } else {
25633 Button::new(("unstage", row as u64), "Unstage")
25634 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
25635 .tooltip({
25636 let focus_handle = editor.focus_handle(cx);
25637 move |_window, cx| {
25638 Tooltip::for_action_in(
25639 "Unstage Hunk",
25640 &::git::ToggleStaged,
25641 &focus_handle,
25642 cx,
25643 )
25644 }
25645 })
25646 .on_click({
25647 let editor = editor.clone();
25648 move |_event, _window, cx| {
25649 editor.update(cx, |editor, cx| {
25650 editor.stage_or_unstage_diff_hunks(
25651 false,
25652 vec![hunk_range.start..hunk_range.start],
25653 cx,
25654 );
25655 });
25656 }
25657 })
25658 })
25659 .child(
25660 Button::new(("restore", row as u64), "Restore")
25661 .tooltip({
25662 let focus_handle = editor.focus_handle(cx);
25663 move |_window, cx| {
25664 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25665 }
25666 })
25667 .on_click({
25668 let editor = editor.clone();
25669 move |_event, window, cx| {
25670 editor.update(cx, |editor, cx| {
25671 let snapshot = editor.snapshot(window, cx);
25672 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25673 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25674 });
25675 }
25676 })
25677 .disabled(is_created_file),
25678 )
25679 .when(
25680 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25681 |el| {
25682 el.child(
25683 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25684 .shape(IconButtonShape::Square)
25685 .icon_size(IconSize::Small)
25686 // .disabled(!has_multiple_hunks)
25687 .tooltip({
25688 let focus_handle = editor.focus_handle(cx);
25689 move |_window, cx| {
25690 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25691 }
25692 })
25693 .on_click({
25694 let editor = editor.clone();
25695 move |_event, window, cx| {
25696 editor.update(cx, |editor, cx| {
25697 let snapshot = editor.snapshot(window, cx);
25698 let position =
25699 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25700 editor.go_to_hunk_before_or_after_position(
25701 &snapshot,
25702 position,
25703 Direction::Next,
25704 window,
25705 cx,
25706 );
25707 editor.expand_selected_diff_hunks(cx);
25708 });
25709 }
25710 }),
25711 )
25712 .child(
25713 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25714 .shape(IconButtonShape::Square)
25715 .icon_size(IconSize::Small)
25716 // .disabled(!has_multiple_hunks)
25717 .tooltip({
25718 let focus_handle = editor.focus_handle(cx);
25719 move |_window, cx| {
25720 Tooltip::for_action_in(
25721 "Previous Hunk",
25722 &GoToPreviousHunk,
25723 &focus_handle,
25724 cx,
25725 )
25726 }
25727 })
25728 .on_click({
25729 let editor = editor.clone();
25730 move |_event, window, cx| {
25731 editor.update(cx, |editor, cx| {
25732 let snapshot = editor.snapshot(window, cx);
25733 let point =
25734 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25735 editor.go_to_hunk_before_or_after_position(
25736 &snapshot,
25737 point,
25738 Direction::Prev,
25739 window,
25740 cx,
25741 );
25742 editor.expand_selected_diff_hunks(cx);
25743 });
25744 }
25745 }),
25746 )
25747 },
25748 )
25749 .into_any_element()
25750}
25751
25752pub fn multibuffer_context_lines(cx: &App) -> u32 {
25753 EditorSettings::try_get(cx)
25754 .map(|settings| settings.excerpt_context_lines)
25755 .unwrap_or(2)
25756 .min(32)
25757}