1#![allow(rustdoc::private_intra_doc_links)]
2//! This is the place where everything editor-related is stored (data-wise) and displayed (ui-wise).
3//! The main point of interest in this crate is [`Editor`] type, which is used in every other Zed part as a user input element.
4//! It comes in different flavors: single line, multiline and a fixed height one.
5//!
6//! Editor contains of multiple large submodules:
7//! * [`element`] — the place where all rendering happens
8//! * [`display_map`] - chunks up text in the editor into the logical blocks, establishes coordinates and mapping between each of them.
9//! Contains all metadata related to text transformations (folds, fake inlay text insertions, soft wraps, tab markup, etc.).
10//!
11//! All other submodules and structs are mostly concerned with holding editor data about the way it displays current buffer region(s).
12//!
13//! If you're looking to improve Vim mode, you should check out Vim crate that wraps Editor and overrides its behavior.
14pub mod actions;
15pub mod blink_manager;
16mod bracket_colorization;
17mod clangd_ext;
18pub mod code_context_menus;
19pub mod display_map;
20mod editor_settings;
21mod element;
22mod git;
23mod highlight_matching_bracket;
24mod hover_links;
25pub mod hover_popover;
26mod indent_guides;
27mod inlays;
28pub mod items;
29mod jsx_tag_auto_close;
30mod linked_editing_ranges;
31mod lsp_colors;
32mod lsp_ext;
33mod mouse_context_menu;
34pub mod movement;
35mod persistence;
36mod rust_analyzer_ext;
37pub mod scroll;
38mod selections_collection;
39mod split;
40pub mod tasks;
41
42#[cfg(test)]
43mod code_completion_tests;
44#[cfg(test)]
45mod edit_prediction_tests;
46#[cfg(test)]
47mod editor_tests;
48mod signature_help;
49#[cfg(any(test, feature = "test-support"))]
50pub mod test;
51
52pub(crate) use actions::*;
53pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
54pub use edit_prediction_types::Direction;
55pub use editor_settings::{
56 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
57 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
58};
59pub use element::{
60 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
61};
62pub use git::blame::BlameRenderer;
63pub use hover_popover::hover_markdown_style;
64pub use inlays::Inlay;
65pub use items::MAX_TAB_TITLE_LEN;
66pub use lsp::CompletionContext;
67pub use lsp_ext::lsp_tasks;
68pub use multi_buffer::{
69 Anchor, AnchorRangeExt, BufferOffset, ExcerptId, ExcerptRange, MBTextSummary, MultiBuffer,
70 MultiBufferOffset, MultiBufferOffsetUtf16, MultiBufferSnapshot, PathKey, RowInfo, ToOffset,
71 ToPoint,
72};
73pub use split::SplittableEditor;
74pub use text::Bias;
75
76use ::git::{
77 Restore,
78 blame::{BlameEntry, ParsedCommitMessage},
79 status::FileStatus,
80};
81use aho_corasick::{AhoCorasick, AhoCorasickBuilder, BuildError};
82use anyhow::{Context as _, Result, anyhow, bail};
83use blink_manager::BlinkManager;
84use buffer_diff::DiffHunkStatus;
85use client::{Collaborator, ParticipantIndex, parse_zed_link};
86use clock::ReplicaId;
87use code_context_menus::{
88 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
89 CompletionsMenu, ContextMenuOrigin,
90};
91use collections::{BTreeMap, HashMap, HashSet, VecDeque};
92use convert_case::{Case, Casing};
93use dap::TelemetrySpawnLocation;
94use display_map::*;
95use edit_prediction_types::{
96 EditPredictionDelegate, EditPredictionDelegateHandle, EditPredictionGranularity,
97};
98use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
99use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
100use futures::{
101 FutureExt, StreamExt as _,
102 future::{self, Shared, join},
103 stream::FuturesUnordered,
104};
105use fuzzy::{StringMatch, StringMatchCandidate};
106use git::blame::{GitBlame, GlobalBlameRenderer};
107use gpui::{
108 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
109 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
110 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
111 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
112 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, PressureStage,
113 Render, ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun,
114 TextStyle, TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle,
115 WeakEntity, WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative,
116 size,
117};
118use hover_links::{HoverLink, HoveredLinkState, find_file};
119use hover_popover::{HoverState, hide_hover};
120use indent_guides::ActiveIndentGuidesState;
121use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
122use itertools::{Either, Itertools};
123use language::{
124 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
125 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
126 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
127 IndentSize, Language, LanguageName, LanguageRegistry, LanguageScope, OffsetRangeExt,
128 OutlineItem, Point, Runnable, Selection, SelectionGoal, TextObject, TransactionId,
129 TreeSitterOptions, WordsQuery,
130 language_settings::{
131 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
132 all_language_settings, language_settings,
133 },
134 point_from_lsp, point_to_lsp, text_diff_with_options,
135};
136use linked_editing_ranges::refresh_linked_ranges;
137use lsp::{
138 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
139 LanguageServerId,
140};
141use lsp_colors::LspColorData;
142use markdown::Markdown;
143use mouse_context_menu::MouseContextMenu;
144use movement::TextLayoutDetails;
145use multi_buffer::{
146 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
147};
148use parking_lot::Mutex;
149use persistence::DB;
150use project::{
151 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
152 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
153 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
154 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
155 debugger::{
156 breakpoint_store::{
157 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
158 BreakpointStore, BreakpointStoreEvent,
159 },
160 session::{Session, SessionEvent},
161 },
162 git_store::GitStoreEvent,
163 lsp_store::{
164 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
165 OpenLspBufferHandle,
166 },
167 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
168};
169use rand::seq::SliceRandom;
170use rpc::{ErrorCode, ErrorExt, proto::PeerId};
171use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
172use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
173use serde::{Deserialize, Serialize};
174use settings::{
175 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
176 update_settings_file,
177};
178use smallvec::{SmallVec, smallvec};
179use snippet::Snippet;
180use std::{
181 any::{Any, TypeId},
182 borrow::Cow,
183 cell::{OnceCell, RefCell},
184 cmp::{self, Ordering, Reverse},
185 collections::hash_map,
186 iter::{self, Peekable},
187 mem,
188 num::NonZeroU32,
189 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
190 path::{Path, PathBuf},
191 rc::Rc,
192 sync::Arc,
193 time::{Duration, Instant},
194};
195use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
196use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
197use theme::{
198 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
199 observe_buffer_font_size_adjustment,
200};
201use ui::{
202 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
203 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
204};
205use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
206use workspace::{
207 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
208 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
209 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
210 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
211 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
212 searchable::SearchEvent,
213};
214
215use crate::{
216 code_context_menus::CompletionsMenuSource,
217 editor_settings::MultiCursorModifier,
218 hover_links::{find_url, find_url_from_range},
219 inlays::{
220 InlineValueCache,
221 inlay_hints::{LspInlayHintData, inlay_hint_settings},
222 },
223 scroll::{ScrollOffset, ScrollPixelOffset},
224 selections_collection::resolve_selections_wrapping_blocks,
225 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
226};
227
228pub const FILE_HEADER_HEIGHT: u32 = 2;
229pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
230const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
231const MAX_LINE_LEN: usize = 1024;
232const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
233const MAX_SELECTION_HISTORY_LEN: usize = 1024;
234pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
235#[doc(hidden)]
236pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
237pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
238
239pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
240pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
241pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
242pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
243
244pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
245pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
246pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
247
248pub type RenderDiffHunkControlsFn = Arc<
249 dyn Fn(
250 u32,
251 &DiffHunkStatus,
252 Range<Anchor>,
253 bool,
254 Pixels,
255 &Entity<Editor>,
256 &mut Window,
257 &mut App,
258 ) -> AnyElement,
259>;
260
261enum ReportEditorEvent {
262 Saved { auto_saved: bool },
263 EditorOpened,
264 Closed,
265}
266
267impl ReportEditorEvent {
268 pub fn event_type(&self) -> &'static str {
269 match self {
270 Self::Saved { .. } => "Editor Saved",
271 Self::EditorOpened => "Editor Opened",
272 Self::Closed => "Editor Closed",
273 }
274 }
275}
276
277pub enum ActiveDebugLine {}
278pub enum DebugStackFrameLine {}
279enum DocumentHighlightRead {}
280enum DocumentHighlightWrite {}
281enum InputComposition {}
282pub enum PendingInput {}
283enum SelectedTextHighlight {}
284
285pub enum ConflictsOuter {}
286pub enum ConflictsOurs {}
287pub enum ConflictsTheirs {}
288pub enum ConflictsOursMarker {}
289pub enum ConflictsTheirsMarker {}
290
291pub struct HunkAddedColor;
292pub struct HunkRemovedColor;
293
294#[derive(Debug, Copy, Clone, PartialEq, Eq)]
295pub enum Navigated {
296 Yes,
297 No,
298}
299
300impl Navigated {
301 pub fn from_bool(yes: bool) -> Navigated {
302 if yes { Navigated::Yes } else { Navigated::No }
303 }
304}
305
306#[derive(Debug, Clone, PartialEq, Eq)]
307enum DisplayDiffHunk {
308 Folded {
309 display_row: DisplayRow,
310 },
311 Unfolded {
312 is_created_file: bool,
313 diff_base_byte_range: Range<usize>,
314 display_row_range: Range<DisplayRow>,
315 multi_buffer_range: Range<Anchor>,
316 status: DiffHunkStatus,
317 word_diffs: Vec<Range<MultiBufferOffset>>,
318 },
319}
320
321pub enum HideMouseCursorOrigin {
322 TypingAction,
323 MovementAction,
324}
325
326pub fn init(cx: &mut App) {
327 cx.set_global(GlobalBlameRenderer(Arc::new(())));
328
329 workspace::register_project_item::<Editor>(cx);
330 workspace::FollowableViewRegistry::register::<Editor>(cx);
331 workspace::register_serializable_item::<Editor>(cx);
332
333 cx.observe_new(
334 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
335 workspace.register_action(Editor::new_file);
336 workspace.register_action(Editor::new_file_split);
337 workspace.register_action(Editor::new_file_vertical);
338 workspace.register_action(Editor::new_file_horizontal);
339 workspace.register_action(Editor::cancel_language_server_work);
340 workspace.register_action(Editor::toggle_focus);
341 },
342 )
343 .detach();
344
345 cx.on_action(move |_: &workspace::NewFile, cx| {
346 let app_state = workspace::AppState::global(cx);
347 if let Some(app_state) = app_state.upgrade() {
348 workspace::open_new(
349 Default::default(),
350 app_state,
351 cx,
352 |workspace, window, cx| {
353 Editor::new_file(workspace, &Default::default(), window, cx)
354 },
355 )
356 .detach();
357 }
358 })
359 .on_action(move |_: &workspace::NewWindow, cx| {
360 let app_state = workspace::AppState::global(cx);
361 if let Some(app_state) = app_state.upgrade() {
362 workspace::open_new(
363 Default::default(),
364 app_state,
365 cx,
366 |workspace, window, cx| {
367 cx.activate(true);
368 Editor::new_file(workspace, &Default::default(), window, cx)
369 },
370 )
371 .detach();
372 }
373 });
374}
375
376pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
377 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
378}
379
380pub trait DiagnosticRenderer {
381 fn render_group(
382 &self,
383 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
384 buffer_id: BufferId,
385 snapshot: EditorSnapshot,
386 editor: WeakEntity<Editor>,
387 language_registry: Option<Arc<LanguageRegistry>>,
388 cx: &mut App,
389 ) -> Vec<BlockProperties<Anchor>>;
390
391 fn render_hover(
392 &self,
393 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
394 range: Range<Point>,
395 buffer_id: BufferId,
396 language_registry: Option<Arc<LanguageRegistry>>,
397 cx: &mut App,
398 ) -> Option<Entity<markdown::Markdown>>;
399
400 fn open_link(
401 &self,
402 editor: &mut Editor,
403 link: SharedString,
404 window: &mut Window,
405 cx: &mut Context<Editor>,
406 );
407}
408
409pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
410
411impl GlobalDiagnosticRenderer {
412 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
413 cx.try_global::<Self>().map(|g| g.0.clone())
414 }
415}
416
417impl gpui::Global for GlobalDiagnosticRenderer {}
418pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
419 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
420}
421
422pub struct SearchWithinRange;
423
424trait InvalidationRegion {
425 fn ranges(&self) -> &[Range<Anchor>];
426}
427
428#[derive(Clone, Debug, PartialEq)]
429pub enum SelectPhase {
430 Begin {
431 position: DisplayPoint,
432 add: bool,
433 click_count: usize,
434 },
435 BeginColumnar {
436 position: DisplayPoint,
437 reset: bool,
438 mode: ColumnarMode,
439 goal_column: u32,
440 },
441 Extend {
442 position: DisplayPoint,
443 click_count: usize,
444 },
445 Update {
446 position: DisplayPoint,
447 goal_column: u32,
448 scroll_delta: gpui::Point<f32>,
449 },
450 End,
451}
452
453#[derive(Clone, Debug, PartialEq)]
454pub enum ColumnarMode {
455 FromMouse,
456 FromSelection,
457}
458
459#[derive(Clone, Debug)]
460pub enum SelectMode {
461 Character,
462 Word(Range<Anchor>),
463 Line(Range<Anchor>),
464 All,
465}
466
467#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
468pub enum SizingBehavior {
469 /// The editor will layout itself using `size_full` and will include the vertical
470 /// scroll margin as requested by user settings.
471 #[default]
472 Default,
473 /// The editor will layout itself using `size_full`, but will not have any
474 /// vertical overscroll.
475 ExcludeOverscrollMargin,
476 /// The editor will request a vertical size according to its content and will be
477 /// layouted without a vertical scroll margin.
478 SizeByContent,
479}
480
481#[derive(Clone, PartialEq, Eq, Debug)]
482pub enum EditorMode {
483 SingleLine,
484 AutoHeight {
485 min_lines: usize,
486 max_lines: Option<usize>,
487 },
488 Full {
489 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
490 scale_ui_elements_with_buffer_font_size: bool,
491 /// When set to `true`, the editor will render a background for the active line.
492 show_active_line_background: bool,
493 /// Determines the sizing behavior for this editor
494 sizing_behavior: SizingBehavior,
495 },
496 Minimap {
497 parent: WeakEntity<Editor>,
498 },
499}
500
501impl EditorMode {
502 pub fn full() -> Self {
503 Self::Full {
504 scale_ui_elements_with_buffer_font_size: true,
505 show_active_line_background: true,
506 sizing_behavior: SizingBehavior::Default,
507 }
508 }
509
510 #[inline]
511 pub fn is_full(&self) -> bool {
512 matches!(self, Self::Full { .. })
513 }
514
515 #[inline]
516 pub fn is_single_line(&self) -> bool {
517 matches!(self, Self::SingleLine { .. })
518 }
519
520 #[inline]
521 fn is_minimap(&self) -> bool {
522 matches!(self, Self::Minimap { .. })
523 }
524}
525
526#[derive(Copy, Clone, Debug)]
527pub enum SoftWrap {
528 /// Prefer not to wrap at all.
529 ///
530 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
531 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
532 GitDiff,
533 /// Prefer a single line generally, unless an overly long line is encountered.
534 None,
535 /// Soft wrap lines that exceed the editor width.
536 EditorWidth,
537 /// Soft wrap lines at the preferred line length.
538 Column(u32),
539 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
540 Bounded(u32),
541}
542
543#[derive(Clone)]
544pub struct EditorStyle {
545 pub background: Hsla,
546 pub border: Hsla,
547 pub local_player: PlayerColor,
548 pub text: TextStyle,
549 pub scrollbar_width: Pixels,
550 pub syntax: Arc<SyntaxTheme>,
551 pub status: StatusColors,
552 pub inlay_hints_style: HighlightStyle,
553 pub edit_prediction_styles: EditPredictionStyles,
554 pub unnecessary_code_fade: f32,
555 pub show_underlines: bool,
556}
557
558impl Default for EditorStyle {
559 fn default() -> Self {
560 Self {
561 background: Hsla::default(),
562 border: Hsla::default(),
563 local_player: PlayerColor::default(),
564 text: TextStyle::default(),
565 scrollbar_width: Pixels::default(),
566 syntax: Default::default(),
567 // HACK: Status colors don't have a real default.
568 // We should look into removing the status colors from the editor
569 // style and retrieve them directly from the theme.
570 status: StatusColors::dark(),
571 inlay_hints_style: HighlightStyle::default(),
572 edit_prediction_styles: EditPredictionStyles {
573 insertion: HighlightStyle::default(),
574 whitespace: HighlightStyle::default(),
575 },
576 unnecessary_code_fade: Default::default(),
577 show_underlines: true,
578 }
579 }
580}
581
582pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
583 let show_background = language_settings::language_settings(None, None, cx)
584 .inlay_hints
585 .show_background;
586
587 let mut style = cx.theme().syntax().get("hint");
588
589 if style.color.is_none() {
590 style.color = Some(cx.theme().status().hint);
591 }
592
593 if !show_background {
594 style.background_color = None;
595 return style;
596 }
597
598 if style.background_color.is_none() {
599 style.background_color = Some(cx.theme().status().hint_background);
600 }
601
602 style
603}
604
605pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
606 EditPredictionStyles {
607 insertion: HighlightStyle {
608 color: Some(cx.theme().status().predictive),
609 ..HighlightStyle::default()
610 },
611 whitespace: HighlightStyle {
612 background_color: Some(cx.theme().status().created_background),
613 ..HighlightStyle::default()
614 },
615 }
616}
617
618type CompletionId = usize;
619
620pub(crate) enum EditDisplayMode {
621 TabAccept,
622 DiffPopover,
623 Inline,
624}
625
626enum EditPrediction {
627 Edit {
628 edits: Vec<(Range<Anchor>, Arc<str>)>,
629 edit_preview: Option<EditPreview>,
630 display_mode: EditDisplayMode,
631 snapshot: BufferSnapshot,
632 },
633 /// Move to a specific location in the active editor
634 MoveWithin {
635 target: Anchor,
636 snapshot: BufferSnapshot,
637 },
638 /// Move to a specific location in a different editor (not the active one)
639 MoveOutside {
640 target: language::Anchor,
641 snapshot: BufferSnapshot,
642 },
643}
644
645struct EditPredictionState {
646 inlay_ids: Vec<InlayId>,
647 completion: EditPrediction,
648 completion_id: Option<SharedString>,
649 invalidation_range: Option<Range<Anchor>>,
650}
651
652enum EditPredictionSettings {
653 Disabled,
654 Enabled {
655 show_in_menu: bool,
656 preview_requires_modifier: bool,
657 },
658}
659
660enum EditPredictionHighlight {}
661
662#[derive(Debug, Clone)]
663struct InlineDiagnostic {
664 message: SharedString,
665 group_id: usize,
666 is_primary: bool,
667 start: Point,
668 severity: lsp::DiagnosticSeverity,
669}
670
671pub enum MenuEditPredictionsPolicy {
672 Never,
673 ByProvider,
674}
675
676pub enum EditPredictionPreview {
677 /// Modifier is not pressed
678 Inactive { released_too_fast: bool },
679 /// Modifier pressed
680 Active {
681 since: Instant,
682 previous_scroll_position: Option<ScrollAnchor>,
683 },
684}
685
686impl EditPredictionPreview {
687 pub fn released_too_fast(&self) -> bool {
688 match self {
689 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
690 EditPredictionPreview::Active { .. } => false,
691 }
692 }
693
694 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
695 if let EditPredictionPreview::Active {
696 previous_scroll_position,
697 ..
698 } = self
699 {
700 *previous_scroll_position = scroll_position;
701 }
702 }
703}
704
705pub struct ContextMenuOptions {
706 pub min_entries_visible: usize,
707 pub max_entries_visible: usize,
708 pub placement: Option<ContextMenuPlacement>,
709}
710
711#[derive(Debug, Clone, PartialEq, Eq)]
712pub enum ContextMenuPlacement {
713 Above,
714 Below,
715}
716
717#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
718struct EditorActionId(usize);
719
720impl EditorActionId {
721 pub fn post_inc(&mut self) -> Self {
722 let answer = self.0;
723
724 *self = Self(answer + 1);
725
726 Self(answer)
727 }
728}
729
730// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
731// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
732
733type BackgroundHighlight = (
734 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
735 Arc<[Range<Anchor>]>,
736);
737type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
738
739#[derive(Default)]
740struct ScrollbarMarkerState {
741 scrollbar_size: Size<Pixels>,
742 dirty: bool,
743 markers: Arc<[PaintQuad]>,
744 pending_refresh: Option<Task<Result<()>>>,
745}
746
747impl ScrollbarMarkerState {
748 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
749 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
750 }
751}
752
753#[derive(Clone, Copy, PartialEq, Eq)]
754pub enum MinimapVisibility {
755 Disabled,
756 Enabled {
757 /// The configuration currently present in the users settings.
758 setting_configuration: bool,
759 /// Whether to override the currently set visibility from the users setting.
760 toggle_override: bool,
761 },
762}
763
764impl MinimapVisibility {
765 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
766 if mode.is_full() {
767 Self::Enabled {
768 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
769 toggle_override: false,
770 }
771 } else {
772 Self::Disabled
773 }
774 }
775
776 fn hidden(&self) -> Self {
777 match *self {
778 Self::Enabled {
779 setting_configuration,
780 ..
781 } => Self::Enabled {
782 setting_configuration,
783 toggle_override: setting_configuration,
784 },
785 Self::Disabled => Self::Disabled,
786 }
787 }
788
789 fn disabled(&self) -> bool {
790 matches!(*self, Self::Disabled)
791 }
792
793 fn settings_visibility(&self) -> bool {
794 match *self {
795 Self::Enabled {
796 setting_configuration,
797 ..
798 } => setting_configuration,
799 _ => false,
800 }
801 }
802
803 fn visible(&self) -> bool {
804 match *self {
805 Self::Enabled {
806 setting_configuration,
807 toggle_override,
808 } => setting_configuration ^ toggle_override,
809 _ => false,
810 }
811 }
812
813 fn toggle_visibility(&self) -> Self {
814 match *self {
815 Self::Enabled {
816 toggle_override,
817 setting_configuration,
818 } => Self::Enabled {
819 setting_configuration,
820 toggle_override: !toggle_override,
821 },
822 Self::Disabled => Self::Disabled,
823 }
824 }
825}
826
827#[derive(Debug, Clone, Copy, PartialEq, Eq)]
828pub enum BufferSerialization {
829 All,
830 NonDirtyBuffers,
831}
832
833impl BufferSerialization {
834 fn new(restore_unsaved_buffers: bool) -> Self {
835 if restore_unsaved_buffers {
836 Self::All
837 } else {
838 Self::NonDirtyBuffers
839 }
840 }
841}
842
843#[derive(Clone, Debug)]
844struct RunnableTasks {
845 templates: Vec<(TaskSourceKind, TaskTemplate)>,
846 offset: multi_buffer::Anchor,
847 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
848 column: u32,
849 // Values of all named captures, including those starting with '_'
850 extra_variables: HashMap<String, String>,
851 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
852 context_range: Range<BufferOffset>,
853}
854
855impl RunnableTasks {
856 fn resolve<'a>(
857 &'a self,
858 cx: &'a task::TaskContext,
859 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
860 self.templates.iter().filter_map(|(kind, template)| {
861 template
862 .resolve_task(&kind.to_id_base(), cx)
863 .map(|task| (kind.clone(), task))
864 })
865 }
866}
867
868#[derive(Clone)]
869pub struct ResolvedTasks {
870 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
871 position: Anchor,
872}
873
874/// Addons allow storing per-editor state in other crates (e.g. Vim)
875pub trait Addon: 'static {
876 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
877
878 fn render_buffer_header_controls(
879 &self,
880 _: &ExcerptInfo,
881 _: &Window,
882 _: &App,
883 ) -> Option<AnyElement> {
884 None
885 }
886
887 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
888 None
889 }
890
891 fn to_any(&self) -> &dyn std::any::Any;
892
893 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
894 None
895 }
896}
897
898struct ChangeLocation {
899 current: Option<Vec<Anchor>>,
900 original: Vec<Anchor>,
901}
902impl ChangeLocation {
903 fn locations(&self) -> &[Anchor] {
904 self.current.as_ref().unwrap_or(&self.original)
905 }
906}
907
908/// A set of caret positions, registered when the editor was edited.
909pub struct ChangeList {
910 changes: Vec<ChangeLocation>,
911 /// Currently "selected" change.
912 position: Option<usize>,
913}
914
915impl ChangeList {
916 pub fn new() -> Self {
917 Self {
918 changes: Vec::new(),
919 position: None,
920 }
921 }
922
923 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
924 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
925 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
926 if self.changes.is_empty() {
927 return None;
928 }
929
930 let prev = self.position.unwrap_or(self.changes.len());
931 let next = if direction == Direction::Prev {
932 prev.saturating_sub(count)
933 } else {
934 (prev + count).min(self.changes.len() - 1)
935 };
936 self.position = Some(next);
937 self.changes.get(next).map(|change| change.locations())
938 }
939
940 /// Adds a new change to the list, resetting the change list position.
941 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
942 self.position.take();
943 if let Some(last) = self.changes.last_mut()
944 && group
945 {
946 last.current = Some(new_positions)
947 } else {
948 self.changes.push(ChangeLocation {
949 original: new_positions,
950 current: None,
951 });
952 }
953 }
954
955 pub fn last(&self) -> Option<&[Anchor]> {
956 self.changes.last().map(|change| change.locations())
957 }
958
959 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
960 self.changes.last().map(|change| change.original.as_slice())
961 }
962
963 pub fn invert_last_group(&mut self) {
964 if let Some(last) = self.changes.last_mut()
965 && let Some(current) = last.current.as_mut()
966 {
967 mem::swap(&mut last.original, current);
968 }
969 }
970}
971
972#[derive(Clone)]
973struct InlineBlamePopoverState {
974 scroll_handle: ScrollHandle,
975 commit_message: Option<ParsedCommitMessage>,
976 markdown: Entity<Markdown>,
977}
978
979struct InlineBlamePopover {
980 position: gpui::Point<Pixels>,
981 hide_task: Option<Task<()>>,
982 popover_bounds: Option<Bounds<Pixels>>,
983 popover_state: InlineBlamePopoverState,
984 keyboard_grace: bool,
985}
986
987enum SelectionDragState {
988 /// State when no drag related activity is detected.
989 None,
990 /// State when the mouse is down on a selection that is about to be dragged.
991 ReadyToDrag {
992 selection: Selection<Anchor>,
993 click_position: gpui::Point<Pixels>,
994 mouse_down_time: Instant,
995 },
996 /// State when the mouse is dragging the selection in the editor.
997 Dragging {
998 selection: Selection<Anchor>,
999 drop_cursor: Selection<Anchor>,
1000 hide_drop_cursor: bool,
1001 },
1002}
1003
1004enum ColumnarSelectionState {
1005 FromMouse {
1006 selection_tail: Anchor,
1007 display_point: Option<DisplayPoint>,
1008 },
1009 FromSelection {
1010 selection_tail: Anchor,
1011 },
1012}
1013
1014/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1015/// a breakpoint on them.
1016#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1017struct PhantomBreakpointIndicator {
1018 display_row: DisplayRow,
1019 /// There's a small debounce between hovering over the line and showing the indicator.
1020 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1021 is_active: bool,
1022 collides_with_existing_breakpoint: bool,
1023}
1024
1025/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1026///
1027/// See the [module level documentation](self) for more information.
1028pub struct Editor {
1029 focus_handle: FocusHandle,
1030 last_focused_descendant: Option<WeakFocusHandle>,
1031 /// The text buffer being edited
1032 buffer: Entity<MultiBuffer>,
1033 /// Map of how text in the buffer should be displayed.
1034 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1035 pub display_map: Entity<DisplayMap>,
1036 placeholder_display_map: Option<Entity<DisplayMap>>,
1037 pub selections: SelectionsCollection,
1038 pub scroll_manager: ScrollManager,
1039 /// When inline assist editors are linked, they all render cursors because
1040 /// typing enters text into each of them, even the ones that aren't focused.
1041 pub(crate) show_cursor_when_unfocused: bool,
1042 columnar_selection_state: Option<ColumnarSelectionState>,
1043 add_selections_state: Option<AddSelectionsState>,
1044 select_next_state: Option<SelectNextState>,
1045 select_prev_state: Option<SelectNextState>,
1046 selection_history: SelectionHistory,
1047 defer_selection_effects: bool,
1048 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1049 autoclose_regions: Vec<AutocloseRegion>,
1050 snippet_stack: InvalidationStack<SnippetState>,
1051 select_syntax_node_history: SelectSyntaxNodeHistory,
1052 ime_transaction: Option<TransactionId>,
1053 pub diagnostics_max_severity: DiagnosticSeverity,
1054 active_diagnostics: ActiveDiagnostic,
1055 show_inline_diagnostics: bool,
1056 inline_diagnostics_update: Task<()>,
1057 inline_diagnostics_enabled: bool,
1058 diagnostics_enabled: bool,
1059 word_completions_enabled: bool,
1060 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1061 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1062 hard_wrap: Option<usize>,
1063 project: Option<Entity<Project>>,
1064 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1065 completion_provider: Option<Rc<dyn CompletionProvider>>,
1066 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1067 blink_manager: Entity<BlinkManager>,
1068 show_cursor_names: bool,
1069 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1070 pub show_local_selections: bool,
1071 mode: EditorMode,
1072 show_breadcrumbs: bool,
1073 show_gutter: bool,
1074 show_scrollbars: ScrollbarAxes,
1075 minimap_visibility: MinimapVisibility,
1076 offset_content: bool,
1077 disable_expand_excerpt_buttons: bool,
1078 show_line_numbers: Option<bool>,
1079 use_relative_line_numbers: Option<bool>,
1080 show_git_diff_gutter: Option<bool>,
1081 show_code_actions: Option<bool>,
1082 show_runnables: Option<bool>,
1083 show_breakpoints: Option<bool>,
1084 show_wrap_guides: Option<bool>,
1085 show_indent_guides: Option<bool>,
1086 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1087 highlight_order: usize,
1088 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1089 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1090 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1091 scrollbar_marker_state: ScrollbarMarkerState,
1092 active_indent_guides_state: ActiveIndentGuidesState,
1093 nav_history: Option<ItemNavHistory>,
1094 context_menu: RefCell<Option<CodeContextMenu>>,
1095 context_menu_options: Option<ContextMenuOptions>,
1096 mouse_context_menu: Option<MouseContextMenu>,
1097 completion_tasks: Vec<(CompletionId, Task<()>)>,
1098 inline_blame_popover: Option<InlineBlamePopover>,
1099 inline_blame_popover_show_task: Option<Task<()>>,
1100 signature_help_state: SignatureHelpState,
1101 auto_signature_help: Option<bool>,
1102 find_all_references_task_sources: Vec<Anchor>,
1103 next_completion_id: CompletionId,
1104 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1105 code_actions_task: Option<Task<Result<()>>>,
1106 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1107 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1108 document_highlights_task: Option<Task<()>>,
1109 linked_editing_range_task: Option<Task<Option<()>>>,
1110 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1111 pending_rename: Option<RenameState>,
1112 searchable: bool,
1113 cursor_shape: CursorShape,
1114 /// Whether the cursor is offset one character to the left when something is
1115 /// selected (needed for vim visual mode)
1116 cursor_offset_on_selection: bool,
1117 current_line_highlight: Option<CurrentLineHighlight>,
1118 pub collapse_matches: bool,
1119 autoindent_mode: Option<AutoindentMode>,
1120 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1121 input_enabled: bool,
1122 use_modal_editing: bool,
1123 read_only: bool,
1124 leader_id: Option<CollaboratorId>,
1125 remote_id: Option<ViewId>,
1126 pub hover_state: HoverState,
1127 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1128 prev_pressure_stage: Option<PressureStage>,
1129 gutter_hovered: bool,
1130 hovered_link_state: Option<HoveredLinkState>,
1131 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1132 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1133 active_edit_prediction: Option<EditPredictionState>,
1134 /// Used to prevent flickering as the user types while the menu is open
1135 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1136 edit_prediction_settings: EditPredictionSettings,
1137 edit_predictions_hidden_for_vim_mode: bool,
1138 show_edit_predictions_override: Option<bool>,
1139 show_completions_on_input_override: Option<bool>,
1140 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1141 edit_prediction_preview: EditPredictionPreview,
1142 edit_prediction_indent_conflict: bool,
1143 edit_prediction_requires_modifier_in_indent_conflict: bool,
1144 next_inlay_id: usize,
1145 next_color_inlay_id: usize,
1146 _subscriptions: Vec<Subscription>,
1147 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1148 gutter_dimensions: GutterDimensions,
1149 style: Option<EditorStyle>,
1150 text_style_refinement: Option<TextStyleRefinement>,
1151 next_editor_action_id: EditorActionId,
1152 editor_actions: Rc<
1153 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1154 >,
1155 use_autoclose: bool,
1156 use_auto_surround: bool,
1157 auto_replace_emoji_shortcode: bool,
1158 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1159 show_git_blame_gutter: bool,
1160 show_git_blame_inline: bool,
1161 show_git_blame_inline_delay_task: Option<Task<()>>,
1162 git_blame_inline_enabled: bool,
1163 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1164 buffer_serialization: Option<BufferSerialization>,
1165 show_selection_menu: Option<bool>,
1166 blame: Option<Entity<GitBlame>>,
1167 blame_subscription: Option<Subscription>,
1168 custom_context_menu: Option<
1169 Box<
1170 dyn 'static
1171 + Fn(
1172 &mut Self,
1173 DisplayPoint,
1174 &mut Window,
1175 &mut Context<Self>,
1176 ) -> Option<Entity<ui::ContextMenu>>,
1177 >,
1178 >,
1179 last_bounds: Option<Bounds<Pixels>>,
1180 last_position_map: Option<Rc<PositionMap>>,
1181 expect_bounds_change: Option<Bounds<Pixels>>,
1182 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1183 tasks_update_task: Option<Task<()>>,
1184 breakpoint_store: Option<Entity<BreakpointStore>>,
1185 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1186 hovered_diff_hunk_row: Option<DisplayRow>,
1187 pull_diagnostics_task: Task<()>,
1188 pull_diagnostics_background_task: Task<()>,
1189 in_project_search: bool,
1190 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1191 breadcrumb_header: Option<String>,
1192 focused_block: Option<FocusedBlock>,
1193 next_scroll_position: NextScrollCursorCenterTopBottom,
1194 addons: HashMap<TypeId, Box<dyn Addon>>,
1195 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1196 load_diff_task: Option<Shared<Task<()>>>,
1197 /// Whether we are temporarily displaying a diff other than git's
1198 temporary_diff_override: bool,
1199 selection_mark_mode: bool,
1200 toggle_fold_multiple_buffers: Task<()>,
1201 _scroll_cursor_center_top_bottom_task: Task<()>,
1202 serialize_selections: Task<()>,
1203 serialize_folds: Task<()>,
1204 mouse_cursor_hidden: bool,
1205 minimap: Option<Entity<Self>>,
1206 hide_mouse_mode: HideMouseMode,
1207 pub change_list: ChangeList,
1208 inline_value_cache: InlineValueCache,
1209
1210 selection_drag_state: SelectionDragState,
1211 colors: Option<LspColorData>,
1212 post_scroll_update: Task<()>,
1213 refresh_colors_task: Task<()>,
1214 inlay_hints: Option<LspInlayHintData>,
1215 folding_newlines: Task<()>,
1216 select_next_is_case_sensitive: Option<bool>,
1217 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1218 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1219 accent_data: Option<AccentData>,
1220 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1221 use_base_text_line_numbers: bool,
1222}
1223
1224#[derive(Debug, PartialEq)]
1225struct AccentData {
1226 colors: AccentColors,
1227 overrides: Vec<SharedString>,
1228}
1229
1230fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1231 if debounce_ms > 0 {
1232 Some(Duration::from_millis(debounce_ms))
1233 } else {
1234 None
1235 }
1236}
1237
1238#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1239enum NextScrollCursorCenterTopBottom {
1240 #[default]
1241 Center,
1242 Top,
1243 Bottom,
1244}
1245
1246impl NextScrollCursorCenterTopBottom {
1247 fn next(&self) -> Self {
1248 match self {
1249 Self::Center => Self::Top,
1250 Self::Top => Self::Bottom,
1251 Self::Bottom => Self::Center,
1252 }
1253 }
1254}
1255
1256#[derive(Clone)]
1257pub struct EditorSnapshot {
1258 pub mode: EditorMode,
1259 show_gutter: bool,
1260 offset_content: bool,
1261 show_line_numbers: Option<bool>,
1262 show_git_diff_gutter: Option<bool>,
1263 show_code_actions: Option<bool>,
1264 show_runnables: Option<bool>,
1265 show_breakpoints: Option<bool>,
1266 git_blame_gutter_max_author_length: Option<usize>,
1267 pub display_snapshot: DisplaySnapshot,
1268 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1269 is_focused: bool,
1270 scroll_anchor: ScrollAnchor,
1271 ongoing_scroll: OngoingScroll,
1272 current_line_highlight: CurrentLineHighlight,
1273 gutter_hovered: bool,
1274}
1275
1276#[derive(Default, Debug, Clone, Copy)]
1277pub struct GutterDimensions {
1278 pub left_padding: Pixels,
1279 pub right_padding: Pixels,
1280 pub width: Pixels,
1281 pub margin: Pixels,
1282 pub git_blame_entries_width: Option<Pixels>,
1283}
1284
1285impl GutterDimensions {
1286 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1287 Self {
1288 margin: Self::default_gutter_margin(font_id, font_size, cx),
1289 ..Default::default()
1290 }
1291 }
1292
1293 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1294 -cx.text_system().descent(font_id, font_size)
1295 }
1296 /// The full width of the space taken up by the gutter.
1297 pub fn full_width(&self) -> Pixels {
1298 self.margin + self.width
1299 }
1300
1301 /// The width of the space reserved for the fold indicators,
1302 /// use alongside 'justify_end' and `gutter_width` to
1303 /// right align content with the line numbers
1304 pub fn fold_area_width(&self) -> Pixels {
1305 self.margin + self.right_padding
1306 }
1307}
1308
1309struct CharacterDimensions {
1310 em_width: Pixels,
1311 em_advance: Pixels,
1312 line_height: Pixels,
1313}
1314
1315#[derive(Debug)]
1316pub struct RemoteSelection {
1317 pub replica_id: ReplicaId,
1318 pub selection: Selection<Anchor>,
1319 pub cursor_shape: CursorShape,
1320 pub collaborator_id: CollaboratorId,
1321 pub line_mode: bool,
1322 pub user_name: Option<SharedString>,
1323 pub color: PlayerColor,
1324}
1325
1326#[derive(Clone, Debug)]
1327struct SelectionHistoryEntry {
1328 selections: Arc<[Selection<Anchor>]>,
1329 select_next_state: Option<SelectNextState>,
1330 select_prev_state: Option<SelectNextState>,
1331 add_selections_state: Option<AddSelectionsState>,
1332}
1333
1334#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1335enum SelectionHistoryMode {
1336 #[default]
1337 Normal,
1338 Undoing,
1339 Redoing,
1340 Skipping,
1341}
1342
1343#[derive(Clone, PartialEq, Eq, Hash)]
1344struct HoveredCursor {
1345 replica_id: ReplicaId,
1346 selection_id: usize,
1347}
1348
1349#[derive(Debug)]
1350/// SelectionEffects controls the side-effects of updating the selection.
1351///
1352/// The default behaviour does "what you mostly want":
1353/// - it pushes to the nav history if the cursor moved by >10 lines
1354/// - it re-triggers completion requests
1355/// - it scrolls to fit
1356///
1357/// You might want to modify these behaviours. For example when doing a "jump"
1358/// like go to definition, we always want to add to nav history; but when scrolling
1359/// in vim mode we never do.
1360///
1361/// Similarly, you might want to disable scrolling if you don't want the viewport to
1362/// move.
1363#[derive(Clone)]
1364pub struct SelectionEffects {
1365 nav_history: Option<bool>,
1366 completions: bool,
1367 scroll: Option<Autoscroll>,
1368}
1369
1370impl Default for SelectionEffects {
1371 fn default() -> Self {
1372 Self {
1373 nav_history: None,
1374 completions: true,
1375 scroll: Some(Autoscroll::fit()),
1376 }
1377 }
1378}
1379impl SelectionEffects {
1380 pub fn scroll(scroll: Autoscroll) -> Self {
1381 Self {
1382 scroll: Some(scroll),
1383 ..Default::default()
1384 }
1385 }
1386
1387 pub fn no_scroll() -> Self {
1388 Self {
1389 scroll: None,
1390 ..Default::default()
1391 }
1392 }
1393
1394 pub fn completions(self, completions: bool) -> Self {
1395 Self {
1396 completions,
1397 ..self
1398 }
1399 }
1400
1401 pub fn nav_history(self, nav_history: bool) -> Self {
1402 Self {
1403 nav_history: Some(nav_history),
1404 ..self
1405 }
1406 }
1407}
1408
1409struct DeferredSelectionEffectsState {
1410 changed: bool,
1411 effects: SelectionEffects,
1412 old_cursor_position: Anchor,
1413 history_entry: SelectionHistoryEntry,
1414}
1415
1416#[derive(Default)]
1417struct SelectionHistory {
1418 #[allow(clippy::type_complexity)]
1419 selections_by_transaction:
1420 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1421 mode: SelectionHistoryMode,
1422 undo_stack: VecDeque<SelectionHistoryEntry>,
1423 redo_stack: VecDeque<SelectionHistoryEntry>,
1424}
1425
1426impl SelectionHistory {
1427 #[track_caller]
1428 fn insert_transaction(
1429 &mut self,
1430 transaction_id: TransactionId,
1431 selections: Arc<[Selection<Anchor>]>,
1432 ) {
1433 if selections.is_empty() {
1434 log::error!(
1435 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1436 std::panic::Location::caller()
1437 );
1438 return;
1439 }
1440 self.selections_by_transaction
1441 .insert(transaction_id, (selections, None));
1442 }
1443
1444 #[allow(clippy::type_complexity)]
1445 fn transaction(
1446 &self,
1447 transaction_id: TransactionId,
1448 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1449 self.selections_by_transaction.get(&transaction_id)
1450 }
1451
1452 #[allow(clippy::type_complexity)]
1453 fn transaction_mut(
1454 &mut self,
1455 transaction_id: TransactionId,
1456 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1457 self.selections_by_transaction.get_mut(&transaction_id)
1458 }
1459
1460 fn push(&mut self, entry: SelectionHistoryEntry) {
1461 if !entry.selections.is_empty() {
1462 match self.mode {
1463 SelectionHistoryMode::Normal => {
1464 self.push_undo(entry);
1465 self.redo_stack.clear();
1466 }
1467 SelectionHistoryMode::Undoing => self.push_redo(entry),
1468 SelectionHistoryMode::Redoing => self.push_undo(entry),
1469 SelectionHistoryMode::Skipping => {}
1470 }
1471 }
1472 }
1473
1474 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1475 if self
1476 .undo_stack
1477 .back()
1478 .is_none_or(|e| e.selections != entry.selections)
1479 {
1480 self.undo_stack.push_back(entry);
1481 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1482 self.undo_stack.pop_front();
1483 }
1484 }
1485 }
1486
1487 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1488 if self
1489 .redo_stack
1490 .back()
1491 .is_none_or(|e| e.selections != entry.selections)
1492 {
1493 self.redo_stack.push_back(entry);
1494 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1495 self.redo_stack.pop_front();
1496 }
1497 }
1498 }
1499}
1500
1501#[derive(Clone, Copy)]
1502pub struct RowHighlightOptions {
1503 pub autoscroll: bool,
1504 pub include_gutter: bool,
1505}
1506
1507impl Default for RowHighlightOptions {
1508 fn default() -> Self {
1509 Self {
1510 autoscroll: Default::default(),
1511 include_gutter: true,
1512 }
1513 }
1514}
1515
1516struct RowHighlight {
1517 index: usize,
1518 range: Range<Anchor>,
1519 color: Hsla,
1520 options: RowHighlightOptions,
1521 type_id: TypeId,
1522}
1523
1524#[derive(Clone, Debug)]
1525struct AddSelectionsState {
1526 groups: Vec<AddSelectionsGroup>,
1527}
1528
1529#[derive(Clone, Debug)]
1530struct AddSelectionsGroup {
1531 above: bool,
1532 stack: Vec<usize>,
1533}
1534
1535#[derive(Clone)]
1536struct SelectNextState {
1537 query: AhoCorasick,
1538 wordwise: bool,
1539 done: bool,
1540}
1541
1542impl std::fmt::Debug for SelectNextState {
1543 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1544 f.debug_struct(std::any::type_name::<Self>())
1545 .field("wordwise", &self.wordwise)
1546 .field("done", &self.done)
1547 .finish()
1548 }
1549}
1550
1551#[derive(Debug)]
1552struct AutocloseRegion {
1553 selection_id: usize,
1554 range: Range<Anchor>,
1555 pair: BracketPair,
1556}
1557
1558#[derive(Debug)]
1559struct SnippetState {
1560 ranges: Vec<Vec<Range<Anchor>>>,
1561 active_index: usize,
1562 choices: Vec<Option<Vec<String>>>,
1563}
1564
1565#[doc(hidden)]
1566pub struct RenameState {
1567 pub range: Range<Anchor>,
1568 pub old_name: Arc<str>,
1569 pub editor: Entity<Editor>,
1570 block_id: CustomBlockId,
1571}
1572
1573struct InvalidationStack<T>(Vec<T>);
1574
1575struct RegisteredEditPredictionDelegate {
1576 provider: Arc<dyn EditPredictionDelegateHandle>,
1577 _subscription: Subscription,
1578}
1579
1580#[derive(Debug, PartialEq, Eq)]
1581pub struct ActiveDiagnosticGroup {
1582 pub active_range: Range<Anchor>,
1583 pub active_message: String,
1584 pub group_id: usize,
1585 pub blocks: HashSet<CustomBlockId>,
1586}
1587
1588#[derive(Debug, PartialEq, Eq)]
1589
1590pub(crate) enum ActiveDiagnostic {
1591 None,
1592 All,
1593 Group(ActiveDiagnosticGroup),
1594}
1595
1596#[derive(Serialize, Deserialize, Clone, Debug)]
1597pub struct ClipboardSelection {
1598 /// The number of bytes in this selection.
1599 pub len: usize,
1600 /// Whether this was a full-line selection.
1601 pub is_entire_line: bool,
1602 /// The indentation of the first line when this content was originally copied.
1603 pub first_line_indent: u32,
1604 #[serde(default)]
1605 pub file_path: Option<PathBuf>,
1606 #[serde(default)]
1607 pub line_range: Option<RangeInclusive<u32>>,
1608}
1609
1610impl ClipboardSelection {
1611 pub fn for_buffer(
1612 len: usize,
1613 is_entire_line: bool,
1614 range: Range<Point>,
1615 buffer: &MultiBufferSnapshot,
1616 project: Option<&Entity<Project>>,
1617 cx: &App,
1618 ) -> Self {
1619 let first_line_indent = buffer
1620 .indent_size_for_line(MultiBufferRow(range.start.row))
1621 .len;
1622
1623 let file_path = util::maybe!({
1624 let project = project?.read(cx);
1625 let file = buffer.file_at(range.start)?;
1626 let project_path = ProjectPath {
1627 worktree_id: file.worktree_id(cx),
1628 path: file.path().clone(),
1629 };
1630 project.absolute_path(&project_path, cx)
1631 });
1632
1633 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1634
1635 Self {
1636 len,
1637 is_entire_line,
1638 first_line_indent,
1639 file_path,
1640 line_range,
1641 }
1642 }
1643}
1644
1645// selections, scroll behavior, was newest selection reversed
1646type SelectSyntaxNodeHistoryState = (
1647 Box<[Selection<MultiBufferOffset>]>,
1648 SelectSyntaxNodeScrollBehavior,
1649 bool,
1650);
1651
1652#[derive(Default)]
1653struct SelectSyntaxNodeHistory {
1654 stack: Vec<SelectSyntaxNodeHistoryState>,
1655 // disable temporarily to allow changing selections without losing the stack
1656 pub disable_clearing: bool,
1657}
1658
1659impl SelectSyntaxNodeHistory {
1660 pub fn try_clear(&mut self) {
1661 if !self.disable_clearing {
1662 self.stack.clear();
1663 }
1664 }
1665
1666 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1667 self.stack.push(selection);
1668 }
1669
1670 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1671 self.stack.pop()
1672 }
1673}
1674
1675enum SelectSyntaxNodeScrollBehavior {
1676 CursorTop,
1677 FitSelection,
1678 CursorBottom,
1679}
1680
1681#[derive(Debug)]
1682pub(crate) struct NavigationData {
1683 cursor_anchor: Anchor,
1684 cursor_position: Point,
1685 scroll_anchor: ScrollAnchor,
1686 scroll_top_row: u32,
1687}
1688
1689#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1690pub enum GotoDefinitionKind {
1691 Symbol,
1692 Declaration,
1693 Type,
1694 Implementation,
1695}
1696
1697pub enum FormatTarget {
1698 Buffers(HashSet<Entity<Buffer>>),
1699 Ranges(Vec<Range<MultiBufferPoint>>),
1700}
1701
1702pub(crate) struct FocusedBlock {
1703 id: BlockId,
1704 focus_handle: WeakFocusHandle,
1705}
1706
1707#[derive(Clone, Debug)]
1708enum JumpData {
1709 MultiBufferRow {
1710 row: MultiBufferRow,
1711 line_offset_from_top: u32,
1712 },
1713 MultiBufferPoint {
1714 excerpt_id: ExcerptId,
1715 position: Point,
1716 anchor: text::Anchor,
1717 line_offset_from_top: u32,
1718 },
1719}
1720
1721pub enum MultibufferSelectionMode {
1722 First,
1723 All,
1724}
1725
1726#[derive(Clone, Copy, Debug, Default)]
1727pub struct RewrapOptions {
1728 pub override_language_settings: bool,
1729 pub preserve_existing_whitespace: bool,
1730}
1731
1732impl Editor {
1733 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1734 let buffer = cx.new(|cx| Buffer::local("", cx));
1735 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1736 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1737 }
1738
1739 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1740 let buffer = cx.new(|cx| Buffer::local("", cx));
1741 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1742 Self::new(EditorMode::full(), buffer, None, window, cx)
1743 }
1744
1745 pub fn auto_height(
1746 min_lines: usize,
1747 max_lines: usize,
1748 window: &mut Window,
1749 cx: &mut Context<Self>,
1750 ) -> Self {
1751 let buffer = cx.new(|cx| Buffer::local("", cx));
1752 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1753 Self::new(
1754 EditorMode::AutoHeight {
1755 min_lines,
1756 max_lines: Some(max_lines),
1757 },
1758 buffer,
1759 None,
1760 window,
1761 cx,
1762 )
1763 }
1764
1765 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1766 /// The editor grows as tall as needed to fit its content.
1767 pub fn auto_height_unbounded(
1768 min_lines: usize,
1769 window: &mut Window,
1770 cx: &mut Context<Self>,
1771 ) -> Self {
1772 let buffer = cx.new(|cx| Buffer::local("", cx));
1773 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1774 Self::new(
1775 EditorMode::AutoHeight {
1776 min_lines,
1777 max_lines: None,
1778 },
1779 buffer,
1780 None,
1781 window,
1782 cx,
1783 )
1784 }
1785
1786 pub fn for_buffer(
1787 buffer: Entity<Buffer>,
1788 project: Option<Entity<Project>>,
1789 window: &mut Window,
1790 cx: &mut Context<Self>,
1791 ) -> Self {
1792 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1793 Self::new(EditorMode::full(), buffer, project, window, cx)
1794 }
1795
1796 pub fn for_multibuffer(
1797 buffer: Entity<MultiBuffer>,
1798 project: Option<Entity<Project>>,
1799 window: &mut Window,
1800 cx: &mut Context<Self>,
1801 ) -> Self {
1802 Self::new(EditorMode::full(), buffer, project, window, cx)
1803 }
1804
1805 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1806 let mut clone = Self::new(
1807 self.mode.clone(),
1808 self.buffer.clone(),
1809 self.project.clone(),
1810 window,
1811 cx,
1812 );
1813 self.display_map.update(cx, |display_map, cx| {
1814 let snapshot = display_map.snapshot(cx);
1815 clone.display_map.update(cx, |display_map, cx| {
1816 display_map.set_state(&snapshot, cx);
1817 });
1818 });
1819 clone.folds_did_change(cx);
1820 clone.selections.clone_state(&self.selections);
1821 clone.scroll_manager.clone_state(&self.scroll_manager);
1822 clone.searchable = self.searchable;
1823 clone.read_only = self.read_only;
1824 clone
1825 }
1826
1827 pub fn new(
1828 mode: EditorMode,
1829 buffer: Entity<MultiBuffer>,
1830 project: Option<Entity<Project>>,
1831 window: &mut Window,
1832 cx: &mut Context<Self>,
1833 ) -> Self {
1834 Editor::new_internal(mode, buffer, project, None, window, cx)
1835 }
1836
1837 pub fn sticky_headers(
1838 &self,
1839 style: &EditorStyle,
1840 cx: &App,
1841 ) -> Option<Vec<OutlineItem<Anchor>>> {
1842 let multi_buffer = self.buffer().read(cx);
1843 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1844 let multi_buffer_visible_start = self
1845 .scroll_manager
1846 .anchor()
1847 .anchor
1848 .to_point(&multi_buffer_snapshot);
1849 let max_row = multi_buffer_snapshot.max_point().row;
1850
1851 let start_row = (multi_buffer_visible_start.row).min(max_row);
1852 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1853
1854 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1855 let outline_items = buffer
1856 .outline_items_containing(
1857 Point::new(start_row, 0)..Point::new(end_row, 0),
1858 true,
1859 Some(style.syntax.as_ref()),
1860 )
1861 .into_iter()
1862 .map(|outline_item| OutlineItem {
1863 depth: outline_item.depth,
1864 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1865 source_range_for_text: Anchor::range_in_buffer(
1866 *excerpt_id,
1867 outline_item.source_range_for_text,
1868 ),
1869 text: outline_item.text,
1870 highlight_ranges: outline_item.highlight_ranges,
1871 name_ranges: outline_item.name_ranges,
1872 body_range: outline_item
1873 .body_range
1874 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1875 annotation_range: outline_item
1876 .annotation_range
1877 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1878 });
1879 return Some(outline_items.collect());
1880 }
1881
1882 None
1883 }
1884
1885 fn new_internal(
1886 mode: EditorMode,
1887 multi_buffer: Entity<MultiBuffer>,
1888 project: Option<Entity<Project>>,
1889 display_map: Option<Entity<DisplayMap>>,
1890 window: &mut Window,
1891 cx: &mut Context<Self>,
1892 ) -> Self {
1893 debug_assert!(
1894 display_map.is_none() || mode.is_minimap(),
1895 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1896 );
1897
1898 let full_mode = mode.is_full();
1899 let is_minimap = mode.is_minimap();
1900 let diagnostics_max_severity = if full_mode {
1901 EditorSettings::get_global(cx)
1902 .diagnostics_max_severity
1903 .unwrap_or(DiagnosticSeverity::Hint)
1904 } else {
1905 DiagnosticSeverity::Off
1906 };
1907 let style = window.text_style();
1908 let font_size = style.font_size.to_pixels(window.rem_size());
1909 let editor = cx.entity().downgrade();
1910 let fold_placeholder = FoldPlaceholder {
1911 constrain_width: false,
1912 render: Arc::new(move |fold_id, fold_range, cx| {
1913 let editor = editor.clone();
1914 div()
1915 .id(fold_id)
1916 .bg(cx.theme().colors().ghost_element_background)
1917 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1918 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1919 .rounded_xs()
1920 .size_full()
1921 .cursor_pointer()
1922 .child("⋯")
1923 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1924 .on_click(move |_, _window, cx| {
1925 editor
1926 .update(cx, |editor, cx| {
1927 editor.unfold_ranges(
1928 &[fold_range.start..fold_range.end],
1929 true,
1930 false,
1931 cx,
1932 );
1933 cx.stop_propagation();
1934 })
1935 .ok();
1936 })
1937 .into_any()
1938 }),
1939 merge_adjacent: true,
1940 ..FoldPlaceholder::default()
1941 };
1942 let display_map = display_map.unwrap_or_else(|| {
1943 cx.new(|cx| {
1944 DisplayMap::new(
1945 multi_buffer.clone(),
1946 style.font(),
1947 font_size,
1948 None,
1949 FILE_HEADER_HEIGHT,
1950 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1951 fold_placeholder,
1952 diagnostics_max_severity,
1953 cx,
1954 )
1955 })
1956 });
1957
1958 let selections = SelectionsCollection::new();
1959
1960 let blink_manager = cx.new(|cx| {
1961 let mut blink_manager = BlinkManager::new(
1962 CURSOR_BLINK_INTERVAL,
1963 |cx| EditorSettings::get_global(cx).cursor_blink,
1964 cx,
1965 );
1966 if is_minimap {
1967 blink_manager.disable(cx);
1968 }
1969 blink_manager
1970 });
1971
1972 let soft_wrap_mode_override =
1973 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1974
1975 let mut project_subscriptions = Vec::new();
1976 if full_mode && let Some(project) = project.as_ref() {
1977 project_subscriptions.push(cx.subscribe_in(
1978 project,
1979 window,
1980 |editor, _, event, window, cx| match event {
1981 project::Event::RefreshCodeLens => {
1982 // we always query lens with actions, without storing them, always refreshing them
1983 }
1984 project::Event::RefreshInlayHints {
1985 server_id,
1986 request_id,
1987 } => {
1988 editor.refresh_inlay_hints(
1989 InlayHintRefreshReason::RefreshRequested {
1990 server_id: *server_id,
1991 request_id: *request_id,
1992 },
1993 cx,
1994 );
1995 }
1996 project::Event::LanguageServerRemoved(..) => {
1997 if editor.tasks_update_task.is_none() {
1998 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1999 }
2000 editor.registered_buffers.clear();
2001 editor.register_visible_buffers(cx);
2002 }
2003 project::Event::LanguageServerAdded(..) => {
2004 if editor.tasks_update_task.is_none() {
2005 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2006 }
2007 }
2008 project::Event::SnippetEdit(id, snippet_edits) => {
2009 // todo(lw): Non singletons
2010 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2011 let snapshot = buffer.read(cx).snapshot();
2012 let focus_handle = editor.focus_handle(cx);
2013 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2014 for (range, snippet) in snippet_edits {
2015 let buffer_range =
2016 language::range_from_lsp(*range).to_offset(&snapshot);
2017 editor
2018 .insert_snippet(
2019 &[MultiBufferOffset(buffer_range.start)
2020 ..MultiBufferOffset(buffer_range.end)],
2021 snippet.clone(),
2022 window,
2023 cx,
2024 )
2025 .ok();
2026 }
2027 }
2028 }
2029 }
2030 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2031 let buffer_id = *buffer_id;
2032 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2033 editor.register_buffer(buffer_id, cx);
2034 editor.update_lsp_data(Some(buffer_id), window, cx);
2035 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2036 refresh_linked_ranges(editor, window, cx);
2037 editor.refresh_code_actions(window, cx);
2038 editor.refresh_document_highlights(cx);
2039 }
2040 }
2041
2042 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2043 let Some(workspace) = editor.workspace() else {
2044 return;
2045 };
2046 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2047 else {
2048 return;
2049 };
2050
2051 if active_editor.entity_id() == cx.entity_id() {
2052 let entity_id = cx.entity_id();
2053 workspace.update(cx, |this, cx| {
2054 this.panes_mut()
2055 .iter_mut()
2056 .filter(|pane| pane.entity_id() != entity_id)
2057 .for_each(|p| {
2058 p.update(cx, |pane, _| {
2059 pane.nav_history_mut().rename_item(
2060 entity_id,
2061 project_path.clone(),
2062 abs_path.clone().into(),
2063 );
2064 })
2065 });
2066 });
2067
2068 Self::open_transaction_for_hidden_buffers(
2069 workspace,
2070 transaction.clone(),
2071 "Rename".to_string(),
2072 window,
2073 cx,
2074 );
2075 }
2076 }
2077
2078 project::Event::WorkspaceEditApplied(transaction) => {
2079 let Some(workspace) = editor.workspace() else {
2080 return;
2081 };
2082 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2083 else {
2084 return;
2085 };
2086
2087 if active_editor.entity_id() == cx.entity_id() {
2088 Self::open_transaction_for_hidden_buffers(
2089 workspace,
2090 transaction.clone(),
2091 "LSP Edit".to_string(),
2092 window,
2093 cx,
2094 );
2095 }
2096 }
2097
2098 _ => {}
2099 },
2100 ));
2101 if let Some(task_inventory) = project
2102 .read(cx)
2103 .task_store()
2104 .read(cx)
2105 .task_inventory()
2106 .cloned()
2107 {
2108 project_subscriptions.push(cx.observe_in(
2109 &task_inventory,
2110 window,
2111 |editor, _, window, cx| {
2112 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2113 },
2114 ));
2115 };
2116
2117 project_subscriptions.push(cx.subscribe_in(
2118 &project.read(cx).breakpoint_store(),
2119 window,
2120 |editor, _, event, window, cx| match event {
2121 BreakpointStoreEvent::ClearDebugLines => {
2122 editor.clear_row_highlights::<ActiveDebugLine>();
2123 editor.refresh_inline_values(cx);
2124 }
2125 BreakpointStoreEvent::SetDebugLine => {
2126 if editor.go_to_active_debug_line(window, cx) {
2127 cx.stop_propagation();
2128 }
2129
2130 editor.refresh_inline_values(cx);
2131 }
2132 _ => {}
2133 },
2134 ));
2135 let git_store = project.read(cx).git_store().clone();
2136 let project = project.clone();
2137 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2138 if let GitStoreEvent::RepositoryAdded = event {
2139 this.load_diff_task = Some(
2140 update_uncommitted_diff_for_buffer(
2141 cx.entity(),
2142 &project,
2143 this.buffer.read(cx).all_buffers(),
2144 this.buffer.clone(),
2145 cx,
2146 )
2147 .shared(),
2148 );
2149 }
2150 }));
2151 }
2152
2153 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2154
2155 let inlay_hint_settings =
2156 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2157 let focus_handle = cx.focus_handle();
2158 if !is_minimap {
2159 cx.on_focus(&focus_handle, window, Self::handle_focus)
2160 .detach();
2161 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2162 .detach();
2163 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2164 .detach();
2165 cx.on_blur(&focus_handle, window, Self::handle_blur)
2166 .detach();
2167 cx.observe_pending_input(window, Self::observe_pending_input)
2168 .detach();
2169 }
2170
2171 let show_indent_guides =
2172 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2173 Some(false)
2174 } else {
2175 None
2176 };
2177
2178 let breakpoint_store = match (&mode, project.as_ref()) {
2179 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2180 _ => None,
2181 };
2182
2183 let mut code_action_providers = Vec::new();
2184 let mut load_uncommitted_diff = None;
2185 if let Some(project) = project.clone() {
2186 load_uncommitted_diff = Some(
2187 update_uncommitted_diff_for_buffer(
2188 cx.entity(),
2189 &project,
2190 multi_buffer.read(cx).all_buffers(),
2191 multi_buffer.clone(),
2192 cx,
2193 )
2194 .shared(),
2195 );
2196 code_action_providers.push(Rc::new(project) as Rc<_>);
2197 }
2198
2199 let mut editor = Self {
2200 focus_handle,
2201 show_cursor_when_unfocused: false,
2202 last_focused_descendant: None,
2203 buffer: multi_buffer.clone(),
2204 display_map: display_map.clone(),
2205 placeholder_display_map: None,
2206 selections,
2207 scroll_manager: ScrollManager::new(cx),
2208 columnar_selection_state: None,
2209 add_selections_state: None,
2210 select_next_state: None,
2211 select_prev_state: None,
2212 selection_history: SelectionHistory::default(),
2213 defer_selection_effects: false,
2214 deferred_selection_effects_state: None,
2215 autoclose_regions: Vec::new(),
2216 snippet_stack: InvalidationStack::default(),
2217 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2218 ime_transaction: None,
2219 active_diagnostics: ActiveDiagnostic::None,
2220 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2221 inline_diagnostics_update: Task::ready(()),
2222 inline_diagnostics: Vec::new(),
2223 soft_wrap_mode_override,
2224 diagnostics_max_severity,
2225 hard_wrap: None,
2226 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2227 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2228 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2229 project,
2230 blink_manager: blink_manager.clone(),
2231 show_local_selections: true,
2232 show_scrollbars: ScrollbarAxes {
2233 horizontal: full_mode,
2234 vertical: full_mode,
2235 },
2236 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2237 offset_content: !matches!(mode, EditorMode::SingleLine),
2238 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2239 show_gutter: full_mode,
2240 show_line_numbers: (!full_mode).then_some(false),
2241 use_relative_line_numbers: None,
2242 disable_expand_excerpt_buttons: !full_mode,
2243 show_git_diff_gutter: None,
2244 show_code_actions: None,
2245 show_runnables: None,
2246 show_breakpoints: None,
2247 show_wrap_guides: None,
2248 show_indent_guides,
2249 buffers_with_disabled_indent_guides: HashSet::default(),
2250 highlight_order: 0,
2251 highlighted_rows: HashMap::default(),
2252 background_highlights: HashMap::default(),
2253 gutter_highlights: HashMap::default(),
2254 scrollbar_marker_state: ScrollbarMarkerState::default(),
2255 active_indent_guides_state: ActiveIndentGuidesState::default(),
2256 nav_history: None,
2257 context_menu: RefCell::new(None),
2258 context_menu_options: None,
2259 mouse_context_menu: None,
2260 completion_tasks: Vec::new(),
2261 inline_blame_popover: None,
2262 inline_blame_popover_show_task: None,
2263 signature_help_state: SignatureHelpState::default(),
2264 auto_signature_help: None,
2265 find_all_references_task_sources: Vec::new(),
2266 next_completion_id: 0,
2267 next_inlay_id: 0,
2268 code_action_providers,
2269 available_code_actions: None,
2270 code_actions_task: None,
2271 quick_selection_highlight_task: None,
2272 debounced_selection_highlight_task: None,
2273 document_highlights_task: None,
2274 linked_editing_range_task: None,
2275 pending_rename: None,
2276 searchable: !is_minimap,
2277 cursor_shape: EditorSettings::get_global(cx)
2278 .cursor_shape
2279 .unwrap_or_default(),
2280 cursor_offset_on_selection: false,
2281 current_line_highlight: None,
2282 autoindent_mode: Some(AutoindentMode::EachLine),
2283 collapse_matches: false,
2284 workspace: None,
2285 input_enabled: !is_minimap,
2286 use_modal_editing: full_mode,
2287 read_only: is_minimap,
2288 use_autoclose: true,
2289 use_auto_surround: true,
2290 auto_replace_emoji_shortcode: false,
2291 jsx_tag_auto_close_enabled_in_any_buffer: false,
2292 leader_id: None,
2293 remote_id: None,
2294 hover_state: HoverState::default(),
2295 pending_mouse_down: None,
2296 prev_pressure_stage: None,
2297 hovered_link_state: None,
2298 edit_prediction_provider: None,
2299 active_edit_prediction: None,
2300 stale_edit_prediction_in_menu: None,
2301 edit_prediction_preview: EditPredictionPreview::Inactive {
2302 released_too_fast: false,
2303 },
2304 inline_diagnostics_enabled: full_mode,
2305 diagnostics_enabled: full_mode,
2306 word_completions_enabled: full_mode,
2307 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2308 gutter_hovered: false,
2309 pixel_position_of_newest_cursor: None,
2310 last_bounds: None,
2311 last_position_map: None,
2312 expect_bounds_change: None,
2313 gutter_dimensions: GutterDimensions::default(),
2314 style: None,
2315 show_cursor_names: false,
2316 hovered_cursors: HashMap::default(),
2317 next_editor_action_id: EditorActionId::default(),
2318 editor_actions: Rc::default(),
2319 edit_predictions_hidden_for_vim_mode: false,
2320 show_edit_predictions_override: None,
2321 show_completions_on_input_override: None,
2322 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2323 edit_prediction_settings: EditPredictionSettings::Disabled,
2324 edit_prediction_indent_conflict: false,
2325 edit_prediction_requires_modifier_in_indent_conflict: true,
2326 custom_context_menu: None,
2327 show_git_blame_gutter: false,
2328 show_git_blame_inline: false,
2329 show_selection_menu: None,
2330 show_git_blame_inline_delay_task: None,
2331 git_blame_inline_enabled: full_mode
2332 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2333 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2334 buffer_serialization: is_minimap.not().then(|| {
2335 BufferSerialization::new(
2336 ProjectSettings::get_global(cx)
2337 .session
2338 .restore_unsaved_buffers,
2339 )
2340 }),
2341 blame: None,
2342 blame_subscription: None,
2343 tasks: BTreeMap::default(),
2344
2345 breakpoint_store,
2346 gutter_breakpoint_indicator: (None, None),
2347 hovered_diff_hunk_row: None,
2348 _subscriptions: (!is_minimap)
2349 .then(|| {
2350 vec![
2351 cx.observe(&multi_buffer, Self::on_buffer_changed),
2352 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2353 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2354 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2355 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2356 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2357 cx.observe_window_activation(window, |editor, window, cx| {
2358 let active = window.is_window_active();
2359 editor.blink_manager.update(cx, |blink_manager, cx| {
2360 if active {
2361 blink_manager.enable(cx);
2362 } else {
2363 blink_manager.disable(cx);
2364 }
2365 });
2366 if active {
2367 editor.show_mouse_cursor(cx);
2368 }
2369 }),
2370 ]
2371 })
2372 .unwrap_or_default(),
2373 tasks_update_task: None,
2374 pull_diagnostics_task: Task::ready(()),
2375 pull_diagnostics_background_task: Task::ready(()),
2376 colors: None,
2377 refresh_colors_task: Task::ready(()),
2378 inlay_hints: None,
2379 next_color_inlay_id: 0,
2380 post_scroll_update: Task::ready(()),
2381 linked_edit_ranges: Default::default(),
2382 in_project_search: false,
2383 previous_search_ranges: None,
2384 breadcrumb_header: None,
2385 focused_block: None,
2386 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2387 addons: HashMap::default(),
2388 registered_buffers: HashMap::default(),
2389 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2390 selection_mark_mode: false,
2391 toggle_fold_multiple_buffers: Task::ready(()),
2392 serialize_selections: Task::ready(()),
2393 serialize_folds: Task::ready(()),
2394 text_style_refinement: None,
2395 load_diff_task: load_uncommitted_diff,
2396 temporary_diff_override: false,
2397 mouse_cursor_hidden: false,
2398 minimap: None,
2399 hide_mouse_mode: EditorSettings::get_global(cx)
2400 .hide_mouse
2401 .unwrap_or_default(),
2402 change_list: ChangeList::new(),
2403 mode,
2404 selection_drag_state: SelectionDragState::None,
2405 folding_newlines: Task::ready(()),
2406 lookup_key: None,
2407 select_next_is_case_sensitive: None,
2408 applicable_language_settings: HashMap::default(),
2409 accent_data: None,
2410 fetched_tree_sitter_chunks: HashMap::default(),
2411 use_base_text_line_numbers: false,
2412 };
2413
2414 if is_minimap {
2415 return editor;
2416 }
2417
2418 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2419 editor.accent_data = editor.fetch_accent_data(cx);
2420
2421 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2422 editor
2423 ._subscriptions
2424 .push(cx.observe(breakpoints, |_, _, cx| {
2425 cx.notify();
2426 }));
2427 }
2428 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2429 editor._subscriptions.extend(project_subscriptions);
2430
2431 editor._subscriptions.push(cx.subscribe_in(
2432 &cx.entity(),
2433 window,
2434 |editor, _, e: &EditorEvent, window, cx| match e {
2435 EditorEvent::ScrollPositionChanged { local, .. } => {
2436 if *local {
2437 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2438 editor.inline_blame_popover.take();
2439 let new_anchor = editor.scroll_manager.anchor();
2440 let snapshot = editor.snapshot(window, cx);
2441 editor.update_restoration_data(cx, move |data| {
2442 data.scroll_position = (
2443 new_anchor.top_row(snapshot.buffer_snapshot()),
2444 new_anchor.offset,
2445 );
2446 });
2447
2448 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2449 cx.background_executor()
2450 .timer(Duration::from_millis(50))
2451 .await;
2452 editor
2453 .update_in(cx, |editor, window, cx| {
2454 editor.register_visible_buffers(cx);
2455 editor.refresh_colors_for_visible_range(None, window, cx);
2456 editor.refresh_inlay_hints(
2457 InlayHintRefreshReason::NewLinesShown,
2458 cx,
2459 );
2460 editor.colorize_brackets(false, cx);
2461 })
2462 .ok();
2463 });
2464 }
2465 }
2466 EditorEvent::Edited { .. } => {
2467 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2468 .map(|vim_mode| vim_mode.0)
2469 .unwrap_or(false);
2470 if !vim_mode {
2471 let display_map = editor.display_snapshot(cx);
2472 let selections = editor.selections.all_adjusted_display(&display_map);
2473 let pop_state = editor
2474 .change_list
2475 .last()
2476 .map(|previous| {
2477 previous.len() == selections.len()
2478 && previous.iter().enumerate().all(|(ix, p)| {
2479 p.to_display_point(&display_map).row()
2480 == selections[ix].head().row()
2481 })
2482 })
2483 .unwrap_or(false);
2484 let new_positions = selections
2485 .into_iter()
2486 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2487 .collect();
2488 editor
2489 .change_list
2490 .push_to_change_list(pop_state, new_positions);
2491 }
2492 }
2493 _ => (),
2494 },
2495 ));
2496
2497 if let Some(dap_store) = editor
2498 .project
2499 .as_ref()
2500 .map(|project| project.read(cx).dap_store())
2501 {
2502 let weak_editor = cx.weak_entity();
2503
2504 editor
2505 ._subscriptions
2506 .push(
2507 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2508 let session_entity = cx.entity();
2509 weak_editor
2510 .update(cx, |editor, cx| {
2511 editor._subscriptions.push(
2512 cx.subscribe(&session_entity, Self::on_debug_session_event),
2513 );
2514 })
2515 .ok();
2516 }),
2517 );
2518
2519 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2520 editor
2521 ._subscriptions
2522 .push(cx.subscribe(&session, Self::on_debug_session_event));
2523 }
2524 }
2525
2526 // skip adding the initial selection to selection history
2527 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2528 editor.end_selection(window, cx);
2529 editor.selection_history.mode = SelectionHistoryMode::Normal;
2530
2531 editor.scroll_manager.show_scrollbars(window, cx);
2532 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2533
2534 if full_mode {
2535 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2536 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2537
2538 if editor.git_blame_inline_enabled {
2539 editor.start_git_blame_inline(false, window, cx);
2540 }
2541
2542 editor.go_to_active_debug_line(window, cx);
2543
2544 editor.minimap =
2545 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2546 editor.colors = Some(LspColorData::new(cx));
2547 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2548
2549 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2550 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2551 }
2552 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2553 }
2554
2555 editor
2556 }
2557
2558 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2559 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2560 }
2561
2562 pub fn deploy_mouse_context_menu(
2563 &mut self,
2564 position: gpui::Point<Pixels>,
2565 context_menu: Entity<ContextMenu>,
2566 window: &mut Window,
2567 cx: &mut Context<Self>,
2568 ) {
2569 self.mouse_context_menu = Some(MouseContextMenu::new(
2570 self,
2571 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2572 context_menu,
2573 window,
2574 cx,
2575 ));
2576 }
2577
2578 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2579 self.mouse_context_menu
2580 .as_ref()
2581 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2582 }
2583
2584 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2585 if self
2586 .selections
2587 .pending_anchor()
2588 .is_some_and(|pending_selection| {
2589 let snapshot = self.buffer().read(cx).snapshot(cx);
2590 pending_selection.range().includes(range, &snapshot)
2591 })
2592 {
2593 return true;
2594 }
2595
2596 self.selections
2597 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2598 .into_iter()
2599 .any(|selection| {
2600 // This is needed to cover a corner case, if we just check for an existing
2601 // selection in the fold range, having a cursor at the start of the fold
2602 // marks it as selected. Non-empty selections don't cause this.
2603 let length = selection.end - selection.start;
2604 length > 0
2605 })
2606 }
2607
2608 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2609 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2610 }
2611
2612 fn key_context_internal(
2613 &self,
2614 has_active_edit_prediction: bool,
2615 window: &mut Window,
2616 cx: &mut App,
2617 ) -> KeyContext {
2618 let mut key_context = KeyContext::new_with_defaults();
2619 key_context.add("Editor");
2620 let mode = match self.mode {
2621 EditorMode::SingleLine => "single_line",
2622 EditorMode::AutoHeight { .. } => "auto_height",
2623 EditorMode::Minimap { .. } => "minimap",
2624 EditorMode::Full { .. } => "full",
2625 };
2626
2627 if EditorSettings::jupyter_enabled(cx) {
2628 key_context.add("jupyter");
2629 }
2630
2631 key_context.set("mode", mode);
2632 if self.pending_rename.is_some() {
2633 key_context.add("renaming");
2634 }
2635
2636 if let Some(snippet_stack) = self.snippet_stack.last() {
2637 key_context.add("in_snippet");
2638
2639 if snippet_stack.active_index > 0 {
2640 key_context.add("has_previous_tabstop");
2641 }
2642
2643 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2644 key_context.add("has_next_tabstop");
2645 }
2646 }
2647
2648 match self.context_menu.borrow().as_ref() {
2649 Some(CodeContextMenu::Completions(menu)) => {
2650 if menu.visible() {
2651 key_context.add("menu");
2652 key_context.add("showing_completions");
2653 }
2654 }
2655 Some(CodeContextMenu::CodeActions(menu)) => {
2656 if menu.visible() {
2657 key_context.add("menu");
2658 key_context.add("showing_code_actions")
2659 }
2660 }
2661 None => {}
2662 }
2663
2664 if self.signature_help_state.has_multiple_signatures() {
2665 key_context.add("showing_signature_help");
2666 }
2667
2668 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2669 if !self.focus_handle(cx).contains_focused(window, cx)
2670 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2671 {
2672 for addon in self.addons.values() {
2673 addon.extend_key_context(&mut key_context, cx)
2674 }
2675 }
2676
2677 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2678 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2679 Some(
2680 file.full_path(cx)
2681 .extension()?
2682 .to_string_lossy()
2683 .into_owned(),
2684 )
2685 }) {
2686 key_context.set("extension", extension);
2687 }
2688 } else {
2689 key_context.add("multibuffer");
2690 }
2691
2692 if has_active_edit_prediction {
2693 if self.edit_prediction_in_conflict() {
2694 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2695 } else {
2696 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2697 key_context.add("copilot_suggestion");
2698 }
2699 }
2700
2701 if self.selection_mark_mode {
2702 key_context.add("selection_mode");
2703 }
2704
2705 let disjoint = self.selections.disjoint_anchors();
2706 let snapshot = self.snapshot(window, cx);
2707 let snapshot = snapshot.buffer_snapshot();
2708 if self.mode == EditorMode::SingleLine
2709 && let [selection] = disjoint
2710 && selection.start == selection.end
2711 && selection.end.to_offset(snapshot) == snapshot.len()
2712 {
2713 key_context.add("end_of_input");
2714 }
2715
2716 if self.has_any_expanded_diff_hunks(cx) {
2717 key_context.add("diffs_expanded");
2718 }
2719
2720 key_context
2721 }
2722
2723 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2724 self.last_bounds.as_ref()
2725 }
2726
2727 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2728 if self.mouse_cursor_hidden {
2729 self.mouse_cursor_hidden = false;
2730 cx.notify();
2731 }
2732 }
2733
2734 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2735 let hide_mouse_cursor = match origin {
2736 HideMouseCursorOrigin::TypingAction => {
2737 matches!(
2738 self.hide_mouse_mode,
2739 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2740 )
2741 }
2742 HideMouseCursorOrigin::MovementAction => {
2743 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2744 }
2745 };
2746 if self.mouse_cursor_hidden != hide_mouse_cursor {
2747 self.mouse_cursor_hidden = hide_mouse_cursor;
2748 cx.notify();
2749 }
2750 }
2751
2752 pub fn edit_prediction_in_conflict(&self) -> bool {
2753 if !self.show_edit_predictions_in_menu() {
2754 return false;
2755 }
2756
2757 let showing_completions = self
2758 .context_menu
2759 .borrow()
2760 .as_ref()
2761 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2762
2763 showing_completions
2764 || self.edit_prediction_requires_modifier()
2765 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2766 // bindings to insert tab characters.
2767 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2768 }
2769
2770 pub fn accept_edit_prediction_keybind(
2771 &self,
2772 granularity: EditPredictionGranularity,
2773 window: &mut Window,
2774 cx: &mut App,
2775 ) -> AcceptEditPredictionBinding {
2776 let key_context = self.key_context_internal(true, window, cx);
2777 let in_conflict = self.edit_prediction_in_conflict();
2778
2779 let bindings =
2780 match granularity {
2781 EditPredictionGranularity::Word => window
2782 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2783 EditPredictionGranularity::Line => window
2784 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2785 EditPredictionGranularity::Full => {
2786 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2787 }
2788 };
2789
2790 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2791 !in_conflict
2792 || binding
2793 .keystrokes()
2794 .first()
2795 .is_some_and(|keystroke| keystroke.modifiers().modified())
2796 }))
2797 }
2798
2799 pub fn new_file(
2800 workspace: &mut Workspace,
2801 _: &workspace::NewFile,
2802 window: &mut Window,
2803 cx: &mut Context<Workspace>,
2804 ) {
2805 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2806 "Failed to create buffer",
2807 window,
2808 cx,
2809 |e, _, _| match e.error_code() {
2810 ErrorCode::RemoteUpgradeRequired => Some(format!(
2811 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2812 e.error_tag("required").unwrap_or("the latest version")
2813 )),
2814 _ => None,
2815 },
2816 );
2817 }
2818
2819 pub fn new_in_workspace(
2820 workspace: &mut Workspace,
2821 window: &mut Window,
2822 cx: &mut Context<Workspace>,
2823 ) -> Task<Result<Entity<Editor>>> {
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, |workspace, window, cx| {
2830 let editor =
2831 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2832 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2833 editor
2834 })
2835 })
2836 }
2837
2838 fn new_file_vertical(
2839 workspace: &mut Workspace,
2840 _: &workspace::NewFileSplitVertical,
2841 window: &mut Window,
2842 cx: &mut Context<Workspace>,
2843 ) {
2844 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2845 }
2846
2847 fn new_file_horizontal(
2848 workspace: &mut Workspace,
2849 _: &workspace::NewFileSplitHorizontal,
2850 window: &mut Window,
2851 cx: &mut Context<Workspace>,
2852 ) {
2853 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2854 }
2855
2856 fn new_file_split(
2857 workspace: &mut Workspace,
2858 action: &workspace::NewFileSplit,
2859 window: &mut Window,
2860 cx: &mut Context<Workspace>,
2861 ) {
2862 Self::new_file_in_direction(workspace, action.0, window, cx)
2863 }
2864
2865 fn new_file_in_direction(
2866 workspace: &mut Workspace,
2867 direction: SplitDirection,
2868 window: &mut Window,
2869 cx: &mut Context<Workspace>,
2870 ) {
2871 let project = workspace.project().clone();
2872 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2873
2874 cx.spawn_in(window, async move |workspace, cx| {
2875 let buffer = create.await?;
2876 workspace.update_in(cx, move |workspace, window, cx| {
2877 workspace.split_item(
2878 direction,
2879 Box::new(
2880 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2881 ),
2882 window,
2883 cx,
2884 )
2885 })?;
2886 anyhow::Ok(())
2887 })
2888 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2889 match e.error_code() {
2890 ErrorCode::RemoteUpgradeRequired => Some(format!(
2891 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2892 e.error_tag("required").unwrap_or("the latest version")
2893 )),
2894 _ => None,
2895 }
2896 });
2897 }
2898
2899 pub fn leader_id(&self) -> Option<CollaboratorId> {
2900 self.leader_id
2901 }
2902
2903 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2904 &self.buffer
2905 }
2906
2907 pub fn project(&self) -> Option<&Entity<Project>> {
2908 self.project.as_ref()
2909 }
2910
2911 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2912 self.workspace.as_ref()?.0.upgrade()
2913 }
2914
2915 /// Returns the workspace serialization ID if this editor should be serialized.
2916 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2917 self.workspace
2918 .as_ref()
2919 .filter(|_| self.should_serialize_buffer())
2920 .and_then(|workspace| workspace.1)
2921 }
2922
2923 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2924 self.buffer().read(cx).title(cx)
2925 }
2926
2927 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2928 let git_blame_gutter_max_author_length = self
2929 .render_git_blame_gutter(cx)
2930 .then(|| {
2931 if let Some(blame) = self.blame.as_ref() {
2932 let max_author_length =
2933 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2934 Some(max_author_length)
2935 } else {
2936 None
2937 }
2938 })
2939 .flatten();
2940
2941 EditorSnapshot {
2942 mode: self.mode.clone(),
2943 show_gutter: self.show_gutter,
2944 offset_content: self.offset_content,
2945 show_line_numbers: self.show_line_numbers,
2946 show_git_diff_gutter: self.show_git_diff_gutter,
2947 show_code_actions: self.show_code_actions,
2948 show_runnables: self.show_runnables,
2949 show_breakpoints: self.show_breakpoints,
2950 git_blame_gutter_max_author_length,
2951 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2952 placeholder_display_snapshot: self
2953 .placeholder_display_map
2954 .as_ref()
2955 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2956 scroll_anchor: self.scroll_manager.anchor(),
2957 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2958 is_focused: self.focus_handle.is_focused(window),
2959 current_line_highlight: self
2960 .current_line_highlight
2961 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2962 gutter_hovered: self.gutter_hovered,
2963 }
2964 }
2965
2966 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2967 self.buffer.read(cx).language_at(point, cx)
2968 }
2969
2970 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2971 self.buffer.read(cx).read(cx).file_at(point).cloned()
2972 }
2973
2974 pub fn active_excerpt(
2975 &self,
2976 cx: &App,
2977 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2978 self.buffer
2979 .read(cx)
2980 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2981 }
2982
2983 pub fn mode(&self) -> &EditorMode {
2984 &self.mode
2985 }
2986
2987 pub fn set_mode(&mut self, mode: EditorMode) {
2988 self.mode = mode;
2989 }
2990
2991 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2992 self.collaboration_hub.as_deref()
2993 }
2994
2995 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2996 self.collaboration_hub = Some(hub);
2997 }
2998
2999 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3000 self.in_project_search = in_project_search;
3001 }
3002
3003 pub fn set_custom_context_menu(
3004 &mut self,
3005 f: impl 'static
3006 + Fn(
3007 &mut Self,
3008 DisplayPoint,
3009 &mut Window,
3010 &mut Context<Self>,
3011 ) -> Option<Entity<ui::ContextMenu>>,
3012 ) {
3013 self.custom_context_menu = Some(Box::new(f))
3014 }
3015
3016 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3017 self.completion_provider = provider;
3018 }
3019
3020 #[cfg(any(test, feature = "test-support"))]
3021 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3022 self.completion_provider.clone()
3023 }
3024
3025 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3026 self.semantics_provider.clone()
3027 }
3028
3029 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3030 self.semantics_provider = provider;
3031 }
3032
3033 pub fn set_edit_prediction_provider<T>(
3034 &mut self,
3035 provider: Option<Entity<T>>,
3036 window: &mut Window,
3037 cx: &mut Context<Self>,
3038 ) where
3039 T: EditPredictionDelegate,
3040 {
3041 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3042 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3043 if this.focus_handle.is_focused(window) {
3044 this.update_visible_edit_prediction(window, cx);
3045 }
3046 }),
3047 provider: Arc::new(provider),
3048 });
3049 self.update_edit_prediction_settings(cx);
3050 self.refresh_edit_prediction(false, false, window, cx);
3051 }
3052
3053 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3054 self.placeholder_display_map
3055 .as_ref()
3056 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3057 }
3058
3059 pub fn set_placeholder_text(
3060 &mut self,
3061 placeholder_text: &str,
3062 window: &mut Window,
3063 cx: &mut Context<Self>,
3064 ) {
3065 let multibuffer = cx
3066 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3067
3068 let style = window.text_style();
3069
3070 self.placeholder_display_map = Some(cx.new(|cx| {
3071 DisplayMap::new(
3072 multibuffer,
3073 style.font(),
3074 style.font_size.to_pixels(window.rem_size()),
3075 None,
3076 FILE_HEADER_HEIGHT,
3077 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3078 Default::default(),
3079 DiagnosticSeverity::Off,
3080 cx,
3081 )
3082 }));
3083 cx.notify();
3084 }
3085
3086 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3087 self.cursor_shape = cursor_shape;
3088
3089 // Disrupt blink for immediate user feedback that the cursor shape has changed
3090 self.blink_manager.update(cx, BlinkManager::show_cursor);
3091
3092 cx.notify();
3093 }
3094
3095 pub fn cursor_shape(&self) -> CursorShape {
3096 self.cursor_shape
3097 }
3098
3099 pub fn set_cursor_offset_on_selection(&mut self, set_cursor_offset_on_selection: bool) {
3100 self.cursor_offset_on_selection = set_cursor_offset_on_selection;
3101 }
3102
3103 pub fn set_current_line_highlight(
3104 &mut self,
3105 current_line_highlight: Option<CurrentLineHighlight>,
3106 ) {
3107 self.current_line_highlight = current_line_highlight;
3108 }
3109
3110 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3111 self.collapse_matches = collapse_matches;
3112 }
3113
3114 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3115 if self.collapse_matches {
3116 return range.start..range.start;
3117 }
3118 range.clone()
3119 }
3120
3121 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3122 self.display_map.read(cx).clip_at_line_ends
3123 }
3124
3125 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3126 if self.display_map.read(cx).clip_at_line_ends != clip {
3127 self.display_map
3128 .update(cx, |map, _| map.clip_at_line_ends = clip);
3129 }
3130 }
3131
3132 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3133 self.input_enabled = input_enabled;
3134 }
3135
3136 pub fn set_edit_predictions_hidden_for_vim_mode(
3137 &mut self,
3138 hidden: bool,
3139 window: &mut Window,
3140 cx: &mut Context<Self>,
3141 ) {
3142 if hidden != self.edit_predictions_hidden_for_vim_mode {
3143 self.edit_predictions_hidden_for_vim_mode = hidden;
3144 if hidden {
3145 self.update_visible_edit_prediction(window, cx);
3146 } else {
3147 self.refresh_edit_prediction(true, false, window, cx);
3148 }
3149 }
3150 }
3151
3152 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3153 self.menu_edit_predictions_policy = value;
3154 }
3155
3156 pub fn set_autoindent(&mut self, autoindent: bool) {
3157 if autoindent {
3158 self.autoindent_mode = Some(AutoindentMode::EachLine);
3159 } else {
3160 self.autoindent_mode = None;
3161 }
3162 }
3163
3164 pub fn read_only(&self, cx: &App) -> bool {
3165 self.read_only || self.buffer.read(cx).read_only()
3166 }
3167
3168 pub fn set_read_only(&mut self, read_only: bool) {
3169 self.read_only = read_only;
3170 }
3171
3172 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3173 self.use_autoclose = autoclose;
3174 }
3175
3176 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3177 self.use_auto_surround = auto_surround;
3178 }
3179
3180 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3181 self.auto_replace_emoji_shortcode = auto_replace;
3182 }
3183
3184 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3185 self.buffer_serialization = should_serialize.then(|| {
3186 BufferSerialization::new(
3187 ProjectSettings::get_global(cx)
3188 .session
3189 .restore_unsaved_buffers,
3190 )
3191 })
3192 }
3193
3194 fn should_serialize_buffer(&self) -> bool {
3195 self.buffer_serialization.is_some()
3196 }
3197
3198 pub fn toggle_edit_predictions(
3199 &mut self,
3200 _: &ToggleEditPrediction,
3201 window: &mut Window,
3202 cx: &mut Context<Self>,
3203 ) {
3204 if self.show_edit_predictions_override.is_some() {
3205 self.set_show_edit_predictions(None, window, cx);
3206 } else {
3207 let show_edit_predictions = !self.edit_predictions_enabled();
3208 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3209 }
3210 }
3211
3212 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3213 self.show_completions_on_input_override = show_completions_on_input;
3214 }
3215
3216 pub fn set_show_edit_predictions(
3217 &mut self,
3218 show_edit_predictions: Option<bool>,
3219 window: &mut Window,
3220 cx: &mut Context<Self>,
3221 ) {
3222 self.show_edit_predictions_override = show_edit_predictions;
3223 self.update_edit_prediction_settings(cx);
3224
3225 if let Some(false) = show_edit_predictions {
3226 self.discard_edit_prediction(false, cx);
3227 } else {
3228 self.refresh_edit_prediction(false, true, window, cx);
3229 }
3230 }
3231
3232 fn edit_predictions_disabled_in_scope(
3233 &self,
3234 buffer: &Entity<Buffer>,
3235 buffer_position: language::Anchor,
3236 cx: &App,
3237 ) -> bool {
3238 let snapshot = buffer.read(cx).snapshot();
3239 let settings = snapshot.settings_at(buffer_position, cx);
3240
3241 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3242 return false;
3243 };
3244
3245 scope.override_name().is_some_and(|scope_name| {
3246 settings
3247 .edit_predictions_disabled_in
3248 .iter()
3249 .any(|s| s == scope_name)
3250 })
3251 }
3252
3253 pub fn set_use_modal_editing(&mut self, to: bool) {
3254 self.use_modal_editing = to;
3255 }
3256
3257 pub fn use_modal_editing(&self) -> bool {
3258 self.use_modal_editing
3259 }
3260
3261 fn selections_did_change(
3262 &mut self,
3263 local: bool,
3264 old_cursor_position: &Anchor,
3265 effects: SelectionEffects,
3266 window: &mut Window,
3267 cx: &mut Context<Self>,
3268 ) {
3269 window.invalidate_character_coordinates();
3270
3271 // Copy selections to primary selection buffer
3272 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3273 if local {
3274 let selections = self
3275 .selections
3276 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3277 let buffer_handle = self.buffer.read(cx).read(cx);
3278
3279 let mut text = String::new();
3280 for (index, selection) in selections.iter().enumerate() {
3281 let text_for_selection = buffer_handle
3282 .text_for_range(selection.start..selection.end)
3283 .collect::<String>();
3284
3285 text.push_str(&text_for_selection);
3286 if index != selections.len() - 1 {
3287 text.push('\n');
3288 }
3289 }
3290
3291 if !text.is_empty() {
3292 cx.write_to_primary(ClipboardItem::new_string(text));
3293 }
3294 }
3295
3296 let selection_anchors = self.selections.disjoint_anchors_arc();
3297
3298 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3299 self.buffer.update(cx, |buffer, cx| {
3300 buffer.set_active_selections(
3301 &selection_anchors,
3302 self.selections.line_mode(),
3303 self.cursor_shape,
3304 cx,
3305 )
3306 });
3307 }
3308 let display_map = self
3309 .display_map
3310 .update(cx, |display_map, cx| display_map.snapshot(cx));
3311 let buffer = display_map.buffer_snapshot();
3312 if self.selections.count() == 1 {
3313 self.add_selections_state = None;
3314 }
3315 self.select_next_state = None;
3316 self.select_prev_state = None;
3317 self.select_syntax_node_history.try_clear();
3318 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3319 self.snippet_stack.invalidate(&selection_anchors, buffer);
3320 self.take_rename(false, window, cx);
3321
3322 let newest_selection = self.selections.newest_anchor();
3323 let new_cursor_position = newest_selection.head();
3324 let selection_start = newest_selection.start;
3325
3326 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3327 self.push_to_nav_history(
3328 *old_cursor_position,
3329 Some(new_cursor_position.to_point(buffer)),
3330 false,
3331 effects.nav_history == Some(true),
3332 cx,
3333 );
3334 }
3335
3336 if local {
3337 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3338 self.register_buffer(buffer_id, cx);
3339 }
3340
3341 let mut context_menu = self.context_menu.borrow_mut();
3342 let completion_menu = match context_menu.as_ref() {
3343 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3344 Some(CodeContextMenu::CodeActions(_)) => {
3345 *context_menu = None;
3346 None
3347 }
3348 None => None,
3349 };
3350 let completion_position = completion_menu.map(|menu| menu.initial_position);
3351 drop(context_menu);
3352
3353 if effects.completions
3354 && let Some(completion_position) = completion_position
3355 {
3356 let start_offset = selection_start.to_offset(buffer);
3357 let position_matches = start_offset == completion_position.to_offset(buffer);
3358 let continue_showing = if position_matches {
3359 if self.snippet_stack.is_empty() {
3360 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3361 == Some(CharKind::Word)
3362 } else {
3363 // Snippet choices can be shown even when the cursor is in whitespace.
3364 // Dismissing the menu with actions like backspace is handled by
3365 // invalidation regions.
3366 true
3367 }
3368 } else {
3369 false
3370 };
3371
3372 if continue_showing {
3373 self.open_or_update_completions_menu(None, None, false, window, cx);
3374 } else {
3375 self.hide_context_menu(window, cx);
3376 }
3377 }
3378
3379 hide_hover(self, cx);
3380
3381 if old_cursor_position.to_display_point(&display_map).row()
3382 != new_cursor_position.to_display_point(&display_map).row()
3383 {
3384 self.available_code_actions.take();
3385 }
3386 self.refresh_code_actions(window, cx);
3387 self.refresh_document_highlights(cx);
3388 refresh_linked_ranges(self, window, cx);
3389
3390 self.refresh_selected_text_highlights(false, window, cx);
3391 self.refresh_matching_bracket_highlights(window, cx);
3392 self.update_visible_edit_prediction(window, cx);
3393 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3394 self.inline_blame_popover.take();
3395 if self.git_blame_inline_enabled {
3396 self.start_inline_blame_timer(window, cx);
3397 }
3398 }
3399
3400 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3401 cx.emit(EditorEvent::SelectionsChanged { local });
3402
3403 let selections = &self.selections.disjoint_anchors_arc();
3404 if selections.len() == 1 {
3405 cx.emit(SearchEvent::ActiveMatchChanged)
3406 }
3407 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3408 let inmemory_selections = selections
3409 .iter()
3410 .map(|s| {
3411 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3412 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3413 })
3414 .collect();
3415 self.update_restoration_data(cx, |data| {
3416 data.selections = inmemory_selections;
3417 });
3418
3419 if WorkspaceSettings::get(None, cx).restore_on_startup
3420 != RestoreOnStartupBehavior::EmptyTab
3421 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3422 {
3423 let snapshot = self.buffer().read(cx).snapshot(cx);
3424 let selections = selections.clone();
3425 let background_executor = cx.background_executor().clone();
3426 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3427 self.serialize_selections = cx.background_spawn(async move {
3428 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3429 let db_selections = selections
3430 .iter()
3431 .map(|selection| {
3432 (
3433 selection.start.to_offset(&snapshot).0,
3434 selection.end.to_offset(&snapshot).0,
3435 )
3436 })
3437 .collect();
3438
3439 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3440 .await
3441 .with_context(|| {
3442 format!(
3443 "persisting editor selections for editor {editor_id}, \
3444 workspace {workspace_id:?}"
3445 )
3446 })
3447 .log_err();
3448 });
3449 }
3450 }
3451
3452 cx.notify();
3453 }
3454
3455 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3456 use text::ToOffset as _;
3457 use text::ToPoint as _;
3458
3459 if self.mode.is_minimap()
3460 || WorkspaceSettings::get(None, cx).restore_on_startup
3461 == RestoreOnStartupBehavior::EmptyTab
3462 {
3463 return;
3464 }
3465
3466 if !self.buffer().read(cx).is_singleton() {
3467 return;
3468 }
3469
3470 let display_snapshot = self
3471 .display_map
3472 .update(cx, |display_map, cx| display_map.snapshot(cx));
3473 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3474 return;
3475 };
3476 let inmemory_folds = display_snapshot
3477 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3478 .map(|fold| {
3479 fold.range.start.text_anchor.to_point(&snapshot)
3480 ..fold.range.end.text_anchor.to_point(&snapshot)
3481 })
3482 .collect();
3483 self.update_restoration_data(cx, |data| {
3484 data.folds = inmemory_folds;
3485 });
3486
3487 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3488 return;
3489 };
3490 let background_executor = cx.background_executor().clone();
3491 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3492 let db_folds = display_snapshot
3493 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3494 .map(|fold| {
3495 (
3496 fold.range.start.text_anchor.to_offset(&snapshot),
3497 fold.range.end.text_anchor.to_offset(&snapshot),
3498 )
3499 })
3500 .collect();
3501 self.serialize_folds = cx.background_spawn(async move {
3502 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3503 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3504 .await
3505 .with_context(|| {
3506 format!(
3507 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3508 )
3509 })
3510 .log_err();
3511 });
3512 }
3513
3514 pub fn sync_selections(
3515 &mut self,
3516 other: Entity<Editor>,
3517 cx: &mut Context<Self>,
3518 ) -> gpui::Subscription {
3519 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3520 if !other_selections.is_empty() {
3521 self.selections
3522 .change_with(&self.display_snapshot(cx), |selections| {
3523 selections.select_anchors(other_selections);
3524 });
3525 }
3526
3527 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3528 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3529 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3530 if other_selections.is_empty() {
3531 return;
3532 }
3533 let snapshot = this.display_snapshot(cx);
3534 this.selections.change_with(&snapshot, |selections| {
3535 selections.select_anchors(other_selections);
3536 });
3537 }
3538 });
3539
3540 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3541 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3542 let these_selections = this.selections.disjoint_anchors().to_vec();
3543 if these_selections.is_empty() {
3544 return;
3545 }
3546 other.update(cx, |other_editor, cx| {
3547 let snapshot = other_editor.display_snapshot(cx);
3548 other_editor
3549 .selections
3550 .change_with(&snapshot, |selections| {
3551 selections.select_anchors(these_selections);
3552 })
3553 });
3554 }
3555 });
3556
3557 Subscription::join(other_subscription, this_subscription)
3558 }
3559
3560 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3561 if self.buffer().read(cx).is_singleton() {
3562 return;
3563 }
3564 let snapshot = self.buffer.read(cx).snapshot(cx);
3565 let buffer_ids: HashSet<BufferId> = self
3566 .selections
3567 .disjoint_anchor_ranges()
3568 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3569 .collect();
3570 for buffer_id in buffer_ids {
3571 self.unfold_buffer(buffer_id, cx);
3572 }
3573 }
3574
3575 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3576 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3577 /// effects of selection change occur at the end of the transaction.
3578 pub fn change_selections<R>(
3579 &mut self,
3580 effects: SelectionEffects,
3581 window: &mut Window,
3582 cx: &mut Context<Self>,
3583 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3584 ) -> R {
3585 let snapshot = self.display_snapshot(cx);
3586 if let Some(state) = &mut self.deferred_selection_effects_state {
3587 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3588 state.effects.completions = effects.completions;
3589 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3590 let (changed, result) = self.selections.change_with(&snapshot, change);
3591 state.changed |= changed;
3592 return result;
3593 }
3594 let mut state = DeferredSelectionEffectsState {
3595 changed: false,
3596 effects,
3597 old_cursor_position: self.selections.newest_anchor().head(),
3598 history_entry: SelectionHistoryEntry {
3599 selections: self.selections.disjoint_anchors_arc(),
3600 select_next_state: self.select_next_state.clone(),
3601 select_prev_state: self.select_prev_state.clone(),
3602 add_selections_state: self.add_selections_state.clone(),
3603 },
3604 };
3605 let (changed, result) = self.selections.change_with(&snapshot, change);
3606 state.changed = state.changed || changed;
3607 if self.defer_selection_effects {
3608 self.deferred_selection_effects_state = Some(state);
3609 } else {
3610 self.apply_selection_effects(state, window, cx);
3611 }
3612 result
3613 }
3614
3615 /// Defers the effects of selection change, so that the effects of multiple calls to
3616 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3617 /// to selection history and the state of popovers based on selection position aren't
3618 /// erroneously updated.
3619 pub fn with_selection_effects_deferred<R>(
3620 &mut self,
3621 window: &mut Window,
3622 cx: &mut Context<Self>,
3623 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3624 ) -> R {
3625 let already_deferred = self.defer_selection_effects;
3626 self.defer_selection_effects = true;
3627 let result = update(self, window, cx);
3628 if !already_deferred {
3629 self.defer_selection_effects = false;
3630 if let Some(state) = self.deferred_selection_effects_state.take() {
3631 self.apply_selection_effects(state, window, cx);
3632 }
3633 }
3634 result
3635 }
3636
3637 fn apply_selection_effects(
3638 &mut self,
3639 state: DeferredSelectionEffectsState,
3640 window: &mut Window,
3641 cx: &mut Context<Self>,
3642 ) {
3643 if state.changed {
3644 self.selection_history.push(state.history_entry);
3645
3646 if let Some(autoscroll) = state.effects.scroll {
3647 self.request_autoscroll(autoscroll, cx);
3648 }
3649
3650 let old_cursor_position = &state.old_cursor_position;
3651
3652 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3653
3654 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3655 self.show_signature_help(&ShowSignatureHelp, window, cx);
3656 }
3657 }
3658 }
3659
3660 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3661 where
3662 I: IntoIterator<Item = (Range<S>, T)>,
3663 S: ToOffset,
3664 T: Into<Arc<str>>,
3665 {
3666 if self.read_only(cx) {
3667 return;
3668 }
3669
3670 self.buffer
3671 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3672 }
3673
3674 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3675 where
3676 I: IntoIterator<Item = (Range<S>, T)>,
3677 S: ToOffset,
3678 T: Into<Arc<str>>,
3679 {
3680 if self.read_only(cx) {
3681 return;
3682 }
3683
3684 self.buffer.update(cx, |buffer, cx| {
3685 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3686 });
3687 }
3688
3689 pub fn edit_with_block_indent<I, S, T>(
3690 &mut self,
3691 edits: I,
3692 original_indent_columns: Vec<Option<u32>>,
3693 cx: &mut Context<Self>,
3694 ) where
3695 I: IntoIterator<Item = (Range<S>, T)>,
3696 S: ToOffset,
3697 T: Into<Arc<str>>,
3698 {
3699 if self.read_only(cx) {
3700 return;
3701 }
3702
3703 self.buffer.update(cx, |buffer, cx| {
3704 buffer.edit(
3705 edits,
3706 Some(AutoindentMode::Block {
3707 original_indent_columns,
3708 }),
3709 cx,
3710 )
3711 });
3712 }
3713
3714 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3715 self.hide_context_menu(window, cx);
3716
3717 match phase {
3718 SelectPhase::Begin {
3719 position,
3720 add,
3721 click_count,
3722 } => self.begin_selection(position, add, click_count, window, cx),
3723 SelectPhase::BeginColumnar {
3724 position,
3725 goal_column,
3726 reset,
3727 mode,
3728 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3729 SelectPhase::Extend {
3730 position,
3731 click_count,
3732 } => self.extend_selection(position, click_count, window, cx),
3733 SelectPhase::Update {
3734 position,
3735 goal_column,
3736 scroll_delta,
3737 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3738 SelectPhase::End => self.end_selection(window, cx),
3739 }
3740 }
3741
3742 fn extend_selection(
3743 &mut self,
3744 position: DisplayPoint,
3745 click_count: usize,
3746 window: &mut Window,
3747 cx: &mut Context<Self>,
3748 ) {
3749 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3750 let tail = self
3751 .selections
3752 .newest::<MultiBufferOffset>(&display_map)
3753 .tail();
3754 let click_count = click_count.max(match self.selections.select_mode() {
3755 SelectMode::Character => 1,
3756 SelectMode::Word(_) => 2,
3757 SelectMode::Line(_) => 3,
3758 SelectMode::All => 4,
3759 });
3760 self.begin_selection(position, false, click_count, window, cx);
3761
3762 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3763
3764 let current_selection = match self.selections.select_mode() {
3765 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3766 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3767 };
3768
3769 let mut pending_selection = self
3770 .selections
3771 .pending_anchor()
3772 .cloned()
3773 .expect("extend_selection not called with pending selection");
3774
3775 if pending_selection
3776 .start
3777 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3778 == Ordering::Greater
3779 {
3780 pending_selection.start = current_selection.start;
3781 }
3782 if pending_selection
3783 .end
3784 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3785 == Ordering::Less
3786 {
3787 pending_selection.end = current_selection.end;
3788 pending_selection.reversed = true;
3789 }
3790
3791 let mut pending_mode = self.selections.pending_mode().unwrap();
3792 match &mut pending_mode {
3793 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3794 _ => {}
3795 }
3796
3797 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3798 SelectionEffects::scroll(Autoscroll::fit())
3799 } else {
3800 SelectionEffects::no_scroll()
3801 };
3802
3803 self.change_selections(effects, window, cx, |s| {
3804 s.set_pending(pending_selection.clone(), pending_mode);
3805 s.set_is_extending(true);
3806 });
3807 }
3808
3809 fn begin_selection(
3810 &mut self,
3811 position: DisplayPoint,
3812 add: bool,
3813 click_count: usize,
3814 window: &mut Window,
3815 cx: &mut Context<Self>,
3816 ) {
3817 if !self.focus_handle.is_focused(window) {
3818 self.last_focused_descendant = None;
3819 window.focus(&self.focus_handle, cx);
3820 }
3821
3822 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3823 let buffer = display_map.buffer_snapshot();
3824 let position = display_map.clip_point(position, Bias::Left);
3825
3826 let start;
3827 let end;
3828 let mode;
3829 let mut auto_scroll;
3830 match click_count {
3831 1 => {
3832 start = buffer.anchor_before(position.to_point(&display_map));
3833 end = start;
3834 mode = SelectMode::Character;
3835 auto_scroll = true;
3836 }
3837 2 => {
3838 let position = display_map
3839 .clip_point(position, Bias::Left)
3840 .to_offset(&display_map, Bias::Left);
3841 let (range, _) = buffer.surrounding_word(position, None);
3842 start = buffer.anchor_before(range.start);
3843 end = buffer.anchor_before(range.end);
3844 mode = SelectMode::Word(start..end);
3845 auto_scroll = true;
3846 }
3847 3 => {
3848 let position = display_map
3849 .clip_point(position, Bias::Left)
3850 .to_point(&display_map);
3851 let line_start = display_map.prev_line_boundary(position).0;
3852 let next_line_start = buffer.clip_point(
3853 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3854 Bias::Left,
3855 );
3856 start = buffer.anchor_before(line_start);
3857 end = buffer.anchor_before(next_line_start);
3858 mode = SelectMode::Line(start..end);
3859 auto_scroll = true;
3860 }
3861 _ => {
3862 start = buffer.anchor_before(MultiBufferOffset(0));
3863 end = buffer.anchor_before(buffer.len());
3864 mode = SelectMode::All;
3865 auto_scroll = false;
3866 }
3867 }
3868 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3869
3870 let point_to_delete: Option<usize> = {
3871 let selected_points: Vec<Selection<Point>> =
3872 self.selections.disjoint_in_range(start..end, &display_map);
3873
3874 if !add || click_count > 1 {
3875 None
3876 } else if !selected_points.is_empty() {
3877 Some(selected_points[0].id)
3878 } else {
3879 let clicked_point_already_selected =
3880 self.selections.disjoint_anchors().iter().find(|selection| {
3881 selection.start.to_point(buffer) == start.to_point(buffer)
3882 || selection.end.to_point(buffer) == end.to_point(buffer)
3883 });
3884
3885 clicked_point_already_selected.map(|selection| selection.id)
3886 }
3887 };
3888
3889 let selections_count = self.selections.count();
3890 let effects = if auto_scroll {
3891 SelectionEffects::default()
3892 } else {
3893 SelectionEffects::no_scroll()
3894 };
3895
3896 self.change_selections(effects, window, cx, |s| {
3897 if let Some(point_to_delete) = point_to_delete {
3898 s.delete(point_to_delete);
3899
3900 if selections_count == 1 {
3901 s.set_pending_anchor_range(start..end, mode);
3902 }
3903 } else {
3904 if !add {
3905 s.clear_disjoint();
3906 }
3907
3908 s.set_pending_anchor_range(start..end, mode);
3909 }
3910 });
3911 }
3912
3913 fn begin_columnar_selection(
3914 &mut self,
3915 position: DisplayPoint,
3916 goal_column: u32,
3917 reset: bool,
3918 mode: ColumnarMode,
3919 window: &mut Window,
3920 cx: &mut Context<Self>,
3921 ) {
3922 if !self.focus_handle.is_focused(window) {
3923 self.last_focused_descendant = None;
3924 window.focus(&self.focus_handle, cx);
3925 }
3926
3927 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3928
3929 if reset {
3930 let pointer_position = display_map
3931 .buffer_snapshot()
3932 .anchor_before(position.to_point(&display_map));
3933
3934 self.change_selections(
3935 SelectionEffects::scroll(Autoscroll::newest()),
3936 window,
3937 cx,
3938 |s| {
3939 s.clear_disjoint();
3940 s.set_pending_anchor_range(
3941 pointer_position..pointer_position,
3942 SelectMode::Character,
3943 );
3944 },
3945 );
3946 };
3947
3948 let tail = self.selections.newest::<Point>(&display_map).tail();
3949 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3950 self.columnar_selection_state = match mode {
3951 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3952 selection_tail: selection_anchor,
3953 display_point: if reset {
3954 if position.column() != goal_column {
3955 Some(DisplayPoint::new(position.row(), goal_column))
3956 } else {
3957 None
3958 }
3959 } else {
3960 None
3961 },
3962 }),
3963 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3964 selection_tail: selection_anchor,
3965 }),
3966 };
3967
3968 if !reset {
3969 self.select_columns(position, goal_column, &display_map, window, cx);
3970 }
3971 }
3972
3973 fn update_selection(
3974 &mut self,
3975 position: DisplayPoint,
3976 goal_column: u32,
3977 scroll_delta: gpui::Point<f32>,
3978 window: &mut Window,
3979 cx: &mut Context<Self>,
3980 ) {
3981 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3982
3983 if self.columnar_selection_state.is_some() {
3984 self.select_columns(position, goal_column, &display_map, window, cx);
3985 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3986 let buffer = display_map.buffer_snapshot();
3987 let head;
3988 let tail;
3989 let mode = self.selections.pending_mode().unwrap();
3990 match &mode {
3991 SelectMode::Character => {
3992 head = position.to_point(&display_map);
3993 tail = pending.tail().to_point(buffer);
3994 }
3995 SelectMode::Word(original_range) => {
3996 let offset = display_map
3997 .clip_point(position, Bias::Left)
3998 .to_offset(&display_map, Bias::Left);
3999 let original_range = original_range.to_offset(buffer);
4000
4001 let head_offset = if buffer.is_inside_word(offset, None)
4002 || original_range.contains(&offset)
4003 {
4004 let (word_range, _) = buffer.surrounding_word(offset, None);
4005 if word_range.start < original_range.start {
4006 word_range.start
4007 } else {
4008 word_range.end
4009 }
4010 } else {
4011 offset
4012 };
4013
4014 head = head_offset.to_point(buffer);
4015 if head_offset <= original_range.start {
4016 tail = original_range.end.to_point(buffer);
4017 } else {
4018 tail = original_range.start.to_point(buffer);
4019 }
4020 }
4021 SelectMode::Line(original_range) => {
4022 let original_range = original_range.to_point(display_map.buffer_snapshot());
4023
4024 let position = display_map
4025 .clip_point(position, Bias::Left)
4026 .to_point(&display_map);
4027 let line_start = display_map.prev_line_boundary(position).0;
4028 let next_line_start = buffer.clip_point(
4029 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4030 Bias::Left,
4031 );
4032
4033 if line_start < original_range.start {
4034 head = line_start
4035 } else {
4036 head = next_line_start
4037 }
4038
4039 if head <= original_range.start {
4040 tail = original_range.end;
4041 } else {
4042 tail = original_range.start;
4043 }
4044 }
4045 SelectMode::All => {
4046 return;
4047 }
4048 };
4049
4050 if head < tail {
4051 pending.start = buffer.anchor_before(head);
4052 pending.end = buffer.anchor_before(tail);
4053 pending.reversed = true;
4054 } else {
4055 pending.start = buffer.anchor_before(tail);
4056 pending.end = buffer.anchor_before(head);
4057 pending.reversed = false;
4058 }
4059
4060 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4061 s.set_pending(pending.clone(), mode);
4062 });
4063 } else {
4064 log::error!("update_selection dispatched with no pending selection");
4065 return;
4066 }
4067
4068 self.apply_scroll_delta(scroll_delta, window, cx);
4069 cx.notify();
4070 }
4071
4072 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4073 self.columnar_selection_state.take();
4074 if let Some(pending_mode) = self.selections.pending_mode() {
4075 let selections = self
4076 .selections
4077 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4078 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4079 s.select(selections);
4080 s.clear_pending();
4081 if s.is_extending() {
4082 s.set_is_extending(false);
4083 } else {
4084 s.set_select_mode(pending_mode);
4085 }
4086 });
4087 }
4088 }
4089
4090 fn select_columns(
4091 &mut self,
4092 head: DisplayPoint,
4093 goal_column: u32,
4094 display_map: &DisplaySnapshot,
4095 window: &mut Window,
4096 cx: &mut Context<Self>,
4097 ) {
4098 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4099 return;
4100 };
4101
4102 let tail = match columnar_state {
4103 ColumnarSelectionState::FromMouse {
4104 selection_tail,
4105 display_point,
4106 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4107 ColumnarSelectionState::FromSelection { selection_tail } => {
4108 selection_tail.to_display_point(display_map)
4109 }
4110 };
4111
4112 let start_row = cmp::min(tail.row(), head.row());
4113 let end_row = cmp::max(tail.row(), head.row());
4114 let start_column = cmp::min(tail.column(), goal_column);
4115 let end_column = cmp::max(tail.column(), goal_column);
4116 let reversed = start_column < tail.column();
4117
4118 let selection_ranges = (start_row.0..=end_row.0)
4119 .map(DisplayRow)
4120 .filter_map(|row| {
4121 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4122 || start_column <= display_map.line_len(row))
4123 && !display_map.is_block_line(row)
4124 {
4125 let start = display_map
4126 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4127 .to_point(display_map);
4128 let end = display_map
4129 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4130 .to_point(display_map);
4131 if reversed {
4132 Some(end..start)
4133 } else {
4134 Some(start..end)
4135 }
4136 } else {
4137 None
4138 }
4139 })
4140 .collect::<Vec<_>>();
4141 if selection_ranges.is_empty() {
4142 return;
4143 }
4144
4145 let ranges = match columnar_state {
4146 ColumnarSelectionState::FromMouse { .. } => {
4147 let mut non_empty_ranges = selection_ranges
4148 .iter()
4149 .filter(|selection_range| selection_range.start != selection_range.end)
4150 .peekable();
4151 if non_empty_ranges.peek().is_some() {
4152 non_empty_ranges.cloned().collect()
4153 } else {
4154 selection_ranges
4155 }
4156 }
4157 _ => selection_ranges,
4158 };
4159
4160 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4161 s.select_ranges(ranges);
4162 });
4163 cx.notify();
4164 }
4165
4166 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4167 self.selections
4168 .all_adjusted(snapshot)
4169 .iter()
4170 .any(|selection| !selection.is_empty())
4171 }
4172
4173 pub fn has_pending_nonempty_selection(&self) -> bool {
4174 let pending_nonempty_selection = match self.selections.pending_anchor() {
4175 Some(Selection { start, end, .. }) => start != end,
4176 None => false,
4177 };
4178
4179 pending_nonempty_selection
4180 || (self.columnar_selection_state.is_some()
4181 && self.selections.disjoint_anchors().len() > 1)
4182 }
4183
4184 pub fn has_pending_selection(&self) -> bool {
4185 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4186 }
4187
4188 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4189 self.selection_mark_mode = false;
4190 self.selection_drag_state = SelectionDragState::None;
4191
4192 if self.dismiss_menus_and_popups(true, window, cx) {
4193 cx.notify();
4194 return;
4195 }
4196 if self.clear_expanded_diff_hunks(cx) {
4197 cx.notify();
4198 return;
4199 }
4200 if self.show_git_blame_gutter {
4201 self.show_git_blame_gutter = false;
4202 cx.notify();
4203 return;
4204 }
4205
4206 if self.mode.is_full()
4207 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4208 {
4209 cx.notify();
4210 return;
4211 }
4212
4213 cx.propagate();
4214 }
4215
4216 pub fn dismiss_menus_and_popups(
4217 &mut self,
4218 is_user_requested: bool,
4219 window: &mut Window,
4220 cx: &mut Context<Self>,
4221 ) -> bool {
4222 let mut dismissed = false;
4223
4224 dismissed |= self.take_rename(false, window, cx).is_some();
4225 dismissed |= self.hide_blame_popover(true, cx);
4226 dismissed |= hide_hover(self, cx);
4227 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4228 dismissed |= self.hide_context_menu(window, cx).is_some();
4229 dismissed |= self.mouse_context_menu.take().is_some();
4230 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4231 dismissed |= self.snippet_stack.pop().is_some();
4232
4233 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4234 self.dismiss_diagnostics(cx);
4235 dismissed = true;
4236 }
4237
4238 dismissed
4239 }
4240
4241 fn linked_editing_ranges_for(
4242 &self,
4243 selection: Range<text::Anchor>,
4244 cx: &App,
4245 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4246 if self.linked_edit_ranges.is_empty() {
4247 return None;
4248 }
4249 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4250 selection.end.buffer_id.and_then(|end_buffer_id| {
4251 if selection.start.buffer_id != Some(end_buffer_id) {
4252 return None;
4253 }
4254 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4255 let snapshot = buffer.read(cx).snapshot();
4256 self.linked_edit_ranges
4257 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4258 .map(|ranges| (ranges, snapshot, buffer))
4259 })?;
4260 use text::ToOffset as TO;
4261 // find offset from the start of current range to current cursor position
4262 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4263
4264 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4265 let start_difference = start_offset - start_byte_offset;
4266 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4267 let end_difference = end_offset - start_byte_offset;
4268 // Current range has associated linked ranges.
4269 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4270 for range in linked_ranges.iter() {
4271 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4272 let end_offset = start_offset + end_difference;
4273 let start_offset = start_offset + start_difference;
4274 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4275 continue;
4276 }
4277 if self.selections.disjoint_anchor_ranges().any(|s| {
4278 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4279 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4280 {
4281 return false;
4282 }
4283 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4284 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4285 }) {
4286 continue;
4287 }
4288 let start = buffer_snapshot.anchor_after(start_offset);
4289 let end = buffer_snapshot.anchor_after(end_offset);
4290 linked_edits
4291 .entry(buffer.clone())
4292 .or_default()
4293 .push(start..end);
4294 }
4295 Some(linked_edits)
4296 }
4297
4298 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4299 let text: Arc<str> = text.into();
4300
4301 if self.read_only(cx) {
4302 return;
4303 }
4304
4305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4306
4307 self.unfold_buffers_with_selections(cx);
4308
4309 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4310 let mut bracket_inserted = false;
4311 let mut edits = Vec::new();
4312 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4313 let mut new_selections = Vec::with_capacity(selections.len());
4314 let mut new_autoclose_regions = Vec::new();
4315 let snapshot = self.buffer.read(cx).read(cx);
4316 let mut clear_linked_edit_ranges = false;
4317
4318 for (selection, autoclose_region) in
4319 self.selections_with_autoclose_regions(selections, &snapshot)
4320 {
4321 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4322 // Determine if the inserted text matches the opening or closing
4323 // bracket of any of this language's bracket pairs.
4324 let mut bracket_pair = None;
4325 let mut is_bracket_pair_start = false;
4326 let mut is_bracket_pair_end = false;
4327 if !text.is_empty() {
4328 let mut bracket_pair_matching_end = None;
4329 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4330 // and they are removing the character that triggered IME popup.
4331 for (pair, enabled) in scope.brackets() {
4332 if !pair.close && !pair.surround {
4333 continue;
4334 }
4335
4336 if enabled && pair.start.ends_with(text.as_ref()) {
4337 let prefix_len = pair.start.len() - text.len();
4338 let preceding_text_matches_prefix = prefix_len == 0
4339 || (selection.start.column >= (prefix_len as u32)
4340 && snapshot.contains_str_at(
4341 Point::new(
4342 selection.start.row,
4343 selection.start.column - (prefix_len as u32),
4344 ),
4345 &pair.start[..prefix_len],
4346 ));
4347 if preceding_text_matches_prefix {
4348 bracket_pair = Some(pair.clone());
4349 is_bracket_pair_start = true;
4350 break;
4351 }
4352 }
4353 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4354 {
4355 // take first bracket pair matching end, but don't break in case a later bracket
4356 // pair matches start
4357 bracket_pair_matching_end = Some(pair.clone());
4358 }
4359 }
4360 if let Some(end) = bracket_pair_matching_end
4361 && bracket_pair.is_none()
4362 {
4363 bracket_pair = Some(end);
4364 is_bracket_pair_end = true;
4365 }
4366 }
4367
4368 if let Some(bracket_pair) = bracket_pair {
4369 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4370 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4371 let auto_surround =
4372 self.use_auto_surround && snapshot_settings.use_auto_surround;
4373 if selection.is_empty() {
4374 if is_bracket_pair_start {
4375 // If the inserted text is a suffix of an opening bracket and the
4376 // selection is preceded by the rest of the opening bracket, then
4377 // insert the closing bracket.
4378 let following_text_allows_autoclose = snapshot
4379 .chars_at(selection.start)
4380 .next()
4381 .is_none_or(|c| scope.should_autoclose_before(c));
4382
4383 let preceding_text_allows_autoclose = selection.start.column == 0
4384 || snapshot
4385 .reversed_chars_at(selection.start)
4386 .next()
4387 .is_none_or(|c| {
4388 bracket_pair.start != bracket_pair.end
4389 || !snapshot
4390 .char_classifier_at(selection.start)
4391 .is_word(c)
4392 });
4393
4394 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4395 && bracket_pair.start.len() == 1
4396 {
4397 let target = bracket_pair.start.chars().next().unwrap();
4398 let mut byte_offset = 0u32;
4399 let current_line_count = snapshot
4400 .reversed_chars_at(selection.start)
4401 .take_while(|&c| c != '\n')
4402 .filter(|c| {
4403 byte_offset += c.len_utf8() as u32;
4404 if *c != target {
4405 return false;
4406 }
4407
4408 let point = Point::new(
4409 selection.start.row,
4410 selection.start.column.saturating_sub(byte_offset),
4411 );
4412
4413 let is_enabled = snapshot
4414 .language_scope_at(point)
4415 .and_then(|scope| {
4416 scope
4417 .brackets()
4418 .find(|(pair, _)| {
4419 pair.start == bracket_pair.start
4420 })
4421 .map(|(_, enabled)| enabled)
4422 })
4423 .unwrap_or(true);
4424
4425 let is_delimiter = snapshot
4426 .language_scope_at(Point::new(
4427 point.row,
4428 point.column + 1,
4429 ))
4430 .and_then(|scope| {
4431 scope
4432 .brackets()
4433 .find(|(pair, _)| {
4434 pair.start == bracket_pair.start
4435 })
4436 .map(|(_, enabled)| !enabled)
4437 })
4438 .unwrap_or(false);
4439
4440 is_enabled && !is_delimiter
4441 })
4442 .count();
4443 current_line_count % 2 == 1
4444 } else {
4445 false
4446 };
4447
4448 if autoclose
4449 && bracket_pair.close
4450 && following_text_allows_autoclose
4451 && preceding_text_allows_autoclose
4452 && !is_closing_quote
4453 {
4454 let anchor = snapshot.anchor_before(selection.end);
4455 new_selections.push((selection.map(|_| anchor), text.len()));
4456 new_autoclose_regions.push((
4457 anchor,
4458 text.len(),
4459 selection.id,
4460 bracket_pair.clone(),
4461 ));
4462 edits.push((
4463 selection.range(),
4464 format!("{}{}", text, bracket_pair.end).into(),
4465 ));
4466 bracket_inserted = true;
4467 continue;
4468 }
4469 }
4470
4471 if let Some(region) = autoclose_region {
4472 // If the selection is followed by an auto-inserted closing bracket,
4473 // then don't insert that closing bracket again; just move the selection
4474 // past the closing bracket.
4475 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4476 && text.as_ref() == region.pair.end.as_str()
4477 && snapshot.contains_str_at(region.range.end, text.as_ref());
4478 if should_skip {
4479 let anchor = snapshot.anchor_after(selection.end);
4480 new_selections
4481 .push((selection.map(|_| anchor), region.pair.end.len()));
4482 continue;
4483 }
4484 }
4485
4486 let always_treat_brackets_as_autoclosed = snapshot
4487 .language_settings_at(selection.start, cx)
4488 .always_treat_brackets_as_autoclosed;
4489 if always_treat_brackets_as_autoclosed
4490 && is_bracket_pair_end
4491 && snapshot.contains_str_at(selection.end, text.as_ref())
4492 {
4493 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4494 // and the inserted text is a closing bracket and the selection is followed
4495 // by the closing bracket then move the selection past the closing bracket.
4496 let anchor = snapshot.anchor_after(selection.end);
4497 new_selections.push((selection.map(|_| anchor), text.len()));
4498 continue;
4499 }
4500 }
4501 // If an opening bracket is 1 character long and is typed while
4502 // text is selected, then surround that text with the bracket pair.
4503 else if auto_surround
4504 && bracket_pair.surround
4505 && is_bracket_pair_start
4506 && bracket_pair.start.chars().count() == 1
4507 {
4508 edits.push((selection.start..selection.start, text.clone()));
4509 edits.push((
4510 selection.end..selection.end,
4511 bracket_pair.end.as_str().into(),
4512 ));
4513 bracket_inserted = true;
4514 new_selections.push((
4515 Selection {
4516 id: selection.id,
4517 start: snapshot.anchor_after(selection.start),
4518 end: snapshot.anchor_before(selection.end),
4519 reversed: selection.reversed,
4520 goal: selection.goal,
4521 },
4522 0,
4523 ));
4524 continue;
4525 }
4526 }
4527 }
4528
4529 if self.auto_replace_emoji_shortcode
4530 && selection.is_empty()
4531 && text.as_ref().ends_with(':')
4532 && let Some(possible_emoji_short_code) =
4533 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4534 && !possible_emoji_short_code.is_empty()
4535 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4536 {
4537 let emoji_shortcode_start = Point::new(
4538 selection.start.row,
4539 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4540 );
4541
4542 // Remove shortcode from buffer
4543 edits.push((
4544 emoji_shortcode_start..selection.start,
4545 "".to_string().into(),
4546 ));
4547 new_selections.push((
4548 Selection {
4549 id: selection.id,
4550 start: snapshot.anchor_after(emoji_shortcode_start),
4551 end: snapshot.anchor_before(selection.start),
4552 reversed: selection.reversed,
4553 goal: selection.goal,
4554 },
4555 0,
4556 ));
4557
4558 // Insert emoji
4559 let selection_start_anchor = snapshot.anchor_after(selection.start);
4560 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4561 edits.push((selection.start..selection.end, emoji.to_string().into()));
4562
4563 continue;
4564 }
4565
4566 // If not handling any auto-close operation, then just replace the selected
4567 // text with the given input and move the selection to the end of the
4568 // newly inserted text.
4569 let anchor = snapshot.anchor_after(selection.end);
4570 if !self.linked_edit_ranges.is_empty() {
4571 let start_anchor = snapshot.anchor_before(selection.start);
4572
4573 let is_word_char = text.chars().next().is_none_or(|char| {
4574 let classifier = snapshot
4575 .char_classifier_at(start_anchor.to_offset(&snapshot))
4576 .scope_context(Some(CharScopeContext::LinkedEdit));
4577 classifier.is_word(char)
4578 });
4579
4580 if is_word_char {
4581 if let Some(ranges) = self
4582 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4583 {
4584 for (buffer, edits) in ranges {
4585 linked_edits
4586 .entry(buffer.clone())
4587 .or_default()
4588 .extend(edits.into_iter().map(|range| (range, text.clone())));
4589 }
4590 }
4591 } else {
4592 clear_linked_edit_ranges = true;
4593 }
4594 }
4595
4596 new_selections.push((selection.map(|_| anchor), 0));
4597 edits.push((selection.start..selection.end, text.clone()));
4598 }
4599
4600 drop(snapshot);
4601
4602 self.transact(window, cx, |this, window, cx| {
4603 if clear_linked_edit_ranges {
4604 this.linked_edit_ranges.clear();
4605 }
4606 let initial_buffer_versions =
4607 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4608
4609 this.buffer.update(cx, |buffer, cx| {
4610 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4611 });
4612 for (buffer, edits) in linked_edits {
4613 buffer.update(cx, |buffer, cx| {
4614 let snapshot = buffer.snapshot();
4615 let edits = edits
4616 .into_iter()
4617 .map(|(range, text)| {
4618 use text::ToPoint as TP;
4619 let end_point = TP::to_point(&range.end, &snapshot);
4620 let start_point = TP::to_point(&range.start, &snapshot);
4621 (start_point..end_point, text)
4622 })
4623 .sorted_by_key(|(range, _)| range.start);
4624 buffer.edit(edits, None, cx);
4625 })
4626 }
4627 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4628 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4629 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4630 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4631 new_anchor_selections,
4632 &map,
4633 )
4634 .zip(new_selection_deltas)
4635 .map(|(selection, delta)| Selection {
4636 id: selection.id,
4637 start: selection.start + delta,
4638 end: selection.end + delta,
4639 reversed: selection.reversed,
4640 goal: SelectionGoal::None,
4641 })
4642 .collect::<Vec<_>>();
4643
4644 let mut i = 0;
4645 for (position, delta, selection_id, pair) in new_autoclose_regions {
4646 let position = position.to_offset(map.buffer_snapshot()) + delta;
4647 let start = map.buffer_snapshot().anchor_before(position);
4648 let end = map.buffer_snapshot().anchor_after(position);
4649 while let Some(existing_state) = this.autoclose_regions.get(i) {
4650 match existing_state
4651 .range
4652 .start
4653 .cmp(&start, map.buffer_snapshot())
4654 {
4655 Ordering::Less => i += 1,
4656 Ordering::Greater => break,
4657 Ordering::Equal => {
4658 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4659 Ordering::Less => i += 1,
4660 Ordering::Equal => break,
4661 Ordering::Greater => break,
4662 }
4663 }
4664 }
4665 }
4666 this.autoclose_regions.insert(
4667 i,
4668 AutocloseRegion {
4669 selection_id,
4670 range: start..end,
4671 pair,
4672 },
4673 );
4674 }
4675
4676 let had_active_edit_prediction = this.has_active_edit_prediction();
4677 this.change_selections(
4678 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4679 window,
4680 cx,
4681 |s| s.select(new_selections),
4682 );
4683
4684 if !bracket_inserted
4685 && let Some(on_type_format_task) =
4686 this.trigger_on_type_formatting(text.to_string(), window, cx)
4687 {
4688 on_type_format_task.detach_and_log_err(cx);
4689 }
4690
4691 let editor_settings = EditorSettings::get_global(cx);
4692 if bracket_inserted
4693 && (editor_settings.auto_signature_help
4694 || editor_settings.show_signature_help_after_edits)
4695 {
4696 this.show_signature_help(&ShowSignatureHelp, window, cx);
4697 }
4698
4699 let trigger_in_words =
4700 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4701 if this.hard_wrap.is_some() {
4702 let latest: Range<Point> = this.selections.newest(&map).range();
4703 if latest.is_empty()
4704 && this
4705 .buffer()
4706 .read(cx)
4707 .snapshot(cx)
4708 .line_len(MultiBufferRow(latest.start.row))
4709 == latest.start.column
4710 {
4711 this.rewrap_impl(
4712 RewrapOptions {
4713 override_language_settings: true,
4714 preserve_existing_whitespace: true,
4715 },
4716 cx,
4717 )
4718 }
4719 }
4720 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4721 refresh_linked_ranges(this, window, cx);
4722 this.refresh_edit_prediction(true, false, window, cx);
4723 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4724 });
4725 }
4726
4727 fn find_possible_emoji_shortcode_at_position(
4728 snapshot: &MultiBufferSnapshot,
4729 position: Point,
4730 ) -> Option<String> {
4731 let mut chars = Vec::new();
4732 let mut found_colon = false;
4733 for char in snapshot.reversed_chars_at(position).take(100) {
4734 // Found a possible emoji shortcode in the middle of the buffer
4735 if found_colon {
4736 if char.is_whitespace() {
4737 chars.reverse();
4738 return Some(chars.iter().collect());
4739 }
4740 // If the previous character is not a whitespace, we are in the middle of a word
4741 // and we only want to complete the shortcode if the word is made up of other emojis
4742 let mut containing_word = String::new();
4743 for ch in snapshot
4744 .reversed_chars_at(position)
4745 .skip(chars.len() + 1)
4746 .take(100)
4747 {
4748 if ch.is_whitespace() {
4749 break;
4750 }
4751 containing_word.push(ch);
4752 }
4753 let containing_word = containing_word.chars().rev().collect::<String>();
4754 if util::word_consists_of_emojis(containing_word.as_str()) {
4755 chars.reverse();
4756 return Some(chars.iter().collect());
4757 }
4758 }
4759
4760 if char.is_whitespace() || !char.is_ascii() {
4761 return None;
4762 }
4763 if char == ':' {
4764 found_colon = true;
4765 } else {
4766 chars.push(char);
4767 }
4768 }
4769 // Found a possible emoji shortcode at the beginning of the buffer
4770 chars.reverse();
4771 Some(chars.iter().collect())
4772 }
4773
4774 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4775 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4776 self.transact(window, cx, |this, window, cx| {
4777 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4778 let selections = this
4779 .selections
4780 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4781 let multi_buffer = this.buffer.read(cx);
4782 let buffer = multi_buffer.snapshot(cx);
4783 selections
4784 .iter()
4785 .map(|selection| {
4786 let start_point = selection.start.to_point(&buffer);
4787 let mut existing_indent =
4788 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4789 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4790 let start = selection.start;
4791 let end = selection.end;
4792 let selection_is_empty = start == end;
4793 let language_scope = buffer.language_scope_at(start);
4794 let (comment_delimiter, doc_delimiter, newline_formatting) =
4795 if let Some(language) = &language_scope {
4796 let mut newline_formatting =
4797 NewlineFormatting::new(&buffer, start..end, language);
4798
4799 // Comment extension on newline is allowed only for cursor selections
4800 let comment_delimiter = maybe!({
4801 if !selection_is_empty {
4802 return None;
4803 }
4804
4805 if !multi_buffer.language_settings(cx).extend_comment_on_newline
4806 {
4807 return None;
4808 }
4809
4810 return comment_delimiter_for_newline(
4811 &start_point,
4812 &buffer,
4813 language,
4814 );
4815 });
4816
4817 let doc_delimiter = maybe!({
4818 if !selection_is_empty {
4819 return None;
4820 }
4821
4822 if !multi_buffer.language_settings(cx).extend_comment_on_newline
4823 {
4824 return None;
4825 }
4826
4827 return documentation_delimiter_for_newline(
4828 &start_point,
4829 &buffer,
4830 language,
4831 &mut newline_formatting,
4832 );
4833 });
4834
4835 (comment_delimiter, doc_delimiter, newline_formatting)
4836 } else {
4837 (None, None, NewlineFormatting::default())
4838 };
4839
4840 let prevent_auto_indent = doc_delimiter.is_some();
4841 let delimiter = comment_delimiter.or(doc_delimiter);
4842
4843 let capacity_for_delimiter =
4844 delimiter.as_deref().map(str::len).unwrap_or_default();
4845 let mut new_text = String::with_capacity(
4846 1 + capacity_for_delimiter
4847 + existing_indent.len as usize
4848 + newline_formatting.indent_on_newline.len as usize
4849 + newline_formatting.indent_on_extra_newline.len as usize,
4850 );
4851 new_text.push('\n');
4852 new_text.extend(existing_indent.chars());
4853 new_text.extend(newline_formatting.indent_on_newline.chars());
4854
4855 if let Some(delimiter) = &delimiter {
4856 new_text.push_str(delimiter);
4857 }
4858
4859 if newline_formatting.insert_extra_newline {
4860 new_text.push('\n');
4861 new_text.extend(existing_indent.chars());
4862 new_text.extend(newline_formatting.indent_on_extra_newline.chars());
4863 }
4864
4865 let anchor = buffer.anchor_after(end);
4866 let new_selection = selection.map(|_| anchor);
4867 (
4868 ((start..end, new_text), prevent_auto_indent),
4869 (newline_formatting.insert_extra_newline, new_selection),
4870 )
4871 })
4872 .unzip()
4873 };
4874
4875 let mut auto_indent_edits = Vec::new();
4876 let mut edits = Vec::new();
4877 for (edit, prevent_auto_indent) in edits_with_flags {
4878 if prevent_auto_indent {
4879 edits.push(edit);
4880 } else {
4881 auto_indent_edits.push(edit);
4882 }
4883 }
4884 if !edits.is_empty() {
4885 this.edit(edits, cx);
4886 }
4887 if !auto_indent_edits.is_empty() {
4888 this.edit_with_autoindent(auto_indent_edits, cx);
4889 }
4890
4891 let buffer = this.buffer.read(cx).snapshot(cx);
4892 let new_selections = selection_info
4893 .into_iter()
4894 .map(|(extra_newline_inserted, new_selection)| {
4895 let mut cursor = new_selection.end.to_point(&buffer);
4896 if extra_newline_inserted {
4897 cursor.row -= 1;
4898 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4899 }
4900 new_selection.map(|_| cursor)
4901 })
4902 .collect();
4903
4904 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4905 this.refresh_edit_prediction(true, false, window, cx);
4906 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
4907 task.detach_and_log_err(cx);
4908 }
4909 });
4910 }
4911
4912 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4913 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4914
4915 let buffer = self.buffer.read(cx);
4916 let snapshot = buffer.snapshot(cx);
4917
4918 let mut edits = Vec::new();
4919 let mut rows = Vec::new();
4920
4921 for (rows_inserted, selection) in self
4922 .selections
4923 .all_adjusted(&self.display_snapshot(cx))
4924 .into_iter()
4925 .enumerate()
4926 {
4927 let cursor = selection.head();
4928 let row = cursor.row;
4929
4930 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4931
4932 let newline = "\n".to_string();
4933 edits.push((start_of_line..start_of_line, newline));
4934
4935 rows.push(row + rows_inserted as u32);
4936 }
4937
4938 self.transact(window, cx, |editor, window, cx| {
4939 editor.edit(edits, cx);
4940
4941 editor.change_selections(Default::default(), window, cx, |s| {
4942 let mut index = 0;
4943 s.move_cursors_with(|map, _, _| {
4944 let row = rows[index];
4945 index += 1;
4946
4947 let point = Point::new(row, 0);
4948 let boundary = map.next_line_boundary(point).1;
4949 let clipped = map.clip_point(boundary, Bias::Left);
4950
4951 (clipped, SelectionGoal::None)
4952 });
4953 });
4954
4955 let mut indent_edits = Vec::new();
4956 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4957 for row in rows {
4958 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4959 for (row, indent) in indents {
4960 if indent.len == 0 {
4961 continue;
4962 }
4963
4964 let text = match indent.kind {
4965 IndentKind::Space => " ".repeat(indent.len as usize),
4966 IndentKind::Tab => "\t".repeat(indent.len as usize),
4967 };
4968 let point = Point::new(row.0, 0);
4969 indent_edits.push((point..point, text));
4970 }
4971 }
4972 editor.edit(indent_edits, cx);
4973 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
4974 format.detach_and_log_err(cx);
4975 }
4976 });
4977 }
4978
4979 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4980 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4981
4982 let buffer = self.buffer.read(cx);
4983 let snapshot = buffer.snapshot(cx);
4984
4985 let mut edits = Vec::new();
4986 let mut rows = Vec::new();
4987 let mut rows_inserted = 0;
4988
4989 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4990 let cursor = selection.head();
4991 let row = cursor.row;
4992
4993 let point = Point::new(row + 1, 0);
4994 let start_of_line = snapshot.clip_point(point, Bias::Left);
4995
4996 let newline = "\n".to_string();
4997 edits.push((start_of_line..start_of_line, newline));
4998
4999 rows_inserted += 1;
5000 rows.push(row + rows_inserted);
5001 }
5002
5003 self.transact(window, cx, |editor, window, cx| {
5004 editor.edit(edits, cx);
5005
5006 editor.change_selections(Default::default(), window, cx, |s| {
5007 let mut index = 0;
5008 s.move_cursors_with(|map, _, _| {
5009 let row = rows[index];
5010 index += 1;
5011
5012 let point = Point::new(row, 0);
5013 let boundary = map.next_line_boundary(point).1;
5014 let clipped = map.clip_point(boundary, Bias::Left);
5015
5016 (clipped, SelectionGoal::None)
5017 });
5018 });
5019
5020 let mut indent_edits = Vec::new();
5021 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5022 for row in rows {
5023 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5024 for (row, indent) in indents {
5025 if indent.len == 0 {
5026 continue;
5027 }
5028
5029 let text = match indent.kind {
5030 IndentKind::Space => " ".repeat(indent.len as usize),
5031 IndentKind::Tab => "\t".repeat(indent.len as usize),
5032 };
5033 let point = Point::new(row.0, 0);
5034 indent_edits.push((point..point, text));
5035 }
5036 }
5037 editor.edit(indent_edits, cx);
5038 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5039 format.detach_and_log_err(cx);
5040 }
5041 });
5042 }
5043
5044 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5045 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5046 original_indent_columns: Vec::new(),
5047 });
5048 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5049 }
5050
5051 fn insert_with_autoindent_mode(
5052 &mut self,
5053 text: &str,
5054 autoindent_mode: Option<AutoindentMode>,
5055 window: &mut Window,
5056 cx: &mut Context<Self>,
5057 ) {
5058 if self.read_only(cx) {
5059 return;
5060 }
5061
5062 let text: Arc<str> = text.into();
5063 self.transact(window, cx, |this, window, cx| {
5064 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5065 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5066 let anchors = {
5067 let snapshot = buffer.read(cx);
5068 old_selections
5069 .iter()
5070 .map(|s| {
5071 let anchor = snapshot.anchor_after(s.head());
5072 s.map(|_| anchor)
5073 })
5074 .collect::<Vec<_>>()
5075 };
5076 buffer.edit(
5077 old_selections
5078 .iter()
5079 .map(|s| (s.start..s.end, text.clone())),
5080 autoindent_mode,
5081 cx,
5082 );
5083 anchors
5084 });
5085
5086 this.change_selections(Default::default(), window, cx, |s| {
5087 s.select_anchors(selection_anchors);
5088 });
5089
5090 cx.notify();
5091 });
5092 }
5093
5094 fn trigger_completion_on_input(
5095 &mut self,
5096 text: &str,
5097 trigger_in_words: bool,
5098 window: &mut Window,
5099 cx: &mut Context<Self>,
5100 ) {
5101 let completions_source = self
5102 .context_menu
5103 .borrow()
5104 .as_ref()
5105 .and_then(|menu| match menu {
5106 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5107 CodeContextMenu::CodeActions(_) => None,
5108 });
5109
5110 match completions_source {
5111 Some(CompletionsMenuSource::Words { .. }) => {
5112 self.open_or_update_completions_menu(
5113 Some(CompletionsMenuSource::Words {
5114 ignore_threshold: false,
5115 }),
5116 None,
5117 trigger_in_words,
5118 window,
5119 cx,
5120 );
5121 }
5122 _ => self.open_or_update_completions_menu(
5123 None,
5124 Some(text.to_owned()).filter(|x| !x.is_empty()),
5125 true,
5126 window,
5127 cx,
5128 ),
5129 }
5130 }
5131
5132 /// If any empty selections is touching the start of its innermost containing autoclose
5133 /// region, expand it to select the brackets.
5134 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5135 let selections = self
5136 .selections
5137 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5138 let buffer = self.buffer.read(cx).read(cx);
5139 let new_selections = self
5140 .selections_with_autoclose_regions(selections, &buffer)
5141 .map(|(mut selection, region)| {
5142 if !selection.is_empty() {
5143 return selection;
5144 }
5145
5146 if let Some(region) = region {
5147 let mut range = region.range.to_offset(&buffer);
5148 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5149 range.start -= region.pair.start.len();
5150 if buffer.contains_str_at(range.start, ®ion.pair.start)
5151 && buffer.contains_str_at(range.end, ®ion.pair.end)
5152 {
5153 range.end += region.pair.end.len();
5154 selection.start = range.start;
5155 selection.end = range.end;
5156
5157 return selection;
5158 }
5159 }
5160 }
5161
5162 let always_treat_brackets_as_autoclosed = buffer
5163 .language_settings_at(selection.start, cx)
5164 .always_treat_brackets_as_autoclosed;
5165
5166 if !always_treat_brackets_as_autoclosed {
5167 return selection;
5168 }
5169
5170 if let Some(scope) = buffer.language_scope_at(selection.start) {
5171 for (pair, enabled) in scope.brackets() {
5172 if !enabled || !pair.close {
5173 continue;
5174 }
5175
5176 if buffer.contains_str_at(selection.start, &pair.end) {
5177 let pair_start_len = pair.start.len();
5178 if buffer.contains_str_at(
5179 selection.start.saturating_sub_usize(pair_start_len),
5180 &pair.start,
5181 ) {
5182 selection.start -= pair_start_len;
5183 selection.end += pair.end.len();
5184
5185 return selection;
5186 }
5187 }
5188 }
5189 }
5190
5191 selection
5192 })
5193 .collect();
5194
5195 drop(buffer);
5196 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5197 selections.select(new_selections)
5198 });
5199 }
5200
5201 /// Iterate the given selections, and for each one, find the smallest surrounding
5202 /// autoclose region. This uses the ordering of the selections and the autoclose
5203 /// regions to avoid repeated comparisons.
5204 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5205 &'a self,
5206 selections: impl IntoIterator<Item = Selection<D>>,
5207 buffer: &'a MultiBufferSnapshot,
5208 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5209 let mut i = 0;
5210 let mut regions = self.autoclose_regions.as_slice();
5211 selections.into_iter().map(move |selection| {
5212 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5213
5214 let mut enclosing = None;
5215 while let Some(pair_state) = regions.get(i) {
5216 if pair_state.range.end.to_offset(buffer) < range.start {
5217 regions = ®ions[i + 1..];
5218 i = 0;
5219 } else if pair_state.range.start.to_offset(buffer) > range.end {
5220 break;
5221 } else {
5222 if pair_state.selection_id == selection.id {
5223 enclosing = Some(pair_state);
5224 }
5225 i += 1;
5226 }
5227 }
5228
5229 (selection, enclosing)
5230 })
5231 }
5232
5233 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5234 fn invalidate_autoclose_regions(
5235 &mut self,
5236 mut selections: &[Selection<Anchor>],
5237 buffer: &MultiBufferSnapshot,
5238 ) {
5239 self.autoclose_regions.retain(|state| {
5240 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5241 return false;
5242 }
5243
5244 let mut i = 0;
5245 while let Some(selection) = selections.get(i) {
5246 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5247 selections = &selections[1..];
5248 continue;
5249 }
5250 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5251 break;
5252 }
5253 if selection.id == state.selection_id {
5254 return true;
5255 } else {
5256 i += 1;
5257 }
5258 }
5259 false
5260 });
5261 }
5262
5263 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5264 let offset = position.to_offset(buffer);
5265 let (word_range, kind) =
5266 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5267 if offset > word_range.start && kind == Some(CharKind::Word) {
5268 Some(
5269 buffer
5270 .text_for_range(word_range.start..offset)
5271 .collect::<String>(),
5272 )
5273 } else {
5274 None
5275 }
5276 }
5277
5278 pub fn visible_excerpts(
5279 &self,
5280 lsp_related_only: bool,
5281 cx: &mut Context<Editor>,
5282 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5283 let project = self.project().cloned();
5284 let multi_buffer = self.buffer().read(cx);
5285 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5286 let multi_buffer_visible_start = self
5287 .scroll_manager
5288 .anchor()
5289 .anchor
5290 .to_point(&multi_buffer_snapshot);
5291 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5292 multi_buffer_visible_start
5293 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5294 Bias::Left,
5295 );
5296 multi_buffer_snapshot
5297 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5298 .into_iter()
5299 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5300 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5301 if !lsp_related_only {
5302 return Some((
5303 excerpt_id,
5304 (
5305 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5306 buffer.version().clone(),
5307 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5308 ),
5309 ));
5310 }
5311
5312 let project = project.as_ref()?.read(cx);
5313 let buffer_file = project::File::from_dyn(buffer.file())?;
5314 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5315 let worktree_entry = buffer_worktree
5316 .read(cx)
5317 .entry_for_id(buffer_file.project_entry_id()?)?;
5318 if worktree_entry.is_ignored {
5319 None
5320 } else {
5321 Some((
5322 excerpt_id,
5323 (
5324 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5325 buffer.version().clone(),
5326 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5327 ),
5328 ))
5329 }
5330 })
5331 .collect()
5332 }
5333
5334 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5335 TextLayoutDetails {
5336 text_system: window.text_system().clone(),
5337 editor_style: self.style.clone().unwrap(),
5338 rem_size: window.rem_size(),
5339 scroll_anchor: self.scroll_manager.anchor(),
5340 visible_rows: self.visible_line_count(),
5341 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5342 }
5343 }
5344
5345 fn trigger_on_type_formatting(
5346 &self,
5347 input: String,
5348 window: &mut Window,
5349 cx: &mut Context<Self>,
5350 ) -> Option<Task<Result<()>>> {
5351 if input.chars().count() != 1 {
5352 return None;
5353 }
5354
5355 let project = self.project()?;
5356 let position = self.selections.newest_anchor().head();
5357 let (buffer, buffer_position) = self
5358 .buffer
5359 .read(cx)
5360 .text_anchor_for_position(position, cx)?;
5361
5362 let settings = language_settings::language_settings(
5363 buffer
5364 .read(cx)
5365 .language_at(buffer_position)
5366 .map(|l| l.name()),
5367 buffer.read(cx).file(),
5368 cx,
5369 );
5370 if !settings.use_on_type_format {
5371 return None;
5372 }
5373
5374 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5375 // hence we do LSP request & edit on host side only — add formats to host's history.
5376 let push_to_lsp_host_history = true;
5377 // If this is not the host, append its history with new edits.
5378 let push_to_client_history = project.read(cx).is_via_collab();
5379
5380 let on_type_formatting = project.update(cx, |project, cx| {
5381 project.on_type_format(
5382 buffer.clone(),
5383 buffer_position,
5384 input,
5385 push_to_lsp_host_history,
5386 cx,
5387 )
5388 });
5389 Some(cx.spawn_in(window, async move |editor, cx| {
5390 if let Some(transaction) = on_type_formatting.await? {
5391 if push_to_client_history {
5392 buffer
5393 .update(cx, |buffer, _| {
5394 buffer.push_transaction(transaction, Instant::now());
5395 buffer.finalize_last_transaction();
5396 })
5397 .ok();
5398 }
5399 editor.update(cx, |editor, cx| {
5400 editor.refresh_document_highlights(cx);
5401 })?;
5402 }
5403 Ok(())
5404 }))
5405 }
5406
5407 pub fn show_word_completions(
5408 &mut self,
5409 _: &ShowWordCompletions,
5410 window: &mut Window,
5411 cx: &mut Context<Self>,
5412 ) {
5413 self.open_or_update_completions_menu(
5414 Some(CompletionsMenuSource::Words {
5415 ignore_threshold: true,
5416 }),
5417 None,
5418 false,
5419 window,
5420 cx,
5421 );
5422 }
5423
5424 pub fn show_completions(
5425 &mut self,
5426 _: &ShowCompletions,
5427 window: &mut Window,
5428 cx: &mut Context<Self>,
5429 ) {
5430 self.open_or_update_completions_menu(None, None, false, window, cx);
5431 }
5432
5433 fn open_or_update_completions_menu(
5434 &mut self,
5435 requested_source: Option<CompletionsMenuSource>,
5436 trigger: Option<String>,
5437 trigger_in_words: bool,
5438 window: &mut Window,
5439 cx: &mut Context<Self>,
5440 ) {
5441 if self.pending_rename.is_some() {
5442 return;
5443 }
5444
5445 let completions_source = self
5446 .context_menu
5447 .borrow()
5448 .as_ref()
5449 .and_then(|menu| match menu {
5450 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5451 CodeContextMenu::CodeActions(_) => None,
5452 });
5453
5454 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5455
5456 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5457 // inserted and selected. To handle that case, the start of the selection is used so that
5458 // the menu starts with all choices.
5459 let position = self
5460 .selections
5461 .newest_anchor()
5462 .start
5463 .bias_right(&multibuffer_snapshot);
5464 if position.diff_base_anchor.is_some() {
5465 return;
5466 }
5467 let buffer_position = multibuffer_snapshot.anchor_before(position);
5468 let Some(buffer) = buffer_position
5469 .text_anchor
5470 .buffer_id
5471 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5472 else {
5473 return;
5474 };
5475 let buffer_snapshot = buffer.read(cx).snapshot();
5476
5477 let menu_is_open = matches!(
5478 self.context_menu.borrow().as_ref(),
5479 Some(CodeContextMenu::Completions(_))
5480 );
5481
5482 let language = buffer_snapshot
5483 .language_at(buffer_position.text_anchor)
5484 .map(|language| language.name());
5485
5486 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5487 let completion_settings = language_settings.completions.clone();
5488
5489 let show_completions_on_input = self
5490 .show_completions_on_input_override
5491 .unwrap_or(language_settings.show_completions_on_input);
5492 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5493 return;
5494 }
5495
5496 let query: Option<Arc<String>> =
5497 Self::completion_query(&multibuffer_snapshot, buffer_position)
5498 .map(|query| query.into());
5499
5500 drop(multibuffer_snapshot);
5501
5502 // Hide the current completions menu when query is empty. Without this, cached
5503 // completions from before the trigger char may be reused (#32774).
5504 if query.is_none() && menu_is_open {
5505 self.hide_context_menu(window, cx);
5506 }
5507
5508 let mut ignore_word_threshold = false;
5509 let provider = match requested_source {
5510 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5511 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5512 ignore_word_threshold = ignore_threshold;
5513 None
5514 }
5515 Some(CompletionsMenuSource::SnippetChoices)
5516 | Some(CompletionsMenuSource::SnippetsOnly) => {
5517 log::error!("bug: SnippetChoices requested_source is not handled");
5518 None
5519 }
5520 };
5521
5522 let sort_completions = provider
5523 .as_ref()
5524 .is_some_and(|provider| provider.sort_completions());
5525
5526 let filter_completions = provider
5527 .as_ref()
5528 .is_none_or(|provider| provider.filter_completions());
5529
5530 let was_snippets_only = matches!(
5531 completions_source,
5532 Some(CompletionsMenuSource::SnippetsOnly)
5533 );
5534
5535 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5536 if filter_completions {
5537 menu.filter(
5538 query.clone().unwrap_or_default(),
5539 buffer_position.text_anchor,
5540 &buffer,
5541 provider.clone(),
5542 window,
5543 cx,
5544 );
5545 }
5546 // When `is_incomplete` is false, no need to re-query completions when the current query
5547 // is a suffix of the initial query.
5548 let was_complete = !menu.is_incomplete;
5549 if was_complete && !was_snippets_only {
5550 // If the new query is a suffix of the old query (typing more characters) and
5551 // the previous result was complete, the existing completions can be filtered.
5552 //
5553 // Note that snippet completions are always complete.
5554 let query_matches = match (&menu.initial_query, &query) {
5555 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5556 (None, _) => true,
5557 _ => false,
5558 };
5559 if query_matches {
5560 let position_matches = if menu.initial_position == position {
5561 true
5562 } else {
5563 let snapshot = self.buffer.read(cx).read(cx);
5564 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5565 };
5566 if position_matches {
5567 return;
5568 }
5569 }
5570 }
5571 };
5572
5573 let Anchor {
5574 excerpt_id: buffer_excerpt_id,
5575 text_anchor: buffer_position,
5576 ..
5577 } = buffer_position;
5578
5579 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5580 buffer_snapshot.surrounding_word(buffer_position, None)
5581 {
5582 let word_to_exclude = buffer_snapshot
5583 .text_for_range(word_range.clone())
5584 .collect::<String>();
5585 (
5586 buffer_snapshot.anchor_before(word_range.start)
5587 ..buffer_snapshot.anchor_after(buffer_position),
5588 Some(word_to_exclude),
5589 )
5590 } else {
5591 (buffer_position..buffer_position, None)
5592 };
5593
5594 let show_completion_documentation = buffer_snapshot
5595 .settings_at(buffer_position, cx)
5596 .show_completion_documentation;
5597
5598 // The document can be large, so stay in reasonable bounds when searching for words,
5599 // otherwise completion pop-up might be slow to appear.
5600 const WORD_LOOKUP_ROWS: u32 = 5_000;
5601 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5602 let min_word_search = buffer_snapshot.clip_point(
5603 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5604 Bias::Left,
5605 );
5606 let max_word_search = buffer_snapshot.clip_point(
5607 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5608 Bias::Right,
5609 );
5610 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5611 ..buffer_snapshot.point_to_offset(max_word_search);
5612
5613 let skip_digits = query
5614 .as_ref()
5615 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5616
5617 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5618 trigger.as_ref().is_none_or(|trigger| {
5619 provider.is_completion_trigger(
5620 &buffer,
5621 position.text_anchor,
5622 trigger,
5623 trigger_in_words,
5624 cx,
5625 )
5626 })
5627 });
5628
5629 let provider_responses = if let Some(provider) = &provider
5630 && load_provider_completions
5631 {
5632 let trigger_character =
5633 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5634 let completion_context = CompletionContext {
5635 trigger_kind: match &trigger_character {
5636 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5637 None => CompletionTriggerKind::INVOKED,
5638 },
5639 trigger_character,
5640 };
5641
5642 provider.completions(
5643 buffer_excerpt_id,
5644 &buffer,
5645 buffer_position,
5646 completion_context,
5647 window,
5648 cx,
5649 )
5650 } else {
5651 Task::ready(Ok(Vec::new()))
5652 };
5653
5654 let load_word_completions = if !self.word_completions_enabled {
5655 false
5656 } else if requested_source
5657 == Some(CompletionsMenuSource::Words {
5658 ignore_threshold: true,
5659 })
5660 {
5661 true
5662 } else {
5663 load_provider_completions
5664 && completion_settings.words != WordsCompletionMode::Disabled
5665 && (ignore_word_threshold || {
5666 let words_min_length = completion_settings.words_min_length;
5667 // check whether word has at least `words_min_length` characters
5668 let query_chars = query.iter().flat_map(|q| q.chars());
5669 query_chars.take(words_min_length).count() == words_min_length
5670 })
5671 };
5672
5673 let mut words = if load_word_completions {
5674 cx.background_spawn({
5675 let buffer_snapshot = buffer_snapshot.clone();
5676 async move {
5677 buffer_snapshot.words_in_range(WordsQuery {
5678 fuzzy_contents: None,
5679 range: word_search_range,
5680 skip_digits,
5681 })
5682 }
5683 })
5684 } else {
5685 Task::ready(BTreeMap::default())
5686 };
5687
5688 let snippets = if let Some(provider) = &provider
5689 && provider.show_snippets()
5690 && let Some(project) = self.project()
5691 {
5692 let char_classifier = buffer_snapshot
5693 .char_classifier_at(buffer_position)
5694 .scope_context(Some(CharScopeContext::Completion));
5695 project.update(cx, |project, cx| {
5696 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5697 })
5698 } else {
5699 Task::ready(Ok(CompletionResponse {
5700 completions: Vec::new(),
5701 display_options: Default::default(),
5702 is_incomplete: false,
5703 }))
5704 };
5705
5706 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5707
5708 let id = post_inc(&mut self.next_completion_id);
5709 let task = cx.spawn_in(window, async move |editor, cx| {
5710 let Ok(()) = editor.update(cx, |this, _| {
5711 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5712 }) else {
5713 return;
5714 };
5715
5716 // TODO: Ideally completions from different sources would be selectively re-queried, so
5717 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5718 let mut completions = Vec::new();
5719 let mut is_incomplete = false;
5720 let mut display_options: Option<CompletionDisplayOptions> = None;
5721 if let Some(provider_responses) = provider_responses.await.log_err()
5722 && !provider_responses.is_empty()
5723 {
5724 for response in provider_responses {
5725 completions.extend(response.completions);
5726 is_incomplete = is_incomplete || response.is_incomplete;
5727 match display_options.as_mut() {
5728 None => {
5729 display_options = Some(response.display_options);
5730 }
5731 Some(options) => options.merge(&response.display_options),
5732 }
5733 }
5734 if completion_settings.words == WordsCompletionMode::Fallback {
5735 words = Task::ready(BTreeMap::default());
5736 }
5737 }
5738 let display_options = display_options.unwrap_or_default();
5739
5740 let mut words = words.await;
5741 if let Some(word_to_exclude) = &word_to_exclude {
5742 words.remove(word_to_exclude);
5743 }
5744 for lsp_completion in &completions {
5745 words.remove(&lsp_completion.new_text);
5746 }
5747 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5748 replace_range: word_replace_range.clone(),
5749 new_text: word.clone(),
5750 label: CodeLabel::plain(word, None),
5751 match_start: None,
5752 snippet_deduplication_key: None,
5753 icon_path: None,
5754 documentation: None,
5755 source: CompletionSource::BufferWord {
5756 word_range,
5757 resolved: false,
5758 },
5759 insert_text_mode: Some(InsertTextMode::AS_IS),
5760 confirm: None,
5761 }));
5762
5763 completions.extend(
5764 snippets
5765 .await
5766 .into_iter()
5767 .flat_map(|response| response.completions),
5768 );
5769
5770 let menu = if completions.is_empty() {
5771 None
5772 } else {
5773 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5774 let languages = editor
5775 .workspace
5776 .as_ref()
5777 .and_then(|(workspace, _)| workspace.upgrade())
5778 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5779 let menu = CompletionsMenu::new(
5780 id,
5781 requested_source.unwrap_or(if load_provider_completions {
5782 CompletionsMenuSource::Normal
5783 } else {
5784 CompletionsMenuSource::SnippetsOnly
5785 }),
5786 sort_completions,
5787 show_completion_documentation,
5788 position,
5789 query.clone(),
5790 is_incomplete,
5791 buffer.clone(),
5792 completions.into(),
5793 editor
5794 .context_menu()
5795 .borrow_mut()
5796 .as_ref()
5797 .map(|menu| menu.primary_scroll_handle()),
5798 display_options,
5799 snippet_sort_order,
5800 languages,
5801 language,
5802 cx,
5803 );
5804
5805 let query = if filter_completions { query } else { None };
5806 let matches_task = menu.do_async_filtering(
5807 query.unwrap_or_default(),
5808 buffer_position,
5809 &buffer,
5810 cx,
5811 );
5812 (menu, matches_task)
5813 }) else {
5814 return;
5815 };
5816
5817 let matches = matches_task.await;
5818
5819 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5820 // Newer menu already set, so exit.
5821 if let Some(CodeContextMenu::Completions(prev_menu)) =
5822 editor.context_menu.borrow().as_ref()
5823 && prev_menu.id > id
5824 {
5825 return;
5826 };
5827
5828 // Only valid to take prev_menu because either the new menu is immediately set
5829 // below, or the menu is hidden.
5830 if let Some(CodeContextMenu::Completions(prev_menu)) =
5831 editor.context_menu.borrow_mut().take()
5832 {
5833 let position_matches =
5834 if prev_menu.initial_position == menu.initial_position {
5835 true
5836 } else {
5837 let snapshot = editor.buffer.read(cx).read(cx);
5838 prev_menu.initial_position.to_offset(&snapshot)
5839 == menu.initial_position.to_offset(&snapshot)
5840 };
5841 if position_matches {
5842 // Preserve markdown cache before `set_filter_results` because it will
5843 // try to populate the documentation cache.
5844 menu.preserve_markdown_cache(prev_menu);
5845 }
5846 };
5847
5848 menu.set_filter_results(matches, provider, window, cx);
5849 }) else {
5850 return;
5851 };
5852
5853 menu.visible().then_some(menu)
5854 };
5855
5856 editor
5857 .update_in(cx, |editor, window, cx| {
5858 if editor.focus_handle.is_focused(window)
5859 && let Some(menu) = menu
5860 {
5861 *editor.context_menu.borrow_mut() =
5862 Some(CodeContextMenu::Completions(menu));
5863
5864 crate::hover_popover::hide_hover(editor, cx);
5865 if editor.show_edit_predictions_in_menu() {
5866 editor.update_visible_edit_prediction(window, cx);
5867 } else {
5868 editor.discard_edit_prediction(false, cx);
5869 }
5870
5871 cx.notify();
5872 return;
5873 }
5874
5875 if editor.completion_tasks.len() <= 1 {
5876 // If there are no more completion tasks and the last menu was empty, we should hide it.
5877 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5878 // If it was already hidden and we don't show edit predictions in the menu,
5879 // we should also show the edit prediction when available.
5880 if was_hidden && editor.show_edit_predictions_in_menu() {
5881 editor.update_visible_edit_prediction(window, cx);
5882 }
5883 }
5884 })
5885 .ok();
5886 });
5887
5888 self.completion_tasks.push((id, task));
5889 }
5890
5891 #[cfg(feature = "test-support")]
5892 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5893 let menu = self.context_menu.borrow();
5894 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5895 let completions = menu.completions.borrow();
5896 Some(completions.to_vec())
5897 } else {
5898 None
5899 }
5900 }
5901
5902 pub fn with_completions_menu_matching_id<R>(
5903 &self,
5904 id: CompletionId,
5905 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5906 ) -> R {
5907 let mut context_menu = self.context_menu.borrow_mut();
5908 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5909 return f(None);
5910 };
5911 if completions_menu.id != id {
5912 return f(None);
5913 }
5914 f(Some(completions_menu))
5915 }
5916
5917 pub fn confirm_completion(
5918 &mut self,
5919 action: &ConfirmCompletion,
5920 window: &mut Window,
5921 cx: &mut Context<Self>,
5922 ) -> Option<Task<Result<()>>> {
5923 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5924 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5925 }
5926
5927 pub fn confirm_completion_insert(
5928 &mut self,
5929 _: &ConfirmCompletionInsert,
5930 window: &mut Window,
5931 cx: &mut Context<Self>,
5932 ) -> Option<Task<Result<()>>> {
5933 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5934 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5935 }
5936
5937 pub fn confirm_completion_replace(
5938 &mut self,
5939 _: &ConfirmCompletionReplace,
5940 window: &mut Window,
5941 cx: &mut Context<Self>,
5942 ) -> Option<Task<Result<()>>> {
5943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5944 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5945 }
5946
5947 pub fn compose_completion(
5948 &mut self,
5949 action: &ComposeCompletion,
5950 window: &mut Window,
5951 cx: &mut Context<Self>,
5952 ) -> Option<Task<Result<()>>> {
5953 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5954 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5955 }
5956
5957 fn do_completion(
5958 &mut self,
5959 item_ix: Option<usize>,
5960 intent: CompletionIntent,
5961 window: &mut Window,
5962 cx: &mut Context<Editor>,
5963 ) -> Option<Task<Result<()>>> {
5964 use language::ToOffset as _;
5965
5966 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5967 else {
5968 return None;
5969 };
5970
5971 let candidate_id = {
5972 let entries = completions_menu.entries.borrow();
5973 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5974 if self.show_edit_predictions_in_menu() {
5975 self.discard_edit_prediction(true, cx);
5976 }
5977 mat.candidate_id
5978 };
5979
5980 let completion = completions_menu
5981 .completions
5982 .borrow()
5983 .get(candidate_id)?
5984 .clone();
5985 cx.stop_propagation();
5986
5987 let buffer_handle = completions_menu.buffer.clone();
5988
5989 let CompletionEdit {
5990 new_text,
5991 snippet,
5992 replace_range,
5993 } = process_completion_for_edit(
5994 &completion,
5995 intent,
5996 &buffer_handle,
5997 &completions_menu.initial_position.text_anchor,
5998 cx,
5999 );
6000
6001 let buffer = buffer_handle.read(cx);
6002 let snapshot = self.buffer.read(cx).snapshot(cx);
6003 let newest_anchor = self.selections.newest_anchor();
6004 let replace_range_multibuffer = {
6005 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6006 excerpt.map_range_from_buffer(replace_range.clone())
6007 };
6008 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6009 return None;
6010 }
6011
6012 let old_text = buffer
6013 .text_for_range(replace_range.clone())
6014 .collect::<String>();
6015 let lookbehind = newest_anchor
6016 .start
6017 .text_anchor
6018 .to_offset(buffer)
6019 .saturating_sub(replace_range.start.0);
6020 let lookahead = replace_range
6021 .end
6022 .0
6023 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6024 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6025 let suffix = &old_text[lookbehind.min(old_text.len())..];
6026
6027 let selections = self
6028 .selections
6029 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6030 let mut ranges = Vec::new();
6031 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6032
6033 for selection in &selections {
6034 let range = if selection.id == newest_anchor.id {
6035 replace_range_multibuffer.clone()
6036 } else {
6037 let mut range = selection.range();
6038
6039 // if prefix is present, don't duplicate it
6040 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6041 range.start = range.start.saturating_sub_usize(lookbehind);
6042
6043 // if suffix is also present, mimic the newest cursor and replace it
6044 if selection.id != newest_anchor.id
6045 && snapshot.contains_str_at(range.end, suffix)
6046 {
6047 range.end += lookahead;
6048 }
6049 }
6050 range
6051 };
6052
6053 ranges.push(range.clone());
6054
6055 if !self.linked_edit_ranges.is_empty() {
6056 let start_anchor = snapshot.anchor_before(range.start);
6057 let end_anchor = snapshot.anchor_after(range.end);
6058 if let Some(ranges) = self
6059 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6060 {
6061 for (buffer, edits) in ranges {
6062 linked_edits
6063 .entry(buffer.clone())
6064 .or_default()
6065 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6066 }
6067 }
6068 }
6069 }
6070
6071 let common_prefix_len = old_text
6072 .chars()
6073 .zip(new_text.chars())
6074 .take_while(|(a, b)| a == b)
6075 .map(|(a, _)| a.len_utf8())
6076 .sum::<usize>();
6077
6078 cx.emit(EditorEvent::InputHandled {
6079 utf16_range_to_replace: None,
6080 text: new_text[common_prefix_len..].into(),
6081 });
6082
6083 self.transact(window, cx, |editor, window, cx| {
6084 if let Some(mut snippet) = snippet {
6085 snippet.text = new_text.to_string();
6086 editor
6087 .insert_snippet(&ranges, snippet, window, cx)
6088 .log_err();
6089 } else {
6090 editor.buffer.update(cx, |multi_buffer, cx| {
6091 let auto_indent = match completion.insert_text_mode {
6092 Some(InsertTextMode::AS_IS) => None,
6093 _ => editor.autoindent_mode.clone(),
6094 };
6095 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6096 multi_buffer.edit(edits, auto_indent, cx);
6097 });
6098 }
6099 for (buffer, edits) in linked_edits {
6100 buffer.update(cx, |buffer, cx| {
6101 let snapshot = buffer.snapshot();
6102 let edits = edits
6103 .into_iter()
6104 .map(|(range, text)| {
6105 use text::ToPoint as TP;
6106 let end_point = TP::to_point(&range.end, &snapshot);
6107 let start_point = TP::to_point(&range.start, &snapshot);
6108 (start_point..end_point, text)
6109 })
6110 .sorted_by_key(|(range, _)| range.start);
6111 buffer.edit(edits, None, cx);
6112 })
6113 }
6114
6115 editor.refresh_edit_prediction(true, false, window, cx);
6116 });
6117 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6118
6119 let show_new_completions_on_confirm = completion
6120 .confirm
6121 .as_ref()
6122 .is_some_and(|confirm| confirm(intent, window, cx));
6123 if show_new_completions_on_confirm {
6124 self.open_or_update_completions_menu(None, None, false, window, cx);
6125 }
6126
6127 let provider = self.completion_provider.as_ref()?;
6128
6129 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6130 let command = lsp_store.as_ref().and_then(|lsp_store| {
6131 let CompletionSource::Lsp {
6132 lsp_completion,
6133 server_id,
6134 ..
6135 } = &completion.source
6136 else {
6137 return None;
6138 };
6139 let lsp_command = lsp_completion.command.as_ref()?;
6140 let available_commands = lsp_store
6141 .read(cx)
6142 .lsp_server_capabilities
6143 .get(server_id)
6144 .and_then(|server_capabilities| {
6145 server_capabilities
6146 .execute_command_provider
6147 .as_ref()
6148 .map(|options| options.commands.as_slice())
6149 })?;
6150 if available_commands.contains(&lsp_command.command) {
6151 Some(CodeAction {
6152 server_id: *server_id,
6153 range: language::Anchor::MIN..language::Anchor::MIN,
6154 lsp_action: LspAction::Command(lsp_command.clone()),
6155 resolved: false,
6156 })
6157 } else {
6158 None
6159 }
6160 });
6161
6162 drop(completion);
6163 let apply_edits = provider.apply_additional_edits_for_completion(
6164 buffer_handle.clone(),
6165 completions_menu.completions.clone(),
6166 candidate_id,
6167 true,
6168 cx,
6169 );
6170
6171 let editor_settings = EditorSettings::get_global(cx);
6172 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6173 // After the code completion is finished, users often want to know what signatures are needed.
6174 // so we should automatically call signature_help
6175 self.show_signature_help(&ShowSignatureHelp, window, cx);
6176 }
6177
6178 Some(cx.spawn_in(window, async move |editor, cx| {
6179 apply_edits.await?;
6180
6181 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6182 let title = command.lsp_action.title().to_owned();
6183 let project_transaction = lsp_store
6184 .update(cx, |lsp_store, cx| {
6185 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6186 })?
6187 .await
6188 .context("applying post-completion command")?;
6189 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6190 Self::open_project_transaction(
6191 &editor,
6192 workspace.downgrade(),
6193 project_transaction,
6194 title,
6195 cx,
6196 )
6197 .await?;
6198 }
6199 }
6200
6201 Ok(())
6202 }))
6203 }
6204
6205 pub fn toggle_code_actions(
6206 &mut self,
6207 action: &ToggleCodeActions,
6208 window: &mut Window,
6209 cx: &mut Context<Self>,
6210 ) {
6211 let quick_launch = action.quick_launch;
6212 let mut context_menu = self.context_menu.borrow_mut();
6213 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6214 if code_actions.deployed_from == action.deployed_from {
6215 // Toggle if we're selecting the same one
6216 *context_menu = None;
6217 cx.notify();
6218 return;
6219 } else {
6220 // Otherwise, clear it and start a new one
6221 *context_menu = None;
6222 cx.notify();
6223 }
6224 }
6225 drop(context_menu);
6226 let snapshot = self.snapshot(window, cx);
6227 let deployed_from = action.deployed_from.clone();
6228 let action = action.clone();
6229 self.completion_tasks.clear();
6230 self.discard_edit_prediction(false, cx);
6231
6232 let multibuffer_point = match &action.deployed_from {
6233 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6234 DisplayPoint::new(*row, 0).to_point(&snapshot)
6235 }
6236 _ => self
6237 .selections
6238 .newest::<Point>(&snapshot.display_snapshot)
6239 .head(),
6240 };
6241 let Some((buffer, buffer_row)) = snapshot
6242 .buffer_snapshot()
6243 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6244 .and_then(|(buffer_snapshot, range)| {
6245 self.buffer()
6246 .read(cx)
6247 .buffer(buffer_snapshot.remote_id())
6248 .map(|buffer| (buffer, range.start.row))
6249 })
6250 else {
6251 return;
6252 };
6253 let buffer_id = buffer.read(cx).remote_id();
6254 let tasks = self
6255 .tasks
6256 .get(&(buffer_id, buffer_row))
6257 .map(|t| Arc::new(t.to_owned()));
6258
6259 if !self.focus_handle.is_focused(window) {
6260 return;
6261 }
6262 let project = self.project.clone();
6263
6264 let code_actions_task = match deployed_from {
6265 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6266 _ => self.code_actions(buffer_row, window, cx),
6267 };
6268
6269 let runnable_task = match deployed_from {
6270 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6271 _ => {
6272 let mut task_context_task = Task::ready(None);
6273 if let Some(tasks) = &tasks
6274 && let Some(project) = project
6275 {
6276 task_context_task =
6277 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6278 }
6279
6280 cx.spawn_in(window, {
6281 let buffer = buffer.clone();
6282 async move |editor, cx| {
6283 let task_context = task_context_task.await;
6284
6285 let resolved_tasks =
6286 tasks
6287 .zip(task_context.clone())
6288 .map(|(tasks, task_context)| ResolvedTasks {
6289 templates: tasks.resolve(&task_context).collect(),
6290 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6291 multibuffer_point.row,
6292 tasks.column,
6293 )),
6294 });
6295 let debug_scenarios = editor
6296 .update(cx, |editor, cx| {
6297 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6298 })?
6299 .await;
6300 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6301 }
6302 })
6303 }
6304 };
6305
6306 cx.spawn_in(window, async move |editor, cx| {
6307 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6308 let code_actions = code_actions_task.await;
6309 let spawn_straight_away = quick_launch
6310 && resolved_tasks
6311 .as_ref()
6312 .is_some_and(|tasks| tasks.templates.len() == 1)
6313 && code_actions
6314 .as_ref()
6315 .is_none_or(|actions| actions.is_empty())
6316 && debug_scenarios.is_empty();
6317
6318 editor.update_in(cx, |editor, window, cx| {
6319 crate::hover_popover::hide_hover(editor, cx);
6320 let actions = CodeActionContents::new(
6321 resolved_tasks,
6322 code_actions,
6323 debug_scenarios,
6324 task_context.unwrap_or_default(),
6325 );
6326
6327 // Don't show the menu if there are no actions available
6328 if actions.is_empty() {
6329 cx.notify();
6330 return Task::ready(Ok(()));
6331 }
6332
6333 *editor.context_menu.borrow_mut() =
6334 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6335 buffer,
6336 actions,
6337 selected_item: Default::default(),
6338 scroll_handle: UniformListScrollHandle::default(),
6339 deployed_from,
6340 }));
6341 cx.notify();
6342 if spawn_straight_away
6343 && let Some(task) = editor.confirm_code_action(
6344 &ConfirmCodeAction { item_ix: Some(0) },
6345 window,
6346 cx,
6347 )
6348 {
6349 return task;
6350 }
6351
6352 Task::ready(Ok(()))
6353 })
6354 })
6355 .detach_and_log_err(cx);
6356 }
6357
6358 fn debug_scenarios(
6359 &mut self,
6360 resolved_tasks: &Option<ResolvedTasks>,
6361 buffer: &Entity<Buffer>,
6362 cx: &mut App,
6363 ) -> Task<Vec<task::DebugScenario>> {
6364 maybe!({
6365 let project = self.project()?;
6366 let dap_store = project.read(cx).dap_store();
6367 let mut scenarios = vec![];
6368 let resolved_tasks = resolved_tasks.as_ref()?;
6369 let buffer = buffer.read(cx);
6370 let language = buffer.language()?;
6371 let file = buffer.file();
6372 let debug_adapter = language_settings(language.name().into(), file, cx)
6373 .debuggers
6374 .first()
6375 .map(SharedString::from)
6376 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6377
6378 dap_store.update(cx, |dap_store, cx| {
6379 for (_, task) in &resolved_tasks.templates {
6380 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6381 task.original_task().clone(),
6382 debug_adapter.clone().into(),
6383 task.display_label().to_owned().into(),
6384 cx,
6385 );
6386 scenarios.push(maybe_scenario);
6387 }
6388 });
6389 Some(cx.background_spawn(async move {
6390 futures::future::join_all(scenarios)
6391 .await
6392 .into_iter()
6393 .flatten()
6394 .collect::<Vec<_>>()
6395 }))
6396 })
6397 .unwrap_or_else(|| Task::ready(vec![]))
6398 }
6399
6400 fn code_actions(
6401 &mut self,
6402 buffer_row: u32,
6403 window: &mut Window,
6404 cx: &mut Context<Self>,
6405 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6406 let mut task = self.code_actions_task.take();
6407 cx.spawn_in(window, async move |editor, cx| {
6408 while let Some(prev_task) = task {
6409 prev_task.await.log_err();
6410 task = editor
6411 .update(cx, |this, _| this.code_actions_task.take())
6412 .ok()?;
6413 }
6414
6415 editor
6416 .update(cx, |editor, cx| {
6417 editor
6418 .available_code_actions
6419 .clone()
6420 .and_then(|(location, code_actions)| {
6421 let snapshot = location.buffer.read(cx).snapshot();
6422 let point_range = location.range.to_point(&snapshot);
6423 let point_range = point_range.start.row..=point_range.end.row;
6424 if point_range.contains(&buffer_row) {
6425 Some(code_actions)
6426 } else {
6427 None
6428 }
6429 })
6430 })
6431 .ok()
6432 .flatten()
6433 })
6434 }
6435
6436 pub fn confirm_code_action(
6437 &mut self,
6438 action: &ConfirmCodeAction,
6439 window: &mut Window,
6440 cx: &mut Context<Self>,
6441 ) -> Option<Task<Result<()>>> {
6442 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6443
6444 let actions_menu =
6445 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6446 menu
6447 } else {
6448 return None;
6449 };
6450
6451 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6452 let action = actions_menu.actions.get(action_ix)?;
6453 let title = action.label();
6454 let buffer = actions_menu.buffer;
6455 let workspace = self.workspace()?;
6456
6457 match action {
6458 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6459 workspace.update(cx, |workspace, cx| {
6460 workspace.schedule_resolved_task(
6461 task_source_kind,
6462 resolved_task,
6463 false,
6464 window,
6465 cx,
6466 );
6467
6468 Some(Task::ready(Ok(())))
6469 })
6470 }
6471 CodeActionsItem::CodeAction {
6472 excerpt_id,
6473 action,
6474 provider,
6475 } => {
6476 let apply_code_action =
6477 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6478 let workspace = workspace.downgrade();
6479 Some(cx.spawn_in(window, async move |editor, cx| {
6480 let project_transaction = apply_code_action.await?;
6481 Self::open_project_transaction(
6482 &editor,
6483 workspace,
6484 project_transaction,
6485 title,
6486 cx,
6487 )
6488 .await
6489 }))
6490 }
6491 CodeActionsItem::DebugScenario(scenario) => {
6492 let context = actions_menu.actions.context;
6493
6494 workspace.update(cx, |workspace, cx| {
6495 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6496 workspace.start_debug_session(
6497 scenario,
6498 context,
6499 Some(buffer),
6500 None,
6501 window,
6502 cx,
6503 );
6504 });
6505 Some(Task::ready(Ok(())))
6506 }
6507 }
6508 }
6509
6510 fn open_transaction_for_hidden_buffers(
6511 workspace: Entity<Workspace>,
6512 transaction: ProjectTransaction,
6513 title: String,
6514 window: &mut Window,
6515 cx: &mut Context<Self>,
6516 ) {
6517 if transaction.0.is_empty() {
6518 return;
6519 }
6520
6521 let edited_buffers_already_open = {
6522 let other_editors: Vec<Entity<Editor>> = workspace
6523 .read(cx)
6524 .panes()
6525 .iter()
6526 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
6527 .filter(|editor| editor.entity_id() != cx.entity_id())
6528 .collect();
6529
6530 transaction.0.keys().all(|buffer| {
6531 other_editors.iter().any(|editor| {
6532 let multi_buffer = editor.read(cx).buffer();
6533 multi_buffer.read(cx).is_singleton()
6534 && multi_buffer
6535 .read(cx)
6536 .as_singleton()
6537 .map_or(false, |singleton| {
6538 singleton.entity_id() == buffer.entity_id()
6539 })
6540 })
6541 })
6542 };
6543 if !edited_buffers_already_open {
6544 let workspace = workspace.downgrade();
6545 cx.defer_in(window, move |_, window, cx| {
6546 cx.spawn_in(window, async move |editor, cx| {
6547 Self::open_project_transaction(&editor, workspace, transaction, title, cx)
6548 .await
6549 .ok()
6550 })
6551 .detach();
6552 });
6553 }
6554 }
6555
6556 pub async fn open_project_transaction(
6557 editor: &WeakEntity<Editor>,
6558 workspace: WeakEntity<Workspace>,
6559 transaction: ProjectTransaction,
6560 title: String,
6561 cx: &mut AsyncWindowContext,
6562 ) -> Result<()> {
6563 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6564 cx.update(|_, cx| {
6565 entries.sort_unstable_by_key(|(buffer, _)| {
6566 buffer.read(cx).file().map(|f| f.path().clone())
6567 });
6568 })?;
6569 if entries.is_empty() {
6570 return Ok(());
6571 }
6572
6573 // If the project transaction's edits are all contained within this editor, then
6574 // avoid opening a new editor to display them.
6575
6576 if let [(buffer, transaction)] = &*entries {
6577 let excerpt = editor.update(cx, |editor, cx| {
6578 editor
6579 .buffer()
6580 .read(cx)
6581 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6582 })?;
6583 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6584 && excerpted_buffer == *buffer
6585 {
6586 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6587 let excerpt_range = excerpt_range.to_offset(buffer);
6588 buffer
6589 .edited_ranges_for_transaction::<usize>(transaction)
6590 .all(|range| {
6591 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6592 })
6593 })?;
6594
6595 if all_edits_within_excerpt {
6596 return Ok(());
6597 }
6598 }
6599 }
6600
6601 let mut ranges_to_highlight = Vec::new();
6602 let excerpt_buffer = cx.new(|cx| {
6603 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6604 for (buffer_handle, transaction) in &entries {
6605 let edited_ranges = buffer_handle
6606 .read(cx)
6607 .edited_ranges_for_transaction::<Point>(transaction)
6608 .collect::<Vec<_>>();
6609 let (ranges, _) = multibuffer.set_excerpts_for_path(
6610 PathKey::for_buffer(buffer_handle, cx),
6611 buffer_handle.clone(),
6612 edited_ranges,
6613 multibuffer_context_lines(cx),
6614 cx,
6615 );
6616
6617 ranges_to_highlight.extend(ranges);
6618 }
6619 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6620 multibuffer
6621 })?;
6622
6623 workspace.update_in(cx, |workspace, window, cx| {
6624 let project = workspace.project().clone();
6625 let editor =
6626 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6627 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6628 editor.update(cx, |editor, cx| {
6629 editor.highlight_background::<Self>(
6630 &ranges_to_highlight,
6631 |_, theme| theme.colors().editor_highlighted_line_background,
6632 cx,
6633 );
6634 });
6635 })?;
6636
6637 Ok(())
6638 }
6639
6640 pub fn clear_code_action_providers(&mut self) {
6641 self.code_action_providers.clear();
6642 self.available_code_actions.take();
6643 }
6644
6645 pub fn add_code_action_provider(
6646 &mut self,
6647 provider: Rc<dyn CodeActionProvider>,
6648 window: &mut Window,
6649 cx: &mut Context<Self>,
6650 ) {
6651 if self
6652 .code_action_providers
6653 .iter()
6654 .any(|existing_provider| existing_provider.id() == provider.id())
6655 {
6656 return;
6657 }
6658
6659 self.code_action_providers.push(provider);
6660 self.refresh_code_actions(window, cx);
6661 }
6662
6663 pub fn remove_code_action_provider(
6664 &mut self,
6665 id: Arc<str>,
6666 window: &mut Window,
6667 cx: &mut Context<Self>,
6668 ) {
6669 self.code_action_providers
6670 .retain(|provider| provider.id() != id);
6671 self.refresh_code_actions(window, cx);
6672 }
6673
6674 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6675 !self.code_action_providers.is_empty()
6676 && EditorSettings::get_global(cx).toolbar.code_actions
6677 }
6678
6679 pub fn has_available_code_actions(&self) -> bool {
6680 self.available_code_actions
6681 .as_ref()
6682 .is_some_and(|(_, actions)| !actions.is_empty())
6683 }
6684
6685 fn render_inline_code_actions(
6686 &self,
6687 icon_size: ui::IconSize,
6688 display_row: DisplayRow,
6689 is_active: bool,
6690 cx: &mut Context<Self>,
6691 ) -> AnyElement {
6692 let show_tooltip = !self.context_menu_visible();
6693 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6694 .icon_size(icon_size)
6695 .shape(ui::IconButtonShape::Square)
6696 .icon_color(ui::Color::Hidden)
6697 .toggle_state(is_active)
6698 .when(show_tooltip, |this| {
6699 this.tooltip({
6700 let focus_handle = self.focus_handle.clone();
6701 move |_window, cx| {
6702 Tooltip::for_action_in(
6703 "Toggle Code Actions",
6704 &ToggleCodeActions {
6705 deployed_from: None,
6706 quick_launch: false,
6707 },
6708 &focus_handle,
6709 cx,
6710 )
6711 }
6712 })
6713 })
6714 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6715 window.focus(&editor.focus_handle(cx), cx);
6716 editor.toggle_code_actions(
6717 &crate::actions::ToggleCodeActions {
6718 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6719 display_row,
6720 )),
6721 quick_launch: false,
6722 },
6723 window,
6724 cx,
6725 );
6726 }))
6727 .into_any_element()
6728 }
6729
6730 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6731 &self.context_menu
6732 }
6733
6734 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6735 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6736 cx.background_executor()
6737 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6738 .await;
6739
6740 let (start_buffer, start, _, end, newest_selection) = this
6741 .update(cx, |this, cx| {
6742 let newest_selection = this.selections.newest_anchor().clone();
6743 if newest_selection.head().diff_base_anchor.is_some() {
6744 return None;
6745 }
6746 let display_snapshot = this.display_snapshot(cx);
6747 let newest_selection_adjusted =
6748 this.selections.newest_adjusted(&display_snapshot);
6749 let buffer = this.buffer.read(cx);
6750
6751 let (start_buffer, start) =
6752 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6753 let (end_buffer, end) =
6754 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6755
6756 Some((start_buffer, start, end_buffer, end, newest_selection))
6757 })?
6758 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6759 .context(
6760 "Expected selection to lie in a single buffer when refreshing code actions",
6761 )?;
6762 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6763 let providers = this.code_action_providers.clone();
6764 let tasks = this
6765 .code_action_providers
6766 .iter()
6767 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6768 .collect::<Vec<_>>();
6769 (providers, tasks)
6770 })?;
6771
6772 let mut actions = Vec::new();
6773 for (provider, provider_actions) in
6774 providers.into_iter().zip(future::join_all(tasks).await)
6775 {
6776 if let Some(provider_actions) = provider_actions.log_err() {
6777 actions.extend(provider_actions.into_iter().map(|action| {
6778 AvailableCodeAction {
6779 excerpt_id: newest_selection.start.excerpt_id,
6780 action,
6781 provider: provider.clone(),
6782 }
6783 }));
6784 }
6785 }
6786
6787 this.update(cx, |this, cx| {
6788 this.available_code_actions = if actions.is_empty() {
6789 None
6790 } else {
6791 Some((
6792 Location {
6793 buffer: start_buffer,
6794 range: start..end,
6795 },
6796 actions.into(),
6797 ))
6798 };
6799 cx.notify();
6800 })
6801 }));
6802 }
6803
6804 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6805 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6806 self.show_git_blame_inline = false;
6807
6808 self.show_git_blame_inline_delay_task =
6809 Some(cx.spawn_in(window, async move |this, cx| {
6810 cx.background_executor().timer(delay).await;
6811
6812 this.update(cx, |this, cx| {
6813 this.show_git_blame_inline = true;
6814 cx.notify();
6815 })
6816 .log_err();
6817 }));
6818 }
6819 }
6820
6821 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6822 let snapshot = self.snapshot(window, cx);
6823 let cursor = self
6824 .selections
6825 .newest::<Point>(&snapshot.display_snapshot)
6826 .head();
6827 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6828 else {
6829 return;
6830 };
6831
6832 if self.blame.is_none() {
6833 self.start_git_blame(true, window, cx);
6834 }
6835 let Some(blame) = self.blame.as_ref() else {
6836 return;
6837 };
6838
6839 let row_info = RowInfo {
6840 buffer_id: Some(buffer.remote_id()),
6841 buffer_row: Some(point.row),
6842 ..Default::default()
6843 };
6844 let Some((buffer, blame_entry)) = blame
6845 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6846 .flatten()
6847 else {
6848 return;
6849 };
6850
6851 let anchor = self.selections.newest_anchor().head();
6852 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6853 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6854 self.show_blame_popover(
6855 buffer,
6856 &blame_entry,
6857 position + last_bounds.origin,
6858 true,
6859 cx,
6860 );
6861 };
6862 }
6863
6864 fn show_blame_popover(
6865 &mut self,
6866 buffer: BufferId,
6867 blame_entry: &BlameEntry,
6868 position: gpui::Point<Pixels>,
6869 ignore_timeout: bool,
6870 cx: &mut Context<Self>,
6871 ) {
6872 if let Some(state) = &mut self.inline_blame_popover {
6873 state.hide_task.take();
6874 } else {
6875 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6876 let blame_entry = blame_entry.clone();
6877 let show_task = cx.spawn(async move |editor, cx| {
6878 if !ignore_timeout {
6879 cx.background_executor()
6880 .timer(std::time::Duration::from_millis(blame_popover_delay))
6881 .await;
6882 }
6883 editor
6884 .update(cx, |editor, cx| {
6885 editor.inline_blame_popover_show_task.take();
6886 let Some(blame) = editor.blame.as_ref() else {
6887 return;
6888 };
6889 let blame = blame.read(cx);
6890 let details = blame.details_for_entry(buffer, &blame_entry);
6891 let markdown = cx.new(|cx| {
6892 Markdown::new(
6893 details
6894 .as_ref()
6895 .map(|message| message.message.clone())
6896 .unwrap_or_default(),
6897 None,
6898 None,
6899 cx,
6900 )
6901 });
6902 editor.inline_blame_popover = Some(InlineBlamePopover {
6903 position,
6904 hide_task: None,
6905 popover_bounds: None,
6906 popover_state: InlineBlamePopoverState {
6907 scroll_handle: ScrollHandle::new(),
6908 commit_message: details,
6909 markdown,
6910 },
6911 keyboard_grace: ignore_timeout,
6912 });
6913 cx.notify();
6914 })
6915 .ok();
6916 });
6917 self.inline_blame_popover_show_task = Some(show_task);
6918 }
6919 }
6920
6921 pub fn has_mouse_context_menu(&self) -> bool {
6922 self.mouse_context_menu.is_some()
6923 }
6924
6925 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6926 self.inline_blame_popover_show_task.take();
6927 if let Some(state) = &mut self.inline_blame_popover {
6928 let hide_task = cx.spawn(async move |editor, cx| {
6929 if !ignore_timeout {
6930 cx.background_executor()
6931 .timer(std::time::Duration::from_millis(100))
6932 .await;
6933 }
6934 editor
6935 .update(cx, |editor, cx| {
6936 editor.inline_blame_popover.take();
6937 cx.notify();
6938 })
6939 .ok();
6940 });
6941 state.hide_task = Some(hide_task);
6942 true
6943 } else {
6944 false
6945 }
6946 }
6947
6948 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6949 if self.pending_rename.is_some() {
6950 return None;
6951 }
6952
6953 let provider = self.semantics_provider.clone()?;
6954 let buffer = self.buffer.read(cx);
6955 let newest_selection = self.selections.newest_anchor().clone();
6956 let cursor_position = newest_selection.head();
6957 let (cursor_buffer, cursor_buffer_position) =
6958 buffer.text_anchor_for_position(cursor_position, cx)?;
6959 let (tail_buffer, tail_buffer_position) =
6960 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6961 if cursor_buffer != tail_buffer {
6962 return None;
6963 }
6964
6965 let snapshot = cursor_buffer.read(cx).snapshot();
6966 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6967 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6968 if start_word_range != end_word_range {
6969 self.document_highlights_task.take();
6970 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6971 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6972 return None;
6973 }
6974
6975 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6976 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6977 cx.background_executor()
6978 .timer(Duration::from_millis(debounce))
6979 .await;
6980
6981 let highlights = if let Some(highlights) = cx
6982 .update(|cx| {
6983 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6984 })
6985 .ok()
6986 .flatten()
6987 {
6988 highlights.await.log_err()
6989 } else {
6990 None
6991 };
6992
6993 if let Some(highlights) = highlights {
6994 this.update(cx, |this, cx| {
6995 if this.pending_rename.is_some() {
6996 return;
6997 }
6998
6999 let buffer = this.buffer.read(cx);
7000 if buffer
7001 .text_anchor_for_position(cursor_position, cx)
7002 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7003 {
7004 return;
7005 }
7006
7007 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7008 let mut write_ranges = Vec::new();
7009 let mut read_ranges = Vec::new();
7010 for highlight in highlights {
7011 let buffer_id = cursor_buffer.read(cx).remote_id();
7012 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7013 {
7014 let start = highlight
7015 .range
7016 .start
7017 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7018 let end = highlight
7019 .range
7020 .end
7021 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7022 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7023 continue;
7024 }
7025
7026 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7027 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7028 write_ranges.push(range);
7029 } else {
7030 read_ranges.push(range);
7031 }
7032 }
7033 }
7034
7035 this.highlight_background::<DocumentHighlightRead>(
7036 &read_ranges,
7037 |_, theme| theme.colors().editor_document_highlight_read_background,
7038 cx,
7039 );
7040 this.highlight_background::<DocumentHighlightWrite>(
7041 &write_ranges,
7042 |_, theme| theme.colors().editor_document_highlight_write_background,
7043 cx,
7044 );
7045 cx.notify();
7046 })
7047 .log_err();
7048 }
7049 }));
7050 None
7051 }
7052
7053 fn prepare_highlight_query_from_selection(
7054 &mut self,
7055 window: &Window,
7056 cx: &mut Context<Editor>,
7057 ) -> Option<(String, Range<Anchor>)> {
7058 if matches!(self.mode, EditorMode::SingleLine) {
7059 return None;
7060 }
7061 if !EditorSettings::get_global(cx).selection_highlight {
7062 return None;
7063 }
7064 if self.selections.count() != 1 || self.selections.line_mode() {
7065 return None;
7066 }
7067 let snapshot = self.snapshot(window, cx);
7068 let selection = self.selections.newest::<Point>(&snapshot);
7069 // If the selection spans multiple rows OR it is empty
7070 if selection.start.row != selection.end.row
7071 || selection.start.column == selection.end.column
7072 {
7073 return None;
7074 }
7075 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7076 let query = snapshot
7077 .buffer_snapshot()
7078 .text_for_range(selection_anchor_range.clone())
7079 .collect::<String>();
7080 if query.trim().is_empty() {
7081 return None;
7082 }
7083 Some((query, selection_anchor_range))
7084 }
7085
7086 #[ztracing::instrument(skip_all)]
7087 fn update_selection_occurrence_highlights(
7088 &mut self,
7089 query_text: String,
7090 query_range: Range<Anchor>,
7091 multi_buffer_range_to_query: Range<Point>,
7092 use_debounce: bool,
7093 window: &mut Window,
7094 cx: &mut Context<Editor>,
7095 ) -> Task<()> {
7096 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7097 cx.spawn_in(window, async move |editor, cx| {
7098 if use_debounce {
7099 cx.background_executor()
7100 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7101 .await;
7102 }
7103 let match_task = cx.background_spawn(async move {
7104 let buffer_ranges = multi_buffer_snapshot
7105 .range_to_buffer_ranges(multi_buffer_range_to_query)
7106 .into_iter()
7107 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7108 let mut match_ranges = Vec::new();
7109 let Ok(regex) = project::search::SearchQuery::text(
7110 query_text.clone(),
7111 false,
7112 false,
7113 false,
7114 Default::default(),
7115 Default::default(),
7116 false,
7117 None,
7118 ) else {
7119 return Vec::default();
7120 };
7121 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7122 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7123 match_ranges.extend(
7124 regex
7125 .search(
7126 buffer_snapshot,
7127 Some(search_range.start.0..search_range.end.0),
7128 )
7129 .await
7130 .into_iter()
7131 .filter_map(|match_range| {
7132 let match_start = buffer_snapshot
7133 .anchor_after(search_range.start + match_range.start);
7134 let match_end = buffer_snapshot
7135 .anchor_before(search_range.start + match_range.end);
7136 let match_anchor_range =
7137 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7138 (match_anchor_range != query_range).then_some(match_anchor_range)
7139 }),
7140 );
7141 }
7142 match_ranges
7143 });
7144 let match_ranges = match_task.await;
7145 editor
7146 .update_in(cx, |editor, _, cx| {
7147 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7148 if !match_ranges.is_empty() {
7149 editor.highlight_background::<SelectedTextHighlight>(
7150 &match_ranges,
7151 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7152 cx,
7153 )
7154 }
7155 })
7156 .log_err();
7157 })
7158 }
7159
7160 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7161 struct NewlineFold;
7162 let type_id = std::any::TypeId::of::<NewlineFold>();
7163 if !self.mode.is_single_line() {
7164 return;
7165 }
7166 let snapshot = self.snapshot(window, cx);
7167 if snapshot.buffer_snapshot().max_point().row == 0 {
7168 return;
7169 }
7170 let task = cx.background_spawn(async move {
7171 let new_newlines = snapshot
7172 .buffer_chars_at(MultiBufferOffset(0))
7173 .filter_map(|(c, i)| {
7174 if c == '\n' {
7175 Some(
7176 snapshot.buffer_snapshot().anchor_after(i)
7177 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7178 )
7179 } else {
7180 None
7181 }
7182 })
7183 .collect::<Vec<_>>();
7184 let existing_newlines = snapshot
7185 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7186 .filter_map(|fold| {
7187 if fold.placeholder.type_tag == Some(type_id) {
7188 Some(fold.range.start..fold.range.end)
7189 } else {
7190 None
7191 }
7192 })
7193 .collect::<Vec<_>>();
7194
7195 (new_newlines, existing_newlines)
7196 });
7197 self.folding_newlines = cx.spawn(async move |this, cx| {
7198 let (new_newlines, existing_newlines) = task.await;
7199 if new_newlines == existing_newlines {
7200 return;
7201 }
7202 let placeholder = FoldPlaceholder {
7203 render: Arc::new(move |_, _, cx| {
7204 div()
7205 .bg(cx.theme().status().hint_background)
7206 .border_b_1()
7207 .size_full()
7208 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7209 .border_color(cx.theme().status().hint)
7210 .child("\\n")
7211 .into_any()
7212 }),
7213 constrain_width: false,
7214 merge_adjacent: false,
7215 type_tag: Some(type_id),
7216 };
7217 let creases = new_newlines
7218 .into_iter()
7219 .map(|range| Crease::simple(range, placeholder.clone()))
7220 .collect();
7221 this.update(cx, |this, cx| {
7222 this.display_map.update(cx, |display_map, cx| {
7223 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7224 display_map.fold(creases, cx);
7225 });
7226 })
7227 .ok();
7228 });
7229 }
7230
7231 #[ztracing::instrument(skip_all)]
7232 fn refresh_selected_text_highlights(
7233 &mut self,
7234 on_buffer_edit: bool,
7235 window: &mut Window,
7236 cx: &mut Context<Editor>,
7237 ) {
7238 let Some((query_text, query_range)) =
7239 self.prepare_highlight_query_from_selection(window, cx)
7240 else {
7241 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7242 self.quick_selection_highlight_task.take();
7243 self.debounced_selection_highlight_task.take();
7244 return;
7245 };
7246 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7247 if on_buffer_edit
7248 || self
7249 .quick_selection_highlight_task
7250 .as_ref()
7251 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7252 {
7253 let multi_buffer_visible_start = self
7254 .scroll_manager
7255 .anchor()
7256 .anchor
7257 .to_point(&multi_buffer_snapshot);
7258 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7259 multi_buffer_visible_start
7260 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7261 Bias::Left,
7262 );
7263 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7264 self.quick_selection_highlight_task = Some((
7265 query_range.clone(),
7266 self.update_selection_occurrence_highlights(
7267 query_text.clone(),
7268 query_range.clone(),
7269 multi_buffer_visible_range,
7270 false,
7271 window,
7272 cx,
7273 ),
7274 ));
7275 }
7276 if on_buffer_edit
7277 || self
7278 .debounced_selection_highlight_task
7279 .as_ref()
7280 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7281 {
7282 let multi_buffer_start = multi_buffer_snapshot
7283 .anchor_before(MultiBufferOffset(0))
7284 .to_point(&multi_buffer_snapshot);
7285 let multi_buffer_end = multi_buffer_snapshot
7286 .anchor_after(multi_buffer_snapshot.len())
7287 .to_point(&multi_buffer_snapshot);
7288 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7289 self.debounced_selection_highlight_task = Some((
7290 query_range.clone(),
7291 self.update_selection_occurrence_highlights(
7292 query_text,
7293 query_range,
7294 multi_buffer_full_range,
7295 true,
7296 window,
7297 cx,
7298 ),
7299 ));
7300 }
7301 }
7302
7303 pub fn refresh_edit_prediction(
7304 &mut self,
7305 debounce: bool,
7306 user_requested: bool,
7307 window: &mut Window,
7308 cx: &mut Context<Self>,
7309 ) -> Option<()> {
7310 if DisableAiSettings::get_global(cx).disable_ai {
7311 return None;
7312 }
7313
7314 let provider = self.edit_prediction_provider()?;
7315 let cursor = self.selections.newest_anchor().head();
7316 let (buffer, cursor_buffer_position) =
7317 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7318
7319 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7320 self.discard_edit_prediction(false, cx);
7321 return None;
7322 }
7323
7324 self.update_visible_edit_prediction(window, cx);
7325
7326 if !user_requested
7327 && (!self.should_show_edit_predictions()
7328 || !self.is_focused(window)
7329 || buffer.read(cx).is_empty())
7330 {
7331 self.discard_edit_prediction(false, cx);
7332 return None;
7333 }
7334
7335 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7336 Some(())
7337 }
7338
7339 fn show_edit_predictions_in_menu(&self) -> bool {
7340 match self.edit_prediction_settings {
7341 EditPredictionSettings::Disabled => false,
7342 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7343 }
7344 }
7345
7346 pub fn edit_predictions_enabled(&self) -> bool {
7347 match self.edit_prediction_settings {
7348 EditPredictionSettings::Disabled => false,
7349 EditPredictionSettings::Enabled { .. } => true,
7350 }
7351 }
7352
7353 fn edit_prediction_requires_modifier(&self) -> bool {
7354 match self.edit_prediction_settings {
7355 EditPredictionSettings::Disabled => false,
7356 EditPredictionSettings::Enabled {
7357 preview_requires_modifier,
7358 ..
7359 } => preview_requires_modifier,
7360 }
7361 }
7362
7363 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7364 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7365 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7366 self.discard_edit_prediction(false, cx);
7367 } else {
7368 let selection = self.selections.newest_anchor();
7369 let cursor = selection.head();
7370
7371 if let Some((buffer, cursor_buffer_position)) =
7372 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7373 {
7374 self.edit_prediction_settings =
7375 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7376 }
7377 }
7378 }
7379
7380 fn edit_prediction_settings_at_position(
7381 &self,
7382 buffer: &Entity<Buffer>,
7383 buffer_position: language::Anchor,
7384 cx: &App,
7385 ) -> EditPredictionSettings {
7386 if !self.mode.is_full()
7387 || !self.show_edit_predictions_override.unwrap_or(true)
7388 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7389 {
7390 return EditPredictionSettings::Disabled;
7391 }
7392
7393 let buffer = buffer.read(cx);
7394
7395 let file = buffer.file();
7396
7397 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7398 return EditPredictionSettings::Disabled;
7399 };
7400
7401 let by_provider = matches!(
7402 self.menu_edit_predictions_policy,
7403 MenuEditPredictionsPolicy::ByProvider
7404 );
7405
7406 let show_in_menu = by_provider
7407 && self
7408 .edit_prediction_provider
7409 .as_ref()
7410 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7411
7412 let preview_requires_modifier =
7413 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7414
7415 EditPredictionSettings::Enabled {
7416 show_in_menu,
7417 preview_requires_modifier,
7418 }
7419 }
7420
7421 fn should_show_edit_predictions(&self) -> bool {
7422 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7423 }
7424
7425 pub fn edit_prediction_preview_is_active(&self) -> bool {
7426 matches!(
7427 self.edit_prediction_preview,
7428 EditPredictionPreview::Active { .. }
7429 )
7430 }
7431
7432 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7433 let cursor = self.selections.newest_anchor().head();
7434 if let Some((buffer, cursor_position)) =
7435 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7436 {
7437 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7438 } else {
7439 false
7440 }
7441 }
7442
7443 pub fn supports_minimap(&self, cx: &App) -> bool {
7444 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7445 }
7446
7447 fn edit_predictions_enabled_in_buffer(
7448 &self,
7449 buffer: &Entity<Buffer>,
7450 buffer_position: language::Anchor,
7451 cx: &App,
7452 ) -> bool {
7453 maybe!({
7454 if self.read_only(cx) {
7455 return Some(false);
7456 }
7457 let provider = self.edit_prediction_provider()?;
7458 if !provider.is_enabled(buffer, buffer_position, cx) {
7459 return Some(false);
7460 }
7461 let buffer = buffer.read(cx);
7462 let Some(file) = buffer.file() else {
7463 return Some(true);
7464 };
7465 let settings = all_language_settings(Some(file), cx);
7466 Some(settings.edit_predictions_enabled_for_file(file, cx))
7467 })
7468 .unwrap_or(false)
7469 }
7470
7471 fn cycle_edit_prediction(
7472 &mut self,
7473 direction: Direction,
7474 window: &mut Window,
7475 cx: &mut Context<Self>,
7476 ) -> Option<()> {
7477 let provider = self.edit_prediction_provider()?;
7478 let cursor = self.selections.newest_anchor().head();
7479 let (buffer, cursor_buffer_position) =
7480 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7481 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7482 return None;
7483 }
7484
7485 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7486 self.update_visible_edit_prediction(window, cx);
7487
7488 Some(())
7489 }
7490
7491 pub fn show_edit_prediction(
7492 &mut self,
7493 _: &ShowEditPrediction,
7494 window: &mut Window,
7495 cx: &mut Context<Self>,
7496 ) {
7497 if !self.has_active_edit_prediction() {
7498 self.refresh_edit_prediction(false, true, window, cx);
7499 return;
7500 }
7501
7502 self.update_visible_edit_prediction(window, cx);
7503 }
7504
7505 pub fn display_cursor_names(
7506 &mut self,
7507 _: &DisplayCursorNames,
7508 window: &mut Window,
7509 cx: &mut Context<Self>,
7510 ) {
7511 self.show_cursor_names(window, cx);
7512 }
7513
7514 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7515 self.show_cursor_names = true;
7516 cx.notify();
7517 cx.spawn_in(window, async move |this, cx| {
7518 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7519 this.update(cx, |this, cx| {
7520 this.show_cursor_names = false;
7521 cx.notify()
7522 })
7523 .ok()
7524 })
7525 .detach();
7526 }
7527
7528 pub fn next_edit_prediction(
7529 &mut self,
7530 _: &NextEditPrediction,
7531 window: &mut Window,
7532 cx: &mut Context<Self>,
7533 ) {
7534 if self.has_active_edit_prediction() {
7535 self.cycle_edit_prediction(Direction::Next, window, cx);
7536 } else {
7537 let is_copilot_disabled = self
7538 .refresh_edit_prediction(false, true, window, cx)
7539 .is_none();
7540 if is_copilot_disabled {
7541 cx.propagate();
7542 }
7543 }
7544 }
7545
7546 pub fn previous_edit_prediction(
7547 &mut self,
7548 _: &PreviousEditPrediction,
7549 window: &mut Window,
7550 cx: &mut Context<Self>,
7551 ) {
7552 if self.has_active_edit_prediction() {
7553 self.cycle_edit_prediction(Direction::Prev, window, cx);
7554 } else {
7555 let is_copilot_disabled = self
7556 .refresh_edit_prediction(false, true, window, cx)
7557 .is_none();
7558 if is_copilot_disabled {
7559 cx.propagate();
7560 }
7561 }
7562 }
7563
7564 pub fn accept_partial_edit_prediction(
7565 &mut self,
7566 granularity: EditPredictionGranularity,
7567 window: &mut Window,
7568 cx: &mut Context<Self>,
7569 ) {
7570 if self.show_edit_predictions_in_menu() {
7571 self.hide_context_menu(window, cx);
7572 }
7573
7574 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7575 return;
7576 };
7577
7578 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7579 return;
7580 }
7581
7582 match &active_edit_prediction.completion {
7583 EditPrediction::MoveWithin { target, .. } => {
7584 let target = *target;
7585
7586 if matches!(granularity, EditPredictionGranularity::Full) {
7587 if let Some(position_map) = &self.last_position_map {
7588 let target_row = target.to_display_point(&position_map.snapshot).row();
7589 let is_visible = position_map.visible_row_range.contains(&target_row);
7590
7591 if is_visible || !self.edit_prediction_requires_modifier() {
7592 self.unfold_ranges(&[target..target], true, false, cx);
7593 self.change_selections(
7594 SelectionEffects::scroll(Autoscroll::newest()),
7595 window,
7596 cx,
7597 |selections| {
7598 selections.select_anchor_ranges([target..target]);
7599 },
7600 );
7601 self.clear_row_highlights::<EditPredictionPreview>();
7602 self.edit_prediction_preview
7603 .set_previous_scroll_position(None);
7604 } else {
7605 // Highlight and request scroll
7606 self.edit_prediction_preview
7607 .set_previous_scroll_position(Some(
7608 position_map.snapshot.scroll_anchor,
7609 ));
7610 self.highlight_rows::<EditPredictionPreview>(
7611 target..target,
7612 cx.theme().colors().editor_highlighted_line_background,
7613 RowHighlightOptions {
7614 autoscroll: true,
7615 ..Default::default()
7616 },
7617 cx,
7618 );
7619 self.request_autoscroll(Autoscroll::fit(), cx);
7620 }
7621 }
7622 } else {
7623 self.change_selections(
7624 SelectionEffects::scroll(Autoscroll::newest()),
7625 window,
7626 cx,
7627 |selections| {
7628 selections.select_anchor_ranges([target..target]);
7629 },
7630 );
7631 }
7632 }
7633 EditPrediction::MoveOutside { snapshot, target } => {
7634 if let Some(workspace) = self.workspace() {
7635 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7636 .detach_and_log_err(cx);
7637 }
7638 }
7639 EditPrediction::Edit { edits, .. } => {
7640 self.report_edit_prediction_event(
7641 active_edit_prediction.completion_id.clone(),
7642 true,
7643 cx,
7644 );
7645
7646 match granularity {
7647 EditPredictionGranularity::Full => {
7648 if let Some(provider) = self.edit_prediction_provider() {
7649 provider.accept(cx);
7650 }
7651
7652 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7653 let snapshot = self.buffer.read(cx).snapshot(cx);
7654 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7655
7656 self.buffer.update(cx, |buffer, cx| {
7657 buffer.edit(edits.iter().cloned(), None, cx)
7658 });
7659
7660 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7661 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7662 });
7663
7664 let selections = self.selections.disjoint_anchors_arc();
7665 if let Some(transaction_id_now) =
7666 self.buffer.read(cx).last_transaction_id(cx)
7667 {
7668 if transaction_id_prev != Some(transaction_id_now) {
7669 self.selection_history
7670 .insert_transaction(transaction_id_now, selections);
7671 }
7672 }
7673
7674 self.update_visible_edit_prediction(window, cx);
7675 if self.active_edit_prediction.is_none() {
7676 self.refresh_edit_prediction(true, true, window, cx);
7677 }
7678 cx.notify();
7679 }
7680 _ => {
7681 let snapshot = self.buffer.read(cx).snapshot(cx);
7682 let cursor_offset = self
7683 .selections
7684 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7685 .head();
7686
7687 let insertion = edits.iter().find_map(|(range, text)| {
7688 let range = range.to_offset(&snapshot);
7689 if range.is_empty() && range.start == cursor_offset {
7690 Some(text)
7691 } else {
7692 None
7693 }
7694 });
7695
7696 if let Some(text) = insertion {
7697 let text_to_insert = match granularity {
7698 EditPredictionGranularity::Word => {
7699 let mut partial = text
7700 .chars()
7701 .by_ref()
7702 .take_while(|c| c.is_alphabetic())
7703 .collect::<String>();
7704 if partial.is_empty() {
7705 partial = text
7706 .chars()
7707 .by_ref()
7708 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7709 .collect::<String>();
7710 }
7711 partial
7712 }
7713 EditPredictionGranularity::Line => {
7714 if let Some(line) = text.split_inclusive('\n').next() {
7715 line.to_string()
7716 } else {
7717 text.to_string()
7718 }
7719 }
7720 EditPredictionGranularity::Full => unreachable!(),
7721 };
7722
7723 cx.emit(EditorEvent::InputHandled {
7724 utf16_range_to_replace: None,
7725 text: text_to_insert.clone().into(),
7726 });
7727
7728 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7729 self.refresh_edit_prediction(true, true, window, cx);
7730 cx.notify();
7731 } else {
7732 self.accept_partial_edit_prediction(
7733 EditPredictionGranularity::Full,
7734 window,
7735 cx,
7736 );
7737 }
7738 }
7739 }
7740 }
7741 }
7742
7743 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7744 }
7745
7746 pub fn accept_next_word_edit_prediction(
7747 &mut self,
7748 _: &AcceptNextWordEditPrediction,
7749 window: &mut Window,
7750 cx: &mut Context<Self>,
7751 ) {
7752 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7753 }
7754
7755 pub fn accept_next_line_edit_prediction(
7756 &mut self,
7757 _: &AcceptNextLineEditPrediction,
7758 window: &mut Window,
7759 cx: &mut Context<Self>,
7760 ) {
7761 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7762 }
7763
7764 pub fn accept_edit_prediction(
7765 &mut self,
7766 _: &AcceptEditPrediction,
7767 window: &mut Window,
7768 cx: &mut Context<Self>,
7769 ) {
7770 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7771 }
7772
7773 fn discard_edit_prediction(
7774 &mut self,
7775 should_report_edit_prediction_event: bool,
7776 cx: &mut Context<Self>,
7777 ) -> bool {
7778 if should_report_edit_prediction_event {
7779 let completion_id = self
7780 .active_edit_prediction
7781 .as_ref()
7782 .and_then(|active_completion| active_completion.completion_id.clone());
7783
7784 self.report_edit_prediction_event(completion_id, false, cx);
7785 }
7786
7787 if let Some(provider) = self.edit_prediction_provider() {
7788 provider.discard(cx);
7789 }
7790
7791 self.take_active_edit_prediction(cx)
7792 }
7793
7794 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7795 let Some(provider) = self.edit_prediction_provider() else {
7796 return;
7797 };
7798
7799 let Some((_, buffer, _)) = self
7800 .buffer
7801 .read(cx)
7802 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7803 else {
7804 return;
7805 };
7806
7807 let extension = buffer
7808 .read(cx)
7809 .file()
7810 .and_then(|file| Some(file.path().extension()?.to_string()));
7811
7812 let event_type = match accepted {
7813 true => "Edit Prediction Accepted",
7814 false => "Edit Prediction Discarded",
7815 };
7816 telemetry::event!(
7817 event_type,
7818 provider = provider.name(),
7819 prediction_id = id,
7820 suggestion_accepted = accepted,
7821 file_extension = extension,
7822 );
7823 }
7824
7825 fn open_editor_at_anchor(
7826 snapshot: &language::BufferSnapshot,
7827 target: language::Anchor,
7828 workspace: &Entity<Workspace>,
7829 window: &mut Window,
7830 cx: &mut App,
7831 ) -> Task<Result<()>> {
7832 workspace.update(cx, |workspace, cx| {
7833 let path = snapshot.file().map(|file| file.full_path(cx));
7834 let Some(path) =
7835 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7836 else {
7837 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7838 };
7839 let target = text::ToPoint::to_point(&target, snapshot);
7840 let item = workspace.open_path(path, None, true, window, cx);
7841 window.spawn(cx, async move |cx| {
7842 let Some(editor) = item.await?.downcast::<Editor>() else {
7843 return Ok(());
7844 };
7845 editor
7846 .update_in(cx, |editor, window, cx| {
7847 editor.go_to_singleton_buffer_point(target, window, cx);
7848 })
7849 .ok();
7850 anyhow::Ok(())
7851 })
7852 })
7853 }
7854
7855 pub fn has_active_edit_prediction(&self) -> bool {
7856 self.active_edit_prediction.is_some()
7857 }
7858
7859 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7860 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7861 return false;
7862 };
7863
7864 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7865 self.clear_highlights::<EditPredictionHighlight>(cx);
7866 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7867 true
7868 }
7869
7870 /// Returns true when we're displaying the edit prediction popover below the cursor
7871 /// like we are not previewing and the LSP autocomplete menu is visible
7872 /// or we are in `when_holding_modifier` mode.
7873 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7874 if self.edit_prediction_preview_is_active()
7875 || !self.show_edit_predictions_in_menu()
7876 || !self.edit_predictions_enabled()
7877 {
7878 return false;
7879 }
7880
7881 if self.has_visible_completions_menu() {
7882 return true;
7883 }
7884
7885 has_completion && self.edit_prediction_requires_modifier()
7886 }
7887
7888 fn handle_modifiers_changed(
7889 &mut self,
7890 modifiers: Modifiers,
7891 position_map: &PositionMap,
7892 window: &mut Window,
7893 cx: &mut Context<Self>,
7894 ) {
7895 // Ensure that the edit prediction preview is updated, even when not
7896 // enabled, if there's an active edit prediction preview.
7897 if self.show_edit_predictions_in_menu()
7898 || matches!(
7899 self.edit_prediction_preview,
7900 EditPredictionPreview::Active { .. }
7901 )
7902 {
7903 self.update_edit_prediction_preview(&modifiers, window, cx);
7904 }
7905
7906 self.update_selection_mode(&modifiers, position_map, window, cx);
7907
7908 let mouse_position = window.mouse_position();
7909 if !position_map.text_hitbox.is_hovered(window) {
7910 return;
7911 }
7912
7913 self.update_hovered_link(
7914 position_map.point_for_position(mouse_position),
7915 &position_map.snapshot,
7916 modifiers,
7917 window,
7918 cx,
7919 )
7920 }
7921
7922 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7923 match EditorSettings::get_global(cx).multi_cursor_modifier {
7924 MultiCursorModifier::Alt => modifiers.secondary(),
7925 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7926 }
7927 }
7928
7929 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7930 match EditorSettings::get_global(cx).multi_cursor_modifier {
7931 MultiCursorModifier::Alt => modifiers.alt,
7932 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7933 }
7934 }
7935
7936 fn columnar_selection_mode(
7937 modifiers: &Modifiers,
7938 cx: &mut Context<Self>,
7939 ) -> Option<ColumnarMode> {
7940 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7941 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7942 Some(ColumnarMode::FromMouse)
7943 } else if Self::is_alt_pressed(modifiers, cx) {
7944 Some(ColumnarMode::FromSelection)
7945 } else {
7946 None
7947 }
7948 } else {
7949 None
7950 }
7951 }
7952
7953 fn update_selection_mode(
7954 &mut self,
7955 modifiers: &Modifiers,
7956 position_map: &PositionMap,
7957 window: &mut Window,
7958 cx: &mut Context<Self>,
7959 ) {
7960 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7961 return;
7962 };
7963 if self.selections.pending_anchor().is_none() {
7964 return;
7965 }
7966
7967 let mouse_position = window.mouse_position();
7968 let point_for_position = position_map.point_for_position(mouse_position);
7969 let position = point_for_position.previous_valid;
7970
7971 self.select(
7972 SelectPhase::BeginColumnar {
7973 position,
7974 reset: false,
7975 mode,
7976 goal_column: point_for_position.exact_unclipped.column(),
7977 },
7978 window,
7979 cx,
7980 );
7981 }
7982
7983 fn update_edit_prediction_preview(
7984 &mut self,
7985 modifiers: &Modifiers,
7986 window: &mut Window,
7987 cx: &mut Context<Self>,
7988 ) {
7989 let mut modifiers_held = false;
7990
7991 // Check bindings for all granularities.
7992 // If the user holds the key for Word, Line, or Full, we want to show the preview.
7993 let granularities = [
7994 EditPredictionGranularity::Full,
7995 EditPredictionGranularity::Line,
7996 EditPredictionGranularity::Word,
7997 ];
7998
7999 for granularity in granularities {
8000 if let Some(keystroke) = self
8001 .accept_edit_prediction_keybind(granularity, window, cx)
8002 .keystroke()
8003 {
8004 modifiers_held = modifiers_held
8005 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8006 }
8007 }
8008
8009 if modifiers_held {
8010 if matches!(
8011 self.edit_prediction_preview,
8012 EditPredictionPreview::Inactive { .. }
8013 ) {
8014 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8015 provider.provider.did_show(cx)
8016 }
8017
8018 self.edit_prediction_preview = EditPredictionPreview::Active {
8019 previous_scroll_position: None,
8020 since: Instant::now(),
8021 };
8022
8023 self.update_visible_edit_prediction(window, cx);
8024 cx.notify();
8025 }
8026 } else if let EditPredictionPreview::Active {
8027 previous_scroll_position,
8028 since,
8029 } = self.edit_prediction_preview
8030 {
8031 if let (Some(previous_scroll_position), Some(position_map)) =
8032 (previous_scroll_position, self.last_position_map.as_ref())
8033 {
8034 self.set_scroll_position(
8035 previous_scroll_position
8036 .scroll_position(&position_map.snapshot.display_snapshot),
8037 window,
8038 cx,
8039 );
8040 }
8041
8042 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8043 released_too_fast: since.elapsed() < Duration::from_millis(200),
8044 };
8045 self.clear_row_highlights::<EditPredictionPreview>();
8046 self.update_visible_edit_prediction(window, cx);
8047 cx.notify();
8048 }
8049 }
8050
8051 fn update_visible_edit_prediction(
8052 &mut self,
8053 _window: &mut Window,
8054 cx: &mut Context<Self>,
8055 ) -> Option<()> {
8056 if DisableAiSettings::get_global(cx).disable_ai {
8057 return None;
8058 }
8059
8060 if self.ime_transaction.is_some() {
8061 self.discard_edit_prediction(false, cx);
8062 return None;
8063 }
8064
8065 let selection = self.selections.newest_anchor();
8066 let cursor = selection.head();
8067 let multibuffer = self.buffer.read(cx).snapshot(cx);
8068 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8069 let excerpt_id = cursor.excerpt_id;
8070
8071 let show_in_menu = self.show_edit_predictions_in_menu();
8072 let completions_menu_has_precedence = !show_in_menu
8073 && (self.context_menu.borrow().is_some()
8074 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8075
8076 if completions_menu_has_precedence
8077 || !offset_selection.is_empty()
8078 || self
8079 .active_edit_prediction
8080 .as_ref()
8081 .is_some_and(|completion| {
8082 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8083 return false;
8084 };
8085 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8086 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8087 !invalidation_range.contains(&offset_selection.head())
8088 })
8089 {
8090 self.discard_edit_prediction(false, cx);
8091 return None;
8092 }
8093
8094 self.take_active_edit_prediction(cx);
8095 let Some(provider) = self.edit_prediction_provider() else {
8096 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8097 return None;
8098 };
8099
8100 let (buffer, cursor_buffer_position) =
8101 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8102
8103 self.edit_prediction_settings =
8104 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8105
8106 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8107
8108 if self.edit_prediction_indent_conflict {
8109 let cursor_point = cursor.to_point(&multibuffer);
8110 let mut suggested_indent = None;
8111 multibuffer.suggested_indents_callback(
8112 cursor_point.row..cursor_point.row + 1,
8113 |_, indent| {
8114 suggested_indent = Some(indent);
8115 ControlFlow::Break(())
8116 },
8117 cx,
8118 );
8119
8120 if let Some(indent) = suggested_indent
8121 && indent.len == cursor_point.column
8122 {
8123 self.edit_prediction_indent_conflict = false;
8124 }
8125 }
8126
8127 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8128
8129 let (completion_id, edits, edit_preview) = match edit_prediction {
8130 edit_prediction_types::EditPrediction::Local {
8131 id,
8132 edits,
8133 edit_preview,
8134 } => (id, edits, edit_preview),
8135 edit_prediction_types::EditPrediction::Jump {
8136 id,
8137 snapshot,
8138 target,
8139 } => {
8140 self.stale_edit_prediction_in_menu = None;
8141 self.active_edit_prediction = Some(EditPredictionState {
8142 inlay_ids: vec![],
8143 completion: EditPrediction::MoveOutside { snapshot, target },
8144 completion_id: id,
8145 invalidation_range: None,
8146 });
8147 cx.notify();
8148 return Some(());
8149 }
8150 };
8151
8152 let edits = edits
8153 .into_iter()
8154 .flat_map(|(range, new_text)| {
8155 Some((
8156 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8157 new_text,
8158 ))
8159 })
8160 .collect::<Vec<_>>();
8161 if edits.is_empty() {
8162 return None;
8163 }
8164
8165 let first_edit_start = edits.first().unwrap().0.start;
8166 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8167 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8168
8169 let last_edit_end = edits.last().unwrap().0.end;
8170 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8171 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8172
8173 let cursor_row = cursor.to_point(&multibuffer).row;
8174
8175 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8176
8177 let mut inlay_ids = Vec::new();
8178 let invalidation_row_range;
8179 let move_invalidation_row_range = if cursor_row < edit_start_row {
8180 Some(cursor_row..edit_end_row)
8181 } else if cursor_row > edit_end_row {
8182 Some(edit_start_row..cursor_row)
8183 } else {
8184 None
8185 };
8186 let supports_jump = self
8187 .edit_prediction_provider
8188 .as_ref()
8189 .map(|provider| provider.provider.supports_jump_to_edit())
8190 .unwrap_or(true);
8191
8192 let is_move = supports_jump
8193 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8194 let completion = if is_move {
8195 invalidation_row_range =
8196 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8197 let target = first_edit_start;
8198 EditPrediction::MoveWithin { target, snapshot }
8199 } else {
8200 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8201 && !self.edit_predictions_hidden_for_vim_mode;
8202
8203 if show_completions_in_buffer {
8204 if let Some(provider) = &self.edit_prediction_provider {
8205 provider.provider.did_show(cx);
8206 }
8207 if edits
8208 .iter()
8209 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8210 {
8211 let mut inlays = Vec::new();
8212 for (range, new_text) in &edits {
8213 let inlay = Inlay::edit_prediction(
8214 post_inc(&mut self.next_inlay_id),
8215 range.start,
8216 new_text.as_ref(),
8217 );
8218 inlay_ids.push(inlay.id);
8219 inlays.push(inlay);
8220 }
8221
8222 self.splice_inlays(&[], inlays, cx);
8223 } else {
8224 let background_color = cx.theme().status().deleted_background;
8225 self.highlight_text::<EditPredictionHighlight>(
8226 edits.iter().map(|(range, _)| range.clone()).collect(),
8227 HighlightStyle {
8228 background_color: Some(background_color),
8229 ..Default::default()
8230 },
8231 cx,
8232 );
8233 }
8234 }
8235
8236 invalidation_row_range = edit_start_row..edit_end_row;
8237
8238 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8239 if provider.show_tab_accept_marker() {
8240 EditDisplayMode::TabAccept
8241 } else {
8242 EditDisplayMode::Inline
8243 }
8244 } else {
8245 EditDisplayMode::DiffPopover
8246 };
8247
8248 EditPrediction::Edit {
8249 edits,
8250 edit_preview,
8251 display_mode,
8252 snapshot,
8253 }
8254 };
8255
8256 let invalidation_range = multibuffer
8257 .anchor_before(Point::new(invalidation_row_range.start, 0))
8258 ..multibuffer.anchor_after(Point::new(
8259 invalidation_row_range.end,
8260 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8261 ));
8262
8263 self.stale_edit_prediction_in_menu = None;
8264 self.active_edit_prediction = Some(EditPredictionState {
8265 inlay_ids,
8266 completion,
8267 completion_id,
8268 invalidation_range: Some(invalidation_range),
8269 });
8270
8271 cx.notify();
8272
8273 Some(())
8274 }
8275
8276 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8277 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8278 }
8279
8280 fn clear_tasks(&mut self) {
8281 self.tasks.clear()
8282 }
8283
8284 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8285 if self.tasks.insert(key, value).is_some() {
8286 // This case should hopefully be rare, but just in case...
8287 log::error!(
8288 "multiple different run targets found on a single line, only the last target will be rendered"
8289 )
8290 }
8291 }
8292
8293 /// Get all display points of breakpoints that will be rendered within editor
8294 ///
8295 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8296 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8297 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8298 fn active_breakpoints(
8299 &self,
8300 range: Range<DisplayRow>,
8301 window: &mut Window,
8302 cx: &mut Context<Self>,
8303 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8304 let mut breakpoint_display_points = HashMap::default();
8305
8306 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8307 return breakpoint_display_points;
8308 };
8309
8310 let snapshot = self.snapshot(window, cx);
8311
8312 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8313 let Some(project) = self.project() else {
8314 return breakpoint_display_points;
8315 };
8316
8317 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8318 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8319
8320 for (buffer_snapshot, range, excerpt_id) in
8321 multi_buffer_snapshot.range_to_buffer_ranges(range)
8322 {
8323 let Some(buffer) = project
8324 .read(cx)
8325 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8326 else {
8327 continue;
8328 };
8329 let breakpoints = breakpoint_store.read(cx).breakpoints(
8330 &buffer,
8331 Some(
8332 buffer_snapshot.anchor_before(range.start)
8333 ..buffer_snapshot.anchor_after(range.end),
8334 ),
8335 buffer_snapshot,
8336 cx,
8337 );
8338 for (breakpoint, state) in breakpoints {
8339 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8340 let position = multi_buffer_anchor
8341 .to_point(&multi_buffer_snapshot)
8342 .to_display_point(&snapshot);
8343
8344 breakpoint_display_points.insert(
8345 position.row(),
8346 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8347 );
8348 }
8349 }
8350
8351 breakpoint_display_points
8352 }
8353
8354 fn breakpoint_context_menu(
8355 &self,
8356 anchor: Anchor,
8357 window: &mut Window,
8358 cx: &mut Context<Self>,
8359 ) -> Entity<ui::ContextMenu> {
8360 let weak_editor = cx.weak_entity();
8361 let focus_handle = self.focus_handle(cx);
8362
8363 let row = self
8364 .buffer
8365 .read(cx)
8366 .snapshot(cx)
8367 .summary_for_anchor::<Point>(&anchor)
8368 .row;
8369
8370 let breakpoint = self
8371 .breakpoint_at_row(row, window, cx)
8372 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8373
8374 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8375 "Edit Log Breakpoint"
8376 } else {
8377 "Set Log Breakpoint"
8378 };
8379
8380 let condition_breakpoint_msg = if breakpoint
8381 .as_ref()
8382 .is_some_and(|bp| bp.1.condition.is_some())
8383 {
8384 "Edit Condition Breakpoint"
8385 } else {
8386 "Set Condition Breakpoint"
8387 };
8388
8389 let hit_condition_breakpoint_msg = if breakpoint
8390 .as_ref()
8391 .is_some_and(|bp| bp.1.hit_condition.is_some())
8392 {
8393 "Edit Hit Condition Breakpoint"
8394 } else {
8395 "Set Hit Condition Breakpoint"
8396 };
8397
8398 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8399 "Unset Breakpoint"
8400 } else {
8401 "Set Breakpoint"
8402 };
8403
8404 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8405
8406 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8407 BreakpointState::Enabled => Some("Disable"),
8408 BreakpointState::Disabled => Some("Enable"),
8409 });
8410
8411 let (anchor, breakpoint) =
8412 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8413
8414 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8415 menu.on_blur_subscription(Subscription::new(|| {}))
8416 .context(focus_handle)
8417 .when(run_to_cursor, |this| {
8418 let weak_editor = weak_editor.clone();
8419 this.entry("Run to cursor", None, move |window, cx| {
8420 weak_editor
8421 .update(cx, |editor, cx| {
8422 editor.change_selections(
8423 SelectionEffects::no_scroll(),
8424 window,
8425 cx,
8426 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8427 );
8428 })
8429 .ok();
8430
8431 window.dispatch_action(Box::new(RunToCursor), cx);
8432 })
8433 .separator()
8434 })
8435 .when_some(toggle_state_msg, |this, msg| {
8436 this.entry(msg, None, {
8437 let weak_editor = weak_editor.clone();
8438 let breakpoint = breakpoint.clone();
8439 move |_window, cx| {
8440 weak_editor
8441 .update(cx, |this, cx| {
8442 this.edit_breakpoint_at_anchor(
8443 anchor,
8444 breakpoint.as_ref().clone(),
8445 BreakpointEditAction::InvertState,
8446 cx,
8447 );
8448 })
8449 .log_err();
8450 }
8451 })
8452 })
8453 .entry(set_breakpoint_msg, None, {
8454 let weak_editor = weak_editor.clone();
8455 let breakpoint = breakpoint.clone();
8456 move |_window, cx| {
8457 weak_editor
8458 .update(cx, |this, cx| {
8459 this.edit_breakpoint_at_anchor(
8460 anchor,
8461 breakpoint.as_ref().clone(),
8462 BreakpointEditAction::Toggle,
8463 cx,
8464 );
8465 })
8466 .log_err();
8467 }
8468 })
8469 .entry(log_breakpoint_msg, None, {
8470 let breakpoint = breakpoint.clone();
8471 let weak_editor = weak_editor.clone();
8472 move |window, cx| {
8473 weak_editor
8474 .update(cx, |this, cx| {
8475 this.add_edit_breakpoint_block(
8476 anchor,
8477 breakpoint.as_ref(),
8478 BreakpointPromptEditAction::Log,
8479 window,
8480 cx,
8481 );
8482 })
8483 .log_err();
8484 }
8485 })
8486 .entry(condition_breakpoint_msg, None, {
8487 let breakpoint = breakpoint.clone();
8488 let weak_editor = weak_editor.clone();
8489 move |window, cx| {
8490 weak_editor
8491 .update(cx, |this, cx| {
8492 this.add_edit_breakpoint_block(
8493 anchor,
8494 breakpoint.as_ref(),
8495 BreakpointPromptEditAction::Condition,
8496 window,
8497 cx,
8498 );
8499 })
8500 .log_err();
8501 }
8502 })
8503 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8504 weak_editor
8505 .update(cx, |this, cx| {
8506 this.add_edit_breakpoint_block(
8507 anchor,
8508 breakpoint.as_ref(),
8509 BreakpointPromptEditAction::HitCondition,
8510 window,
8511 cx,
8512 );
8513 })
8514 .log_err();
8515 })
8516 })
8517 }
8518
8519 fn render_breakpoint(
8520 &self,
8521 position: Anchor,
8522 row: DisplayRow,
8523 breakpoint: &Breakpoint,
8524 state: Option<BreakpointSessionState>,
8525 cx: &mut Context<Self>,
8526 ) -> IconButton {
8527 let is_rejected = state.is_some_and(|s| !s.verified);
8528 // Is it a breakpoint that shows up when hovering over gutter?
8529 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8530 (false, false),
8531 |PhantomBreakpointIndicator {
8532 is_active,
8533 display_row,
8534 collides_with_existing_breakpoint,
8535 }| {
8536 (
8537 is_active && display_row == row,
8538 collides_with_existing_breakpoint,
8539 )
8540 },
8541 );
8542
8543 let (color, icon) = {
8544 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8545 (false, false) => ui::IconName::DebugBreakpoint,
8546 (true, false) => ui::IconName::DebugLogBreakpoint,
8547 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8548 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8549 };
8550
8551 let color = cx.theme().colors();
8552
8553 let color = if is_phantom {
8554 if collides_with_existing {
8555 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8556 } else {
8557 Color::Hint
8558 }
8559 } else if is_rejected {
8560 Color::Disabled
8561 } else {
8562 Color::Debugger
8563 };
8564
8565 (color, icon)
8566 };
8567
8568 let breakpoint = Arc::from(breakpoint.clone());
8569
8570 let alt_as_text = gpui::Keystroke {
8571 modifiers: Modifiers::secondary_key(),
8572 ..Default::default()
8573 };
8574 let primary_action_text = if breakpoint.is_disabled() {
8575 "Enable breakpoint"
8576 } else if is_phantom && !collides_with_existing {
8577 "Set breakpoint"
8578 } else {
8579 "Unset breakpoint"
8580 };
8581 let focus_handle = self.focus_handle.clone();
8582
8583 let meta = if is_rejected {
8584 SharedString::from("No executable code is associated with this line.")
8585 } else if collides_with_existing && !breakpoint.is_disabled() {
8586 SharedString::from(format!(
8587 "{alt_as_text}-click to disable,\nright-click for more options."
8588 ))
8589 } else {
8590 SharedString::from("Right-click for more options.")
8591 };
8592 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8593 .icon_size(IconSize::XSmall)
8594 .size(ui::ButtonSize::None)
8595 .when(is_rejected, |this| {
8596 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8597 })
8598 .icon_color(color)
8599 .style(ButtonStyle::Transparent)
8600 .on_click(cx.listener({
8601 move |editor, event: &ClickEvent, window, cx| {
8602 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8603 BreakpointEditAction::InvertState
8604 } else {
8605 BreakpointEditAction::Toggle
8606 };
8607
8608 window.focus(&editor.focus_handle(cx), cx);
8609 editor.edit_breakpoint_at_anchor(
8610 position,
8611 breakpoint.as_ref().clone(),
8612 edit_action,
8613 cx,
8614 );
8615 }
8616 }))
8617 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8618 editor.set_breakpoint_context_menu(
8619 row,
8620 Some(position),
8621 event.position(),
8622 window,
8623 cx,
8624 );
8625 }))
8626 .tooltip(move |_window, cx| {
8627 Tooltip::with_meta_in(
8628 primary_action_text,
8629 Some(&ToggleBreakpoint),
8630 meta.clone(),
8631 &focus_handle,
8632 cx,
8633 )
8634 })
8635 }
8636
8637 fn build_tasks_context(
8638 project: &Entity<Project>,
8639 buffer: &Entity<Buffer>,
8640 buffer_row: u32,
8641 tasks: &Arc<RunnableTasks>,
8642 cx: &mut Context<Self>,
8643 ) -> Task<Option<task::TaskContext>> {
8644 let position = Point::new(buffer_row, tasks.column);
8645 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8646 let location = Location {
8647 buffer: buffer.clone(),
8648 range: range_start..range_start,
8649 };
8650 // Fill in the environmental variables from the tree-sitter captures
8651 let mut captured_task_variables = TaskVariables::default();
8652 for (capture_name, value) in tasks.extra_variables.clone() {
8653 captured_task_variables.insert(
8654 task::VariableName::Custom(capture_name.into()),
8655 value.clone(),
8656 );
8657 }
8658 project.update(cx, |project, cx| {
8659 project.task_store().update(cx, |task_store, cx| {
8660 task_store.task_context_for_location(captured_task_variables, location, cx)
8661 })
8662 })
8663 }
8664
8665 pub fn spawn_nearest_task(
8666 &mut self,
8667 action: &SpawnNearestTask,
8668 window: &mut Window,
8669 cx: &mut Context<Self>,
8670 ) {
8671 let Some((workspace, _)) = self.workspace.clone() else {
8672 return;
8673 };
8674 let Some(project) = self.project.clone() else {
8675 return;
8676 };
8677
8678 // Try to find a closest, enclosing node using tree-sitter that has a task
8679 let Some((buffer, buffer_row, tasks)) = self
8680 .find_enclosing_node_task(cx)
8681 // Or find the task that's closest in row-distance.
8682 .or_else(|| self.find_closest_task(cx))
8683 else {
8684 return;
8685 };
8686
8687 let reveal_strategy = action.reveal;
8688 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8689 cx.spawn_in(window, async move |_, cx| {
8690 let context = task_context.await?;
8691 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8692
8693 let resolved = &mut resolved_task.resolved;
8694 resolved.reveal = reveal_strategy;
8695
8696 workspace
8697 .update_in(cx, |workspace, window, cx| {
8698 workspace.schedule_resolved_task(
8699 task_source_kind,
8700 resolved_task,
8701 false,
8702 window,
8703 cx,
8704 );
8705 })
8706 .ok()
8707 })
8708 .detach();
8709 }
8710
8711 fn find_closest_task(
8712 &mut self,
8713 cx: &mut Context<Self>,
8714 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8715 let cursor_row = self
8716 .selections
8717 .newest_adjusted(&self.display_snapshot(cx))
8718 .head()
8719 .row;
8720
8721 let ((buffer_id, row), tasks) = self
8722 .tasks
8723 .iter()
8724 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8725
8726 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8727 let tasks = Arc::new(tasks.to_owned());
8728 Some((buffer, *row, tasks))
8729 }
8730
8731 fn find_enclosing_node_task(
8732 &mut self,
8733 cx: &mut Context<Self>,
8734 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8735 let snapshot = self.buffer.read(cx).snapshot(cx);
8736 let offset = self
8737 .selections
8738 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8739 .head();
8740 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8741 let offset = excerpt.map_offset_to_buffer(offset);
8742 let buffer_id = excerpt.buffer().remote_id();
8743
8744 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8745 let mut cursor = layer.node().walk();
8746
8747 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8748 if cursor.node().end_byte() == offset.0 {
8749 cursor.goto_next_sibling();
8750 }
8751 }
8752
8753 // Ascend to the smallest ancestor that contains the range and has a task.
8754 loop {
8755 let node = cursor.node();
8756 let node_range = node.byte_range();
8757 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8758
8759 // Check if this node contains our offset
8760 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8761 // If it contains offset, check for task
8762 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8763 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8764 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8765 }
8766 }
8767
8768 if !cursor.goto_parent() {
8769 break;
8770 }
8771 }
8772 None
8773 }
8774
8775 fn render_run_indicator(
8776 &self,
8777 _style: &EditorStyle,
8778 is_active: bool,
8779 row: DisplayRow,
8780 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8781 cx: &mut Context<Self>,
8782 ) -> IconButton {
8783 let color = Color::Muted;
8784 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8785
8786 IconButton::new(
8787 ("run_indicator", row.0 as usize),
8788 ui::IconName::PlayOutlined,
8789 )
8790 .shape(ui::IconButtonShape::Square)
8791 .icon_size(IconSize::XSmall)
8792 .icon_color(color)
8793 .toggle_state(is_active)
8794 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8795 let quick_launch = match e {
8796 ClickEvent::Keyboard(_) => true,
8797 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8798 };
8799
8800 window.focus(&editor.focus_handle(cx), cx);
8801 editor.toggle_code_actions(
8802 &ToggleCodeActions {
8803 deployed_from: Some(CodeActionSource::RunMenu(row)),
8804 quick_launch,
8805 },
8806 window,
8807 cx,
8808 );
8809 }))
8810 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8811 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8812 }))
8813 }
8814
8815 pub fn context_menu_visible(&self) -> bool {
8816 !self.edit_prediction_preview_is_active()
8817 && self
8818 .context_menu
8819 .borrow()
8820 .as_ref()
8821 .is_some_and(|menu| menu.visible())
8822 }
8823
8824 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8825 self.context_menu
8826 .borrow()
8827 .as_ref()
8828 .map(|menu| menu.origin())
8829 }
8830
8831 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8832 self.context_menu_options = Some(options);
8833 }
8834
8835 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8836 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8837
8838 fn render_edit_prediction_popover(
8839 &mut self,
8840 text_bounds: &Bounds<Pixels>,
8841 content_origin: gpui::Point<Pixels>,
8842 right_margin: Pixels,
8843 editor_snapshot: &EditorSnapshot,
8844 visible_row_range: Range<DisplayRow>,
8845 scroll_top: ScrollOffset,
8846 scroll_bottom: ScrollOffset,
8847 line_layouts: &[LineWithInvisibles],
8848 line_height: Pixels,
8849 scroll_position: gpui::Point<ScrollOffset>,
8850 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8851 newest_selection_head: Option<DisplayPoint>,
8852 editor_width: Pixels,
8853 style: &EditorStyle,
8854 window: &mut Window,
8855 cx: &mut App,
8856 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8857 if self.mode().is_minimap() {
8858 return None;
8859 }
8860 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8861
8862 if self.edit_prediction_visible_in_cursor_popover(true) {
8863 return None;
8864 }
8865
8866 match &active_edit_prediction.completion {
8867 EditPrediction::MoveWithin { target, .. } => {
8868 let target_display_point = target.to_display_point(editor_snapshot);
8869
8870 if self.edit_prediction_requires_modifier() {
8871 if !self.edit_prediction_preview_is_active() {
8872 return None;
8873 }
8874
8875 self.render_edit_prediction_modifier_jump_popover(
8876 text_bounds,
8877 content_origin,
8878 visible_row_range,
8879 line_layouts,
8880 line_height,
8881 scroll_pixel_position,
8882 newest_selection_head,
8883 target_display_point,
8884 window,
8885 cx,
8886 )
8887 } else {
8888 self.render_edit_prediction_eager_jump_popover(
8889 text_bounds,
8890 content_origin,
8891 editor_snapshot,
8892 visible_row_range,
8893 scroll_top,
8894 scroll_bottom,
8895 line_height,
8896 scroll_pixel_position,
8897 target_display_point,
8898 editor_width,
8899 window,
8900 cx,
8901 )
8902 }
8903 }
8904 EditPrediction::Edit {
8905 display_mode: EditDisplayMode::Inline,
8906 ..
8907 } => None,
8908 EditPrediction::Edit {
8909 display_mode: EditDisplayMode::TabAccept,
8910 edits,
8911 ..
8912 } => {
8913 let range = &edits.first()?.0;
8914 let target_display_point = range.end.to_display_point(editor_snapshot);
8915
8916 self.render_edit_prediction_end_of_line_popover(
8917 "Accept",
8918 editor_snapshot,
8919 visible_row_range,
8920 target_display_point,
8921 line_height,
8922 scroll_pixel_position,
8923 content_origin,
8924 editor_width,
8925 window,
8926 cx,
8927 )
8928 }
8929 EditPrediction::Edit {
8930 edits,
8931 edit_preview,
8932 display_mode: EditDisplayMode::DiffPopover,
8933 snapshot,
8934 } => self.render_edit_prediction_diff_popover(
8935 text_bounds,
8936 content_origin,
8937 right_margin,
8938 editor_snapshot,
8939 visible_row_range,
8940 line_layouts,
8941 line_height,
8942 scroll_position,
8943 scroll_pixel_position,
8944 newest_selection_head,
8945 editor_width,
8946 style,
8947 edits,
8948 edit_preview,
8949 snapshot,
8950 window,
8951 cx,
8952 ),
8953 EditPrediction::MoveOutside { snapshot, .. } => {
8954 let mut element = self
8955 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8956 .into_any();
8957
8958 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8959 let origin_x = text_bounds.size.width - size.width - px(30.);
8960 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8961 element.prepaint_at(origin, window, cx);
8962
8963 Some((element, origin))
8964 }
8965 }
8966 }
8967
8968 fn render_edit_prediction_modifier_jump_popover(
8969 &mut self,
8970 text_bounds: &Bounds<Pixels>,
8971 content_origin: gpui::Point<Pixels>,
8972 visible_row_range: Range<DisplayRow>,
8973 line_layouts: &[LineWithInvisibles],
8974 line_height: Pixels,
8975 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8976 newest_selection_head: Option<DisplayPoint>,
8977 target_display_point: DisplayPoint,
8978 window: &mut Window,
8979 cx: &mut App,
8980 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8981 let scrolled_content_origin =
8982 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8983
8984 const SCROLL_PADDING_Y: Pixels = px(12.);
8985
8986 if target_display_point.row() < visible_row_range.start {
8987 return self.render_edit_prediction_scroll_popover(
8988 |_| SCROLL_PADDING_Y,
8989 IconName::ArrowUp,
8990 visible_row_range,
8991 line_layouts,
8992 newest_selection_head,
8993 scrolled_content_origin,
8994 window,
8995 cx,
8996 );
8997 } else if target_display_point.row() >= visible_row_range.end {
8998 return self.render_edit_prediction_scroll_popover(
8999 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9000 IconName::ArrowDown,
9001 visible_row_range,
9002 line_layouts,
9003 newest_selection_head,
9004 scrolled_content_origin,
9005 window,
9006 cx,
9007 );
9008 }
9009
9010 const POLE_WIDTH: Pixels = px(2.);
9011
9012 let line_layout =
9013 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9014 let target_column = target_display_point.column() as usize;
9015
9016 let target_x = line_layout.x_for_index(target_column);
9017 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9018 - scroll_pixel_position.y;
9019
9020 let flag_on_right = target_x < text_bounds.size.width / 2.;
9021
9022 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9023 border_color.l += 0.001;
9024
9025 let mut element = v_flex()
9026 .items_end()
9027 .when(flag_on_right, |el| el.items_start())
9028 .child(if flag_on_right {
9029 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9030 .rounded_bl(px(0.))
9031 .rounded_tl(px(0.))
9032 .border_l_2()
9033 .border_color(border_color)
9034 } else {
9035 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9036 .rounded_br(px(0.))
9037 .rounded_tr(px(0.))
9038 .border_r_2()
9039 .border_color(border_color)
9040 })
9041 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9042 .into_any();
9043
9044 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9045
9046 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9047 - point(
9048 if flag_on_right {
9049 POLE_WIDTH
9050 } else {
9051 size.width - POLE_WIDTH
9052 },
9053 size.height - line_height,
9054 );
9055
9056 origin.x = origin.x.max(content_origin.x);
9057
9058 element.prepaint_at(origin, window, cx);
9059
9060 Some((element, origin))
9061 }
9062
9063 fn render_edit_prediction_scroll_popover(
9064 &mut self,
9065 to_y: impl Fn(Size<Pixels>) -> Pixels,
9066 scroll_icon: IconName,
9067 visible_row_range: Range<DisplayRow>,
9068 line_layouts: &[LineWithInvisibles],
9069 newest_selection_head: Option<DisplayPoint>,
9070 scrolled_content_origin: gpui::Point<Pixels>,
9071 window: &mut Window,
9072 cx: &mut App,
9073 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9074 let mut element = self
9075 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9076 .into_any();
9077
9078 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9079
9080 let cursor = newest_selection_head?;
9081 let cursor_row_layout =
9082 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9083 let cursor_column = cursor.column() as usize;
9084
9085 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9086
9087 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9088
9089 element.prepaint_at(origin, window, cx);
9090 Some((element, origin))
9091 }
9092
9093 fn render_edit_prediction_eager_jump_popover(
9094 &mut self,
9095 text_bounds: &Bounds<Pixels>,
9096 content_origin: gpui::Point<Pixels>,
9097 editor_snapshot: &EditorSnapshot,
9098 visible_row_range: Range<DisplayRow>,
9099 scroll_top: ScrollOffset,
9100 scroll_bottom: ScrollOffset,
9101 line_height: Pixels,
9102 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9103 target_display_point: DisplayPoint,
9104 editor_width: Pixels,
9105 window: &mut Window,
9106 cx: &mut App,
9107 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9108 if target_display_point.row().as_f64() < scroll_top {
9109 let mut element = self
9110 .render_edit_prediction_line_popover(
9111 "Jump to Edit",
9112 Some(IconName::ArrowUp),
9113 window,
9114 cx,
9115 )
9116 .into_any();
9117
9118 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9119 let offset = point(
9120 (text_bounds.size.width - size.width) / 2.,
9121 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9122 );
9123
9124 let origin = text_bounds.origin + offset;
9125 element.prepaint_at(origin, window, cx);
9126 Some((element, origin))
9127 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9128 let mut element = self
9129 .render_edit_prediction_line_popover(
9130 "Jump to Edit",
9131 Some(IconName::ArrowDown),
9132 window,
9133 cx,
9134 )
9135 .into_any();
9136
9137 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9138 let offset = point(
9139 (text_bounds.size.width - size.width) / 2.,
9140 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9141 );
9142
9143 let origin = text_bounds.origin + offset;
9144 element.prepaint_at(origin, window, cx);
9145 Some((element, origin))
9146 } else {
9147 self.render_edit_prediction_end_of_line_popover(
9148 "Jump to Edit",
9149 editor_snapshot,
9150 visible_row_range,
9151 target_display_point,
9152 line_height,
9153 scroll_pixel_position,
9154 content_origin,
9155 editor_width,
9156 window,
9157 cx,
9158 )
9159 }
9160 }
9161
9162 fn render_edit_prediction_end_of_line_popover(
9163 self: &mut Editor,
9164 label: &'static str,
9165 editor_snapshot: &EditorSnapshot,
9166 visible_row_range: Range<DisplayRow>,
9167 target_display_point: DisplayPoint,
9168 line_height: Pixels,
9169 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9170 content_origin: gpui::Point<Pixels>,
9171 editor_width: Pixels,
9172 window: &mut Window,
9173 cx: &mut App,
9174 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9175 let target_line_end = DisplayPoint::new(
9176 target_display_point.row(),
9177 editor_snapshot.line_len(target_display_point.row()),
9178 );
9179
9180 let mut element = self
9181 .render_edit_prediction_line_popover(label, None, window, cx)
9182 .into_any();
9183
9184 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9185
9186 let line_origin =
9187 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9188
9189 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9190 let mut origin = start_point
9191 + line_origin
9192 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9193 origin.x = origin.x.max(content_origin.x);
9194
9195 let max_x = content_origin.x + editor_width - size.width;
9196
9197 if origin.x > max_x {
9198 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9199
9200 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9201 origin.y += offset;
9202 IconName::ArrowUp
9203 } else {
9204 origin.y -= offset;
9205 IconName::ArrowDown
9206 };
9207
9208 element = self
9209 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9210 .into_any();
9211
9212 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9213
9214 origin.x = content_origin.x + editor_width - size.width - px(2.);
9215 }
9216
9217 element.prepaint_at(origin, window, cx);
9218 Some((element, origin))
9219 }
9220
9221 fn render_edit_prediction_diff_popover(
9222 self: &Editor,
9223 text_bounds: &Bounds<Pixels>,
9224 content_origin: gpui::Point<Pixels>,
9225 right_margin: Pixels,
9226 editor_snapshot: &EditorSnapshot,
9227 visible_row_range: Range<DisplayRow>,
9228 line_layouts: &[LineWithInvisibles],
9229 line_height: Pixels,
9230 scroll_position: gpui::Point<ScrollOffset>,
9231 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9232 newest_selection_head: Option<DisplayPoint>,
9233 editor_width: Pixels,
9234 style: &EditorStyle,
9235 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9236 edit_preview: &Option<language::EditPreview>,
9237 snapshot: &language::BufferSnapshot,
9238 window: &mut Window,
9239 cx: &mut App,
9240 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9241 let edit_start = edits
9242 .first()
9243 .unwrap()
9244 .0
9245 .start
9246 .to_display_point(editor_snapshot);
9247 let edit_end = edits
9248 .last()
9249 .unwrap()
9250 .0
9251 .end
9252 .to_display_point(editor_snapshot);
9253
9254 let is_visible = visible_row_range.contains(&edit_start.row())
9255 || visible_row_range.contains(&edit_end.row());
9256 if !is_visible {
9257 return None;
9258 }
9259
9260 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9261 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9262 } else {
9263 // Fallback for providers without edit_preview
9264 crate::edit_prediction_fallback_text(edits, cx)
9265 };
9266
9267 let styled_text = highlighted_edits.to_styled_text(&style.text);
9268 let line_count = highlighted_edits.text.lines().count();
9269
9270 const BORDER_WIDTH: Pixels = px(1.);
9271
9272 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9273 let has_keybind = keybind.is_some();
9274
9275 let mut element = h_flex()
9276 .items_start()
9277 .child(
9278 h_flex()
9279 .bg(cx.theme().colors().editor_background)
9280 .border(BORDER_WIDTH)
9281 .shadow_xs()
9282 .border_color(cx.theme().colors().border)
9283 .rounded_l_lg()
9284 .when(line_count > 1, |el| el.rounded_br_lg())
9285 .pr_1()
9286 .child(styled_text),
9287 )
9288 .child(
9289 h_flex()
9290 .h(line_height + BORDER_WIDTH * 2.)
9291 .px_1p5()
9292 .gap_1()
9293 // Workaround: For some reason, there's a gap if we don't do this
9294 .ml(-BORDER_WIDTH)
9295 .shadow(vec![gpui::BoxShadow {
9296 color: gpui::black().opacity(0.05),
9297 offset: point(px(1.), px(1.)),
9298 blur_radius: px(2.),
9299 spread_radius: px(0.),
9300 }])
9301 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9302 .border(BORDER_WIDTH)
9303 .border_color(cx.theme().colors().border)
9304 .rounded_r_lg()
9305 .id("edit_prediction_diff_popover_keybind")
9306 .when(!has_keybind, |el| {
9307 let status_colors = cx.theme().status();
9308
9309 el.bg(status_colors.error_background)
9310 .border_color(status_colors.error.opacity(0.6))
9311 .child(Icon::new(IconName::Info).color(Color::Error))
9312 .cursor_default()
9313 .hoverable_tooltip(move |_window, cx| {
9314 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9315 })
9316 })
9317 .children(keybind),
9318 )
9319 .into_any();
9320
9321 let longest_row =
9322 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9323 let longest_line_width = if visible_row_range.contains(&longest_row) {
9324 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9325 } else {
9326 layout_line(
9327 longest_row,
9328 editor_snapshot,
9329 style,
9330 editor_width,
9331 |_| false,
9332 window,
9333 cx,
9334 )
9335 .width
9336 };
9337
9338 let viewport_bounds =
9339 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9340 right: -right_margin,
9341 ..Default::default()
9342 });
9343
9344 let x_after_longest = Pixels::from(
9345 ScrollPixelOffset::from(
9346 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9347 ) - scroll_pixel_position.x,
9348 );
9349
9350 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9351
9352 // Fully visible if it can be displayed within the window (allow overlapping other
9353 // panes). However, this is only allowed if the popover starts within text_bounds.
9354 let can_position_to_the_right = x_after_longest < text_bounds.right()
9355 && x_after_longest + element_bounds.width < viewport_bounds.right();
9356
9357 let mut origin = if can_position_to_the_right {
9358 point(
9359 x_after_longest,
9360 text_bounds.origin.y
9361 + Pixels::from(
9362 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9363 - scroll_pixel_position.y,
9364 ),
9365 )
9366 } else {
9367 let cursor_row = newest_selection_head.map(|head| head.row());
9368 let above_edit = edit_start
9369 .row()
9370 .0
9371 .checked_sub(line_count as u32)
9372 .map(DisplayRow);
9373 let below_edit = Some(edit_end.row() + 1);
9374 let above_cursor =
9375 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9376 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9377
9378 // Place the edit popover adjacent to the edit if there is a location
9379 // available that is onscreen and does not obscure the cursor. Otherwise,
9380 // place it adjacent to the cursor.
9381 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9382 .into_iter()
9383 .flatten()
9384 .find(|&start_row| {
9385 let end_row = start_row + line_count as u32;
9386 visible_row_range.contains(&start_row)
9387 && visible_row_range.contains(&end_row)
9388 && cursor_row
9389 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9390 })?;
9391
9392 content_origin
9393 + point(
9394 Pixels::from(-scroll_pixel_position.x),
9395 Pixels::from(
9396 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9397 ),
9398 )
9399 };
9400
9401 origin.x -= BORDER_WIDTH;
9402
9403 window.defer_draw(element, origin, 1);
9404
9405 // Do not return an element, since it will already be drawn due to defer_draw.
9406 None
9407 }
9408
9409 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9410 px(30.)
9411 }
9412
9413 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9414 if self.read_only(cx) {
9415 cx.theme().players().read_only()
9416 } else {
9417 self.style.as_ref().unwrap().local_player
9418 }
9419 }
9420
9421 fn render_edit_prediction_accept_keybind(
9422 &self,
9423 window: &mut Window,
9424 cx: &mut App,
9425 ) -> Option<AnyElement> {
9426 let accept_binding =
9427 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9428 let accept_keystroke = accept_binding.keystroke()?;
9429
9430 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9431
9432 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9433 Color::Accent
9434 } else {
9435 Color::Muted
9436 };
9437
9438 h_flex()
9439 .px_0p5()
9440 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9441 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9442 .text_size(TextSize::XSmall.rems(cx))
9443 .child(h_flex().children(ui::render_modifiers(
9444 accept_keystroke.modifiers(),
9445 PlatformStyle::platform(),
9446 Some(modifiers_color),
9447 Some(IconSize::XSmall.rems().into()),
9448 true,
9449 )))
9450 .when(is_platform_style_mac, |parent| {
9451 parent.child(accept_keystroke.key().to_string())
9452 })
9453 .when(!is_platform_style_mac, |parent| {
9454 parent.child(
9455 Key::new(
9456 util::capitalize(accept_keystroke.key()),
9457 Some(Color::Default),
9458 )
9459 .size(Some(IconSize::XSmall.rems().into())),
9460 )
9461 })
9462 .into_any()
9463 .into()
9464 }
9465
9466 fn render_edit_prediction_line_popover(
9467 &self,
9468 label: impl Into<SharedString>,
9469 icon: Option<IconName>,
9470 window: &mut Window,
9471 cx: &mut App,
9472 ) -> Stateful<Div> {
9473 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9474
9475 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9476 let has_keybind = keybind.is_some();
9477
9478 h_flex()
9479 .id("ep-line-popover")
9480 .py_0p5()
9481 .pl_1()
9482 .pr(padding_right)
9483 .gap_1()
9484 .rounded_md()
9485 .border_1()
9486 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9487 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9488 .shadow_xs()
9489 .when(!has_keybind, |el| {
9490 let status_colors = cx.theme().status();
9491
9492 el.bg(status_colors.error_background)
9493 .border_color(status_colors.error.opacity(0.6))
9494 .pl_2()
9495 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9496 .cursor_default()
9497 .hoverable_tooltip(move |_window, cx| {
9498 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9499 })
9500 })
9501 .children(keybind)
9502 .child(
9503 Label::new(label)
9504 .size(LabelSize::Small)
9505 .when(!has_keybind, |el| {
9506 el.color(cx.theme().status().error.into()).strikethrough()
9507 }),
9508 )
9509 .when(!has_keybind, |el| {
9510 el.child(
9511 h_flex().ml_1().child(
9512 Icon::new(IconName::Info)
9513 .size(IconSize::Small)
9514 .color(cx.theme().status().error.into()),
9515 ),
9516 )
9517 })
9518 .when_some(icon, |element, icon| {
9519 element.child(
9520 div()
9521 .mt(px(1.5))
9522 .child(Icon::new(icon).size(IconSize::Small)),
9523 )
9524 })
9525 }
9526
9527 fn render_edit_prediction_jump_outside_popover(
9528 &self,
9529 snapshot: &BufferSnapshot,
9530 window: &mut Window,
9531 cx: &mut App,
9532 ) -> Stateful<Div> {
9533 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9534 let has_keybind = keybind.is_some();
9535
9536 let file_name = snapshot
9537 .file()
9538 .map(|file| SharedString::new(file.file_name(cx)))
9539 .unwrap_or(SharedString::new_static("untitled"));
9540
9541 h_flex()
9542 .id("ep-jump-outside-popover")
9543 .py_1()
9544 .px_2()
9545 .gap_1()
9546 .rounded_md()
9547 .border_1()
9548 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9549 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9550 .shadow_xs()
9551 .when(!has_keybind, |el| {
9552 let status_colors = cx.theme().status();
9553
9554 el.bg(status_colors.error_background)
9555 .border_color(status_colors.error.opacity(0.6))
9556 .pl_2()
9557 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9558 .cursor_default()
9559 .hoverable_tooltip(move |_window, cx| {
9560 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9561 })
9562 })
9563 .children(keybind)
9564 .child(
9565 Label::new(file_name)
9566 .size(LabelSize::Small)
9567 .buffer_font(cx)
9568 .when(!has_keybind, |el| {
9569 el.color(cx.theme().status().error.into()).strikethrough()
9570 }),
9571 )
9572 .when(!has_keybind, |el| {
9573 el.child(
9574 h_flex().ml_1().child(
9575 Icon::new(IconName::Info)
9576 .size(IconSize::Small)
9577 .color(cx.theme().status().error.into()),
9578 ),
9579 )
9580 })
9581 .child(
9582 div()
9583 .mt(px(1.5))
9584 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9585 )
9586 }
9587
9588 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9589 let accent_color = cx.theme().colors().text_accent;
9590 let editor_bg_color = cx.theme().colors().editor_background;
9591 editor_bg_color.blend(accent_color.opacity(0.1))
9592 }
9593
9594 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9595 let accent_color = cx.theme().colors().text_accent;
9596 let editor_bg_color = cx.theme().colors().editor_background;
9597 editor_bg_color.blend(accent_color.opacity(0.6))
9598 }
9599 fn get_prediction_provider_icon_name(
9600 provider: &Option<RegisteredEditPredictionDelegate>,
9601 ) -> IconName {
9602 match provider {
9603 Some(provider) => match provider.provider.name() {
9604 "copilot" => IconName::Copilot,
9605 "supermaven" => IconName::Supermaven,
9606 _ => IconName::ZedPredict,
9607 },
9608 None => IconName::ZedPredict,
9609 }
9610 }
9611
9612 fn render_edit_prediction_cursor_popover(
9613 &self,
9614 min_width: Pixels,
9615 max_width: Pixels,
9616 cursor_point: Point,
9617 style: &EditorStyle,
9618 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9619 _window: &Window,
9620 cx: &mut Context<Editor>,
9621 ) -> Option<AnyElement> {
9622 let provider = self.edit_prediction_provider.as_ref()?;
9623 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9624
9625 let is_refreshing = provider.provider.is_refreshing(cx);
9626
9627 fn pending_completion_container(icon: IconName) -> Div {
9628 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9629 }
9630
9631 let completion = match &self.active_edit_prediction {
9632 Some(prediction) => {
9633 if !self.has_visible_completions_menu() {
9634 const RADIUS: Pixels = px(6.);
9635 const BORDER_WIDTH: Pixels = px(1.);
9636
9637 return Some(
9638 h_flex()
9639 .elevation_2(cx)
9640 .border(BORDER_WIDTH)
9641 .border_color(cx.theme().colors().border)
9642 .when(accept_keystroke.is_none(), |el| {
9643 el.border_color(cx.theme().status().error)
9644 })
9645 .rounded(RADIUS)
9646 .rounded_tl(px(0.))
9647 .overflow_hidden()
9648 .child(div().px_1p5().child(match &prediction.completion {
9649 EditPrediction::MoveWithin { target, snapshot } => {
9650 use text::ToPoint as _;
9651 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9652 {
9653 Icon::new(IconName::ZedPredictDown)
9654 } else {
9655 Icon::new(IconName::ZedPredictUp)
9656 }
9657 }
9658 EditPrediction::MoveOutside { .. } => {
9659 // TODO [zeta2] custom icon for external jump?
9660 Icon::new(provider_icon)
9661 }
9662 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9663 }))
9664 .child(
9665 h_flex()
9666 .gap_1()
9667 .py_1()
9668 .px_2()
9669 .rounded_r(RADIUS - BORDER_WIDTH)
9670 .border_l_1()
9671 .border_color(cx.theme().colors().border)
9672 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9673 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9674 el.child(
9675 Label::new("Hold")
9676 .size(LabelSize::Small)
9677 .when(accept_keystroke.is_none(), |el| {
9678 el.strikethrough()
9679 })
9680 .line_height_style(LineHeightStyle::UiLabel),
9681 )
9682 })
9683 .id("edit_prediction_cursor_popover_keybind")
9684 .when(accept_keystroke.is_none(), |el| {
9685 let status_colors = cx.theme().status();
9686
9687 el.bg(status_colors.error_background)
9688 .border_color(status_colors.error.opacity(0.6))
9689 .child(Icon::new(IconName::Info).color(Color::Error))
9690 .cursor_default()
9691 .hoverable_tooltip(move |_window, cx| {
9692 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9693 .into()
9694 })
9695 })
9696 .when_some(
9697 accept_keystroke.as_ref(),
9698 |el, accept_keystroke| {
9699 el.child(h_flex().children(ui::render_modifiers(
9700 accept_keystroke.modifiers(),
9701 PlatformStyle::platform(),
9702 Some(Color::Default),
9703 Some(IconSize::XSmall.rems().into()),
9704 false,
9705 )))
9706 },
9707 ),
9708 )
9709 .into_any(),
9710 );
9711 }
9712
9713 self.render_edit_prediction_cursor_popover_preview(
9714 prediction,
9715 cursor_point,
9716 style,
9717 cx,
9718 )?
9719 }
9720
9721 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9722 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9723 stale_completion,
9724 cursor_point,
9725 style,
9726 cx,
9727 )?,
9728
9729 None => pending_completion_container(provider_icon)
9730 .child(Label::new("...").size(LabelSize::Small)),
9731 },
9732
9733 None => pending_completion_container(provider_icon)
9734 .child(Label::new("...").size(LabelSize::Small)),
9735 };
9736
9737 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9738 completion
9739 .with_animation(
9740 "loading-completion",
9741 Animation::new(Duration::from_secs(2))
9742 .repeat()
9743 .with_easing(pulsating_between(0.4, 0.8)),
9744 |label, delta| label.opacity(delta),
9745 )
9746 .into_any_element()
9747 } else {
9748 completion.into_any_element()
9749 };
9750
9751 let has_completion = self.active_edit_prediction.is_some();
9752
9753 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9754 Some(
9755 h_flex()
9756 .min_w(min_width)
9757 .max_w(max_width)
9758 .flex_1()
9759 .elevation_2(cx)
9760 .border_color(cx.theme().colors().border)
9761 .child(
9762 div()
9763 .flex_1()
9764 .py_1()
9765 .px_2()
9766 .overflow_hidden()
9767 .child(completion),
9768 )
9769 .when_some(accept_keystroke, |el, accept_keystroke| {
9770 if !accept_keystroke.modifiers().modified() {
9771 return el;
9772 }
9773
9774 el.child(
9775 h_flex()
9776 .h_full()
9777 .border_l_1()
9778 .rounded_r_lg()
9779 .border_color(cx.theme().colors().border)
9780 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9781 .gap_1()
9782 .py_1()
9783 .px_2()
9784 .child(
9785 h_flex()
9786 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9787 .when(is_platform_style_mac, |parent| parent.gap_1())
9788 .child(h_flex().children(ui::render_modifiers(
9789 accept_keystroke.modifiers(),
9790 PlatformStyle::platform(),
9791 Some(if !has_completion {
9792 Color::Muted
9793 } else {
9794 Color::Default
9795 }),
9796 None,
9797 false,
9798 ))),
9799 )
9800 .child(Label::new("Preview").into_any_element())
9801 .opacity(if has_completion { 1.0 } else { 0.4 }),
9802 )
9803 })
9804 .into_any(),
9805 )
9806 }
9807
9808 fn render_edit_prediction_cursor_popover_preview(
9809 &self,
9810 completion: &EditPredictionState,
9811 cursor_point: Point,
9812 style: &EditorStyle,
9813 cx: &mut Context<Editor>,
9814 ) -> Option<Div> {
9815 use text::ToPoint as _;
9816
9817 fn render_relative_row_jump(
9818 prefix: impl Into<String>,
9819 current_row: u32,
9820 target_row: u32,
9821 ) -> Div {
9822 let (row_diff, arrow) = if target_row < current_row {
9823 (current_row - target_row, IconName::ArrowUp)
9824 } else {
9825 (target_row - current_row, IconName::ArrowDown)
9826 };
9827
9828 h_flex()
9829 .child(
9830 Label::new(format!("{}{}", prefix.into(), row_diff))
9831 .color(Color::Muted)
9832 .size(LabelSize::Small),
9833 )
9834 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9835 }
9836
9837 let supports_jump = self
9838 .edit_prediction_provider
9839 .as_ref()
9840 .map(|provider| provider.provider.supports_jump_to_edit())
9841 .unwrap_or(true);
9842
9843 match &completion.completion {
9844 EditPrediction::MoveWithin {
9845 target, snapshot, ..
9846 } => {
9847 if !supports_jump {
9848 return None;
9849 }
9850
9851 Some(
9852 h_flex()
9853 .px_2()
9854 .gap_2()
9855 .flex_1()
9856 .child(
9857 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9858 Icon::new(IconName::ZedPredictDown)
9859 } else {
9860 Icon::new(IconName::ZedPredictUp)
9861 },
9862 )
9863 .child(Label::new("Jump to Edit")),
9864 )
9865 }
9866 EditPrediction::MoveOutside { snapshot, .. } => {
9867 let file_name = snapshot
9868 .file()
9869 .map(|file| file.file_name(cx))
9870 .unwrap_or("untitled");
9871 Some(
9872 h_flex()
9873 .px_2()
9874 .gap_2()
9875 .flex_1()
9876 .child(Icon::new(IconName::ZedPredict))
9877 .child(Label::new(format!("Jump to {file_name}"))),
9878 )
9879 }
9880 EditPrediction::Edit {
9881 edits,
9882 edit_preview,
9883 snapshot,
9884 display_mode: _,
9885 } => {
9886 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9887
9888 let (highlighted_edits, has_more_lines) =
9889 if let Some(edit_preview) = edit_preview.as_ref() {
9890 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9891 .first_line_preview()
9892 } else {
9893 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9894 };
9895
9896 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9897 .with_default_highlights(&style.text, highlighted_edits.highlights);
9898
9899 let preview = h_flex()
9900 .gap_1()
9901 .min_w_16()
9902 .child(styled_text)
9903 .when(has_more_lines, |parent| parent.child("…"));
9904
9905 let left = if supports_jump && first_edit_row != cursor_point.row {
9906 render_relative_row_jump("", cursor_point.row, first_edit_row)
9907 .into_any_element()
9908 } else {
9909 let icon_name =
9910 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9911 Icon::new(icon_name).into_any_element()
9912 };
9913
9914 Some(
9915 h_flex()
9916 .h_full()
9917 .flex_1()
9918 .gap_2()
9919 .pr_1()
9920 .overflow_x_hidden()
9921 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9922 .child(left)
9923 .child(preview),
9924 )
9925 }
9926 }
9927 }
9928
9929 pub fn render_context_menu(
9930 &mut self,
9931 max_height_in_lines: u32,
9932 window: &mut Window,
9933 cx: &mut Context<Editor>,
9934 ) -> Option<AnyElement> {
9935 let menu = self.context_menu.borrow();
9936 let menu = menu.as_ref()?;
9937 if !menu.visible() {
9938 return None;
9939 };
9940 self.style
9941 .as_ref()
9942 .map(|style| menu.render(style, max_height_in_lines, window, cx))
9943 }
9944
9945 fn render_context_menu_aside(
9946 &mut self,
9947 max_size: Size<Pixels>,
9948 window: &mut Window,
9949 cx: &mut Context<Editor>,
9950 ) -> Option<AnyElement> {
9951 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9952 if menu.visible() {
9953 menu.render_aside(max_size, window, cx)
9954 } else {
9955 None
9956 }
9957 })
9958 }
9959
9960 fn hide_context_menu(
9961 &mut self,
9962 window: &mut Window,
9963 cx: &mut Context<Self>,
9964 ) -> Option<CodeContextMenu> {
9965 cx.notify();
9966 self.completion_tasks.clear();
9967 let context_menu = self.context_menu.borrow_mut().take();
9968 self.stale_edit_prediction_in_menu.take();
9969 self.update_visible_edit_prediction(window, cx);
9970 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9971 && let Some(completion_provider) = &self.completion_provider
9972 {
9973 completion_provider.selection_changed(None, window, cx);
9974 }
9975 context_menu
9976 }
9977
9978 fn show_snippet_choices(
9979 &mut self,
9980 choices: &Vec<String>,
9981 selection: Range<Anchor>,
9982 cx: &mut Context<Self>,
9983 ) {
9984 let Some((_, buffer, _)) = self
9985 .buffer()
9986 .read(cx)
9987 .excerpt_containing(selection.start, cx)
9988 else {
9989 return;
9990 };
9991 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9992 else {
9993 return;
9994 };
9995 if buffer != end_buffer {
9996 log::error!("expected anchor range to have matching buffer IDs");
9997 return;
9998 }
9999
10000 let id = post_inc(&mut self.next_completion_id);
10001 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10002 let mut context_menu = self.context_menu.borrow_mut();
10003 let old_menu = context_menu.take();
10004 *context_menu = Some(CodeContextMenu::Completions(
10005 CompletionsMenu::new_snippet_choices(
10006 id,
10007 true,
10008 choices,
10009 selection,
10010 buffer,
10011 old_menu.map(|menu| menu.primary_scroll_handle()),
10012 snippet_sort_order,
10013 ),
10014 ));
10015 }
10016
10017 pub fn insert_snippet(
10018 &mut self,
10019 insertion_ranges: &[Range<MultiBufferOffset>],
10020 snippet: Snippet,
10021 window: &mut Window,
10022 cx: &mut Context<Self>,
10023 ) -> Result<()> {
10024 struct Tabstop<T> {
10025 is_end_tabstop: bool,
10026 ranges: Vec<Range<T>>,
10027 choices: Option<Vec<String>>,
10028 }
10029
10030 let tabstops = self.buffer.update(cx, |buffer, cx| {
10031 let snippet_text: Arc<str> = snippet.text.clone().into();
10032 let edits = insertion_ranges
10033 .iter()
10034 .cloned()
10035 .map(|range| (range, snippet_text.clone()));
10036 let autoindent_mode = AutoindentMode::Block {
10037 original_indent_columns: Vec::new(),
10038 };
10039 buffer.edit(edits, Some(autoindent_mode), cx);
10040
10041 let snapshot = &*buffer.read(cx);
10042 let snippet = &snippet;
10043 snippet
10044 .tabstops
10045 .iter()
10046 .map(|tabstop| {
10047 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10048 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10049 });
10050 let mut tabstop_ranges = tabstop
10051 .ranges
10052 .iter()
10053 .flat_map(|tabstop_range| {
10054 let mut delta = 0_isize;
10055 insertion_ranges.iter().map(move |insertion_range| {
10056 let insertion_start = insertion_range.start + delta;
10057 delta += snippet.text.len() as isize
10058 - (insertion_range.end - insertion_range.start) as isize;
10059
10060 let start =
10061 (insertion_start + tabstop_range.start).min(snapshot.len());
10062 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10063 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10064 })
10065 })
10066 .collect::<Vec<_>>();
10067 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10068
10069 Tabstop {
10070 is_end_tabstop,
10071 ranges: tabstop_ranges,
10072 choices: tabstop.choices.clone(),
10073 }
10074 })
10075 .collect::<Vec<_>>()
10076 });
10077 if let Some(tabstop) = tabstops.first() {
10078 self.change_selections(Default::default(), window, cx, |s| {
10079 // Reverse order so that the first range is the newest created selection.
10080 // Completions will use it and autoscroll will prioritize it.
10081 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10082 });
10083
10084 if let Some(choices) = &tabstop.choices
10085 && let Some(selection) = tabstop.ranges.first()
10086 {
10087 self.show_snippet_choices(choices, selection.clone(), cx)
10088 }
10089
10090 // If we're already at the last tabstop and it's at the end of the snippet,
10091 // we're done, we don't need to keep the state around.
10092 if !tabstop.is_end_tabstop {
10093 let choices = tabstops
10094 .iter()
10095 .map(|tabstop| tabstop.choices.clone())
10096 .collect();
10097
10098 let ranges = tabstops
10099 .into_iter()
10100 .map(|tabstop| tabstop.ranges)
10101 .collect::<Vec<_>>();
10102
10103 self.snippet_stack.push(SnippetState {
10104 active_index: 0,
10105 ranges,
10106 choices,
10107 });
10108 }
10109
10110 // Check whether the just-entered snippet ends with an auto-closable bracket.
10111 if self.autoclose_regions.is_empty() {
10112 let snapshot = self.buffer.read(cx).snapshot(cx);
10113 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10114 let selection_head = selection.head();
10115 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10116 continue;
10117 };
10118
10119 let mut bracket_pair = None;
10120 let max_lookup_length = scope
10121 .brackets()
10122 .map(|(pair, _)| {
10123 pair.start
10124 .as_str()
10125 .chars()
10126 .count()
10127 .max(pair.end.as_str().chars().count())
10128 })
10129 .max();
10130 if let Some(max_lookup_length) = max_lookup_length {
10131 let next_text = snapshot
10132 .chars_at(selection_head)
10133 .take(max_lookup_length)
10134 .collect::<String>();
10135 let prev_text = snapshot
10136 .reversed_chars_at(selection_head)
10137 .take(max_lookup_length)
10138 .collect::<String>();
10139
10140 for (pair, enabled) in scope.brackets() {
10141 if enabled
10142 && pair.close
10143 && prev_text.starts_with(pair.start.as_str())
10144 && next_text.starts_with(pair.end.as_str())
10145 {
10146 bracket_pair = Some(pair.clone());
10147 break;
10148 }
10149 }
10150 }
10151
10152 if let Some(pair) = bracket_pair {
10153 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10154 let autoclose_enabled =
10155 self.use_autoclose && snapshot_settings.use_autoclose;
10156 if autoclose_enabled {
10157 let start = snapshot.anchor_after(selection_head);
10158 let end = snapshot.anchor_after(selection_head);
10159 self.autoclose_regions.push(AutocloseRegion {
10160 selection_id: selection.id,
10161 range: start..end,
10162 pair,
10163 });
10164 }
10165 }
10166 }
10167 }
10168 }
10169 Ok(())
10170 }
10171
10172 pub fn move_to_next_snippet_tabstop(
10173 &mut self,
10174 window: &mut Window,
10175 cx: &mut Context<Self>,
10176 ) -> bool {
10177 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10178 }
10179
10180 pub fn move_to_prev_snippet_tabstop(
10181 &mut self,
10182 window: &mut Window,
10183 cx: &mut Context<Self>,
10184 ) -> bool {
10185 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10186 }
10187
10188 pub fn move_to_snippet_tabstop(
10189 &mut self,
10190 bias: Bias,
10191 window: &mut Window,
10192 cx: &mut Context<Self>,
10193 ) -> bool {
10194 if let Some(mut snippet) = self.snippet_stack.pop() {
10195 match bias {
10196 Bias::Left => {
10197 if snippet.active_index > 0 {
10198 snippet.active_index -= 1;
10199 } else {
10200 self.snippet_stack.push(snippet);
10201 return false;
10202 }
10203 }
10204 Bias::Right => {
10205 if snippet.active_index + 1 < snippet.ranges.len() {
10206 snippet.active_index += 1;
10207 } else {
10208 self.snippet_stack.push(snippet);
10209 return false;
10210 }
10211 }
10212 }
10213 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10214 self.change_selections(Default::default(), window, cx, |s| {
10215 // Reverse order so that the first range is the newest created selection.
10216 // Completions will use it and autoscroll will prioritize it.
10217 s.select_ranges(current_ranges.iter().rev().cloned())
10218 });
10219
10220 if let Some(choices) = &snippet.choices[snippet.active_index]
10221 && let Some(selection) = current_ranges.first()
10222 {
10223 self.show_snippet_choices(choices, selection.clone(), cx);
10224 }
10225
10226 // If snippet state is not at the last tabstop, push it back on the stack
10227 if snippet.active_index + 1 < snippet.ranges.len() {
10228 self.snippet_stack.push(snippet);
10229 }
10230 return true;
10231 }
10232 }
10233
10234 false
10235 }
10236
10237 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10238 self.transact(window, cx, |this, window, cx| {
10239 this.select_all(&SelectAll, window, cx);
10240 this.insert("", window, cx);
10241 });
10242 }
10243
10244 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10245 if self.read_only(cx) {
10246 return;
10247 }
10248 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10249 self.transact(window, cx, |this, window, cx| {
10250 this.select_autoclose_pair(window, cx);
10251
10252 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10253
10254 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10255 if !this.linked_edit_ranges.is_empty() {
10256 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10257 let snapshot = this.buffer.read(cx).snapshot(cx);
10258
10259 for selection in selections.iter() {
10260 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10261 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10262 if selection_start.buffer_id != selection_end.buffer_id {
10263 continue;
10264 }
10265 if let Some(ranges) =
10266 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10267 {
10268 for (buffer, entries) in ranges {
10269 linked_ranges.entry(buffer).or_default().extend(entries);
10270 }
10271 }
10272 }
10273 }
10274
10275 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10276 for selection in &mut selections {
10277 if selection.is_empty() {
10278 let old_head = selection.head();
10279 let mut new_head =
10280 movement::left(&display_map, old_head.to_display_point(&display_map))
10281 .to_point(&display_map);
10282 if let Some((buffer, line_buffer_range)) = display_map
10283 .buffer_snapshot()
10284 .buffer_line_for_row(MultiBufferRow(old_head.row))
10285 {
10286 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10287 let indent_len = match indent_size.kind {
10288 IndentKind::Space => {
10289 buffer.settings_at(line_buffer_range.start, cx).tab_size
10290 }
10291 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10292 };
10293 if old_head.column <= indent_size.len && old_head.column > 0 {
10294 let indent_len = indent_len.get();
10295 new_head = cmp::min(
10296 new_head,
10297 MultiBufferPoint::new(
10298 old_head.row,
10299 ((old_head.column - 1) / indent_len) * indent_len,
10300 ),
10301 );
10302 }
10303 }
10304
10305 selection.set_head(new_head, SelectionGoal::None);
10306 }
10307 }
10308
10309 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10310 this.insert("", window, cx);
10311 let empty_str: Arc<str> = Arc::from("");
10312 for (buffer, edits) in linked_ranges {
10313 let snapshot = buffer.read(cx).snapshot();
10314 use text::ToPoint as TP;
10315
10316 let edits = edits
10317 .into_iter()
10318 .map(|range| {
10319 let end_point = TP::to_point(&range.end, &snapshot);
10320 let mut start_point = TP::to_point(&range.start, &snapshot);
10321
10322 if end_point == start_point {
10323 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10324 .saturating_sub(1);
10325 start_point =
10326 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10327 };
10328
10329 (start_point..end_point, empty_str.clone())
10330 })
10331 .sorted_by_key(|(range, _)| range.start)
10332 .collect::<Vec<_>>();
10333 buffer.update(cx, |this, cx| {
10334 this.edit(edits, None, cx);
10335 })
10336 }
10337 this.refresh_edit_prediction(true, false, window, cx);
10338 refresh_linked_ranges(this, window, cx);
10339 });
10340 }
10341
10342 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10343 if self.read_only(cx) {
10344 return;
10345 }
10346 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10347 self.transact(window, cx, |this, window, cx| {
10348 this.change_selections(Default::default(), window, cx, |s| {
10349 s.move_with(|map, selection| {
10350 if selection.is_empty() {
10351 let cursor = movement::right(map, selection.head());
10352 selection.end = cursor;
10353 selection.reversed = true;
10354 selection.goal = SelectionGoal::None;
10355 }
10356 })
10357 });
10358 this.insert("", window, cx);
10359 this.refresh_edit_prediction(true, false, window, cx);
10360 });
10361 }
10362
10363 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10364 if self.mode.is_single_line() {
10365 cx.propagate();
10366 return;
10367 }
10368
10369 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10370 if self.move_to_prev_snippet_tabstop(window, cx) {
10371 return;
10372 }
10373 self.outdent(&Outdent, window, cx);
10374 }
10375
10376 pub fn next_snippet_tabstop(
10377 &mut self,
10378 _: &NextSnippetTabstop,
10379 window: &mut Window,
10380 cx: &mut Context<Self>,
10381 ) {
10382 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10383 cx.propagate();
10384 return;
10385 }
10386
10387 if self.move_to_next_snippet_tabstop(window, cx) {
10388 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10389 return;
10390 }
10391 cx.propagate();
10392 }
10393
10394 pub fn previous_snippet_tabstop(
10395 &mut self,
10396 _: &PreviousSnippetTabstop,
10397 window: &mut Window,
10398 cx: &mut Context<Self>,
10399 ) {
10400 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10401 cx.propagate();
10402 return;
10403 }
10404
10405 if self.move_to_prev_snippet_tabstop(window, cx) {
10406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10407 return;
10408 }
10409 cx.propagate();
10410 }
10411
10412 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10413 if self.mode.is_single_line() {
10414 cx.propagate();
10415 return;
10416 }
10417
10418 if self.move_to_next_snippet_tabstop(window, cx) {
10419 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10420 return;
10421 }
10422 if self.read_only(cx) {
10423 return;
10424 }
10425 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10426 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10427 let buffer = self.buffer.read(cx);
10428 let snapshot = buffer.snapshot(cx);
10429 let rows_iter = selections.iter().map(|s| s.head().row);
10430 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10431
10432 let has_some_cursor_in_whitespace = selections
10433 .iter()
10434 .filter(|selection| selection.is_empty())
10435 .any(|selection| {
10436 let cursor = selection.head();
10437 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10438 cursor.column < current_indent.len
10439 });
10440
10441 let mut edits = Vec::new();
10442 let mut prev_edited_row = 0;
10443 let mut row_delta = 0;
10444 for selection in &mut selections {
10445 if selection.start.row != prev_edited_row {
10446 row_delta = 0;
10447 }
10448 prev_edited_row = selection.end.row;
10449
10450 // If the selection is non-empty, then increase the indentation of the selected lines.
10451 if !selection.is_empty() {
10452 row_delta =
10453 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10454 continue;
10455 }
10456
10457 let cursor = selection.head();
10458 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10459 if let Some(suggested_indent) =
10460 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10461 {
10462 // Don't do anything if already at suggested indent
10463 // and there is any other cursor which is not
10464 if has_some_cursor_in_whitespace
10465 && cursor.column == current_indent.len
10466 && current_indent.len == suggested_indent.len
10467 {
10468 continue;
10469 }
10470
10471 // Adjust line and move cursor to suggested indent
10472 // if cursor is not at suggested indent
10473 if cursor.column < suggested_indent.len
10474 && cursor.column <= current_indent.len
10475 && current_indent.len <= suggested_indent.len
10476 {
10477 selection.start = Point::new(cursor.row, suggested_indent.len);
10478 selection.end = selection.start;
10479 if row_delta == 0 {
10480 edits.extend(Buffer::edit_for_indent_size_adjustment(
10481 cursor.row,
10482 current_indent,
10483 suggested_indent,
10484 ));
10485 row_delta = suggested_indent.len - current_indent.len;
10486 }
10487 continue;
10488 }
10489
10490 // If current indent is more than suggested indent
10491 // only move cursor to current indent and skip indent
10492 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10493 selection.start = Point::new(cursor.row, current_indent.len);
10494 selection.end = selection.start;
10495 continue;
10496 }
10497 }
10498
10499 // Otherwise, insert a hard or soft tab.
10500 let settings = buffer.language_settings_at(cursor, cx);
10501 let tab_size = if settings.hard_tabs {
10502 IndentSize::tab()
10503 } else {
10504 let tab_size = settings.tab_size.get();
10505 let indent_remainder = snapshot
10506 .text_for_range(Point::new(cursor.row, 0)..cursor)
10507 .flat_map(str::chars)
10508 .fold(row_delta % tab_size, |counter: u32, c| {
10509 if c == '\t' {
10510 0
10511 } else {
10512 (counter + 1) % tab_size
10513 }
10514 });
10515
10516 let chars_to_next_tab_stop = tab_size - indent_remainder;
10517 IndentSize::spaces(chars_to_next_tab_stop)
10518 };
10519 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10520 selection.end = selection.start;
10521 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10522 row_delta += tab_size.len;
10523 }
10524
10525 self.transact(window, cx, |this, window, cx| {
10526 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10527 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10528 this.refresh_edit_prediction(true, false, window, cx);
10529 });
10530 }
10531
10532 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10533 if self.read_only(cx) {
10534 return;
10535 }
10536 if self.mode.is_single_line() {
10537 cx.propagate();
10538 return;
10539 }
10540
10541 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10542 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10543 let mut prev_edited_row = 0;
10544 let mut row_delta = 0;
10545 let mut edits = Vec::new();
10546 let buffer = self.buffer.read(cx);
10547 let snapshot = buffer.snapshot(cx);
10548 for selection in &mut selections {
10549 if selection.start.row != prev_edited_row {
10550 row_delta = 0;
10551 }
10552 prev_edited_row = selection.end.row;
10553
10554 row_delta =
10555 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10556 }
10557
10558 self.transact(window, cx, |this, window, cx| {
10559 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10560 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10561 });
10562 }
10563
10564 fn indent_selection(
10565 buffer: &MultiBuffer,
10566 snapshot: &MultiBufferSnapshot,
10567 selection: &mut Selection<Point>,
10568 edits: &mut Vec<(Range<Point>, String)>,
10569 delta_for_start_row: u32,
10570 cx: &App,
10571 ) -> u32 {
10572 let settings = buffer.language_settings_at(selection.start, cx);
10573 let tab_size = settings.tab_size.get();
10574 let indent_kind = if settings.hard_tabs {
10575 IndentKind::Tab
10576 } else {
10577 IndentKind::Space
10578 };
10579 let mut start_row = selection.start.row;
10580 let mut end_row = selection.end.row + 1;
10581
10582 // If a selection ends at the beginning of a line, don't indent
10583 // that last line.
10584 if selection.end.column == 0 && selection.end.row > selection.start.row {
10585 end_row -= 1;
10586 }
10587
10588 // Avoid re-indenting a row that has already been indented by a
10589 // previous selection, but still update this selection's column
10590 // to reflect that indentation.
10591 if delta_for_start_row > 0 {
10592 start_row += 1;
10593 selection.start.column += delta_for_start_row;
10594 if selection.end.row == selection.start.row {
10595 selection.end.column += delta_for_start_row;
10596 }
10597 }
10598
10599 let mut delta_for_end_row = 0;
10600 let has_multiple_rows = start_row + 1 != end_row;
10601 for row in start_row..end_row {
10602 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10603 let indent_delta = match (current_indent.kind, indent_kind) {
10604 (IndentKind::Space, IndentKind::Space) => {
10605 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10606 IndentSize::spaces(columns_to_next_tab_stop)
10607 }
10608 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10609 (_, IndentKind::Tab) => IndentSize::tab(),
10610 };
10611
10612 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10613 0
10614 } else {
10615 selection.start.column
10616 };
10617 let row_start = Point::new(row, start);
10618 edits.push((
10619 row_start..row_start,
10620 indent_delta.chars().collect::<String>(),
10621 ));
10622
10623 // Update this selection's endpoints to reflect the indentation.
10624 if row == selection.start.row {
10625 selection.start.column += indent_delta.len;
10626 }
10627 if row == selection.end.row {
10628 selection.end.column += indent_delta.len;
10629 delta_for_end_row = indent_delta.len;
10630 }
10631 }
10632
10633 if selection.start.row == selection.end.row {
10634 delta_for_start_row + delta_for_end_row
10635 } else {
10636 delta_for_end_row
10637 }
10638 }
10639
10640 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10641 if self.read_only(cx) {
10642 return;
10643 }
10644 if self.mode.is_single_line() {
10645 cx.propagate();
10646 return;
10647 }
10648
10649 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10650 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10651 let selections = self.selections.all::<Point>(&display_map);
10652 let mut deletion_ranges = Vec::new();
10653 let mut last_outdent = None;
10654 {
10655 let buffer = self.buffer.read(cx);
10656 let snapshot = buffer.snapshot(cx);
10657 for selection in &selections {
10658 let settings = buffer.language_settings_at(selection.start, cx);
10659 let tab_size = settings.tab_size.get();
10660 let mut rows = selection.spanned_rows(false, &display_map);
10661
10662 // Avoid re-outdenting a row that has already been outdented by a
10663 // previous selection.
10664 if let Some(last_row) = last_outdent
10665 && last_row == rows.start
10666 {
10667 rows.start = rows.start.next_row();
10668 }
10669 let has_multiple_rows = rows.len() > 1;
10670 for row in rows.iter_rows() {
10671 let indent_size = snapshot.indent_size_for_line(row);
10672 if indent_size.len > 0 {
10673 let deletion_len = match indent_size.kind {
10674 IndentKind::Space => {
10675 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10676 if columns_to_prev_tab_stop == 0 {
10677 tab_size
10678 } else {
10679 columns_to_prev_tab_stop
10680 }
10681 }
10682 IndentKind::Tab => 1,
10683 };
10684 let start = if has_multiple_rows
10685 || deletion_len > selection.start.column
10686 || indent_size.len < selection.start.column
10687 {
10688 0
10689 } else {
10690 selection.start.column - deletion_len
10691 };
10692 deletion_ranges.push(
10693 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10694 );
10695 last_outdent = Some(row);
10696 }
10697 }
10698 }
10699 }
10700
10701 self.transact(window, cx, |this, window, cx| {
10702 this.buffer.update(cx, |buffer, cx| {
10703 let empty_str: Arc<str> = Arc::default();
10704 buffer.edit(
10705 deletion_ranges
10706 .into_iter()
10707 .map(|range| (range, empty_str.clone())),
10708 None,
10709 cx,
10710 );
10711 });
10712 let selections = this
10713 .selections
10714 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10715 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10716 });
10717 }
10718
10719 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10720 if self.read_only(cx) {
10721 return;
10722 }
10723 if self.mode.is_single_line() {
10724 cx.propagate();
10725 return;
10726 }
10727
10728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10729 let selections = self
10730 .selections
10731 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10732 .into_iter()
10733 .map(|s| s.range());
10734
10735 self.transact(window, cx, |this, window, cx| {
10736 this.buffer.update(cx, |buffer, cx| {
10737 buffer.autoindent_ranges(selections, cx);
10738 });
10739 let selections = this
10740 .selections
10741 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10742 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10743 });
10744 }
10745
10746 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10747 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10748 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10749 let selections = self.selections.all::<Point>(&display_map);
10750
10751 let mut new_cursors = Vec::new();
10752 let mut edit_ranges = Vec::new();
10753 let mut selections = selections.iter().peekable();
10754 while let Some(selection) = selections.next() {
10755 let mut rows = selection.spanned_rows(false, &display_map);
10756
10757 // Accumulate contiguous regions of rows that we want to delete.
10758 while let Some(next_selection) = selections.peek() {
10759 let next_rows = next_selection.spanned_rows(false, &display_map);
10760 if next_rows.start <= rows.end {
10761 rows.end = next_rows.end;
10762 selections.next().unwrap();
10763 } else {
10764 break;
10765 }
10766 }
10767
10768 let buffer = display_map.buffer_snapshot();
10769 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10770 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10771 // If there's a line after the range, delete the \n from the end of the row range
10772 (
10773 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10774 rows.end,
10775 )
10776 } else {
10777 // If there isn't a line after the range, delete the \n from the line before the
10778 // start of the row range
10779 edit_start = edit_start.saturating_sub_usize(1);
10780 (buffer.len(), rows.start.previous_row())
10781 };
10782
10783 let text_layout_details = self.text_layout_details(window);
10784 let x = display_map.x_for_display_point(
10785 selection.head().to_display_point(&display_map),
10786 &text_layout_details,
10787 );
10788 let row = Point::new(target_row.0, 0)
10789 .to_display_point(&display_map)
10790 .row();
10791 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10792
10793 new_cursors.push((
10794 selection.id,
10795 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10796 SelectionGoal::None,
10797 ));
10798 edit_ranges.push(edit_start..edit_end);
10799 }
10800
10801 self.transact(window, cx, |this, window, cx| {
10802 let buffer = this.buffer.update(cx, |buffer, cx| {
10803 let empty_str: Arc<str> = Arc::default();
10804 buffer.edit(
10805 edit_ranges
10806 .into_iter()
10807 .map(|range| (range, empty_str.clone())),
10808 None,
10809 cx,
10810 );
10811 buffer.snapshot(cx)
10812 });
10813 let new_selections = new_cursors
10814 .into_iter()
10815 .map(|(id, cursor, goal)| {
10816 let cursor = cursor.to_point(&buffer);
10817 Selection {
10818 id,
10819 start: cursor,
10820 end: cursor,
10821 reversed: false,
10822 goal,
10823 }
10824 })
10825 .collect();
10826
10827 this.change_selections(Default::default(), window, cx, |s| {
10828 s.select(new_selections);
10829 });
10830 });
10831 }
10832
10833 pub fn join_lines_impl(
10834 &mut self,
10835 insert_whitespace: bool,
10836 window: &mut Window,
10837 cx: &mut Context<Self>,
10838 ) {
10839 if self.read_only(cx) {
10840 return;
10841 }
10842 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10843 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10844 let start = MultiBufferRow(selection.start.row);
10845 // Treat single line selections as if they include the next line. Otherwise this action
10846 // would do nothing for single line selections individual cursors.
10847 let end = if selection.start.row == selection.end.row {
10848 MultiBufferRow(selection.start.row + 1)
10849 } else {
10850 MultiBufferRow(selection.end.row)
10851 };
10852
10853 if let Some(last_row_range) = row_ranges.last_mut()
10854 && start <= last_row_range.end
10855 {
10856 last_row_range.end = end;
10857 continue;
10858 }
10859 row_ranges.push(start..end);
10860 }
10861
10862 let snapshot = self.buffer.read(cx).snapshot(cx);
10863 let mut cursor_positions = Vec::new();
10864 for row_range in &row_ranges {
10865 let anchor = snapshot.anchor_before(Point::new(
10866 row_range.end.previous_row().0,
10867 snapshot.line_len(row_range.end.previous_row()),
10868 ));
10869 cursor_positions.push(anchor..anchor);
10870 }
10871
10872 self.transact(window, cx, |this, window, cx| {
10873 for row_range in row_ranges.into_iter().rev() {
10874 for row in row_range.iter_rows().rev() {
10875 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10876 let next_line_row = row.next_row();
10877 let indent = snapshot.indent_size_for_line(next_line_row);
10878 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10879
10880 let replace =
10881 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10882 " "
10883 } else {
10884 ""
10885 };
10886
10887 this.buffer.update(cx, |buffer, cx| {
10888 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10889 });
10890 }
10891 }
10892
10893 this.change_selections(Default::default(), window, cx, |s| {
10894 s.select_anchor_ranges(cursor_positions)
10895 });
10896 });
10897 }
10898
10899 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10900 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10901 self.join_lines_impl(true, window, cx);
10902 }
10903
10904 pub fn sort_lines_case_sensitive(
10905 &mut self,
10906 _: &SortLinesCaseSensitive,
10907 window: &mut Window,
10908 cx: &mut Context<Self>,
10909 ) {
10910 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10911 }
10912
10913 pub fn sort_lines_by_length(
10914 &mut self,
10915 _: &SortLinesByLength,
10916 window: &mut Window,
10917 cx: &mut Context<Self>,
10918 ) {
10919 self.manipulate_immutable_lines(window, cx, |lines| {
10920 lines.sort_by_key(|&line| line.chars().count())
10921 })
10922 }
10923
10924 pub fn sort_lines_case_insensitive(
10925 &mut self,
10926 _: &SortLinesCaseInsensitive,
10927 window: &mut Window,
10928 cx: &mut Context<Self>,
10929 ) {
10930 self.manipulate_immutable_lines(window, cx, |lines| {
10931 lines.sort_by_key(|line| line.to_lowercase())
10932 })
10933 }
10934
10935 pub fn unique_lines_case_insensitive(
10936 &mut self,
10937 _: &UniqueLinesCaseInsensitive,
10938 window: &mut Window,
10939 cx: &mut Context<Self>,
10940 ) {
10941 self.manipulate_immutable_lines(window, cx, |lines| {
10942 let mut seen = HashSet::default();
10943 lines.retain(|line| seen.insert(line.to_lowercase()));
10944 })
10945 }
10946
10947 pub fn unique_lines_case_sensitive(
10948 &mut self,
10949 _: &UniqueLinesCaseSensitive,
10950 window: &mut Window,
10951 cx: &mut Context<Self>,
10952 ) {
10953 self.manipulate_immutable_lines(window, cx, |lines| {
10954 let mut seen = HashSet::default();
10955 lines.retain(|line| seen.insert(*line));
10956 })
10957 }
10958
10959 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10960 let snapshot = self.buffer.read(cx).snapshot(cx);
10961 for selection in self.selections.disjoint_anchors_arc().iter() {
10962 if snapshot
10963 .language_at(selection.start)
10964 .and_then(|lang| lang.config().wrap_characters.as_ref())
10965 .is_some()
10966 {
10967 return true;
10968 }
10969 }
10970 false
10971 }
10972
10973 fn wrap_selections_in_tag(
10974 &mut self,
10975 _: &WrapSelectionsInTag,
10976 window: &mut Window,
10977 cx: &mut Context<Self>,
10978 ) {
10979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10980
10981 let snapshot = self.buffer.read(cx).snapshot(cx);
10982
10983 let mut edits = Vec::new();
10984 let mut boundaries = Vec::new();
10985
10986 for selection in self
10987 .selections
10988 .all_adjusted(&self.display_snapshot(cx))
10989 .iter()
10990 {
10991 let Some(wrap_config) = snapshot
10992 .language_at(selection.start)
10993 .and_then(|lang| lang.config().wrap_characters.clone())
10994 else {
10995 continue;
10996 };
10997
10998 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10999 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11000
11001 let start_before = snapshot.anchor_before(selection.start);
11002 let end_after = snapshot.anchor_after(selection.end);
11003
11004 edits.push((start_before..start_before, open_tag));
11005 edits.push((end_after..end_after, close_tag));
11006
11007 boundaries.push((
11008 start_before,
11009 end_after,
11010 wrap_config.start_prefix.len(),
11011 wrap_config.end_suffix.len(),
11012 ));
11013 }
11014
11015 if edits.is_empty() {
11016 return;
11017 }
11018
11019 self.transact(window, cx, |this, window, cx| {
11020 let buffer = this.buffer.update(cx, |buffer, cx| {
11021 buffer.edit(edits, None, cx);
11022 buffer.snapshot(cx)
11023 });
11024
11025 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11026 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11027 boundaries.into_iter()
11028 {
11029 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11030 let close_offset = end_after
11031 .to_offset(&buffer)
11032 .saturating_sub_usize(end_suffix_len);
11033 new_selections.push(open_offset..open_offset);
11034 new_selections.push(close_offset..close_offset);
11035 }
11036
11037 this.change_selections(Default::default(), window, cx, |s| {
11038 s.select_ranges(new_selections);
11039 });
11040
11041 this.request_autoscroll(Autoscroll::fit(), cx);
11042 });
11043 }
11044
11045 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11046 let Some(project) = self.project.clone() else {
11047 return;
11048 };
11049 self.reload(project, window, cx)
11050 .detach_and_notify_err(window, cx);
11051 }
11052
11053 pub fn restore_file(
11054 &mut self,
11055 _: &::git::RestoreFile,
11056 window: &mut Window,
11057 cx: &mut Context<Self>,
11058 ) {
11059 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11060 let mut buffer_ids = HashSet::default();
11061 let snapshot = self.buffer().read(cx).snapshot(cx);
11062 for selection in self
11063 .selections
11064 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11065 {
11066 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11067 }
11068
11069 let buffer = self.buffer().read(cx);
11070 let ranges = buffer_ids
11071 .into_iter()
11072 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11073 .collect::<Vec<_>>();
11074
11075 self.restore_hunks_in_ranges(ranges, window, cx);
11076 }
11077
11078 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11079 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11080 let selections = self
11081 .selections
11082 .all(&self.display_snapshot(cx))
11083 .into_iter()
11084 .map(|s| s.range())
11085 .collect();
11086 self.restore_hunks_in_ranges(selections, window, cx);
11087 }
11088
11089 pub fn restore_hunks_in_ranges(
11090 &mut self,
11091 ranges: Vec<Range<Point>>,
11092 window: &mut Window,
11093 cx: &mut Context<Editor>,
11094 ) {
11095 let mut revert_changes = HashMap::default();
11096 let chunk_by = self
11097 .snapshot(window, cx)
11098 .hunks_for_ranges(ranges)
11099 .into_iter()
11100 .chunk_by(|hunk| hunk.buffer_id);
11101 for (buffer_id, hunks) in &chunk_by {
11102 let hunks = hunks.collect::<Vec<_>>();
11103 for hunk in &hunks {
11104 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11105 }
11106 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11107 }
11108 drop(chunk_by);
11109 if !revert_changes.is_empty() {
11110 self.transact(window, cx, |editor, window, cx| {
11111 editor.restore(revert_changes, window, cx);
11112 });
11113 }
11114 }
11115
11116 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11117 if let Some(status) = self
11118 .addons
11119 .iter()
11120 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11121 {
11122 return Some(status);
11123 }
11124 self.project
11125 .as_ref()?
11126 .read(cx)
11127 .status_for_buffer_id(buffer_id, cx)
11128 }
11129
11130 pub fn open_active_item_in_terminal(
11131 &mut self,
11132 _: &OpenInTerminal,
11133 window: &mut Window,
11134 cx: &mut Context<Self>,
11135 ) {
11136 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11137 let project_path = buffer.read(cx).project_path(cx)?;
11138 let project = self.project()?.read(cx);
11139 let entry = project.entry_for_path(&project_path, cx)?;
11140 let parent = match &entry.canonical_path {
11141 Some(canonical_path) => canonical_path.to_path_buf(),
11142 None => project.absolute_path(&project_path, cx)?,
11143 }
11144 .parent()?
11145 .to_path_buf();
11146 Some(parent)
11147 }) {
11148 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11149 }
11150 }
11151
11152 fn set_breakpoint_context_menu(
11153 &mut self,
11154 display_row: DisplayRow,
11155 position: Option<Anchor>,
11156 clicked_point: gpui::Point<Pixels>,
11157 window: &mut Window,
11158 cx: &mut Context<Self>,
11159 ) {
11160 let source = self
11161 .buffer
11162 .read(cx)
11163 .snapshot(cx)
11164 .anchor_before(Point::new(display_row.0, 0u32));
11165
11166 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11167
11168 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11169 self,
11170 source,
11171 clicked_point,
11172 context_menu,
11173 window,
11174 cx,
11175 );
11176 }
11177
11178 fn add_edit_breakpoint_block(
11179 &mut self,
11180 anchor: Anchor,
11181 breakpoint: &Breakpoint,
11182 edit_action: BreakpointPromptEditAction,
11183 window: &mut Window,
11184 cx: &mut Context<Self>,
11185 ) {
11186 let weak_editor = cx.weak_entity();
11187 let bp_prompt = cx.new(|cx| {
11188 BreakpointPromptEditor::new(
11189 weak_editor,
11190 anchor,
11191 breakpoint.clone(),
11192 edit_action,
11193 window,
11194 cx,
11195 )
11196 });
11197
11198 let height = bp_prompt.update(cx, |this, cx| {
11199 this.prompt
11200 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11201 });
11202 let cloned_prompt = bp_prompt.clone();
11203 let blocks = vec![BlockProperties {
11204 style: BlockStyle::Sticky,
11205 placement: BlockPlacement::Above(anchor),
11206 height: Some(height),
11207 render: Arc::new(move |cx| {
11208 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11209 cloned_prompt.clone().into_any_element()
11210 }),
11211 priority: 0,
11212 }];
11213
11214 let focus_handle = bp_prompt.focus_handle(cx);
11215 window.focus(&focus_handle, cx);
11216
11217 let block_ids = self.insert_blocks(blocks, None, cx);
11218 bp_prompt.update(cx, |prompt, _| {
11219 prompt.add_block_ids(block_ids);
11220 });
11221 }
11222
11223 pub(crate) fn breakpoint_at_row(
11224 &self,
11225 row: u32,
11226 window: &mut Window,
11227 cx: &mut Context<Self>,
11228 ) -> Option<(Anchor, Breakpoint)> {
11229 let snapshot = self.snapshot(window, cx);
11230 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11231
11232 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11233 }
11234
11235 pub(crate) fn breakpoint_at_anchor(
11236 &self,
11237 breakpoint_position: Anchor,
11238 snapshot: &EditorSnapshot,
11239 cx: &mut Context<Self>,
11240 ) -> Option<(Anchor, Breakpoint)> {
11241 let buffer = self
11242 .buffer
11243 .read(cx)
11244 .buffer_for_anchor(breakpoint_position, cx)?;
11245
11246 let enclosing_excerpt = breakpoint_position.excerpt_id;
11247 let buffer_snapshot = buffer.read(cx).snapshot();
11248
11249 let row = buffer_snapshot
11250 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11251 .row;
11252
11253 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11254 let anchor_end = snapshot
11255 .buffer_snapshot()
11256 .anchor_after(Point::new(row, line_len));
11257
11258 self.breakpoint_store
11259 .as_ref()?
11260 .read_with(cx, |breakpoint_store, cx| {
11261 breakpoint_store
11262 .breakpoints(
11263 &buffer,
11264 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11265 &buffer_snapshot,
11266 cx,
11267 )
11268 .next()
11269 .and_then(|(bp, _)| {
11270 let breakpoint_row = buffer_snapshot
11271 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11272 .row;
11273
11274 if breakpoint_row == row {
11275 snapshot
11276 .buffer_snapshot()
11277 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11278 .map(|position| (position, bp.bp.clone()))
11279 } else {
11280 None
11281 }
11282 })
11283 })
11284 }
11285
11286 pub fn edit_log_breakpoint(
11287 &mut self,
11288 _: &EditLogBreakpoint,
11289 window: &mut Window,
11290 cx: &mut Context<Self>,
11291 ) {
11292 if self.breakpoint_store.is_none() {
11293 return;
11294 }
11295
11296 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11297 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11298 message: None,
11299 state: BreakpointState::Enabled,
11300 condition: None,
11301 hit_condition: None,
11302 });
11303
11304 self.add_edit_breakpoint_block(
11305 anchor,
11306 &breakpoint,
11307 BreakpointPromptEditAction::Log,
11308 window,
11309 cx,
11310 );
11311 }
11312 }
11313
11314 fn breakpoints_at_cursors(
11315 &self,
11316 window: &mut Window,
11317 cx: &mut Context<Self>,
11318 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11319 let snapshot = self.snapshot(window, cx);
11320 let cursors = self
11321 .selections
11322 .disjoint_anchors_arc()
11323 .iter()
11324 .map(|selection| {
11325 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11326
11327 let breakpoint_position = self
11328 .breakpoint_at_row(cursor_position.row, window, cx)
11329 .map(|bp| bp.0)
11330 .unwrap_or_else(|| {
11331 snapshot
11332 .display_snapshot
11333 .buffer_snapshot()
11334 .anchor_after(Point::new(cursor_position.row, 0))
11335 });
11336
11337 let breakpoint = self
11338 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11339 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11340
11341 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11342 })
11343 // 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.
11344 .collect::<HashMap<Anchor, _>>();
11345
11346 cursors.into_iter().collect()
11347 }
11348
11349 pub fn enable_breakpoint(
11350 &mut self,
11351 _: &crate::actions::EnableBreakpoint,
11352 window: &mut Window,
11353 cx: &mut Context<Self>,
11354 ) {
11355 if self.breakpoint_store.is_none() {
11356 return;
11357 }
11358
11359 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11360 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11361 continue;
11362 };
11363 self.edit_breakpoint_at_anchor(
11364 anchor,
11365 breakpoint,
11366 BreakpointEditAction::InvertState,
11367 cx,
11368 );
11369 }
11370 }
11371
11372 pub fn disable_breakpoint(
11373 &mut self,
11374 _: &crate::actions::DisableBreakpoint,
11375 window: &mut Window,
11376 cx: &mut Context<Self>,
11377 ) {
11378 if self.breakpoint_store.is_none() {
11379 return;
11380 }
11381
11382 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11383 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11384 continue;
11385 };
11386 self.edit_breakpoint_at_anchor(
11387 anchor,
11388 breakpoint,
11389 BreakpointEditAction::InvertState,
11390 cx,
11391 );
11392 }
11393 }
11394
11395 pub fn toggle_breakpoint(
11396 &mut self,
11397 _: &crate::actions::ToggleBreakpoint,
11398 window: &mut Window,
11399 cx: &mut Context<Self>,
11400 ) {
11401 if self.breakpoint_store.is_none() {
11402 return;
11403 }
11404
11405 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11406 if let Some(breakpoint) = breakpoint {
11407 self.edit_breakpoint_at_anchor(
11408 anchor,
11409 breakpoint,
11410 BreakpointEditAction::Toggle,
11411 cx,
11412 );
11413 } else {
11414 self.edit_breakpoint_at_anchor(
11415 anchor,
11416 Breakpoint::new_standard(),
11417 BreakpointEditAction::Toggle,
11418 cx,
11419 );
11420 }
11421 }
11422 }
11423
11424 pub fn edit_breakpoint_at_anchor(
11425 &mut self,
11426 breakpoint_position: Anchor,
11427 breakpoint: Breakpoint,
11428 edit_action: BreakpointEditAction,
11429 cx: &mut Context<Self>,
11430 ) {
11431 let Some(breakpoint_store) = &self.breakpoint_store else {
11432 return;
11433 };
11434
11435 let Some(buffer) = self
11436 .buffer
11437 .read(cx)
11438 .buffer_for_anchor(breakpoint_position, cx)
11439 else {
11440 return;
11441 };
11442
11443 breakpoint_store.update(cx, |breakpoint_store, cx| {
11444 breakpoint_store.toggle_breakpoint(
11445 buffer,
11446 BreakpointWithPosition {
11447 position: breakpoint_position.text_anchor,
11448 bp: breakpoint,
11449 },
11450 edit_action,
11451 cx,
11452 );
11453 });
11454
11455 cx.notify();
11456 }
11457
11458 #[cfg(any(test, feature = "test-support"))]
11459 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11460 self.breakpoint_store.clone()
11461 }
11462
11463 pub fn prepare_restore_change(
11464 &self,
11465 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11466 hunk: &MultiBufferDiffHunk,
11467 cx: &mut App,
11468 ) -> Option<()> {
11469 if hunk.is_created_file() {
11470 return None;
11471 }
11472 let buffer = self.buffer.read(cx);
11473 let diff = buffer.diff_for(hunk.buffer_id)?;
11474 let buffer = buffer.buffer(hunk.buffer_id)?;
11475 let buffer = buffer.read(cx);
11476 let original_text = diff
11477 .read(cx)
11478 .base_text()
11479 .as_rope()
11480 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11481 let buffer_snapshot = buffer.snapshot();
11482 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11483 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11484 probe
11485 .0
11486 .start
11487 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11488 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11489 }) {
11490 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11491 Some(())
11492 } else {
11493 None
11494 }
11495 }
11496
11497 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11498 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11499 }
11500
11501 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11502 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11503 }
11504
11505 pub fn rotate_selections_forward(
11506 &mut self,
11507 _: &RotateSelectionsForward,
11508 window: &mut Window,
11509 cx: &mut Context<Self>,
11510 ) {
11511 self.rotate_selections(window, cx, false)
11512 }
11513
11514 pub fn rotate_selections_backward(
11515 &mut self,
11516 _: &RotateSelectionsBackward,
11517 window: &mut Window,
11518 cx: &mut Context<Self>,
11519 ) {
11520 self.rotate_selections(window, cx, true)
11521 }
11522
11523 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11525 let display_snapshot = self.display_snapshot(cx);
11526 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11527
11528 if selections.len() < 2 {
11529 return;
11530 }
11531
11532 let (edits, new_selections) = {
11533 let buffer = self.buffer.read(cx).read(cx);
11534 let has_selections = selections.iter().any(|s| !s.is_empty());
11535 if has_selections {
11536 let mut selected_texts: Vec<String> = selections
11537 .iter()
11538 .map(|selection| {
11539 buffer
11540 .text_for_range(selection.start..selection.end)
11541 .collect()
11542 })
11543 .collect();
11544
11545 if reverse {
11546 selected_texts.rotate_left(1);
11547 } else {
11548 selected_texts.rotate_right(1);
11549 }
11550
11551 let mut offset_delta: i64 = 0;
11552 let mut new_selections = Vec::new();
11553 let edits: Vec<_> = selections
11554 .iter()
11555 .zip(selected_texts.iter())
11556 .map(|(selection, new_text)| {
11557 let old_len = (selection.end.0 - selection.start.0) as i64;
11558 let new_len = new_text.len() as i64;
11559 let adjusted_start =
11560 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11561 let adjusted_end =
11562 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11563
11564 new_selections.push(Selection {
11565 id: selection.id,
11566 start: adjusted_start,
11567 end: adjusted_end,
11568 reversed: selection.reversed,
11569 goal: selection.goal,
11570 });
11571
11572 offset_delta += new_len - old_len;
11573 (selection.start..selection.end, new_text.clone())
11574 })
11575 .collect();
11576 (edits, new_selections)
11577 } else {
11578 let mut all_rows: Vec<u32> = selections
11579 .iter()
11580 .map(|selection| buffer.offset_to_point(selection.start).row)
11581 .collect();
11582 all_rows.sort_unstable();
11583 all_rows.dedup();
11584
11585 if all_rows.len() < 2 {
11586 return;
11587 }
11588
11589 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11590 .iter()
11591 .map(|&row| {
11592 let start = Point::new(row, 0);
11593 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11594 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11595 })
11596 .collect();
11597
11598 let mut line_texts: Vec<String> = line_ranges
11599 .iter()
11600 .map(|range| buffer.text_for_range(range.clone()).collect())
11601 .collect();
11602
11603 if reverse {
11604 line_texts.rotate_left(1);
11605 } else {
11606 line_texts.rotate_right(1);
11607 }
11608
11609 let edits = line_ranges
11610 .iter()
11611 .zip(line_texts.iter())
11612 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11613 .collect();
11614
11615 let num_rows = all_rows.len();
11616 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11617 .iter()
11618 .enumerate()
11619 .map(|(i, &row)| (row, i))
11620 .collect();
11621
11622 // Compute new line start offsets after rotation (handles CRLF)
11623 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11624 let first_line_start = line_ranges[0].start.0;
11625 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11626 for text in line_texts.iter().take(num_rows - 1) {
11627 let prev_start = *new_line_starts.last().unwrap();
11628 new_line_starts.push(prev_start + text.len() + newline_len);
11629 }
11630
11631 let new_selections = selections
11632 .iter()
11633 .map(|selection| {
11634 let point = buffer.offset_to_point(selection.start);
11635 let old_index = row_to_index[&point.row];
11636 let new_index = if reverse {
11637 (old_index + num_rows - 1) % num_rows
11638 } else {
11639 (old_index + 1) % num_rows
11640 };
11641 let new_offset =
11642 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11643 Selection {
11644 id: selection.id,
11645 start: new_offset,
11646 end: new_offset,
11647 reversed: selection.reversed,
11648 goal: selection.goal,
11649 }
11650 })
11651 .collect();
11652
11653 (edits, new_selections)
11654 }
11655 };
11656
11657 self.transact(window, cx, |this, window, cx| {
11658 this.buffer.update(cx, |buffer, cx| {
11659 buffer.edit(edits, None, cx);
11660 });
11661 this.change_selections(Default::default(), window, cx, |s| {
11662 s.select(new_selections);
11663 });
11664 });
11665 }
11666
11667 fn manipulate_lines<M>(
11668 &mut self,
11669 window: &mut Window,
11670 cx: &mut Context<Self>,
11671 mut manipulate: M,
11672 ) where
11673 M: FnMut(&str) -> LineManipulationResult,
11674 {
11675 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11676
11677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11678 let buffer = self.buffer.read(cx).snapshot(cx);
11679
11680 let mut edits = Vec::new();
11681
11682 let selections = self.selections.all::<Point>(&display_map);
11683 let mut selections = selections.iter().peekable();
11684 let mut contiguous_row_selections = Vec::new();
11685 let mut new_selections = Vec::new();
11686 let mut added_lines = 0;
11687 let mut removed_lines = 0;
11688
11689 while let Some(selection) = selections.next() {
11690 let (start_row, end_row) = consume_contiguous_rows(
11691 &mut contiguous_row_selections,
11692 selection,
11693 &display_map,
11694 &mut selections,
11695 );
11696
11697 let start_point = Point::new(start_row.0, 0);
11698 let end_point = Point::new(
11699 end_row.previous_row().0,
11700 buffer.line_len(end_row.previous_row()),
11701 );
11702 let text = buffer
11703 .text_for_range(start_point..end_point)
11704 .collect::<String>();
11705
11706 let LineManipulationResult {
11707 new_text,
11708 line_count_before,
11709 line_count_after,
11710 } = manipulate(&text);
11711
11712 edits.push((start_point..end_point, new_text));
11713
11714 // Selections must change based on added and removed line count
11715 let start_row =
11716 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11717 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11718 new_selections.push(Selection {
11719 id: selection.id,
11720 start: start_row,
11721 end: end_row,
11722 goal: SelectionGoal::None,
11723 reversed: selection.reversed,
11724 });
11725
11726 if line_count_after > line_count_before {
11727 added_lines += line_count_after - line_count_before;
11728 } else if line_count_before > line_count_after {
11729 removed_lines += line_count_before - line_count_after;
11730 }
11731 }
11732
11733 self.transact(window, cx, |this, window, cx| {
11734 let buffer = this.buffer.update(cx, |buffer, cx| {
11735 buffer.edit(edits, None, cx);
11736 buffer.snapshot(cx)
11737 });
11738
11739 // Recalculate offsets on newly edited buffer
11740 let new_selections = new_selections
11741 .iter()
11742 .map(|s| {
11743 let start_point = Point::new(s.start.0, 0);
11744 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11745 Selection {
11746 id: s.id,
11747 start: buffer.point_to_offset(start_point),
11748 end: buffer.point_to_offset(end_point),
11749 goal: s.goal,
11750 reversed: s.reversed,
11751 }
11752 })
11753 .collect();
11754
11755 this.change_selections(Default::default(), window, cx, |s| {
11756 s.select(new_selections);
11757 });
11758
11759 this.request_autoscroll(Autoscroll::fit(), cx);
11760 });
11761 }
11762
11763 fn manipulate_immutable_lines<Fn>(
11764 &mut self,
11765 window: &mut Window,
11766 cx: &mut Context<Self>,
11767 mut callback: Fn,
11768 ) where
11769 Fn: FnMut(&mut Vec<&str>),
11770 {
11771 self.manipulate_lines(window, cx, |text| {
11772 let mut lines: Vec<&str> = text.split('\n').collect();
11773 let line_count_before = lines.len();
11774
11775 callback(&mut lines);
11776
11777 LineManipulationResult {
11778 new_text: lines.join("\n"),
11779 line_count_before,
11780 line_count_after: lines.len(),
11781 }
11782 });
11783 }
11784
11785 fn manipulate_mutable_lines<Fn>(
11786 &mut self,
11787 window: &mut Window,
11788 cx: &mut Context<Self>,
11789 mut callback: Fn,
11790 ) where
11791 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11792 {
11793 self.manipulate_lines(window, cx, |text| {
11794 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11795 let line_count_before = lines.len();
11796
11797 callback(&mut lines);
11798
11799 LineManipulationResult {
11800 new_text: lines.join("\n"),
11801 line_count_before,
11802 line_count_after: lines.len(),
11803 }
11804 });
11805 }
11806
11807 pub fn convert_indentation_to_spaces(
11808 &mut self,
11809 _: &ConvertIndentationToSpaces,
11810 window: &mut Window,
11811 cx: &mut Context<Self>,
11812 ) {
11813 let settings = self.buffer.read(cx).language_settings(cx);
11814 let tab_size = settings.tab_size.get() as usize;
11815
11816 self.manipulate_mutable_lines(window, cx, |lines| {
11817 // Allocates a reasonably sized scratch buffer once for the whole loop
11818 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11819 // Avoids recomputing spaces that could be inserted many times
11820 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11821 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11822 .collect();
11823
11824 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11825 let mut chars = line.as_ref().chars();
11826 let mut col = 0;
11827 let mut changed = false;
11828
11829 for ch in chars.by_ref() {
11830 match ch {
11831 ' ' => {
11832 reindented_line.push(' ');
11833 col += 1;
11834 }
11835 '\t' => {
11836 // \t are converted to spaces depending on the current column
11837 let spaces_len = tab_size - (col % tab_size);
11838 reindented_line.extend(&space_cache[spaces_len - 1]);
11839 col += spaces_len;
11840 changed = true;
11841 }
11842 _ => {
11843 // If we dont append before break, the character is consumed
11844 reindented_line.push(ch);
11845 break;
11846 }
11847 }
11848 }
11849
11850 if !changed {
11851 reindented_line.clear();
11852 continue;
11853 }
11854 // Append the rest of the line and replace old reference with new one
11855 reindented_line.extend(chars);
11856 *line = Cow::Owned(reindented_line.clone());
11857 reindented_line.clear();
11858 }
11859 });
11860 }
11861
11862 pub fn convert_indentation_to_tabs(
11863 &mut self,
11864 _: &ConvertIndentationToTabs,
11865 window: &mut Window,
11866 cx: &mut Context<Self>,
11867 ) {
11868 let settings = self.buffer.read(cx).language_settings(cx);
11869 let tab_size = settings.tab_size.get() as usize;
11870
11871 self.manipulate_mutable_lines(window, cx, |lines| {
11872 // Allocates a reasonably sized buffer once for the whole loop
11873 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11874 // Avoids recomputing spaces that could be inserted many times
11875 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11876 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11877 .collect();
11878
11879 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11880 let mut chars = line.chars();
11881 let mut spaces_count = 0;
11882 let mut first_non_indent_char = None;
11883 let mut changed = false;
11884
11885 for ch in chars.by_ref() {
11886 match ch {
11887 ' ' => {
11888 // Keep track of spaces. Append \t when we reach tab_size
11889 spaces_count += 1;
11890 changed = true;
11891 if spaces_count == tab_size {
11892 reindented_line.push('\t');
11893 spaces_count = 0;
11894 }
11895 }
11896 '\t' => {
11897 reindented_line.push('\t');
11898 spaces_count = 0;
11899 }
11900 _ => {
11901 // Dont append it yet, we might have remaining spaces
11902 first_non_indent_char = Some(ch);
11903 break;
11904 }
11905 }
11906 }
11907
11908 if !changed {
11909 reindented_line.clear();
11910 continue;
11911 }
11912 // Remaining spaces that didn't make a full tab stop
11913 if spaces_count > 0 {
11914 reindented_line.extend(&space_cache[spaces_count - 1]);
11915 }
11916 // If we consume an extra character that was not indentation, add it back
11917 if let Some(extra_char) = first_non_indent_char {
11918 reindented_line.push(extra_char);
11919 }
11920 // Append the rest of the line and replace old reference with new one
11921 reindented_line.extend(chars);
11922 *line = Cow::Owned(reindented_line.clone());
11923 reindented_line.clear();
11924 }
11925 });
11926 }
11927
11928 pub fn convert_to_upper_case(
11929 &mut self,
11930 _: &ConvertToUpperCase,
11931 window: &mut Window,
11932 cx: &mut Context<Self>,
11933 ) {
11934 self.manipulate_text(window, cx, |text| text.to_uppercase())
11935 }
11936
11937 pub fn convert_to_lower_case(
11938 &mut self,
11939 _: &ConvertToLowerCase,
11940 window: &mut Window,
11941 cx: &mut Context<Self>,
11942 ) {
11943 self.manipulate_text(window, cx, |text| text.to_lowercase())
11944 }
11945
11946 pub fn convert_to_title_case(
11947 &mut self,
11948 _: &ConvertToTitleCase,
11949 window: &mut Window,
11950 cx: &mut Context<Self>,
11951 ) {
11952 self.manipulate_text(window, cx, |text| {
11953 text.split('\n')
11954 .map(|line| line.to_case(Case::Title))
11955 .join("\n")
11956 })
11957 }
11958
11959 pub fn convert_to_snake_case(
11960 &mut self,
11961 _: &ConvertToSnakeCase,
11962 window: &mut Window,
11963 cx: &mut Context<Self>,
11964 ) {
11965 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11966 }
11967
11968 pub fn convert_to_kebab_case(
11969 &mut self,
11970 _: &ConvertToKebabCase,
11971 window: &mut Window,
11972 cx: &mut Context<Self>,
11973 ) {
11974 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11975 }
11976
11977 pub fn convert_to_upper_camel_case(
11978 &mut self,
11979 _: &ConvertToUpperCamelCase,
11980 window: &mut Window,
11981 cx: &mut Context<Self>,
11982 ) {
11983 self.manipulate_text(window, cx, |text| {
11984 text.split('\n')
11985 .map(|line| line.to_case(Case::UpperCamel))
11986 .join("\n")
11987 })
11988 }
11989
11990 pub fn convert_to_lower_camel_case(
11991 &mut self,
11992 _: &ConvertToLowerCamelCase,
11993 window: &mut Window,
11994 cx: &mut Context<Self>,
11995 ) {
11996 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11997 }
11998
11999 pub fn convert_to_opposite_case(
12000 &mut self,
12001 _: &ConvertToOppositeCase,
12002 window: &mut Window,
12003 cx: &mut Context<Self>,
12004 ) {
12005 self.manipulate_text(window, cx, |text| {
12006 text.chars()
12007 .fold(String::with_capacity(text.len()), |mut t, c| {
12008 if c.is_uppercase() {
12009 t.extend(c.to_lowercase());
12010 } else {
12011 t.extend(c.to_uppercase());
12012 }
12013 t
12014 })
12015 })
12016 }
12017
12018 pub fn convert_to_sentence_case(
12019 &mut self,
12020 _: &ConvertToSentenceCase,
12021 window: &mut Window,
12022 cx: &mut Context<Self>,
12023 ) {
12024 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12025 }
12026
12027 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12028 self.manipulate_text(window, cx, |text| {
12029 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12030 if has_upper_case_characters {
12031 text.to_lowercase()
12032 } else {
12033 text.to_uppercase()
12034 }
12035 })
12036 }
12037
12038 pub fn convert_to_rot13(
12039 &mut self,
12040 _: &ConvertToRot13,
12041 window: &mut Window,
12042 cx: &mut Context<Self>,
12043 ) {
12044 self.manipulate_text(window, cx, |text| {
12045 text.chars()
12046 .map(|c| match c {
12047 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12048 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12049 _ => c,
12050 })
12051 .collect()
12052 })
12053 }
12054
12055 pub fn convert_to_rot47(
12056 &mut self,
12057 _: &ConvertToRot47,
12058 window: &mut Window,
12059 cx: &mut Context<Self>,
12060 ) {
12061 self.manipulate_text(window, cx, |text| {
12062 text.chars()
12063 .map(|c| {
12064 let code_point = c as u32;
12065 if code_point >= 33 && code_point <= 126 {
12066 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12067 }
12068 c
12069 })
12070 .collect()
12071 })
12072 }
12073
12074 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12075 where
12076 Fn: FnMut(&str) -> String,
12077 {
12078 let buffer = self.buffer.read(cx).snapshot(cx);
12079
12080 let mut new_selections = Vec::new();
12081 let mut edits = Vec::new();
12082 let mut selection_adjustment = 0isize;
12083
12084 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12085 let selection_is_empty = selection.is_empty();
12086
12087 let (start, end) = if selection_is_empty {
12088 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12089 (word_range.start, word_range.end)
12090 } else {
12091 (
12092 buffer.point_to_offset(selection.start),
12093 buffer.point_to_offset(selection.end),
12094 )
12095 };
12096
12097 let text = buffer.text_for_range(start..end).collect::<String>();
12098 let old_length = text.len() as isize;
12099 let text = callback(&text);
12100
12101 new_selections.push(Selection {
12102 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12103 end: MultiBufferOffset(
12104 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12105 ),
12106 goal: SelectionGoal::None,
12107 id: selection.id,
12108 reversed: selection.reversed,
12109 });
12110
12111 selection_adjustment += old_length - text.len() as isize;
12112
12113 edits.push((start..end, text));
12114 }
12115
12116 self.transact(window, cx, |this, window, cx| {
12117 this.buffer.update(cx, |buffer, cx| {
12118 buffer.edit(edits, None, cx);
12119 });
12120
12121 this.change_selections(Default::default(), window, cx, |s| {
12122 s.select(new_selections);
12123 });
12124
12125 this.request_autoscroll(Autoscroll::fit(), cx);
12126 });
12127 }
12128
12129 pub fn move_selection_on_drop(
12130 &mut self,
12131 selection: &Selection<Anchor>,
12132 target: DisplayPoint,
12133 is_cut: bool,
12134 window: &mut Window,
12135 cx: &mut Context<Self>,
12136 ) {
12137 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12138 let buffer = display_map.buffer_snapshot();
12139 let mut edits = Vec::new();
12140 let insert_point = display_map
12141 .clip_point(target, Bias::Left)
12142 .to_point(&display_map);
12143 let text = buffer
12144 .text_for_range(selection.start..selection.end)
12145 .collect::<String>();
12146 if is_cut {
12147 edits.push(((selection.start..selection.end), String::new()));
12148 }
12149 let insert_anchor = buffer.anchor_before(insert_point);
12150 edits.push(((insert_anchor..insert_anchor), text));
12151 let last_edit_start = insert_anchor.bias_left(buffer);
12152 let last_edit_end = insert_anchor.bias_right(buffer);
12153 self.transact(window, cx, |this, window, cx| {
12154 this.buffer.update(cx, |buffer, cx| {
12155 buffer.edit(edits, None, cx);
12156 });
12157 this.change_selections(Default::default(), window, cx, |s| {
12158 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12159 });
12160 });
12161 }
12162
12163 pub fn clear_selection_drag_state(&mut self) {
12164 self.selection_drag_state = SelectionDragState::None;
12165 }
12166
12167 pub fn duplicate(
12168 &mut self,
12169 upwards: bool,
12170 whole_lines: bool,
12171 window: &mut Window,
12172 cx: &mut Context<Self>,
12173 ) {
12174 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12175
12176 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12177 let buffer = display_map.buffer_snapshot();
12178 let selections = self.selections.all::<Point>(&display_map);
12179
12180 let mut edits = Vec::new();
12181 let mut selections_iter = selections.iter().peekable();
12182 while let Some(selection) = selections_iter.next() {
12183 let mut rows = selection.spanned_rows(false, &display_map);
12184 // duplicate line-wise
12185 if whole_lines || selection.start == selection.end {
12186 // Avoid duplicating the same lines twice.
12187 while let Some(next_selection) = selections_iter.peek() {
12188 let next_rows = next_selection.spanned_rows(false, &display_map);
12189 if next_rows.start < rows.end {
12190 rows.end = next_rows.end;
12191 selections_iter.next().unwrap();
12192 } else {
12193 break;
12194 }
12195 }
12196
12197 // Copy the text from the selected row region and splice it either at the start
12198 // or end of the region.
12199 let start = Point::new(rows.start.0, 0);
12200 let end = Point::new(
12201 rows.end.previous_row().0,
12202 buffer.line_len(rows.end.previous_row()),
12203 );
12204
12205 let mut text = buffer.text_for_range(start..end).collect::<String>();
12206
12207 let insert_location = if upwards {
12208 // When duplicating upward, we need to insert before the current line.
12209 // If we're on the last line and it doesn't end with a newline,
12210 // we need to add a newline before the duplicated content.
12211 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12212 && buffer.max_point().column > 0
12213 && !text.ends_with('\n');
12214
12215 if needs_leading_newline {
12216 text.insert(0, '\n');
12217 end
12218 } else {
12219 text.push('\n');
12220 Point::new(rows.start.0, 0)
12221 }
12222 } else {
12223 text.push('\n');
12224 start
12225 };
12226 edits.push((insert_location..insert_location, text));
12227 } else {
12228 // duplicate character-wise
12229 let start = selection.start;
12230 let end = selection.end;
12231 let text = buffer.text_for_range(start..end).collect::<String>();
12232 edits.push((selection.end..selection.end, text));
12233 }
12234 }
12235
12236 self.transact(window, cx, |this, window, cx| {
12237 this.buffer.update(cx, |buffer, cx| {
12238 buffer.edit(edits, None, cx);
12239 });
12240
12241 // When duplicating upward with whole lines, move the cursor to the duplicated line
12242 if upwards && whole_lines {
12243 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12244
12245 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12246 let mut new_ranges = Vec::new();
12247 let selections = s.all::<Point>(&display_map);
12248 let mut selections_iter = selections.iter().peekable();
12249
12250 while let Some(first_selection) = selections_iter.next() {
12251 // Group contiguous selections together to find the total row span
12252 let mut group_selections = vec![first_selection];
12253 let mut rows = first_selection.spanned_rows(false, &display_map);
12254
12255 while let Some(next_selection) = selections_iter.peek() {
12256 let next_rows = next_selection.spanned_rows(false, &display_map);
12257 if next_rows.start < rows.end {
12258 rows.end = next_rows.end;
12259 group_selections.push(selections_iter.next().unwrap());
12260 } else {
12261 break;
12262 }
12263 }
12264
12265 let row_count = rows.end.0 - rows.start.0;
12266
12267 // Move all selections in this group up by the total number of duplicated rows
12268 for selection in group_selections {
12269 let new_start = Point::new(
12270 selection.start.row.saturating_sub(row_count),
12271 selection.start.column,
12272 );
12273
12274 let new_end = Point::new(
12275 selection.end.row.saturating_sub(row_count),
12276 selection.end.column,
12277 );
12278
12279 new_ranges.push(new_start..new_end);
12280 }
12281 }
12282
12283 s.select_ranges(new_ranges);
12284 });
12285 }
12286
12287 this.request_autoscroll(Autoscroll::fit(), cx);
12288 });
12289 }
12290
12291 pub fn duplicate_line_up(
12292 &mut self,
12293 _: &DuplicateLineUp,
12294 window: &mut Window,
12295 cx: &mut Context<Self>,
12296 ) {
12297 self.duplicate(true, true, window, cx);
12298 }
12299
12300 pub fn duplicate_line_down(
12301 &mut self,
12302 _: &DuplicateLineDown,
12303 window: &mut Window,
12304 cx: &mut Context<Self>,
12305 ) {
12306 self.duplicate(false, true, window, cx);
12307 }
12308
12309 pub fn duplicate_selection(
12310 &mut self,
12311 _: &DuplicateSelection,
12312 window: &mut Window,
12313 cx: &mut Context<Self>,
12314 ) {
12315 self.duplicate(false, false, window, cx);
12316 }
12317
12318 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12319 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12320 if self.mode.is_single_line() {
12321 cx.propagate();
12322 return;
12323 }
12324
12325 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12326 let buffer = self.buffer.read(cx).snapshot(cx);
12327
12328 let mut edits = Vec::new();
12329 let mut unfold_ranges = Vec::new();
12330 let mut refold_creases = Vec::new();
12331
12332 let selections = self.selections.all::<Point>(&display_map);
12333 let mut selections = selections.iter().peekable();
12334 let mut contiguous_row_selections = Vec::new();
12335 let mut new_selections = Vec::new();
12336
12337 while let Some(selection) = selections.next() {
12338 // Find all the selections that span a contiguous row range
12339 let (start_row, end_row) = consume_contiguous_rows(
12340 &mut contiguous_row_selections,
12341 selection,
12342 &display_map,
12343 &mut selections,
12344 );
12345
12346 // Move the text spanned by the row range to be before the line preceding the row range
12347 if start_row.0 > 0 {
12348 let range_to_move = Point::new(
12349 start_row.previous_row().0,
12350 buffer.line_len(start_row.previous_row()),
12351 )
12352 ..Point::new(
12353 end_row.previous_row().0,
12354 buffer.line_len(end_row.previous_row()),
12355 );
12356 let insertion_point = display_map
12357 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12358 .0;
12359
12360 // Don't move lines across excerpts
12361 if buffer
12362 .excerpt_containing(insertion_point..range_to_move.end)
12363 .is_some()
12364 {
12365 let text = buffer
12366 .text_for_range(range_to_move.clone())
12367 .flat_map(|s| s.chars())
12368 .skip(1)
12369 .chain(['\n'])
12370 .collect::<String>();
12371
12372 edits.push((
12373 buffer.anchor_after(range_to_move.start)
12374 ..buffer.anchor_before(range_to_move.end),
12375 String::new(),
12376 ));
12377 let insertion_anchor = buffer.anchor_after(insertion_point);
12378 edits.push((insertion_anchor..insertion_anchor, text));
12379
12380 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12381
12382 // Move selections up
12383 new_selections.extend(contiguous_row_selections.drain(..).map(
12384 |mut selection| {
12385 selection.start.row -= row_delta;
12386 selection.end.row -= row_delta;
12387 selection
12388 },
12389 ));
12390
12391 // Move folds up
12392 unfold_ranges.push(range_to_move.clone());
12393 for fold in display_map.folds_in_range(
12394 buffer.anchor_before(range_to_move.start)
12395 ..buffer.anchor_after(range_to_move.end),
12396 ) {
12397 let mut start = fold.range.start.to_point(&buffer);
12398 let mut end = fold.range.end.to_point(&buffer);
12399 start.row -= row_delta;
12400 end.row -= row_delta;
12401 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12402 }
12403 }
12404 }
12405
12406 // If we didn't move line(s), preserve the existing selections
12407 new_selections.append(&mut contiguous_row_selections);
12408 }
12409
12410 self.transact(window, cx, |this, window, cx| {
12411 this.unfold_ranges(&unfold_ranges, true, true, cx);
12412 this.buffer.update(cx, |buffer, cx| {
12413 for (range, text) in edits {
12414 buffer.edit([(range, text)], None, cx);
12415 }
12416 });
12417 this.fold_creases(refold_creases, true, window, cx);
12418 this.change_selections(Default::default(), window, cx, |s| {
12419 s.select(new_selections);
12420 })
12421 });
12422 }
12423
12424 pub fn move_line_down(
12425 &mut self,
12426 _: &MoveLineDown,
12427 window: &mut Window,
12428 cx: &mut Context<Self>,
12429 ) {
12430 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12431 if self.mode.is_single_line() {
12432 cx.propagate();
12433 return;
12434 }
12435
12436 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12437 let buffer = self.buffer.read(cx).snapshot(cx);
12438
12439 let mut edits = Vec::new();
12440 let mut unfold_ranges = Vec::new();
12441 let mut refold_creases = Vec::new();
12442
12443 let selections = self.selections.all::<Point>(&display_map);
12444 let mut selections = selections.iter().peekable();
12445 let mut contiguous_row_selections = Vec::new();
12446 let mut new_selections = Vec::new();
12447
12448 while let Some(selection) = selections.next() {
12449 // Find all the selections that span a contiguous row range
12450 let (start_row, end_row) = consume_contiguous_rows(
12451 &mut contiguous_row_selections,
12452 selection,
12453 &display_map,
12454 &mut selections,
12455 );
12456
12457 // Move the text spanned by the row range to be after the last line of the row range
12458 if end_row.0 <= buffer.max_point().row {
12459 let range_to_move =
12460 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12461 let insertion_point = display_map
12462 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12463 .0;
12464
12465 // Don't move lines across excerpt boundaries
12466 if buffer
12467 .excerpt_containing(range_to_move.start..insertion_point)
12468 .is_some()
12469 {
12470 let mut text = String::from("\n");
12471 text.extend(buffer.text_for_range(range_to_move.clone()));
12472 text.pop(); // Drop trailing newline
12473 edits.push((
12474 buffer.anchor_after(range_to_move.start)
12475 ..buffer.anchor_before(range_to_move.end),
12476 String::new(),
12477 ));
12478 let insertion_anchor = buffer.anchor_after(insertion_point);
12479 edits.push((insertion_anchor..insertion_anchor, text));
12480
12481 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12482
12483 // Move selections down
12484 new_selections.extend(contiguous_row_selections.drain(..).map(
12485 |mut selection| {
12486 selection.start.row += row_delta;
12487 selection.end.row += row_delta;
12488 selection
12489 },
12490 ));
12491
12492 // Move folds down
12493 unfold_ranges.push(range_to_move.clone());
12494 for fold in display_map.folds_in_range(
12495 buffer.anchor_before(range_to_move.start)
12496 ..buffer.anchor_after(range_to_move.end),
12497 ) {
12498 let mut start = fold.range.start.to_point(&buffer);
12499 let mut end = fold.range.end.to_point(&buffer);
12500 start.row += row_delta;
12501 end.row += row_delta;
12502 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12503 }
12504 }
12505 }
12506
12507 // If we didn't move line(s), preserve the existing selections
12508 new_selections.append(&mut contiguous_row_selections);
12509 }
12510
12511 self.transact(window, cx, |this, window, cx| {
12512 this.unfold_ranges(&unfold_ranges, true, true, cx);
12513 this.buffer.update(cx, |buffer, cx| {
12514 for (range, text) in edits {
12515 buffer.edit([(range, text)], None, cx);
12516 }
12517 });
12518 this.fold_creases(refold_creases, true, window, cx);
12519 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12520 });
12521 }
12522
12523 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12524 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12525 let text_layout_details = &self.text_layout_details(window);
12526 self.transact(window, cx, |this, window, cx| {
12527 let edits = this.change_selections(Default::default(), window, cx, |s| {
12528 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12529 s.move_with(|display_map, selection| {
12530 if !selection.is_empty() {
12531 return;
12532 }
12533
12534 let mut head = selection.head();
12535 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12536 if head.column() == display_map.line_len(head.row()) {
12537 transpose_offset = display_map
12538 .buffer_snapshot()
12539 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12540 }
12541
12542 if transpose_offset == MultiBufferOffset(0) {
12543 return;
12544 }
12545
12546 *head.column_mut() += 1;
12547 head = display_map.clip_point(head, Bias::Right);
12548 let goal = SelectionGoal::HorizontalPosition(
12549 display_map
12550 .x_for_display_point(head, text_layout_details)
12551 .into(),
12552 );
12553 selection.collapse_to(head, goal);
12554
12555 let transpose_start = display_map
12556 .buffer_snapshot()
12557 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12558 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12559 let transpose_end = display_map
12560 .buffer_snapshot()
12561 .clip_offset(transpose_offset + 1usize, Bias::Right);
12562 if let Some(ch) = display_map
12563 .buffer_snapshot()
12564 .chars_at(transpose_start)
12565 .next()
12566 {
12567 edits.push((transpose_start..transpose_offset, String::new()));
12568 edits.push((transpose_end..transpose_end, ch.to_string()));
12569 }
12570 }
12571 });
12572 edits
12573 });
12574 this.buffer
12575 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12576 let selections = this
12577 .selections
12578 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12579 this.change_selections(Default::default(), window, cx, |s| {
12580 s.select(selections);
12581 });
12582 });
12583 }
12584
12585 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12586 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12587 if self.mode.is_single_line() {
12588 cx.propagate();
12589 return;
12590 }
12591
12592 self.rewrap_impl(RewrapOptions::default(), cx)
12593 }
12594
12595 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12596 let buffer = self.buffer.read(cx).snapshot(cx);
12597 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12598
12599 #[derive(Clone, Debug, PartialEq)]
12600 enum CommentFormat {
12601 /// single line comment, with prefix for line
12602 Line(String),
12603 /// single line within a block comment, with prefix for line
12604 BlockLine(String),
12605 /// a single line of a block comment that includes the initial delimiter
12606 BlockCommentWithStart(BlockCommentConfig),
12607 /// a single line of a block comment that includes the ending delimiter
12608 BlockCommentWithEnd(BlockCommentConfig),
12609 }
12610
12611 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12612 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12613 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12614 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12615 .peekable();
12616
12617 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12618 row
12619 } else {
12620 return Vec::new();
12621 };
12622
12623 let language_settings = buffer.language_settings_at(selection.head(), cx);
12624 let language_scope = buffer.language_scope_at(selection.head());
12625
12626 let indent_and_prefix_for_row =
12627 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12628 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12629 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12630 &language_scope
12631 {
12632 let indent_end = Point::new(row, indent.len);
12633 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12634 let line_text_after_indent = buffer
12635 .text_for_range(indent_end..line_end)
12636 .collect::<String>();
12637
12638 let is_within_comment_override = buffer
12639 .language_scope_at(indent_end)
12640 .is_some_and(|scope| scope.override_name() == Some("comment"));
12641 let comment_delimiters = if is_within_comment_override {
12642 // we are within a comment syntax node, but we don't
12643 // yet know what kind of comment: block, doc or line
12644 match (
12645 language_scope.documentation_comment(),
12646 language_scope.block_comment(),
12647 ) {
12648 (Some(config), _) | (_, Some(config))
12649 if buffer.contains_str_at(indent_end, &config.start) =>
12650 {
12651 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12652 }
12653 (Some(config), _) | (_, Some(config))
12654 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12655 {
12656 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12657 }
12658 (Some(config), _) | (_, Some(config))
12659 if buffer.contains_str_at(indent_end, &config.prefix) =>
12660 {
12661 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12662 }
12663 (_, _) => language_scope
12664 .line_comment_prefixes()
12665 .iter()
12666 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12667 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12668 }
12669 } else {
12670 // we not in an overridden comment node, but we may
12671 // be within a non-overridden line comment node
12672 language_scope
12673 .line_comment_prefixes()
12674 .iter()
12675 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12676 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12677 };
12678
12679 let rewrap_prefix = language_scope
12680 .rewrap_prefixes()
12681 .iter()
12682 .find_map(|prefix_regex| {
12683 prefix_regex.find(&line_text_after_indent).map(|mat| {
12684 if mat.start() == 0 {
12685 Some(mat.as_str().to_string())
12686 } else {
12687 None
12688 }
12689 })
12690 })
12691 .flatten();
12692 (comment_delimiters, rewrap_prefix)
12693 } else {
12694 (None, None)
12695 };
12696 (indent, comment_prefix, rewrap_prefix)
12697 };
12698
12699 let mut ranges = Vec::new();
12700 let from_empty_selection = selection.is_empty();
12701
12702 let mut current_range_start = first_row;
12703 let mut prev_row = first_row;
12704 let (
12705 mut current_range_indent,
12706 mut current_range_comment_delimiters,
12707 mut current_range_rewrap_prefix,
12708 ) = indent_and_prefix_for_row(first_row);
12709
12710 for row in non_blank_rows_iter.skip(1) {
12711 let has_paragraph_break = row > prev_row + 1;
12712
12713 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12714 indent_and_prefix_for_row(row);
12715
12716 let has_indent_change = row_indent != current_range_indent;
12717 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12718
12719 let has_boundary_change = has_comment_change
12720 || row_rewrap_prefix.is_some()
12721 || (has_indent_change && current_range_comment_delimiters.is_some());
12722
12723 if has_paragraph_break || has_boundary_change {
12724 ranges.push((
12725 language_settings.clone(),
12726 Point::new(current_range_start, 0)
12727 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12728 current_range_indent,
12729 current_range_comment_delimiters.clone(),
12730 current_range_rewrap_prefix.clone(),
12731 from_empty_selection,
12732 ));
12733 current_range_start = row;
12734 current_range_indent = row_indent;
12735 current_range_comment_delimiters = row_comment_delimiters;
12736 current_range_rewrap_prefix = row_rewrap_prefix;
12737 }
12738 prev_row = row;
12739 }
12740
12741 ranges.push((
12742 language_settings.clone(),
12743 Point::new(current_range_start, 0)
12744 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12745 current_range_indent,
12746 current_range_comment_delimiters,
12747 current_range_rewrap_prefix,
12748 from_empty_selection,
12749 ));
12750
12751 ranges
12752 });
12753
12754 let mut edits = Vec::new();
12755 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12756
12757 for (
12758 language_settings,
12759 wrap_range,
12760 mut indent_size,
12761 comment_prefix,
12762 rewrap_prefix,
12763 from_empty_selection,
12764 ) in wrap_ranges
12765 {
12766 let mut start_row = wrap_range.start.row;
12767 let mut end_row = wrap_range.end.row;
12768
12769 // Skip selections that overlap with a range that has already been rewrapped.
12770 let selection_range = start_row..end_row;
12771 if rewrapped_row_ranges
12772 .iter()
12773 .any(|range| range.overlaps(&selection_range))
12774 {
12775 continue;
12776 }
12777
12778 let tab_size = language_settings.tab_size;
12779
12780 let (line_prefix, inside_comment) = match &comment_prefix {
12781 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12782 (Some(prefix.as_str()), true)
12783 }
12784 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12785 (Some(prefix.as_ref()), true)
12786 }
12787 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12788 start: _,
12789 end: _,
12790 prefix,
12791 tab_size,
12792 })) => {
12793 indent_size.len += tab_size;
12794 (Some(prefix.as_ref()), true)
12795 }
12796 None => (None, false),
12797 };
12798 let indent_prefix = indent_size.chars().collect::<String>();
12799 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12800
12801 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12802 RewrapBehavior::InComments => inside_comment,
12803 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12804 RewrapBehavior::Anywhere => true,
12805 };
12806
12807 let should_rewrap = options.override_language_settings
12808 || allow_rewrap_based_on_language
12809 || self.hard_wrap.is_some();
12810 if !should_rewrap {
12811 continue;
12812 }
12813
12814 if from_empty_selection {
12815 'expand_upwards: while start_row > 0 {
12816 let prev_row = start_row - 1;
12817 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12818 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12819 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12820 {
12821 start_row = prev_row;
12822 } else {
12823 break 'expand_upwards;
12824 }
12825 }
12826
12827 'expand_downwards: while end_row < buffer.max_point().row {
12828 let next_row = end_row + 1;
12829 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12830 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12831 && !buffer.is_line_blank(MultiBufferRow(next_row))
12832 {
12833 end_row = next_row;
12834 } else {
12835 break 'expand_downwards;
12836 }
12837 }
12838 }
12839
12840 let start = Point::new(start_row, 0);
12841 let start_offset = ToOffset::to_offset(&start, &buffer);
12842 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12843 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12844 let mut first_line_delimiter = None;
12845 let mut last_line_delimiter = None;
12846 let Some(lines_without_prefixes) = selection_text
12847 .lines()
12848 .enumerate()
12849 .map(|(ix, line)| {
12850 let line_trimmed = line.trim_start();
12851 if rewrap_prefix.is_some() && ix > 0 {
12852 Ok(line_trimmed)
12853 } else if let Some(
12854 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12855 start,
12856 prefix,
12857 end,
12858 tab_size,
12859 })
12860 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12861 start,
12862 prefix,
12863 end,
12864 tab_size,
12865 }),
12866 ) = &comment_prefix
12867 {
12868 let line_trimmed = line_trimmed
12869 .strip_prefix(start.as_ref())
12870 .map(|s| {
12871 let mut indent_size = indent_size;
12872 indent_size.len -= tab_size;
12873 let indent_prefix: String = indent_size.chars().collect();
12874 first_line_delimiter = Some((indent_prefix, start));
12875 s.trim_start()
12876 })
12877 .unwrap_or(line_trimmed);
12878 let line_trimmed = line_trimmed
12879 .strip_suffix(end.as_ref())
12880 .map(|s| {
12881 last_line_delimiter = Some(end);
12882 s.trim_end()
12883 })
12884 .unwrap_or(line_trimmed);
12885 let line_trimmed = line_trimmed
12886 .strip_prefix(prefix.as_ref())
12887 .unwrap_or(line_trimmed);
12888 Ok(line_trimmed)
12889 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12890 line_trimmed.strip_prefix(prefix).with_context(|| {
12891 format!("line did not start with prefix {prefix:?}: {line:?}")
12892 })
12893 } else {
12894 line_trimmed
12895 .strip_prefix(&line_prefix.trim_start())
12896 .with_context(|| {
12897 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12898 })
12899 }
12900 })
12901 .collect::<Result<Vec<_>, _>>()
12902 .log_err()
12903 else {
12904 continue;
12905 };
12906
12907 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12908 buffer
12909 .language_settings_at(Point::new(start_row, 0), cx)
12910 .preferred_line_length as usize
12911 });
12912
12913 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12914 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12915 } else {
12916 line_prefix.clone()
12917 };
12918
12919 let wrapped_text = {
12920 let mut wrapped_text = wrap_with_prefix(
12921 line_prefix,
12922 subsequent_lines_prefix,
12923 lines_without_prefixes.join("\n"),
12924 wrap_column,
12925 tab_size,
12926 options.preserve_existing_whitespace,
12927 );
12928
12929 if let Some((indent, delimiter)) = first_line_delimiter {
12930 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12931 }
12932 if let Some(last_line) = last_line_delimiter {
12933 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12934 }
12935
12936 wrapped_text
12937 };
12938
12939 // TODO: should always use char-based diff while still supporting cursor behavior that
12940 // matches vim.
12941 let mut diff_options = DiffOptions::default();
12942 if options.override_language_settings {
12943 diff_options.max_word_diff_len = 0;
12944 diff_options.max_word_diff_line_count = 0;
12945 } else {
12946 diff_options.max_word_diff_len = usize::MAX;
12947 diff_options.max_word_diff_line_count = usize::MAX;
12948 }
12949
12950 for (old_range, new_text) in
12951 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12952 {
12953 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12954 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12955 edits.push((edit_start..edit_end, new_text));
12956 }
12957
12958 rewrapped_row_ranges.push(start_row..=end_row);
12959 }
12960
12961 self.buffer
12962 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12963 }
12964
12965 pub fn cut_common(
12966 &mut self,
12967 cut_no_selection_line: bool,
12968 window: &mut Window,
12969 cx: &mut Context<Self>,
12970 ) -> ClipboardItem {
12971 let mut text = String::new();
12972 let buffer = self.buffer.read(cx).snapshot(cx);
12973 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12974 let mut clipboard_selections = Vec::with_capacity(selections.len());
12975 {
12976 let max_point = buffer.max_point();
12977 let mut is_first = true;
12978 let mut prev_selection_was_entire_line = false;
12979 for selection in &mut selections {
12980 let is_entire_line =
12981 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12982 if is_entire_line {
12983 selection.start = Point::new(selection.start.row, 0);
12984 if !selection.is_empty() && selection.end.column == 0 {
12985 selection.end = cmp::min(max_point, selection.end);
12986 } else {
12987 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12988 }
12989 selection.goal = SelectionGoal::None;
12990 }
12991 if is_first {
12992 is_first = false;
12993 } else if !prev_selection_was_entire_line {
12994 text += "\n";
12995 }
12996 prev_selection_was_entire_line = is_entire_line;
12997 let mut len = 0;
12998 for chunk in buffer.text_for_range(selection.start..selection.end) {
12999 text.push_str(chunk);
13000 len += chunk.len();
13001 }
13002
13003 clipboard_selections.push(ClipboardSelection::for_buffer(
13004 len,
13005 is_entire_line,
13006 selection.range(),
13007 &buffer,
13008 self.project.as_ref(),
13009 cx,
13010 ));
13011 }
13012 }
13013
13014 self.transact(window, cx, |this, window, cx| {
13015 this.change_selections(Default::default(), window, cx, |s| {
13016 s.select(selections);
13017 });
13018 this.insert("", window, cx);
13019 });
13020 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13021 }
13022
13023 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13024 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13025 let item = self.cut_common(true, window, cx);
13026 cx.write_to_clipboard(item);
13027 }
13028
13029 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13030 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13031 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13032 s.move_with(|snapshot, sel| {
13033 if sel.is_empty() {
13034 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13035 }
13036 if sel.is_empty() {
13037 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13038 }
13039 });
13040 });
13041 let item = self.cut_common(false, window, cx);
13042 cx.set_global(KillRing(item))
13043 }
13044
13045 pub fn kill_ring_yank(
13046 &mut self,
13047 _: &KillRingYank,
13048 window: &mut Window,
13049 cx: &mut Context<Self>,
13050 ) {
13051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13052 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13053 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13054 (kill_ring.text().to_string(), kill_ring.metadata_json())
13055 } else {
13056 return;
13057 }
13058 } else {
13059 return;
13060 };
13061 self.do_paste(&text, metadata, false, window, cx);
13062 }
13063
13064 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13065 self.do_copy(true, cx);
13066 }
13067
13068 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13069 self.do_copy(false, cx);
13070 }
13071
13072 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13073 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13074 let buffer = self.buffer.read(cx).read(cx);
13075 let mut text = String::new();
13076
13077 let mut clipboard_selections = Vec::with_capacity(selections.len());
13078 {
13079 let max_point = buffer.max_point();
13080 let mut is_first = true;
13081 let mut prev_selection_was_entire_line = false;
13082 for selection in &selections {
13083 let mut start = selection.start;
13084 let mut end = selection.end;
13085 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13086 let mut add_trailing_newline = false;
13087 if is_entire_line {
13088 start = Point::new(start.row, 0);
13089 let next_line_start = Point::new(end.row + 1, 0);
13090 if next_line_start <= max_point {
13091 end = next_line_start;
13092 } else {
13093 // We're on the last line without a trailing newline.
13094 // Copy to the end of the line and add a newline afterwards.
13095 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13096 add_trailing_newline = true;
13097 }
13098 }
13099
13100 let mut trimmed_selections = Vec::new();
13101 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13102 let row = MultiBufferRow(start.row);
13103 let first_indent = buffer.indent_size_for_line(row);
13104 if first_indent.len == 0 || start.column > first_indent.len {
13105 trimmed_selections.push(start..end);
13106 } else {
13107 trimmed_selections.push(
13108 Point::new(row.0, first_indent.len)
13109 ..Point::new(row.0, buffer.line_len(row)),
13110 );
13111 for row in start.row + 1..=end.row {
13112 let mut line_len = buffer.line_len(MultiBufferRow(row));
13113 if row == end.row {
13114 line_len = end.column;
13115 }
13116 if line_len == 0 {
13117 trimmed_selections
13118 .push(Point::new(row, 0)..Point::new(row, line_len));
13119 continue;
13120 }
13121 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13122 if row_indent_size.len >= first_indent.len {
13123 trimmed_selections.push(
13124 Point::new(row, first_indent.len)..Point::new(row, line_len),
13125 );
13126 } else {
13127 trimmed_selections.clear();
13128 trimmed_selections.push(start..end);
13129 break;
13130 }
13131 }
13132 }
13133 } else {
13134 trimmed_selections.push(start..end);
13135 }
13136
13137 for trimmed_range in trimmed_selections {
13138 if is_first {
13139 is_first = false;
13140 } else if !prev_selection_was_entire_line {
13141 text += "\n";
13142 }
13143 prev_selection_was_entire_line = is_entire_line;
13144 let mut len = 0;
13145 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13146 text.push_str(chunk);
13147 len += chunk.len();
13148 }
13149 if add_trailing_newline {
13150 text.push('\n');
13151 len += 1;
13152 }
13153 clipboard_selections.push(ClipboardSelection::for_buffer(
13154 len,
13155 is_entire_line,
13156 trimmed_range,
13157 &buffer,
13158 self.project.as_ref(),
13159 cx,
13160 ));
13161 }
13162 }
13163 }
13164
13165 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13166 text,
13167 clipboard_selections,
13168 ));
13169 }
13170
13171 pub fn do_paste(
13172 &mut self,
13173 text: &String,
13174 clipboard_selections: Option<Vec<ClipboardSelection>>,
13175 handle_entire_lines: bool,
13176 window: &mut Window,
13177 cx: &mut Context<Self>,
13178 ) {
13179 if self.read_only(cx) {
13180 return;
13181 }
13182
13183 let clipboard_text = Cow::Borrowed(text.as_str());
13184
13185 self.transact(window, cx, |this, window, cx| {
13186 let had_active_edit_prediction = this.has_active_edit_prediction();
13187 let display_map = this.display_snapshot(cx);
13188 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13189 let cursor_offset = this
13190 .selections
13191 .last::<MultiBufferOffset>(&display_map)
13192 .head();
13193
13194 if let Some(mut clipboard_selections) = clipboard_selections {
13195 let all_selections_were_entire_line =
13196 clipboard_selections.iter().all(|s| s.is_entire_line);
13197 let first_selection_indent_column =
13198 clipboard_selections.first().map(|s| s.first_line_indent);
13199 if clipboard_selections.len() != old_selections.len() {
13200 clipboard_selections.drain(..);
13201 }
13202 let mut auto_indent_on_paste = true;
13203
13204 this.buffer.update(cx, |buffer, cx| {
13205 let snapshot = buffer.read(cx);
13206 auto_indent_on_paste = snapshot
13207 .language_settings_at(cursor_offset, cx)
13208 .auto_indent_on_paste;
13209
13210 let mut start_offset = 0;
13211 let mut edits = Vec::new();
13212 let mut original_indent_columns = Vec::new();
13213 for (ix, selection) in old_selections.iter().enumerate() {
13214 let to_insert;
13215 let entire_line;
13216 let original_indent_column;
13217 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13218 let end_offset = start_offset + clipboard_selection.len;
13219 to_insert = &clipboard_text[start_offset..end_offset];
13220 entire_line = clipboard_selection.is_entire_line;
13221 start_offset = if entire_line {
13222 end_offset
13223 } else {
13224 end_offset + 1
13225 };
13226 original_indent_column = Some(clipboard_selection.first_line_indent);
13227 } else {
13228 to_insert = &*clipboard_text;
13229 entire_line = all_selections_were_entire_line;
13230 original_indent_column = first_selection_indent_column
13231 }
13232
13233 let (range, to_insert) =
13234 if selection.is_empty() && handle_entire_lines && entire_line {
13235 // If the corresponding selection was empty when this slice of the
13236 // clipboard text was written, then the entire line containing the
13237 // selection was copied. If this selection is also currently empty,
13238 // then paste the line before the current line of the buffer.
13239 let column = selection.start.to_point(&snapshot).column as usize;
13240 let line_start = selection.start - column;
13241 (line_start..line_start, Cow::Borrowed(to_insert))
13242 } else {
13243 let language = snapshot.language_at(selection.head());
13244 let range = selection.range();
13245 if let Some(language) = language
13246 && language.name() == "Markdown".into()
13247 {
13248 edit_for_markdown_paste(
13249 &snapshot,
13250 range,
13251 to_insert,
13252 url::Url::parse(to_insert).ok(),
13253 )
13254 } else {
13255 (range, Cow::Borrowed(to_insert))
13256 }
13257 };
13258
13259 edits.push((range, to_insert));
13260 original_indent_columns.push(original_indent_column);
13261 }
13262 drop(snapshot);
13263
13264 buffer.edit(
13265 edits,
13266 if auto_indent_on_paste {
13267 Some(AutoindentMode::Block {
13268 original_indent_columns,
13269 })
13270 } else {
13271 None
13272 },
13273 cx,
13274 );
13275 });
13276
13277 let selections = this
13278 .selections
13279 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13280 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13281 } else {
13282 let url = url::Url::parse(&clipboard_text).ok();
13283
13284 let auto_indent_mode = if !clipboard_text.is_empty() {
13285 Some(AutoindentMode::Block {
13286 original_indent_columns: Vec::new(),
13287 })
13288 } else {
13289 None
13290 };
13291
13292 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13293 let snapshot = buffer.snapshot(cx);
13294
13295 let anchors = old_selections
13296 .iter()
13297 .map(|s| {
13298 let anchor = snapshot.anchor_after(s.head());
13299 s.map(|_| anchor)
13300 })
13301 .collect::<Vec<_>>();
13302
13303 let mut edits = Vec::new();
13304
13305 for selection in old_selections.iter() {
13306 let language = snapshot.language_at(selection.head());
13307 let range = selection.range();
13308
13309 let (edit_range, edit_text) = if let Some(language) = language
13310 && language.name() == "Markdown".into()
13311 {
13312 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13313 } else {
13314 (range, clipboard_text.clone())
13315 };
13316
13317 edits.push((edit_range, edit_text));
13318 }
13319
13320 drop(snapshot);
13321 buffer.edit(edits, auto_indent_mode, cx);
13322
13323 anchors
13324 });
13325
13326 this.change_selections(Default::default(), window, cx, |s| {
13327 s.select_anchors(selection_anchors);
13328 });
13329 }
13330
13331 // 🤔 | .. | show_in_menu |
13332 // | .. | true true
13333 // | had_edit_prediction | false true
13334
13335 let trigger_in_words =
13336 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13337
13338 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13339 });
13340 }
13341
13342 pub fn diff_clipboard_with_selection(
13343 &mut self,
13344 _: &DiffClipboardWithSelection,
13345 window: &mut Window,
13346 cx: &mut Context<Self>,
13347 ) {
13348 let selections = self
13349 .selections
13350 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13351
13352 if selections.is_empty() {
13353 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13354 return;
13355 };
13356
13357 let clipboard_text = match cx.read_from_clipboard() {
13358 Some(item) => match item.entries().first() {
13359 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13360 _ => None,
13361 },
13362 None => None,
13363 };
13364
13365 let Some(clipboard_text) = clipboard_text else {
13366 log::warn!("Clipboard doesn't contain text.");
13367 return;
13368 };
13369
13370 window.dispatch_action(
13371 Box::new(DiffClipboardWithSelectionData {
13372 clipboard_text,
13373 editor: cx.entity(),
13374 }),
13375 cx,
13376 );
13377 }
13378
13379 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13380 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13381 if let Some(item) = cx.read_from_clipboard() {
13382 let entries = item.entries();
13383
13384 match entries.first() {
13385 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13386 // of all the pasted entries.
13387 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13388 .do_paste(
13389 clipboard_string.text(),
13390 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13391 true,
13392 window,
13393 cx,
13394 ),
13395 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13396 }
13397 }
13398 }
13399
13400 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13401 if self.read_only(cx) {
13402 return;
13403 }
13404
13405 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13406
13407 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13408 if let Some((selections, _)) =
13409 self.selection_history.transaction(transaction_id).cloned()
13410 {
13411 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13412 s.select_anchors(selections.to_vec());
13413 });
13414 } else {
13415 log::error!(
13416 "No entry in selection_history found for undo. \
13417 This may correspond to a bug where undo does not update the selection. \
13418 If this is occurring, please add details to \
13419 https://github.com/zed-industries/zed/issues/22692"
13420 );
13421 }
13422 self.request_autoscroll(Autoscroll::fit(), cx);
13423 self.unmark_text(window, cx);
13424 self.refresh_edit_prediction(true, false, window, cx);
13425 cx.emit(EditorEvent::Edited { transaction_id });
13426 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13427 }
13428 }
13429
13430 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13431 if self.read_only(cx) {
13432 return;
13433 }
13434
13435 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13436
13437 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13438 if let Some((_, Some(selections))) =
13439 self.selection_history.transaction(transaction_id).cloned()
13440 {
13441 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13442 s.select_anchors(selections.to_vec());
13443 });
13444 } else {
13445 log::error!(
13446 "No entry in selection_history found for redo. \
13447 This may correspond to a bug where undo does not update the selection. \
13448 If this is occurring, please add details to \
13449 https://github.com/zed-industries/zed/issues/22692"
13450 );
13451 }
13452 self.request_autoscroll(Autoscroll::fit(), cx);
13453 self.unmark_text(window, cx);
13454 self.refresh_edit_prediction(true, false, window, cx);
13455 cx.emit(EditorEvent::Edited { transaction_id });
13456 }
13457 }
13458
13459 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13460 self.buffer
13461 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13462 }
13463
13464 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13465 self.buffer
13466 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13467 }
13468
13469 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13470 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13471 self.change_selections(Default::default(), window, cx, |s| {
13472 s.move_with(|map, selection| {
13473 let cursor = if selection.is_empty() {
13474 movement::left(map, selection.start)
13475 } else {
13476 selection.start
13477 };
13478 selection.collapse_to(cursor, SelectionGoal::None);
13479 });
13480 })
13481 }
13482
13483 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13485 self.change_selections(Default::default(), window, cx, |s| {
13486 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13487 })
13488 }
13489
13490 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13491 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13492 self.change_selections(Default::default(), window, cx, |s| {
13493 s.move_with(|map, selection| {
13494 let cursor = if selection.is_empty() {
13495 movement::right(map, selection.end)
13496 } else {
13497 selection.end
13498 };
13499 selection.collapse_to(cursor, SelectionGoal::None)
13500 });
13501 })
13502 }
13503
13504 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13505 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13506 self.change_selections(Default::default(), window, cx, |s| {
13507 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13508 });
13509 }
13510
13511 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13512 if self.take_rename(true, window, cx).is_some() {
13513 return;
13514 }
13515
13516 if self.mode.is_single_line() {
13517 cx.propagate();
13518 return;
13519 }
13520
13521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13522
13523 let text_layout_details = &self.text_layout_details(window);
13524 let selection_count = self.selections.count();
13525 let first_selection = self.selections.first_anchor();
13526
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_with(|map, selection| {
13529 if !selection.is_empty() {
13530 selection.goal = SelectionGoal::None;
13531 }
13532 let (cursor, goal) = movement::up(
13533 map,
13534 selection.start,
13535 selection.goal,
13536 false,
13537 text_layout_details,
13538 );
13539 selection.collapse_to(cursor, goal);
13540 });
13541 });
13542
13543 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13544 {
13545 cx.propagate();
13546 }
13547 }
13548
13549 pub fn move_up_by_lines(
13550 &mut self,
13551 action: &MoveUpByLines,
13552 window: &mut Window,
13553 cx: &mut Context<Self>,
13554 ) {
13555 if self.take_rename(true, window, cx).is_some() {
13556 return;
13557 }
13558
13559 if self.mode.is_single_line() {
13560 cx.propagate();
13561 return;
13562 }
13563
13564 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13565
13566 let text_layout_details = &self.text_layout_details(window);
13567
13568 self.change_selections(Default::default(), window, cx, |s| {
13569 s.move_with(|map, selection| {
13570 if !selection.is_empty() {
13571 selection.goal = SelectionGoal::None;
13572 }
13573 let (cursor, goal) = movement::up_by_rows(
13574 map,
13575 selection.start,
13576 action.lines,
13577 selection.goal,
13578 false,
13579 text_layout_details,
13580 );
13581 selection.collapse_to(cursor, goal);
13582 });
13583 })
13584 }
13585
13586 pub fn move_down_by_lines(
13587 &mut self,
13588 action: &MoveDownByLines,
13589 window: &mut Window,
13590 cx: &mut Context<Self>,
13591 ) {
13592 if self.take_rename(true, window, cx).is_some() {
13593 return;
13594 }
13595
13596 if self.mode.is_single_line() {
13597 cx.propagate();
13598 return;
13599 }
13600
13601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13602
13603 let text_layout_details = &self.text_layout_details(window);
13604
13605 self.change_selections(Default::default(), window, cx, |s| {
13606 s.move_with(|map, selection| {
13607 if !selection.is_empty() {
13608 selection.goal = SelectionGoal::None;
13609 }
13610 let (cursor, goal) = movement::down_by_rows(
13611 map,
13612 selection.start,
13613 action.lines,
13614 selection.goal,
13615 false,
13616 text_layout_details,
13617 );
13618 selection.collapse_to(cursor, goal);
13619 });
13620 })
13621 }
13622
13623 pub fn select_down_by_lines(
13624 &mut self,
13625 action: &SelectDownByLines,
13626 window: &mut Window,
13627 cx: &mut Context<Self>,
13628 ) {
13629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13630 let text_layout_details = &self.text_layout_details(window);
13631 self.change_selections(Default::default(), window, cx, |s| {
13632 s.move_heads_with(|map, head, goal| {
13633 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13634 })
13635 })
13636 }
13637
13638 pub fn select_up_by_lines(
13639 &mut self,
13640 action: &SelectUpByLines,
13641 window: &mut Window,
13642 cx: &mut Context<Self>,
13643 ) {
13644 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13645 let text_layout_details = &self.text_layout_details(window);
13646 self.change_selections(Default::default(), window, cx, |s| {
13647 s.move_heads_with(|map, head, goal| {
13648 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13649 })
13650 })
13651 }
13652
13653 pub fn select_page_up(
13654 &mut self,
13655 _: &SelectPageUp,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 let Some(row_count) = self.visible_row_count() else {
13660 return;
13661 };
13662
13663 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13664
13665 let text_layout_details = &self.text_layout_details(window);
13666
13667 self.change_selections(Default::default(), window, cx, |s| {
13668 s.move_heads_with(|map, head, goal| {
13669 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13670 })
13671 })
13672 }
13673
13674 pub fn move_page_up(
13675 &mut self,
13676 action: &MovePageUp,
13677 window: &mut Window,
13678 cx: &mut Context<Self>,
13679 ) {
13680 if self.take_rename(true, window, cx).is_some() {
13681 return;
13682 }
13683
13684 if self
13685 .context_menu
13686 .borrow_mut()
13687 .as_mut()
13688 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13689 .unwrap_or(false)
13690 {
13691 return;
13692 }
13693
13694 if matches!(self.mode, EditorMode::SingleLine) {
13695 cx.propagate();
13696 return;
13697 }
13698
13699 let Some(row_count) = self.visible_row_count() else {
13700 return;
13701 };
13702
13703 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13704
13705 let effects = if action.center_cursor {
13706 SelectionEffects::scroll(Autoscroll::center())
13707 } else {
13708 SelectionEffects::default()
13709 };
13710
13711 let text_layout_details = &self.text_layout_details(window);
13712
13713 self.change_selections(effects, window, cx, |s| {
13714 s.move_with(|map, selection| {
13715 if !selection.is_empty() {
13716 selection.goal = SelectionGoal::None;
13717 }
13718 let (cursor, goal) = movement::up_by_rows(
13719 map,
13720 selection.end,
13721 row_count,
13722 selection.goal,
13723 false,
13724 text_layout_details,
13725 );
13726 selection.collapse_to(cursor, goal);
13727 });
13728 });
13729 }
13730
13731 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13732 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13733 let text_layout_details = &self.text_layout_details(window);
13734 self.change_selections(Default::default(), window, cx, |s| {
13735 s.move_heads_with(|map, head, goal| {
13736 movement::up(map, head, goal, false, text_layout_details)
13737 })
13738 })
13739 }
13740
13741 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13742 self.take_rename(true, window, cx);
13743
13744 if self.mode.is_single_line() {
13745 cx.propagate();
13746 return;
13747 }
13748
13749 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13750
13751 let text_layout_details = &self.text_layout_details(window);
13752 let selection_count = self.selections.count();
13753 let first_selection = self.selections.first_anchor();
13754
13755 self.change_selections(Default::default(), window, cx, |s| {
13756 s.move_with(|map, selection| {
13757 if !selection.is_empty() {
13758 selection.goal = SelectionGoal::None;
13759 }
13760 let (cursor, goal) = movement::down(
13761 map,
13762 selection.end,
13763 selection.goal,
13764 false,
13765 text_layout_details,
13766 );
13767 selection.collapse_to(cursor, goal);
13768 });
13769 });
13770
13771 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13772 {
13773 cx.propagate();
13774 }
13775 }
13776
13777 pub fn select_page_down(
13778 &mut self,
13779 _: &SelectPageDown,
13780 window: &mut Window,
13781 cx: &mut Context<Self>,
13782 ) {
13783 let Some(row_count) = self.visible_row_count() else {
13784 return;
13785 };
13786
13787 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13788
13789 let text_layout_details = &self.text_layout_details(window);
13790
13791 self.change_selections(Default::default(), window, cx, |s| {
13792 s.move_heads_with(|map, head, goal| {
13793 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13794 })
13795 })
13796 }
13797
13798 pub fn move_page_down(
13799 &mut self,
13800 action: &MovePageDown,
13801 window: &mut Window,
13802 cx: &mut Context<Self>,
13803 ) {
13804 if self.take_rename(true, window, cx).is_some() {
13805 return;
13806 }
13807
13808 if self
13809 .context_menu
13810 .borrow_mut()
13811 .as_mut()
13812 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13813 .unwrap_or(false)
13814 {
13815 return;
13816 }
13817
13818 if matches!(self.mode, EditorMode::SingleLine) {
13819 cx.propagate();
13820 return;
13821 }
13822
13823 let Some(row_count) = self.visible_row_count() else {
13824 return;
13825 };
13826
13827 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13828
13829 let effects = if action.center_cursor {
13830 SelectionEffects::scroll(Autoscroll::center())
13831 } else {
13832 SelectionEffects::default()
13833 };
13834
13835 let text_layout_details = &self.text_layout_details(window);
13836 self.change_selections(effects, window, cx, |s| {
13837 s.move_with(|map, selection| {
13838 if !selection.is_empty() {
13839 selection.goal = SelectionGoal::None;
13840 }
13841 let (cursor, goal) = movement::down_by_rows(
13842 map,
13843 selection.end,
13844 row_count,
13845 selection.goal,
13846 false,
13847 text_layout_details,
13848 );
13849 selection.collapse_to(cursor, goal);
13850 });
13851 });
13852 }
13853
13854 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13856 let text_layout_details = &self.text_layout_details(window);
13857 self.change_selections(Default::default(), window, cx, |s| {
13858 s.move_heads_with(|map, head, goal| {
13859 movement::down(map, head, goal, false, text_layout_details)
13860 })
13861 });
13862 }
13863
13864 pub fn context_menu_first(
13865 &mut self,
13866 _: &ContextMenuFirst,
13867 window: &mut Window,
13868 cx: &mut Context<Self>,
13869 ) {
13870 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13871 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13872 }
13873 }
13874
13875 pub fn context_menu_prev(
13876 &mut self,
13877 _: &ContextMenuPrevious,
13878 window: &mut Window,
13879 cx: &mut Context<Self>,
13880 ) {
13881 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13882 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13883 }
13884 }
13885
13886 pub fn context_menu_next(
13887 &mut self,
13888 _: &ContextMenuNext,
13889 window: &mut Window,
13890 cx: &mut Context<Self>,
13891 ) {
13892 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13893 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13894 }
13895 }
13896
13897 pub fn context_menu_last(
13898 &mut self,
13899 _: &ContextMenuLast,
13900 window: &mut Window,
13901 cx: &mut Context<Self>,
13902 ) {
13903 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13904 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13905 }
13906 }
13907
13908 pub fn signature_help_prev(
13909 &mut self,
13910 _: &SignatureHelpPrevious,
13911 _: &mut Window,
13912 cx: &mut Context<Self>,
13913 ) {
13914 if let Some(popover) = self.signature_help_state.popover_mut() {
13915 if popover.current_signature == 0 {
13916 popover.current_signature = popover.signatures.len() - 1;
13917 } else {
13918 popover.current_signature -= 1;
13919 }
13920 cx.notify();
13921 }
13922 }
13923
13924 pub fn signature_help_next(
13925 &mut self,
13926 _: &SignatureHelpNext,
13927 _: &mut Window,
13928 cx: &mut Context<Self>,
13929 ) {
13930 if let Some(popover) = self.signature_help_state.popover_mut() {
13931 if popover.current_signature + 1 == popover.signatures.len() {
13932 popover.current_signature = 0;
13933 } else {
13934 popover.current_signature += 1;
13935 }
13936 cx.notify();
13937 }
13938 }
13939
13940 pub fn move_to_previous_word_start(
13941 &mut self,
13942 _: &MoveToPreviousWordStart,
13943 window: &mut Window,
13944 cx: &mut Context<Self>,
13945 ) {
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.move_cursors_with(|map, head, _| {
13949 (
13950 movement::previous_word_start(map, head),
13951 SelectionGoal::None,
13952 )
13953 });
13954 })
13955 }
13956
13957 pub fn move_to_previous_subword_start(
13958 &mut self,
13959 _: &MoveToPreviousSubwordStart,
13960 window: &mut Window,
13961 cx: &mut Context<Self>,
13962 ) {
13963 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13964 self.change_selections(Default::default(), window, cx, |s| {
13965 s.move_cursors_with(|map, head, _| {
13966 (
13967 movement::previous_subword_start(map, head),
13968 SelectionGoal::None,
13969 )
13970 });
13971 })
13972 }
13973
13974 pub fn select_to_previous_word_start(
13975 &mut self,
13976 _: &SelectToPreviousWordStart,
13977 window: &mut Window,
13978 cx: &mut Context<Self>,
13979 ) {
13980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13981 self.change_selections(Default::default(), window, cx, |s| {
13982 s.move_heads_with(|map, head, _| {
13983 (
13984 movement::previous_word_start(map, head),
13985 SelectionGoal::None,
13986 )
13987 });
13988 })
13989 }
13990
13991 pub fn select_to_previous_subword_start(
13992 &mut self,
13993 _: &SelectToPreviousSubwordStart,
13994 window: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) {
13997 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13998 self.change_selections(Default::default(), window, cx, |s| {
13999 s.move_heads_with(|map, head, _| {
14000 (
14001 movement::previous_subword_start(map, head),
14002 SelectionGoal::None,
14003 )
14004 });
14005 })
14006 }
14007
14008 pub fn delete_to_previous_word_start(
14009 &mut self,
14010 action: &DeleteToPreviousWordStart,
14011 window: &mut Window,
14012 cx: &mut Context<Self>,
14013 ) {
14014 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14015 self.transact(window, cx, |this, window, cx| {
14016 this.select_autoclose_pair(window, cx);
14017 this.change_selections(Default::default(), window, cx, |s| {
14018 s.move_with(|map, selection| {
14019 if selection.is_empty() {
14020 let mut cursor = if action.ignore_newlines {
14021 movement::previous_word_start(map, selection.head())
14022 } else {
14023 movement::previous_word_start_or_newline(map, selection.head())
14024 };
14025 cursor = movement::adjust_greedy_deletion(
14026 map,
14027 selection.head(),
14028 cursor,
14029 action.ignore_brackets,
14030 );
14031 selection.set_head(cursor, SelectionGoal::None);
14032 }
14033 });
14034 });
14035 this.insert("", window, cx);
14036 });
14037 }
14038
14039 pub fn delete_to_previous_subword_start(
14040 &mut self,
14041 _: &DeleteToPreviousSubwordStart,
14042 window: &mut Window,
14043 cx: &mut Context<Self>,
14044 ) {
14045 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14046 self.transact(window, cx, |this, window, cx| {
14047 this.select_autoclose_pair(window, cx);
14048 this.change_selections(Default::default(), window, cx, |s| {
14049 s.move_with(|map, selection| {
14050 if selection.is_empty() {
14051 let mut cursor = movement::previous_subword_start(map, selection.head());
14052 cursor =
14053 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14054 selection.set_head(cursor, SelectionGoal::None);
14055 }
14056 });
14057 });
14058 this.insert("", window, cx);
14059 });
14060 }
14061
14062 pub fn move_to_next_word_end(
14063 &mut self,
14064 _: &MoveToNextWordEnd,
14065 window: &mut Window,
14066 cx: &mut Context<Self>,
14067 ) {
14068 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14069 self.change_selections(Default::default(), window, cx, |s| {
14070 s.move_cursors_with(|map, head, _| {
14071 (movement::next_word_end(map, head), SelectionGoal::None)
14072 });
14073 })
14074 }
14075
14076 pub fn move_to_next_subword_end(
14077 &mut self,
14078 _: &MoveToNextSubwordEnd,
14079 window: &mut Window,
14080 cx: &mut Context<Self>,
14081 ) {
14082 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14083 self.change_selections(Default::default(), window, cx, |s| {
14084 s.move_cursors_with(|map, head, _| {
14085 (movement::next_subword_end(map, head), SelectionGoal::None)
14086 });
14087 })
14088 }
14089
14090 pub fn select_to_next_word_end(
14091 &mut self,
14092 _: &SelectToNextWordEnd,
14093 window: &mut Window,
14094 cx: &mut Context<Self>,
14095 ) {
14096 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14097 self.change_selections(Default::default(), window, cx, |s| {
14098 s.move_heads_with(|map, head, _| {
14099 (movement::next_word_end(map, head), SelectionGoal::None)
14100 });
14101 })
14102 }
14103
14104 pub fn select_to_next_subword_end(
14105 &mut self,
14106 _: &SelectToNextSubwordEnd,
14107 window: &mut Window,
14108 cx: &mut Context<Self>,
14109 ) {
14110 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14111 self.change_selections(Default::default(), window, cx, |s| {
14112 s.move_heads_with(|map, head, _| {
14113 (movement::next_subword_end(map, head), SelectionGoal::None)
14114 });
14115 })
14116 }
14117
14118 pub fn delete_to_next_word_end(
14119 &mut self,
14120 action: &DeleteToNextWordEnd,
14121 window: &mut Window,
14122 cx: &mut Context<Self>,
14123 ) {
14124 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14125 self.transact(window, cx, |this, window, cx| {
14126 this.change_selections(Default::default(), window, cx, |s| {
14127 s.move_with(|map, selection| {
14128 if selection.is_empty() {
14129 let mut cursor = if action.ignore_newlines {
14130 movement::next_word_end(map, selection.head())
14131 } else {
14132 movement::next_word_end_or_newline(map, selection.head())
14133 };
14134 cursor = movement::adjust_greedy_deletion(
14135 map,
14136 selection.head(),
14137 cursor,
14138 action.ignore_brackets,
14139 );
14140 selection.set_head(cursor, SelectionGoal::None);
14141 }
14142 });
14143 });
14144 this.insert("", window, cx);
14145 });
14146 }
14147
14148 pub fn delete_to_next_subword_end(
14149 &mut self,
14150 _: &DeleteToNextSubwordEnd,
14151 window: &mut Window,
14152 cx: &mut Context<Self>,
14153 ) {
14154 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14155 self.transact(window, cx, |this, window, cx| {
14156 this.change_selections(Default::default(), window, cx, |s| {
14157 s.move_with(|map, selection| {
14158 if selection.is_empty() {
14159 let mut cursor = movement::next_subword_end(map, selection.head());
14160 cursor =
14161 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14162 selection.set_head(cursor, SelectionGoal::None);
14163 }
14164 });
14165 });
14166 this.insert("", window, cx);
14167 });
14168 }
14169
14170 pub fn move_to_beginning_of_line(
14171 &mut self,
14172 action: &MoveToBeginningOfLine,
14173 window: &mut Window,
14174 cx: &mut Context<Self>,
14175 ) {
14176 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14177 self.change_selections(Default::default(), window, cx, |s| {
14178 s.move_cursors_with(|map, head, _| {
14179 (
14180 movement::indented_line_beginning(
14181 map,
14182 head,
14183 action.stop_at_soft_wraps,
14184 action.stop_at_indent,
14185 ),
14186 SelectionGoal::None,
14187 )
14188 });
14189 })
14190 }
14191
14192 pub fn select_to_beginning_of_line(
14193 &mut self,
14194 action: &SelectToBeginningOfLine,
14195 window: &mut Window,
14196 cx: &mut Context<Self>,
14197 ) {
14198 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14199 self.change_selections(Default::default(), window, cx, |s| {
14200 s.move_heads_with(|map, head, _| {
14201 (
14202 movement::indented_line_beginning(
14203 map,
14204 head,
14205 action.stop_at_soft_wraps,
14206 action.stop_at_indent,
14207 ),
14208 SelectionGoal::None,
14209 )
14210 });
14211 });
14212 }
14213
14214 pub fn delete_to_beginning_of_line(
14215 &mut self,
14216 action: &DeleteToBeginningOfLine,
14217 window: &mut Window,
14218 cx: &mut Context<Self>,
14219 ) {
14220 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14221 self.transact(window, cx, |this, window, cx| {
14222 this.change_selections(Default::default(), window, cx, |s| {
14223 s.move_with(|_, selection| {
14224 selection.reversed = true;
14225 });
14226 });
14227
14228 this.select_to_beginning_of_line(
14229 &SelectToBeginningOfLine {
14230 stop_at_soft_wraps: false,
14231 stop_at_indent: action.stop_at_indent,
14232 },
14233 window,
14234 cx,
14235 );
14236 this.backspace(&Backspace, window, cx);
14237 });
14238 }
14239
14240 pub fn move_to_end_of_line(
14241 &mut self,
14242 action: &MoveToEndOfLine,
14243 window: &mut Window,
14244 cx: &mut Context<Self>,
14245 ) {
14246 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14247 self.change_selections(Default::default(), window, cx, |s| {
14248 s.move_cursors_with(|map, head, _| {
14249 (
14250 movement::line_end(map, head, action.stop_at_soft_wraps),
14251 SelectionGoal::None,
14252 )
14253 });
14254 })
14255 }
14256
14257 pub fn select_to_end_of_line(
14258 &mut self,
14259 action: &SelectToEndOfLine,
14260 window: &mut Window,
14261 cx: &mut Context<Self>,
14262 ) {
14263 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14264 self.change_selections(Default::default(), window, cx, |s| {
14265 s.move_heads_with(|map, head, _| {
14266 (
14267 movement::line_end(map, head, action.stop_at_soft_wraps),
14268 SelectionGoal::None,
14269 )
14270 });
14271 })
14272 }
14273
14274 pub fn delete_to_end_of_line(
14275 &mut self,
14276 _: &DeleteToEndOfLine,
14277 window: &mut Window,
14278 cx: &mut Context<Self>,
14279 ) {
14280 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14281 self.transact(window, cx, |this, window, cx| {
14282 this.select_to_end_of_line(
14283 &SelectToEndOfLine {
14284 stop_at_soft_wraps: false,
14285 },
14286 window,
14287 cx,
14288 );
14289 this.delete(&Delete, window, cx);
14290 });
14291 }
14292
14293 pub fn cut_to_end_of_line(
14294 &mut self,
14295 action: &CutToEndOfLine,
14296 window: &mut Window,
14297 cx: &mut Context<Self>,
14298 ) {
14299 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14300 self.transact(window, cx, |this, window, cx| {
14301 this.select_to_end_of_line(
14302 &SelectToEndOfLine {
14303 stop_at_soft_wraps: false,
14304 },
14305 window,
14306 cx,
14307 );
14308 if !action.stop_at_newlines {
14309 this.change_selections(Default::default(), window, cx, |s| {
14310 s.move_with(|_, sel| {
14311 if sel.is_empty() {
14312 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14313 }
14314 });
14315 });
14316 }
14317 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14318 let item = this.cut_common(false, window, cx);
14319 cx.write_to_clipboard(item);
14320 });
14321 }
14322
14323 pub fn move_to_start_of_paragraph(
14324 &mut self,
14325 _: &MoveToStartOfParagraph,
14326 window: &mut Window,
14327 cx: &mut Context<Self>,
14328 ) {
14329 if matches!(self.mode, EditorMode::SingleLine) {
14330 cx.propagate();
14331 return;
14332 }
14333 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14334 self.change_selections(Default::default(), window, cx, |s| {
14335 s.move_with(|map, selection| {
14336 selection.collapse_to(
14337 movement::start_of_paragraph(map, selection.head(), 1),
14338 SelectionGoal::None,
14339 )
14340 });
14341 })
14342 }
14343
14344 pub fn move_to_end_of_paragraph(
14345 &mut self,
14346 _: &MoveToEndOfParagraph,
14347 window: &mut Window,
14348 cx: &mut Context<Self>,
14349 ) {
14350 if matches!(self.mode, EditorMode::SingleLine) {
14351 cx.propagate();
14352 return;
14353 }
14354 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14355 self.change_selections(Default::default(), window, cx, |s| {
14356 s.move_with(|map, selection| {
14357 selection.collapse_to(
14358 movement::end_of_paragraph(map, selection.head(), 1),
14359 SelectionGoal::None,
14360 )
14361 });
14362 })
14363 }
14364
14365 pub fn select_to_start_of_paragraph(
14366 &mut self,
14367 _: &SelectToStartOfParagraph,
14368 window: &mut Window,
14369 cx: &mut Context<Self>,
14370 ) {
14371 if matches!(self.mode, EditorMode::SingleLine) {
14372 cx.propagate();
14373 return;
14374 }
14375 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14376 self.change_selections(Default::default(), window, cx, |s| {
14377 s.move_heads_with(|map, head, _| {
14378 (
14379 movement::start_of_paragraph(map, head, 1),
14380 SelectionGoal::None,
14381 )
14382 });
14383 })
14384 }
14385
14386 pub fn select_to_end_of_paragraph(
14387 &mut self,
14388 _: &SelectToEndOfParagraph,
14389 window: &mut Window,
14390 cx: &mut Context<Self>,
14391 ) {
14392 if matches!(self.mode, EditorMode::SingleLine) {
14393 cx.propagate();
14394 return;
14395 }
14396 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14397 self.change_selections(Default::default(), window, cx, |s| {
14398 s.move_heads_with(|map, head, _| {
14399 (
14400 movement::end_of_paragraph(map, head, 1),
14401 SelectionGoal::None,
14402 )
14403 });
14404 })
14405 }
14406
14407 pub fn move_to_start_of_excerpt(
14408 &mut self,
14409 _: &MoveToStartOfExcerpt,
14410 window: &mut Window,
14411 cx: &mut Context<Self>,
14412 ) {
14413 if matches!(self.mode, EditorMode::SingleLine) {
14414 cx.propagate();
14415 return;
14416 }
14417 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14418 self.change_selections(Default::default(), window, cx, |s| {
14419 s.move_with(|map, selection| {
14420 selection.collapse_to(
14421 movement::start_of_excerpt(
14422 map,
14423 selection.head(),
14424 workspace::searchable::Direction::Prev,
14425 ),
14426 SelectionGoal::None,
14427 )
14428 });
14429 })
14430 }
14431
14432 pub fn move_to_start_of_next_excerpt(
14433 &mut self,
14434 _: &MoveToStartOfNextExcerpt,
14435 window: &mut Window,
14436 cx: &mut Context<Self>,
14437 ) {
14438 if matches!(self.mode, EditorMode::SingleLine) {
14439 cx.propagate();
14440 return;
14441 }
14442
14443 self.change_selections(Default::default(), window, cx, |s| {
14444 s.move_with(|map, selection| {
14445 selection.collapse_to(
14446 movement::start_of_excerpt(
14447 map,
14448 selection.head(),
14449 workspace::searchable::Direction::Next,
14450 ),
14451 SelectionGoal::None,
14452 )
14453 });
14454 })
14455 }
14456
14457 pub fn move_to_end_of_excerpt(
14458 &mut self,
14459 _: &MoveToEndOfExcerpt,
14460 window: &mut Window,
14461 cx: &mut Context<Self>,
14462 ) {
14463 if matches!(self.mode, EditorMode::SingleLine) {
14464 cx.propagate();
14465 return;
14466 }
14467 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14468 self.change_selections(Default::default(), window, cx, |s| {
14469 s.move_with(|map, selection| {
14470 selection.collapse_to(
14471 movement::end_of_excerpt(
14472 map,
14473 selection.head(),
14474 workspace::searchable::Direction::Next,
14475 ),
14476 SelectionGoal::None,
14477 )
14478 });
14479 })
14480 }
14481
14482 pub fn move_to_end_of_previous_excerpt(
14483 &mut self,
14484 _: &MoveToEndOfPreviousExcerpt,
14485 window: &mut Window,
14486 cx: &mut Context<Self>,
14487 ) {
14488 if matches!(self.mode, EditorMode::SingleLine) {
14489 cx.propagate();
14490 return;
14491 }
14492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14493 self.change_selections(Default::default(), window, cx, |s| {
14494 s.move_with(|map, selection| {
14495 selection.collapse_to(
14496 movement::end_of_excerpt(
14497 map,
14498 selection.head(),
14499 workspace::searchable::Direction::Prev,
14500 ),
14501 SelectionGoal::None,
14502 )
14503 });
14504 })
14505 }
14506
14507 pub fn select_to_start_of_excerpt(
14508 &mut self,
14509 _: &SelectToStartOfExcerpt,
14510 window: &mut Window,
14511 cx: &mut Context<Self>,
14512 ) {
14513 if matches!(self.mode, EditorMode::SingleLine) {
14514 cx.propagate();
14515 return;
14516 }
14517 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14518 self.change_selections(Default::default(), window, cx, |s| {
14519 s.move_heads_with(|map, head, _| {
14520 (
14521 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14522 SelectionGoal::None,
14523 )
14524 });
14525 })
14526 }
14527
14528 pub fn select_to_start_of_next_excerpt(
14529 &mut self,
14530 _: &SelectToStartOfNextExcerpt,
14531 window: &mut Window,
14532 cx: &mut Context<Self>,
14533 ) {
14534 if matches!(self.mode, EditorMode::SingleLine) {
14535 cx.propagate();
14536 return;
14537 }
14538 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14539 self.change_selections(Default::default(), window, cx, |s| {
14540 s.move_heads_with(|map, head, _| {
14541 (
14542 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14543 SelectionGoal::None,
14544 )
14545 });
14546 })
14547 }
14548
14549 pub fn select_to_end_of_excerpt(
14550 &mut self,
14551 _: &SelectToEndOfExcerpt,
14552 window: &mut Window,
14553 cx: &mut Context<Self>,
14554 ) {
14555 if matches!(self.mode, EditorMode::SingleLine) {
14556 cx.propagate();
14557 return;
14558 }
14559 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14560 self.change_selections(Default::default(), window, cx, |s| {
14561 s.move_heads_with(|map, head, _| {
14562 (
14563 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14564 SelectionGoal::None,
14565 )
14566 });
14567 })
14568 }
14569
14570 pub fn select_to_end_of_previous_excerpt(
14571 &mut self,
14572 _: &SelectToEndOfPreviousExcerpt,
14573 window: &mut Window,
14574 cx: &mut Context<Self>,
14575 ) {
14576 if matches!(self.mode, EditorMode::SingleLine) {
14577 cx.propagate();
14578 return;
14579 }
14580 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14581 self.change_selections(Default::default(), window, cx, |s| {
14582 s.move_heads_with(|map, head, _| {
14583 (
14584 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14585 SelectionGoal::None,
14586 )
14587 });
14588 })
14589 }
14590
14591 pub fn move_to_beginning(
14592 &mut self,
14593 _: &MoveToBeginning,
14594 window: &mut Window,
14595 cx: &mut Context<Self>,
14596 ) {
14597 if matches!(self.mode, EditorMode::SingleLine) {
14598 cx.propagate();
14599 return;
14600 }
14601 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14602 self.change_selections(Default::default(), window, cx, |s| {
14603 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14604 });
14605 }
14606
14607 pub fn select_to_beginning(
14608 &mut self,
14609 _: &SelectToBeginning,
14610 window: &mut Window,
14611 cx: &mut Context<Self>,
14612 ) {
14613 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14614 selection.set_head(Point::zero(), SelectionGoal::None);
14615 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14616 self.change_selections(Default::default(), window, cx, |s| {
14617 s.select(vec![selection]);
14618 });
14619 }
14620
14621 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14622 if matches!(self.mode, EditorMode::SingleLine) {
14623 cx.propagate();
14624 return;
14625 }
14626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14627 let cursor = self.buffer.read(cx).read(cx).len();
14628 self.change_selections(Default::default(), window, cx, |s| {
14629 s.select_ranges(vec![cursor..cursor])
14630 });
14631 }
14632
14633 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14634 self.nav_history = nav_history;
14635 }
14636
14637 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14638 self.nav_history.as_ref()
14639 }
14640
14641 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14642 self.push_to_nav_history(
14643 self.selections.newest_anchor().head(),
14644 None,
14645 false,
14646 true,
14647 cx,
14648 );
14649 }
14650
14651 fn push_to_nav_history(
14652 &mut self,
14653 cursor_anchor: Anchor,
14654 new_position: Option<Point>,
14655 is_deactivate: bool,
14656 always: bool,
14657 cx: &mut Context<Self>,
14658 ) {
14659 if let Some(nav_history) = self.nav_history.as_mut() {
14660 let buffer = self.buffer.read(cx).read(cx);
14661 let cursor_position = cursor_anchor.to_point(&buffer);
14662 let scroll_state = self.scroll_manager.anchor();
14663 let scroll_top_row = scroll_state.top_row(&buffer);
14664 drop(buffer);
14665
14666 if let Some(new_position) = new_position {
14667 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14668 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14669 return;
14670 }
14671 }
14672
14673 nav_history.push(
14674 Some(NavigationData {
14675 cursor_anchor,
14676 cursor_position,
14677 scroll_anchor: scroll_state,
14678 scroll_top_row,
14679 }),
14680 cx,
14681 );
14682 cx.emit(EditorEvent::PushedToNavHistory {
14683 anchor: cursor_anchor,
14684 is_deactivate,
14685 })
14686 }
14687 }
14688
14689 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14690 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14691 let buffer = self.buffer.read(cx).snapshot(cx);
14692 let mut selection = self
14693 .selections
14694 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14695 selection.set_head(buffer.len(), SelectionGoal::None);
14696 self.change_selections(Default::default(), window, cx, |s| {
14697 s.select(vec![selection]);
14698 });
14699 }
14700
14701 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14702 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14703 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14704 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14705 });
14706 }
14707
14708 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14709 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14710 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14711 let mut selections = self.selections.all::<Point>(&display_map);
14712 let max_point = display_map.buffer_snapshot().max_point();
14713 for selection in &mut selections {
14714 let rows = selection.spanned_rows(true, &display_map);
14715 selection.start = Point::new(rows.start.0, 0);
14716 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14717 selection.reversed = false;
14718 }
14719 self.change_selections(Default::default(), window, cx, |s| {
14720 s.select(selections);
14721 });
14722 }
14723
14724 pub fn split_selection_into_lines(
14725 &mut self,
14726 action: &SplitSelectionIntoLines,
14727 window: &mut Window,
14728 cx: &mut Context<Self>,
14729 ) {
14730 let selections = self
14731 .selections
14732 .all::<Point>(&self.display_snapshot(cx))
14733 .into_iter()
14734 .map(|selection| selection.start..selection.end)
14735 .collect::<Vec<_>>();
14736 self.unfold_ranges(&selections, true, true, cx);
14737
14738 let mut new_selection_ranges = Vec::new();
14739 {
14740 let buffer = self.buffer.read(cx).read(cx);
14741 for selection in selections {
14742 for row in selection.start.row..selection.end.row {
14743 let line_start = Point::new(row, 0);
14744 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14745
14746 if action.keep_selections {
14747 // Keep the selection range for each line
14748 let selection_start = if row == selection.start.row {
14749 selection.start
14750 } else {
14751 line_start
14752 };
14753 new_selection_ranges.push(selection_start..line_end);
14754 } else {
14755 // Collapse to cursor at end of line
14756 new_selection_ranges.push(line_end..line_end);
14757 }
14758 }
14759
14760 let is_multiline_selection = selection.start.row != selection.end.row;
14761 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14762 // so this action feels more ergonomic when paired with other selection operations
14763 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14764 if !should_skip_last {
14765 if action.keep_selections {
14766 if is_multiline_selection {
14767 let line_start = Point::new(selection.end.row, 0);
14768 new_selection_ranges.push(line_start..selection.end);
14769 } else {
14770 new_selection_ranges.push(selection.start..selection.end);
14771 }
14772 } else {
14773 new_selection_ranges.push(selection.end..selection.end);
14774 }
14775 }
14776 }
14777 }
14778 self.change_selections(Default::default(), window, cx, |s| {
14779 s.select_ranges(new_selection_ranges);
14780 });
14781 }
14782
14783 pub fn add_selection_above(
14784 &mut self,
14785 action: &AddSelectionAbove,
14786 window: &mut Window,
14787 cx: &mut Context<Self>,
14788 ) {
14789 self.add_selection(true, action.skip_soft_wrap, window, cx);
14790 }
14791
14792 pub fn add_selection_below(
14793 &mut self,
14794 action: &AddSelectionBelow,
14795 window: &mut Window,
14796 cx: &mut Context<Self>,
14797 ) {
14798 self.add_selection(false, action.skip_soft_wrap, window, cx);
14799 }
14800
14801 fn add_selection(
14802 &mut self,
14803 above: bool,
14804 skip_soft_wrap: bool,
14805 window: &mut Window,
14806 cx: &mut Context<Self>,
14807 ) {
14808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14809
14810 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14811 let all_selections = self.selections.all::<Point>(&display_map);
14812 let text_layout_details = self.text_layout_details(window);
14813
14814 let (mut columnar_selections, new_selections_to_columnarize) = {
14815 if let Some(state) = self.add_selections_state.as_ref() {
14816 let columnar_selection_ids: HashSet<_> = state
14817 .groups
14818 .iter()
14819 .flat_map(|group| group.stack.iter())
14820 .copied()
14821 .collect();
14822
14823 all_selections
14824 .into_iter()
14825 .partition(|s| columnar_selection_ids.contains(&s.id))
14826 } else {
14827 (Vec::new(), all_selections)
14828 }
14829 };
14830
14831 let mut state = self
14832 .add_selections_state
14833 .take()
14834 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14835
14836 for selection in new_selections_to_columnarize {
14837 let range = selection.display_range(&display_map).sorted();
14838 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14839 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14840 let positions = start_x.min(end_x)..start_x.max(end_x);
14841 let mut stack = Vec::new();
14842 for row in range.start.row().0..=range.end.row().0 {
14843 if let Some(selection) = self.selections.build_columnar_selection(
14844 &display_map,
14845 DisplayRow(row),
14846 &positions,
14847 selection.reversed,
14848 &text_layout_details,
14849 ) {
14850 stack.push(selection.id);
14851 columnar_selections.push(selection);
14852 }
14853 }
14854 if !stack.is_empty() {
14855 if above {
14856 stack.reverse();
14857 }
14858 state.groups.push(AddSelectionsGroup { above, stack });
14859 }
14860 }
14861
14862 let mut final_selections = Vec::new();
14863 let end_row = if above {
14864 DisplayRow(0)
14865 } else {
14866 display_map.max_point().row()
14867 };
14868
14869 let mut last_added_item_per_group = HashMap::default();
14870 for group in state.groups.iter_mut() {
14871 if let Some(last_id) = group.stack.last() {
14872 last_added_item_per_group.insert(*last_id, group);
14873 }
14874 }
14875
14876 for selection in columnar_selections {
14877 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14878 if above == group.above {
14879 let range = selection.display_range(&display_map).sorted();
14880 debug_assert_eq!(range.start.row(), range.end.row());
14881 let mut row = range.start.row();
14882 let positions =
14883 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14884 Pixels::from(start)..Pixels::from(end)
14885 } else {
14886 let start_x =
14887 display_map.x_for_display_point(range.start, &text_layout_details);
14888 let end_x =
14889 display_map.x_for_display_point(range.end, &text_layout_details);
14890 start_x.min(end_x)..start_x.max(end_x)
14891 };
14892
14893 let mut maybe_new_selection = None;
14894 let direction = if above { -1 } else { 1 };
14895
14896 while row != end_row {
14897 if skip_soft_wrap {
14898 row = display_map
14899 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14900 .row();
14901 } else if above {
14902 row.0 -= 1;
14903 } else {
14904 row.0 += 1;
14905 }
14906
14907 if let Some(new_selection) = self.selections.build_columnar_selection(
14908 &display_map,
14909 row,
14910 &positions,
14911 selection.reversed,
14912 &text_layout_details,
14913 ) {
14914 maybe_new_selection = Some(new_selection);
14915 break;
14916 }
14917 }
14918
14919 if let Some(new_selection) = maybe_new_selection {
14920 group.stack.push(new_selection.id);
14921 if above {
14922 final_selections.push(new_selection);
14923 final_selections.push(selection);
14924 } else {
14925 final_selections.push(selection);
14926 final_selections.push(new_selection);
14927 }
14928 } else {
14929 final_selections.push(selection);
14930 }
14931 } else {
14932 group.stack.pop();
14933 }
14934 } else {
14935 final_selections.push(selection);
14936 }
14937 }
14938
14939 self.change_selections(Default::default(), window, cx, |s| {
14940 s.select(final_selections);
14941 });
14942
14943 let final_selection_ids: HashSet<_> = self
14944 .selections
14945 .all::<Point>(&display_map)
14946 .iter()
14947 .map(|s| s.id)
14948 .collect();
14949 state.groups.retain_mut(|group| {
14950 // selections might get merged above so we remove invalid items from stacks
14951 group.stack.retain(|id| final_selection_ids.contains(id));
14952
14953 // single selection in stack can be treated as initial state
14954 group.stack.len() > 1
14955 });
14956
14957 if !state.groups.is_empty() {
14958 self.add_selections_state = Some(state);
14959 }
14960 }
14961
14962 pub fn insert_snippet_at_selections(
14963 &mut self,
14964 action: &InsertSnippet,
14965 window: &mut Window,
14966 cx: &mut Context<Self>,
14967 ) {
14968 self.try_insert_snippet_at_selections(action, window, cx)
14969 .log_err();
14970 }
14971
14972 fn try_insert_snippet_at_selections(
14973 &mut self,
14974 action: &InsertSnippet,
14975 window: &mut Window,
14976 cx: &mut Context<Self>,
14977 ) -> Result<()> {
14978 let insertion_ranges = self
14979 .selections
14980 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
14981 .into_iter()
14982 .map(|selection| selection.range())
14983 .collect_vec();
14984
14985 let snippet = if let Some(snippet_body) = &action.snippet {
14986 if action.language.is_none() && action.name.is_none() {
14987 Snippet::parse(snippet_body)?
14988 } else {
14989 bail!("`snippet` is mutually exclusive with `language` and `name`")
14990 }
14991 } else if let Some(name) = &action.name {
14992 let project = self.project().context("no project")?;
14993 let snippet_store = project.read(cx).snippets().read(cx);
14994 let snippet = snippet_store
14995 .snippets_for(action.language.clone(), cx)
14996 .into_iter()
14997 .find(|snippet| snippet.name == *name)
14998 .context("snippet not found")?;
14999 Snippet::parse(&snippet.body)?
15000 } else {
15001 // todo(andrew): open modal to select snippet
15002 bail!("`name` or `snippet` is required")
15003 };
15004
15005 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15006 }
15007
15008 fn select_match_ranges(
15009 &mut self,
15010 range: Range<MultiBufferOffset>,
15011 reversed: bool,
15012 replace_newest: bool,
15013 auto_scroll: Option<Autoscroll>,
15014 window: &mut Window,
15015 cx: &mut Context<Editor>,
15016 ) {
15017 self.unfold_ranges(
15018 std::slice::from_ref(&range),
15019 false,
15020 auto_scroll.is_some(),
15021 cx,
15022 );
15023 let effects = if let Some(scroll) = auto_scroll {
15024 SelectionEffects::scroll(scroll)
15025 } else {
15026 SelectionEffects::no_scroll()
15027 };
15028 self.change_selections(effects, window, cx, |s| {
15029 if replace_newest {
15030 s.delete(s.newest_anchor().id);
15031 }
15032 if reversed {
15033 s.insert_range(range.end..range.start);
15034 } else {
15035 s.insert_range(range);
15036 }
15037 });
15038 }
15039
15040 pub fn select_next_match_internal(
15041 &mut self,
15042 display_map: &DisplaySnapshot,
15043 replace_newest: bool,
15044 autoscroll: Option<Autoscroll>,
15045 window: &mut Window,
15046 cx: &mut Context<Self>,
15047 ) -> Result<()> {
15048 let buffer = display_map.buffer_snapshot();
15049 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15050 if let Some(mut select_next_state) = self.select_next_state.take() {
15051 let query = &select_next_state.query;
15052 if !select_next_state.done {
15053 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15054 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15055 let mut next_selected_range = None;
15056
15057 let bytes_after_last_selection =
15058 buffer.bytes_in_range(last_selection.end..buffer.len());
15059 let bytes_before_first_selection =
15060 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15061 let query_matches = query
15062 .stream_find_iter(bytes_after_last_selection)
15063 .map(|result| (last_selection.end, result))
15064 .chain(
15065 query
15066 .stream_find_iter(bytes_before_first_selection)
15067 .map(|result| (MultiBufferOffset(0), result)),
15068 );
15069
15070 for (start_offset, query_match) in query_matches {
15071 let query_match = query_match.unwrap(); // can only fail due to I/O
15072 let offset_range =
15073 start_offset + query_match.start()..start_offset + query_match.end();
15074
15075 if !select_next_state.wordwise
15076 || (!buffer.is_inside_word(offset_range.start, None)
15077 && !buffer.is_inside_word(offset_range.end, None))
15078 {
15079 let idx = selections
15080 .partition_point(|selection| selection.end <= offset_range.start);
15081 let overlaps = selections
15082 .get(idx)
15083 .map_or(false, |selection| selection.start < offset_range.end);
15084
15085 if !overlaps {
15086 next_selected_range = Some(offset_range);
15087 break;
15088 }
15089 }
15090 }
15091
15092 if let Some(next_selected_range) = next_selected_range {
15093 self.select_match_ranges(
15094 next_selected_range,
15095 last_selection.reversed,
15096 replace_newest,
15097 autoscroll,
15098 window,
15099 cx,
15100 );
15101 } else {
15102 select_next_state.done = true;
15103 }
15104 }
15105
15106 self.select_next_state = Some(select_next_state);
15107 } else {
15108 let mut only_carets = true;
15109 let mut same_text_selected = true;
15110 let mut selected_text = None;
15111
15112 let mut selections_iter = selections.iter().peekable();
15113 while let Some(selection) = selections_iter.next() {
15114 if selection.start != selection.end {
15115 only_carets = false;
15116 }
15117
15118 if same_text_selected {
15119 if selected_text.is_none() {
15120 selected_text =
15121 Some(buffer.text_for_range(selection.range()).collect::<String>());
15122 }
15123
15124 if let Some(next_selection) = selections_iter.peek() {
15125 if next_selection.len() == selection.len() {
15126 let next_selected_text = buffer
15127 .text_for_range(next_selection.range())
15128 .collect::<String>();
15129 if Some(next_selected_text) != selected_text {
15130 same_text_selected = false;
15131 selected_text = None;
15132 }
15133 } else {
15134 same_text_selected = false;
15135 selected_text = None;
15136 }
15137 }
15138 }
15139 }
15140
15141 if only_carets {
15142 for selection in &mut selections {
15143 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15144 selection.start = word_range.start;
15145 selection.end = word_range.end;
15146 selection.goal = SelectionGoal::None;
15147 selection.reversed = false;
15148 self.select_match_ranges(
15149 selection.start..selection.end,
15150 selection.reversed,
15151 replace_newest,
15152 autoscroll,
15153 window,
15154 cx,
15155 );
15156 }
15157
15158 if selections.len() == 1 {
15159 let selection = selections
15160 .last()
15161 .expect("ensured that there's only one selection");
15162 let query = buffer
15163 .text_for_range(selection.start..selection.end)
15164 .collect::<String>();
15165 let is_empty = query.is_empty();
15166 let select_state = SelectNextState {
15167 query: self.build_query(&[query], cx)?,
15168 wordwise: true,
15169 done: is_empty,
15170 };
15171 self.select_next_state = Some(select_state);
15172 } else {
15173 self.select_next_state = None;
15174 }
15175 } else if let Some(selected_text) = selected_text {
15176 self.select_next_state = Some(SelectNextState {
15177 query: self.build_query(&[selected_text], cx)?,
15178 wordwise: false,
15179 done: false,
15180 });
15181 self.select_next_match_internal(
15182 display_map,
15183 replace_newest,
15184 autoscroll,
15185 window,
15186 cx,
15187 )?;
15188 }
15189 }
15190 Ok(())
15191 }
15192
15193 pub fn select_all_matches(
15194 &mut self,
15195 _action: &SelectAllMatches,
15196 window: &mut Window,
15197 cx: &mut Context<Self>,
15198 ) -> Result<()> {
15199 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15200
15201 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15202
15203 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15204 let Some(select_next_state) = self.select_next_state.as_mut() else {
15205 return Ok(());
15206 };
15207 if select_next_state.done {
15208 return Ok(());
15209 }
15210
15211 let mut new_selections = Vec::new();
15212
15213 let reversed = self
15214 .selections
15215 .oldest::<MultiBufferOffset>(&display_map)
15216 .reversed;
15217 let buffer = display_map.buffer_snapshot();
15218 let query_matches = select_next_state
15219 .query
15220 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15221
15222 for query_match in query_matches.into_iter() {
15223 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15224 let offset_range = if reversed {
15225 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15226 } else {
15227 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15228 };
15229
15230 if !select_next_state.wordwise
15231 || (!buffer.is_inside_word(offset_range.start, None)
15232 && !buffer.is_inside_word(offset_range.end, None))
15233 {
15234 new_selections.push(offset_range.start..offset_range.end);
15235 }
15236 }
15237
15238 select_next_state.done = true;
15239
15240 if new_selections.is_empty() {
15241 log::error!("bug: new_selections is empty in select_all_matches");
15242 return Ok(());
15243 }
15244
15245 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15246 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15247 selections.select_ranges(new_selections)
15248 });
15249
15250 Ok(())
15251 }
15252
15253 pub fn select_next(
15254 &mut self,
15255 action: &SelectNext,
15256 window: &mut Window,
15257 cx: &mut Context<Self>,
15258 ) -> Result<()> {
15259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15260 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15261 self.select_next_match_internal(
15262 &display_map,
15263 action.replace_newest,
15264 Some(Autoscroll::newest()),
15265 window,
15266 cx,
15267 )?;
15268 Ok(())
15269 }
15270
15271 pub fn select_previous(
15272 &mut self,
15273 action: &SelectPrevious,
15274 window: &mut Window,
15275 cx: &mut Context<Self>,
15276 ) -> Result<()> {
15277 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15278 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15279 let buffer = display_map.buffer_snapshot();
15280 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15281 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15282 let query = &select_prev_state.query;
15283 if !select_prev_state.done {
15284 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15285 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15286 let mut next_selected_range = None;
15287 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15288 let bytes_before_last_selection =
15289 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15290 let bytes_after_first_selection =
15291 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15292 let query_matches = query
15293 .stream_find_iter(bytes_before_last_selection)
15294 .map(|result| (last_selection.start, result))
15295 .chain(
15296 query
15297 .stream_find_iter(bytes_after_first_selection)
15298 .map(|result| (buffer.len(), result)),
15299 );
15300 for (end_offset, query_match) in query_matches {
15301 let query_match = query_match.unwrap(); // can only fail due to I/O
15302 let offset_range =
15303 end_offset - query_match.end()..end_offset - query_match.start();
15304
15305 if !select_prev_state.wordwise
15306 || (!buffer.is_inside_word(offset_range.start, None)
15307 && !buffer.is_inside_word(offset_range.end, None))
15308 {
15309 next_selected_range = Some(offset_range);
15310 break;
15311 }
15312 }
15313
15314 if let Some(next_selected_range) = next_selected_range {
15315 self.select_match_ranges(
15316 next_selected_range,
15317 last_selection.reversed,
15318 action.replace_newest,
15319 Some(Autoscroll::newest()),
15320 window,
15321 cx,
15322 );
15323 } else {
15324 select_prev_state.done = true;
15325 }
15326 }
15327
15328 self.select_prev_state = Some(select_prev_state);
15329 } else {
15330 let mut only_carets = true;
15331 let mut same_text_selected = true;
15332 let mut selected_text = None;
15333
15334 let mut selections_iter = selections.iter().peekable();
15335 while let Some(selection) = selections_iter.next() {
15336 if selection.start != selection.end {
15337 only_carets = false;
15338 }
15339
15340 if same_text_selected {
15341 if selected_text.is_none() {
15342 selected_text =
15343 Some(buffer.text_for_range(selection.range()).collect::<String>());
15344 }
15345
15346 if let Some(next_selection) = selections_iter.peek() {
15347 if next_selection.len() == selection.len() {
15348 let next_selected_text = buffer
15349 .text_for_range(next_selection.range())
15350 .collect::<String>();
15351 if Some(next_selected_text) != selected_text {
15352 same_text_selected = false;
15353 selected_text = None;
15354 }
15355 } else {
15356 same_text_selected = false;
15357 selected_text = None;
15358 }
15359 }
15360 }
15361 }
15362
15363 if only_carets {
15364 for selection in &mut selections {
15365 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15366 selection.start = word_range.start;
15367 selection.end = word_range.end;
15368 selection.goal = SelectionGoal::None;
15369 selection.reversed = false;
15370 self.select_match_ranges(
15371 selection.start..selection.end,
15372 selection.reversed,
15373 action.replace_newest,
15374 Some(Autoscroll::newest()),
15375 window,
15376 cx,
15377 );
15378 }
15379 if selections.len() == 1 {
15380 let selection = selections
15381 .last()
15382 .expect("ensured that there's only one selection");
15383 let query = buffer
15384 .text_for_range(selection.start..selection.end)
15385 .collect::<String>();
15386 let is_empty = query.is_empty();
15387 let select_state = SelectNextState {
15388 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15389 wordwise: true,
15390 done: is_empty,
15391 };
15392 self.select_prev_state = Some(select_state);
15393 } else {
15394 self.select_prev_state = None;
15395 }
15396 } else if let Some(selected_text) = selected_text {
15397 self.select_prev_state = Some(SelectNextState {
15398 query: self
15399 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15400 wordwise: false,
15401 done: false,
15402 });
15403 self.select_previous(action, window, cx)?;
15404 }
15405 }
15406 Ok(())
15407 }
15408
15409 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15410 /// setting the case sensitivity based on the global
15411 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15412 /// editor's settings.
15413 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15414 where
15415 I: IntoIterator<Item = P>,
15416 P: AsRef<[u8]>,
15417 {
15418 let case_sensitive = self
15419 .select_next_is_case_sensitive
15420 .unwrap_or_else(|| EditorSettings::get_global(cx).search.case_sensitive);
15421
15422 let mut builder = AhoCorasickBuilder::new();
15423 builder.ascii_case_insensitive(!case_sensitive);
15424 builder.build(patterns)
15425 }
15426
15427 pub fn find_next_match(
15428 &mut self,
15429 _: &FindNextMatch,
15430 window: &mut Window,
15431 cx: &mut Context<Self>,
15432 ) -> Result<()> {
15433 let selections = self.selections.disjoint_anchors_arc();
15434 match selections.first() {
15435 Some(first) if selections.len() >= 2 => {
15436 self.change_selections(Default::default(), window, cx, |s| {
15437 s.select_ranges([first.range()]);
15438 });
15439 }
15440 _ => self.select_next(
15441 &SelectNext {
15442 replace_newest: true,
15443 },
15444 window,
15445 cx,
15446 )?,
15447 }
15448 Ok(())
15449 }
15450
15451 pub fn find_previous_match(
15452 &mut self,
15453 _: &FindPreviousMatch,
15454 window: &mut Window,
15455 cx: &mut Context<Self>,
15456 ) -> Result<()> {
15457 let selections = self.selections.disjoint_anchors_arc();
15458 match selections.last() {
15459 Some(last) if selections.len() >= 2 => {
15460 self.change_selections(Default::default(), window, cx, |s| {
15461 s.select_ranges([last.range()]);
15462 });
15463 }
15464 _ => self.select_previous(
15465 &SelectPrevious {
15466 replace_newest: true,
15467 },
15468 window,
15469 cx,
15470 )?,
15471 }
15472 Ok(())
15473 }
15474
15475 pub fn toggle_comments(
15476 &mut self,
15477 action: &ToggleComments,
15478 window: &mut Window,
15479 cx: &mut Context<Self>,
15480 ) {
15481 if self.read_only(cx) {
15482 return;
15483 }
15484 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15485 let text_layout_details = &self.text_layout_details(window);
15486 self.transact(window, cx, |this, window, cx| {
15487 let mut selections = this
15488 .selections
15489 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15490 let mut edits = Vec::new();
15491 let mut selection_edit_ranges = Vec::new();
15492 let mut last_toggled_row = None;
15493 let snapshot = this.buffer.read(cx).read(cx);
15494 let empty_str: Arc<str> = Arc::default();
15495 let mut suffixes_inserted = Vec::new();
15496 let ignore_indent = action.ignore_indent;
15497
15498 fn comment_prefix_range(
15499 snapshot: &MultiBufferSnapshot,
15500 row: MultiBufferRow,
15501 comment_prefix: &str,
15502 comment_prefix_whitespace: &str,
15503 ignore_indent: bool,
15504 ) -> Range<Point> {
15505 let indent_size = if ignore_indent {
15506 0
15507 } else {
15508 snapshot.indent_size_for_line(row).len
15509 };
15510
15511 let start = Point::new(row.0, indent_size);
15512
15513 let mut line_bytes = snapshot
15514 .bytes_in_range(start..snapshot.max_point())
15515 .flatten()
15516 .copied();
15517
15518 // If this line currently begins with the line comment prefix, then record
15519 // the range containing the prefix.
15520 if line_bytes
15521 .by_ref()
15522 .take(comment_prefix.len())
15523 .eq(comment_prefix.bytes())
15524 {
15525 // Include any whitespace that matches the comment prefix.
15526 let matching_whitespace_len = line_bytes
15527 .zip(comment_prefix_whitespace.bytes())
15528 .take_while(|(a, b)| a == b)
15529 .count() as u32;
15530 let end = Point::new(
15531 start.row,
15532 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15533 );
15534 start..end
15535 } else {
15536 start..start
15537 }
15538 }
15539
15540 fn comment_suffix_range(
15541 snapshot: &MultiBufferSnapshot,
15542 row: MultiBufferRow,
15543 comment_suffix: &str,
15544 comment_suffix_has_leading_space: bool,
15545 ) -> Range<Point> {
15546 let end = Point::new(row.0, snapshot.line_len(row));
15547 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15548
15549 let mut line_end_bytes = snapshot
15550 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15551 .flatten()
15552 .copied();
15553
15554 let leading_space_len = if suffix_start_column > 0
15555 && line_end_bytes.next() == Some(b' ')
15556 && comment_suffix_has_leading_space
15557 {
15558 1
15559 } else {
15560 0
15561 };
15562
15563 // If this line currently begins with the line comment prefix, then record
15564 // the range containing the prefix.
15565 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15566 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15567 start..end
15568 } else {
15569 end..end
15570 }
15571 }
15572
15573 // TODO: Handle selections that cross excerpts
15574 for selection in &mut selections {
15575 let start_column = snapshot
15576 .indent_size_for_line(MultiBufferRow(selection.start.row))
15577 .len;
15578 let language = if let Some(language) =
15579 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15580 {
15581 language
15582 } else {
15583 continue;
15584 };
15585
15586 selection_edit_ranges.clear();
15587
15588 // If multiple selections contain a given row, avoid processing that
15589 // row more than once.
15590 let mut start_row = MultiBufferRow(selection.start.row);
15591 if last_toggled_row == Some(start_row) {
15592 start_row = start_row.next_row();
15593 }
15594 let end_row =
15595 if selection.end.row > selection.start.row && selection.end.column == 0 {
15596 MultiBufferRow(selection.end.row - 1)
15597 } else {
15598 MultiBufferRow(selection.end.row)
15599 };
15600 last_toggled_row = Some(end_row);
15601
15602 if start_row > end_row {
15603 continue;
15604 }
15605
15606 // If the language has line comments, toggle those.
15607 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15608
15609 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15610 if ignore_indent {
15611 full_comment_prefixes = full_comment_prefixes
15612 .into_iter()
15613 .map(|s| Arc::from(s.trim_end()))
15614 .collect();
15615 }
15616
15617 if !full_comment_prefixes.is_empty() {
15618 let first_prefix = full_comment_prefixes
15619 .first()
15620 .expect("prefixes is non-empty");
15621 let prefix_trimmed_lengths = full_comment_prefixes
15622 .iter()
15623 .map(|p| p.trim_end_matches(' ').len())
15624 .collect::<SmallVec<[usize; 4]>>();
15625
15626 let mut all_selection_lines_are_comments = true;
15627
15628 for row in start_row.0..=end_row.0 {
15629 let row = MultiBufferRow(row);
15630 if start_row < end_row && snapshot.is_line_blank(row) {
15631 continue;
15632 }
15633
15634 let prefix_range = full_comment_prefixes
15635 .iter()
15636 .zip(prefix_trimmed_lengths.iter().copied())
15637 .map(|(prefix, trimmed_prefix_len)| {
15638 comment_prefix_range(
15639 snapshot.deref(),
15640 row,
15641 &prefix[..trimmed_prefix_len],
15642 &prefix[trimmed_prefix_len..],
15643 ignore_indent,
15644 )
15645 })
15646 .max_by_key(|range| range.end.column - range.start.column)
15647 .expect("prefixes is non-empty");
15648
15649 if prefix_range.is_empty() {
15650 all_selection_lines_are_comments = false;
15651 }
15652
15653 selection_edit_ranges.push(prefix_range);
15654 }
15655
15656 if all_selection_lines_are_comments {
15657 edits.extend(
15658 selection_edit_ranges
15659 .iter()
15660 .cloned()
15661 .map(|range| (range, empty_str.clone())),
15662 );
15663 } else {
15664 let min_column = selection_edit_ranges
15665 .iter()
15666 .map(|range| range.start.column)
15667 .min()
15668 .unwrap_or(0);
15669 edits.extend(selection_edit_ranges.iter().map(|range| {
15670 let position = Point::new(range.start.row, min_column);
15671 (position..position, first_prefix.clone())
15672 }));
15673 }
15674 } else if let Some(BlockCommentConfig {
15675 start: full_comment_prefix,
15676 end: comment_suffix,
15677 ..
15678 }) = language.block_comment()
15679 {
15680 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15681 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15682 let prefix_range = comment_prefix_range(
15683 snapshot.deref(),
15684 start_row,
15685 comment_prefix,
15686 comment_prefix_whitespace,
15687 ignore_indent,
15688 );
15689 let suffix_range = comment_suffix_range(
15690 snapshot.deref(),
15691 end_row,
15692 comment_suffix.trim_start_matches(' '),
15693 comment_suffix.starts_with(' '),
15694 );
15695
15696 if prefix_range.is_empty() || suffix_range.is_empty() {
15697 edits.push((
15698 prefix_range.start..prefix_range.start,
15699 full_comment_prefix.clone(),
15700 ));
15701 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15702 suffixes_inserted.push((end_row, comment_suffix.len()));
15703 } else {
15704 edits.push((prefix_range, empty_str.clone()));
15705 edits.push((suffix_range, empty_str.clone()));
15706 }
15707 } else {
15708 continue;
15709 }
15710 }
15711
15712 drop(snapshot);
15713 this.buffer.update(cx, |buffer, cx| {
15714 buffer.edit(edits, None, cx);
15715 });
15716
15717 // Adjust selections so that they end before any comment suffixes that
15718 // were inserted.
15719 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15720 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15721 let snapshot = this.buffer.read(cx).read(cx);
15722 for selection in &mut selections {
15723 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15724 match row.cmp(&MultiBufferRow(selection.end.row)) {
15725 Ordering::Less => {
15726 suffixes_inserted.next();
15727 continue;
15728 }
15729 Ordering::Greater => break,
15730 Ordering::Equal => {
15731 if selection.end.column == snapshot.line_len(row) {
15732 if selection.is_empty() {
15733 selection.start.column -= suffix_len as u32;
15734 }
15735 selection.end.column -= suffix_len as u32;
15736 }
15737 break;
15738 }
15739 }
15740 }
15741 }
15742
15743 drop(snapshot);
15744 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15745
15746 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15747 let selections_on_single_row = selections.windows(2).all(|selections| {
15748 selections[0].start.row == selections[1].start.row
15749 && selections[0].end.row == selections[1].end.row
15750 && selections[0].start.row == selections[0].end.row
15751 });
15752 let selections_selecting = selections
15753 .iter()
15754 .any(|selection| selection.start != selection.end);
15755 let advance_downwards = action.advance_downwards
15756 && selections_on_single_row
15757 && !selections_selecting
15758 && !matches!(this.mode, EditorMode::SingleLine);
15759
15760 if advance_downwards {
15761 let snapshot = this.buffer.read(cx).snapshot(cx);
15762
15763 this.change_selections(Default::default(), window, cx, |s| {
15764 s.move_cursors_with(|display_snapshot, display_point, _| {
15765 let mut point = display_point.to_point(display_snapshot);
15766 point.row += 1;
15767 point = snapshot.clip_point(point, Bias::Left);
15768 let display_point = point.to_display_point(display_snapshot);
15769 let goal = SelectionGoal::HorizontalPosition(
15770 display_snapshot
15771 .x_for_display_point(display_point, text_layout_details)
15772 .into(),
15773 );
15774 (display_point, goal)
15775 })
15776 });
15777 }
15778 });
15779 }
15780
15781 pub fn select_enclosing_symbol(
15782 &mut self,
15783 _: &SelectEnclosingSymbol,
15784 window: &mut Window,
15785 cx: &mut Context<Self>,
15786 ) {
15787 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15788
15789 let buffer = self.buffer.read(cx).snapshot(cx);
15790 let old_selections = self
15791 .selections
15792 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15793 .into_boxed_slice();
15794
15795 fn update_selection(
15796 selection: &Selection<MultiBufferOffset>,
15797 buffer_snap: &MultiBufferSnapshot,
15798 ) -> Option<Selection<MultiBufferOffset>> {
15799 let cursor = selection.head();
15800 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15801 for symbol in symbols.iter().rev() {
15802 let start = symbol.range.start.to_offset(buffer_snap);
15803 let end = symbol.range.end.to_offset(buffer_snap);
15804 let new_range = start..end;
15805 if start < selection.start || end > selection.end {
15806 return Some(Selection {
15807 id: selection.id,
15808 start: new_range.start,
15809 end: new_range.end,
15810 goal: SelectionGoal::None,
15811 reversed: selection.reversed,
15812 });
15813 }
15814 }
15815 None
15816 }
15817
15818 let mut selected_larger_symbol = false;
15819 let new_selections = old_selections
15820 .iter()
15821 .map(|selection| match update_selection(selection, &buffer) {
15822 Some(new_selection) => {
15823 if new_selection.range() != selection.range() {
15824 selected_larger_symbol = true;
15825 }
15826 new_selection
15827 }
15828 None => selection.clone(),
15829 })
15830 .collect::<Vec<_>>();
15831
15832 if selected_larger_symbol {
15833 self.change_selections(Default::default(), window, cx, |s| {
15834 s.select(new_selections);
15835 });
15836 }
15837 }
15838
15839 pub fn select_larger_syntax_node(
15840 &mut self,
15841 _: &SelectLargerSyntaxNode,
15842 window: &mut Window,
15843 cx: &mut Context<Self>,
15844 ) {
15845 let Some(visible_row_count) = self.visible_row_count() else {
15846 return;
15847 };
15848 let old_selections: Box<[_]> = self
15849 .selections
15850 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15851 .into();
15852 if old_selections.is_empty() {
15853 return;
15854 }
15855
15856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15857
15858 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15859 let buffer = self.buffer.read(cx).snapshot(cx);
15860
15861 let mut selected_larger_node = false;
15862 let mut new_selections = old_selections
15863 .iter()
15864 .map(|selection| {
15865 let old_range = selection.start..selection.end;
15866
15867 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15868 // manually select word at selection
15869 if ["string_content", "inline"].contains(&node.kind()) {
15870 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15871 // ignore if word is already selected
15872 if !word_range.is_empty() && old_range != word_range {
15873 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15874 // only select word if start and end point belongs to same word
15875 if word_range == last_word_range {
15876 selected_larger_node = true;
15877 return Selection {
15878 id: selection.id,
15879 start: word_range.start,
15880 end: word_range.end,
15881 goal: SelectionGoal::None,
15882 reversed: selection.reversed,
15883 };
15884 }
15885 }
15886 }
15887 }
15888
15889 let mut new_range = old_range.clone();
15890 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15891 new_range = range;
15892 if !node.is_named() {
15893 continue;
15894 }
15895 if !display_map.intersects_fold(new_range.start)
15896 && !display_map.intersects_fold(new_range.end)
15897 {
15898 break;
15899 }
15900 }
15901
15902 selected_larger_node |= new_range != old_range;
15903 Selection {
15904 id: selection.id,
15905 start: new_range.start,
15906 end: new_range.end,
15907 goal: SelectionGoal::None,
15908 reversed: selection.reversed,
15909 }
15910 })
15911 .collect::<Vec<_>>();
15912
15913 if !selected_larger_node {
15914 return; // don't put this call in the history
15915 }
15916
15917 // scroll based on transformation done to the last selection created by the user
15918 let (last_old, last_new) = old_selections
15919 .last()
15920 .zip(new_selections.last().cloned())
15921 .expect("old_selections isn't empty");
15922
15923 // revert selection
15924 let is_selection_reversed = {
15925 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15926 new_selections.last_mut().expect("checked above").reversed =
15927 should_newest_selection_be_reversed;
15928 should_newest_selection_be_reversed
15929 };
15930
15931 if selected_larger_node {
15932 self.select_syntax_node_history.disable_clearing = true;
15933 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15934 s.select(new_selections.clone());
15935 });
15936 self.select_syntax_node_history.disable_clearing = false;
15937 }
15938
15939 let start_row = last_new.start.to_display_point(&display_map).row().0;
15940 let end_row = last_new.end.to_display_point(&display_map).row().0;
15941 let selection_height = end_row - start_row + 1;
15942 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15943
15944 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15945 let scroll_behavior = if fits_on_the_screen {
15946 self.request_autoscroll(Autoscroll::fit(), cx);
15947 SelectSyntaxNodeScrollBehavior::FitSelection
15948 } else if is_selection_reversed {
15949 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15950 SelectSyntaxNodeScrollBehavior::CursorTop
15951 } else {
15952 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15953 SelectSyntaxNodeScrollBehavior::CursorBottom
15954 };
15955
15956 self.select_syntax_node_history.push((
15957 old_selections,
15958 scroll_behavior,
15959 is_selection_reversed,
15960 ));
15961 }
15962
15963 pub fn select_smaller_syntax_node(
15964 &mut self,
15965 _: &SelectSmallerSyntaxNode,
15966 window: &mut Window,
15967 cx: &mut Context<Self>,
15968 ) {
15969 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15970
15971 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15972 self.select_syntax_node_history.pop()
15973 {
15974 if let Some(selection) = selections.last_mut() {
15975 selection.reversed = is_selection_reversed;
15976 }
15977
15978 self.select_syntax_node_history.disable_clearing = true;
15979 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15980 s.select(selections.to_vec());
15981 });
15982 self.select_syntax_node_history.disable_clearing = false;
15983
15984 match scroll_behavior {
15985 SelectSyntaxNodeScrollBehavior::CursorTop => {
15986 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15987 }
15988 SelectSyntaxNodeScrollBehavior::FitSelection => {
15989 self.request_autoscroll(Autoscroll::fit(), cx);
15990 }
15991 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15992 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15993 }
15994 }
15995 }
15996 }
15997
15998 pub fn unwrap_syntax_node(
15999 &mut self,
16000 _: &UnwrapSyntaxNode,
16001 window: &mut Window,
16002 cx: &mut Context<Self>,
16003 ) {
16004 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16005
16006 let buffer = self.buffer.read(cx).snapshot(cx);
16007 let selections = self
16008 .selections
16009 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16010 .into_iter()
16011 // subtracting the offset requires sorting
16012 .sorted_by_key(|i| i.start);
16013
16014 let full_edits = selections
16015 .into_iter()
16016 .filter_map(|selection| {
16017 let child = if selection.is_empty()
16018 && let Some((_, ancestor_range)) =
16019 buffer.syntax_ancestor(selection.start..selection.end)
16020 {
16021 ancestor_range
16022 } else {
16023 selection.range()
16024 };
16025
16026 let mut parent = child.clone();
16027 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16028 parent = ancestor_range;
16029 if parent.start < child.start || parent.end > child.end {
16030 break;
16031 }
16032 }
16033
16034 if parent == child {
16035 return None;
16036 }
16037 let text = buffer.text_for_range(child).collect::<String>();
16038 Some((selection.id, parent, text))
16039 })
16040 .collect::<Vec<_>>();
16041 if full_edits.is_empty() {
16042 return;
16043 }
16044
16045 self.transact(window, cx, |this, window, cx| {
16046 this.buffer.update(cx, |buffer, cx| {
16047 buffer.edit(
16048 full_edits
16049 .iter()
16050 .map(|(_, p, t)| (p.clone(), t.clone()))
16051 .collect::<Vec<_>>(),
16052 None,
16053 cx,
16054 );
16055 });
16056 this.change_selections(Default::default(), window, cx, |s| {
16057 let mut offset = 0;
16058 let mut selections = vec![];
16059 for (id, parent, text) in full_edits {
16060 let start = parent.start - offset;
16061 offset += (parent.end - parent.start) - text.len();
16062 selections.push(Selection {
16063 id,
16064 start,
16065 end: start + text.len(),
16066 reversed: false,
16067 goal: Default::default(),
16068 });
16069 }
16070 s.select(selections);
16071 });
16072 });
16073 }
16074
16075 pub fn select_next_syntax_node(
16076 &mut self,
16077 _: &SelectNextSyntaxNode,
16078 window: &mut Window,
16079 cx: &mut Context<Self>,
16080 ) {
16081 let old_selections: Box<[_]> = self
16082 .selections
16083 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16084 .into();
16085 if old_selections.is_empty() {
16086 return;
16087 }
16088
16089 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16090
16091 let buffer = self.buffer.read(cx).snapshot(cx);
16092 let mut selected_sibling = false;
16093
16094 let new_selections = old_selections
16095 .iter()
16096 .map(|selection| {
16097 let old_range = selection.start..selection.end;
16098
16099 let old_range =
16100 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16101 let excerpt = buffer.excerpt_containing(old_range.clone());
16102
16103 if let Some(mut excerpt) = excerpt
16104 && let Some(node) = excerpt
16105 .buffer()
16106 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16107 {
16108 let new_range = excerpt.map_range_from_buffer(
16109 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16110 );
16111 selected_sibling = true;
16112 Selection {
16113 id: selection.id,
16114 start: new_range.start,
16115 end: new_range.end,
16116 goal: SelectionGoal::None,
16117 reversed: selection.reversed,
16118 }
16119 } else {
16120 selection.clone()
16121 }
16122 })
16123 .collect::<Vec<_>>();
16124
16125 if selected_sibling {
16126 self.change_selections(
16127 SelectionEffects::scroll(Autoscroll::fit()),
16128 window,
16129 cx,
16130 |s| {
16131 s.select(new_selections);
16132 },
16133 );
16134 }
16135 }
16136
16137 pub fn select_prev_syntax_node(
16138 &mut self,
16139 _: &SelectPreviousSyntaxNode,
16140 window: &mut Window,
16141 cx: &mut Context<Self>,
16142 ) {
16143 let old_selections: Box<[_]> = self
16144 .selections
16145 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16146 .into();
16147 if old_selections.is_empty() {
16148 return;
16149 }
16150
16151 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16152
16153 let buffer = self.buffer.read(cx).snapshot(cx);
16154 let mut selected_sibling = false;
16155
16156 let new_selections = old_selections
16157 .iter()
16158 .map(|selection| {
16159 let old_range = selection.start..selection.end;
16160 let old_range =
16161 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16162 let excerpt = buffer.excerpt_containing(old_range.clone());
16163
16164 if let Some(mut excerpt) = excerpt
16165 && let Some(node) = excerpt
16166 .buffer()
16167 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16168 {
16169 let new_range = excerpt.map_range_from_buffer(
16170 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16171 );
16172 selected_sibling = true;
16173 Selection {
16174 id: selection.id,
16175 start: new_range.start,
16176 end: new_range.end,
16177 goal: SelectionGoal::None,
16178 reversed: selection.reversed,
16179 }
16180 } else {
16181 selection.clone()
16182 }
16183 })
16184 .collect::<Vec<_>>();
16185
16186 if selected_sibling {
16187 self.change_selections(
16188 SelectionEffects::scroll(Autoscroll::fit()),
16189 window,
16190 cx,
16191 |s| {
16192 s.select(new_selections);
16193 },
16194 );
16195 }
16196 }
16197
16198 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16199 if !EditorSettings::get_global(cx).gutter.runnables {
16200 self.clear_tasks();
16201 return Task::ready(());
16202 }
16203 let project = self.project().map(Entity::downgrade);
16204 let task_sources = self.lsp_task_sources(cx);
16205 let multi_buffer = self.buffer.downgrade();
16206 cx.spawn_in(window, async move |editor, cx| {
16207 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16208 let Some(project) = project.and_then(|p| p.upgrade()) else {
16209 return;
16210 };
16211 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16212 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16213 }) else {
16214 return;
16215 };
16216
16217 let hide_runnables = project
16218 .update(cx, |project, _| project.is_via_collab())
16219 .unwrap_or(true);
16220 if hide_runnables {
16221 return;
16222 }
16223 let new_rows =
16224 cx.background_spawn({
16225 let snapshot = display_snapshot.clone();
16226 async move {
16227 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16228 }
16229 })
16230 .await;
16231 let Ok(lsp_tasks) =
16232 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16233 else {
16234 return;
16235 };
16236 let lsp_tasks = lsp_tasks.await;
16237
16238 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16239 lsp_tasks
16240 .into_iter()
16241 .flat_map(|(kind, tasks)| {
16242 tasks.into_iter().filter_map(move |(location, task)| {
16243 Some((kind.clone(), location?, task))
16244 })
16245 })
16246 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16247 let buffer = location.target.buffer;
16248 let buffer_snapshot = buffer.read(cx).snapshot();
16249 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16250 |(excerpt_id, snapshot, _)| {
16251 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16252 display_snapshot
16253 .buffer_snapshot()
16254 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16255 } else {
16256 None
16257 }
16258 },
16259 );
16260 if let Some(offset) = offset {
16261 let task_buffer_range =
16262 location.target.range.to_point(&buffer_snapshot);
16263 let context_buffer_range =
16264 task_buffer_range.to_offset(&buffer_snapshot);
16265 let context_range = BufferOffset(context_buffer_range.start)
16266 ..BufferOffset(context_buffer_range.end);
16267
16268 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16269 .or_insert_with(|| RunnableTasks {
16270 templates: Vec::new(),
16271 offset,
16272 column: task_buffer_range.start.column,
16273 extra_variables: HashMap::default(),
16274 context_range,
16275 })
16276 .templates
16277 .push((kind, task.original_task().clone()));
16278 }
16279
16280 acc
16281 })
16282 }) else {
16283 return;
16284 };
16285
16286 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16287 buffer.language_settings(cx).tasks.prefer_lsp
16288 }) else {
16289 return;
16290 };
16291
16292 let rows = Self::runnable_rows(
16293 project,
16294 display_snapshot,
16295 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16296 new_rows,
16297 cx.clone(),
16298 )
16299 .await;
16300 editor
16301 .update(cx, |editor, _| {
16302 editor.clear_tasks();
16303 for (key, mut value) in rows {
16304 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16305 value.templates.extend(lsp_tasks.templates);
16306 }
16307
16308 editor.insert_tasks(key, value);
16309 }
16310 for (key, value) in lsp_tasks_by_rows {
16311 editor.insert_tasks(key, value);
16312 }
16313 })
16314 .ok();
16315 })
16316 }
16317 fn fetch_runnable_ranges(
16318 snapshot: &DisplaySnapshot,
16319 range: Range<Anchor>,
16320 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16321 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16322 }
16323
16324 fn runnable_rows(
16325 project: Entity<Project>,
16326 snapshot: DisplaySnapshot,
16327 prefer_lsp: bool,
16328 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16329 cx: AsyncWindowContext,
16330 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16331 cx.spawn(async move |cx| {
16332 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16333 for (run_range, mut runnable) in runnable_ranges {
16334 let Some(tasks) = cx
16335 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16336 .ok()
16337 else {
16338 continue;
16339 };
16340 let mut tasks = tasks.await;
16341
16342 if prefer_lsp {
16343 tasks.retain(|(task_kind, _)| {
16344 !matches!(task_kind, TaskSourceKind::Language { .. })
16345 });
16346 }
16347 if tasks.is_empty() {
16348 continue;
16349 }
16350
16351 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16352 let Some(row) = snapshot
16353 .buffer_snapshot()
16354 .buffer_line_for_row(MultiBufferRow(point.row))
16355 .map(|(_, range)| range.start.row)
16356 else {
16357 continue;
16358 };
16359
16360 let context_range =
16361 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16362 runnable_rows.push((
16363 (runnable.buffer_id, row),
16364 RunnableTasks {
16365 templates: tasks,
16366 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16367 context_range,
16368 column: point.column,
16369 extra_variables: runnable.extra_captures,
16370 },
16371 ));
16372 }
16373 runnable_rows
16374 })
16375 }
16376
16377 fn templates_with_tags(
16378 project: &Entity<Project>,
16379 runnable: &mut Runnable,
16380 cx: &mut App,
16381 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16382 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16383 let (worktree_id, file) = project
16384 .buffer_for_id(runnable.buffer, cx)
16385 .and_then(|buffer| buffer.read(cx).file())
16386 .map(|file| (file.worktree_id(cx), file.clone()))
16387 .unzip();
16388
16389 (
16390 project.task_store().read(cx).task_inventory().cloned(),
16391 worktree_id,
16392 file,
16393 )
16394 });
16395
16396 let tags = mem::take(&mut runnable.tags);
16397 let language = runnable.language.clone();
16398 cx.spawn(async move |cx| {
16399 let mut templates_with_tags = Vec::new();
16400 if let Some(inventory) = inventory {
16401 for RunnableTag(tag) in tags {
16402 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16403 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16404 }) else {
16405 return templates_with_tags;
16406 };
16407 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16408 move |(_, template)| {
16409 template.tags.iter().any(|source_tag| source_tag == &tag)
16410 },
16411 ));
16412 }
16413 }
16414 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16415
16416 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16417 // Strongest source wins; if we have worktree tag binding, prefer that to
16418 // global and language bindings;
16419 // if we have a global binding, prefer that to language binding.
16420 let first_mismatch = templates_with_tags
16421 .iter()
16422 .position(|(tag_source, _)| tag_source != leading_tag_source);
16423 if let Some(index) = first_mismatch {
16424 templates_with_tags.truncate(index);
16425 }
16426 }
16427
16428 templates_with_tags
16429 })
16430 }
16431
16432 pub fn move_to_enclosing_bracket(
16433 &mut self,
16434 _: &MoveToEnclosingBracket,
16435 window: &mut Window,
16436 cx: &mut Context<Self>,
16437 ) {
16438 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16439 self.change_selections(Default::default(), window, cx, |s| {
16440 s.move_offsets_with(|snapshot, selection| {
16441 let Some(enclosing_bracket_ranges) =
16442 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16443 else {
16444 return;
16445 };
16446
16447 let mut best_length = usize::MAX;
16448 let mut best_inside = false;
16449 let mut best_in_bracket_range = false;
16450 let mut best_destination = None;
16451 for (open, close) in enclosing_bracket_ranges {
16452 let close = close.to_inclusive();
16453 let length = *close.end() - open.start;
16454 let inside = selection.start >= open.end && selection.end <= *close.start();
16455 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16456 || close.contains(&selection.head());
16457
16458 // If best is next to a bracket and current isn't, skip
16459 if !in_bracket_range && best_in_bracket_range {
16460 continue;
16461 }
16462
16463 // Prefer smaller lengths unless best is inside and current isn't
16464 if length > best_length && (best_inside || !inside) {
16465 continue;
16466 }
16467
16468 best_length = length;
16469 best_inside = inside;
16470 best_in_bracket_range = in_bracket_range;
16471 best_destination = Some(
16472 if close.contains(&selection.start) && close.contains(&selection.end) {
16473 if inside { open.end } else { open.start }
16474 } else if inside {
16475 *close.start()
16476 } else {
16477 *close.end()
16478 },
16479 );
16480 }
16481
16482 if let Some(destination) = best_destination {
16483 selection.collapse_to(destination, SelectionGoal::None);
16484 }
16485 })
16486 });
16487 }
16488
16489 pub fn undo_selection(
16490 &mut self,
16491 _: &UndoSelection,
16492 window: &mut Window,
16493 cx: &mut Context<Self>,
16494 ) {
16495 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16496 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16497 self.selection_history.mode = SelectionHistoryMode::Undoing;
16498 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16499 this.end_selection(window, cx);
16500 this.change_selections(
16501 SelectionEffects::scroll(Autoscroll::newest()),
16502 window,
16503 cx,
16504 |s| s.select_anchors(entry.selections.to_vec()),
16505 );
16506 });
16507 self.selection_history.mode = SelectionHistoryMode::Normal;
16508
16509 self.select_next_state = entry.select_next_state;
16510 self.select_prev_state = entry.select_prev_state;
16511 self.add_selections_state = entry.add_selections_state;
16512 }
16513 }
16514
16515 pub fn redo_selection(
16516 &mut self,
16517 _: &RedoSelection,
16518 window: &mut Window,
16519 cx: &mut Context<Self>,
16520 ) {
16521 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16522 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16523 self.selection_history.mode = SelectionHistoryMode::Redoing;
16524 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16525 this.end_selection(window, cx);
16526 this.change_selections(
16527 SelectionEffects::scroll(Autoscroll::newest()),
16528 window,
16529 cx,
16530 |s| s.select_anchors(entry.selections.to_vec()),
16531 );
16532 });
16533 self.selection_history.mode = SelectionHistoryMode::Normal;
16534
16535 self.select_next_state = entry.select_next_state;
16536 self.select_prev_state = entry.select_prev_state;
16537 self.add_selections_state = entry.add_selections_state;
16538 }
16539 }
16540
16541 pub fn expand_excerpts(
16542 &mut self,
16543 action: &ExpandExcerpts,
16544 _: &mut Window,
16545 cx: &mut Context<Self>,
16546 ) {
16547 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16548 }
16549
16550 pub fn expand_excerpts_down(
16551 &mut self,
16552 action: &ExpandExcerptsDown,
16553 _: &mut Window,
16554 cx: &mut Context<Self>,
16555 ) {
16556 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16557 }
16558
16559 pub fn expand_excerpts_up(
16560 &mut self,
16561 action: &ExpandExcerptsUp,
16562 _: &mut Window,
16563 cx: &mut Context<Self>,
16564 ) {
16565 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16566 }
16567
16568 pub fn expand_excerpts_for_direction(
16569 &mut self,
16570 lines: u32,
16571 direction: ExpandExcerptDirection,
16572
16573 cx: &mut Context<Self>,
16574 ) {
16575 let selections = self.selections.disjoint_anchors_arc();
16576
16577 let lines = if lines == 0 {
16578 EditorSettings::get_global(cx).expand_excerpt_lines
16579 } else {
16580 lines
16581 };
16582
16583 self.buffer.update(cx, |buffer, cx| {
16584 let snapshot = buffer.snapshot(cx);
16585 let mut excerpt_ids = selections
16586 .iter()
16587 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16588 .collect::<Vec<_>>();
16589 excerpt_ids.sort();
16590 excerpt_ids.dedup();
16591 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16592 })
16593 }
16594
16595 pub fn expand_excerpt(
16596 &mut self,
16597 excerpt: ExcerptId,
16598 direction: ExpandExcerptDirection,
16599 window: &mut Window,
16600 cx: &mut Context<Self>,
16601 ) {
16602 let current_scroll_position = self.scroll_position(cx);
16603 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16604 let mut scroll = None;
16605
16606 if direction == ExpandExcerptDirection::Down {
16607 let multi_buffer = self.buffer.read(cx);
16608 let snapshot = multi_buffer.snapshot(cx);
16609 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16610 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16611 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16612 {
16613 let buffer_snapshot = buffer.read(cx).snapshot();
16614 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16615 let last_row = buffer_snapshot.max_point().row;
16616 let lines_below = last_row.saturating_sub(excerpt_end_row);
16617 if lines_below >= lines_to_expand {
16618 scroll = Some(
16619 current_scroll_position
16620 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16621 );
16622 }
16623 }
16624 }
16625 if direction == ExpandExcerptDirection::Up
16626 && self
16627 .buffer
16628 .read(cx)
16629 .snapshot(cx)
16630 .excerpt_before(excerpt)
16631 .is_none()
16632 {
16633 scroll = Some(current_scroll_position);
16634 }
16635
16636 self.buffer.update(cx, |buffer, cx| {
16637 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16638 });
16639
16640 if let Some(new_scroll_position) = scroll {
16641 self.set_scroll_position(new_scroll_position, window, cx);
16642 }
16643 }
16644
16645 pub fn go_to_singleton_buffer_point(
16646 &mut self,
16647 point: Point,
16648 window: &mut Window,
16649 cx: &mut Context<Self>,
16650 ) {
16651 self.go_to_singleton_buffer_range(point..point, window, cx);
16652 }
16653
16654 pub fn go_to_singleton_buffer_range(
16655 &mut self,
16656 range: Range<Point>,
16657 window: &mut Window,
16658 cx: &mut Context<Self>,
16659 ) {
16660 let multibuffer = self.buffer().read(cx);
16661 let Some(buffer) = multibuffer.as_singleton() else {
16662 return;
16663 };
16664 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16665 return;
16666 };
16667 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16668 return;
16669 };
16670 self.change_selections(
16671 SelectionEffects::default().nav_history(true),
16672 window,
16673 cx,
16674 |s| s.select_anchor_ranges([start..end]),
16675 );
16676 }
16677
16678 pub fn go_to_diagnostic(
16679 &mut self,
16680 action: &GoToDiagnostic,
16681 window: &mut Window,
16682 cx: &mut Context<Self>,
16683 ) {
16684 if !self.diagnostics_enabled() {
16685 return;
16686 }
16687 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16688 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16689 }
16690
16691 pub fn go_to_prev_diagnostic(
16692 &mut self,
16693 action: &GoToPreviousDiagnostic,
16694 window: &mut Window,
16695 cx: &mut Context<Self>,
16696 ) {
16697 if !self.diagnostics_enabled() {
16698 return;
16699 }
16700 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16701 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16702 }
16703
16704 pub fn go_to_diagnostic_impl(
16705 &mut self,
16706 direction: Direction,
16707 severity: GoToDiagnosticSeverityFilter,
16708 window: &mut Window,
16709 cx: &mut Context<Self>,
16710 ) {
16711 let buffer = self.buffer.read(cx).snapshot(cx);
16712 let selection = self
16713 .selections
16714 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16715
16716 let mut active_group_id = None;
16717 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16718 && active_group.active_range.start.to_offset(&buffer) == selection.start
16719 {
16720 active_group_id = Some(active_group.group_id);
16721 }
16722
16723 fn filtered<'a>(
16724 severity: GoToDiagnosticSeverityFilter,
16725 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16726 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16727 diagnostics
16728 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16729 .filter(|entry| entry.range.start != entry.range.end)
16730 .filter(|entry| !entry.diagnostic.is_unnecessary)
16731 }
16732
16733 let before = filtered(
16734 severity,
16735 buffer
16736 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16737 .filter(|entry| entry.range.start <= selection.start),
16738 );
16739 let after = filtered(
16740 severity,
16741 buffer
16742 .diagnostics_in_range(selection.start..buffer.len())
16743 .filter(|entry| entry.range.start >= selection.start),
16744 );
16745
16746 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16747 if direction == Direction::Prev {
16748 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16749 {
16750 for diagnostic in prev_diagnostics.into_iter().rev() {
16751 if diagnostic.range.start != selection.start
16752 || active_group_id
16753 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16754 {
16755 found = Some(diagnostic);
16756 break 'outer;
16757 }
16758 }
16759 }
16760 } else {
16761 for diagnostic in after.chain(before) {
16762 if diagnostic.range.start != selection.start
16763 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16764 {
16765 found = Some(diagnostic);
16766 break;
16767 }
16768 }
16769 }
16770 let Some(next_diagnostic) = found else {
16771 return;
16772 };
16773
16774 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16775 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16776 return;
16777 };
16778 let snapshot = self.snapshot(window, cx);
16779 if snapshot.intersects_fold(next_diagnostic.range.start) {
16780 self.unfold_ranges(
16781 std::slice::from_ref(&next_diagnostic.range),
16782 true,
16783 false,
16784 cx,
16785 );
16786 }
16787 self.change_selections(Default::default(), window, cx, |s| {
16788 s.select_ranges(vec![
16789 next_diagnostic.range.start..next_diagnostic.range.start,
16790 ])
16791 });
16792 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16793 self.refresh_edit_prediction(false, true, window, cx);
16794 }
16795
16796 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16798 let snapshot = self.snapshot(window, cx);
16799 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16800 self.go_to_hunk_before_or_after_position(
16801 &snapshot,
16802 selection.head(),
16803 Direction::Next,
16804 window,
16805 cx,
16806 );
16807 }
16808
16809 pub fn go_to_hunk_before_or_after_position(
16810 &mut self,
16811 snapshot: &EditorSnapshot,
16812 position: Point,
16813 direction: Direction,
16814 window: &mut Window,
16815 cx: &mut Context<Editor>,
16816 ) {
16817 let row = if direction == Direction::Next {
16818 self.hunk_after_position(snapshot, position)
16819 .map(|hunk| hunk.row_range.start)
16820 } else {
16821 self.hunk_before_position(snapshot, position)
16822 };
16823
16824 if let Some(row) = row {
16825 let destination = Point::new(row.0, 0);
16826 let autoscroll = Autoscroll::center();
16827
16828 self.unfold_ranges(&[destination..destination], false, false, cx);
16829 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16830 s.select_ranges([destination..destination]);
16831 });
16832 }
16833 }
16834
16835 fn hunk_after_position(
16836 &mut self,
16837 snapshot: &EditorSnapshot,
16838 position: Point,
16839 ) -> Option<MultiBufferDiffHunk> {
16840 snapshot
16841 .buffer_snapshot()
16842 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16843 .find(|hunk| hunk.row_range.start.0 > position.row)
16844 .or_else(|| {
16845 snapshot
16846 .buffer_snapshot()
16847 .diff_hunks_in_range(Point::zero()..position)
16848 .find(|hunk| hunk.row_range.end.0 < position.row)
16849 })
16850 }
16851
16852 fn go_to_prev_hunk(
16853 &mut self,
16854 _: &GoToPreviousHunk,
16855 window: &mut Window,
16856 cx: &mut Context<Self>,
16857 ) {
16858 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16859 let snapshot = self.snapshot(window, cx);
16860 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16861 self.go_to_hunk_before_or_after_position(
16862 &snapshot,
16863 selection.head(),
16864 Direction::Prev,
16865 window,
16866 cx,
16867 );
16868 }
16869
16870 fn hunk_before_position(
16871 &mut self,
16872 snapshot: &EditorSnapshot,
16873 position: Point,
16874 ) -> Option<MultiBufferRow> {
16875 snapshot
16876 .buffer_snapshot()
16877 .diff_hunk_before(position)
16878 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16879 }
16880
16881 fn go_to_next_change(
16882 &mut self,
16883 _: &GoToNextChange,
16884 window: &mut Window,
16885 cx: &mut Context<Self>,
16886 ) {
16887 if let Some(selections) = self
16888 .change_list
16889 .next_change(1, Direction::Next)
16890 .map(|s| s.to_vec())
16891 {
16892 self.change_selections(Default::default(), window, cx, |s| {
16893 let map = s.display_snapshot();
16894 s.select_display_ranges(selections.iter().map(|a| {
16895 let point = a.to_display_point(&map);
16896 point..point
16897 }))
16898 })
16899 }
16900 }
16901
16902 fn go_to_previous_change(
16903 &mut self,
16904 _: &GoToPreviousChange,
16905 window: &mut Window,
16906 cx: &mut Context<Self>,
16907 ) {
16908 if let Some(selections) = self
16909 .change_list
16910 .next_change(1, Direction::Prev)
16911 .map(|s| s.to_vec())
16912 {
16913 self.change_selections(Default::default(), window, cx, |s| {
16914 let map = s.display_snapshot();
16915 s.select_display_ranges(selections.iter().map(|a| {
16916 let point = a.to_display_point(&map);
16917 point..point
16918 }))
16919 })
16920 }
16921 }
16922
16923 pub fn go_to_next_document_highlight(
16924 &mut self,
16925 _: &GoToNextDocumentHighlight,
16926 window: &mut Window,
16927 cx: &mut Context<Self>,
16928 ) {
16929 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16930 }
16931
16932 pub fn go_to_prev_document_highlight(
16933 &mut self,
16934 _: &GoToPreviousDocumentHighlight,
16935 window: &mut Window,
16936 cx: &mut Context<Self>,
16937 ) {
16938 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16939 }
16940
16941 pub fn go_to_document_highlight_before_or_after_position(
16942 &mut self,
16943 direction: Direction,
16944 window: &mut Window,
16945 cx: &mut Context<Editor>,
16946 ) {
16947 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16948 let snapshot = self.snapshot(window, cx);
16949 let buffer = &snapshot.buffer_snapshot();
16950 let position = self
16951 .selections
16952 .newest::<Point>(&snapshot.display_snapshot)
16953 .head();
16954 let anchor_position = buffer.anchor_after(position);
16955
16956 // Get all document highlights (both read and write)
16957 let mut all_highlights = Vec::new();
16958
16959 if let Some((_, read_highlights)) = self
16960 .background_highlights
16961 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16962 {
16963 all_highlights.extend(read_highlights.iter());
16964 }
16965
16966 if let Some((_, write_highlights)) = self
16967 .background_highlights
16968 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16969 {
16970 all_highlights.extend(write_highlights.iter());
16971 }
16972
16973 if all_highlights.is_empty() {
16974 return;
16975 }
16976
16977 // Sort highlights by position
16978 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16979
16980 let target_highlight = match direction {
16981 Direction::Next => {
16982 // Find the first highlight after the current position
16983 all_highlights
16984 .iter()
16985 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16986 }
16987 Direction::Prev => {
16988 // Find the last highlight before the current position
16989 all_highlights
16990 .iter()
16991 .rev()
16992 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16993 }
16994 };
16995
16996 if let Some(highlight) = target_highlight {
16997 let destination = highlight.start.to_point(buffer);
16998 let autoscroll = Autoscroll::center();
16999
17000 self.unfold_ranges(&[destination..destination], false, false, cx);
17001 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17002 s.select_ranges([destination..destination]);
17003 });
17004 }
17005 }
17006
17007 fn go_to_line<T: 'static>(
17008 &mut self,
17009 position: Anchor,
17010 highlight_color: Option<Hsla>,
17011 window: &mut Window,
17012 cx: &mut Context<Self>,
17013 ) {
17014 let snapshot = self.snapshot(window, cx).display_snapshot;
17015 let position = position.to_point(&snapshot.buffer_snapshot());
17016 let start = snapshot
17017 .buffer_snapshot()
17018 .clip_point(Point::new(position.row, 0), Bias::Left);
17019 let end = start + Point::new(1, 0);
17020 let start = snapshot.buffer_snapshot().anchor_before(start);
17021 let end = snapshot.buffer_snapshot().anchor_before(end);
17022
17023 self.highlight_rows::<T>(
17024 start..end,
17025 highlight_color
17026 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17027 Default::default(),
17028 cx,
17029 );
17030
17031 if self.buffer.read(cx).is_singleton() {
17032 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17033 }
17034 }
17035
17036 pub fn go_to_definition(
17037 &mut self,
17038 _: &GoToDefinition,
17039 window: &mut Window,
17040 cx: &mut Context<Self>,
17041 ) -> Task<Result<Navigated>> {
17042 let definition =
17043 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17044 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17045 cx.spawn_in(window, async move |editor, cx| {
17046 if definition.await? == Navigated::Yes {
17047 return Ok(Navigated::Yes);
17048 }
17049 match fallback_strategy {
17050 GoToDefinitionFallback::None => Ok(Navigated::No),
17051 GoToDefinitionFallback::FindAllReferences => {
17052 match editor.update_in(cx, |editor, window, cx| {
17053 editor.find_all_references(&FindAllReferences::default(), window, cx)
17054 })? {
17055 Some(references) => references.await,
17056 None => Ok(Navigated::No),
17057 }
17058 }
17059 }
17060 })
17061 }
17062
17063 pub fn go_to_declaration(
17064 &mut self,
17065 _: &GoToDeclaration,
17066 window: &mut Window,
17067 cx: &mut Context<Self>,
17068 ) -> Task<Result<Navigated>> {
17069 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17070 }
17071
17072 pub fn go_to_declaration_split(
17073 &mut self,
17074 _: &GoToDeclaration,
17075 window: &mut Window,
17076 cx: &mut Context<Self>,
17077 ) -> Task<Result<Navigated>> {
17078 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17079 }
17080
17081 pub fn go_to_implementation(
17082 &mut self,
17083 _: &GoToImplementation,
17084 window: &mut Window,
17085 cx: &mut Context<Self>,
17086 ) -> Task<Result<Navigated>> {
17087 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17088 }
17089
17090 pub fn go_to_implementation_split(
17091 &mut self,
17092 _: &GoToImplementationSplit,
17093 window: &mut Window,
17094 cx: &mut Context<Self>,
17095 ) -> Task<Result<Navigated>> {
17096 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17097 }
17098
17099 pub fn go_to_type_definition(
17100 &mut self,
17101 _: &GoToTypeDefinition,
17102 window: &mut Window,
17103 cx: &mut Context<Self>,
17104 ) -> Task<Result<Navigated>> {
17105 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17106 }
17107
17108 pub fn go_to_definition_split(
17109 &mut self,
17110 _: &GoToDefinitionSplit,
17111 window: &mut Window,
17112 cx: &mut Context<Self>,
17113 ) -> Task<Result<Navigated>> {
17114 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17115 }
17116
17117 pub fn go_to_type_definition_split(
17118 &mut self,
17119 _: &GoToTypeDefinitionSplit,
17120 window: &mut Window,
17121 cx: &mut Context<Self>,
17122 ) -> Task<Result<Navigated>> {
17123 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17124 }
17125
17126 fn go_to_definition_of_kind(
17127 &mut self,
17128 kind: GotoDefinitionKind,
17129 split: bool,
17130 window: &mut Window,
17131 cx: &mut Context<Self>,
17132 ) -> Task<Result<Navigated>> {
17133 let Some(provider) = self.semantics_provider.clone() else {
17134 return Task::ready(Ok(Navigated::No));
17135 };
17136 let head = self
17137 .selections
17138 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17139 .head();
17140 let buffer = self.buffer.read(cx);
17141 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17142 return Task::ready(Ok(Navigated::No));
17143 };
17144 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17145 return Task::ready(Ok(Navigated::No));
17146 };
17147
17148 cx.spawn_in(window, async move |editor, cx| {
17149 let Some(definitions) = definitions.await? else {
17150 return Ok(Navigated::No);
17151 };
17152 let navigated = editor
17153 .update_in(cx, |editor, window, cx| {
17154 editor.navigate_to_hover_links(
17155 Some(kind),
17156 definitions
17157 .into_iter()
17158 .filter(|location| {
17159 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17160 })
17161 .map(HoverLink::Text)
17162 .collect::<Vec<_>>(),
17163 split,
17164 window,
17165 cx,
17166 )
17167 })?
17168 .await?;
17169 anyhow::Ok(navigated)
17170 })
17171 }
17172
17173 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17174 let selection = self.selections.newest_anchor();
17175 let head = selection.head();
17176 let tail = selection.tail();
17177
17178 let Some((buffer, start_position)) =
17179 self.buffer.read(cx).text_anchor_for_position(head, cx)
17180 else {
17181 return;
17182 };
17183
17184 let end_position = if head != tail {
17185 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17186 return;
17187 };
17188 Some(pos)
17189 } else {
17190 None
17191 };
17192
17193 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17194 let url = if let Some(end_pos) = end_position {
17195 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17196 } else {
17197 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17198 };
17199
17200 if let Some(url) = url {
17201 cx.update(|window, cx| {
17202 if parse_zed_link(&url, cx).is_some() {
17203 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17204 } else {
17205 cx.open_url(&url);
17206 }
17207 })?;
17208 }
17209
17210 anyhow::Ok(())
17211 });
17212
17213 url_finder.detach();
17214 }
17215
17216 pub fn open_selected_filename(
17217 &mut self,
17218 _: &OpenSelectedFilename,
17219 window: &mut Window,
17220 cx: &mut Context<Self>,
17221 ) {
17222 let Some(workspace) = self.workspace() else {
17223 return;
17224 };
17225
17226 let position = self.selections.newest_anchor().head();
17227
17228 let Some((buffer, buffer_position)) =
17229 self.buffer.read(cx).text_anchor_for_position(position, cx)
17230 else {
17231 return;
17232 };
17233
17234 let project = self.project.clone();
17235
17236 cx.spawn_in(window, async move |_, cx| {
17237 let result = find_file(&buffer, project, buffer_position, cx).await;
17238
17239 if let Some((_, path)) = result {
17240 workspace
17241 .update_in(cx, |workspace, window, cx| {
17242 workspace.open_resolved_path(path, window, cx)
17243 })?
17244 .await?;
17245 }
17246 anyhow::Ok(())
17247 })
17248 .detach();
17249 }
17250
17251 pub(crate) fn navigate_to_hover_links(
17252 &mut self,
17253 kind: Option<GotoDefinitionKind>,
17254 definitions: Vec<HoverLink>,
17255 split: bool,
17256 window: &mut Window,
17257 cx: &mut Context<Editor>,
17258 ) -> Task<Result<Navigated>> {
17259 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17260 let mut first_url_or_file = None;
17261 let definitions: Vec<_> = definitions
17262 .into_iter()
17263 .filter_map(|def| match def {
17264 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17265 HoverLink::InlayHint(lsp_location, server_id) => {
17266 let computation =
17267 self.compute_target_location(lsp_location, server_id, window, cx);
17268 Some(cx.background_spawn(computation))
17269 }
17270 HoverLink::Url(url) => {
17271 first_url_or_file = Some(Either::Left(url));
17272 None
17273 }
17274 HoverLink::File(path) => {
17275 first_url_or_file = Some(Either::Right(path));
17276 None
17277 }
17278 })
17279 .collect();
17280
17281 let workspace = self.workspace();
17282
17283 cx.spawn_in(window, async move |editor, cx| {
17284 let locations: Vec<Location> = future::join_all(definitions)
17285 .await
17286 .into_iter()
17287 .filter_map(|location| location.transpose())
17288 .collect::<Result<_>>()
17289 .context("location tasks")?;
17290 let mut locations = cx.update(|_, cx| {
17291 locations
17292 .into_iter()
17293 .map(|location| {
17294 let buffer = location.buffer.read(cx);
17295 (location.buffer, location.range.to_point(buffer))
17296 })
17297 .into_group_map()
17298 })?;
17299 let mut num_locations = 0;
17300 for ranges in locations.values_mut() {
17301 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17302 ranges.dedup();
17303 num_locations += ranges.len();
17304 }
17305
17306 if num_locations > 1 {
17307 let tab_kind = match kind {
17308 Some(GotoDefinitionKind::Implementation) => "Implementations",
17309 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17310 Some(GotoDefinitionKind::Declaration) => "Declarations",
17311 Some(GotoDefinitionKind::Type) => "Types",
17312 };
17313 let title = editor
17314 .update_in(cx, |_, _, cx| {
17315 let target = locations
17316 .iter()
17317 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17318 .map(|(buffer, location)| {
17319 buffer
17320 .read(cx)
17321 .text_for_range(location.clone())
17322 .collect::<String>()
17323 })
17324 .filter(|text| !text.contains('\n'))
17325 .unique()
17326 .take(3)
17327 .join(", ");
17328 if target.is_empty() {
17329 tab_kind.to_owned()
17330 } else {
17331 format!("{tab_kind} for {target}")
17332 }
17333 })
17334 .context("buffer title")?;
17335
17336 let Some(workspace) = workspace else {
17337 return Ok(Navigated::No);
17338 };
17339
17340 let opened = workspace
17341 .update_in(cx, |workspace, window, cx| {
17342 let allow_preview = PreviewTabsSettings::get_global(cx)
17343 .enable_preview_multibuffer_from_code_navigation;
17344 Self::open_locations_in_multibuffer(
17345 workspace,
17346 locations,
17347 title,
17348 split,
17349 allow_preview,
17350 MultibufferSelectionMode::First,
17351 window,
17352 cx,
17353 )
17354 })
17355 .is_ok();
17356
17357 anyhow::Ok(Navigated::from_bool(opened))
17358 } else if num_locations == 0 {
17359 // If there is one url or file, open it directly
17360 match first_url_or_file {
17361 Some(Either::Left(url)) => {
17362 cx.update(|window, cx| {
17363 if parse_zed_link(&url, cx).is_some() {
17364 window
17365 .dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17366 } else {
17367 cx.open_url(&url);
17368 }
17369 })?;
17370 Ok(Navigated::Yes)
17371 }
17372 Some(Either::Right(path)) => {
17373 // TODO(andrew): respect preview tab settings
17374 // `enable_keep_preview_on_code_navigation` and
17375 // `enable_preview_file_from_code_navigation`
17376 let Some(workspace) = workspace else {
17377 return Ok(Navigated::No);
17378 };
17379 workspace
17380 .update_in(cx, |workspace, window, cx| {
17381 workspace.open_resolved_path(path, window, cx)
17382 })?
17383 .await?;
17384 Ok(Navigated::Yes)
17385 }
17386 None => Ok(Navigated::No),
17387 }
17388 } else {
17389 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17390 let target_range = target_ranges.first().unwrap().clone();
17391
17392 editor.update_in(cx, |editor, window, cx| {
17393 let range = target_range.to_point(target_buffer.read(cx));
17394 let range = editor.range_for_match(&range);
17395 let range = collapse_multiline_range(range);
17396
17397 if !split
17398 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17399 {
17400 editor.go_to_singleton_buffer_range(range, window, cx);
17401 } else {
17402 let Some(workspace) = workspace else {
17403 return Navigated::No;
17404 };
17405 let pane = workspace.read(cx).active_pane().clone();
17406 window.defer(cx, move |window, cx| {
17407 let target_editor: Entity<Self> =
17408 workspace.update(cx, |workspace, cx| {
17409 let pane = if split {
17410 workspace.adjacent_pane(window, cx)
17411 } else {
17412 workspace.active_pane().clone()
17413 };
17414
17415 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17416 let keep_old_preview = preview_tabs_settings
17417 .enable_keep_preview_on_code_navigation;
17418 let allow_new_preview = preview_tabs_settings
17419 .enable_preview_file_from_code_navigation;
17420
17421 workspace.open_project_item(
17422 pane,
17423 target_buffer.clone(),
17424 true,
17425 true,
17426 keep_old_preview,
17427 allow_new_preview,
17428 window,
17429 cx,
17430 )
17431 });
17432 target_editor.update(cx, |target_editor, cx| {
17433 // When selecting a definition in a different buffer, disable the nav history
17434 // to avoid creating a history entry at the previous cursor location.
17435 pane.update(cx, |pane, _| pane.disable_history());
17436 target_editor.go_to_singleton_buffer_range(range, window, cx);
17437 pane.update(cx, |pane, _| pane.enable_history());
17438 });
17439 });
17440 }
17441 Navigated::Yes
17442 })
17443 }
17444 })
17445 }
17446
17447 fn compute_target_location(
17448 &self,
17449 lsp_location: lsp::Location,
17450 server_id: LanguageServerId,
17451 window: &mut Window,
17452 cx: &mut Context<Self>,
17453 ) -> Task<anyhow::Result<Option<Location>>> {
17454 let Some(project) = self.project.clone() else {
17455 return Task::ready(Ok(None));
17456 };
17457
17458 cx.spawn_in(window, async move |editor, cx| {
17459 let location_task = editor.update(cx, |_, cx| {
17460 project.update(cx, |project, cx| {
17461 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17462 })
17463 })?;
17464 let location = Some({
17465 let target_buffer_handle = location_task.await.context("open local buffer")?;
17466 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17467 let target_start = target_buffer
17468 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17469 let target_end = target_buffer
17470 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17471 target_buffer.anchor_after(target_start)
17472 ..target_buffer.anchor_before(target_end)
17473 })?;
17474 Location {
17475 buffer: target_buffer_handle,
17476 range,
17477 }
17478 });
17479 Ok(location)
17480 })
17481 }
17482
17483 fn go_to_next_reference(
17484 &mut self,
17485 _: &GoToNextReference,
17486 window: &mut Window,
17487 cx: &mut Context<Self>,
17488 ) {
17489 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17490 if let Some(task) = task {
17491 task.detach();
17492 };
17493 }
17494
17495 fn go_to_prev_reference(
17496 &mut self,
17497 _: &GoToPreviousReference,
17498 window: &mut Window,
17499 cx: &mut Context<Self>,
17500 ) {
17501 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17502 if let Some(task) = task {
17503 task.detach();
17504 };
17505 }
17506
17507 pub fn go_to_reference_before_or_after_position(
17508 &mut self,
17509 direction: Direction,
17510 count: usize,
17511 window: &mut Window,
17512 cx: &mut Context<Self>,
17513 ) -> Option<Task<Result<()>>> {
17514 let selection = self.selections.newest_anchor();
17515 let head = selection.head();
17516
17517 let multi_buffer = self.buffer.read(cx);
17518
17519 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17520 let workspace = self.workspace()?;
17521 let project = workspace.read(cx).project().clone();
17522 let references =
17523 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17524 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17525 let Some(locations) = references.await? else {
17526 return Ok(());
17527 };
17528
17529 if locations.is_empty() {
17530 // totally normal - the cursor may be on something which is not
17531 // a symbol (e.g. a keyword)
17532 log::info!("no references found under cursor");
17533 return Ok(());
17534 }
17535
17536 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17537
17538 let (locations, current_location_index) =
17539 multi_buffer.update(cx, |multi_buffer, cx| {
17540 let mut locations = locations
17541 .into_iter()
17542 .filter_map(|loc| {
17543 let start = multi_buffer.buffer_anchor_to_anchor(
17544 &loc.buffer,
17545 loc.range.start,
17546 cx,
17547 )?;
17548 let end = multi_buffer.buffer_anchor_to_anchor(
17549 &loc.buffer,
17550 loc.range.end,
17551 cx,
17552 )?;
17553 Some(start..end)
17554 })
17555 .collect::<Vec<_>>();
17556
17557 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17558 // There is an O(n) implementation, but given this list will be
17559 // small (usually <100 items), the extra O(log(n)) factor isn't
17560 // worth the (surprisingly large amount of) extra complexity.
17561 locations
17562 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17563
17564 let head_offset = head.to_offset(&multi_buffer_snapshot);
17565
17566 let current_location_index = locations.iter().position(|loc| {
17567 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17568 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17569 });
17570
17571 (locations, current_location_index)
17572 })?;
17573
17574 let Some(current_location_index) = current_location_index else {
17575 // This indicates something has gone wrong, because we already
17576 // handle the "no references" case above
17577 log::error!(
17578 "failed to find current reference under cursor. Total references: {}",
17579 locations.len()
17580 );
17581 return Ok(());
17582 };
17583
17584 let destination_location_index = match direction {
17585 Direction::Next => (current_location_index + count) % locations.len(),
17586 Direction::Prev => {
17587 (current_location_index + locations.len() - count % locations.len())
17588 % locations.len()
17589 }
17590 };
17591
17592 // TODO(cameron): is this needed?
17593 // the thinking is to avoid "jumping to the current location" (avoid
17594 // polluting "jumplist" in vim terms)
17595 if current_location_index == destination_location_index {
17596 return Ok(());
17597 }
17598
17599 let Range { start, end } = locations[destination_location_index];
17600
17601 editor.update_in(cx, |editor, window, cx| {
17602 let effects = SelectionEffects::default();
17603
17604 editor.unfold_ranges(&[start..end], false, false, cx);
17605 editor.change_selections(effects, window, cx, |s| {
17606 s.select_ranges([start..start]);
17607 });
17608 })?;
17609
17610 Ok(())
17611 }))
17612 }
17613
17614 pub fn find_all_references(
17615 &mut self,
17616 action: &FindAllReferences,
17617 window: &mut Window,
17618 cx: &mut Context<Self>,
17619 ) -> Option<Task<Result<Navigated>>> {
17620 let always_open_multibuffer = action.always_open_multibuffer;
17621 let selection = self.selections.newest_anchor();
17622 let multi_buffer = self.buffer.read(cx);
17623 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17624 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17625 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17626 let head = selection_offset.head();
17627
17628 let head_anchor = multi_buffer_snapshot.anchor_at(
17629 head,
17630 if head < selection_offset.tail() {
17631 Bias::Right
17632 } else {
17633 Bias::Left
17634 },
17635 );
17636
17637 match self
17638 .find_all_references_task_sources
17639 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17640 {
17641 Ok(_) => {
17642 log::info!(
17643 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17644 );
17645 return None;
17646 }
17647 Err(i) => {
17648 self.find_all_references_task_sources.insert(i, head_anchor);
17649 }
17650 }
17651
17652 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17653 let workspace = self.workspace()?;
17654 let project = workspace.read(cx).project().clone();
17655 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17656 Some(cx.spawn_in(window, async move |editor, cx| {
17657 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17658 if let Ok(i) = editor
17659 .find_all_references_task_sources
17660 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17661 {
17662 editor.find_all_references_task_sources.remove(i);
17663 }
17664 });
17665
17666 let Some(locations) = references.await? else {
17667 return anyhow::Ok(Navigated::No);
17668 };
17669 let mut locations = cx.update(|_, cx| {
17670 locations
17671 .into_iter()
17672 .map(|location| {
17673 let buffer = location.buffer.read(cx);
17674 (location.buffer, location.range.to_point(buffer))
17675 })
17676 // if special-casing the single-match case, remove ranges
17677 // that intersect current selection
17678 .filter(|(location_buffer, location)| {
17679 if always_open_multibuffer || &buffer != location_buffer {
17680 return true;
17681 }
17682
17683 !location.contains_inclusive(&selection_point.range())
17684 })
17685 .into_group_map()
17686 })?;
17687 if locations.is_empty() {
17688 return anyhow::Ok(Navigated::No);
17689 }
17690 for ranges in locations.values_mut() {
17691 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17692 ranges.dedup();
17693 }
17694 let mut num_locations = 0;
17695 for ranges in locations.values_mut() {
17696 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17697 ranges.dedup();
17698 num_locations += ranges.len();
17699 }
17700
17701 if num_locations == 1 && !always_open_multibuffer {
17702 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17703 let target_range = target_ranges.first().unwrap().clone();
17704
17705 return editor.update_in(cx, |editor, window, cx| {
17706 let range = target_range.to_point(target_buffer.read(cx));
17707 let range = editor.range_for_match(&range);
17708 let range = range.start..range.start;
17709
17710 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17711 editor.go_to_singleton_buffer_range(range, window, cx);
17712 } else {
17713 let pane = workspace.read(cx).active_pane().clone();
17714 window.defer(cx, move |window, cx| {
17715 let target_editor: Entity<Self> =
17716 workspace.update(cx, |workspace, cx| {
17717 let pane = workspace.active_pane().clone();
17718
17719 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17720 let keep_old_preview = preview_tabs_settings
17721 .enable_keep_preview_on_code_navigation;
17722 let allow_new_preview = preview_tabs_settings
17723 .enable_preview_file_from_code_navigation;
17724
17725 workspace.open_project_item(
17726 pane,
17727 target_buffer.clone(),
17728 true,
17729 true,
17730 keep_old_preview,
17731 allow_new_preview,
17732 window,
17733 cx,
17734 )
17735 });
17736 target_editor.update(cx, |target_editor, cx| {
17737 // When selecting a definition in a different buffer, disable the nav history
17738 // to avoid creating a history entry at the previous cursor location.
17739 pane.update(cx, |pane, _| pane.disable_history());
17740 target_editor.go_to_singleton_buffer_range(range, window, cx);
17741 pane.update(cx, |pane, _| pane.enable_history());
17742 });
17743 });
17744 }
17745 Navigated::No
17746 });
17747 }
17748
17749 workspace.update_in(cx, |workspace, window, cx| {
17750 let target = locations
17751 .iter()
17752 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17753 .map(|(buffer, location)| {
17754 buffer
17755 .read(cx)
17756 .text_for_range(location.clone())
17757 .collect::<String>()
17758 })
17759 .filter(|text| !text.contains('\n'))
17760 .unique()
17761 .take(3)
17762 .join(", ");
17763 let title = if target.is_empty() {
17764 "References".to_owned()
17765 } else {
17766 format!("References to {target}")
17767 };
17768 let allow_preview = PreviewTabsSettings::get_global(cx)
17769 .enable_preview_multibuffer_from_code_navigation;
17770 Self::open_locations_in_multibuffer(
17771 workspace,
17772 locations,
17773 title,
17774 false,
17775 allow_preview,
17776 MultibufferSelectionMode::First,
17777 window,
17778 cx,
17779 );
17780 Navigated::Yes
17781 })
17782 }))
17783 }
17784
17785 /// Opens a multibuffer with the given project locations in it.
17786 pub fn open_locations_in_multibuffer(
17787 workspace: &mut Workspace,
17788 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17789 title: String,
17790 split: bool,
17791 allow_preview: bool,
17792 multibuffer_selection_mode: MultibufferSelectionMode,
17793 window: &mut Window,
17794 cx: &mut Context<Workspace>,
17795 ) {
17796 if locations.is_empty() {
17797 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17798 return;
17799 }
17800
17801 let capability = workspace.project().read(cx).capability();
17802 let mut ranges = <Vec<Range<Anchor>>>::new();
17803
17804 // a key to find existing multibuffer editors with the same set of locations
17805 // to prevent us from opening more and more multibuffer tabs for searches and the like
17806 let mut key = (title.clone(), vec![]);
17807 let excerpt_buffer = cx.new(|cx| {
17808 let key = &mut key.1;
17809 let mut multibuffer = MultiBuffer::new(capability);
17810 for (buffer, mut ranges_for_buffer) in locations {
17811 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17812 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17813 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17814 PathKey::for_buffer(&buffer, cx),
17815 buffer.clone(),
17816 ranges_for_buffer,
17817 multibuffer_context_lines(cx),
17818 cx,
17819 );
17820 ranges.extend(new_ranges)
17821 }
17822
17823 multibuffer.with_title(title)
17824 });
17825 let existing = workspace.active_pane().update(cx, |pane, cx| {
17826 pane.items()
17827 .filter_map(|item| item.downcast::<Editor>())
17828 .find(|editor| {
17829 editor
17830 .read(cx)
17831 .lookup_key
17832 .as_ref()
17833 .and_then(|it| {
17834 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17835 })
17836 .is_some_and(|it| *it == key)
17837 })
17838 });
17839 let was_existing = existing.is_some();
17840 let editor = existing.unwrap_or_else(|| {
17841 cx.new(|cx| {
17842 let mut editor = Editor::for_multibuffer(
17843 excerpt_buffer,
17844 Some(workspace.project().clone()),
17845 window,
17846 cx,
17847 );
17848 editor.lookup_key = Some(Box::new(key));
17849 editor
17850 })
17851 });
17852 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17853 MultibufferSelectionMode::First => {
17854 if let Some(first_range) = ranges.first() {
17855 editor.change_selections(
17856 SelectionEffects::no_scroll(),
17857 window,
17858 cx,
17859 |selections| {
17860 selections.clear_disjoint();
17861 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17862 },
17863 );
17864 }
17865 editor.highlight_background::<Self>(
17866 &ranges,
17867 |_, theme| theme.colors().editor_highlighted_line_background,
17868 cx,
17869 );
17870 }
17871 MultibufferSelectionMode::All => {
17872 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17873 selections.clear_disjoint();
17874 selections.select_anchor_ranges(ranges);
17875 });
17876 }
17877 });
17878
17879 let item = Box::new(editor);
17880
17881 let pane = if split {
17882 workspace.adjacent_pane(window, cx)
17883 } else {
17884 workspace.active_pane().clone()
17885 };
17886 let activate_pane = split;
17887
17888 let mut destination_index = None;
17889 pane.update(cx, |pane, cx| {
17890 if allow_preview && !was_existing {
17891 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17892 }
17893 if was_existing && !allow_preview {
17894 pane.unpreview_item_if_preview(item.item_id());
17895 }
17896 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17897 });
17898 }
17899
17900 pub fn rename(
17901 &mut self,
17902 _: &Rename,
17903 window: &mut Window,
17904 cx: &mut Context<Self>,
17905 ) -> Option<Task<Result<()>>> {
17906 use language::ToOffset as _;
17907
17908 let provider = self.semantics_provider.clone()?;
17909 let selection = self.selections.newest_anchor().clone();
17910 let (cursor_buffer, cursor_buffer_position) = self
17911 .buffer
17912 .read(cx)
17913 .text_anchor_for_position(selection.head(), cx)?;
17914 let (tail_buffer, cursor_buffer_position_end) = self
17915 .buffer
17916 .read(cx)
17917 .text_anchor_for_position(selection.tail(), cx)?;
17918 if tail_buffer != cursor_buffer {
17919 return None;
17920 }
17921
17922 let snapshot = cursor_buffer.read(cx).snapshot();
17923 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17924 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17925 let prepare_rename = provider
17926 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17927 .unwrap_or_else(|| Task::ready(Ok(None)));
17928 drop(snapshot);
17929
17930 Some(cx.spawn_in(window, async move |this, cx| {
17931 let rename_range = if let Some(range) = prepare_rename.await? {
17932 Some(range)
17933 } else {
17934 this.update(cx, |this, cx| {
17935 let buffer = this.buffer.read(cx).snapshot(cx);
17936 let mut buffer_highlights = this
17937 .document_highlights_for_position(selection.head(), &buffer)
17938 .filter(|highlight| {
17939 highlight.start.excerpt_id == selection.head().excerpt_id
17940 && highlight.end.excerpt_id == selection.head().excerpt_id
17941 });
17942 buffer_highlights
17943 .next()
17944 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17945 })?
17946 };
17947 if let Some(rename_range) = rename_range {
17948 this.update_in(cx, |this, window, cx| {
17949 let snapshot = cursor_buffer.read(cx).snapshot();
17950 let rename_buffer_range = rename_range.to_offset(&snapshot);
17951 let cursor_offset_in_rename_range =
17952 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17953 let cursor_offset_in_rename_range_end =
17954 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17955
17956 this.take_rename(false, window, cx);
17957 let buffer = this.buffer.read(cx).read(cx);
17958 let cursor_offset = selection.head().to_offset(&buffer);
17959 let rename_start =
17960 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17961 let rename_end = rename_start + rename_buffer_range.len();
17962 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17963 let mut old_highlight_id = None;
17964 let old_name: Arc<str> = buffer
17965 .chunks(rename_start..rename_end, true)
17966 .map(|chunk| {
17967 if old_highlight_id.is_none() {
17968 old_highlight_id = chunk.syntax_highlight_id;
17969 }
17970 chunk.text
17971 })
17972 .collect::<String>()
17973 .into();
17974
17975 drop(buffer);
17976
17977 // Position the selection in the rename editor so that it matches the current selection.
17978 this.show_local_selections = false;
17979 let rename_editor = cx.new(|cx| {
17980 let mut editor = Editor::single_line(window, cx);
17981 editor.buffer.update(cx, |buffer, cx| {
17982 buffer.edit(
17983 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
17984 None,
17985 cx,
17986 )
17987 });
17988 let cursor_offset_in_rename_range =
17989 MultiBufferOffset(cursor_offset_in_rename_range);
17990 let cursor_offset_in_rename_range_end =
17991 MultiBufferOffset(cursor_offset_in_rename_range_end);
17992 let rename_selection_range = match cursor_offset_in_rename_range
17993 .cmp(&cursor_offset_in_rename_range_end)
17994 {
17995 Ordering::Equal => {
17996 editor.select_all(&SelectAll, window, cx);
17997 return editor;
17998 }
17999 Ordering::Less => {
18000 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18001 }
18002 Ordering::Greater => {
18003 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18004 }
18005 };
18006 if rename_selection_range.end.0 > old_name.len() {
18007 editor.select_all(&SelectAll, window, cx);
18008 } else {
18009 editor.change_selections(Default::default(), window, cx, |s| {
18010 s.select_ranges([rename_selection_range]);
18011 });
18012 }
18013 editor
18014 });
18015 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18016 if e == &EditorEvent::Focused {
18017 cx.emit(EditorEvent::FocusedIn)
18018 }
18019 })
18020 .detach();
18021
18022 let write_highlights =
18023 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18024 let read_highlights =
18025 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18026 let ranges = write_highlights
18027 .iter()
18028 .flat_map(|(_, ranges)| ranges.iter())
18029 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18030 .cloned()
18031 .collect();
18032
18033 this.highlight_text::<Rename>(
18034 ranges,
18035 HighlightStyle {
18036 fade_out: Some(0.6),
18037 ..Default::default()
18038 },
18039 cx,
18040 );
18041 let rename_focus_handle = rename_editor.focus_handle(cx);
18042 window.focus(&rename_focus_handle, cx);
18043 let block_id = this.insert_blocks(
18044 [BlockProperties {
18045 style: BlockStyle::Flex,
18046 placement: BlockPlacement::Below(range.start),
18047 height: Some(1),
18048 render: Arc::new({
18049 let rename_editor = rename_editor.clone();
18050 move |cx: &mut BlockContext| {
18051 let mut text_style = cx.editor_style.text.clone();
18052 if let Some(highlight_style) = old_highlight_id
18053 .and_then(|h| h.style(&cx.editor_style.syntax))
18054 {
18055 text_style = text_style.highlight(highlight_style);
18056 }
18057 div()
18058 .block_mouse_except_scroll()
18059 .pl(cx.anchor_x)
18060 .child(EditorElement::new(
18061 &rename_editor,
18062 EditorStyle {
18063 background: cx.theme().system().transparent,
18064 local_player: cx.editor_style.local_player,
18065 text: text_style,
18066 scrollbar_width: cx.editor_style.scrollbar_width,
18067 syntax: cx.editor_style.syntax.clone(),
18068 status: cx.editor_style.status.clone(),
18069 inlay_hints_style: HighlightStyle {
18070 font_weight: Some(FontWeight::BOLD),
18071 ..make_inlay_hints_style(cx.app)
18072 },
18073 edit_prediction_styles: make_suggestion_styles(
18074 cx.app,
18075 ),
18076 ..EditorStyle::default()
18077 },
18078 ))
18079 .into_any_element()
18080 }
18081 }),
18082 priority: 0,
18083 }],
18084 Some(Autoscroll::fit()),
18085 cx,
18086 )[0];
18087 this.pending_rename = Some(RenameState {
18088 range,
18089 old_name,
18090 editor: rename_editor,
18091 block_id,
18092 });
18093 })?;
18094 }
18095
18096 Ok(())
18097 }))
18098 }
18099
18100 pub fn confirm_rename(
18101 &mut self,
18102 _: &ConfirmRename,
18103 window: &mut Window,
18104 cx: &mut Context<Self>,
18105 ) -> Option<Task<Result<()>>> {
18106 let rename = self.take_rename(false, window, cx)?;
18107 let workspace = self.workspace()?.downgrade();
18108 let (buffer, start) = self
18109 .buffer
18110 .read(cx)
18111 .text_anchor_for_position(rename.range.start, cx)?;
18112 let (end_buffer, _) = self
18113 .buffer
18114 .read(cx)
18115 .text_anchor_for_position(rename.range.end, cx)?;
18116 if buffer != end_buffer {
18117 return None;
18118 }
18119
18120 let old_name = rename.old_name;
18121 let new_name = rename.editor.read(cx).text(cx);
18122
18123 let rename = self.semantics_provider.as_ref()?.perform_rename(
18124 &buffer,
18125 start,
18126 new_name.clone(),
18127 cx,
18128 )?;
18129
18130 Some(cx.spawn_in(window, async move |editor, cx| {
18131 let project_transaction = rename.await?;
18132 Self::open_project_transaction(
18133 &editor,
18134 workspace,
18135 project_transaction,
18136 format!("Rename: {} → {}", old_name, new_name),
18137 cx,
18138 )
18139 .await?;
18140
18141 editor.update(cx, |editor, cx| {
18142 editor.refresh_document_highlights(cx);
18143 })?;
18144 Ok(())
18145 }))
18146 }
18147
18148 fn take_rename(
18149 &mut self,
18150 moving_cursor: bool,
18151 window: &mut Window,
18152 cx: &mut Context<Self>,
18153 ) -> Option<RenameState> {
18154 let rename = self.pending_rename.take()?;
18155 if rename.editor.focus_handle(cx).is_focused(window) {
18156 window.focus(&self.focus_handle, cx);
18157 }
18158
18159 self.remove_blocks(
18160 [rename.block_id].into_iter().collect(),
18161 Some(Autoscroll::fit()),
18162 cx,
18163 );
18164 self.clear_highlights::<Rename>(cx);
18165 self.show_local_selections = true;
18166
18167 if moving_cursor {
18168 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18169 editor
18170 .selections
18171 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18172 .head()
18173 });
18174
18175 // Update the selection to match the position of the selection inside
18176 // the rename editor.
18177 let snapshot = self.buffer.read(cx).read(cx);
18178 let rename_range = rename.range.to_offset(&snapshot);
18179 let cursor_in_editor = snapshot
18180 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18181 .min(rename_range.end);
18182 drop(snapshot);
18183
18184 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18185 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18186 });
18187 } else {
18188 self.refresh_document_highlights(cx);
18189 }
18190
18191 Some(rename)
18192 }
18193
18194 pub fn pending_rename(&self) -> Option<&RenameState> {
18195 self.pending_rename.as_ref()
18196 }
18197
18198 fn format(
18199 &mut self,
18200 _: &Format,
18201 window: &mut Window,
18202 cx: &mut Context<Self>,
18203 ) -> Option<Task<Result<()>>> {
18204 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18205
18206 let project = match &self.project {
18207 Some(project) => project.clone(),
18208 None => return None,
18209 };
18210
18211 Some(self.perform_format(
18212 project,
18213 FormatTrigger::Manual,
18214 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18215 window,
18216 cx,
18217 ))
18218 }
18219
18220 fn format_selections(
18221 &mut self,
18222 _: &FormatSelections,
18223 window: &mut Window,
18224 cx: &mut Context<Self>,
18225 ) -> Option<Task<Result<()>>> {
18226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18227
18228 let project = match &self.project {
18229 Some(project) => project.clone(),
18230 None => return None,
18231 };
18232
18233 let ranges = self
18234 .selections
18235 .all_adjusted(&self.display_snapshot(cx))
18236 .into_iter()
18237 .map(|selection| selection.range())
18238 .collect_vec();
18239
18240 Some(self.perform_format(
18241 project,
18242 FormatTrigger::Manual,
18243 FormatTarget::Ranges(ranges),
18244 window,
18245 cx,
18246 ))
18247 }
18248
18249 fn perform_format(
18250 &mut self,
18251 project: Entity<Project>,
18252 trigger: FormatTrigger,
18253 target: FormatTarget,
18254 window: &mut Window,
18255 cx: &mut Context<Self>,
18256 ) -> Task<Result<()>> {
18257 let buffer = self.buffer.clone();
18258 let (buffers, target) = match target {
18259 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18260 FormatTarget::Ranges(selection_ranges) => {
18261 let multi_buffer = buffer.read(cx);
18262 let snapshot = multi_buffer.read(cx);
18263 let mut buffers = HashSet::default();
18264 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18265 BTreeMap::new();
18266 for selection_range in selection_ranges {
18267 for (buffer, buffer_range, _) in
18268 snapshot.range_to_buffer_ranges(selection_range)
18269 {
18270 let buffer_id = buffer.remote_id();
18271 let start = buffer.anchor_before(buffer_range.start);
18272 let end = buffer.anchor_after(buffer_range.end);
18273 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18274 buffer_id_to_ranges
18275 .entry(buffer_id)
18276 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18277 .or_insert_with(|| vec![start..end]);
18278 }
18279 }
18280 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18281 }
18282 };
18283
18284 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18285 let selections_prev = transaction_id_prev
18286 .and_then(|transaction_id_prev| {
18287 // default to selections as they were after the last edit, if we have them,
18288 // instead of how they are now.
18289 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18290 // will take you back to where you made the last edit, instead of staying where you scrolled
18291 self.selection_history
18292 .transaction(transaction_id_prev)
18293 .map(|t| t.0.clone())
18294 })
18295 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18296
18297 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18298 let format = project.update(cx, |project, cx| {
18299 project.format(buffers, target, true, trigger, cx)
18300 });
18301
18302 cx.spawn_in(window, async move |editor, cx| {
18303 let transaction = futures::select_biased! {
18304 transaction = format.log_err().fuse() => transaction,
18305 () = timeout => {
18306 log::warn!("timed out waiting for formatting");
18307 None
18308 }
18309 };
18310
18311 buffer
18312 .update(cx, |buffer, cx| {
18313 if let Some(transaction) = transaction
18314 && !buffer.is_singleton()
18315 {
18316 buffer.push_transaction(&transaction.0, cx);
18317 }
18318 cx.notify();
18319 })
18320 .ok();
18321
18322 if let Some(transaction_id_now) =
18323 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18324 {
18325 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18326 if has_new_transaction {
18327 _ = editor.update(cx, |editor, _| {
18328 editor
18329 .selection_history
18330 .insert_transaction(transaction_id_now, selections_prev);
18331 });
18332 }
18333 }
18334
18335 Ok(())
18336 })
18337 }
18338
18339 fn organize_imports(
18340 &mut self,
18341 _: &OrganizeImports,
18342 window: &mut Window,
18343 cx: &mut Context<Self>,
18344 ) -> Option<Task<Result<()>>> {
18345 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18346 let project = match &self.project {
18347 Some(project) => project.clone(),
18348 None => return None,
18349 };
18350 Some(self.perform_code_action_kind(
18351 project,
18352 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18353 window,
18354 cx,
18355 ))
18356 }
18357
18358 fn perform_code_action_kind(
18359 &mut self,
18360 project: Entity<Project>,
18361 kind: CodeActionKind,
18362 window: &mut Window,
18363 cx: &mut Context<Self>,
18364 ) -> Task<Result<()>> {
18365 let buffer = self.buffer.clone();
18366 let buffers = buffer.read(cx).all_buffers();
18367 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18368 let apply_action = project.update(cx, |project, cx| {
18369 project.apply_code_action_kind(buffers, kind, true, cx)
18370 });
18371 cx.spawn_in(window, async move |_, cx| {
18372 let transaction = futures::select_biased! {
18373 () = timeout => {
18374 log::warn!("timed out waiting for executing code action");
18375 None
18376 }
18377 transaction = apply_action.log_err().fuse() => transaction,
18378 };
18379 buffer
18380 .update(cx, |buffer, cx| {
18381 // check if we need this
18382 if let Some(transaction) = transaction
18383 && !buffer.is_singleton()
18384 {
18385 buffer.push_transaction(&transaction.0, cx);
18386 }
18387 cx.notify();
18388 })
18389 .ok();
18390 Ok(())
18391 })
18392 }
18393
18394 pub fn restart_language_server(
18395 &mut self,
18396 _: &RestartLanguageServer,
18397 _: &mut Window,
18398 cx: &mut Context<Self>,
18399 ) {
18400 if let Some(project) = self.project.clone() {
18401 self.buffer.update(cx, |multi_buffer, cx| {
18402 project.update(cx, |project, cx| {
18403 project.restart_language_servers_for_buffers(
18404 multi_buffer.all_buffers().into_iter().collect(),
18405 HashSet::default(),
18406 cx,
18407 );
18408 });
18409 })
18410 }
18411 }
18412
18413 pub fn stop_language_server(
18414 &mut self,
18415 _: &StopLanguageServer,
18416 _: &mut Window,
18417 cx: &mut Context<Self>,
18418 ) {
18419 if let Some(project) = self.project.clone() {
18420 self.buffer.update(cx, |multi_buffer, cx| {
18421 project.update(cx, |project, cx| {
18422 project.stop_language_servers_for_buffers(
18423 multi_buffer.all_buffers().into_iter().collect(),
18424 HashSet::default(),
18425 cx,
18426 );
18427 });
18428 });
18429 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18430 }
18431 }
18432
18433 fn cancel_language_server_work(
18434 workspace: &mut Workspace,
18435 _: &actions::CancelLanguageServerWork,
18436 _: &mut Window,
18437 cx: &mut Context<Workspace>,
18438 ) {
18439 let project = workspace.project();
18440 let buffers = workspace
18441 .active_item(cx)
18442 .and_then(|item| item.act_as::<Editor>(cx))
18443 .map_or(HashSet::default(), |editor| {
18444 editor.read(cx).buffer.read(cx).all_buffers()
18445 });
18446 project.update(cx, |project, cx| {
18447 project.cancel_language_server_work_for_buffers(buffers, cx);
18448 });
18449 }
18450
18451 fn show_character_palette(
18452 &mut self,
18453 _: &ShowCharacterPalette,
18454 window: &mut Window,
18455 _: &mut Context<Self>,
18456 ) {
18457 window.show_character_palette();
18458 }
18459
18460 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18461 if !self.diagnostics_enabled() {
18462 return;
18463 }
18464
18465 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18466 let buffer = self.buffer.read(cx).snapshot(cx);
18467 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18468 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18469 let is_valid = buffer
18470 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18471 .any(|entry| {
18472 entry.diagnostic.is_primary
18473 && !entry.range.is_empty()
18474 && entry.range.start == primary_range_start
18475 && entry.diagnostic.message == active_diagnostics.active_message
18476 });
18477
18478 if !is_valid {
18479 self.dismiss_diagnostics(cx);
18480 }
18481 }
18482 }
18483
18484 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18485 match &self.active_diagnostics {
18486 ActiveDiagnostic::Group(group) => Some(group),
18487 _ => None,
18488 }
18489 }
18490
18491 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18492 if !self.diagnostics_enabled() {
18493 return;
18494 }
18495 self.dismiss_diagnostics(cx);
18496 self.active_diagnostics = ActiveDiagnostic::All;
18497 }
18498
18499 fn activate_diagnostics(
18500 &mut self,
18501 buffer_id: BufferId,
18502 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18503 window: &mut Window,
18504 cx: &mut Context<Self>,
18505 ) {
18506 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18507 return;
18508 }
18509 self.dismiss_diagnostics(cx);
18510 let snapshot = self.snapshot(window, cx);
18511 let buffer = self.buffer.read(cx).snapshot(cx);
18512 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18513 return;
18514 };
18515
18516 let diagnostic_group = buffer
18517 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18518 .collect::<Vec<_>>();
18519
18520 let language_registry = self
18521 .project()
18522 .map(|project| project.read(cx).languages().clone());
18523
18524 let blocks = renderer.render_group(
18525 diagnostic_group,
18526 buffer_id,
18527 snapshot,
18528 cx.weak_entity(),
18529 language_registry,
18530 cx,
18531 );
18532
18533 let blocks = self.display_map.update(cx, |display_map, cx| {
18534 display_map.insert_blocks(blocks, cx).into_iter().collect()
18535 });
18536 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18537 active_range: buffer.anchor_before(diagnostic.range.start)
18538 ..buffer.anchor_after(diagnostic.range.end),
18539 active_message: diagnostic.diagnostic.message.clone(),
18540 group_id: diagnostic.diagnostic.group_id,
18541 blocks,
18542 });
18543 cx.notify();
18544 }
18545
18546 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18547 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18548 return;
18549 };
18550
18551 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18552 if let ActiveDiagnostic::Group(group) = prev {
18553 self.display_map.update(cx, |display_map, cx| {
18554 display_map.remove_blocks(group.blocks, cx);
18555 });
18556 cx.notify();
18557 }
18558 }
18559
18560 /// Disable inline diagnostics rendering for this editor.
18561 pub fn disable_inline_diagnostics(&mut self) {
18562 self.inline_diagnostics_enabled = false;
18563 self.inline_diagnostics_update = Task::ready(());
18564 self.inline_diagnostics.clear();
18565 }
18566
18567 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18568 self.diagnostics_enabled = false;
18569 self.dismiss_diagnostics(cx);
18570 self.inline_diagnostics_update = Task::ready(());
18571 self.inline_diagnostics.clear();
18572 }
18573
18574 pub fn disable_word_completions(&mut self) {
18575 self.word_completions_enabled = false;
18576 }
18577
18578 pub fn diagnostics_enabled(&self) -> bool {
18579 self.diagnostics_enabled && self.mode.is_full()
18580 }
18581
18582 pub fn inline_diagnostics_enabled(&self) -> bool {
18583 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18584 }
18585
18586 pub fn show_inline_diagnostics(&self) -> bool {
18587 self.show_inline_diagnostics
18588 }
18589
18590 pub fn toggle_inline_diagnostics(
18591 &mut self,
18592 _: &ToggleInlineDiagnostics,
18593 window: &mut Window,
18594 cx: &mut Context<Editor>,
18595 ) {
18596 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18597 self.refresh_inline_diagnostics(false, window, cx);
18598 }
18599
18600 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18601 self.diagnostics_max_severity = severity;
18602 self.display_map.update(cx, |display_map, _| {
18603 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18604 });
18605 }
18606
18607 pub fn toggle_diagnostics(
18608 &mut self,
18609 _: &ToggleDiagnostics,
18610 window: &mut Window,
18611 cx: &mut Context<Editor>,
18612 ) {
18613 if !self.diagnostics_enabled() {
18614 return;
18615 }
18616
18617 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18618 EditorSettings::get_global(cx)
18619 .diagnostics_max_severity
18620 .filter(|severity| severity != &DiagnosticSeverity::Off)
18621 .unwrap_or(DiagnosticSeverity::Hint)
18622 } else {
18623 DiagnosticSeverity::Off
18624 };
18625 self.set_max_diagnostics_severity(new_severity, cx);
18626 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18627 self.active_diagnostics = ActiveDiagnostic::None;
18628 self.inline_diagnostics_update = Task::ready(());
18629 self.inline_diagnostics.clear();
18630 } else {
18631 self.refresh_inline_diagnostics(false, window, cx);
18632 }
18633
18634 cx.notify();
18635 }
18636
18637 pub fn toggle_minimap(
18638 &mut self,
18639 _: &ToggleMinimap,
18640 window: &mut Window,
18641 cx: &mut Context<Editor>,
18642 ) {
18643 if self.supports_minimap(cx) {
18644 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18645 }
18646 }
18647
18648 fn refresh_inline_diagnostics(
18649 &mut self,
18650 debounce: bool,
18651 window: &mut Window,
18652 cx: &mut Context<Self>,
18653 ) {
18654 let max_severity = ProjectSettings::get_global(cx)
18655 .diagnostics
18656 .inline
18657 .max_severity
18658 .unwrap_or(self.diagnostics_max_severity);
18659
18660 if !self.inline_diagnostics_enabled()
18661 || !self.diagnostics_enabled()
18662 || !self.show_inline_diagnostics
18663 || max_severity == DiagnosticSeverity::Off
18664 {
18665 self.inline_diagnostics_update = Task::ready(());
18666 self.inline_diagnostics.clear();
18667 return;
18668 }
18669
18670 let debounce_ms = ProjectSettings::get_global(cx)
18671 .diagnostics
18672 .inline
18673 .update_debounce_ms;
18674 let debounce = if debounce && debounce_ms > 0 {
18675 Some(Duration::from_millis(debounce_ms))
18676 } else {
18677 None
18678 };
18679 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18680 if let Some(debounce) = debounce {
18681 cx.background_executor().timer(debounce).await;
18682 }
18683 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18684 editor
18685 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18686 .ok()
18687 }) else {
18688 return;
18689 };
18690
18691 let new_inline_diagnostics = cx
18692 .background_spawn(async move {
18693 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18694 for diagnostic_entry in
18695 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18696 {
18697 let message = diagnostic_entry
18698 .diagnostic
18699 .message
18700 .split_once('\n')
18701 .map(|(line, _)| line)
18702 .map(SharedString::new)
18703 .unwrap_or_else(|| {
18704 SharedString::new(&*diagnostic_entry.diagnostic.message)
18705 });
18706 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18707 let (Ok(i) | Err(i)) = inline_diagnostics
18708 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18709 inline_diagnostics.insert(
18710 i,
18711 (
18712 start_anchor,
18713 InlineDiagnostic {
18714 message,
18715 group_id: diagnostic_entry.diagnostic.group_id,
18716 start: diagnostic_entry.range.start.to_point(&snapshot),
18717 is_primary: diagnostic_entry.diagnostic.is_primary,
18718 severity: diagnostic_entry.diagnostic.severity,
18719 },
18720 ),
18721 );
18722 }
18723 inline_diagnostics
18724 })
18725 .await;
18726
18727 editor
18728 .update(cx, |editor, cx| {
18729 editor.inline_diagnostics = new_inline_diagnostics;
18730 cx.notify();
18731 })
18732 .ok();
18733 });
18734 }
18735
18736 fn pull_diagnostics(
18737 &mut self,
18738 buffer_id: Option<BufferId>,
18739 window: &Window,
18740 cx: &mut Context<Self>,
18741 ) -> Option<()> {
18742 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18743 return None;
18744 }
18745 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18746 .diagnostics
18747 .lsp_pull_diagnostics;
18748 if !pull_diagnostics_settings.enabled {
18749 return None;
18750 }
18751 let project = self.project()?.downgrade();
18752
18753 let mut edited_buffer_ids = HashSet::default();
18754 let mut edited_worktree_ids = HashSet::default();
18755 let edited_buffers = match buffer_id {
18756 Some(buffer_id) => {
18757 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18758 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18759 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18760 edited_worktree_ids.insert(worktree_id);
18761 vec![buffer]
18762 }
18763 None => self
18764 .buffer()
18765 .read(cx)
18766 .all_buffers()
18767 .into_iter()
18768 .filter(|buffer| {
18769 let buffer = buffer.read(cx);
18770 match buffer.file().map(|f| f.worktree_id(cx)) {
18771 Some(worktree_id) => {
18772 edited_buffer_ids.insert(buffer.remote_id());
18773 edited_worktree_ids.insert(worktree_id);
18774 true
18775 }
18776 None => false,
18777 }
18778 })
18779 .collect::<Vec<_>>(),
18780 };
18781
18782 if edited_buffers.is_empty() {
18783 self.pull_diagnostics_task = Task::ready(());
18784 self.pull_diagnostics_background_task = Task::ready(());
18785 return None;
18786 }
18787
18788 let mut already_used_buffers = HashSet::default();
18789 let related_open_buffers = self
18790 .workspace
18791 .as_ref()
18792 .and_then(|(workspace, _)| workspace.upgrade())
18793 .into_iter()
18794 .flat_map(|workspace| workspace.read(cx).panes())
18795 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18796 .filter(|editor| editor != &cx.entity())
18797 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18798 .filter(|buffer| {
18799 let buffer = buffer.read(cx);
18800 let buffer_id = buffer.remote_id();
18801 if already_used_buffers.insert(buffer_id) {
18802 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18803 return !edited_buffer_ids.contains(&buffer_id)
18804 && !edited_worktree_ids.contains(&worktree_id);
18805 }
18806 }
18807 false
18808 })
18809 .collect::<Vec<_>>();
18810
18811 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18812 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18813 if buffers.is_empty() {
18814 return Task::ready(());
18815 }
18816 let project_weak = project.clone();
18817 cx.spawn_in(window, async move |_, cx| {
18818 cx.background_executor().timer(delay).await;
18819
18820 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18821 buffers
18822 .into_iter()
18823 .filter_map(|buffer| {
18824 project_weak
18825 .update(cx, |project, cx| {
18826 project.lsp_store().update(cx, |lsp_store, cx| {
18827 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18828 })
18829 })
18830 .ok()
18831 })
18832 .collect::<FuturesUnordered<_>>()
18833 }) else {
18834 return;
18835 };
18836
18837 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18838 if let Err(e) = pull_task {
18839 log::error!("Failed to update project diagnostics: {e:#}");
18840 }
18841 }
18842 })
18843 };
18844
18845 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18846 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18847
18848 Some(())
18849 }
18850
18851 pub fn set_selections_from_remote(
18852 &mut self,
18853 selections: Vec<Selection<Anchor>>,
18854 pending_selection: Option<Selection<Anchor>>,
18855 window: &mut Window,
18856 cx: &mut Context<Self>,
18857 ) {
18858 let old_cursor_position = self.selections.newest_anchor().head();
18859 self.selections
18860 .change_with(&self.display_snapshot(cx), |s| {
18861 s.select_anchors(selections);
18862 if let Some(pending_selection) = pending_selection {
18863 s.set_pending(pending_selection, SelectMode::Character);
18864 } else {
18865 s.clear_pending();
18866 }
18867 });
18868 self.selections_did_change(
18869 false,
18870 &old_cursor_position,
18871 SelectionEffects::default(),
18872 window,
18873 cx,
18874 );
18875 }
18876
18877 pub fn transact(
18878 &mut self,
18879 window: &mut Window,
18880 cx: &mut Context<Self>,
18881 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18882 ) -> Option<TransactionId> {
18883 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18884 this.start_transaction_at(Instant::now(), window, cx);
18885 update(this, window, cx);
18886 this.end_transaction_at(Instant::now(), cx)
18887 })
18888 }
18889
18890 pub fn start_transaction_at(
18891 &mut self,
18892 now: Instant,
18893 window: &mut Window,
18894 cx: &mut Context<Self>,
18895 ) -> Option<TransactionId> {
18896 self.end_selection(window, cx);
18897 if let Some(tx_id) = self
18898 .buffer
18899 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18900 {
18901 self.selection_history
18902 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18903 cx.emit(EditorEvent::TransactionBegun {
18904 transaction_id: tx_id,
18905 });
18906 Some(tx_id)
18907 } else {
18908 None
18909 }
18910 }
18911
18912 pub fn end_transaction_at(
18913 &mut self,
18914 now: Instant,
18915 cx: &mut Context<Self>,
18916 ) -> Option<TransactionId> {
18917 if let Some(transaction_id) = self
18918 .buffer
18919 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18920 {
18921 if let Some((_, end_selections)) =
18922 self.selection_history.transaction_mut(transaction_id)
18923 {
18924 *end_selections = Some(self.selections.disjoint_anchors_arc());
18925 } else {
18926 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18927 }
18928
18929 cx.emit(EditorEvent::Edited { transaction_id });
18930 Some(transaction_id)
18931 } else {
18932 None
18933 }
18934 }
18935
18936 pub fn modify_transaction_selection_history(
18937 &mut self,
18938 transaction_id: TransactionId,
18939 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18940 ) -> bool {
18941 self.selection_history
18942 .transaction_mut(transaction_id)
18943 .map(modify)
18944 .is_some()
18945 }
18946
18947 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18948 if self.selection_mark_mode {
18949 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18950 s.move_with(|_, sel| {
18951 sel.collapse_to(sel.head(), SelectionGoal::None);
18952 });
18953 })
18954 }
18955 self.selection_mark_mode = true;
18956 cx.notify();
18957 }
18958
18959 pub fn swap_selection_ends(
18960 &mut self,
18961 _: &actions::SwapSelectionEnds,
18962 window: &mut Window,
18963 cx: &mut Context<Self>,
18964 ) {
18965 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18966 s.move_with(|_, sel| {
18967 if sel.start != sel.end {
18968 sel.reversed = !sel.reversed
18969 }
18970 });
18971 });
18972 self.request_autoscroll(Autoscroll::newest(), cx);
18973 cx.notify();
18974 }
18975
18976 pub fn toggle_focus(
18977 workspace: &mut Workspace,
18978 _: &actions::ToggleFocus,
18979 window: &mut Window,
18980 cx: &mut Context<Workspace>,
18981 ) {
18982 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18983 return;
18984 };
18985 workspace.activate_item(&item, true, true, window, cx);
18986 }
18987
18988 pub fn toggle_fold(
18989 &mut self,
18990 _: &actions::ToggleFold,
18991 window: &mut Window,
18992 cx: &mut Context<Self>,
18993 ) {
18994 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18995 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18996 let selection = self.selections.newest::<Point>(&display_map);
18997
18998 let range = if selection.is_empty() {
18999 let point = selection.head().to_display_point(&display_map);
19000 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19001 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19002 .to_point(&display_map);
19003 start..end
19004 } else {
19005 selection.range()
19006 };
19007 if display_map.folds_in_range(range).next().is_some() {
19008 self.unfold_lines(&Default::default(), window, cx)
19009 } else {
19010 self.fold(&Default::default(), window, cx)
19011 }
19012 } else {
19013 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19014 let buffer_ids: HashSet<_> = self
19015 .selections
19016 .disjoint_anchor_ranges()
19017 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19018 .collect();
19019
19020 let should_unfold = buffer_ids
19021 .iter()
19022 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19023
19024 for buffer_id in buffer_ids {
19025 if should_unfold {
19026 self.unfold_buffer(buffer_id, cx);
19027 } else {
19028 self.fold_buffer(buffer_id, cx);
19029 }
19030 }
19031 }
19032 }
19033
19034 pub fn toggle_fold_recursive(
19035 &mut self,
19036 _: &actions::ToggleFoldRecursive,
19037 window: &mut Window,
19038 cx: &mut Context<Self>,
19039 ) {
19040 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19041
19042 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19043 let range = if selection.is_empty() {
19044 let point = selection.head().to_display_point(&display_map);
19045 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19046 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19047 .to_point(&display_map);
19048 start..end
19049 } else {
19050 selection.range()
19051 };
19052 if display_map.folds_in_range(range).next().is_some() {
19053 self.unfold_recursive(&Default::default(), window, cx)
19054 } else {
19055 self.fold_recursive(&Default::default(), window, cx)
19056 }
19057 }
19058
19059 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19060 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19061 let mut to_fold = Vec::new();
19062 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19063 let selections = self.selections.all_adjusted(&display_map);
19064
19065 for selection in selections {
19066 let range = selection.range().sorted();
19067 let buffer_start_row = range.start.row;
19068
19069 if range.start.row != range.end.row {
19070 let mut found = false;
19071 let mut row = range.start.row;
19072 while row <= range.end.row {
19073 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19074 {
19075 found = true;
19076 row = crease.range().end.row + 1;
19077 to_fold.push(crease);
19078 } else {
19079 row += 1
19080 }
19081 }
19082 if found {
19083 continue;
19084 }
19085 }
19086
19087 for row in (0..=range.start.row).rev() {
19088 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19089 && crease.range().end.row >= buffer_start_row
19090 {
19091 to_fold.push(crease);
19092 if row <= range.start.row {
19093 break;
19094 }
19095 }
19096 }
19097 }
19098
19099 self.fold_creases(to_fold, true, window, cx);
19100 } else {
19101 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19102 let buffer_ids = self
19103 .selections
19104 .disjoint_anchor_ranges()
19105 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19106 .collect::<HashSet<_>>();
19107 for buffer_id in buffer_ids {
19108 self.fold_buffer(buffer_id, cx);
19109 }
19110 }
19111 }
19112
19113 pub fn toggle_fold_all(
19114 &mut self,
19115 _: &actions::ToggleFoldAll,
19116 window: &mut Window,
19117 cx: &mut Context<Self>,
19118 ) {
19119 if self.buffer.read(cx).is_singleton() {
19120 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19121 let has_folds = display_map
19122 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19123 .next()
19124 .is_some();
19125
19126 if has_folds {
19127 self.unfold_all(&actions::UnfoldAll, window, cx);
19128 } else {
19129 self.fold_all(&actions::FoldAll, window, cx);
19130 }
19131 } else {
19132 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19133 let should_unfold = buffer_ids
19134 .iter()
19135 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19136
19137 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19138 editor
19139 .update_in(cx, |editor, _, cx| {
19140 for buffer_id in buffer_ids {
19141 if should_unfold {
19142 editor.unfold_buffer(buffer_id, cx);
19143 } else {
19144 editor.fold_buffer(buffer_id, cx);
19145 }
19146 }
19147 })
19148 .ok();
19149 });
19150 }
19151 }
19152
19153 fn fold_at_level(
19154 &mut self,
19155 fold_at: &FoldAtLevel,
19156 window: &mut Window,
19157 cx: &mut Context<Self>,
19158 ) {
19159 if !self.buffer.read(cx).is_singleton() {
19160 return;
19161 }
19162
19163 let fold_at_level = fold_at.0;
19164 let snapshot = self.buffer.read(cx).snapshot(cx);
19165 let mut to_fold = Vec::new();
19166 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19167
19168 let row_ranges_to_keep: Vec<Range<u32>> = self
19169 .selections
19170 .all::<Point>(&self.display_snapshot(cx))
19171 .into_iter()
19172 .map(|sel| sel.start.row..sel.end.row)
19173 .collect();
19174
19175 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19176 while start_row < end_row {
19177 match self
19178 .snapshot(window, cx)
19179 .crease_for_buffer_row(MultiBufferRow(start_row))
19180 {
19181 Some(crease) => {
19182 let nested_start_row = crease.range().start.row + 1;
19183 let nested_end_row = crease.range().end.row;
19184
19185 if current_level < fold_at_level {
19186 stack.push((nested_start_row, nested_end_row, current_level + 1));
19187 } else if current_level == fold_at_level {
19188 // Fold iff there is no selection completely contained within the fold region
19189 if !row_ranges_to_keep.iter().any(|selection| {
19190 selection.end >= nested_start_row
19191 && selection.start <= nested_end_row
19192 }) {
19193 to_fold.push(crease);
19194 }
19195 }
19196
19197 start_row = nested_end_row + 1;
19198 }
19199 None => start_row += 1,
19200 }
19201 }
19202 }
19203
19204 self.fold_creases(to_fold, true, window, cx);
19205 }
19206
19207 pub fn fold_at_level_1(
19208 &mut self,
19209 _: &actions::FoldAtLevel1,
19210 window: &mut Window,
19211 cx: &mut Context<Self>,
19212 ) {
19213 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19214 }
19215
19216 pub fn fold_at_level_2(
19217 &mut self,
19218 _: &actions::FoldAtLevel2,
19219 window: &mut Window,
19220 cx: &mut Context<Self>,
19221 ) {
19222 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19223 }
19224
19225 pub fn fold_at_level_3(
19226 &mut self,
19227 _: &actions::FoldAtLevel3,
19228 window: &mut Window,
19229 cx: &mut Context<Self>,
19230 ) {
19231 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19232 }
19233
19234 pub fn fold_at_level_4(
19235 &mut self,
19236 _: &actions::FoldAtLevel4,
19237 window: &mut Window,
19238 cx: &mut Context<Self>,
19239 ) {
19240 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19241 }
19242
19243 pub fn fold_at_level_5(
19244 &mut self,
19245 _: &actions::FoldAtLevel5,
19246 window: &mut Window,
19247 cx: &mut Context<Self>,
19248 ) {
19249 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19250 }
19251
19252 pub fn fold_at_level_6(
19253 &mut self,
19254 _: &actions::FoldAtLevel6,
19255 window: &mut Window,
19256 cx: &mut Context<Self>,
19257 ) {
19258 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19259 }
19260
19261 pub fn fold_at_level_7(
19262 &mut self,
19263 _: &actions::FoldAtLevel7,
19264 window: &mut Window,
19265 cx: &mut Context<Self>,
19266 ) {
19267 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19268 }
19269
19270 pub fn fold_at_level_8(
19271 &mut self,
19272 _: &actions::FoldAtLevel8,
19273 window: &mut Window,
19274 cx: &mut Context<Self>,
19275 ) {
19276 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19277 }
19278
19279 pub fn fold_at_level_9(
19280 &mut self,
19281 _: &actions::FoldAtLevel9,
19282 window: &mut Window,
19283 cx: &mut Context<Self>,
19284 ) {
19285 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19286 }
19287
19288 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19289 if self.buffer.read(cx).is_singleton() {
19290 let mut fold_ranges = Vec::new();
19291 let snapshot = self.buffer.read(cx).snapshot(cx);
19292
19293 for row in 0..snapshot.max_row().0 {
19294 if let Some(foldable_range) = self
19295 .snapshot(window, cx)
19296 .crease_for_buffer_row(MultiBufferRow(row))
19297 {
19298 fold_ranges.push(foldable_range);
19299 }
19300 }
19301
19302 self.fold_creases(fold_ranges, true, window, cx);
19303 } else {
19304 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19305 editor
19306 .update_in(cx, |editor, _, cx| {
19307 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19308 editor.fold_buffer(buffer_id, cx);
19309 }
19310 })
19311 .ok();
19312 });
19313 }
19314 }
19315
19316 pub fn fold_function_bodies(
19317 &mut self,
19318 _: &actions::FoldFunctionBodies,
19319 window: &mut Window,
19320 cx: &mut Context<Self>,
19321 ) {
19322 let snapshot = self.buffer.read(cx).snapshot(cx);
19323
19324 let ranges = snapshot
19325 .text_object_ranges(
19326 MultiBufferOffset(0)..snapshot.len(),
19327 TreeSitterOptions::default(),
19328 )
19329 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19330 .collect::<Vec<_>>();
19331
19332 let creases = ranges
19333 .into_iter()
19334 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19335 .collect();
19336
19337 self.fold_creases(creases, true, window, cx);
19338 }
19339
19340 pub fn fold_recursive(
19341 &mut self,
19342 _: &actions::FoldRecursive,
19343 window: &mut Window,
19344 cx: &mut Context<Self>,
19345 ) {
19346 let mut to_fold = Vec::new();
19347 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19348 let selections = self.selections.all_adjusted(&display_map);
19349
19350 for selection in selections {
19351 let range = selection.range().sorted();
19352 let buffer_start_row = range.start.row;
19353
19354 if range.start.row != range.end.row {
19355 let mut found = false;
19356 for row in range.start.row..=range.end.row {
19357 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19358 found = true;
19359 to_fold.push(crease);
19360 }
19361 }
19362 if found {
19363 continue;
19364 }
19365 }
19366
19367 for row in (0..=range.start.row).rev() {
19368 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19369 if crease.range().end.row >= buffer_start_row {
19370 to_fold.push(crease);
19371 } else {
19372 break;
19373 }
19374 }
19375 }
19376 }
19377
19378 self.fold_creases(to_fold, true, window, cx);
19379 }
19380
19381 pub fn fold_at(
19382 &mut self,
19383 buffer_row: MultiBufferRow,
19384 window: &mut Window,
19385 cx: &mut Context<Self>,
19386 ) {
19387 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19388
19389 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19390 let autoscroll = self
19391 .selections
19392 .all::<Point>(&display_map)
19393 .iter()
19394 .any(|selection| crease.range().overlaps(&selection.range()));
19395
19396 self.fold_creases(vec![crease], autoscroll, window, cx);
19397 }
19398 }
19399
19400 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19401 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19402 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19403 let buffer = display_map.buffer_snapshot();
19404 let selections = self.selections.all::<Point>(&display_map);
19405 let ranges = selections
19406 .iter()
19407 .map(|s| {
19408 let range = s.display_range(&display_map).sorted();
19409 let mut start = range.start.to_point(&display_map);
19410 let mut end = range.end.to_point(&display_map);
19411 start.column = 0;
19412 end.column = buffer.line_len(MultiBufferRow(end.row));
19413 start..end
19414 })
19415 .collect::<Vec<_>>();
19416
19417 self.unfold_ranges(&ranges, true, true, cx);
19418 } else {
19419 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19420 let buffer_ids = self
19421 .selections
19422 .disjoint_anchor_ranges()
19423 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19424 .collect::<HashSet<_>>();
19425 for buffer_id in buffer_ids {
19426 self.unfold_buffer(buffer_id, cx);
19427 }
19428 }
19429 }
19430
19431 pub fn unfold_recursive(
19432 &mut self,
19433 _: &UnfoldRecursive,
19434 _window: &mut Window,
19435 cx: &mut Context<Self>,
19436 ) {
19437 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19438 let selections = self.selections.all::<Point>(&display_map);
19439 let ranges = selections
19440 .iter()
19441 .map(|s| {
19442 let mut range = s.display_range(&display_map).sorted();
19443 *range.start.column_mut() = 0;
19444 *range.end.column_mut() = display_map.line_len(range.end.row());
19445 let start = range.start.to_point(&display_map);
19446 let end = range.end.to_point(&display_map);
19447 start..end
19448 })
19449 .collect::<Vec<_>>();
19450
19451 self.unfold_ranges(&ranges, true, true, cx);
19452 }
19453
19454 pub fn unfold_at(
19455 &mut self,
19456 buffer_row: MultiBufferRow,
19457 _window: &mut Window,
19458 cx: &mut Context<Self>,
19459 ) {
19460 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19461
19462 let intersection_range = Point::new(buffer_row.0, 0)
19463 ..Point::new(
19464 buffer_row.0,
19465 display_map.buffer_snapshot().line_len(buffer_row),
19466 );
19467
19468 let autoscroll = self
19469 .selections
19470 .all::<Point>(&display_map)
19471 .iter()
19472 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19473
19474 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19475 }
19476
19477 pub fn unfold_all(
19478 &mut self,
19479 _: &actions::UnfoldAll,
19480 _window: &mut Window,
19481 cx: &mut Context<Self>,
19482 ) {
19483 if self.buffer.read(cx).is_singleton() {
19484 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19485 self.unfold_ranges(
19486 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19487 true,
19488 true,
19489 cx,
19490 );
19491 } else {
19492 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19493 editor
19494 .update(cx, |editor, cx| {
19495 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19496 editor.unfold_buffer(buffer_id, cx);
19497 }
19498 })
19499 .ok();
19500 });
19501 }
19502 }
19503
19504 pub fn fold_selected_ranges(
19505 &mut self,
19506 _: &FoldSelectedRanges,
19507 window: &mut Window,
19508 cx: &mut Context<Self>,
19509 ) {
19510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19511 let selections = self.selections.all_adjusted(&display_map);
19512 let ranges = selections
19513 .into_iter()
19514 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19515 .collect::<Vec<_>>();
19516 self.fold_creases(ranges, true, window, cx);
19517 }
19518
19519 pub fn fold_ranges<T: ToOffset + Clone>(
19520 &mut self,
19521 ranges: Vec<Range<T>>,
19522 auto_scroll: bool,
19523 window: &mut Window,
19524 cx: &mut Context<Self>,
19525 ) {
19526 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19527 let ranges = ranges
19528 .into_iter()
19529 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19530 .collect::<Vec<_>>();
19531 self.fold_creases(ranges, auto_scroll, window, cx);
19532 }
19533
19534 pub fn fold_creases<T: ToOffset + Clone>(
19535 &mut self,
19536 creases: Vec<Crease<T>>,
19537 auto_scroll: bool,
19538 _window: &mut Window,
19539 cx: &mut Context<Self>,
19540 ) {
19541 if creases.is_empty() {
19542 return;
19543 }
19544
19545 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19546
19547 if auto_scroll {
19548 self.request_autoscroll(Autoscroll::fit(), cx);
19549 }
19550
19551 cx.notify();
19552
19553 self.scrollbar_marker_state.dirty = true;
19554 self.folds_did_change(cx);
19555 }
19556
19557 /// Removes any folds whose ranges intersect any of the given ranges.
19558 pub fn unfold_ranges<T: ToOffset + Clone>(
19559 &mut self,
19560 ranges: &[Range<T>],
19561 inclusive: bool,
19562 auto_scroll: bool,
19563 cx: &mut Context<Self>,
19564 ) {
19565 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19566 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19567 });
19568 self.folds_did_change(cx);
19569 }
19570
19571 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19572 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19573 return;
19574 }
19575
19576 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19577 self.display_map.update(cx, |display_map, cx| {
19578 display_map.fold_buffers([buffer_id], cx)
19579 });
19580
19581 let snapshot = self.display_snapshot(cx);
19582 self.selections.change_with(&snapshot, |selections| {
19583 selections.remove_selections_from_buffer(buffer_id);
19584 });
19585
19586 cx.emit(EditorEvent::BufferFoldToggled {
19587 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19588 folded: true,
19589 });
19590 cx.notify();
19591 }
19592
19593 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19594 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19595 return;
19596 }
19597 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19598 self.display_map.update(cx, |display_map, cx| {
19599 display_map.unfold_buffers([buffer_id], cx);
19600 });
19601 cx.emit(EditorEvent::BufferFoldToggled {
19602 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19603 folded: false,
19604 });
19605 cx.notify();
19606 }
19607
19608 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19609 self.display_map.read(cx).is_buffer_folded(buffer)
19610 }
19611
19612 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19613 self.display_map.read(cx).folded_buffers()
19614 }
19615
19616 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19617 self.display_map.update(cx, |display_map, cx| {
19618 display_map.disable_header_for_buffer(buffer_id, cx);
19619 });
19620 cx.notify();
19621 }
19622
19623 /// Removes any folds with the given ranges.
19624 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19625 &mut self,
19626 ranges: &[Range<T>],
19627 type_id: TypeId,
19628 auto_scroll: bool,
19629 cx: &mut Context<Self>,
19630 ) {
19631 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19632 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19633 });
19634 self.folds_did_change(cx);
19635 }
19636
19637 fn remove_folds_with<T: ToOffset + Clone>(
19638 &mut self,
19639 ranges: &[Range<T>],
19640 auto_scroll: bool,
19641 cx: &mut Context<Self>,
19642 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19643 ) {
19644 if ranges.is_empty() {
19645 return;
19646 }
19647
19648 let mut buffers_affected = HashSet::default();
19649 let multi_buffer = self.buffer().read(cx);
19650 for range in ranges {
19651 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19652 buffers_affected.insert(buffer.read(cx).remote_id());
19653 };
19654 }
19655
19656 self.display_map.update(cx, update);
19657
19658 if auto_scroll {
19659 self.request_autoscroll(Autoscroll::fit(), cx);
19660 }
19661
19662 cx.notify();
19663 self.scrollbar_marker_state.dirty = true;
19664 self.active_indent_guides_state.dirty = true;
19665 }
19666
19667 pub fn update_renderer_widths(
19668 &mut self,
19669 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19670 cx: &mut Context<Self>,
19671 ) -> bool {
19672 self.display_map
19673 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19674 }
19675
19676 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19677 self.display_map.read(cx).fold_placeholder.clone()
19678 }
19679
19680 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19681 self.use_base_text_line_numbers = show;
19682 }
19683
19684 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19685 self.buffer.update(cx, |buffer, cx| {
19686 buffer.set_all_diff_hunks_expanded(cx);
19687 });
19688 }
19689
19690 pub fn expand_all_diff_hunks(
19691 &mut self,
19692 _: &ExpandAllDiffHunks,
19693 _window: &mut Window,
19694 cx: &mut Context<Self>,
19695 ) {
19696 self.buffer.update(cx, |buffer, cx| {
19697 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19698 });
19699 }
19700
19701 pub fn collapse_all_diff_hunks(
19702 &mut self,
19703 _: &CollapseAllDiffHunks,
19704 _window: &mut Window,
19705 cx: &mut Context<Self>,
19706 ) {
19707 self.buffer.update(cx, |buffer, cx| {
19708 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19709 });
19710 }
19711
19712 pub fn toggle_selected_diff_hunks(
19713 &mut self,
19714 _: &ToggleSelectedDiffHunks,
19715 _window: &mut Window,
19716 cx: &mut Context<Self>,
19717 ) {
19718 let ranges: Vec<_> = self
19719 .selections
19720 .disjoint_anchors()
19721 .iter()
19722 .map(|s| s.range())
19723 .collect();
19724 self.toggle_diff_hunks_in_ranges(ranges, cx);
19725 }
19726
19727 pub fn diff_hunks_in_ranges<'a>(
19728 &'a self,
19729 ranges: &'a [Range<Anchor>],
19730 buffer: &'a MultiBufferSnapshot,
19731 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19732 ranges.iter().flat_map(move |range| {
19733 let end_excerpt_id = range.end.excerpt_id;
19734 let range = range.to_point(buffer);
19735 let mut peek_end = range.end;
19736 if range.end.row < buffer.max_row().0 {
19737 peek_end = Point::new(range.end.row + 1, 0);
19738 }
19739 buffer
19740 .diff_hunks_in_range(range.start..peek_end)
19741 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19742 })
19743 }
19744
19745 pub fn has_stageable_diff_hunks_in_ranges(
19746 &self,
19747 ranges: &[Range<Anchor>],
19748 snapshot: &MultiBufferSnapshot,
19749 ) -> bool {
19750 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19751 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19752 }
19753
19754 pub fn toggle_staged_selected_diff_hunks(
19755 &mut self,
19756 _: &::git::ToggleStaged,
19757 _: &mut Window,
19758 cx: &mut Context<Self>,
19759 ) {
19760 let snapshot = self.buffer.read(cx).snapshot(cx);
19761 let ranges: Vec<_> = self
19762 .selections
19763 .disjoint_anchors()
19764 .iter()
19765 .map(|s| s.range())
19766 .collect();
19767 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19768 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19769 }
19770
19771 pub fn set_render_diff_hunk_controls(
19772 &mut self,
19773 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19774 cx: &mut Context<Self>,
19775 ) {
19776 self.render_diff_hunk_controls = render_diff_hunk_controls;
19777 cx.notify();
19778 }
19779
19780 pub fn stage_and_next(
19781 &mut self,
19782 _: &::git::StageAndNext,
19783 window: &mut Window,
19784 cx: &mut Context<Self>,
19785 ) {
19786 self.do_stage_or_unstage_and_next(true, window, cx);
19787 }
19788
19789 pub fn unstage_and_next(
19790 &mut self,
19791 _: &::git::UnstageAndNext,
19792 window: &mut Window,
19793 cx: &mut Context<Self>,
19794 ) {
19795 self.do_stage_or_unstage_and_next(false, window, cx);
19796 }
19797
19798 pub fn stage_or_unstage_diff_hunks(
19799 &mut self,
19800 stage: bool,
19801 ranges: Vec<Range<Anchor>>,
19802 cx: &mut Context<Self>,
19803 ) {
19804 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19805 cx.spawn(async move |this, cx| {
19806 task.await?;
19807 this.update(cx, |this, cx| {
19808 let snapshot = this.buffer.read(cx).snapshot(cx);
19809 let chunk_by = this
19810 .diff_hunks_in_ranges(&ranges, &snapshot)
19811 .chunk_by(|hunk| hunk.buffer_id);
19812 for (buffer_id, hunks) in &chunk_by {
19813 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19814 }
19815 })
19816 })
19817 .detach_and_log_err(cx);
19818 }
19819
19820 fn save_buffers_for_ranges_if_needed(
19821 &mut self,
19822 ranges: &[Range<Anchor>],
19823 cx: &mut Context<Editor>,
19824 ) -> Task<Result<()>> {
19825 let multibuffer = self.buffer.read(cx);
19826 let snapshot = multibuffer.read(cx);
19827 let buffer_ids: HashSet<_> = ranges
19828 .iter()
19829 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19830 .collect();
19831 drop(snapshot);
19832
19833 let mut buffers = HashSet::default();
19834 for buffer_id in buffer_ids {
19835 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19836 let buffer = buffer_entity.read(cx);
19837 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19838 {
19839 buffers.insert(buffer_entity);
19840 }
19841 }
19842 }
19843
19844 if let Some(project) = &self.project {
19845 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19846 } else {
19847 Task::ready(Ok(()))
19848 }
19849 }
19850
19851 fn do_stage_or_unstage_and_next(
19852 &mut self,
19853 stage: bool,
19854 window: &mut Window,
19855 cx: &mut Context<Self>,
19856 ) {
19857 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19858
19859 if ranges.iter().any(|range| range.start != range.end) {
19860 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19861 return;
19862 }
19863
19864 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19865 let snapshot = self.snapshot(window, cx);
19866 let position = self
19867 .selections
19868 .newest::<Point>(&snapshot.display_snapshot)
19869 .head();
19870 let mut row = snapshot
19871 .buffer_snapshot()
19872 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19873 .find(|hunk| hunk.row_range.start.0 > position.row)
19874 .map(|hunk| hunk.row_range.start);
19875
19876 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19877 // Outside of the project diff editor, wrap around to the beginning.
19878 if !all_diff_hunks_expanded {
19879 row = row.or_else(|| {
19880 snapshot
19881 .buffer_snapshot()
19882 .diff_hunks_in_range(Point::zero()..position)
19883 .find(|hunk| hunk.row_range.end.0 < position.row)
19884 .map(|hunk| hunk.row_range.start)
19885 });
19886 }
19887
19888 if let Some(row) = row {
19889 let destination = Point::new(row.0, 0);
19890 let autoscroll = Autoscroll::center();
19891
19892 self.unfold_ranges(&[destination..destination], false, false, cx);
19893 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19894 s.select_ranges([destination..destination]);
19895 });
19896 }
19897 }
19898
19899 fn do_stage_or_unstage(
19900 &self,
19901 stage: bool,
19902 buffer_id: BufferId,
19903 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19904 cx: &mut App,
19905 ) -> Option<()> {
19906 let project = self.project()?;
19907 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19908 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19909 let buffer_snapshot = buffer.read(cx).snapshot();
19910 let file_exists = buffer_snapshot
19911 .file()
19912 .is_some_and(|file| file.disk_state().exists());
19913 diff.update(cx, |diff, cx| {
19914 diff.stage_or_unstage_hunks(
19915 stage,
19916 &hunks
19917 .map(|hunk| buffer_diff::DiffHunk {
19918 buffer_range: hunk.buffer_range,
19919 // We don't need to pass in word diffs here because they're only used for rendering and
19920 // this function changes internal state
19921 base_word_diffs: Vec::default(),
19922 buffer_word_diffs: Vec::default(),
19923 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19924 ..hunk.diff_base_byte_range.end.0,
19925 secondary_status: hunk.secondary_status,
19926 range: Point::zero()..Point::zero(), // unused
19927 })
19928 .collect::<Vec<_>>(),
19929 &buffer_snapshot,
19930 file_exists,
19931 cx,
19932 )
19933 });
19934 None
19935 }
19936
19937 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19938 let ranges: Vec<_> = self
19939 .selections
19940 .disjoint_anchors()
19941 .iter()
19942 .map(|s| s.range())
19943 .collect();
19944 self.buffer
19945 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19946 }
19947
19948 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19949 self.buffer.update(cx, |buffer, cx| {
19950 let ranges = vec![Anchor::min()..Anchor::max()];
19951 if !buffer.all_diff_hunks_expanded()
19952 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19953 {
19954 buffer.collapse_diff_hunks(ranges, cx);
19955 true
19956 } else {
19957 false
19958 }
19959 })
19960 }
19961
19962 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19963 if self.buffer.read(cx).all_diff_hunks_expanded() {
19964 return true;
19965 }
19966 let ranges = vec![Anchor::min()..Anchor::max()];
19967 self.buffer
19968 .read(cx)
19969 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19970 }
19971
19972 fn toggle_diff_hunks_in_ranges(
19973 &mut self,
19974 ranges: Vec<Range<Anchor>>,
19975 cx: &mut Context<Editor>,
19976 ) {
19977 self.buffer.update(cx, |buffer, cx| {
19978 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19979 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19980 })
19981 }
19982
19983 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19984 self.buffer.update(cx, |buffer, cx| {
19985 let snapshot = buffer.snapshot(cx);
19986 let excerpt_id = range.end.excerpt_id;
19987 let point_range = range.to_point(&snapshot);
19988 let expand = !buffer.single_hunk_is_expanded(range, cx);
19989 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19990 })
19991 }
19992
19993 pub(crate) fn apply_all_diff_hunks(
19994 &mut self,
19995 _: &ApplyAllDiffHunks,
19996 window: &mut Window,
19997 cx: &mut Context<Self>,
19998 ) {
19999 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20000
20001 let buffers = self.buffer.read(cx).all_buffers();
20002 for branch_buffer in buffers {
20003 branch_buffer.update(cx, |branch_buffer, cx| {
20004 branch_buffer.merge_into_base(Vec::new(), cx);
20005 });
20006 }
20007
20008 if let Some(project) = self.project.clone() {
20009 self.save(
20010 SaveOptions {
20011 format: true,
20012 autosave: false,
20013 },
20014 project,
20015 window,
20016 cx,
20017 )
20018 .detach_and_log_err(cx);
20019 }
20020 }
20021
20022 pub(crate) fn apply_selected_diff_hunks(
20023 &mut self,
20024 _: &ApplyDiffHunk,
20025 window: &mut Window,
20026 cx: &mut Context<Self>,
20027 ) {
20028 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20029 let snapshot = self.snapshot(window, cx);
20030 let hunks = snapshot.hunks_for_ranges(
20031 self.selections
20032 .all(&snapshot.display_snapshot)
20033 .into_iter()
20034 .map(|selection| selection.range()),
20035 );
20036 let mut ranges_by_buffer = HashMap::default();
20037 self.transact(window, cx, |editor, _window, cx| {
20038 for hunk in hunks {
20039 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20040 ranges_by_buffer
20041 .entry(buffer.clone())
20042 .or_insert_with(Vec::new)
20043 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20044 }
20045 }
20046
20047 for (buffer, ranges) in ranges_by_buffer {
20048 buffer.update(cx, |buffer, cx| {
20049 buffer.merge_into_base(ranges, cx);
20050 });
20051 }
20052 });
20053
20054 if let Some(project) = self.project.clone() {
20055 self.save(
20056 SaveOptions {
20057 format: true,
20058 autosave: false,
20059 },
20060 project,
20061 window,
20062 cx,
20063 )
20064 .detach_and_log_err(cx);
20065 }
20066 }
20067
20068 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20069 if hovered != self.gutter_hovered {
20070 self.gutter_hovered = hovered;
20071 cx.notify();
20072 }
20073 }
20074
20075 pub fn insert_blocks(
20076 &mut self,
20077 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20078 autoscroll: Option<Autoscroll>,
20079 cx: &mut Context<Self>,
20080 ) -> Vec<CustomBlockId> {
20081 let blocks = self
20082 .display_map
20083 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20084 if let Some(autoscroll) = autoscroll {
20085 self.request_autoscroll(autoscroll, cx);
20086 }
20087 cx.notify();
20088 blocks
20089 }
20090
20091 pub fn resize_blocks(
20092 &mut self,
20093 heights: HashMap<CustomBlockId, u32>,
20094 autoscroll: Option<Autoscroll>,
20095 cx: &mut Context<Self>,
20096 ) {
20097 self.display_map
20098 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20099 if let Some(autoscroll) = autoscroll {
20100 self.request_autoscroll(autoscroll, cx);
20101 }
20102 cx.notify();
20103 }
20104
20105 pub fn replace_blocks(
20106 &mut self,
20107 renderers: HashMap<CustomBlockId, RenderBlock>,
20108 autoscroll: Option<Autoscroll>,
20109 cx: &mut Context<Self>,
20110 ) {
20111 self.display_map
20112 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20113 if let Some(autoscroll) = autoscroll {
20114 self.request_autoscroll(autoscroll, cx);
20115 }
20116 cx.notify();
20117 }
20118
20119 pub fn remove_blocks(
20120 &mut self,
20121 block_ids: HashSet<CustomBlockId>,
20122 autoscroll: Option<Autoscroll>,
20123 cx: &mut Context<Self>,
20124 ) {
20125 self.display_map.update(cx, |display_map, cx| {
20126 display_map.remove_blocks(block_ids, cx)
20127 });
20128 if let Some(autoscroll) = autoscroll {
20129 self.request_autoscroll(autoscroll, cx);
20130 }
20131 cx.notify();
20132 }
20133
20134 pub fn row_for_block(
20135 &self,
20136 block_id: CustomBlockId,
20137 cx: &mut Context<Self>,
20138 ) -> Option<DisplayRow> {
20139 self.display_map
20140 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20141 }
20142
20143 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20144 self.focused_block = Some(focused_block);
20145 }
20146
20147 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20148 self.focused_block.take()
20149 }
20150
20151 pub fn insert_creases(
20152 &mut self,
20153 creases: impl IntoIterator<Item = Crease<Anchor>>,
20154 cx: &mut Context<Self>,
20155 ) -> Vec<CreaseId> {
20156 self.display_map
20157 .update(cx, |map, cx| map.insert_creases(creases, cx))
20158 }
20159
20160 pub fn remove_creases(
20161 &mut self,
20162 ids: impl IntoIterator<Item = CreaseId>,
20163 cx: &mut Context<Self>,
20164 ) -> Vec<(CreaseId, Range<Anchor>)> {
20165 self.display_map
20166 .update(cx, |map, cx| map.remove_creases(ids, cx))
20167 }
20168
20169 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20170 self.display_map
20171 .update(cx, |map, cx| map.snapshot(cx))
20172 .longest_row()
20173 }
20174
20175 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20176 self.display_map
20177 .update(cx, |map, cx| map.snapshot(cx))
20178 .max_point()
20179 }
20180
20181 pub fn text(&self, cx: &App) -> String {
20182 self.buffer.read(cx).read(cx).text()
20183 }
20184
20185 pub fn is_empty(&self, cx: &App) -> bool {
20186 self.buffer.read(cx).read(cx).is_empty()
20187 }
20188
20189 pub fn text_option(&self, cx: &App) -> Option<String> {
20190 let text = self.text(cx);
20191 let text = text.trim();
20192
20193 if text.is_empty() {
20194 return None;
20195 }
20196
20197 Some(text.to_string())
20198 }
20199
20200 pub fn set_text(
20201 &mut self,
20202 text: impl Into<Arc<str>>,
20203 window: &mut Window,
20204 cx: &mut Context<Self>,
20205 ) {
20206 self.transact(window, cx, |this, _, cx| {
20207 this.buffer
20208 .read(cx)
20209 .as_singleton()
20210 .expect("you can only call set_text on editors for singleton buffers")
20211 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20212 });
20213 }
20214
20215 pub fn display_text(&self, cx: &mut App) -> String {
20216 self.display_map
20217 .update(cx, |map, cx| map.snapshot(cx))
20218 .text()
20219 }
20220
20221 fn create_minimap(
20222 &self,
20223 minimap_settings: MinimapSettings,
20224 window: &mut Window,
20225 cx: &mut Context<Self>,
20226 ) -> Option<Entity<Self>> {
20227 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20228 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20229 }
20230
20231 fn initialize_new_minimap(
20232 &self,
20233 minimap_settings: MinimapSettings,
20234 window: &mut Window,
20235 cx: &mut Context<Self>,
20236 ) -> Entity<Self> {
20237 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20238
20239 let mut minimap = Editor::new_internal(
20240 EditorMode::Minimap {
20241 parent: cx.weak_entity(),
20242 },
20243 self.buffer.clone(),
20244 None,
20245 Some(self.display_map.clone()),
20246 window,
20247 cx,
20248 );
20249 minimap.scroll_manager.clone_state(&self.scroll_manager);
20250 minimap.set_text_style_refinement(TextStyleRefinement {
20251 font_size: Some(MINIMAP_FONT_SIZE),
20252 font_weight: Some(MINIMAP_FONT_WEIGHT),
20253 ..Default::default()
20254 });
20255 minimap.update_minimap_configuration(minimap_settings, cx);
20256 cx.new(|_| minimap)
20257 }
20258
20259 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20260 let current_line_highlight = minimap_settings
20261 .current_line_highlight
20262 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20263 self.set_current_line_highlight(Some(current_line_highlight));
20264 }
20265
20266 pub fn minimap(&self) -> Option<&Entity<Self>> {
20267 self.minimap
20268 .as_ref()
20269 .filter(|_| self.minimap_visibility.visible())
20270 }
20271
20272 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20273 let mut wrap_guides = smallvec![];
20274
20275 if self.show_wrap_guides == Some(false) {
20276 return wrap_guides;
20277 }
20278
20279 let settings = self.buffer.read(cx).language_settings(cx);
20280 if settings.show_wrap_guides {
20281 match self.soft_wrap_mode(cx) {
20282 SoftWrap::Column(soft_wrap) => {
20283 wrap_guides.push((soft_wrap as usize, true));
20284 }
20285 SoftWrap::Bounded(soft_wrap) => {
20286 wrap_guides.push((soft_wrap as usize, true));
20287 }
20288 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20289 }
20290 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20291 }
20292
20293 wrap_guides
20294 }
20295
20296 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20297 let settings = self.buffer.read(cx).language_settings(cx);
20298 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20299 match mode {
20300 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20301 SoftWrap::None
20302 }
20303 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20304 language_settings::SoftWrap::PreferredLineLength => {
20305 SoftWrap::Column(settings.preferred_line_length)
20306 }
20307 language_settings::SoftWrap::Bounded => {
20308 SoftWrap::Bounded(settings.preferred_line_length)
20309 }
20310 }
20311 }
20312
20313 pub fn set_soft_wrap_mode(
20314 &mut self,
20315 mode: language_settings::SoftWrap,
20316
20317 cx: &mut Context<Self>,
20318 ) {
20319 self.soft_wrap_mode_override = Some(mode);
20320 cx.notify();
20321 }
20322
20323 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20324 self.hard_wrap = hard_wrap;
20325 cx.notify();
20326 }
20327
20328 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20329 self.text_style_refinement = Some(style);
20330 }
20331
20332 /// called by the Element so we know what style we were most recently rendered with.
20333 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20334 // We intentionally do not inform the display map about the minimap style
20335 // so that wrapping is not recalculated and stays consistent for the editor
20336 // and its linked minimap.
20337 if !self.mode.is_minimap() {
20338 let font = style.text.font();
20339 let font_size = style.text.font_size.to_pixels(window.rem_size());
20340 let display_map = self
20341 .placeholder_display_map
20342 .as_ref()
20343 .filter(|_| self.is_empty(cx))
20344 .unwrap_or(&self.display_map);
20345
20346 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20347 }
20348 self.style = Some(style);
20349 }
20350
20351 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20352 if self.style.is_none() {
20353 self.style = Some(self.create_style(cx));
20354 }
20355 self.style.as_ref().unwrap()
20356 }
20357
20358 // Called by the element. This method is not designed to be called outside of the editor
20359 // element's layout code because it does not notify when rewrapping is computed synchronously.
20360 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20361 if self.is_empty(cx) {
20362 self.placeholder_display_map
20363 .as_ref()
20364 .map_or(false, |display_map| {
20365 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20366 })
20367 } else {
20368 self.display_map
20369 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20370 }
20371 }
20372
20373 pub fn set_soft_wrap(&mut self) {
20374 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20375 }
20376
20377 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20378 if self.soft_wrap_mode_override.is_some() {
20379 self.soft_wrap_mode_override.take();
20380 } else {
20381 let soft_wrap = match self.soft_wrap_mode(cx) {
20382 SoftWrap::GitDiff => return,
20383 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20384 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20385 language_settings::SoftWrap::None
20386 }
20387 };
20388 self.soft_wrap_mode_override = Some(soft_wrap);
20389 }
20390 cx.notify();
20391 }
20392
20393 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20394 let Some(workspace) = self.workspace() else {
20395 return;
20396 };
20397 let fs = workspace.read(cx).app_state().fs.clone();
20398 let current_show = TabBarSettings::get_global(cx).show;
20399 update_settings_file(fs, cx, move |setting, _| {
20400 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20401 });
20402 }
20403
20404 pub fn toggle_indent_guides(
20405 &mut self,
20406 _: &ToggleIndentGuides,
20407 _: &mut Window,
20408 cx: &mut Context<Self>,
20409 ) {
20410 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20411 self.buffer
20412 .read(cx)
20413 .language_settings(cx)
20414 .indent_guides
20415 .enabled
20416 });
20417 self.show_indent_guides = Some(!currently_enabled);
20418 cx.notify();
20419 }
20420
20421 fn should_show_indent_guides(&self) -> Option<bool> {
20422 self.show_indent_guides
20423 }
20424
20425 pub fn disable_indent_guides_for_buffer(
20426 &mut self,
20427 buffer_id: BufferId,
20428 cx: &mut Context<Self>,
20429 ) {
20430 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20431 cx.notify();
20432 }
20433
20434 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20435 self.buffers_with_disabled_indent_guides
20436 .contains(&buffer_id)
20437 }
20438
20439 pub fn toggle_line_numbers(
20440 &mut self,
20441 _: &ToggleLineNumbers,
20442 _: &mut Window,
20443 cx: &mut Context<Self>,
20444 ) {
20445 let mut editor_settings = EditorSettings::get_global(cx).clone();
20446 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20447 EditorSettings::override_global(editor_settings, cx);
20448 }
20449
20450 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20451 if let Some(show_line_numbers) = self.show_line_numbers {
20452 return show_line_numbers;
20453 }
20454 EditorSettings::get_global(cx).gutter.line_numbers
20455 }
20456
20457 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20458 match (
20459 self.use_relative_line_numbers,
20460 EditorSettings::get_global(cx).relative_line_numbers,
20461 ) {
20462 (None, setting) => setting,
20463 (Some(false), _) => RelativeLineNumbers::Disabled,
20464 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20465 (Some(true), _) => RelativeLineNumbers::Enabled,
20466 }
20467 }
20468
20469 pub fn toggle_relative_line_numbers(
20470 &mut self,
20471 _: &ToggleRelativeLineNumbers,
20472 _: &mut Window,
20473 cx: &mut Context<Self>,
20474 ) {
20475 let is_relative = self.relative_line_numbers(cx);
20476 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20477 }
20478
20479 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20480 self.use_relative_line_numbers = is_relative;
20481 cx.notify();
20482 }
20483
20484 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20485 self.show_gutter = show_gutter;
20486 cx.notify();
20487 }
20488
20489 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20490 self.show_scrollbars = ScrollbarAxes {
20491 horizontal: show,
20492 vertical: show,
20493 };
20494 cx.notify();
20495 }
20496
20497 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20498 self.show_scrollbars.vertical = show;
20499 cx.notify();
20500 }
20501
20502 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20503 self.show_scrollbars.horizontal = show;
20504 cx.notify();
20505 }
20506
20507 pub fn set_minimap_visibility(
20508 &mut self,
20509 minimap_visibility: MinimapVisibility,
20510 window: &mut Window,
20511 cx: &mut Context<Self>,
20512 ) {
20513 if self.minimap_visibility != minimap_visibility {
20514 if minimap_visibility.visible() && self.minimap.is_none() {
20515 let minimap_settings = EditorSettings::get_global(cx).minimap;
20516 self.minimap =
20517 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20518 }
20519 self.minimap_visibility = minimap_visibility;
20520 cx.notify();
20521 }
20522 }
20523
20524 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20525 self.set_show_scrollbars(false, cx);
20526 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20527 }
20528
20529 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20530 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20531 }
20532
20533 /// Normally the text in full mode and auto height editors is padded on the
20534 /// left side by roughly half a character width for improved hit testing.
20535 ///
20536 /// Use this method to disable this for cases where this is not wanted (e.g.
20537 /// if you want to align the editor text with some other text above or below)
20538 /// or if you want to add this padding to single-line editors.
20539 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20540 self.offset_content = offset_content;
20541 cx.notify();
20542 }
20543
20544 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20545 self.show_line_numbers = Some(show_line_numbers);
20546 cx.notify();
20547 }
20548
20549 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20550 self.disable_expand_excerpt_buttons = true;
20551 cx.notify();
20552 }
20553
20554 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20555 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20556 cx.notify();
20557 }
20558
20559 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20560 self.show_code_actions = Some(show_code_actions);
20561 cx.notify();
20562 }
20563
20564 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20565 self.show_runnables = Some(show_runnables);
20566 cx.notify();
20567 }
20568
20569 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20570 self.show_breakpoints = Some(show_breakpoints);
20571 cx.notify();
20572 }
20573
20574 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20575 if self.display_map.read(cx).masked != masked {
20576 self.display_map.update(cx, |map, _| map.masked = masked);
20577 }
20578 cx.notify()
20579 }
20580
20581 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20582 self.show_wrap_guides = Some(show_wrap_guides);
20583 cx.notify();
20584 }
20585
20586 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20587 self.show_indent_guides = Some(show_indent_guides);
20588 cx.notify();
20589 }
20590
20591 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20592 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20593 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20594 && let Some(dir) = file.abs_path(cx).parent()
20595 {
20596 return Some(dir.to_owned());
20597 }
20598 }
20599
20600 None
20601 }
20602
20603 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20604 self.active_excerpt(cx)?
20605 .1
20606 .read(cx)
20607 .file()
20608 .and_then(|f| f.as_local())
20609 }
20610
20611 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20612 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20613 let buffer = buffer.read(cx);
20614 if let Some(project_path) = buffer.project_path(cx) {
20615 let project = self.project()?.read(cx);
20616 project.absolute_path(&project_path, cx)
20617 } else {
20618 buffer
20619 .file()
20620 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20621 }
20622 })
20623 }
20624
20625 pub fn reveal_in_finder(
20626 &mut self,
20627 _: &RevealInFileManager,
20628 _window: &mut Window,
20629 cx: &mut Context<Self>,
20630 ) {
20631 if let Some(target) = self.target_file(cx) {
20632 cx.reveal_path(&target.abs_path(cx));
20633 }
20634 }
20635
20636 pub fn copy_path(
20637 &mut self,
20638 _: &zed_actions::workspace::CopyPath,
20639 _window: &mut Window,
20640 cx: &mut Context<Self>,
20641 ) {
20642 if let Some(path) = self.target_file_abs_path(cx)
20643 && let Some(path) = path.to_str()
20644 {
20645 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20646 } else {
20647 cx.propagate();
20648 }
20649 }
20650
20651 pub fn copy_relative_path(
20652 &mut self,
20653 _: &zed_actions::workspace::CopyRelativePath,
20654 _window: &mut Window,
20655 cx: &mut Context<Self>,
20656 ) {
20657 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20658 let project = self.project()?.read(cx);
20659 let path = buffer.read(cx).file()?.path();
20660 let path = path.display(project.path_style(cx));
20661 Some(path)
20662 }) {
20663 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20664 } else {
20665 cx.propagate();
20666 }
20667 }
20668
20669 /// Returns the project path for the editor's buffer, if any buffer is
20670 /// opened in the editor.
20671 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20672 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20673 buffer.read(cx).project_path(cx)
20674 } else {
20675 None
20676 }
20677 }
20678
20679 // Returns true if the editor handled a go-to-line request
20680 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20681 maybe!({
20682 let breakpoint_store = self.breakpoint_store.as_ref()?;
20683
20684 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20685 else {
20686 self.clear_row_highlights::<ActiveDebugLine>();
20687 return None;
20688 };
20689
20690 let position = active_stack_frame.position;
20691 let buffer_id = position.buffer_id?;
20692 let snapshot = self
20693 .project
20694 .as_ref()?
20695 .read(cx)
20696 .buffer_for_id(buffer_id, cx)?
20697 .read(cx)
20698 .snapshot();
20699
20700 let mut handled = false;
20701 for (id, ExcerptRange { context, .. }) in
20702 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20703 {
20704 if context.start.cmp(&position, &snapshot).is_ge()
20705 || context.end.cmp(&position, &snapshot).is_lt()
20706 {
20707 continue;
20708 }
20709 let snapshot = self.buffer.read(cx).snapshot(cx);
20710 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20711
20712 handled = true;
20713 self.clear_row_highlights::<ActiveDebugLine>();
20714
20715 self.go_to_line::<ActiveDebugLine>(
20716 multibuffer_anchor,
20717 Some(cx.theme().colors().editor_debugger_active_line_background),
20718 window,
20719 cx,
20720 );
20721
20722 cx.notify();
20723 }
20724
20725 handled.then_some(())
20726 })
20727 .is_some()
20728 }
20729
20730 pub fn copy_file_name_without_extension(
20731 &mut self,
20732 _: &CopyFileNameWithoutExtension,
20733 _: &mut Window,
20734 cx: &mut Context<Self>,
20735 ) {
20736 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20737 let file = buffer.read(cx).file()?;
20738 file.path().file_stem()
20739 }) {
20740 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20741 }
20742 }
20743
20744 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20745 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20746 let file = buffer.read(cx).file()?;
20747 Some(file.file_name(cx))
20748 }) {
20749 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20750 }
20751 }
20752
20753 pub fn toggle_git_blame(
20754 &mut self,
20755 _: &::git::Blame,
20756 window: &mut Window,
20757 cx: &mut Context<Self>,
20758 ) {
20759 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20760
20761 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20762 self.start_git_blame(true, window, cx);
20763 }
20764
20765 cx.notify();
20766 }
20767
20768 pub fn toggle_git_blame_inline(
20769 &mut self,
20770 _: &ToggleGitBlameInline,
20771 window: &mut Window,
20772 cx: &mut Context<Self>,
20773 ) {
20774 self.toggle_git_blame_inline_internal(true, window, cx);
20775 cx.notify();
20776 }
20777
20778 pub fn open_git_blame_commit(
20779 &mut self,
20780 _: &OpenGitBlameCommit,
20781 window: &mut Window,
20782 cx: &mut Context<Self>,
20783 ) {
20784 self.open_git_blame_commit_internal(window, cx);
20785 }
20786
20787 fn open_git_blame_commit_internal(
20788 &mut self,
20789 window: &mut Window,
20790 cx: &mut Context<Self>,
20791 ) -> Option<()> {
20792 let blame = self.blame.as_ref()?;
20793 let snapshot = self.snapshot(window, cx);
20794 let cursor = self
20795 .selections
20796 .newest::<Point>(&snapshot.display_snapshot)
20797 .head();
20798 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20799 let (_, blame_entry) = blame
20800 .update(cx, |blame, cx| {
20801 blame
20802 .blame_for_rows(
20803 &[RowInfo {
20804 buffer_id: Some(buffer.remote_id()),
20805 buffer_row: Some(point.row),
20806 ..Default::default()
20807 }],
20808 cx,
20809 )
20810 .next()
20811 })
20812 .flatten()?;
20813 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20814 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20815 let workspace = self.workspace()?.downgrade();
20816 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20817 None
20818 }
20819
20820 pub fn git_blame_inline_enabled(&self) -> bool {
20821 self.git_blame_inline_enabled
20822 }
20823
20824 pub fn toggle_selection_menu(
20825 &mut self,
20826 _: &ToggleSelectionMenu,
20827 _: &mut Window,
20828 cx: &mut Context<Self>,
20829 ) {
20830 self.show_selection_menu = self
20831 .show_selection_menu
20832 .map(|show_selections_menu| !show_selections_menu)
20833 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20834
20835 cx.notify();
20836 }
20837
20838 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20839 self.show_selection_menu
20840 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20841 }
20842
20843 fn start_git_blame(
20844 &mut self,
20845 user_triggered: bool,
20846 window: &mut Window,
20847 cx: &mut Context<Self>,
20848 ) {
20849 if let Some(project) = self.project() {
20850 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20851 && buffer.read(cx).file().is_none()
20852 {
20853 return;
20854 }
20855
20856 let focused = self.focus_handle(cx).contains_focused(window, cx);
20857
20858 let project = project.clone();
20859 let blame = cx
20860 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20861 self.blame_subscription =
20862 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20863 self.blame = Some(blame);
20864 }
20865 }
20866
20867 fn toggle_git_blame_inline_internal(
20868 &mut self,
20869 user_triggered: bool,
20870 window: &mut Window,
20871 cx: &mut Context<Self>,
20872 ) {
20873 if self.git_blame_inline_enabled {
20874 self.git_blame_inline_enabled = false;
20875 self.show_git_blame_inline = false;
20876 self.show_git_blame_inline_delay_task.take();
20877 } else {
20878 self.git_blame_inline_enabled = true;
20879 self.start_git_blame_inline(user_triggered, window, cx);
20880 }
20881
20882 cx.notify();
20883 }
20884
20885 fn start_git_blame_inline(
20886 &mut self,
20887 user_triggered: bool,
20888 window: &mut Window,
20889 cx: &mut Context<Self>,
20890 ) {
20891 self.start_git_blame(user_triggered, window, cx);
20892
20893 if ProjectSettings::get_global(cx)
20894 .git
20895 .inline_blame_delay()
20896 .is_some()
20897 {
20898 self.start_inline_blame_timer(window, cx);
20899 } else {
20900 self.show_git_blame_inline = true
20901 }
20902 }
20903
20904 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20905 self.blame.as_ref()
20906 }
20907
20908 pub fn show_git_blame_gutter(&self) -> bool {
20909 self.show_git_blame_gutter
20910 }
20911
20912 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20913 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20914 }
20915
20916 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20917 self.show_git_blame_inline
20918 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20919 && !self.newest_selection_head_on_empty_line(cx)
20920 && self.has_blame_entries(cx)
20921 }
20922
20923 fn has_blame_entries(&self, cx: &App) -> bool {
20924 self.blame()
20925 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20926 }
20927
20928 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20929 let cursor_anchor = self.selections.newest_anchor().head();
20930
20931 let snapshot = self.buffer.read(cx).snapshot(cx);
20932 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20933
20934 snapshot.line_len(buffer_row) == 0
20935 }
20936
20937 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20938 let buffer_and_selection = maybe!({
20939 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20940 let selection_range = selection.range();
20941
20942 let multi_buffer = self.buffer().read(cx);
20943 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20944 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20945
20946 let (buffer, range, _) = if selection.reversed {
20947 buffer_ranges.first()
20948 } else {
20949 buffer_ranges.last()
20950 }?;
20951
20952 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
20953 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
20954
20955 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
20956 let selection = start_row_in_buffer..end_row_in_buffer;
20957
20958 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
20959 };
20960
20961 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
20962
20963 Some((
20964 multi_buffer.buffer(buffer.remote_id()).unwrap(),
20965 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
20966 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
20967 ))
20968 });
20969
20970 let Some((buffer, selection)) = buffer_and_selection else {
20971 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20972 };
20973
20974 let Some(project) = self.project() else {
20975 return Task::ready(Err(anyhow!("editor does not have project")));
20976 };
20977
20978 project.update(cx, |project, cx| {
20979 project.get_permalink_to_line(&buffer, selection, cx)
20980 })
20981 }
20982
20983 pub fn copy_permalink_to_line(
20984 &mut self,
20985 _: &CopyPermalinkToLine,
20986 window: &mut Window,
20987 cx: &mut Context<Self>,
20988 ) {
20989 let permalink_task = self.get_permalink_to_line(cx);
20990 let workspace = self.workspace();
20991
20992 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20993 Ok(permalink) => {
20994 cx.update(|_, cx| {
20995 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20996 })
20997 .ok();
20998 }
20999 Err(err) => {
21000 let message = format!("Failed to copy permalink: {err}");
21001
21002 anyhow::Result::<()>::Err(err).log_err();
21003
21004 if let Some(workspace) = workspace {
21005 workspace
21006 .update_in(cx, |workspace, _, cx| {
21007 struct CopyPermalinkToLine;
21008
21009 workspace.show_toast(
21010 Toast::new(
21011 NotificationId::unique::<CopyPermalinkToLine>(),
21012 message,
21013 ),
21014 cx,
21015 )
21016 })
21017 .ok();
21018 }
21019 }
21020 })
21021 .detach();
21022 }
21023
21024 pub fn copy_file_location(
21025 &mut self,
21026 _: &CopyFileLocation,
21027 _: &mut Window,
21028 cx: &mut Context<Self>,
21029 ) {
21030 let selection = self
21031 .selections
21032 .newest::<Point>(&self.display_snapshot(cx))
21033 .start
21034 .row
21035 + 1;
21036 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21037 let project = self.project()?.read(cx);
21038 let file = buffer.read(cx).file()?;
21039 let path = file.path().display(project.path_style(cx));
21040
21041 Some(format!("{path}:{selection}"))
21042 }) {
21043 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21044 }
21045 }
21046
21047 pub fn open_permalink_to_line(
21048 &mut self,
21049 _: &OpenPermalinkToLine,
21050 window: &mut Window,
21051 cx: &mut Context<Self>,
21052 ) {
21053 let permalink_task = self.get_permalink_to_line(cx);
21054 let workspace = self.workspace();
21055
21056 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21057 Ok(permalink) => {
21058 cx.update(|_, cx| {
21059 cx.open_url(permalink.as_ref());
21060 })
21061 .ok();
21062 }
21063 Err(err) => {
21064 let message = format!("Failed to open permalink: {err}");
21065
21066 anyhow::Result::<()>::Err(err).log_err();
21067
21068 if let Some(workspace) = workspace {
21069 workspace
21070 .update(cx, |workspace, cx| {
21071 struct OpenPermalinkToLine;
21072
21073 workspace.show_toast(
21074 Toast::new(
21075 NotificationId::unique::<OpenPermalinkToLine>(),
21076 message,
21077 ),
21078 cx,
21079 )
21080 })
21081 .ok();
21082 }
21083 }
21084 })
21085 .detach();
21086 }
21087
21088 pub fn insert_uuid_v4(
21089 &mut self,
21090 _: &InsertUuidV4,
21091 window: &mut Window,
21092 cx: &mut Context<Self>,
21093 ) {
21094 self.insert_uuid(UuidVersion::V4, window, cx);
21095 }
21096
21097 pub fn insert_uuid_v7(
21098 &mut self,
21099 _: &InsertUuidV7,
21100 window: &mut Window,
21101 cx: &mut Context<Self>,
21102 ) {
21103 self.insert_uuid(UuidVersion::V7, window, cx);
21104 }
21105
21106 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21108 self.transact(window, cx, |this, window, cx| {
21109 let edits = this
21110 .selections
21111 .all::<Point>(&this.display_snapshot(cx))
21112 .into_iter()
21113 .map(|selection| {
21114 let uuid = match version {
21115 UuidVersion::V4 => uuid::Uuid::new_v4(),
21116 UuidVersion::V7 => uuid::Uuid::now_v7(),
21117 };
21118
21119 (selection.range(), uuid.to_string())
21120 });
21121 this.edit(edits, cx);
21122 this.refresh_edit_prediction(true, false, window, cx);
21123 });
21124 }
21125
21126 pub fn open_selections_in_multibuffer(
21127 &mut self,
21128 _: &OpenSelectionsInMultibuffer,
21129 window: &mut Window,
21130 cx: &mut Context<Self>,
21131 ) {
21132 let multibuffer = self.buffer.read(cx);
21133
21134 let Some(buffer) = multibuffer.as_singleton() else {
21135 return;
21136 };
21137
21138 let Some(workspace) = self.workspace() else {
21139 return;
21140 };
21141
21142 let title = multibuffer.title(cx).to_string();
21143
21144 let locations = self
21145 .selections
21146 .all_anchors(&self.display_snapshot(cx))
21147 .iter()
21148 .map(|selection| {
21149 (
21150 buffer.clone(),
21151 (selection.start.text_anchor..selection.end.text_anchor)
21152 .to_point(buffer.read(cx)),
21153 )
21154 })
21155 .into_group_map();
21156
21157 cx.spawn_in(window, async move |_, cx| {
21158 workspace.update_in(cx, |workspace, window, cx| {
21159 Self::open_locations_in_multibuffer(
21160 workspace,
21161 locations,
21162 format!("Selections for '{title}'"),
21163 false,
21164 false,
21165 MultibufferSelectionMode::All,
21166 window,
21167 cx,
21168 );
21169 })
21170 })
21171 .detach();
21172 }
21173
21174 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21175 /// last highlight added will be used.
21176 ///
21177 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21178 pub fn highlight_rows<T: 'static>(
21179 &mut self,
21180 range: Range<Anchor>,
21181 color: Hsla,
21182 options: RowHighlightOptions,
21183 cx: &mut Context<Self>,
21184 ) {
21185 let snapshot = self.buffer().read(cx).snapshot(cx);
21186 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21187 let ix = row_highlights.binary_search_by(|highlight| {
21188 Ordering::Equal
21189 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21190 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21191 });
21192
21193 if let Err(mut ix) = ix {
21194 let index = post_inc(&mut self.highlight_order);
21195
21196 // If this range intersects with the preceding highlight, then merge it with
21197 // the preceding highlight. Otherwise insert a new highlight.
21198 let mut merged = false;
21199 if ix > 0 {
21200 let prev_highlight = &mut row_highlights[ix - 1];
21201 if prev_highlight
21202 .range
21203 .end
21204 .cmp(&range.start, &snapshot)
21205 .is_ge()
21206 {
21207 ix -= 1;
21208 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21209 prev_highlight.range.end = range.end;
21210 }
21211 merged = true;
21212 prev_highlight.index = index;
21213 prev_highlight.color = color;
21214 prev_highlight.options = options;
21215 }
21216 }
21217
21218 if !merged {
21219 row_highlights.insert(
21220 ix,
21221 RowHighlight {
21222 range,
21223 index,
21224 color,
21225 options,
21226 type_id: TypeId::of::<T>(),
21227 },
21228 );
21229 }
21230
21231 // If any of the following highlights intersect with this one, merge them.
21232 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21233 let highlight = &row_highlights[ix];
21234 if next_highlight
21235 .range
21236 .start
21237 .cmp(&highlight.range.end, &snapshot)
21238 .is_le()
21239 {
21240 if next_highlight
21241 .range
21242 .end
21243 .cmp(&highlight.range.end, &snapshot)
21244 .is_gt()
21245 {
21246 row_highlights[ix].range.end = next_highlight.range.end;
21247 }
21248 row_highlights.remove(ix + 1);
21249 } else {
21250 break;
21251 }
21252 }
21253 }
21254 }
21255
21256 /// Remove any highlighted row ranges of the given type that intersect the
21257 /// given ranges.
21258 pub fn remove_highlighted_rows<T: 'static>(
21259 &mut self,
21260 ranges_to_remove: Vec<Range<Anchor>>,
21261 cx: &mut Context<Self>,
21262 ) {
21263 let snapshot = self.buffer().read(cx).snapshot(cx);
21264 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21265 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21266 row_highlights.retain(|highlight| {
21267 while let Some(range_to_remove) = ranges_to_remove.peek() {
21268 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21269 Ordering::Less | Ordering::Equal => {
21270 ranges_to_remove.next();
21271 }
21272 Ordering::Greater => {
21273 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21274 Ordering::Less | Ordering::Equal => {
21275 return false;
21276 }
21277 Ordering::Greater => break,
21278 }
21279 }
21280 }
21281 }
21282
21283 true
21284 })
21285 }
21286
21287 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21288 pub fn clear_row_highlights<T: 'static>(&mut self) {
21289 self.highlighted_rows.remove(&TypeId::of::<T>());
21290 }
21291
21292 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21293 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21294 self.highlighted_rows
21295 .get(&TypeId::of::<T>())
21296 .map_or(&[] as &[_], |vec| vec.as_slice())
21297 .iter()
21298 .map(|highlight| (highlight.range.clone(), highlight.color))
21299 }
21300
21301 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21302 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21303 /// Allows to ignore certain kinds of highlights.
21304 pub fn highlighted_display_rows(
21305 &self,
21306 window: &mut Window,
21307 cx: &mut App,
21308 ) -> BTreeMap<DisplayRow, LineHighlight> {
21309 let snapshot = self.snapshot(window, cx);
21310 let mut used_highlight_orders = HashMap::default();
21311 self.highlighted_rows
21312 .iter()
21313 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21314 .fold(
21315 BTreeMap::<DisplayRow, LineHighlight>::new(),
21316 |mut unique_rows, highlight| {
21317 let start = highlight.range.start.to_display_point(&snapshot);
21318 let end = highlight.range.end.to_display_point(&snapshot);
21319 let start_row = start.row().0;
21320 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21321 {
21322 end.row().0.saturating_sub(1)
21323 } else {
21324 end.row().0
21325 };
21326 for row in start_row..=end_row {
21327 let used_index =
21328 used_highlight_orders.entry(row).or_insert(highlight.index);
21329 if highlight.index >= *used_index {
21330 *used_index = highlight.index;
21331 unique_rows.insert(
21332 DisplayRow(row),
21333 LineHighlight {
21334 include_gutter: highlight.options.include_gutter,
21335 border: None,
21336 background: highlight.color.into(),
21337 type_id: Some(highlight.type_id),
21338 },
21339 );
21340 }
21341 }
21342 unique_rows
21343 },
21344 )
21345 }
21346
21347 pub fn highlighted_display_row_for_autoscroll(
21348 &self,
21349 snapshot: &DisplaySnapshot,
21350 ) -> Option<DisplayRow> {
21351 self.highlighted_rows
21352 .values()
21353 .flat_map(|highlighted_rows| highlighted_rows.iter())
21354 .filter_map(|highlight| {
21355 if highlight.options.autoscroll {
21356 Some(highlight.range.start.to_display_point(snapshot).row())
21357 } else {
21358 None
21359 }
21360 })
21361 .min()
21362 }
21363
21364 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21365 self.highlight_background::<SearchWithinRange>(
21366 ranges,
21367 |_, colors| colors.colors().editor_document_highlight_read_background,
21368 cx,
21369 )
21370 }
21371
21372 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21373 self.breadcrumb_header = Some(new_header);
21374 }
21375
21376 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21377 self.clear_background_highlights::<SearchWithinRange>(cx);
21378 }
21379
21380 pub fn highlight_background<T: 'static>(
21381 &mut self,
21382 ranges: &[Range<Anchor>],
21383 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21384 cx: &mut Context<Self>,
21385 ) {
21386 self.background_highlights.insert(
21387 HighlightKey::Type(TypeId::of::<T>()),
21388 (Arc::new(color_fetcher), Arc::from(ranges)),
21389 );
21390 self.scrollbar_marker_state.dirty = true;
21391 cx.notify();
21392 }
21393
21394 pub fn highlight_background_key<T: 'static>(
21395 &mut self,
21396 key: usize,
21397 ranges: &[Range<Anchor>],
21398 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21399 cx: &mut Context<Self>,
21400 ) {
21401 self.background_highlights.insert(
21402 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21403 (Arc::new(color_fetcher), Arc::from(ranges)),
21404 );
21405 self.scrollbar_marker_state.dirty = true;
21406 cx.notify();
21407 }
21408
21409 pub fn clear_background_highlights<T: 'static>(
21410 &mut self,
21411 cx: &mut Context<Self>,
21412 ) -> Option<BackgroundHighlight> {
21413 let text_highlights = self
21414 .background_highlights
21415 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21416 if !text_highlights.1.is_empty() {
21417 self.scrollbar_marker_state.dirty = true;
21418 cx.notify();
21419 }
21420 Some(text_highlights)
21421 }
21422
21423 pub fn highlight_gutter<T: 'static>(
21424 &mut self,
21425 ranges: impl Into<Vec<Range<Anchor>>>,
21426 color_fetcher: fn(&App) -> Hsla,
21427 cx: &mut Context<Self>,
21428 ) {
21429 self.gutter_highlights
21430 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21431 cx.notify();
21432 }
21433
21434 pub fn clear_gutter_highlights<T: 'static>(
21435 &mut self,
21436 cx: &mut Context<Self>,
21437 ) -> Option<GutterHighlight> {
21438 cx.notify();
21439 self.gutter_highlights.remove(&TypeId::of::<T>())
21440 }
21441
21442 pub fn insert_gutter_highlight<T: 'static>(
21443 &mut self,
21444 range: Range<Anchor>,
21445 color_fetcher: fn(&App) -> Hsla,
21446 cx: &mut Context<Self>,
21447 ) {
21448 let snapshot = self.buffer().read(cx).snapshot(cx);
21449 let mut highlights = self
21450 .gutter_highlights
21451 .remove(&TypeId::of::<T>())
21452 .map(|(_, highlights)| highlights)
21453 .unwrap_or_default();
21454 let ix = highlights.binary_search_by(|highlight| {
21455 Ordering::Equal
21456 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21457 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21458 });
21459 if let Err(ix) = ix {
21460 highlights.insert(ix, range);
21461 }
21462 self.gutter_highlights
21463 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21464 }
21465
21466 pub fn remove_gutter_highlights<T: 'static>(
21467 &mut self,
21468 ranges_to_remove: Vec<Range<Anchor>>,
21469 cx: &mut Context<Self>,
21470 ) {
21471 let snapshot = self.buffer().read(cx).snapshot(cx);
21472 let Some((color_fetcher, mut gutter_highlights)) =
21473 self.gutter_highlights.remove(&TypeId::of::<T>())
21474 else {
21475 return;
21476 };
21477 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21478 gutter_highlights.retain(|highlight| {
21479 while let Some(range_to_remove) = ranges_to_remove.peek() {
21480 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21481 Ordering::Less | Ordering::Equal => {
21482 ranges_to_remove.next();
21483 }
21484 Ordering::Greater => {
21485 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21486 Ordering::Less | Ordering::Equal => {
21487 return false;
21488 }
21489 Ordering::Greater => break,
21490 }
21491 }
21492 }
21493 }
21494
21495 true
21496 });
21497 self.gutter_highlights
21498 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21499 }
21500
21501 #[cfg(feature = "test-support")]
21502 pub fn all_text_highlights(
21503 &self,
21504 window: &mut Window,
21505 cx: &mut Context<Self>,
21506 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21507 let snapshot = self.snapshot(window, cx);
21508 self.display_map.update(cx, |display_map, _| {
21509 display_map
21510 .all_text_highlights()
21511 .map(|highlight| {
21512 let (style, ranges) = highlight.as_ref();
21513 (
21514 *style,
21515 ranges
21516 .iter()
21517 .map(|range| range.clone().to_display_points(&snapshot))
21518 .collect(),
21519 )
21520 })
21521 .collect()
21522 })
21523 }
21524
21525 #[cfg(feature = "test-support")]
21526 pub fn all_text_background_highlights(
21527 &self,
21528 window: &mut Window,
21529 cx: &mut Context<Self>,
21530 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21531 let snapshot = self.snapshot(window, cx);
21532 let buffer = &snapshot.buffer_snapshot();
21533 let start = buffer.anchor_before(MultiBufferOffset(0));
21534 let end = buffer.anchor_after(buffer.len());
21535 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21536 }
21537
21538 #[cfg(any(test, feature = "test-support"))]
21539 pub fn sorted_background_highlights_in_range(
21540 &self,
21541 search_range: Range<Anchor>,
21542 display_snapshot: &DisplaySnapshot,
21543 theme: &Theme,
21544 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21545 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21546 res.sort_by(|a, b| {
21547 a.0.start
21548 .cmp(&b.0.start)
21549 .then_with(|| a.0.end.cmp(&b.0.end))
21550 .then_with(|| a.1.cmp(&b.1))
21551 });
21552 res
21553 }
21554
21555 #[cfg(feature = "test-support")]
21556 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21557 let snapshot = self.buffer().read(cx).snapshot(cx);
21558
21559 let highlights = self
21560 .background_highlights
21561 .get(&HighlightKey::Type(TypeId::of::<
21562 items::BufferSearchHighlights,
21563 >()));
21564
21565 if let Some((_color, ranges)) = highlights {
21566 ranges
21567 .iter()
21568 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21569 .collect_vec()
21570 } else {
21571 vec![]
21572 }
21573 }
21574
21575 fn document_highlights_for_position<'a>(
21576 &'a self,
21577 position: Anchor,
21578 buffer: &'a MultiBufferSnapshot,
21579 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21580 let read_highlights = self
21581 .background_highlights
21582 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21583 .map(|h| &h.1);
21584 let write_highlights = self
21585 .background_highlights
21586 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21587 .map(|h| &h.1);
21588 let left_position = position.bias_left(buffer);
21589 let right_position = position.bias_right(buffer);
21590 read_highlights
21591 .into_iter()
21592 .chain(write_highlights)
21593 .flat_map(move |ranges| {
21594 let start_ix = match ranges.binary_search_by(|probe| {
21595 let cmp = probe.end.cmp(&left_position, buffer);
21596 if cmp.is_ge() {
21597 Ordering::Greater
21598 } else {
21599 Ordering::Less
21600 }
21601 }) {
21602 Ok(i) | Err(i) => i,
21603 };
21604
21605 ranges[start_ix..]
21606 .iter()
21607 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21608 })
21609 }
21610
21611 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21612 self.background_highlights
21613 .get(&HighlightKey::Type(TypeId::of::<T>()))
21614 .is_some_and(|(_, highlights)| !highlights.is_empty())
21615 }
21616
21617 /// Returns all background highlights for a given range.
21618 ///
21619 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21620 pub fn background_highlights_in_range(
21621 &self,
21622 search_range: Range<Anchor>,
21623 display_snapshot: &DisplaySnapshot,
21624 theme: &Theme,
21625 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21626 let mut results = Vec::new();
21627 for (color_fetcher, ranges) in self.background_highlights.values() {
21628 let start_ix = match ranges.binary_search_by(|probe| {
21629 let cmp = probe
21630 .end
21631 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21632 if cmp.is_gt() {
21633 Ordering::Greater
21634 } else {
21635 Ordering::Less
21636 }
21637 }) {
21638 Ok(i) | Err(i) => i,
21639 };
21640 for (index, range) in ranges[start_ix..].iter().enumerate() {
21641 if range
21642 .start
21643 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21644 .is_ge()
21645 {
21646 break;
21647 }
21648
21649 let color = color_fetcher(&(start_ix + index), theme);
21650 let start = range.start.to_display_point(display_snapshot);
21651 let end = range.end.to_display_point(display_snapshot);
21652 results.push((start..end, color))
21653 }
21654 }
21655 results
21656 }
21657
21658 pub fn gutter_highlights_in_range(
21659 &self,
21660 search_range: Range<Anchor>,
21661 display_snapshot: &DisplaySnapshot,
21662 cx: &App,
21663 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21664 let mut results = Vec::new();
21665 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21666 let color = color_fetcher(cx);
21667 let start_ix = match ranges.binary_search_by(|probe| {
21668 let cmp = probe
21669 .end
21670 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21671 if cmp.is_gt() {
21672 Ordering::Greater
21673 } else {
21674 Ordering::Less
21675 }
21676 }) {
21677 Ok(i) | Err(i) => i,
21678 };
21679 for range in &ranges[start_ix..] {
21680 if range
21681 .start
21682 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21683 .is_ge()
21684 {
21685 break;
21686 }
21687
21688 let start = range.start.to_display_point(display_snapshot);
21689 let end = range.end.to_display_point(display_snapshot);
21690 results.push((start..end, color))
21691 }
21692 }
21693 results
21694 }
21695
21696 /// Get the text ranges corresponding to the redaction query
21697 pub fn redacted_ranges(
21698 &self,
21699 search_range: Range<Anchor>,
21700 display_snapshot: &DisplaySnapshot,
21701 cx: &App,
21702 ) -> Vec<Range<DisplayPoint>> {
21703 display_snapshot
21704 .buffer_snapshot()
21705 .redacted_ranges(search_range, |file| {
21706 if let Some(file) = file {
21707 file.is_private()
21708 && EditorSettings::get(
21709 Some(SettingsLocation {
21710 worktree_id: file.worktree_id(cx),
21711 path: file.path().as_ref(),
21712 }),
21713 cx,
21714 )
21715 .redact_private_values
21716 } else {
21717 false
21718 }
21719 })
21720 .map(|range| {
21721 range.start.to_display_point(display_snapshot)
21722 ..range.end.to_display_point(display_snapshot)
21723 })
21724 .collect()
21725 }
21726
21727 pub fn highlight_text_key<T: 'static>(
21728 &mut self,
21729 key: usize,
21730 ranges: Vec<Range<Anchor>>,
21731 style: HighlightStyle,
21732 merge: bool,
21733 cx: &mut Context<Self>,
21734 ) {
21735 self.display_map.update(cx, |map, cx| {
21736 map.highlight_text(
21737 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21738 ranges,
21739 style,
21740 merge,
21741 cx,
21742 );
21743 });
21744 cx.notify();
21745 }
21746
21747 pub fn highlight_text<T: 'static>(
21748 &mut self,
21749 ranges: Vec<Range<Anchor>>,
21750 style: HighlightStyle,
21751 cx: &mut Context<Self>,
21752 ) {
21753 self.display_map.update(cx, |map, cx| {
21754 map.highlight_text(
21755 HighlightKey::Type(TypeId::of::<T>()),
21756 ranges,
21757 style,
21758 false,
21759 cx,
21760 )
21761 });
21762 cx.notify();
21763 }
21764
21765 pub fn text_highlights<'a, T: 'static>(
21766 &'a self,
21767 cx: &'a App,
21768 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21769 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21770 }
21771
21772 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21773 let cleared = self
21774 .display_map
21775 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21776 if cleared {
21777 cx.notify();
21778 }
21779 }
21780
21781 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21782 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21783 && self.focus_handle.is_focused(window)
21784 }
21785
21786 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21787 self.show_cursor_when_unfocused = is_enabled;
21788 cx.notify();
21789 }
21790
21791 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21792 cx.notify();
21793 }
21794
21795 fn on_debug_session_event(
21796 &mut self,
21797 _session: Entity<Session>,
21798 event: &SessionEvent,
21799 cx: &mut Context<Self>,
21800 ) {
21801 if let SessionEvent::InvalidateInlineValue = event {
21802 self.refresh_inline_values(cx);
21803 }
21804 }
21805
21806 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21807 let Some(project) = self.project.clone() else {
21808 return;
21809 };
21810
21811 if !self.inline_value_cache.enabled {
21812 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21813 self.splice_inlays(&inlays, Vec::new(), cx);
21814 return;
21815 }
21816
21817 let current_execution_position = self
21818 .highlighted_rows
21819 .get(&TypeId::of::<ActiveDebugLine>())
21820 .and_then(|lines| lines.last().map(|line| line.range.end));
21821
21822 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21823 let inline_values = editor
21824 .update(cx, |editor, cx| {
21825 let Some(current_execution_position) = current_execution_position else {
21826 return Some(Task::ready(Ok(Vec::new())));
21827 };
21828
21829 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21830 let snapshot = buffer.snapshot(cx);
21831
21832 let excerpt = snapshot.excerpt_containing(
21833 current_execution_position..current_execution_position,
21834 )?;
21835
21836 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21837 })?;
21838
21839 let range =
21840 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21841
21842 project.inline_values(buffer, range, cx)
21843 })
21844 .ok()
21845 .flatten()?
21846 .await
21847 .context("refreshing debugger inlays")
21848 .log_err()?;
21849
21850 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21851
21852 for (buffer_id, inline_value) in inline_values
21853 .into_iter()
21854 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21855 {
21856 buffer_inline_values
21857 .entry(buffer_id)
21858 .or_default()
21859 .push(inline_value);
21860 }
21861
21862 editor
21863 .update(cx, |editor, cx| {
21864 let snapshot = editor.buffer.read(cx).snapshot(cx);
21865 let mut new_inlays = Vec::default();
21866
21867 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21868 let buffer_id = buffer_snapshot.remote_id();
21869 buffer_inline_values
21870 .get(&buffer_id)
21871 .into_iter()
21872 .flatten()
21873 .for_each(|hint| {
21874 let inlay = Inlay::debugger(
21875 post_inc(&mut editor.next_inlay_id),
21876 Anchor::in_buffer(excerpt_id, hint.position),
21877 hint.text(),
21878 );
21879 if !inlay.text().chars().contains(&'\n') {
21880 new_inlays.push(inlay);
21881 }
21882 });
21883 }
21884
21885 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21886 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21887
21888 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21889 })
21890 .ok()?;
21891 Some(())
21892 });
21893 }
21894
21895 fn on_buffer_event(
21896 &mut self,
21897 multibuffer: &Entity<MultiBuffer>,
21898 event: &multi_buffer::Event,
21899 window: &mut Window,
21900 cx: &mut Context<Self>,
21901 ) {
21902 match event {
21903 multi_buffer::Event::Edited { edited_buffer } => {
21904 self.scrollbar_marker_state.dirty = true;
21905 self.active_indent_guides_state.dirty = true;
21906 self.refresh_active_diagnostics(cx);
21907 self.refresh_code_actions(window, cx);
21908 self.refresh_single_line_folds(window, cx);
21909 self.refresh_matching_bracket_highlights(window, cx);
21910 if self.has_active_edit_prediction() {
21911 self.update_visible_edit_prediction(window, cx);
21912 }
21913
21914 if let Some(buffer) = edited_buffer {
21915 if buffer.read(cx).file().is_none() {
21916 cx.emit(EditorEvent::TitleChanged);
21917 }
21918
21919 if self.project.is_some() {
21920 let buffer_id = buffer.read(cx).remote_id();
21921 self.register_buffer(buffer_id, cx);
21922 self.update_lsp_data(Some(buffer_id), window, cx);
21923 self.refresh_inlay_hints(
21924 InlayHintRefreshReason::BufferEdited(buffer_id),
21925 cx,
21926 );
21927 }
21928 }
21929
21930 cx.emit(EditorEvent::BufferEdited);
21931 cx.emit(SearchEvent::MatchesInvalidated);
21932
21933 let Some(project) = &self.project else { return };
21934 let (telemetry, is_via_ssh) = {
21935 let project = project.read(cx);
21936 let telemetry = project.client().telemetry().clone();
21937 let is_via_ssh = project.is_via_remote_server();
21938 (telemetry, is_via_ssh)
21939 };
21940 telemetry.log_edit_event("editor", is_via_ssh);
21941 }
21942 multi_buffer::Event::ExcerptsAdded {
21943 buffer,
21944 predecessor,
21945 excerpts,
21946 } => {
21947 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21948 let buffer_id = buffer.read(cx).remote_id();
21949 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21950 && let Some(project) = &self.project
21951 {
21952 update_uncommitted_diff_for_buffer(
21953 cx.entity(),
21954 project,
21955 [buffer.clone()],
21956 self.buffer.clone(),
21957 cx,
21958 )
21959 .detach();
21960 }
21961 self.update_lsp_data(Some(buffer_id), window, cx);
21962 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21963 self.colorize_brackets(false, cx);
21964 cx.emit(EditorEvent::ExcerptsAdded {
21965 buffer: buffer.clone(),
21966 predecessor: *predecessor,
21967 excerpts: excerpts.clone(),
21968 });
21969 }
21970 multi_buffer::Event::ExcerptsRemoved {
21971 ids,
21972 removed_buffer_ids,
21973 } => {
21974 if let Some(inlay_hints) = &mut self.inlay_hints {
21975 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21976 }
21977 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21978 for buffer_id in removed_buffer_ids {
21979 self.registered_buffers.remove(buffer_id);
21980 }
21981 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21982 cx.emit(EditorEvent::ExcerptsRemoved {
21983 ids: ids.clone(),
21984 removed_buffer_ids: removed_buffer_ids.clone(),
21985 });
21986 }
21987 multi_buffer::Event::ExcerptsEdited {
21988 excerpt_ids,
21989 buffer_ids,
21990 } => {
21991 self.display_map.update(cx, |map, cx| {
21992 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21993 });
21994 cx.emit(EditorEvent::ExcerptsEdited {
21995 ids: excerpt_ids.clone(),
21996 });
21997 }
21998 multi_buffer::Event::ExcerptsExpanded { ids } => {
21999 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22000 self.refresh_document_highlights(cx);
22001 for id in ids {
22002 self.fetched_tree_sitter_chunks.remove(id);
22003 }
22004 self.colorize_brackets(false, cx);
22005 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22006 }
22007 multi_buffer::Event::Reparsed(buffer_id) => {
22008 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22009 self.refresh_selected_text_highlights(true, window, cx);
22010 self.colorize_brackets(true, cx);
22011 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22012
22013 cx.emit(EditorEvent::Reparsed(*buffer_id));
22014 }
22015 multi_buffer::Event::DiffHunksToggled => {
22016 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22017 }
22018 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22019 if !is_fresh_language {
22020 self.registered_buffers.remove(&buffer_id);
22021 }
22022 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22023 cx.emit(EditorEvent::Reparsed(*buffer_id));
22024 cx.notify();
22025 }
22026 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22027 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22028 multi_buffer::Event::FileHandleChanged
22029 | multi_buffer::Event::Reloaded
22030 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22031 multi_buffer::Event::DiagnosticsUpdated => {
22032 self.update_diagnostics_state(window, cx);
22033 }
22034 _ => {}
22035 };
22036 }
22037
22038 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22039 if !self.diagnostics_enabled() {
22040 return;
22041 }
22042 self.refresh_active_diagnostics(cx);
22043 self.refresh_inline_diagnostics(true, window, cx);
22044 self.scrollbar_marker_state.dirty = true;
22045 cx.notify();
22046 }
22047
22048 pub fn start_temporary_diff_override(&mut self) {
22049 self.load_diff_task.take();
22050 self.temporary_diff_override = true;
22051 }
22052
22053 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22054 self.temporary_diff_override = false;
22055 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22056 self.buffer.update(cx, |buffer, cx| {
22057 buffer.set_all_diff_hunks_collapsed(cx);
22058 });
22059
22060 if let Some(project) = self.project.clone() {
22061 self.load_diff_task = Some(
22062 update_uncommitted_diff_for_buffer(
22063 cx.entity(),
22064 &project,
22065 self.buffer.read(cx).all_buffers(),
22066 self.buffer.clone(),
22067 cx,
22068 )
22069 .shared(),
22070 );
22071 }
22072 }
22073
22074 fn on_display_map_changed(
22075 &mut self,
22076 _: Entity<DisplayMap>,
22077 _: &mut Window,
22078 cx: &mut Context<Self>,
22079 ) {
22080 cx.notify();
22081 }
22082
22083 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22084 if !self.mode.is_full() {
22085 return None;
22086 }
22087
22088 let theme_settings = theme::ThemeSettings::get_global(cx);
22089 let theme = cx.theme();
22090 let accent_colors = theme.accents().clone();
22091
22092 let accent_overrides = theme_settings
22093 .theme_overrides
22094 .get(theme.name.as_ref())
22095 .map(|theme_style| &theme_style.accents)
22096 .into_iter()
22097 .flatten()
22098 .chain(
22099 theme_settings
22100 .experimental_theme_overrides
22101 .as_ref()
22102 .map(|overrides| &overrides.accents)
22103 .into_iter()
22104 .flatten(),
22105 )
22106 .flat_map(|accent| accent.0.clone())
22107 .collect();
22108
22109 Some(AccentData {
22110 colors: accent_colors,
22111 overrides: accent_overrides,
22112 })
22113 }
22114
22115 fn fetch_applicable_language_settings(
22116 &self,
22117 cx: &App,
22118 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22119 if !self.mode.is_full() {
22120 return HashMap::default();
22121 }
22122
22123 self.buffer().read(cx).all_buffers().into_iter().fold(
22124 HashMap::default(),
22125 |mut acc, buffer| {
22126 let buffer = buffer.read(cx);
22127 let language = buffer.language().map(|language| language.name());
22128 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22129 let file = buffer.file();
22130 v.insert(language_settings(language, file, cx).into_owned());
22131 }
22132 acc
22133 },
22134 )
22135 }
22136
22137 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22138 let new_language_settings = self.fetch_applicable_language_settings(cx);
22139 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22140 self.applicable_language_settings = new_language_settings;
22141
22142 let new_accents = self.fetch_accent_data(cx);
22143 let accents_changed = new_accents != self.accent_data;
22144 self.accent_data = new_accents;
22145
22146 if self.diagnostics_enabled() {
22147 let new_severity = EditorSettings::get_global(cx)
22148 .diagnostics_max_severity
22149 .unwrap_or(DiagnosticSeverity::Hint);
22150 self.set_max_diagnostics_severity(new_severity, cx);
22151 }
22152 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22153 self.update_edit_prediction_settings(cx);
22154 self.refresh_edit_prediction(true, false, window, cx);
22155 self.refresh_inline_values(cx);
22156 self.refresh_inlay_hints(
22157 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22158 self.selections.newest_anchor().head(),
22159 &self.buffer.read(cx).snapshot(cx),
22160 cx,
22161 )),
22162 cx,
22163 );
22164
22165 let old_cursor_shape = self.cursor_shape;
22166 let old_show_breadcrumbs = self.show_breadcrumbs;
22167
22168 {
22169 let editor_settings = EditorSettings::get_global(cx);
22170 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22171 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22172 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22173 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22174 }
22175
22176 if old_cursor_shape != self.cursor_shape {
22177 cx.emit(EditorEvent::CursorShapeChanged);
22178 }
22179
22180 if old_show_breadcrumbs != self.show_breadcrumbs {
22181 cx.emit(EditorEvent::BreadcrumbsChanged);
22182 }
22183
22184 let project_settings = ProjectSettings::get_global(cx);
22185 self.buffer_serialization = self
22186 .should_serialize_buffer()
22187 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22188
22189 if self.mode.is_full() {
22190 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22191 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22192 if self.show_inline_diagnostics != show_inline_diagnostics {
22193 self.show_inline_diagnostics = show_inline_diagnostics;
22194 self.refresh_inline_diagnostics(false, window, cx);
22195 }
22196
22197 if self.git_blame_inline_enabled != inline_blame_enabled {
22198 self.toggle_git_blame_inline_internal(false, window, cx);
22199 }
22200
22201 let minimap_settings = EditorSettings::get_global(cx).minimap;
22202 if self.minimap_visibility != MinimapVisibility::Disabled {
22203 if self.minimap_visibility.settings_visibility()
22204 != minimap_settings.minimap_enabled()
22205 {
22206 self.set_minimap_visibility(
22207 MinimapVisibility::for_mode(self.mode(), cx),
22208 window,
22209 cx,
22210 );
22211 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22212 minimap_entity.update(cx, |minimap_editor, cx| {
22213 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22214 })
22215 }
22216 }
22217
22218 if language_settings_changed || accents_changed {
22219 self.colorize_brackets(true, cx);
22220 }
22221
22222 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22223 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22224 }) {
22225 if !inlay_splice.is_empty() {
22226 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22227 }
22228 self.refresh_colors_for_visible_range(None, window, cx);
22229 }
22230 }
22231
22232 cx.notify();
22233 }
22234
22235 pub fn set_searchable(&mut self, searchable: bool) {
22236 self.searchable = searchable;
22237 }
22238
22239 pub fn searchable(&self) -> bool {
22240 self.searchable
22241 }
22242
22243 pub fn open_excerpts_in_split(
22244 &mut self,
22245 _: &OpenExcerptsSplit,
22246 window: &mut Window,
22247 cx: &mut Context<Self>,
22248 ) {
22249 self.open_excerpts_common(None, true, window, cx)
22250 }
22251
22252 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22253 self.open_excerpts_common(None, false, window, cx)
22254 }
22255
22256 fn open_excerpts_common(
22257 &mut self,
22258 jump_data: Option<JumpData>,
22259 split: bool,
22260 window: &mut Window,
22261 cx: &mut Context<Self>,
22262 ) {
22263 let Some(workspace) = self.workspace() else {
22264 cx.propagate();
22265 return;
22266 };
22267
22268 if self.buffer.read(cx).is_singleton() {
22269 cx.propagate();
22270 return;
22271 }
22272
22273 let mut new_selections_by_buffer = HashMap::default();
22274 match &jump_data {
22275 Some(JumpData::MultiBufferPoint {
22276 excerpt_id,
22277 position,
22278 anchor,
22279 line_offset_from_top,
22280 }) => {
22281 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22282 if let Some(buffer) = multi_buffer_snapshot
22283 .buffer_id_for_excerpt(*excerpt_id)
22284 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22285 {
22286 let buffer_snapshot = buffer.read(cx).snapshot();
22287 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22288 language::ToPoint::to_point(anchor, &buffer_snapshot)
22289 } else {
22290 buffer_snapshot.clip_point(*position, Bias::Left)
22291 };
22292 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22293 new_selections_by_buffer.insert(
22294 buffer,
22295 (
22296 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22297 Some(*line_offset_from_top),
22298 ),
22299 );
22300 }
22301 }
22302 Some(JumpData::MultiBufferRow {
22303 row,
22304 line_offset_from_top,
22305 }) => {
22306 let point = MultiBufferPoint::new(row.0, 0);
22307 if let Some((buffer, buffer_point, _)) =
22308 self.buffer.read(cx).point_to_buffer_point(point, cx)
22309 {
22310 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22311 new_selections_by_buffer
22312 .entry(buffer)
22313 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22314 .0
22315 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22316 }
22317 }
22318 None => {
22319 let selections = self
22320 .selections
22321 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22322 let multi_buffer = self.buffer.read(cx);
22323 for selection in selections {
22324 for (snapshot, range, _, anchor) in multi_buffer
22325 .snapshot(cx)
22326 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22327 {
22328 if let Some(anchor) = anchor {
22329 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22330 else {
22331 continue;
22332 };
22333 let offset = text::ToOffset::to_offset(
22334 &anchor.text_anchor,
22335 &buffer_handle.read(cx).snapshot(),
22336 );
22337 let range = BufferOffset(offset)..BufferOffset(offset);
22338 new_selections_by_buffer
22339 .entry(buffer_handle)
22340 .or_insert((Vec::new(), None))
22341 .0
22342 .push(range)
22343 } else {
22344 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22345 else {
22346 continue;
22347 };
22348 new_selections_by_buffer
22349 .entry(buffer_handle)
22350 .or_insert((Vec::new(), None))
22351 .0
22352 .push(range)
22353 }
22354 }
22355 }
22356 }
22357 }
22358
22359 new_selections_by_buffer
22360 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22361
22362 if new_selections_by_buffer.is_empty() {
22363 return;
22364 }
22365
22366 // We defer the pane interaction because we ourselves are a workspace item
22367 // and activating a new item causes the pane to call a method on us reentrantly,
22368 // which panics if we're on the stack.
22369 window.defer(cx, move |window, cx| {
22370 workspace.update(cx, |workspace, cx| {
22371 let pane = if split {
22372 workspace.adjacent_pane(window, cx)
22373 } else {
22374 workspace.active_pane().clone()
22375 };
22376
22377 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22378 let buffer_read = buffer.read(cx);
22379 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22380 (true, project::File::from_dyn(Some(file)).is_some())
22381 } else {
22382 (false, false)
22383 };
22384
22385 // If project file is none workspace.open_project_item will fail to open the excerpt
22386 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22387 // so we check if there's a tab match in that case first
22388 let editor = (!has_file || !is_project_file)
22389 .then(|| {
22390 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22391 // so `workspace.open_project_item` will never find them, always opening a new editor.
22392 // Instead, we try to activate the existing editor in the pane first.
22393 let (editor, pane_item_index, pane_item_id) =
22394 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22395 let editor = item.downcast::<Editor>()?;
22396 let singleton_buffer =
22397 editor.read(cx).buffer().read(cx).as_singleton()?;
22398 if singleton_buffer == buffer {
22399 Some((editor, i, item.item_id()))
22400 } else {
22401 None
22402 }
22403 })?;
22404 pane.update(cx, |pane, cx| {
22405 pane.activate_item(pane_item_index, true, true, window, cx);
22406 if !PreviewTabsSettings::get_global(cx)
22407 .enable_preview_from_multibuffer
22408 {
22409 pane.unpreview_item_if_preview(pane_item_id);
22410 }
22411 });
22412 Some(editor)
22413 })
22414 .flatten()
22415 .unwrap_or_else(|| {
22416 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22417 .enable_keep_preview_on_code_navigation;
22418 let allow_new_preview =
22419 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22420 workspace.open_project_item::<Self>(
22421 pane.clone(),
22422 buffer,
22423 true,
22424 true,
22425 keep_old_preview,
22426 allow_new_preview,
22427 window,
22428 cx,
22429 )
22430 });
22431
22432 editor.update(cx, |editor, cx| {
22433 if has_file && !is_project_file {
22434 editor.set_read_only(true);
22435 }
22436 let autoscroll = match scroll_offset {
22437 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22438 None => Autoscroll::newest(),
22439 };
22440 let nav_history = editor.nav_history.take();
22441 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22442 let Some((&excerpt_id, _, buffer_snapshot)) =
22443 multibuffer_snapshot.as_singleton()
22444 else {
22445 return;
22446 };
22447 editor.change_selections(
22448 SelectionEffects::scroll(autoscroll),
22449 window,
22450 cx,
22451 |s| {
22452 s.select_ranges(ranges.into_iter().map(|range| {
22453 let range = buffer_snapshot.anchor_before(range.start)
22454 ..buffer_snapshot.anchor_after(range.end);
22455 multibuffer_snapshot
22456 .anchor_range_in_excerpt(excerpt_id, range)
22457 .unwrap()
22458 }));
22459 },
22460 );
22461 editor.nav_history = nav_history;
22462 });
22463 }
22464 })
22465 });
22466 }
22467
22468 // Allow opening excerpts for buffers that either belong to the current project
22469 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22470 // are also supported so tests and other in-memory views keep working.
22471 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22472 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22473 }
22474
22475 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22476 let snapshot = self.buffer.read(cx).read(cx);
22477 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22478 Some(
22479 ranges
22480 .iter()
22481 .map(move |range| {
22482 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22483 })
22484 .collect(),
22485 )
22486 }
22487
22488 fn selection_replacement_ranges(
22489 &self,
22490 range: Range<MultiBufferOffsetUtf16>,
22491 cx: &mut App,
22492 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22493 let selections = self
22494 .selections
22495 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22496 let newest_selection = selections
22497 .iter()
22498 .max_by_key(|selection| selection.id)
22499 .unwrap();
22500 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22501 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22502 let snapshot = self.buffer.read(cx).read(cx);
22503 selections
22504 .into_iter()
22505 .map(|mut selection| {
22506 selection.start.0.0 =
22507 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22508 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22509 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22510 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22511 })
22512 .collect()
22513 }
22514
22515 fn report_editor_event(
22516 &self,
22517 reported_event: ReportEditorEvent,
22518 file_extension: Option<String>,
22519 cx: &App,
22520 ) {
22521 if cfg!(any(test, feature = "test-support")) {
22522 return;
22523 }
22524
22525 let Some(project) = &self.project else { return };
22526
22527 // If None, we are in a file without an extension
22528 let file = self
22529 .buffer
22530 .read(cx)
22531 .as_singleton()
22532 .and_then(|b| b.read(cx).file());
22533 let file_extension = file_extension.or(file
22534 .as_ref()
22535 .and_then(|file| Path::new(file.file_name(cx)).extension())
22536 .and_then(|e| e.to_str())
22537 .map(|a| a.to_string()));
22538
22539 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22540 .map(|vim_mode| vim_mode.0)
22541 .unwrap_or(false);
22542
22543 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22544 let copilot_enabled = edit_predictions_provider
22545 == language::language_settings::EditPredictionProvider::Copilot;
22546 let copilot_enabled_for_language = self
22547 .buffer
22548 .read(cx)
22549 .language_settings(cx)
22550 .show_edit_predictions;
22551
22552 let project = project.read(cx);
22553 let event_type = reported_event.event_type();
22554
22555 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22556 telemetry::event!(
22557 event_type,
22558 type = if auto_saved {"autosave"} else {"manual"},
22559 file_extension,
22560 vim_mode,
22561 copilot_enabled,
22562 copilot_enabled_for_language,
22563 edit_predictions_provider,
22564 is_via_ssh = project.is_via_remote_server(),
22565 );
22566 } else {
22567 telemetry::event!(
22568 event_type,
22569 file_extension,
22570 vim_mode,
22571 copilot_enabled,
22572 copilot_enabled_for_language,
22573 edit_predictions_provider,
22574 is_via_ssh = project.is_via_remote_server(),
22575 );
22576 };
22577 }
22578
22579 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22580 /// with each line being an array of {text, highlight} objects.
22581 fn copy_highlight_json(
22582 &mut self,
22583 _: &CopyHighlightJson,
22584 window: &mut Window,
22585 cx: &mut Context<Self>,
22586 ) {
22587 #[derive(Serialize)]
22588 struct Chunk<'a> {
22589 text: String,
22590 highlight: Option<&'a str>,
22591 }
22592
22593 let snapshot = self.buffer.read(cx).snapshot(cx);
22594 let range = self
22595 .selected_text_range(false, window, cx)
22596 .and_then(|selection| {
22597 if selection.range.is_empty() {
22598 None
22599 } else {
22600 Some(
22601 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22602 selection.range.start,
22603 )))
22604 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22605 selection.range.end,
22606 ))),
22607 )
22608 }
22609 })
22610 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22611
22612 let chunks = snapshot.chunks(range, true);
22613 let mut lines = Vec::new();
22614 let mut line: VecDeque<Chunk> = VecDeque::new();
22615
22616 let Some(style) = self.style.as_ref() else {
22617 return;
22618 };
22619
22620 for chunk in chunks {
22621 let highlight = chunk
22622 .syntax_highlight_id
22623 .and_then(|id| id.name(&style.syntax));
22624 let mut chunk_lines = chunk.text.split('\n').peekable();
22625 while let Some(text) = chunk_lines.next() {
22626 let mut merged_with_last_token = false;
22627 if let Some(last_token) = line.back_mut()
22628 && last_token.highlight == highlight
22629 {
22630 last_token.text.push_str(text);
22631 merged_with_last_token = true;
22632 }
22633
22634 if !merged_with_last_token {
22635 line.push_back(Chunk {
22636 text: text.into(),
22637 highlight,
22638 });
22639 }
22640
22641 if chunk_lines.peek().is_some() {
22642 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22643 line.pop_front();
22644 }
22645 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22646 line.pop_back();
22647 }
22648
22649 lines.push(mem::take(&mut line));
22650 }
22651 }
22652 }
22653
22654 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22655 return;
22656 };
22657 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22658 }
22659
22660 pub fn open_context_menu(
22661 &mut self,
22662 _: &OpenContextMenu,
22663 window: &mut Window,
22664 cx: &mut Context<Self>,
22665 ) {
22666 self.request_autoscroll(Autoscroll::newest(), cx);
22667 let position = self
22668 .selections
22669 .newest_display(&self.display_snapshot(cx))
22670 .start;
22671 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22672 }
22673
22674 pub fn replay_insert_event(
22675 &mut self,
22676 text: &str,
22677 relative_utf16_range: Option<Range<isize>>,
22678 window: &mut Window,
22679 cx: &mut Context<Self>,
22680 ) {
22681 if !self.input_enabled {
22682 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22683 return;
22684 }
22685 if let Some(relative_utf16_range) = relative_utf16_range {
22686 let selections = self
22687 .selections
22688 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22689 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22690 let new_ranges = selections.into_iter().map(|range| {
22691 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22692 range
22693 .head()
22694 .0
22695 .0
22696 .saturating_add_signed(relative_utf16_range.start),
22697 ));
22698 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22699 range
22700 .head()
22701 .0
22702 .0
22703 .saturating_add_signed(relative_utf16_range.end),
22704 ));
22705 start..end
22706 });
22707 s.select_ranges(new_ranges);
22708 });
22709 }
22710
22711 self.handle_input(text, window, cx);
22712 }
22713
22714 pub fn is_focused(&self, window: &Window) -> bool {
22715 self.focus_handle.is_focused(window)
22716 }
22717
22718 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22719 cx.emit(EditorEvent::Focused);
22720
22721 if let Some(descendant) = self
22722 .last_focused_descendant
22723 .take()
22724 .and_then(|descendant| descendant.upgrade())
22725 {
22726 window.focus(&descendant, cx);
22727 } else {
22728 if let Some(blame) = self.blame.as_ref() {
22729 blame.update(cx, GitBlame::focus)
22730 }
22731
22732 self.blink_manager.update(cx, BlinkManager::enable);
22733 self.show_cursor_names(window, cx);
22734 self.buffer.update(cx, |buffer, cx| {
22735 buffer.finalize_last_transaction(cx);
22736 if self.leader_id.is_none() {
22737 buffer.set_active_selections(
22738 &self.selections.disjoint_anchors_arc(),
22739 self.selections.line_mode(),
22740 self.cursor_shape,
22741 cx,
22742 );
22743 }
22744 });
22745
22746 if let Some(position_map) = self.last_position_map.clone() {
22747 EditorElement::mouse_moved(
22748 self,
22749 &MouseMoveEvent {
22750 position: window.mouse_position(),
22751 pressed_button: None,
22752 modifiers: window.modifiers(),
22753 },
22754 &position_map,
22755 window,
22756 cx,
22757 );
22758 }
22759 }
22760 }
22761
22762 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22763 cx.emit(EditorEvent::FocusedIn)
22764 }
22765
22766 fn handle_focus_out(
22767 &mut self,
22768 event: FocusOutEvent,
22769 _window: &mut Window,
22770 cx: &mut Context<Self>,
22771 ) {
22772 if event.blurred != self.focus_handle {
22773 self.last_focused_descendant = Some(event.blurred);
22774 }
22775 self.selection_drag_state = SelectionDragState::None;
22776 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22777 }
22778
22779 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22780 self.blink_manager.update(cx, BlinkManager::disable);
22781 self.buffer
22782 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22783
22784 if let Some(blame) = self.blame.as_ref() {
22785 blame.update(cx, GitBlame::blur)
22786 }
22787 if !self.hover_state.focused(window, cx) {
22788 hide_hover(self, cx);
22789 }
22790 if !self
22791 .context_menu
22792 .borrow()
22793 .as_ref()
22794 .is_some_and(|context_menu| context_menu.focused(window, cx))
22795 {
22796 self.hide_context_menu(window, cx);
22797 }
22798 self.take_active_edit_prediction(cx);
22799 cx.emit(EditorEvent::Blurred);
22800 cx.notify();
22801 }
22802
22803 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22804 let mut pending: String = window
22805 .pending_input_keystrokes()
22806 .into_iter()
22807 .flatten()
22808 .filter_map(|keystroke| keystroke.key_char.clone())
22809 .collect();
22810
22811 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22812 pending = "".to_string();
22813 }
22814
22815 let existing_pending = self
22816 .text_highlights::<PendingInput>(cx)
22817 .map(|(_, ranges)| ranges.to_vec());
22818 if existing_pending.is_none() && pending.is_empty() {
22819 return;
22820 }
22821 let transaction =
22822 self.transact(window, cx, |this, window, cx| {
22823 let selections = this
22824 .selections
22825 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22826 let edits = selections
22827 .iter()
22828 .map(|selection| (selection.end..selection.end, pending.clone()));
22829 this.edit(edits, cx);
22830 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22831 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22832 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22833 }));
22834 });
22835 if let Some(existing_ranges) = existing_pending {
22836 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22837 this.edit(edits, cx);
22838 }
22839 });
22840
22841 let snapshot = self.snapshot(window, cx);
22842 let ranges = self
22843 .selections
22844 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22845 .into_iter()
22846 .map(|selection| {
22847 snapshot.buffer_snapshot().anchor_after(selection.end)
22848 ..snapshot
22849 .buffer_snapshot()
22850 .anchor_before(selection.end + pending.len())
22851 })
22852 .collect();
22853
22854 if pending.is_empty() {
22855 self.clear_highlights::<PendingInput>(cx);
22856 } else {
22857 self.highlight_text::<PendingInput>(
22858 ranges,
22859 HighlightStyle {
22860 underline: Some(UnderlineStyle {
22861 thickness: px(1.),
22862 color: None,
22863 wavy: false,
22864 }),
22865 ..Default::default()
22866 },
22867 cx,
22868 );
22869 }
22870
22871 self.ime_transaction = self.ime_transaction.or(transaction);
22872 if let Some(transaction) = self.ime_transaction {
22873 self.buffer.update(cx, |buffer, cx| {
22874 buffer.group_until_transaction(transaction, cx);
22875 });
22876 }
22877
22878 if self.text_highlights::<PendingInput>(cx).is_none() {
22879 self.ime_transaction.take();
22880 }
22881 }
22882
22883 pub fn register_action_renderer(
22884 &mut self,
22885 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22886 ) -> Subscription {
22887 let id = self.next_editor_action_id.post_inc();
22888 self.editor_actions
22889 .borrow_mut()
22890 .insert(id, Box::new(listener));
22891
22892 let editor_actions = self.editor_actions.clone();
22893 Subscription::new(move || {
22894 editor_actions.borrow_mut().remove(&id);
22895 })
22896 }
22897
22898 pub fn register_action<A: Action>(
22899 &mut self,
22900 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22901 ) -> Subscription {
22902 let id = self.next_editor_action_id.post_inc();
22903 let listener = Arc::new(listener);
22904 self.editor_actions.borrow_mut().insert(
22905 id,
22906 Box::new(move |_, window, _| {
22907 let listener = listener.clone();
22908 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22909 let action = action.downcast_ref().unwrap();
22910 if phase == DispatchPhase::Bubble {
22911 listener(action, window, cx)
22912 }
22913 })
22914 }),
22915 );
22916
22917 let editor_actions = self.editor_actions.clone();
22918 Subscription::new(move || {
22919 editor_actions.borrow_mut().remove(&id);
22920 })
22921 }
22922
22923 pub fn file_header_size(&self) -> u32 {
22924 FILE_HEADER_HEIGHT
22925 }
22926
22927 pub fn restore(
22928 &mut self,
22929 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22930 window: &mut Window,
22931 cx: &mut Context<Self>,
22932 ) {
22933 self.buffer().update(cx, |multi_buffer, cx| {
22934 for (buffer_id, changes) in revert_changes {
22935 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22936 buffer.update(cx, |buffer, cx| {
22937 buffer.edit(
22938 changes
22939 .into_iter()
22940 .map(|(range, text)| (range, text.to_string())),
22941 None,
22942 cx,
22943 );
22944 });
22945 }
22946 }
22947 });
22948 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22949 selections.refresh()
22950 });
22951 }
22952
22953 pub fn to_pixel_point(
22954 &mut self,
22955 source: multi_buffer::Anchor,
22956 editor_snapshot: &EditorSnapshot,
22957 window: &mut Window,
22958 cx: &App,
22959 ) -> Option<gpui::Point<Pixels>> {
22960 let source_point = source.to_display_point(editor_snapshot);
22961 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
22962 }
22963
22964 pub fn display_to_pixel_point(
22965 &mut self,
22966 source: DisplayPoint,
22967 editor_snapshot: &EditorSnapshot,
22968 window: &mut Window,
22969 cx: &App,
22970 ) -> Option<gpui::Point<Pixels>> {
22971 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
22972 let text_layout_details = self.text_layout_details(window);
22973 let scroll_top = text_layout_details
22974 .scroll_anchor
22975 .scroll_position(editor_snapshot)
22976 .y;
22977
22978 if source.row().as_f64() < scroll_top.floor() {
22979 return None;
22980 }
22981 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22982 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22983 Some(gpui::Point::new(source_x, source_y))
22984 }
22985
22986 pub fn has_visible_completions_menu(&self) -> bool {
22987 !self.edit_prediction_preview_is_active()
22988 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22989 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22990 })
22991 }
22992
22993 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22994 if self.mode.is_minimap() {
22995 return;
22996 }
22997 self.addons
22998 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22999 }
23000
23001 pub fn unregister_addon<T: Addon>(&mut self) {
23002 self.addons.remove(&std::any::TypeId::of::<T>());
23003 }
23004
23005 pub fn addon<T: Addon>(&self) -> Option<&T> {
23006 let type_id = std::any::TypeId::of::<T>();
23007 self.addons
23008 .get(&type_id)
23009 .and_then(|item| item.to_any().downcast_ref::<T>())
23010 }
23011
23012 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23013 let type_id = std::any::TypeId::of::<T>();
23014 self.addons
23015 .get_mut(&type_id)
23016 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23017 }
23018
23019 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23020 let text_layout_details = self.text_layout_details(window);
23021 let style = &text_layout_details.editor_style;
23022 let font_id = window.text_system().resolve_font(&style.text.font());
23023 let font_size = style.text.font_size.to_pixels(window.rem_size());
23024 let line_height = style.text.line_height_in_pixels(window.rem_size());
23025 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23026 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23027
23028 CharacterDimensions {
23029 em_width,
23030 em_advance,
23031 line_height,
23032 }
23033 }
23034
23035 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23036 self.load_diff_task.clone()
23037 }
23038
23039 fn read_metadata_from_db(
23040 &mut self,
23041 item_id: u64,
23042 workspace_id: WorkspaceId,
23043 window: &mut Window,
23044 cx: &mut Context<Editor>,
23045 ) {
23046 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23047 && !self.mode.is_minimap()
23048 && WorkspaceSettings::get(None, cx).restore_on_startup
23049 != RestoreOnStartupBehavior::EmptyTab
23050 {
23051 let buffer_snapshot = OnceCell::new();
23052
23053 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23054 && !folds.is_empty()
23055 {
23056 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23057 self.fold_ranges(
23058 folds
23059 .into_iter()
23060 .map(|(start, end)| {
23061 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23062 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23063 })
23064 .collect(),
23065 false,
23066 window,
23067 cx,
23068 );
23069 }
23070
23071 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23072 && !selections.is_empty()
23073 {
23074 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23075 // skip adding the initial selection to selection history
23076 self.selection_history.mode = SelectionHistoryMode::Skipping;
23077 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23078 s.select_ranges(selections.into_iter().map(|(start, end)| {
23079 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23080 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23081 }));
23082 });
23083 self.selection_history.mode = SelectionHistoryMode::Normal;
23084 };
23085 }
23086
23087 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23088 }
23089
23090 fn update_lsp_data(
23091 &mut self,
23092 for_buffer: Option<BufferId>,
23093 window: &mut Window,
23094 cx: &mut Context<'_, Self>,
23095 ) {
23096 self.pull_diagnostics(for_buffer, window, cx);
23097 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23098 }
23099
23100 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23101 if self.ignore_lsp_data() {
23102 return;
23103 }
23104 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23105 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23106 }
23107 }
23108
23109 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23110 if self.ignore_lsp_data() {
23111 return;
23112 }
23113
23114 if !self.registered_buffers.contains_key(&buffer_id)
23115 && let Some(project) = self.project.as_ref()
23116 {
23117 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23118 project.update(cx, |project, cx| {
23119 self.registered_buffers.insert(
23120 buffer_id,
23121 project.register_buffer_with_language_servers(&buffer, cx),
23122 );
23123 });
23124 } else {
23125 self.registered_buffers.remove(&buffer_id);
23126 }
23127 }
23128 }
23129
23130 fn ignore_lsp_data(&self) -> bool {
23131 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23132 // skip any LSP updates for it.
23133 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23134 }
23135
23136 fn create_style(&self, cx: &App) -> EditorStyle {
23137 let settings = ThemeSettings::get_global(cx);
23138
23139 let mut text_style = match self.mode {
23140 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23141 color: cx.theme().colors().editor_foreground,
23142 font_family: settings.ui_font.family.clone(),
23143 font_features: settings.ui_font.features.clone(),
23144 font_fallbacks: settings.ui_font.fallbacks.clone(),
23145 font_size: rems(0.875).into(),
23146 font_weight: settings.ui_font.weight,
23147 line_height: relative(settings.buffer_line_height.value()),
23148 ..Default::default()
23149 },
23150 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23151 color: cx.theme().colors().editor_foreground,
23152 font_family: settings.buffer_font.family.clone(),
23153 font_features: settings.buffer_font.features.clone(),
23154 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23155 font_size: settings.buffer_font_size(cx).into(),
23156 font_weight: settings.buffer_font.weight,
23157 line_height: relative(settings.buffer_line_height.value()),
23158 ..Default::default()
23159 },
23160 };
23161 if let Some(text_style_refinement) = &self.text_style_refinement {
23162 text_style.refine(text_style_refinement)
23163 }
23164
23165 let background = match self.mode {
23166 EditorMode::SingleLine => cx.theme().system().transparent,
23167 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23168 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23169 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23170 };
23171
23172 EditorStyle {
23173 background,
23174 border: cx.theme().colors().border,
23175 local_player: cx.theme().players().local(),
23176 text: text_style,
23177 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23178 syntax: cx.theme().syntax().clone(),
23179 status: cx.theme().status().clone(),
23180 inlay_hints_style: make_inlay_hints_style(cx),
23181 edit_prediction_styles: make_suggestion_styles(cx),
23182 unnecessary_code_fade: settings.unnecessary_code_fade,
23183 show_underlines: self.diagnostics_enabled(),
23184 }
23185 }
23186}
23187
23188fn edit_for_markdown_paste<'a>(
23189 buffer: &MultiBufferSnapshot,
23190 range: Range<MultiBufferOffset>,
23191 to_insert: &'a str,
23192 url: Option<url::Url>,
23193) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23194 if url.is_none() {
23195 return (range, Cow::Borrowed(to_insert));
23196 };
23197
23198 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23199
23200 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23201 Cow::Borrowed(to_insert)
23202 } else {
23203 Cow::Owned(format!("[{old_text}]({to_insert})"))
23204 };
23205 (range, new_text)
23206}
23207
23208fn process_completion_for_edit(
23209 completion: &Completion,
23210 intent: CompletionIntent,
23211 buffer: &Entity<Buffer>,
23212 cursor_position: &text::Anchor,
23213 cx: &mut Context<Editor>,
23214) -> CompletionEdit {
23215 let buffer = buffer.read(cx);
23216 let buffer_snapshot = buffer.snapshot();
23217 let (snippet, new_text) = if completion.is_snippet() {
23218 let mut snippet_source = completion.new_text.clone();
23219 // Workaround for typescript language server issues so that methods don't expand within
23220 // strings and functions with type expressions. The previous point is used because the query
23221 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23222 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23223 let previous_point = if previous_point.column > 0 {
23224 cursor_position.to_previous_offset(&buffer_snapshot)
23225 } else {
23226 cursor_position.to_offset(&buffer_snapshot)
23227 };
23228 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23229 && scope.prefers_label_for_snippet_in_completion()
23230 && let Some(label) = completion.label()
23231 && matches!(
23232 completion.kind(),
23233 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23234 )
23235 {
23236 snippet_source = label;
23237 }
23238 match Snippet::parse(&snippet_source).log_err() {
23239 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23240 None => (None, completion.new_text.clone()),
23241 }
23242 } else {
23243 (None, completion.new_text.clone())
23244 };
23245
23246 let mut range_to_replace = {
23247 let replace_range = &completion.replace_range;
23248 if let CompletionSource::Lsp {
23249 insert_range: Some(insert_range),
23250 ..
23251 } = &completion.source
23252 {
23253 debug_assert_eq!(
23254 insert_range.start, replace_range.start,
23255 "insert_range and replace_range should start at the same position"
23256 );
23257 debug_assert!(
23258 insert_range
23259 .start
23260 .cmp(cursor_position, &buffer_snapshot)
23261 .is_le(),
23262 "insert_range should start before or at cursor position"
23263 );
23264 debug_assert!(
23265 replace_range
23266 .start
23267 .cmp(cursor_position, &buffer_snapshot)
23268 .is_le(),
23269 "replace_range should start before or at cursor position"
23270 );
23271
23272 let should_replace = match intent {
23273 CompletionIntent::CompleteWithInsert => false,
23274 CompletionIntent::CompleteWithReplace => true,
23275 CompletionIntent::Complete | CompletionIntent::Compose => {
23276 let insert_mode =
23277 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23278 .completions
23279 .lsp_insert_mode;
23280 match insert_mode {
23281 LspInsertMode::Insert => false,
23282 LspInsertMode::Replace => true,
23283 LspInsertMode::ReplaceSubsequence => {
23284 let mut text_to_replace = buffer.chars_for_range(
23285 buffer.anchor_before(replace_range.start)
23286 ..buffer.anchor_after(replace_range.end),
23287 );
23288 let mut current_needle = text_to_replace.next();
23289 for haystack_ch in completion.label.text.chars() {
23290 if let Some(needle_ch) = current_needle
23291 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23292 {
23293 current_needle = text_to_replace.next();
23294 }
23295 }
23296 current_needle.is_none()
23297 }
23298 LspInsertMode::ReplaceSuffix => {
23299 if replace_range
23300 .end
23301 .cmp(cursor_position, &buffer_snapshot)
23302 .is_gt()
23303 {
23304 let range_after_cursor = *cursor_position..replace_range.end;
23305 let text_after_cursor = buffer
23306 .text_for_range(
23307 buffer.anchor_before(range_after_cursor.start)
23308 ..buffer.anchor_after(range_after_cursor.end),
23309 )
23310 .collect::<String>()
23311 .to_ascii_lowercase();
23312 completion
23313 .label
23314 .text
23315 .to_ascii_lowercase()
23316 .ends_with(&text_after_cursor)
23317 } else {
23318 true
23319 }
23320 }
23321 }
23322 }
23323 };
23324
23325 if should_replace {
23326 replace_range.clone()
23327 } else {
23328 insert_range.clone()
23329 }
23330 } else {
23331 replace_range.clone()
23332 }
23333 };
23334
23335 if range_to_replace
23336 .end
23337 .cmp(cursor_position, &buffer_snapshot)
23338 .is_lt()
23339 {
23340 range_to_replace.end = *cursor_position;
23341 }
23342
23343 let replace_range = range_to_replace.to_offset(buffer);
23344 CompletionEdit {
23345 new_text,
23346 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23347 snippet,
23348 }
23349}
23350
23351struct CompletionEdit {
23352 new_text: String,
23353 replace_range: Range<BufferOffset>,
23354 snippet: Option<Snippet>,
23355}
23356
23357fn comment_delimiter_for_newline(
23358 start_point: &Point,
23359 buffer: &MultiBufferSnapshot,
23360 language: &LanguageScope,
23361) -> Option<Arc<str>> {
23362 let delimiters = language.line_comment_prefixes();
23363 let max_len_of_delimiter = delimiters.iter().map(|delimiter| delimiter.len()).max()?;
23364 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23365
23366 let num_of_whitespaces = snapshot
23367 .chars_for_range(range.clone())
23368 .take_while(|c| c.is_whitespace())
23369 .count();
23370 let comment_candidate = snapshot
23371 .chars_for_range(range.clone())
23372 .skip(num_of_whitespaces)
23373 .take(max_len_of_delimiter)
23374 .collect::<String>();
23375 let (delimiter, trimmed_len) = delimiters
23376 .iter()
23377 .filter_map(|delimiter| {
23378 let prefix = delimiter.trim_end();
23379 if comment_candidate.starts_with(prefix) {
23380 Some((delimiter, prefix.len()))
23381 } else {
23382 None
23383 }
23384 })
23385 .max_by_key(|(_, len)| *len)?;
23386
23387 if let Some(BlockCommentConfig {
23388 start: block_start, ..
23389 }) = language.block_comment()
23390 {
23391 let block_start_trimmed = block_start.trim_end();
23392 if block_start_trimmed.starts_with(delimiter.trim_end()) {
23393 let line_content = snapshot
23394 .chars_for_range(range)
23395 .skip(num_of_whitespaces)
23396 .take(block_start_trimmed.len())
23397 .collect::<String>();
23398
23399 if line_content.starts_with(block_start_trimmed) {
23400 return None;
23401 }
23402 }
23403 }
23404
23405 let cursor_is_placed_after_comment_marker =
23406 num_of_whitespaces + trimmed_len <= start_point.column as usize;
23407 if cursor_is_placed_after_comment_marker {
23408 Some(delimiter.clone())
23409 } else {
23410 None
23411 }
23412}
23413
23414fn documentation_delimiter_for_newline(
23415 start_point: &Point,
23416 buffer: &MultiBufferSnapshot,
23417 language: &LanguageScope,
23418 newline_formatting: &mut NewlineFormatting,
23419) -> Option<Arc<str>> {
23420 let BlockCommentConfig {
23421 start: start_tag,
23422 end: end_tag,
23423 prefix: delimiter,
23424 tab_size: len,
23425 } = language.documentation_comment()?;
23426 let is_within_block_comment = buffer
23427 .language_scope_at(*start_point)
23428 .is_some_and(|scope| scope.override_name() == Some("comment"));
23429 if !is_within_block_comment {
23430 return None;
23431 }
23432
23433 let (snapshot, range) = buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
23434
23435 let num_of_whitespaces = snapshot
23436 .chars_for_range(range.clone())
23437 .take_while(|c| c.is_whitespace())
23438 .count();
23439
23440 // 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.
23441 let column = start_point.column;
23442 let cursor_is_after_start_tag = {
23443 let start_tag_len = start_tag.len();
23444 let start_tag_line = snapshot
23445 .chars_for_range(range.clone())
23446 .skip(num_of_whitespaces)
23447 .take(start_tag_len)
23448 .collect::<String>();
23449 if start_tag_line.starts_with(start_tag.as_ref()) {
23450 num_of_whitespaces + start_tag_len <= column as usize
23451 } else {
23452 false
23453 }
23454 };
23455
23456 let cursor_is_after_delimiter = {
23457 let delimiter_trim = delimiter.trim_end();
23458 let delimiter_line = snapshot
23459 .chars_for_range(range.clone())
23460 .skip(num_of_whitespaces)
23461 .take(delimiter_trim.len())
23462 .collect::<String>();
23463 if delimiter_line.starts_with(delimiter_trim) {
23464 num_of_whitespaces + delimiter_trim.len() <= column as usize
23465 } else {
23466 false
23467 }
23468 };
23469
23470 let cursor_is_before_end_tag_if_exists = {
23471 let mut char_position = 0u32;
23472 let mut end_tag_offset = None;
23473
23474 'outer: for chunk in snapshot.text_for_range(range) {
23475 if let Some(byte_pos) = chunk.find(&**end_tag) {
23476 let chars_before_match = chunk[..byte_pos].chars().count() as u32;
23477 end_tag_offset = Some(char_position + chars_before_match);
23478 break 'outer;
23479 }
23480 char_position += chunk.chars().count() as u32;
23481 }
23482
23483 if let Some(end_tag_offset) = end_tag_offset {
23484 let cursor_is_before_end_tag = column <= end_tag_offset;
23485 if cursor_is_after_start_tag {
23486 if cursor_is_before_end_tag {
23487 newline_formatting.insert_extra_newline = true;
23488 }
23489 let cursor_is_at_start_of_end_tag = column == end_tag_offset;
23490 if cursor_is_at_start_of_end_tag {
23491 newline_formatting.indent_on_extra_newline.len = *len;
23492 }
23493 }
23494 cursor_is_before_end_tag
23495 } else {
23496 true
23497 }
23498 };
23499
23500 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
23501 && cursor_is_before_end_tag_if_exists
23502 {
23503 if cursor_is_after_start_tag {
23504 newline_formatting.indent_on_newline.len = *len;
23505 }
23506 Some(delimiter.clone())
23507 } else {
23508 None
23509 }
23510}
23511
23512#[derive(Debug, Default)]
23513struct NewlineFormatting {
23514 insert_extra_newline: bool,
23515 indent_on_newline: IndentSize,
23516 indent_on_extra_newline: IndentSize,
23517}
23518
23519impl NewlineFormatting {
23520 fn new(
23521 buffer: &MultiBufferSnapshot,
23522 range: Range<MultiBufferOffset>,
23523 language: &LanguageScope,
23524 ) -> Self {
23525 Self {
23526 insert_extra_newline: Self::insert_extra_newline_brackets(
23527 buffer,
23528 range.clone(),
23529 language,
23530 ) || Self::insert_extra_newline_tree_sitter(buffer, range),
23531 indent_on_newline: IndentSize::spaces(0),
23532 indent_on_extra_newline: IndentSize::spaces(0),
23533 }
23534 }
23535
23536 fn insert_extra_newline_brackets(
23537 buffer: &MultiBufferSnapshot,
23538 range: Range<MultiBufferOffset>,
23539 language: &language::LanguageScope,
23540 ) -> bool {
23541 let leading_whitespace_len = buffer
23542 .reversed_chars_at(range.start)
23543 .take_while(|c| c.is_whitespace() && *c != '\n')
23544 .map(|c| c.len_utf8())
23545 .sum::<usize>();
23546 let trailing_whitespace_len = buffer
23547 .chars_at(range.end)
23548 .take_while(|c| c.is_whitespace() && *c != '\n')
23549 .map(|c| c.len_utf8())
23550 .sum::<usize>();
23551 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23552
23553 language.brackets().any(|(pair, enabled)| {
23554 let pair_start = pair.start.trim_end();
23555 let pair_end = pair.end.trim_start();
23556
23557 enabled
23558 && pair.newline
23559 && buffer.contains_str_at(range.end, pair_end)
23560 && buffer.contains_str_at(
23561 range.start.saturating_sub_usize(pair_start.len()),
23562 pair_start,
23563 )
23564 })
23565 }
23566
23567 fn insert_extra_newline_tree_sitter(
23568 buffer: &MultiBufferSnapshot,
23569 range: Range<MultiBufferOffset>,
23570 ) -> bool {
23571 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23572 [(buffer, range, _)] => (*buffer, range.clone()),
23573 _ => return false,
23574 };
23575 let pair = {
23576 let mut result: Option<BracketMatch<usize>> = None;
23577
23578 for pair in buffer
23579 .all_bracket_ranges(range.start.0..range.end.0)
23580 .filter(move |pair| {
23581 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23582 })
23583 {
23584 let len = pair.close_range.end - pair.open_range.start;
23585
23586 if let Some(existing) = &result {
23587 let existing_len = existing.close_range.end - existing.open_range.start;
23588 if len > existing_len {
23589 continue;
23590 }
23591 }
23592
23593 result = Some(pair);
23594 }
23595
23596 result
23597 };
23598 let Some(pair) = pair else {
23599 return false;
23600 };
23601 pair.newline_only
23602 && buffer
23603 .chars_for_range(pair.open_range.end..range.start.0)
23604 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23605 .all(|c| c.is_whitespace() && c != '\n')
23606 }
23607}
23608
23609fn update_uncommitted_diff_for_buffer(
23610 editor: Entity<Editor>,
23611 project: &Entity<Project>,
23612 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23613 buffer: Entity<MultiBuffer>,
23614 cx: &mut App,
23615) -> Task<()> {
23616 let mut tasks = Vec::new();
23617 project.update(cx, |project, cx| {
23618 for buffer in buffers {
23619 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23620 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23621 }
23622 }
23623 });
23624 cx.spawn(async move |cx| {
23625 let diffs = future::join_all(tasks).await;
23626 if editor
23627 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23628 .unwrap_or(false)
23629 {
23630 return;
23631 }
23632
23633 buffer
23634 .update(cx, |buffer, cx| {
23635 for diff in diffs.into_iter().flatten() {
23636 buffer.add_diff(diff, cx);
23637 }
23638 })
23639 .ok();
23640 })
23641}
23642
23643fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23644 let tab_size = tab_size.get() as usize;
23645 let mut width = offset;
23646
23647 for ch in text.chars() {
23648 width += if ch == '\t' {
23649 tab_size - (width % tab_size)
23650 } else {
23651 1
23652 };
23653 }
23654
23655 width - offset
23656}
23657
23658#[cfg(test)]
23659mod tests {
23660 use super::*;
23661
23662 #[test]
23663 fn test_string_size_with_expanded_tabs() {
23664 let nz = |val| NonZeroU32::new(val).unwrap();
23665 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23666 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23667 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23668 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23669 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23670 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23671 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23672 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23673 }
23674}
23675
23676/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23677struct WordBreakingTokenizer<'a> {
23678 input: &'a str,
23679}
23680
23681impl<'a> WordBreakingTokenizer<'a> {
23682 fn new(input: &'a str) -> Self {
23683 Self { input }
23684 }
23685}
23686
23687fn is_char_ideographic(ch: char) -> bool {
23688 use unicode_script::Script::*;
23689 use unicode_script::UnicodeScript;
23690 matches!(ch.script(), Han | Tangut | Yi)
23691}
23692
23693fn is_grapheme_ideographic(text: &str) -> bool {
23694 text.chars().any(is_char_ideographic)
23695}
23696
23697fn is_grapheme_whitespace(text: &str) -> bool {
23698 text.chars().any(|x| x.is_whitespace())
23699}
23700
23701fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23702 text.chars()
23703 .next()
23704 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23705}
23706
23707#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23708enum WordBreakToken<'a> {
23709 Word { token: &'a str, grapheme_len: usize },
23710 InlineWhitespace { token: &'a str, grapheme_len: usize },
23711 Newline,
23712}
23713
23714impl<'a> Iterator for WordBreakingTokenizer<'a> {
23715 /// Yields a span, the count of graphemes in the token, and whether it was
23716 /// whitespace. Note that it also breaks at word boundaries.
23717 type Item = WordBreakToken<'a>;
23718
23719 fn next(&mut self) -> Option<Self::Item> {
23720 use unicode_segmentation::UnicodeSegmentation;
23721 if self.input.is_empty() {
23722 return None;
23723 }
23724
23725 let mut iter = self.input.graphemes(true).peekable();
23726 let mut offset = 0;
23727 let mut grapheme_len = 0;
23728 if let Some(first_grapheme) = iter.next() {
23729 let is_newline = first_grapheme == "\n";
23730 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23731 offset += first_grapheme.len();
23732 grapheme_len += 1;
23733 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23734 if let Some(grapheme) = iter.peek().copied()
23735 && should_stay_with_preceding_ideograph(grapheme)
23736 {
23737 offset += grapheme.len();
23738 grapheme_len += 1;
23739 }
23740 } else {
23741 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23742 let mut next_word_bound = words.peek().copied();
23743 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23744 next_word_bound = words.next();
23745 }
23746 while let Some(grapheme) = iter.peek().copied() {
23747 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23748 break;
23749 };
23750 if is_grapheme_whitespace(grapheme) != is_whitespace
23751 || (grapheme == "\n") != is_newline
23752 {
23753 break;
23754 };
23755 offset += grapheme.len();
23756 grapheme_len += 1;
23757 iter.next();
23758 }
23759 }
23760 let token = &self.input[..offset];
23761 self.input = &self.input[offset..];
23762 if token == "\n" {
23763 Some(WordBreakToken::Newline)
23764 } else if is_whitespace {
23765 Some(WordBreakToken::InlineWhitespace {
23766 token,
23767 grapheme_len,
23768 })
23769 } else {
23770 Some(WordBreakToken::Word {
23771 token,
23772 grapheme_len,
23773 })
23774 }
23775 } else {
23776 None
23777 }
23778 }
23779}
23780
23781#[test]
23782fn test_word_breaking_tokenizer() {
23783 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23784 ("", &[]),
23785 (" ", &[whitespace(" ", 2)]),
23786 ("Ʒ", &[word("Ʒ", 1)]),
23787 ("Ǽ", &[word("Ǽ", 1)]),
23788 ("⋑", &[word("⋑", 1)]),
23789 ("⋑⋑", &[word("⋑⋑", 2)]),
23790 (
23791 "原理,进而",
23792 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23793 ),
23794 (
23795 "hello world",
23796 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23797 ),
23798 (
23799 "hello, world",
23800 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23801 ),
23802 (
23803 " hello world",
23804 &[
23805 whitespace(" ", 2),
23806 word("hello", 5),
23807 whitespace(" ", 1),
23808 word("world", 5),
23809 ],
23810 ),
23811 (
23812 "这是什么 \n 钢笔",
23813 &[
23814 word("这", 1),
23815 word("是", 1),
23816 word("什", 1),
23817 word("么", 1),
23818 whitespace(" ", 1),
23819 newline(),
23820 whitespace(" ", 1),
23821 word("钢", 1),
23822 word("笔", 1),
23823 ],
23824 ),
23825 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23826 ];
23827
23828 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23829 WordBreakToken::Word {
23830 token,
23831 grapheme_len,
23832 }
23833 }
23834
23835 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23836 WordBreakToken::InlineWhitespace {
23837 token,
23838 grapheme_len,
23839 }
23840 }
23841
23842 fn newline() -> WordBreakToken<'static> {
23843 WordBreakToken::Newline
23844 }
23845
23846 for (input, result) in tests {
23847 assert_eq!(
23848 WordBreakingTokenizer::new(input)
23849 .collect::<Vec<_>>()
23850 .as_slice(),
23851 *result,
23852 );
23853 }
23854}
23855
23856fn wrap_with_prefix(
23857 first_line_prefix: String,
23858 subsequent_lines_prefix: String,
23859 unwrapped_text: String,
23860 wrap_column: usize,
23861 tab_size: NonZeroU32,
23862 preserve_existing_whitespace: bool,
23863) -> String {
23864 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23865 let subsequent_lines_prefix_len =
23866 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23867 let mut wrapped_text = String::new();
23868 let mut current_line = first_line_prefix;
23869 let mut is_first_line = true;
23870
23871 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23872 let mut current_line_len = first_line_prefix_len;
23873 let mut in_whitespace = false;
23874 for token in tokenizer {
23875 let have_preceding_whitespace = in_whitespace;
23876 match token {
23877 WordBreakToken::Word {
23878 token,
23879 grapheme_len,
23880 } => {
23881 in_whitespace = false;
23882 let current_prefix_len = if is_first_line {
23883 first_line_prefix_len
23884 } else {
23885 subsequent_lines_prefix_len
23886 };
23887 if current_line_len + grapheme_len > wrap_column
23888 && current_line_len != current_prefix_len
23889 {
23890 wrapped_text.push_str(current_line.trim_end());
23891 wrapped_text.push('\n');
23892 is_first_line = false;
23893 current_line = subsequent_lines_prefix.clone();
23894 current_line_len = subsequent_lines_prefix_len;
23895 }
23896 current_line.push_str(token);
23897 current_line_len += grapheme_len;
23898 }
23899 WordBreakToken::InlineWhitespace {
23900 mut token,
23901 mut grapheme_len,
23902 } => {
23903 in_whitespace = true;
23904 if have_preceding_whitespace && !preserve_existing_whitespace {
23905 continue;
23906 }
23907 if !preserve_existing_whitespace {
23908 // Keep a single whitespace grapheme as-is
23909 if let Some(first) =
23910 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23911 {
23912 token = first;
23913 } else {
23914 token = " ";
23915 }
23916 grapheme_len = 1;
23917 }
23918 let current_prefix_len = if is_first_line {
23919 first_line_prefix_len
23920 } else {
23921 subsequent_lines_prefix_len
23922 };
23923 if current_line_len + grapheme_len > wrap_column {
23924 wrapped_text.push_str(current_line.trim_end());
23925 wrapped_text.push('\n');
23926 is_first_line = false;
23927 current_line = subsequent_lines_prefix.clone();
23928 current_line_len = subsequent_lines_prefix_len;
23929 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23930 current_line.push_str(token);
23931 current_line_len += grapheme_len;
23932 }
23933 }
23934 WordBreakToken::Newline => {
23935 in_whitespace = true;
23936 let current_prefix_len = if is_first_line {
23937 first_line_prefix_len
23938 } else {
23939 subsequent_lines_prefix_len
23940 };
23941 if preserve_existing_whitespace {
23942 wrapped_text.push_str(current_line.trim_end());
23943 wrapped_text.push('\n');
23944 is_first_line = false;
23945 current_line = subsequent_lines_prefix.clone();
23946 current_line_len = subsequent_lines_prefix_len;
23947 } else if have_preceding_whitespace {
23948 continue;
23949 } else if current_line_len + 1 > wrap_column
23950 && current_line_len != current_prefix_len
23951 {
23952 wrapped_text.push_str(current_line.trim_end());
23953 wrapped_text.push('\n');
23954 is_first_line = false;
23955 current_line = subsequent_lines_prefix.clone();
23956 current_line_len = subsequent_lines_prefix_len;
23957 } else if current_line_len != current_prefix_len {
23958 current_line.push(' ');
23959 current_line_len += 1;
23960 }
23961 }
23962 }
23963 }
23964
23965 if !current_line.is_empty() {
23966 wrapped_text.push_str(¤t_line);
23967 }
23968 wrapped_text
23969}
23970
23971#[test]
23972fn test_wrap_with_prefix() {
23973 assert_eq!(
23974 wrap_with_prefix(
23975 "# ".to_string(),
23976 "# ".to_string(),
23977 "abcdefg".to_string(),
23978 4,
23979 NonZeroU32::new(4).unwrap(),
23980 false,
23981 ),
23982 "# abcdefg"
23983 );
23984 assert_eq!(
23985 wrap_with_prefix(
23986 "".to_string(),
23987 "".to_string(),
23988 "\thello world".to_string(),
23989 8,
23990 NonZeroU32::new(4).unwrap(),
23991 false,
23992 ),
23993 "hello\nworld"
23994 );
23995 assert_eq!(
23996 wrap_with_prefix(
23997 "// ".to_string(),
23998 "// ".to_string(),
23999 "xx \nyy zz aa bb cc".to_string(),
24000 12,
24001 NonZeroU32::new(4).unwrap(),
24002 false,
24003 ),
24004 "// xx yy zz\n// aa bb cc"
24005 );
24006 assert_eq!(
24007 wrap_with_prefix(
24008 String::new(),
24009 String::new(),
24010 "这是什么 \n 钢笔".to_string(),
24011 3,
24012 NonZeroU32::new(4).unwrap(),
24013 false,
24014 ),
24015 "这是什\n么 钢\n笔"
24016 );
24017 assert_eq!(
24018 wrap_with_prefix(
24019 String::new(),
24020 String::new(),
24021 format!("foo{}bar", '\u{2009}'), // thin space
24022 80,
24023 NonZeroU32::new(4).unwrap(),
24024 false,
24025 ),
24026 format!("foo{}bar", '\u{2009}')
24027 );
24028}
24029
24030pub trait CollaborationHub {
24031 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
24032 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
24033 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
24034}
24035
24036impl CollaborationHub for Entity<Project> {
24037 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
24038 self.read(cx).collaborators()
24039 }
24040
24041 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
24042 self.read(cx).user_store().read(cx).participant_indices()
24043 }
24044
24045 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
24046 let this = self.read(cx);
24047 let user_ids = this.collaborators().values().map(|c| c.user_id);
24048 this.user_store().read(cx).participant_names(user_ids, cx)
24049 }
24050}
24051
24052pub trait SemanticsProvider {
24053 fn hover(
24054 &self,
24055 buffer: &Entity<Buffer>,
24056 position: text::Anchor,
24057 cx: &mut App,
24058 ) -> Option<Task<Option<Vec<project::Hover>>>>;
24059
24060 fn inline_values(
24061 &self,
24062 buffer_handle: Entity<Buffer>,
24063 range: Range<text::Anchor>,
24064 cx: &mut App,
24065 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
24066
24067 fn applicable_inlay_chunks(
24068 &self,
24069 buffer: &Entity<Buffer>,
24070 ranges: &[Range<text::Anchor>],
24071 cx: &mut App,
24072 ) -> Vec<Range<BufferRow>>;
24073
24074 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24075
24076 fn inlay_hints(
24077 &self,
24078 invalidate: InvalidationStrategy,
24079 buffer: Entity<Buffer>,
24080 ranges: Vec<Range<text::Anchor>>,
24081 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24082 cx: &mut App,
24083 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24084
24085 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24086
24087 fn document_highlights(
24088 &self,
24089 buffer: &Entity<Buffer>,
24090 position: text::Anchor,
24091 cx: &mut App,
24092 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24093
24094 fn definitions(
24095 &self,
24096 buffer: &Entity<Buffer>,
24097 position: text::Anchor,
24098 kind: GotoDefinitionKind,
24099 cx: &mut App,
24100 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24101
24102 fn range_for_rename(
24103 &self,
24104 buffer: &Entity<Buffer>,
24105 position: text::Anchor,
24106 cx: &mut App,
24107 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24108
24109 fn perform_rename(
24110 &self,
24111 buffer: &Entity<Buffer>,
24112 position: text::Anchor,
24113 new_name: String,
24114 cx: &mut App,
24115 ) -> Option<Task<Result<ProjectTransaction>>>;
24116}
24117
24118pub trait CompletionProvider {
24119 fn completions(
24120 &self,
24121 excerpt_id: ExcerptId,
24122 buffer: &Entity<Buffer>,
24123 buffer_position: text::Anchor,
24124 trigger: CompletionContext,
24125 window: &mut Window,
24126 cx: &mut Context<Editor>,
24127 ) -> Task<Result<Vec<CompletionResponse>>>;
24128
24129 fn resolve_completions(
24130 &self,
24131 _buffer: Entity<Buffer>,
24132 _completion_indices: Vec<usize>,
24133 _completions: Rc<RefCell<Box<[Completion]>>>,
24134 _cx: &mut Context<Editor>,
24135 ) -> Task<Result<bool>> {
24136 Task::ready(Ok(false))
24137 }
24138
24139 fn apply_additional_edits_for_completion(
24140 &self,
24141 _buffer: Entity<Buffer>,
24142 _completions: Rc<RefCell<Box<[Completion]>>>,
24143 _completion_index: usize,
24144 _push_to_history: bool,
24145 _cx: &mut Context<Editor>,
24146 ) -> Task<Result<Option<language::Transaction>>> {
24147 Task::ready(Ok(None))
24148 }
24149
24150 fn is_completion_trigger(
24151 &self,
24152 buffer: &Entity<Buffer>,
24153 position: language::Anchor,
24154 text: &str,
24155 trigger_in_words: bool,
24156 cx: &mut Context<Editor>,
24157 ) -> bool;
24158
24159 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24160
24161 fn sort_completions(&self) -> bool {
24162 true
24163 }
24164
24165 fn filter_completions(&self) -> bool {
24166 true
24167 }
24168
24169 fn show_snippets(&self) -> bool {
24170 false
24171 }
24172}
24173
24174pub trait CodeActionProvider {
24175 fn id(&self) -> Arc<str>;
24176
24177 fn code_actions(
24178 &self,
24179 buffer: &Entity<Buffer>,
24180 range: Range<text::Anchor>,
24181 window: &mut Window,
24182 cx: &mut App,
24183 ) -> Task<Result<Vec<CodeAction>>>;
24184
24185 fn apply_code_action(
24186 &self,
24187 buffer_handle: Entity<Buffer>,
24188 action: CodeAction,
24189 excerpt_id: ExcerptId,
24190 push_to_history: bool,
24191 window: &mut Window,
24192 cx: &mut App,
24193 ) -> Task<Result<ProjectTransaction>>;
24194}
24195
24196impl CodeActionProvider for Entity<Project> {
24197 fn id(&self) -> Arc<str> {
24198 "project".into()
24199 }
24200
24201 fn code_actions(
24202 &self,
24203 buffer: &Entity<Buffer>,
24204 range: Range<text::Anchor>,
24205 _window: &mut Window,
24206 cx: &mut App,
24207 ) -> Task<Result<Vec<CodeAction>>> {
24208 self.update(cx, |project, cx| {
24209 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24210 let code_actions = project.code_actions(buffer, range, None, cx);
24211 cx.background_spawn(async move {
24212 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24213 Ok(code_lens_actions
24214 .context("code lens fetch")?
24215 .into_iter()
24216 .flatten()
24217 .chain(
24218 code_actions
24219 .context("code action fetch")?
24220 .into_iter()
24221 .flatten(),
24222 )
24223 .collect())
24224 })
24225 })
24226 }
24227
24228 fn apply_code_action(
24229 &self,
24230 buffer_handle: Entity<Buffer>,
24231 action: CodeAction,
24232 _excerpt_id: ExcerptId,
24233 push_to_history: bool,
24234 _window: &mut Window,
24235 cx: &mut App,
24236 ) -> Task<Result<ProjectTransaction>> {
24237 self.update(cx, |project, cx| {
24238 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24239 })
24240 }
24241}
24242
24243fn snippet_completions(
24244 project: &Project,
24245 buffer: &Entity<Buffer>,
24246 buffer_anchor: text::Anchor,
24247 classifier: CharClassifier,
24248 cx: &mut App,
24249) -> Task<Result<CompletionResponse>> {
24250 let languages = buffer.read(cx).languages_at(buffer_anchor);
24251 let snippet_store = project.snippets().read(cx);
24252
24253 let scopes: Vec<_> = languages
24254 .iter()
24255 .filter_map(|language| {
24256 let language_name = language.lsp_id();
24257 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24258
24259 if snippets.is_empty() {
24260 None
24261 } else {
24262 Some((language.default_scope(), snippets))
24263 }
24264 })
24265 .collect();
24266
24267 if scopes.is_empty() {
24268 return Task::ready(Ok(CompletionResponse {
24269 completions: vec![],
24270 display_options: CompletionDisplayOptions::default(),
24271 is_incomplete: false,
24272 }));
24273 }
24274
24275 let snapshot = buffer.read(cx).text_snapshot();
24276 let executor = cx.background_executor().clone();
24277
24278 cx.background_spawn(async move {
24279 let is_word_char = |c| classifier.is_word(c);
24280
24281 let mut is_incomplete = false;
24282 let mut completions: Vec<Completion> = Vec::new();
24283
24284 const MAX_PREFIX_LEN: usize = 128;
24285 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24286 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24287 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24288
24289 let max_buffer_window: String = snapshot
24290 .text_for_range(window_start..buffer_offset)
24291 .collect();
24292
24293 if max_buffer_window.is_empty() {
24294 return Ok(CompletionResponse {
24295 completions: vec![],
24296 display_options: CompletionDisplayOptions::default(),
24297 is_incomplete: true,
24298 });
24299 }
24300
24301 for (_scope, snippets) in scopes.into_iter() {
24302 // Sort snippets by word count to match longer snippet prefixes first.
24303 let mut sorted_snippet_candidates = snippets
24304 .iter()
24305 .enumerate()
24306 .flat_map(|(snippet_ix, snippet)| {
24307 snippet
24308 .prefix
24309 .iter()
24310 .enumerate()
24311 .map(move |(prefix_ix, prefix)| {
24312 let word_count =
24313 snippet_candidate_suffixes(prefix, is_word_char).count();
24314 ((snippet_ix, prefix_ix), prefix, word_count)
24315 })
24316 })
24317 .collect_vec();
24318 sorted_snippet_candidates
24319 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24320
24321 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24322
24323 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24324 .take(
24325 sorted_snippet_candidates
24326 .first()
24327 .map(|(_, _, word_count)| *word_count)
24328 .unwrap_or_default(),
24329 )
24330 .collect_vec();
24331
24332 const MAX_RESULTS: usize = 100;
24333 // Each match also remembers how many characters from the buffer it consumed
24334 let mut matches: Vec<(StringMatch, usize)> = vec![];
24335
24336 let mut snippet_list_cutoff_index = 0;
24337 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24338 let word_count = buffer_index + 1;
24339 // Increase `snippet_list_cutoff_index` until we have all of the
24340 // snippets with sufficiently many words.
24341 while sorted_snippet_candidates
24342 .get(snippet_list_cutoff_index)
24343 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24344 *snippet_word_count >= word_count
24345 })
24346 {
24347 snippet_list_cutoff_index += 1;
24348 }
24349
24350 // Take only the candidates with at least `word_count` many words
24351 let snippet_candidates_at_word_len =
24352 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24353
24354 let candidates = snippet_candidates_at_word_len
24355 .iter()
24356 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24357 .enumerate() // index in `sorted_snippet_candidates`
24358 // First char must match
24359 .filter(|(_ix, prefix)| {
24360 itertools::equal(
24361 prefix
24362 .chars()
24363 .next()
24364 .into_iter()
24365 .flat_map(|c| c.to_lowercase()),
24366 buffer_window
24367 .chars()
24368 .next()
24369 .into_iter()
24370 .flat_map(|c| c.to_lowercase()),
24371 )
24372 })
24373 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24374 .collect::<Vec<StringMatchCandidate>>();
24375
24376 matches.extend(
24377 fuzzy::match_strings(
24378 &candidates,
24379 &buffer_window,
24380 buffer_window.chars().any(|c| c.is_uppercase()),
24381 true,
24382 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24383 &Default::default(),
24384 executor.clone(),
24385 )
24386 .await
24387 .into_iter()
24388 .map(|string_match| (string_match, buffer_window.len())),
24389 );
24390
24391 if matches.len() >= MAX_RESULTS {
24392 break;
24393 }
24394 }
24395
24396 let to_lsp = |point: &text::Anchor| {
24397 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24398 point_to_lsp(end)
24399 };
24400 let lsp_end = to_lsp(&buffer_anchor);
24401
24402 if matches.len() >= MAX_RESULTS {
24403 is_incomplete = true;
24404 }
24405
24406 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24407 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24408 sorted_snippet_candidates[string_match.candidate_id];
24409 let snippet = &snippets[snippet_index];
24410 let start = buffer_offset - buffer_window_len;
24411 let start = snapshot.anchor_before(start);
24412 let range = start..buffer_anchor;
24413 let lsp_start = to_lsp(&start);
24414 let lsp_range = lsp::Range {
24415 start: lsp_start,
24416 end: lsp_end,
24417 };
24418 Completion {
24419 replace_range: range,
24420 new_text: snippet.body.clone(),
24421 source: CompletionSource::Lsp {
24422 insert_range: None,
24423 server_id: LanguageServerId(usize::MAX),
24424 resolved: true,
24425 lsp_completion: Box::new(lsp::CompletionItem {
24426 label: snippet.prefix.first().unwrap().clone(),
24427 kind: Some(CompletionItemKind::SNIPPET),
24428 label_details: snippet.description.as_ref().map(|description| {
24429 lsp::CompletionItemLabelDetails {
24430 detail: Some(description.clone()),
24431 description: None,
24432 }
24433 }),
24434 insert_text_format: Some(InsertTextFormat::SNIPPET),
24435 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24436 lsp::InsertReplaceEdit {
24437 new_text: snippet.body.clone(),
24438 insert: lsp_range,
24439 replace: lsp_range,
24440 },
24441 )),
24442 filter_text: Some(snippet.body.clone()),
24443 sort_text: Some(char::MAX.to_string()),
24444 ..lsp::CompletionItem::default()
24445 }),
24446 lsp_defaults: None,
24447 },
24448 label: CodeLabel {
24449 text: matching_prefix.clone(),
24450 runs: Vec::new(),
24451 filter_range: 0..matching_prefix.len(),
24452 },
24453 icon_path: None,
24454 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24455 single_line: snippet.name.clone().into(),
24456 plain_text: snippet
24457 .description
24458 .clone()
24459 .map(|description| description.into()),
24460 }),
24461 insert_text_mode: None,
24462 confirm: None,
24463 match_start: Some(start),
24464 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24465 }
24466 }));
24467 }
24468
24469 Ok(CompletionResponse {
24470 completions,
24471 display_options: CompletionDisplayOptions::default(),
24472 is_incomplete,
24473 })
24474 })
24475}
24476
24477impl CompletionProvider for Entity<Project> {
24478 fn completions(
24479 &self,
24480 _excerpt_id: ExcerptId,
24481 buffer: &Entity<Buffer>,
24482 buffer_position: text::Anchor,
24483 options: CompletionContext,
24484 _window: &mut Window,
24485 cx: &mut Context<Editor>,
24486 ) -> Task<Result<Vec<CompletionResponse>>> {
24487 self.update(cx, |project, cx| {
24488 let task = project.completions(buffer, buffer_position, options, cx);
24489 cx.background_spawn(task)
24490 })
24491 }
24492
24493 fn resolve_completions(
24494 &self,
24495 buffer: Entity<Buffer>,
24496 completion_indices: Vec<usize>,
24497 completions: Rc<RefCell<Box<[Completion]>>>,
24498 cx: &mut Context<Editor>,
24499 ) -> Task<Result<bool>> {
24500 self.update(cx, |project, cx| {
24501 project.lsp_store().update(cx, |lsp_store, cx| {
24502 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24503 })
24504 })
24505 }
24506
24507 fn apply_additional_edits_for_completion(
24508 &self,
24509 buffer: Entity<Buffer>,
24510 completions: Rc<RefCell<Box<[Completion]>>>,
24511 completion_index: usize,
24512 push_to_history: bool,
24513 cx: &mut Context<Editor>,
24514 ) -> Task<Result<Option<language::Transaction>>> {
24515 self.update(cx, |project, cx| {
24516 project.lsp_store().update(cx, |lsp_store, cx| {
24517 lsp_store.apply_additional_edits_for_completion(
24518 buffer,
24519 completions,
24520 completion_index,
24521 push_to_history,
24522 cx,
24523 )
24524 })
24525 })
24526 }
24527
24528 fn is_completion_trigger(
24529 &self,
24530 buffer: &Entity<Buffer>,
24531 position: language::Anchor,
24532 text: &str,
24533 trigger_in_words: bool,
24534 cx: &mut Context<Editor>,
24535 ) -> bool {
24536 let mut chars = text.chars();
24537 let char = if let Some(char) = chars.next() {
24538 char
24539 } else {
24540 return false;
24541 };
24542 if chars.next().is_some() {
24543 return false;
24544 }
24545
24546 let buffer = buffer.read(cx);
24547 let snapshot = buffer.snapshot();
24548 let classifier = snapshot
24549 .char_classifier_at(position)
24550 .scope_context(Some(CharScopeContext::Completion));
24551 if trigger_in_words && classifier.is_word(char) {
24552 return true;
24553 }
24554
24555 buffer.completion_triggers().contains(text)
24556 }
24557
24558 fn show_snippets(&self) -> bool {
24559 true
24560 }
24561}
24562
24563impl SemanticsProvider for Entity<Project> {
24564 fn hover(
24565 &self,
24566 buffer: &Entity<Buffer>,
24567 position: text::Anchor,
24568 cx: &mut App,
24569 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24570 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24571 }
24572
24573 fn document_highlights(
24574 &self,
24575 buffer: &Entity<Buffer>,
24576 position: text::Anchor,
24577 cx: &mut App,
24578 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24579 Some(self.update(cx, |project, cx| {
24580 project.document_highlights(buffer, position, cx)
24581 }))
24582 }
24583
24584 fn definitions(
24585 &self,
24586 buffer: &Entity<Buffer>,
24587 position: text::Anchor,
24588 kind: GotoDefinitionKind,
24589 cx: &mut App,
24590 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24591 Some(self.update(cx, |project, cx| match kind {
24592 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24593 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24594 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24595 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24596 }))
24597 }
24598
24599 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24600 self.update(cx, |project, cx| {
24601 if project
24602 .active_debug_session(cx)
24603 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24604 {
24605 return true;
24606 }
24607
24608 buffer.update(cx, |buffer, cx| {
24609 project.any_language_server_supports_inlay_hints(buffer, cx)
24610 })
24611 })
24612 }
24613
24614 fn inline_values(
24615 &self,
24616 buffer_handle: Entity<Buffer>,
24617 range: Range<text::Anchor>,
24618 cx: &mut App,
24619 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24620 self.update(cx, |project, cx| {
24621 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24622
24623 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24624 })
24625 }
24626
24627 fn applicable_inlay_chunks(
24628 &self,
24629 buffer: &Entity<Buffer>,
24630 ranges: &[Range<text::Anchor>],
24631 cx: &mut App,
24632 ) -> Vec<Range<BufferRow>> {
24633 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24634 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24635 })
24636 }
24637
24638 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24639 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24640 lsp_store.invalidate_inlay_hints(for_buffers)
24641 });
24642 }
24643
24644 fn inlay_hints(
24645 &self,
24646 invalidate: InvalidationStrategy,
24647 buffer: Entity<Buffer>,
24648 ranges: Vec<Range<text::Anchor>>,
24649 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24650 cx: &mut App,
24651 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24652 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24653 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24654 }))
24655 }
24656
24657 fn range_for_rename(
24658 &self,
24659 buffer: &Entity<Buffer>,
24660 position: text::Anchor,
24661 cx: &mut App,
24662 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24663 Some(self.update(cx, |project, cx| {
24664 let buffer = buffer.clone();
24665 let task = project.prepare_rename(buffer.clone(), position, cx);
24666 cx.spawn(async move |_, cx| {
24667 Ok(match task.await? {
24668 PrepareRenameResponse::Success(range) => Some(range),
24669 PrepareRenameResponse::InvalidPosition => None,
24670 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24671 // Fallback on using TreeSitter info to determine identifier range
24672 buffer.read_with(cx, |buffer, _| {
24673 let snapshot = buffer.snapshot();
24674 let (range, kind) = snapshot.surrounding_word(position, None);
24675 if kind != Some(CharKind::Word) {
24676 return None;
24677 }
24678 Some(
24679 snapshot.anchor_before(range.start)
24680 ..snapshot.anchor_after(range.end),
24681 )
24682 })?
24683 }
24684 })
24685 })
24686 }))
24687 }
24688
24689 fn perform_rename(
24690 &self,
24691 buffer: &Entity<Buffer>,
24692 position: text::Anchor,
24693 new_name: String,
24694 cx: &mut App,
24695 ) -> Option<Task<Result<ProjectTransaction>>> {
24696 Some(self.update(cx, |project, cx| {
24697 project.perform_rename(buffer.clone(), position, new_name, cx)
24698 }))
24699 }
24700}
24701
24702fn consume_contiguous_rows(
24703 contiguous_row_selections: &mut Vec<Selection<Point>>,
24704 selection: &Selection<Point>,
24705 display_map: &DisplaySnapshot,
24706 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24707) -> (MultiBufferRow, MultiBufferRow) {
24708 contiguous_row_selections.push(selection.clone());
24709 let start_row = starting_row(selection, display_map);
24710 let mut end_row = ending_row(selection, display_map);
24711
24712 while let Some(next_selection) = selections.peek() {
24713 if next_selection.start.row <= end_row.0 {
24714 end_row = ending_row(next_selection, display_map);
24715 contiguous_row_selections.push(selections.next().unwrap().clone());
24716 } else {
24717 break;
24718 }
24719 }
24720 (start_row, end_row)
24721}
24722
24723fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24724 if selection.start.column > 0 {
24725 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24726 } else {
24727 MultiBufferRow(selection.start.row)
24728 }
24729}
24730
24731fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24732 if next_selection.end.column > 0 || next_selection.is_empty() {
24733 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24734 } else {
24735 MultiBufferRow(next_selection.end.row)
24736 }
24737}
24738
24739impl EditorSnapshot {
24740 pub fn remote_selections_in_range<'a>(
24741 &'a self,
24742 range: &'a Range<Anchor>,
24743 collaboration_hub: &dyn CollaborationHub,
24744 cx: &'a App,
24745 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24746 let participant_names = collaboration_hub.user_names(cx);
24747 let participant_indices = collaboration_hub.user_participant_indices(cx);
24748 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24749 let collaborators_by_replica_id = collaborators_by_peer_id
24750 .values()
24751 .map(|collaborator| (collaborator.replica_id, collaborator))
24752 .collect::<HashMap<_, _>>();
24753 self.buffer_snapshot()
24754 .selections_in_range(range, false)
24755 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24756 if replica_id == ReplicaId::AGENT {
24757 Some(RemoteSelection {
24758 replica_id,
24759 selection,
24760 cursor_shape,
24761 line_mode,
24762 collaborator_id: CollaboratorId::Agent,
24763 user_name: Some("Agent".into()),
24764 color: cx.theme().players().agent(),
24765 })
24766 } else {
24767 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24768 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24769 let user_name = participant_names.get(&collaborator.user_id).cloned();
24770 Some(RemoteSelection {
24771 replica_id,
24772 selection,
24773 cursor_shape,
24774 line_mode,
24775 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24776 user_name,
24777 color: if let Some(index) = participant_index {
24778 cx.theme().players().color_for_participant(index.0)
24779 } else {
24780 cx.theme().players().absent()
24781 },
24782 })
24783 }
24784 })
24785 }
24786
24787 pub fn hunks_for_ranges(
24788 &self,
24789 ranges: impl IntoIterator<Item = Range<Point>>,
24790 ) -> Vec<MultiBufferDiffHunk> {
24791 let mut hunks = Vec::new();
24792 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24793 HashMap::default();
24794 for query_range in ranges {
24795 let query_rows =
24796 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24797 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24798 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24799 ) {
24800 // Include deleted hunks that are adjacent to the query range, because
24801 // otherwise they would be missed.
24802 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24803 if hunk.status().is_deleted() {
24804 intersects_range |= hunk.row_range.start == query_rows.end;
24805 intersects_range |= hunk.row_range.end == query_rows.start;
24806 }
24807 if intersects_range {
24808 if !processed_buffer_rows
24809 .entry(hunk.buffer_id)
24810 .or_default()
24811 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24812 {
24813 continue;
24814 }
24815 hunks.push(hunk);
24816 }
24817 }
24818 }
24819
24820 hunks
24821 }
24822
24823 fn display_diff_hunks_for_rows<'a>(
24824 &'a self,
24825 display_rows: Range<DisplayRow>,
24826 folded_buffers: &'a HashSet<BufferId>,
24827 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24828 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24829 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24830
24831 self.buffer_snapshot()
24832 .diff_hunks_in_range(buffer_start..buffer_end)
24833 .filter_map(|hunk| {
24834 if folded_buffers.contains(&hunk.buffer_id) {
24835 return None;
24836 }
24837
24838 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24839 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24840
24841 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24842 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24843
24844 let display_hunk = if hunk_display_start.column() != 0 {
24845 DisplayDiffHunk::Folded {
24846 display_row: hunk_display_start.row(),
24847 }
24848 } else {
24849 let mut end_row = hunk_display_end.row();
24850 if hunk_display_end.column() > 0 {
24851 end_row.0 += 1;
24852 }
24853 let is_created_file = hunk.is_created_file();
24854
24855 DisplayDiffHunk::Unfolded {
24856 status: hunk.status(),
24857 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24858 ..hunk.diff_base_byte_range.end.0,
24859 word_diffs: hunk.word_diffs,
24860 display_row_range: hunk_display_start.row()..end_row,
24861 multi_buffer_range: Anchor::range_in_buffer(
24862 hunk.excerpt_id,
24863 hunk.buffer_range,
24864 ),
24865 is_created_file,
24866 }
24867 };
24868
24869 Some(display_hunk)
24870 })
24871 }
24872
24873 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24874 self.display_snapshot
24875 .buffer_snapshot()
24876 .language_at(position)
24877 }
24878
24879 pub fn is_focused(&self) -> bool {
24880 self.is_focused
24881 }
24882
24883 pub fn placeholder_text(&self) -> Option<String> {
24884 self.placeholder_display_snapshot
24885 .as_ref()
24886 .map(|display_map| display_map.text())
24887 }
24888
24889 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24890 self.scroll_anchor.scroll_position(&self.display_snapshot)
24891 }
24892
24893 pub fn gutter_dimensions(
24894 &self,
24895 font_id: FontId,
24896 font_size: Pixels,
24897 style: &EditorStyle,
24898 window: &mut Window,
24899 cx: &App,
24900 ) -> GutterDimensions {
24901 if self.show_gutter
24902 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24903 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24904 {
24905 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24906 matches!(
24907 ProjectSettings::get_global(cx).git.git_gutter,
24908 GitGutterSetting::TrackedFiles
24909 )
24910 });
24911 let gutter_settings = EditorSettings::get_global(cx).gutter;
24912 let show_line_numbers = self
24913 .show_line_numbers
24914 .unwrap_or(gutter_settings.line_numbers);
24915 let line_gutter_width = if show_line_numbers {
24916 // Avoid flicker-like gutter resizes when the line number gains another digit by
24917 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24918 let min_width_for_number_on_gutter =
24919 ch_advance * gutter_settings.min_line_number_digits as f32;
24920 self.max_line_number_width(style, window)
24921 .max(min_width_for_number_on_gutter)
24922 } else {
24923 0.0.into()
24924 };
24925
24926 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24927 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24928
24929 let git_blame_entries_width =
24930 self.git_blame_gutter_max_author_length
24931 .map(|max_author_length| {
24932 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24933 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24934
24935 /// The number of characters to dedicate to gaps and margins.
24936 const SPACING_WIDTH: usize = 4;
24937
24938 let max_char_count = max_author_length.min(renderer.max_author_length())
24939 + ::git::SHORT_SHA_LENGTH
24940 + MAX_RELATIVE_TIMESTAMP.len()
24941 + SPACING_WIDTH;
24942
24943 ch_advance * max_char_count
24944 });
24945
24946 let is_singleton = self.buffer_snapshot().is_singleton();
24947
24948 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24949 left_padding += if !is_singleton {
24950 ch_width * 4.0
24951 } else if show_runnables || show_breakpoints {
24952 ch_width * 3.0
24953 } else if show_git_gutter && show_line_numbers {
24954 ch_width * 2.0
24955 } else if show_git_gutter || show_line_numbers {
24956 ch_width
24957 } else {
24958 px(0.)
24959 };
24960
24961 let shows_folds = is_singleton && gutter_settings.folds;
24962
24963 let right_padding = if shows_folds && show_line_numbers {
24964 ch_width * 4.0
24965 } else if shows_folds || (!is_singleton && show_line_numbers) {
24966 ch_width * 3.0
24967 } else if show_line_numbers {
24968 ch_width
24969 } else {
24970 px(0.)
24971 };
24972
24973 GutterDimensions {
24974 left_padding,
24975 right_padding,
24976 width: line_gutter_width + left_padding + right_padding,
24977 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24978 git_blame_entries_width,
24979 }
24980 } else if self.offset_content {
24981 GutterDimensions::default_with_margin(font_id, font_size, cx)
24982 } else {
24983 GutterDimensions::default()
24984 }
24985 }
24986
24987 pub fn render_crease_toggle(
24988 &self,
24989 buffer_row: MultiBufferRow,
24990 row_contains_cursor: bool,
24991 editor: Entity<Editor>,
24992 window: &mut Window,
24993 cx: &mut App,
24994 ) -> Option<AnyElement> {
24995 let folded = self.is_line_folded(buffer_row);
24996 let mut is_foldable = false;
24997
24998 if let Some(crease) = self
24999 .crease_snapshot
25000 .query_row(buffer_row, self.buffer_snapshot())
25001 {
25002 is_foldable = true;
25003 match crease {
25004 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
25005 if let Some(render_toggle) = render_toggle {
25006 let toggle_callback =
25007 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
25008 if folded {
25009 editor.update(cx, |editor, cx| {
25010 editor.fold_at(buffer_row, window, cx)
25011 });
25012 } else {
25013 editor.update(cx, |editor, cx| {
25014 editor.unfold_at(buffer_row, window, cx)
25015 });
25016 }
25017 });
25018 return Some((render_toggle)(
25019 buffer_row,
25020 folded,
25021 toggle_callback,
25022 window,
25023 cx,
25024 ));
25025 }
25026 }
25027 }
25028 }
25029
25030 is_foldable |= self.starts_indent(buffer_row);
25031
25032 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
25033 Some(
25034 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
25035 .toggle_state(folded)
25036 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
25037 if folded {
25038 this.unfold_at(buffer_row, window, cx);
25039 } else {
25040 this.fold_at(buffer_row, window, cx);
25041 }
25042 }))
25043 .into_any_element(),
25044 )
25045 } else {
25046 None
25047 }
25048 }
25049
25050 pub fn render_crease_trailer(
25051 &self,
25052 buffer_row: MultiBufferRow,
25053 window: &mut Window,
25054 cx: &mut App,
25055 ) -> Option<AnyElement> {
25056 let folded = self.is_line_folded(buffer_row);
25057 if let Crease::Inline { render_trailer, .. } = self
25058 .crease_snapshot
25059 .query_row(buffer_row, self.buffer_snapshot())?
25060 {
25061 let render_trailer = render_trailer.as_ref()?;
25062 Some(render_trailer(buffer_row, folded, window, cx))
25063 } else {
25064 None
25065 }
25066 }
25067
25068 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
25069 let digit_count = self.widest_line_number().ilog10() + 1;
25070 column_pixels(style, digit_count as usize, window)
25071 }
25072}
25073
25074pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25075 let font_size = style.text.font_size.to_pixels(window.rem_size());
25076 let layout = window.text_system().shape_line(
25077 SharedString::from(" ".repeat(column)),
25078 font_size,
25079 &[TextRun {
25080 len: column,
25081 font: style.text.font(),
25082 color: Hsla::default(),
25083 ..Default::default()
25084 }],
25085 None,
25086 );
25087
25088 layout.width
25089}
25090
25091impl Deref for EditorSnapshot {
25092 type Target = DisplaySnapshot;
25093
25094 fn deref(&self) -> &Self::Target {
25095 &self.display_snapshot
25096 }
25097}
25098
25099#[derive(Clone, Debug, PartialEq, Eq)]
25100pub enum EditorEvent {
25101 InputIgnored {
25102 text: Arc<str>,
25103 },
25104 InputHandled {
25105 utf16_range_to_replace: Option<Range<isize>>,
25106 text: Arc<str>,
25107 },
25108 ExcerptsAdded {
25109 buffer: Entity<Buffer>,
25110 predecessor: ExcerptId,
25111 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25112 },
25113 ExcerptsRemoved {
25114 ids: Vec<ExcerptId>,
25115 removed_buffer_ids: Vec<BufferId>,
25116 },
25117 BufferFoldToggled {
25118 ids: Vec<ExcerptId>,
25119 folded: bool,
25120 },
25121 ExcerptsEdited {
25122 ids: Vec<ExcerptId>,
25123 },
25124 ExcerptsExpanded {
25125 ids: Vec<ExcerptId>,
25126 },
25127 BufferEdited,
25128 Edited {
25129 transaction_id: clock::Lamport,
25130 },
25131 Reparsed(BufferId),
25132 Focused,
25133 FocusedIn,
25134 Blurred,
25135 DirtyChanged,
25136 Saved,
25137 TitleChanged,
25138 SelectionsChanged {
25139 local: bool,
25140 },
25141 ScrollPositionChanged {
25142 local: bool,
25143 autoscroll: bool,
25144 },
25145 TransactionUndone {
25146 transaction_id: clock::Lamport,
25147 },
25148 TransactionBegun {
25149 transaction_id: clock::Lamport,
25150 },
25151 CursorShapeChanged,
25152 BreadcrumbsChanged,
25153 PushedToNavHistory {
25154 anchor: Anchor,
25155 is_deactivate: bool,
25156 },
25157}
25158
25159impl EventEmitter<EditorEvent> for Editor {}
25160
25161impl Focusable for Editor {
25162 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25163 self.focus_handle.clone()
25164 }
25165}
25166
25167impl Render for Editor {
25168 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25169 EditorElement::new(&cx.entity(), self.create_style(cx))
25170 }
25171}
25172
25173impl EntityInputHandler for Editor {
25174 fn text_for_range(
25175 &mut self,
25176 range_utf16: Range<usize>,
25177 adjusted_range: &mut Option<Range<usize>>,
25178 _: &mut Window,
25179 cx: &mut Context<Self>,
25180 ) -> Option<String> {
25181 let snapshot = self.buffer.read(cx).read(cx);
25182 let start = snapshot.clip_offset_utf16(
25183 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25184 Bias::Left,
25185 );
25186 let end = snapshot.clip_offset_utf16(
25187 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25188 Bias::Right,
25189 );
25190 if (start.0.0..end.0.0) != range_utf16 {
25191 adjusted_range.replace(start.0.0..end.0.0);
25192 }
25193 Some(snapshot.text_for_range(start..end).collect())
25194 }
25195
25196 fn selected_text_range(
25197 &mut self,
25198 ignore_disabled_input: bool,
25199 _: &mut Window,
25200 cx: &mut Context<Self>,
25201 ) -> Option<UTF16Selection> {
25202 // Prevent the IME menu from appearing when holding down an alphabetic key
25203 // while input is disabled.
25204 if !ignore_disabled_input && !self.input_enabled {
25205 return None;
25206 }
25207
25208 let selection = self
25209 .selections
25210 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25211 let range = selection.range();
25212
25213 Some(UTF16Selection {
25214 range: range.start.0.0..range.end.0.0,
25215 reversed: selection.reversed,
25216 })
25217 }
25218
25219 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25220 let snapshot = self.buffer.read(cx).read(cx);
25221 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25222 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25223 }
25224
25225 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25226 self.clear_highlights::<InputComposition>(cx);
25227 self.ime_transaction.take();
25228 }
25229
25230 fn replace_text_in_range(
25231 &mut self,
25232 range_utf16: Option<Range<usize>>,
25233 text: &str,
25234 window: &mut Window,
25235 cx: &mut Context<Self>,
25236 ) {
25237 if !self.input_enabled {
25238 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25239 return;
25240 }
25241
25242 self.transact(window, cx, |this, window, cx| {
25243 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25244 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25245 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25246 Some(this.selection_replacement_ranges(range_utf16, cx))
25247 } else {
25248 this.marked_text_ranges(cx)
25249 };
25250
25251 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25252 let newest_selection_id = this.selections.newest_anchor().id;
25253 this.selections
25254 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25255 .iter()
25256 .zip(ranges_to_replace.iter())
25257 .find_map(|(selection, range)| {
25258 if selection.id == newest_selection_id {
25259 Some(
25260 (range.start.0.0 as isize - selection.head().0.0 as isize)
25261 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25262 )
25263 } else {
25264 None
25265 }
25266 })
25267 });
25268
25269 cx.emit(EditorEvent::InputHandled {
25270 utf16_range_to_replace: range_to_replace,
25271 text: text.into(),
25272 });
25273
25274 if let Some(new_selected_ranges) = new_selected_ranges {
25275 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25276 selections.select_ranges(new_selected_ranges)
25277 });
25278 this.backspace(&Default::default(), window, cx);
25279 }
25280
25281 this.handle_input(text, window, cx);
25282 });
25283
25284 if let Some(transaction) = self.ime_transaction {
25285 self.buffer.update(cx, |buffer, cx| {
25286 buffer.group_until_transaction(transaction, cx);
25287 });
25288 }
25289
25290 self.unmark_text(window, cx);
25291 }
25292
25293 fn replace_and_mark_text_in_range(
25294 &mut self,
25295 range_utf16: Option<Range<usize>>,
25296 text: &str,
25297 new_selected_range_utf16: Option<Range<usize>>,
25298 window: &mut Window,
25299 cx: &mut Context<Self>,
25300 ) {
25301 if !self.input_enabled {
25302 return;
25303 }
25304
25305 let transaction = self.transact(window, cx, |this, window, cx| {
25306 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25307 let snapshot = this.buffer.read(cx).read(cx);
25308 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25309 for marked_range in &mut marked_ranges {
25310 marked_range.end = marked_range.start + relative_range_utf16.end;
25311 marked_range.start += relative_range_utf16.start;
25312 marked_range.start =
25313 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25314 marked_range.end =
25315 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25316 }
25317 }
25318 Some(marked_ranges)
25319 } else if let Some(range_utf16) = range_utf16 {
25320 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25321 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25322 Some(this.selection_replacement_ranges(range_utf16, cx))
25323 } else {
25324 None
25325 };
25326
25327 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25328 let newest_selection_id = this.selections.newest_anchor().id;
25329 this.selections
25330 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25331 .iter()
25332 .zip(ranges_to_replace.iter())
25333 .find_map(|(selection, range)| {
25334 if selection.id == newest_selection_id {
25335 Some(
25336 (range.start.0.0 as isize - selection.head().0.0 as isize)
25337 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25338 )
25339 } else {
25340 None
25341 }
25342 })
25343 });
25344
25345 cx.emit(EditorEvent::InputHandled {
25346 utf16_range_to_replace: range_to_replace,
25347 text: text.into(),
25348 });
25349
25350 if let Some(ranges) = ranges_to_replace {
25351 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25352 s.select_ranges(ranges)
25353 });
25354 }
25355
25356 let marked_ranges = {
25357 let snapshot = this.buffer.read(cx).read(cx);
25358 this.selections
25359 .disjoint_anchors_arc()
25360 .iter()
25361 .map(|selection| {
25362 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25363 })
25364 .collect::<Vec<_>>()
25365 };
25366
25367 if text.is_empty() {
25368 this.unmark_text(window, cx);
25369 } else {
25370 this.highlight_text::<InputComposition>(
25371 marked_ranges.clone(),
25372 HighlightStyle {
25373 underline: Some(UnderlineStyle {
25374 thickness: px(1.),
25375 color: None,
25376 wavy: false,
25377 }),
25378 ..Default::default()
25379 },
25380 cx,
25381 );
25382 }
25383
25384 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25385 let use_autoclose = this.use_autoclose;
25386 let use_auto_surround = this.use_auto_surround;
25387 this.set_use_autoclose(false);
25388 this.set_use_auto_surround(false);
25389 this.handle_input(text, window, cx);
25390 this.set_use_autoclose(use_autoclose);
25391 this.set_use_auto_surround(use_auto_surround);
25392
25393 if let Some(new_selected_range) = new_selected_range_utf16 {
25394 let snapshot = this.buffer.read(cx).read(cx);
25395 let new_selected_ranges = marked_ranges
25396 .into_iter()
25397 .map(|marked_range| {
25398 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25399 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25400 insertion_start.0 + new_selected_range.start,
25401 ));
25402 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25403 insertion_start.0 + new_selected_range.end,
25404 ));
25405 snapshot.clip_offset_utf16(new_start, Bias::Left)
25406 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25407 })
25408 .collect::<Vec<_>>();
25409
25410 drop(snapshot);
25411 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25412 selections.select_ranges(new_selected_ranges)
25413 });
25414 }
25415 });
25416
25417 self.ime_transaction = self.ime_transaction.or(transaction);
25418 if let Some(transaction) = self.ime_transaction {
25419 self.buffer.update(cx, |buffer, cx| {
25420 buffer.group_until_transaction(transaction, cx);
25421 });
25422 }
25423
25424 if self.text_highlights::<InputComposition>(cx).is_none() {
25425 self.ime_transaction.take();
25426 }
25427 }
25428
25429 fn bounds_for_range(
25430 &mut self,
25431 range_utf16: Range<usize>,
25432 element_bounds: gpui::Bounds<Pixels>,
25433 window: &mut Window,
25434 cx: &mut Context<Self>,
25435 ) -> Option<gpui::Bounds<Pixels>> {
25436 let text_layout_details = self.text_layout_details(window);
25437 let CharacterDimensions {
25438 em_width,
25439 em_advance,
25440 line_height,
25441 } = self.character_dimensions(window);
25442
25443 let snapshot = self.snapshot(window, cx);
25444 let scroll_position = snapshot.scroll_position();
25445 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25446
25447 let start =
25448 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25449 let x = Pixels::from(
25450 ScrollOffset::from(
25451 snapshot.x_for_display_point(start, &text_layout_details)
25452 + self.gutter_dimensions.full_width(),
25453 ) - scroll_left,
25454 );
25455 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25456
25457 Some(Bounds {
25458 origin: element_bounds.origin + point(x, y),
25459 size: size(em_width, line_height),
25460 })
25461 }
25462
25463 fn character_index_for_point(
25464 &mut self,
25465 point: gpui::Point<Pixels>,
25466 _window: &mut Window,
25467 _cx: &mut Context<Self>,
25468 ) -> Option<usize> {
25469 let position_map = self.last_position_map.as_ref()?;
25470 if !position_map.text_hitbox.contains(&point) {
25471 return None;
25472 }
25473 let display_point = position_map.point_for_position(point).previous_valid;
25474 let anchor = position_map
25475 .snapshot
25476 .display_point_to_anchor(display_point, Bias::Left);
25477 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25478 Some(utf16_offset.0.0)
25479 }
25480
25481 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25482 self.input_enabled
25483 }
25484}
25485
25486trait SelectionExt {
25487 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25488 fn spanned_rows(
25489 &self,
25490 include_end_if_at_line_start: bool,
25491 map: &DisplaySnapshot,
25492 ) -> Range<MultiBufferRow>;
25493}
25494
25495impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25496 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25497 let start = self
25498 .start
25499 .to_point(map.buffer_snapshot())
25500 .to_display_point(map);
25501 let end = self
25502 .end
25503 .to_point(map.buffer_snapshot())
25504 .to_display_point(map);
25505 if self.reversed {
25506 end..start
25507 } else {
25508 start..end
25509 }
25510 }
25511
25512 fn spanned_rows(
25513 &self,
25514 include_end_if_at_line_start: bool,
25515 map: &DisplaySnapshot,
25516 ) -> Range<MultiBufferRow> {
25517 let start = self.start.to_point(map.buffer_snapshot());
25518 let mut end = self.end.to_point(map.buffer_snapshot());
25519 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25520 end.row -= 1;
25521 }
25522
25523 let buffer_start = map.prev_line_boundary(start).0;
25524 let buffer_end = map.next_line_boundary(end).0;
25525 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25526 }
25527}
25528
25529impl<T: InvalidationRegion> InvalidationStack<T> {
25530 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25531 where
25532 S: Clone + ToOffset,
25533 {
25534 while let Some(region) = self.last() {
25535 let all_selections_inside_invalidation_ranges =
25536 if selections.len() == region.ranges().len() {
25537 selections
25538 .iter()
25539 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25540 .all(|(selection, invalidation_range)| {
25541 let head = selection.head().to_offset(buffer);
25542 invalidation_range.start <= head && invalidation_range.end >= head
25543 })
25544 } else {
25545 false
25546 };
25547
25548 if all_selections_inside_invalidation_ranges {
25549 break;
25550 } else {
25551 self.pop();
25552 }
25553 }
25554 }
25555}
25556
25557impl<T> Default for InvalidationStack<T> {
25558 fn default() -> Self {
25559 Self(Default::default())
25560 }
25561}
25562
25563impl<T> Deref for InvalidationStack<T> {
25564 type Target = Vec<T>;
25565
25566 fn deref(&self) -> &Self::Target {
25567 &self.0
25568 }
25569}
25570
25571impl<T> DerefMut for InvalidationStack<T> {
25572 fn deref_mut(&mut self) -> &mut Self::Target {
25573 &mut self.0
25574 }
25575}
25576
25577impl InvalidationRegion for SnippetState {
25578 fn ranges(&self) -> &[Range<Anchor>] {
25579 &self.ranges[self.active_index]
25580 }
25581}
25582
25583fn edit_prediction_edit_text(
25584 current_snapshot: &BufferSnapshot,
25585 edits: &[(Range<Anchor>, impl AsRef<str>)],
25586 edit_preview: &EditPreview,
25587 include_deletions: bool,
25588 cx: &App,
25589) -> HighlightedText {
25590 let edits = edits
25591 .iter()
25592 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25593 .collect::<Vec<_>>();
25594
25595 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25596}
25597
25598fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25599 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25600 // Just show the raw edit text with basic styling
25601 let mut text = String::new();
25602 let mut highlights = Vec::new();
25603
25604 let insertion_highlight_style = HighlightStyle {
25605 color: Some(cx.theme().colors().text),
25606 ..Default::default()
25607 };
25608
25609 for (_, edit_text) in edits {
25610 let start_offset = text.len();
25611 text.push_str(edit_text);
25612 let end_offset = text.len();
25613
25614 if start_offset < end_offset {
25615 highlights.push((start_offset..end_offset, insertion_highlight_style));
25616 }
25617 }
25618
25619 HighlightedText {
25620 text: text.into(),
25621 highlights,
25622 }
25623}
25624
25625pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25626 match severity {
25627 lsp::DiagnosticSeverity::ERROR => colors.error,
25628 lsp::DiagnosticSeverity::WARNING => colors.warning,
25629 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25630 lsp::DiagnosticSeverity::HINT => colors.info,
25631 _ => colors.ignored,
25632 }
25633}
25634
25635pub fn styled_runs_for_code_label<'a>(
25636 label: &'a CodeLabel,
25637 syntax_theme: &'a theme::SyntaxTheme,
25638 local_player: &'a theme::PlayerColor,
25639) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25640 let fade_out = HighlightStyle {
25641 fade_out: Some(0.35),
25642 ..Default::default()
25643 };
25644
25645 let mut prev_end = label.filter_range.end;
25646 label
25647 .runs
25648 .iter()
25649 .enumerate()
25650 .flat_map(move |(ix, (range, highlight_id))| {
25651 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25652 HighlightStyle {
25653 color: Some(local_player.cursor),
25654 ..Default::default()
25655 }
25656 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25657 HighlightStyle {
25658 background_color: Some(local_player.selection),
25659 ..Default::default()
25660 }
25661 } else if let Some(style) = highlight_id.style(syntax_theme) {
25662 style
25663 } else {
25664 return Default::default();
25665 };
25666 let muted_style = style.highlight(fade_out);
25667
25668 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25669 if range.start >= label.filter_range.end {
25670 if range.start > prev_end {
25671 runs.push((prev_end..range.start, fade_out));
25672 }
25673 runs.push((range.clone(), muted_style));
25674 } else if range.end <= label.filter_range.end {
25675 runs.push((range.clone(), style));
25676 } else {
25677 runs.push((range.start..label.filter_range.end, style));
25678 runs.push((label.filter_range.end..range.end, muted_style));
25679 }
25680 prev_end = cmp::max(prev_end, range.end);
25681
25682 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25683 runs.push((prev_end..label.text.len(), fade_out));
25684 }
25685
25686 runs
25687 })
25688}
25689
25690pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25691 let mut prev_index = 0;
25692 let mut prev_codepoint: Option<char> = None;
25693 text.char_indices()
25694 .chain([(text.len(), '\0')])
25695 .filter_map(move |(index, codepoint)| {
25696 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25697 let is_boundary = index == text.len()
25698 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25699 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25700 if is_boundary {
25701 let chunk = &text[prev_index..index];
25702 prev_index = index;
25703 Some(chunk)
25704 } else {
25705 None
25706 }
25707 })
25708}
25709
25710/// Given a string of text immediately before the cursor, iterates over possible
25711/// strings a snippet could match to. More precisely: returns an iterator over
25712/// suffixes of `text` created by splitting at word boundaries (before & after
25713/// every non-word character).
25714///
25715/// Shorter suffixes are returned first.
25716pub(crate) fn snippet_candidate_suffixes(
25717 text: &str,
25718 is_word_char: impl Fn(char) -> bool,
25719) -> impl std::iter::Iterator<Item = &str> {
25720 let mut prev_index = text.len();
25721 let mut prev_codepoint = None;
25722 text.char_indices()
25723 .rev()
25724 .chain([(0, '\0')])
25725 .filter_map(move |(index, codepoint)| {
25726 let prev_index = std::mem::replace(&mut prev_index, index);
25727 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25728 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25729 None
25730 } else {
25731 let chunk = &text[prev_index..]; // go to end of string
25732 Some(chunk)
25733 }
25734 })
25735}
25736
25737pub trait RangeToAnchorExt: Sized {
25738 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25739
25740 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25741 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25742 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25743 }
25744}
25745
25746impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25747 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25748 let start_offset = self.start.to_offset(snapshot);
25749 let end_offset = self.end.to_offset(snapshot);
25750 if start_offset == end_offset {
25751 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25752 } else {
25753 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25754 }
25755 }
25756}
25757
25758pub trait RowExt {
25759 fn as_f64(&self) -> f64;
25760
25761 fn next_row(&self) -> Self;
25762
25763 fn previous_row(&self) -> Self;
25764
25765 fn minus(&self, other: Self) -> u32;
25766}
25767
25768impl RowExt for DisplayRow {
25769 fn as_f64(&self) -> f64 {
25770 self.0 as _
25771 }
25772
25773 fn next_row(&self) -> Self {
25774 Self(self.0 + 1)
25775 }
25776
25777 fn previous_row(&self) -> Self {
25778 Self(self.0.saturating_sub(1))
25779 }
25780
25781 fn minus(&self, other: Self) -> u32 {
25782 self.0 - other.0
25783 }
25784}
25785
25786impl RowExt for MultiBufferRow {
25787 fn as_f64(&self) -> f64 {
25788 self.0 as _
25789 }
25790
25791 fn next_row(&self) -> Self {
25792 Self(self.0 + 1)
25793 }
25794
25795 fn previous_row(&self) -> Self {
25796 Self(self.0.saturating_sub(1))
25797 }
25798
25799 fn minus(&self, other: Self) -> u32 {
25800 self.0 - other.0
25801 }
25802}
25803
25804trait RowRangeExt {
25805 type Row;
25806
25807 fn len(&self) -> usize;
25808
25809 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25810}
25811
25812impl RowRangeExt for Range<MultiBufferRow> {
25813 type Row = MultiBufferRow;
25814
25815 fn len(&self) -> usize {
25816 (self.end.0 - self.start.0) as usize
25817 }
25818
25819 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25820 (self.start.0..self.end.0).map(MultiBufferRow)
25821 }
25822}
25823
25824impl RowRangeExt for Range<DisplayRow> {
25825 type Row = DisplayRow;
25826
25827 fn len(&self) -> usize {
25828 (self.end.0 - self.start.0) as usize
25829 }
25830
25831 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25832 (self.start.0..self.end.0).map(DisplayRow)
25833 }
25834}
25835
25836/// If select range has more than one line, we
25837/// just point the cursor to range.start.
25838fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25839 if range.start.row == range.end.row {
25840 range
25841 } else {
25842 range.start..range.start
25843 }
25844}
25845pub struct KillRing(ClipboardItem);
25846impl Global for KillRing {}
25847
25848const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25849
25850enum BreakpointPromptEditAction {
25851 Log,
25852 Condition,
25853 HitCondition,
25854}
25855
25856struct BreakpointPromptEditor {
25857 pub(crate) prompt: Entity<Editor>,
25858 editor: WeakEntity<Editor>,
25859 breakpoint_anchor: Anchor,
25860 breakpoint: Breakpoint,
25861 edit_action: BreakpointPromptEditAction,
25862 block_ids: HashSet<CustomBlockId>,
25863 editor_margins: Arc<Mutex<EditorMargins>>,
25864 _subscriptions: Vec<Subscription>,
25865}
25866
25867impl BreakpointPromptEditor {
25868 const MAX_LINES: u8 = 4;
25869
25870 fn new(
25871 editor: WeakEntity<Editor>,
25872 breakpoint_anchor: Anchor,
25873 breakpoint: Breakpoint,
25874 edit_action: BreakpointPromptEditAction,
25875 window: &mut Window,
25876 cx: &mut Context<Self>,
25877 ) -> Self {
25878 let base_text = match edit_action {
25879 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25880 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25881 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25882 }
25883 .map(|msg| msg.to_string())
25884 .unwrap_or_default();
25885
25886 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25887 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25888
25889 let prompt = cx.new(|cx| {
25890 let mut prompt = Editor::new(
25891 EditorMode::AutoHeight {
25892 min_lines: 1,
25893 max_lines: Some(Self::MAX_LINES as usize),
25894 },
25895 buffer,
25896 None,
25897 window,
25898 cx,
25899 );
25900 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25901 prompt.set_show_cursor_when_unfocused(false, cx);
25902 prompt.set_placeholder_text(
25903 match edit_action {
25904 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25905 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25906 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25907 },
25908 window,
25909 cx,
25910 );
25911
25912 prompt
25913 });
25914
25915 Self {
25916 prompt,
25917 editor,
25918 breakpoint_anchor,
25919 breakpoint,
25920 edit_action,
25921 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25922 block_ids: Default::default(),
25923 _subscriptions: vec![],
25924 }
25925 }
25926
25927 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25928 self.block_ids.extend(block_ids)
25929 }
25930
25931 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25932 if let Some(editor) = self.editor.upgrade() {
25933 let message = self
25934 .prompt
25935 .read(cx)
25936 .buffer
25937 .read(cx)
25938 .as_singleton()
25939 .expect("A multi buffer in breakpoint prompt isn't possible")
25940 .read(cx)
25941 .as_rope()
25942 .to_string();
25943
25944 editor.update(cx, |editor, cx| {
25945 editor.edit_breakpoint_at_anchor(
25946 self.breakpoint_anchor,
25947 self.breakpoint.clone(),
25948 match self.edit_action {
25949 BreakpointPromptEditAction::Log => {
25950 BreakpointEditAction::EditLogMessage(message.into())
25951 }
25952 BreakpointPromptEditAction::Condition => {
25953 BreakpointEditAction::EditCondition(message.into())
25954 }
25955 BreakpointPromptEditAction::HitCondition => {
25956 BreakpointEditAction::EditHitCondition(message.into())
25957 }
25958 },
25959 cx,
25960 );
25961
25962 editor.remove_blocks(self.block_ids.clone(), None, cx);
25963 cx.focus_self(window);
25964 });
25965 }
25966 }
25967
25968 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25969 self.editor
25970 .update(cx, |editor, cx| {
25971 editor.remove_blocks(self.block_ids.clone(), None, cx);
25972 window.focus(&editor.focus_handle, cx);
25973 })
25974 .log_err();
25975 }
25976
25977 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25978 let settings = ThemeSettings::get_global(cx);
25979 let text_style = TextStyle {
25980 color: if self.prompt.read(cx).read_only(cx) {
25981 cx.theme().colors().text_disabled
25982 } else {
25983 cx.theme().colors().text
25984 },
25985 font_family: settings.buffer_font.family.clone(),
25986 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25987 font_size: settings.buffer_font_size(cx).into(),
25988 font_weight: settings.buffer_font.weight,
25989 line_height: relative(settings.buffer_line_height.value()),
25990 ..Default::default()
25991 };
25992 EditorElement::new(
25993 &self.prompt,
25994 EditorStyle {
25995 background: cx.theme().colors().editor_background,
25996 local_player: cx.theme().players().local(),
25997 text: text_style,
25998 ..Default::default()
25999 },
26000 )
26001 }
26002}
26003
26004impl Render for BreakpointPromptEditor {
26005 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26006 let editor_margins = *self.editor_margins.lock();
26007 let gutter_dimensions = editor_margins.gutter;
26008 h_flex()
26009 .key_context("Editor")
26010 .bg(cx.theme().colors().editor_background)
26011 .border_y_1()
26012 .border_color(cx.theme().status().info_border)
26013 .size_full()
26014 .py(window.line_height() / 2.5)
26015 .on_action(cx.listener(Self::confirm))
26016 .on_action(cx.listener(Self::cancel))
26017 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
26018 .child(div().flex_1().child(self.render_prompt_editor(cx)))
26019 }
26020}
26021
26022impl Focusable for BreakpointPromptEditor {
26023 fn focus_handle(&self, cx: &App) -> FocusHandle {
26024 self.prompt.focus_handle(cx)
26025 }
26026}
26027
26028fn all_edits_insertions_or_deletions(
26029 edits: &Vec<(Range<Anchor>, Arc<str>)>,
26030 snapshot: &MultiBufferSnapshot,
26031) -> bool {
26032 let mut all_insertions = true;
26033 let mut all_deletions = true;
26034
26035 for (range, new_text) in edits.iter() {
26036 let range_is_empty = range.to_offset(snapshot).is_empty();
26037 let text_is_empty = new_text.is_empty();
26038
26039 if range_is_empty != text_is_empty {
26040 if range_is_empty {
26041 all_deletions = false;
26042 } else {
26043 all_insertions = false;
26044 }
26045 } else {
26046 return false;
26047 }
26048
26049 if !all_insertions && !all_deletions {
26050 return false;
26051 }
26052 }
26053 all_insertions || all_deletions
26054}
26055
26056struct MissingEditPredictionKeybindingTooltip;
26057
26058impl Render for MissingEditPredictionKeybindingTooltip {
26059 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
26060 ui::tooltip_container(cx, |container, cx| {
26061 container
26062 .flex_shrink_0()
26063 .max_w_80()
26064 .min_h(rems_from_px(124.))
26065 .justify_between()
26066 .child(
26067 v_flex()
26068 .flex_1()
26069 .text_ui_sm(cx)
26070 .child(Label::new("Conflict with Accept Keybinding"))
26071 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
26072 )
26073 .child(
26074 h_flex()
26075 .pb_1()
26076 .gap_1()
26077 .items_end()
26078 .w_full()
26079 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26080 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26081 }))
26082 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26083 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26084 })),
26085 )
26086 })
26087 }
26088}
26089
26090#[derive(Debug, Clone, Copy, PartialEq)]
26091pub struct LineHighlight {
26092 pub background: Background,
26093 pub border: Option<gpui::Hsla>,
26094 pub include_gutter: bool,
26095 pub type_id: Option<TypeId>,
26096}
26097
26098struct LineManipulationResult {
26099 pub new_text: String,
26100 pub line_count_before: usize,
26101 pub line_count_after: usize,
26102}
26103
26104fn render_diff_hunk_controls(
26105 row: u32,
26106 status: &DiffHunkStatus,
26107 hunk_range: Range<Anchor>,
26108 is_created_file: bool,
26109 line_height: Pixels,
26110 editor: &Entity<Editor>,
26111 _window: &mut Window,
26112 cx: &mut App,
26113) -> AnyElement {
26114 h_flex()
26115 .h(line_height)
26116 .mr_1()
26117 .gap_1()
26118 .px_0p5()
26119 .pb_1()
26120 .border_x_1()
26121 .border_b_1()
26122 .border_color(cx.theme().colors().border_variant)
26123 .rounded_b_lg()
26124 .bg(cx.theme().colors().editor_background)
26125 .gap_1()
26126 .block_mouse_except_scroll()
26127 .shadow_md()
26128 .child(if status.has_secondary_hunk() {
26129 Button::new(("stage", row as u64), "Stage")
26130 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26131 .tooltip({
26132 let focus_handle = editor.focus_handle(cx);
26133 move |_window, cx| {
26134 Tooltip::for_action_in(
26135 "Stage Hunk",
26136 &::git::ToggleStaged,
26137 &focus_handle,
26138 cx,
26139 )
26140 }
26141 })
26142 .on_click({
26143 let editor = editor.clone();
26144 move |_event, _window, cx| {
26145 editor.update(cx, |editor, cx| {
26146 editor.stage_or_unstage_diff_hunks(
26147 true,
26148 vec![hunk_range.start..hunk_range.start],
26149 cx,
26150 );
26151 });
26152 }
26153 })
26154 } else {
26155 Button::new(("unstage", row as u64), "Unstage")
26156 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26157 .tooltip({
26158 let focus_handle = editor.focus_handle(cx);
26159 move |_window, cx| {
26160 Tooltip::for_action_in(
26161 "Unstage Hunk",
26162 &::git::ToggleStaged,
26163 &focus_handle,
26164 cx,
26165 )
26166 }
26167 })
26168 .on_click({
26169 let editor = editor.clone();
26170 move |_event, _window, cx| {
26171 editor.update(cx, |editor, cx| {
26172 editor.stage_or_unstage_diff_hunks(
26173 false,
26174 vec![hunk_range.start..hunk_range.start],
26175 cx,
26176 );
26177 });
26178 }
26179 })
26180 })
26181 .child(
26182 Button::new(("restore", row as u64), "Restore")
26183 .tooltip({
26184 let focus_handle = editor.focus_handle(cx);
26185 move |_window, cx| {
26186 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26187 }
26188 })
26189 .on_click({
26190 let editor = editor.clone();
26191 move |_event, window, cx| {
26192 editor.update(cx, |editor, cx| {
26193 let snapshot = editor.snapshot(window, cx);
26194 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26195 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26196 });
26197 }
26198 })
26199 .disabled(is_created_file),
26200 )
26201 .when(
26202 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26203 |el| {
26204 el.child(
26205 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26206 .shape(IconButtonShape::Square)
26207 .icon_size(IconSize::Small)
26208 // .disabled(!has_multiple_hunks)
26209 .tooltip({
26210 let focus_handle = editor.focus_handle(cx);
26211 move |_window, cx| {
26212 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26213 }
26214 })
26215 .on_click({
26216 let editor = editor.clone();
26217 move |_event, window, cx| {
26218 editor.update(cx, |editor, cx| {
26219 let snapshot = editor.snapshot(window, cx);
26220 let position =
26221 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26222 editor.go_to_hunk_before_or_after_position(
26223 &snapshot,
26224 position,
26225 Direction::Next,
26226 window,
26227 cx,
26228 );
26229 editor.expand_selected_diff_hunks(cx);
26230 });
26231 }
26232 }),
26233 )
26234 .child(
26235 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26236 .shape(IconButtonShape::Square)
26237 .icon_size(IconSize::Small)
26238 // .disabled(!has_multiple_hunks)
26239 .tooltip({
26240 let focus_handle = editor.focus_handle(cx);
26241 move |_window, cx| {
26242 Tooltip::for_action_in(
26243 "Previous Hunk",
26244 &GoToPreviousHunk,
26245 &focus_handle,
26246 cx,
26247 )
26248 }
26249 })
26250 .on_click({
26251 let editor = editor.clone();
26252 move |_event, window, cx| {
26253 editor.update(cx, |editor, cx| {
26254 let snapshot = editor.snapshot(window, cx);
26255 let point =
26256 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26257 editor.go_to_hunk_before_or_after_position(
26258 &snapshot,
26259 point,
26260 Direction::Prev,
26261 window,
26262 cx,
26263 );
26264 editor.expand_selected_diff_hunks(cx);
26265 });
26266 }
26267 }),
26268 )
26269 },
26270 )
26271 .into_any_element()
26272}
26273
26274pub fn multibuffer_context_lines(cx: &App) -> u32 {
26275 EditorSettings::try_get(cx)
26276 .map(|settings| settings.excerpt_context_lines)
26277 .unwrap_or(2)
26278 .min(32)
26279}