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, OffsetRangeExt, OutlineItem, Point,
128 Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
129 language_settings::{
130 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
131 all_language_settings, language_settings,
132 },
133 point_from_lsp, point_to_lsp, text_diff_with_options,
134};
135use linked_editing_ranges::refresh_linked_ranges;
136use lsp::{
137 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
138 LanguageServerId,
139};
140use lsp_colors::LspColorData;
141use markdown::Markdown;
142use mouse_context_menu::MouseContextMenu;
143use movement::TextLayoutDetails;
144use multi_buffer::{
145 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
146};
147use parking_lot::Mutex;
148use persistence::DB;
149use project::{
150 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
151 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
152 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
153 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
154 debugger::{
155 breakpoint_store::{
156 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
157 BreakpointStore, BreakpointStoreEvent,
158 },
159 session::{Session, SessionEvent},
160 },
161 git_store::GitStoreEvent,
162 lsp_store::{
163 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
164 OpenLspBufferHandle,
165 },
166 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
167};
168use rand::seq::SliceRandom;
169use rpc::{ErrorCode, ErrorExt, proto::PeerId};
170use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
171use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
172use serde::{Deserialize, Serialize};
173use settings::{
174 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
175 update_settings_file,
176};
177use smallvec::{SmallVec, smallvec};
178use snippet::Snippet;
179use std::{
180 any::{Any, TypeId},
181 borrow::Cow,
182 cell::{OnceCell, RefCell},
183 cmp::{self, Ordering, Reverse},
184 collections::hash_map,
185 iter::{self, Peekable},
186 mem,
187 num::NonZeroU32,
188 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
189 path::{Path, PathBuf},
190 rc::Rc,
191 sync::Arc,
192 time::{Duration, Instant},
193};
194use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
195use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
196use theme::{
197 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
198 observe_buffer_font_size_adjustment,
199};
200use ui::{
201 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
202 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
203};
204use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
205use vim_mode_setting::VimModeSetting;
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 current_line_highlight: Option<CurrentLineHighlight>,
1115 pub collapse_matches: bool,
1116 autoindent_mode: Option<AutoindentMode>,
1117 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1118 input_enabled: bool,
1119 use_modal_editing: bool,
1120 read_only: bool,
1121 leader_id: Option<CollaboratorId>,
1122 remote_id: Option<ViewId>,
1123 pub hover_state: HoverState,
1124 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1125 prev_pressure_stage: Option<PressureStage>,
1126 gutter_hovered: bool,
1127 hovered_link_state: Option<HoveredLinkState>,
1128 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1129 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1130 active_edit_prediction: Option<EditPredictionState>,
1131 /// Used to prevent flickering as the user types while the menu is open
1132 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1133 edit_prediction_settings: EditPredictionSettings,
1134 edit_predictions_hidden_for_vim_mode: bool,
1135 show_edit_predictions_override: Option<bool>,
1136 show_completions_on_input_override: Option<bool>,
1137 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1138 edit_prediction_preview: EditPredictionPreview,
1139 edit_prediction_indent_conflict: bool,
1140 edit_prediction_requires_modifier_in_indent_conflict: bool,
1141 next_inlay_id: usize,
1142 next_color_inlay_id: usize,
1143 _subscriptions: Vec<Subscription>,
1144 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1145 gutter_dimensions: GutterDimensions,
1146 style: Option<EditorStyle>,
1147 text_style_refinement: Option<TextStyleRefinement>,
1148 next_editor_action_id: EditorActionId,
1149 editor_actions: Rc<
1150 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1151 >,
1152 use_autoclose: bool,
1153 use_auto_surround: bool,
1154 auto_replace_emoji_shortcode: bool,
1155 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1156 show_git_blame_gutter: bool,
1157 show_git_blame_inline: bool,
1158 show_git_blame_inline_delay_task: Option<Task<()>>,
1159 git_blame_inline_enabled: bool,
1160 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1161 buffer_serialization: Option<BufferSerialization>,
1162 show_selection_menu: Option<bool>,
1163 blame: Option<Entity<GitBlame>>,
1164 blame_subscription: Option<Subscription>,
1165 custom_context_menu: Option<
1166 Box<
1167 dyn 'static
1168 + Fn(
1169 &mut Self,
1170 DisplayPoint,
1171 &mut Window,
1172 &mut Context<Self>,
1173 ) -> Option<Entity<ui::ContextMenu>>,
1174 >,
1175 >,
1176 last_bounds: Option<Bounds<Pixels>>,
1177 last_position_map: Option<Rc<PositionMap>>,
1178 expect_bounds_change: Option<Bounds<Pixels>>,
1179 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1180 tasks_update_task: Option<Task<()>>,
1181 breakpoint_store: Option<Entity<BreakpointStore>>,
1182 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1183 hovered_diff_hunk_row: Option<DisplayRow>,
1184 pull_diagnostics_task: Task<()>,
1185 pull_diagnostics_background_task: Task<()>,
1186 in_project_search: bool,
1187 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1188 breadcrumb_header: Option<String>,
1189 focused_block: Option<FocusedBlock>,
1190 next_scroll_position: NextScrollCursorCenterTopBottom,
1191 addons: HashMap<TypeId, Box<dyn Addon>>,
1192 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1193 load_diff_task: Option<Shared<Task<()>>>,
1194 /// Whether we are temporarily displaying a diff other than git's
1195 temporary_diff_override: bool,
1196 selection_mark_mode: bool,
1197 toggle_fold_multiple_buffers: Task<()>,
1198 _scroll_cursor_center_top_bottom_task: Task<()>,
1199 serialize_selections: Task<()>,
1200 serialize_folds: Task<()>,
1201 mouse_cursor_hidden: bool,
1202 minimap: Option<Entity<Self>>,
1203 hide_mouse_mode: HideMouseMode,
1204 pub change_list: ChangeList,
1205 inline_value_cache: InlineValueCache,
1206
1207 selection_drag_state: SelectionDragState,
1208 colors: Option<LspColorData>,
1209 post_scroll_update: Task<()>,
1210 refresh_colors_task: Task<()>,
1211 inlay_hints: Option<LspInlayHintData>,
1212 folding_newlines: Task<()>,
1213 select_next_is_case_sensitive: Option<bool>,
1214 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1215 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1216 accent_data: Option<AccentData>,
1217 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1218 use_base_text_line_numbers: bool,
1219}
1220
1221#[derive(Debug, PartialEq)]
1222struct AccentData {
1223 colors: AccentColors,
1224 overrides: Vec<SharedString>,
1225}
1226
1227fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1228 if debounce_ms > 0 {
1229 Some(Duration::from_millis(debounce_ms))
1230 } else {
1231 None
1232 }
1233}
1234
1235#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1236enum NextScrollCursorCenterTopBottom {
1237 #[default]
1238 Center,
1239 Top,
1240 Bottom,
1241}
1242
1243impl NextScrollCursorCenterTopBottom {
1244 fn next(&self) -> Self {
1245 match self {
1246 Self::Center => Self::Top,
1247 Self::Top => Self::Bottom,
1248 Self::Bottom => Self::Center,
1249 }
1250 }
1251}
1252
1253#[derive(Clone)]
1254pub struct EditorSnapshot {
1255 pub mode: EditorMode,
1256 show_gutter: bool,
1257 offset_content: bool,
1258 show_line_numbers: Option<bool>,
1259 show_git_diff_gutter: Option<bool>,
1260 show_code_actions: Option<bool>,
1261 show_runnables: Option<bool>,
1262 show_breakpoints: Option<bool>,
1263 git_blame_gutter_max_author_length: Option<usize>,
1264 pub display_snapshot: DisplaySnapshot,
1265 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1266 is_focused: bool,
1267 scroll_anchor: ScrollAnchor,
1268 ongoing_scroll: OngoingScroll,
1269 current_line_highlight: CurrentLineHighlight,
1270 gutter_hovered: bool,
1271}
1272
1273#[derive(Default, Debug, Clone, Copy)]
1274pub struct GutterDimensions {
1275 pub left_padding: Pixels,
1276 pub right_padding: Pixels,
1277 pub width: Pixels,
1278 pub margin: Pixels,
1279 pub git_blame_entries_width: Option<Pixels>,
1280}
1281
1282impl GutterDimensions {
1283 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1284 Self {
1285 margin: Self::default_gutter_margin(font_id, font_size, cx),
1286 ..Default::default()
1287 }
1288 }
1289
1290 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1291 -cx.text_system().descent(font_id, font_size)
1292 }
1293 /// The full width of the space taken up by the gutter.
1294 pub fn full_width(&self) -> Pixels {
1295 self.margin + self.width
1296 }
1297
1298 /// The width of the space reserved for the fold indicators,
1299 /// use alongside 'justify_end' and `gutter_width` to
1300 /// right align content with the line numbers
1301 pub fn fold_area_width(&self) -> Pixels {
1302 self.margin + self.right_padding
1303 }
1304}
1305
1306struct CharacterDimensions {
1307 em_width: Pixels,
1308 em_advance: Pixels,
1309 line_height: Pixels,
1310}
1311
1312#[derive(Debug)]
1313pub struct RemoteSelection {
1314 pub replica_id: ReplicaId,
1315 pub selection: Selection<Anchor>,
1316 pub cursor_shape: CursorShape,
1317 pub collaborator_id: CollaboratorId,
1318 pub line_mode: bool,
1319 pub user_name: Option<SharedString>,
1320 pub color: PlayerColor,
1321}
1322
1323#[derive(Clone, Debug)]
1324struct SelectionHistoryEntry {
1325 selections: Arc<[Selection<Anchor>]>,
1326 select_next_state: Option<SelectNextState>,
1327 select_prev_state: Option<SelectNextState>,
1328 add_selections_state: Option<AddSelectionsState>,
1329}
1330
1331#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1332enum SelectionHistoryMode {
1333 #[default]
1334 Normal,
1335 Undoing,
1336 Redoing,
1337 Skipping,
1338}
1339
1340#[derive(Clone, PartialEq, Eq, Hash)]
1341struct HoveredCursor {
1342 replica_id: ReplicaId,
1343 selection_id: usize,
1344}
1345
1346#[derive(Debug)]
1347/// SelectionEffects controls the side-effects of updating the selection.
1348///
1349/// The default behaviour does "what you mostly want":
1350/// - it pushes to the nav history if the cursor moved by >10 lines
1351/// - it re-triggers completion requests
1352/// - it scrolls to fit
1353///
1354/// You might want to modify these behaviours. For example when doing a "jump"
1355/// like go to definition, we always want to add to nav history; but when scrolling
1356/// in vim mode we never do.
1357///
1358/// Similarly, you might want to disable scrolling if you don't want the viewport to
1359/// move.
1360#[derive(Clone)]
1361pub struct SelectionEffects {
1362 nav_history: Option<bool>,
1363 completions: bool,
1364 scroll: Option<Autoscroll>,
1365}
1366
1367impl Default for SelectionEffects {
1368 fn default() -> Self {
1369 Self {
1370 nav_history: None,
1371 completions: true,
1372 scroll: Some(Autoscroll::fit()),
1373 }
1374 }
1375}
1376impl SelectionEffects {
1377 pub fn scroll(scroll: Autoscroll) -> Self {
1378 Self {
1379 scroll: Some(scroll),
1380 ..Default::default()
1381 }
1382 }
1383
1384 pub fn no_scroll() -> Self {
1385 Self {
1386 scroll: None,
1387 ..Default::default()
1388 }
1389 }
1390
1391 pub fn completions(self, completions: bool) -> Self {
1392 Self {
1393 completions,
1394 ..self
1395 }
1396 }
1397
1398 pub fn nav_history(self, nav_history: bool) -> Self {
1399 Self {
1400 nav_history: Some(nav_history),
1401 ..self
1402 }
1403 }
1404}
1405
1406struct DeferredSelectionEffectsState {
1407 changed: bool,
1408 effects: SelectionEffects,
1409 old_cursor_position: Anchor,
1410 history_entry: SelectionHistoryEntry,
1411}
1412
1413#[derive(Default)]
1414struct SelectionHistory {
1415 #[allow(clippy::type_complexity)]
1416 selections_by_transaction:
1417 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1418 mode: SelectionHistoryMode,
1419 undo_stack: VecDeque<SelectionHistoryEntry>,
1420 redo_stack: VecDeque<SelectionHistoryEntry>,
1421}
1422
1423impl SelectionHistory {
1424 #[track_caller]
1425 fn insert_transaction(
1426 &mut self,
1427 transaction_id: TransactionId,
1428 selections: Arc<[Selection<Anchor>]>,
1429 ) {
1430 if selections.is_empty() {
1431 log::error!(
1432 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1433 std::panic::Location::caller()
1434 );
1435 return;
1436 }
1437 self.selections_by_transaction
1438 .insert(transaction_id, (selections, None));
1439 }
1440
1441 #[allow(clippy::type_complexity)]
1442 fn transaction(
1443 &self,
1444 transaction_id: TransactionId,
1445 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1446 self.selections_by_transaction.get(&transaction_id)
1447 }
1448
1449 #[allow(clippy::type_complexity)]
1450 fn transaction_mut(
1451 &mut self,
1452 transaction_id: TransactionId,
1453 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1454 self.selections_by_transaction.get_mut(&transaction_id)
1455 }
1456
1457 fn push(&mut self, entry: SelectionHistoryEntry) {
1458 if !entry.selections.is_empty() {
1459 match self.mode {
1460 SelectionHistoryMode::Normal => {
1461 self.push_undo(entry);
1462 self.redo_stack.clear();
1463 }
1464 SelectionHistoryMode::Undoing => self.push_redo(entry),
1465 SelectionHistoryMode::Redoing => self.push_undo(entry),
1466 SelectionHistoryMode::Skipping => {}
1467 }
1468 }
1469 }
1470
1471 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1472 if self
1473 .undo_stack
1474 .back()
1475 .is_none_or(|e| e.selections != entry.selections)
1476 {
1477 self.undo_stack.push_back(entry);
1478 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1479 self.undo_stack.pop_front();
1480 }
1481 }
1482 }
1483
1484 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1485 if self
1486 .redo_stack
1487 .back()
1488 .is_none_or(|e| e.selections != entry.selections)
1489 {
1490 self.redo_stack.push_back(entry);
1491 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1492 self.redo_stack.pop_front();
1493 }
1494 }
1495 }
1496}
1497
1498#[derive(Clone, Copy)]
1499pub struct RowHighlightOptions {
1500 pub autoscroll: bool,
1501 pub include_gutter: bool,
1502}
1503
1504impl Default for RowHighlightOptions {
1505 fn default() -> Self {
1506 Self {
1507 autoscroll: Default::default(),
1508 include_gutter: true,
1509 }
1510 }
1511}
1512
1513struct RowHighlight {
1514 index: usize,
1515 range: Range<Anchor>,
1516 color: Hsla,
1517 options: RowHighlightOptions,
1518 type_id: TypeId,
1519}
1520
1521#[derive(Clone, Debug)]
1522struct AddSelectionsState {
1523 groups: Vec<AddSelectionsGroup>,
1524}
1525
1526#[derive(Clone, Debug)]
1527struct AddSelectionsGroup {
1528 above: bool,
1529 stack: Vec<usize>,
1530}
1531
1532#[derive(Clone)]
1533struct SelectNextState {
1534 query: AhoCorasick,
1535 wordwise: bool,
1536 done: bool,
1537}
1538
1539impl std::fmt::Debug for SelectNextState {
1540 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1541 f.debug_struct(std::any::type_name::<Self>())
1542 .field("wordwise", &self.wordwise)
1543 .field("done", &self.done)
1544 .finish()
1545 }
1546}
1547
1548#[derive(Debug)]
1549struct AutocloseRegion {
1550 selection_id: usize,
1551 range: Range<Anchor>,
1552 pair: BracketPair,
1553}
1554
1555#[derive(Debug)]
1556struct SnippetState {
1557 ranges: Vec<Vec<Range<Anchor>>>,
1558 active_index: usize,
1559 choices: Vec<Option<Vec<String>>>,
1560}
1561
1562#[doc(hidden)]
1563pub struct RenameState {
1564 pub range: Range<Anchor>,
1565 pub old_name: Arc<str>,
1566 pub editor: Entity<Editor>,
1567 block_id: CustomBlockId,
1568}
1569
1570struct InvalidationStack<T>(Vec<T>);
1571
1572struct RegisteredEditPredictionDelegate {
1573 provider: Arc<dyn EditPredictionDelegateHandle>,
1574 _subscription: Subscription,
1575}
1576
1577#[derive(Debug, PartialEq, Eq)]
1578pub struct ActiveDiagnosticGroup {
1579 pub active_range: Range<Anchor>,
1580 pub active_message: String,
1581 pub group_id: usize,
1582 pub blocks: HashSet<CustomBlockId>,
1583}
1584
1585#[derive(Debug, PartialEq, Eq)]
1586
1587pub(crate) enum ActiveDiagnostic {
1588 None,
1589 All,
1590 Group(ActiveDiagnosticGroup),
1591}
1592
1593#[derive(Serialize, Deserialize, Clone, Debug)]
1594pub struct ClipboardSelection {
1595 /// The number of bytes in this selection.
1596 pub len: usize,
1597 /// Whether this was a full-line selection.
1598 pub is_entire_line: bool,
1599 /// The indentation of the first line when this content was originally copied.
1600 pub first_line_indent: u32,
1601 #[serde(default)]
1602 pub file_path: Option<PathBuf>,
1603 #[serde(default)]
1604 pub line_range: Option<RangeInclusive<u32>>,
1605}
1606
1607impl ClipboardSelection {
1608 pub fn for_buffer(
1609 len: usize,
1610 is_entire_line: bool,
1611 range: Range<Point>,
1612 buffer: &MultiBufferSnapshot,
1613 project: Option<&Entity<Project>>,
1614 cx: &App,
1615 ) -> Self {
1616 let first_line_indent = buffer
1617 .indent_size_for_line(MultiBufferRow(range.start.row))
1618 .len;
1619
1620 let file_path = util::maybe!({
1621 let project = project?.read(cx);
1622 let file = buffer.file_at(range.start)?;
1623 let project_path = ProjectPath {
1624 worktree_id: file.worktree_id(cx),
1625 path: file.path().clone(),
1626 };
1627 project.absolute_path(&project_path, cx)
1628 });
1629
1630 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1631
1632 Self {
1633 len,
1634 is_entire_line,
1635 first_line_indent,
1636 file_path,
1637 line_range,
1638 }
1639 }
1640}
1641
1642// selections, scroll behavior, was newest selection reversed
1643type SelectSyntaxNodeHistoryState = (
1644 Box<[Selection<MultiBufferOffset>]>,
1645 SelectSyntaxNodeScrollBehavior,
1646 bool,
1647);
1648
1649#[derive(Default)]
1650struct SelectSyntaxNodeHistory {
1651 stack: Vec<SelectSyntaxNodeHistoryState>,
1652 // disable temporarily to allow changing selections without losing the stack
1653 pub disable_clearing: bool,
1654}
1655
1656impl SelectSyntaxNodeHistory {
1657 pub fn try_clear(&mut self) {
1658 if !self.disable_clearing {
1659 self.stack.clear();
1660 }
1661 }
1662
1663 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1664 self.stack.push(selection);
1665 }
1666
1667 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1668 self.stack.pop()
1669 }
1670}
1671
1672enum SelectSyntaxNodeScrollBehavior {
1673 CursorTop,
1674 FitSelection,
1675 CursorBottom,
1676}
1677
1678#[derive(Debug)]
1679pub(crate) struct NavigationData {
1680 cursor_anchor: Anchor,
1681 cursor_position: Point,
1682 scroll_anchor: ScrollAnchor,
1683 scroll_top_row: u32,
1684}
1685
1686#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1687pub enum GotoDefinitionKind {
1688 Symbol,
1689 Declaration,
1690 Type,
1691 Implementation,
1692}
1693
1694pub enum FormatTarget {
1695 Buffers(HashSet<Entity<Buffer>>),
1696 Ranges(Vec<Range<MultiBufferPoint>>),
1697}
1698
1699pub(crate) struct FocusedBlock {
1700 id: BlockId,
1701 focus_handle: WeakFocusHandle,
1702}
1703
1704#[derive(Clone, Debug)]
1705enum JumpData {
1706 MultiBufferRow {
1707 row: MultiBufferRow,
1708 line_offset_from_top: u32,
1709 },
1710 MultiBufferPoint {
1711 excerpt_id: ExcerptId,
1712 position: Point,
1713 anchor: text::Anchor,
1714 line_offset_from_top: u32,
1715 },
1716}
1717
1718pub enum MultibufferSelectionMode {
1719 First,
1720 All,
1721}
1722
1723#[derive(Clone, Copy, Debug, Default)]
1724pub struct RewrapOptions {
1725 pub override_language_settings: bool,
1726 pub preserve_existing_whitespace: bool,
1727}
1728
1729impl Editor {
1730 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1731 let buffer = cx.new(|cx| Buffer::local("", cx));
1732 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1733 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1734 }
1735
1736 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1737 let buffer = cx.new(|cx| Buffer::local("", cx));
1738 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1739 Self::new(EditorMode::full(), buffer, None, window, cx)
1740 }
1741
1742 pub fn auto_height(
1743 min_lines: usize,
1744 max_lines: usize,
1745 window: &mut Window,
1746 cx: &mut Context<Self>,
1747 ) -> Self {
1748 let buffer = cx.new(|cx| Buffer::local("", cx));
1749 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1750 Self::new(
1751 EditorMode::AutoHeight {
1752 min_lines,
1753 max_lines: Some(max_lines),
1754 },
1755 buffer,
1756 None,
1757 window,
1758 cx,
1759 )
1760 }
1761
1762 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1763 /// The editor grows as tall as needed to fit its content.
1764 pub fn auto_height_unbounded(
1765 min_lines: usize,
1766 window: &mut Window,
1767 cx: &mut Context<Self>,
1768 ) -> Self {
1769 let buffer = cx.new(|cx| Buffer::local("", cx));
1770 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1771 Self::new(
1772 EditorMode::AutoHeight {
1773 min_lines,
1774 max_lines: None,
1775 },
1776 buffer,
1777 None,
1778 window,
1779 cx,
1780 )
1781 }
1782
1783 pub fn for_buffer(
1784 buffer: Entity<Buffer>,
1785 project: Option<Entity<Project>>,
1786 window: &mut Window,
1787 cx: &mut Context<Self>,
1788 ) -> Self {
1789 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1790 Self::new(EditorMode::full(), buffer, project, window, cx)
1791 }
1792
1793 pub fn for_multibuffer(
1794 buffer: Entity<MultiBuffer>,
1795 project: Option<Entity<Project>>,
1796 window: &mut Window,
1797 cx: &mut Context<Self>,
1798 ) -> Self {
1799 Self::new(EditorMode::full(), buffer, project, window, cx)
1800 }
1801
1802 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1803 let mut clone = Self::new(
1804 self.mode.clone(),
1805 self.buffer.clone(),
1806 self.project.clone(),
1807 window,
1808 cx,
1809 );
1810 self.display_map.update(cx, |display_map, cx| {
1811 let snapshot = display_map.snapshot(cx);
1812 clone.display_map.update(cx, |display_map, cx| {
1813 display_map.set_state(&snapshot, cx);
1814 });
1815 });
1816 clone.folds_did_change(cx);
1817 clone.selections.clone_state(&self.selections);
1818 clone.scroll_manager.clone_state(&self.scroll_manager);
1819 clone.searchable = self.searchable;
1820 clone.read_only = self.read_only;
1821 clone
1822 }
1823
1824 pub fn new(
1825 mode: EditorMode,
1826 buffer: Entity<MultiBuffer>,
1827 project: Option<Entity<Project>>,
1828 window: &mut Window,
1829 cx: &mut Context<Self>,
1830 ) -> Self {
1831 Editor::new_internal(mode, buffer, project, None, window, cx)
1832 }
1833
1834 pub fn sticky_headers(
1835 &self,
1836 style: &EditorStyle,
1837 cx: &App,
1838 ) -> Option<Vec<OutlineItem<Anchor>>> {
1839 let multi_buffer = self.buffer().read(cx);
1840 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1841 let multi_buffer_visible_start = self
1842 .scroll_manager
1843 .anchor()
1844 .anchor
1845 .to_point(&multi_buffer_snapshot);
1846 let max_row = multi_buffer_snapshot.max_point().row;
1847
1848 let start_row = (multi_buffer_visible_start.row).min(max_row);
1849 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1850
1851 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1852 let outline_items = buffer
1853 .outline_items_containing(
1854 Point::new(start_row, 0)..Point::new(end_row, 0),
1855 true,
1856 Some(style.syntax.as_ref()),
1857 )
1858 .into_iter()
1859 .map(|outline_item| OutlineItem {
1860 depth: outline_item.depth,
1861 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1862 source_range_for_text: Anchor::range_in_buffer(
1863 *excerpt_id,
1864 outline_item.source_range_for_text,
1865 ),
1866 text: outline_item.text,
1867 highlight_ranges: outline_item.highlight_ranges,
1868 name_ranges: outline_item.name_ranges,
1869 body_range: outline_item
1870 .body_range
1871 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1872 annotation_range: outline_item
1873 .annotation_range
1874 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1875 });
1876 return Some(outline_items.collect());
1877 }
1878
1879 None
1880 }
1881
1882 fn new_internal(
1883 mode: EditorMode,
1884 multi_buffer: Entity<MultiBuffer>,
1885 project: Option<Entity<Project>>,
1886 display_map: Option<Entity<DisplayMap>>,
1887 window: &mut Window,
1888 cx: &mut Context<Self>,
1889 ) -> Self {
1890 debug_assert!(
1891 display_map.is_none() || mode.is_minimap(),
1892 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1893 );
1894
1895 let full_mode = mode.is_full();
1896 let is_minimap = mode.is_minimap();
1897 let diagnostics_max_severity = if full_mode {
1898 EditorSettings::get_global(cx)
1899 .diagnostics_max_severity
1900 .unwrap_or(DiagnosticSeverity::Hint)
1901 } else {
1902 DiagnosticSeverity::Off
1903 };
1904 let style = window.text_style();
1905 let font_size = style.font_size.to_pixels(window.rem_size());
1906 let editor = cx.entity().downgrade();
1907 let fold_placeholder = FoldPlaceholder {
1908 constrain_width: false,
1909 render: Arc::new(move |fold_id, fold_range, cx| {
1910 let editor = editor.clone();
1911 div()
1912 .id(fold_id)
1913 .bg(cx.theme().colors().ghost_element_background)
1914 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1915 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1916 .rounded_xs()
1917 .size_full()
1918 .cursor_pointer()
1919 .child("⋯")
1920 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1921 .on_click(move |_, _window, cx| {
1922 editor
1923 .update(cx, |editor, cx| {
1924 editor.unfold_ranges(
1925 &[fold_range.start..fold_range.end],
1926 true,
1927 false,
1928 cx,
1929 );
1930 cx.stop_propagation();
1931 })
1932 .ok();
1933 })
1934 .into_any()
1935 }),
1936 merge_adjacent: true,
1937 ..FoldPlaceholder::default()
1938 };
1939 let display_map = display_map.unwrap_or_else(|| {
1940 cx.new(|cx| {
1941 DisplayMap::new(
1942 multi_buffer.clone(),
1943 style.font(),
1944 font_size,
1945 None,
1946 FILE_HEADER_HEIGHT,
1947 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1948 fold_placeholder,
1949 diagnostics_max_severity,
1950 cx,
1951 )
1952 })
1953 });
1954
1955 let selections = SelectionsCollection::new();
1956
1957 let blink_manager = cx.new(|cx| {
1958 let mut blink_manager = BlinkManager::new(
1959 CURSOR_BLINK_INTERVAL,
1960 |cx| EditorSettings::get_global(cx).cursor_blink,
1961 cx,
1962 );
1963 if is_minimap {
1964 blink_manager.disable(cx);
1965 }
1966 blink_manager
1967 });
1968
1969 let soft_wrap_mode_override =
1970 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1971
1972 let mut project_subscriptions = Vec::new();
1973 if full_mode && let Some(project) = project.as_ref() {
1974 project_subscriptions.push(cx.subscribe_in(
1975 project,
1976 window,
1977 |editor, _, event, window, cx| match event {
1978 project::Event::RefreshCodeLens => {
1979 // we always query lens with actions, without storing them, always refreshing them
1980 }
1981 project::Event::RefreshInlayHints {
1982 server_id,
1983 request_id,
1984 } => {
1985 editor.refresh_inlay_hints(
1986 InlayHintRefreshReason::RefreshRequested {
1987 server_id: *server_id,
1988 request_id: *request_id,
1989 },
1990 cx,
1991 );
1992 }
1993 project::Event::LanguageServerRemoved(..) => {
1994 if editor.tasks_update_task.is_none() {
1995 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1996 }
1997 editor.registered_buffers.clear();
1998 editor.register_visible_buffers(cx);
1999 }
2000 project::Event::LanguageServerAdded(..) => {
2001 if editor.tasks_update_task.is_none() {
2002 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2003 }
2004 }
2005 project::Event::SnippetEdit(id, snippet_edits) => {
2006 // todo(lw): Non singletons
2007 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2008 let snapshot = buffer.read(cx).snapshot();
2009 let focus_handle = editor.focus_handle(cx);
2010 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2011 for (range, snippet) in snippet_edits {
2012 let buffer_range =
2013 language::range_from_lsp(*range).to_offset(&snapshot);
2014 editor
2015 .insert_snippet(
2016 &[MultiBufferOffset(buffer_range.start)
2017 ..MultiBufferOffset(buffer_range.end)],
2018 snippet.clone(),
2019 window,
2020 cx,
2021 )
2022 .ok();
2023 }
2024 }
2025 }
2026 }
2027 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2028 let buffer_id = *buffer_id;
2029 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2030 editor.register_buffer(buffer_id, cx);
2031 editor.update_lsp_data(Some(buffer_id), window, cx);
2032 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2033 refresh_linked_ranges(editor, window, cx);
2034 editor.refresh_code_actions(window, cx);
2035 editor.refresh_document_highlights(cx);
2036 }
2037 }
2038
2039 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2040 let Some(workspace) = editor.workspace() else {
2041 return;
2042 };
2043 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2044 else {
2045 return;
2046 };
2047
2048 if active_editor.entity_id() == cx.entity_id() {
2049 let entity_id = cx.entity_id();
2050 workspace.update(cx, |this, cx| {
2051 this.panes_mut()
2052 .iter_mut()
2053 .filter(|pane| pane.entity_id() != entity_id)
2054 .for_each(|p| {
2055 p.update(cx, |pane, _| {
2056 pane.nav_history_mut().rename_item(
2057 entity_id,
2058 project_path.clone(),
2059 abs_path.clone().into(),
2060 );
2061 })
2062 });
2063 });
2064 let edited_buffers_already_open = {
2065 let other_editors: Vec<Entity<Editor>> = workspace
2066 .read(cx)
2067 .panes()
2068 .iter()
2069 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2070 .filter(|editor| editor.entity_id() != cx.entity_id())
2071 .collect();
2072
2073 transaction.0.keys().all(|buffer| {
2074 other_editors.iter().any(|editor| {
2075 let multi_buffer = editor.read(cx).buffer();
2076 multi_buffer.read(cx).is_singleton()
2077 && multi_buffer.read(cx).as_singleton().map_or(
2078 false,
2079 |singleton| {
2080 singleton.entity_id() == buffer.entity_id()
2081 },
2082 )
2083 })
2084 })
2085 };
2086 if !edited_buffers_already_open {
2087 let workspace = workspace.downgrade();
2088 let transaction = transaction.clone();
2089 cx.defer_in(window, move |_, window, cx| {
2090 cx.spawn_in(window, async move |editor, cx| {
2091 Self::open_project_transaction(
2092 &editor,
2093 workspace,
2094 transaction,
2095 "Rename".to_string(),
2096 cx,
2097 )
2098 .await
2099 .ok()
2100 })
2101 .detach();
2102 });
2103 }
2104 }
2105 }
2106
2107 _ => {}
2108 },
2109 ));
2110 if let Some(task_inventory) = project
2111 .read(cx)
2112 .task_store()
2113 .read(cx)
2114 .task_inventory()
2115 .cloned()
2116 {
2117 project_subscriptions.push(cx.observe_in(
2118 &task_inventory,
2119 window,
2120 |editor, _, window, cx| {
2121 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2122 },
2123 ));
2124 };
2125
2126 project_subscriptions.push(cx.subscribe_in(
2127 &project.read(cx).breakpoint_store(),
2128 window,
2129 |editor, _, event, window, cx| match event {
2130 BreakpointStoreEvent::ClearDebugLines => {
2131 editor.clear_row_highlights::<ActiveDebugLine>();
2132 editor.refresh_inline_values(cx);
2133 }
2134 BreakpointStoreEvent::SetDebugLine => {
2135 if editor.go_to_active_debug_line(window, cx) {
2136 cx.stop_propagation();
2137 }
2138
2139 editor.refresh_inline_values(cx);
2140 }
2141 _ => {}
2142 },
2143 ));
2144 let git_store = project.read(cx).git_store().clone();
2145 let project = project.clone();
2146 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2147 if let GitStoreEvent::RepositoryAdded = event {
2148 this.load_diff_task = Some(
2149 update_uncommitted_diff_for_buffer(
2150 cx.entity(),
2151 &project,
2152 this.buffer.read(cx).all_buffers(),
2153 this.buffer.clone(),
2154 cx,
2155 )
2156 .shared(),
2157 );
2158 }
2159 }));
2160 }
2161
2162 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2163
2164 let inlay_hint_settings =
2165 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2166 let focus_handle = cx.focus_handle();
2167 if !is_minimap {
2168 cx.on_focus(&focus_handle, window, Self::handle_focus)
2169 .detach();
2170 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2171 .detach();
2172 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2173 .detach();
2174 cx.on_blur(&focus_handle, window, Self::handle_blur)
2175 .detach();
2176 cx.observe_pending_input(window, Self::observe_pending_input)
2177 .detach();
2178 }
2179
2180 let show_indent_guides =
2181 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2182 Some(false)
2183 } else {
2184 None
2185 };
2186
2187 let breakpoint_store = match (&mode, project.as_ref()) {
2188 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2189 _ => None,
2190 };
2191
2192 let mut code_action_providers = Vec::new();
2193 let mut load_uncommitted_diff = None;
2194 if let Some(project) = project.clone() {
2195 load_uncommitted_diff = Some(
2196 update_uncommitted_diff_for_buffer(
2197 cx.entity(),
2198 &project,
2199 multi_buffer.read(cx).all_buffers(),
2200 multi_buffer.clone(),
2201 cx,
2202 )
2203 .shared(),
2204 );
2205 code_action_providers.push(Rc::new(project) as Rc<_>);
2206 }
2207
2208 let mut editor = Self {
2209 focus_handle,
2210 show_cursor_when_unfocused: false,
2211 last_focused_descendant: None,
2212 buffer: multi_buffer.clone(),
2213 display_map: display_map.clone(),
2214 placeholder_display_map: None,
2215 selections,
2216 scroll_manager: ScrollManager::new(cx),
2217 columnar_selection_state: None,
2218 add_selections_state: None,
2219 select_next_state: None,
2220 select_prev_state: None,
2221 selection_history: SelectionHistory::default(),
2222 defer_selection_effects: false,
2223 deferred_selection_effects_state: None,
2224 autoclose_regions: Vec::new(),
2225 snippet_stack: InvalidationStack::default(),
2226 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2227 ime_transaction: None,
2228 active_diagnostics: ActiveDiagnostic::None,
2229 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2230 inline_diagnostics_update: Task::ready(()),
2231 inline_diagnostics: Vec::new(),
2232 soft_wrap_mode_override,
2233 diagnostics_max_severity,
2234 hard_wrap: None,
2235 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2236 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2237 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2238 project,
2239 blink_manager: blink_manager.clone(),
2240 show_local_selections: true,
2241 show_scrollbars: ScrollbarAxes {
2242 horizontal: full_mode,
2243 vertical: full_mode,
2244 },
2245 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2246 offset_content: !matches!(mode, EditorMode::SingleLine),
2247 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2248 show_gutter: full_mode,
2249 show_line_numbers: (!full_mode).then_some(false),
2250 use_relative_line_numbers: None,
2251 disable_expand_excerpt_buttons: !full_mode,
2252 show_git_diff_gutter: None,
2253 show_code_actions: None,
2254 show_runnables: None,
2255 show_breakpoints: None,
2256 show_wrap_guides: None,
2257 show_indent_guides,
2258 buffers_with_disabled_indent_guides: HashSet::default(),
2259 highlight_order: 0,
2260 highlighted_rows: HashMap::default(),
2261 background_highlights: HashMap::default(),
2262 gutter_highlights: HashMap::default(),
2263 scrollbar_marker_state: ScrollbarMarkerState::default(),
2264 active_indent_guides_state: ActiveIndentGuidesState::default(),
2265 nav_history: None,
2266 context_menu: RefCell::new(None),
2267 context_menu_options: None,
2268 mouse_context_menu: None,
2269 completion_tasks: Vec::new(),
2270 inline_blame_popover: None,
2271 inline_blame_popover_show_task: None,
2272 signature_help_state: SignatureHelpState::default(),
2273 auto_signature_help: None,
2274 find_all_references_task_sources: Vec::new(),
2275 next_completion_id: 0,
2276 next_inlay_id: 0,
2277 code_action_providers,
2278 available_code_actions: None,
2279 code_actions_task: None,
2280 quick_selection_highlight_task: None,
2281 debounced_selection_highlight_task: None,
2282 document_highlights_task: None,
2283 linked_editing_range_task: None,
2284 pending_rename: None,
2285 searchable: !is_minimap,
2286 cursor_shape: EditorSettings::get_global(cx)
2287 .cursor_shape
2288 .unwrap_or_default(),
2289 current_line_highlight: None,
2290 autoindent_mode: Some(AutoindentMode::EachLine),
2291 collapse_matches: false,
2292 workspace: None,
2293 input_enabled: !is_minimap,
2294 use_modal_editing: full_mode,
2295 read_only: is_minimap,
2296 use_autoclose: true,
2297 use_auto_surround: true,
2298 auto_replace_emoji_shortcode: false,
2299 jsx_tag_auto_close_enabled_in_any_buffer: false,
2300 leader_id: None,
2301 remote_id: None,
2302 hover_state: HoverState::default(),
2303 pending_mouse_down: None,
2304 prev_pressure_stage: None,
2305 hovered_link_state: None,
2306 edit_prediction_provider: None,
2307 active_edit_prediction: None,
2308 stale_edit_prediction_in_menu: None,
2309 edit_prediction_preview: EditPredictionPreview::Inactive {
2310 released_too_fast: false,
2311 },
2312 inline_diagnostics_enabled: full_mode,
2313 diagnostics_enabled: full_mode,
2314 word_completions_enabled: full_mode,
2315 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2316 gutter_hovered: false,
2317 pixel_position_of_newest_cursor: None,
2318 last_bounds: None,
2319 last_position_map: None,
2320 expect_bounds_change: None,
2321 gutter_dimensions: GutterDimensions::default(),
2322 style: None,
2323 show_cursor_names: false,
2324 hovered_cursors: HashMap::default(),
2325 next_editor_action_id: EditorActionId::default(),
2326 editor_actions: Rc::default(),
2327 edit_predictions_hidden_for_vim_mode: false,
2328 show_edit_predictions_override: None,
2329 show_completions_on_input_override: None,
2330 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2331 edit_prediction_settings: EditPredictionSettings::Disabled,
2332 edit_prediction_indent_conflict: false,
2333 edit_prediction_requires_modifier_in_indent_conflict: true,
2334 custom_context_menu: None,
2335 show_git_blame_gutter: false,
2336 show_git_blame_inline: false,
2337 show_selection_menu: None,
2338 show_git_blame_inline_delay_task: None,
2339 git_blame_inline_enabled: full_mode
2340 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2341 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2342 buffer_serialization: is_minimap.not().then(|| {
2343 BufferSerialization::new(
2344 ProjectSettings::get_global(cx)
2345 .session
2346 .restore_unsaved_buffers,
2347 )
2348 }),
2349 blame: None,
2350 blame_subscription: None,
2351 tasks: BTreeMap::default(),
2352
2353 breakpoint_store,
2354 gutter_breakpoint_indicator: (None, None),
2355 hovered_diff_hunk_row: None,
2356 _subscriptions: (!is_minimap)
2357 .then(|| {
2358 vec![
2359 cx.observe(&multi_buffer, Self::on_buffer_changed),
2360 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2361 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2362 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2363 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2364 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2365 cx.observe_window_activation(window, |editor, window, cx| {
2366 let active = window.is_window_active();
2367 editor.blink_manager.update(cx, |blink_manager, cx| {
2368 if active {
2369 blink_manager.enable(cx);
2370 } else {
2371 blink_manager.disable(cx);
2372 }
2373 });
2374 if active {
2375 editor.show_mouse_cursor(cx);
2376 }
2377 }),
2378 ]
2379 })
2380 .unwrap_or_default(),
2381 tasks_update_task: None,
2382 pull_diagnostics_task: Task::ready(()),
2383 pull_diagnostics_background_task: Task::ready(()),
2384 colors: None,
2385 refresh_colors_task: Task::ready(()),
2386 inlay_hints: None,
2387 next_color_inlay_id: 0,
2388 post_scroll_update: Task::ready(()),
2389 linked_edit_ranges: Default::default(),
2390 in_project_search: false,
2391 previous_search_ranges: None,
2392 breadcrumb_header: None,
2393 focused_block: None,
2394 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2395 addons: HashMap::default(),
2396 registered_buffers: HashMap::default(),
2397 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2398 selection_mark_mode: false,
2399 toggle_fold_multiple_buffers: Task::ready(()),
2400 serialize_selections: Task::ready(()),
2401 serialize_folds: Task::ready(()),
2402 text_style_refinement: None,
2403 load_diff_task: load_uncommitted_diff,
2404 temporary_diff_override: false,
2405 mouse_cursor_hidden: false,
2406 minimap: None,
2407 hide_mouse_mode: EditorSettings::get_global(cx)
2408 .hide_mouse
2409 .unwrap_or_default(),
2410 change_list: ChangeList::new(),
2411 mode,
2412 selection_drag_state: SelectionDragState::None,
2413 folding_newlines: Task::ready(()),
2414 lookup_key: None,
2415 select_next_is_case_sensitive: None,
2416 applicable_language_settings: HashMap::default(),
2417 accent_data: None,
2418 fetched_tree_sitter_chunks: HashMap::default(),
2419 use_base_text_line_numbers: false,
2420 };
2421
2422 if is_minimap {
2423 return editor;
2424 }
2425
2426 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2427 editor.accent_data = editor.fetch_accent_data(cx);
2428
2429 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2430 editor
2431 ._subscriptions
2432 .push(cx.observe(breakpoints, |_, _, cx| {
2433 cx.notify();
2434 }));
2435 }
2436 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2437 editor._subscriptions.extend(project_subscriptions);
2438
2439 editor._subscriptions.push(cx.subscribe_in(
2440 &cx.entity(),
2441 window,
2442 |editor, _, e: &EditorEvent, window, cx| match e {
2443 EditorEvent::ScrollPositionChanged { local, .. } => {
2444 if *local {
2445 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2446 editor.inline_blame_popover.take();
2447 let new_anchor = editor.scroll_manager.anchor();
2448 let snapshot = editor.snapshot(window, cx);
2449 editor.update_restoration_data(cx, move |data| {
2450 data.scroll_position = (
2451 new_anchor.top_row(snapshot.buffer_snapshot()),
2452 new_anchor.offset,
2453 );
2454 });
2455
2456 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2457 cx.background_executor()
2458 .timer(Duration::from_millis(50))
2459 .await;
2460 editor
2461 .update_in(cx, |editor, window, cx| {
2462 editor.register_visible_buffers(cx);
2463 editor.refresh_colors_for_visible_range(None, window, cx);
2464 editor.refresh_inlay_hints(
2465 InlayHintRefreshReason::NewLinesShown,
2466 cx,
2467 );
2468 editor.colorize_brackets(false, cx);
2469 })
2470 .ok();
2471 });
2472 }
2473 }
2474 EditorEvent::Edited { .. } => {
2475 if !editor.is_vim_mode_enabled(cx) {
2476 let display_map = editor.display_snapshot(cx);
2477 let selections = editor.selections.all_adjusted_display(&display_map);
2478 let pop_state = editor
2479 .change_list
2480 .last()
2481 .map(|previous| {
2482 previous.len() == selections.len()
2483 && previous.iter().enumerate().all(|(ix, p)| {
2484 p.to_display_point(&display_map).row()
2485 == selections[ix].head().row()
2486 })
2487 })
2488 .unwrap_or(false);
2489 let new_positions = selections
2490 .into_iter()
2491 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2492 .collect();
2493 editor
2494 .change_list
2495 .push_to_change_list(pop_state, new_positions);
2496 }
2497 }
2498 _ => (),
2499 },
2500 ));
2501
2502 if let Some(dap_store) = editor
2503 .project
2504 .as_ref()
2505 .map(|project| project.read(cx).dap_store())
2506 {
2507 let weak_editor = cx.weak_entity();
2508
2509 editor
2510 ._subscriptions
2511 .push(
2512 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2513 let session_entity = cx.entity();
2514 weak_editor
2515 .update(cx, |editor, cx| {
2516 editor._subscriptions.push(
2517 cx.subscribe(&session_entity, Self::on_debug_session_event),
2518 );
2519 })
2520 .ok();
2521 }),
2522 );
2523
2524 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2525 editor
2526 ._subscriptions
2527 .push(cx.subscribe(&session, Self::on_debug_session_event));
2528 }
2529 }
2530
2531 // skip adding the initial selection to selection history
2532 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2533 editor.end_selection(window, cx);
2534 editor.selection_history.mode = SelectionHistoryMode::Normal;
2535
2536 editor.scroll_manager.show_scrollbars(window, cx);
2537 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2538
2539 if full_mode {
2540 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2541 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2542
2543 if editor.git_blame_inline_enabled {
2544 editor.start_git_blame_inline(false, window, cx);
2545 }
2546
2547 editor.go_to_active_debug_line(window, cx);
2548
2549 editor.minimap =
2550 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2551 editor.colors = Some(LspColorData::new(cx));
2552 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2553
2554 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2555 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2556 }
2557 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2558 }
2559
2560 editor
2561 }
2562
2563 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2564 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2565 }
2566
2567 pub fn deploy_mouse_context_menu(
2568 &mut self,
2569 position: gpui::Point<Pixels>,
2570 context_menu: Entity<ContextMenu>,
2571 window: &mut Window,
2572 cx: &mut Context<Self>,
2573 ) {
2574 self.mouse_context_menu = Some(MouseContextMenu::new(
2575 self,
2576 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2577 context_menu,
2578 window,
2579 cx,
2580 ));
2581 }
2582
2583 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2584 self.mouse_context_menu
2585 .as_ref()
2586 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2587 }
2588
2589 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2590 if self
2591 .selections
2592 .pending_anchor()
2593 .is_some_and(|pending_selection| {
2594 let snapshot = self.buffer().read(cx).snapshot(cx);
2595 pending_selection.range().includes(range, &snapshot)
2596 })
2597 {
2598 return true;
2599 }
2600
2601 self.selections
2602 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2603 .into_iter()
2604 .any(|selection| {
2605 // This is needed to cover a corner case, if we just check for an existing
2606 // selection in the fold range, having a cursor at the start of the fold
2607 // marks it as selected. Non-empty selections don't cause this.
2608 let length = selection.end - selection.start;
2609 length > 0
2610 })
2611 }
2612
2613 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2614 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2615 }
2616
2617 fn key_context_internal(
2618 &self,
2619 has_active_edit_prediction: bool,
2620 window: &mut Window,
2621 cx: &mut App,
2622 ) -> KeyContext {
2623 let mut key_context = KeyContext::new_with_defaults();
2624 key_context.add("Editor");
2625 let mode = match self.mode {
2626 EditorMode::SingleLine => "single_line",
2627 EditorMode::AutoHeight { .. } => "auto_height",
2628 EditorMode::Minimap { .. } => "minimap",
2629 EditorMode::Full { .. } => "full",
2630 };
2631
2632 if EditorSettings::jupyter_enabled(cx) {
2633 key_context.add("jupyter");
2634 }
2635
2636 key_context.set("mode", mode);
2637 if self.pending_rename.is_some() {
2638 key_context.add("renaming");
2639 }
2640
2641 if let Some(snippet_stack) = self.snippet_stack.last() {
2642 key_context.add("in_snippet");
2643
2644 if snippet_stack.active_index > 0 {
2645 key_context.add("has_previous_tabstop");
2646 }
2647
2648 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2649 key_context.add("has_next_tabstop");
2650 }
2651 }
2652
2653 match self.context_menu.borrow().as_ref() {
2654 Some(CodeContextMenu::Completions(menu)) => {
2655 if menu.visible() {
2656 key_context.add("menu");
2657 key_context.add("showing_completions");
2658 }
2659 }
2660 Some(CodeContextMenu::CodeActions(menu)) => {
2661 if menu.visible() {
2662 key_context.add("menu");
2663 key_context.add("showing_code_actions")
2664 }
2665 }
2666 None => {}
2667 }
2668
2669 if self.signature_help_state.has_multiple_signatures() {
2670 key_context.add("showing_signature_help");
2671 }
2672
2673 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2674 if !self.focus_handle(cx).contains_focused(window, cx)
2675 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2676 {
2677 for addon in self.addons.values() {
2678 addon.extend_key_context(&mut key_context, cx)
2679 }
2680 }
2681
2682 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2683 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2684 Some(
2685 file.full_path(cx)
2686 .extension()?
2687 .to_string_lossy()
2688 .into_owned(),
2689 )
2690 }) {
2691 key_context.set("extension", extension);
2692 }
2693 } else {
2694 key_context.add("multibuffer");
2695 }
2696
2697 if has_active_edit_prediction {
2698 if self.edit_prediction_in_conflict() {
2699 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2700 } else {
2701 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2702 key_context.add("copilot_suggestion");
2703 }
2704 }
2705
2706 if self.selection_mark_mode {
2707 key_context.add("selection_mode");
2708 }
2709
2710 let disjoint = self.selections.disjoint_anchors();
2711 let snapshot = self.snapshot(window, cx);
2712 let snapshot = snapshot.buffer_snapshot();
2713 if self.mode == EditorMode::SingleLine
2714 && let [selection] = disjoint
2715 && selection.start == selection.end
2716 && selection.end.to_offset(snapshot) == snapshot.len()
2717 {
2718 key_context.add("end_of_input");
2719 }
2720
2721 if self.has_any_expanded_diff_hunks(cx) {
2722 key_context.add("diffs_expanded");
2723 }
2724
2725 key_context
2726 }
2727
2728 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2729 self.last_bounds.as_ref()
2730 }
2731
2732 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2733 if self.mouse_cursor_hidden {
2734 self.mouse_cursor_hidden = false;
2735 cx.notify();
2736 }
2737 }
2738
2739 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2740 let hide_mouse_cursor = match origin {
2741 HideMouseCursorOrigin::TypingAction => {
2742 matches!(
2743 self.hide_mouse_mode,
2744 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2745 )
2746 }
2747 HideMouseCursorOrigin::MovementAction => {
2748 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2749 }
2750 };
2751 if self.mouse_cursor_hidden != hide_mouse_cursor {
2752 self.mouse_cursor_hidden = hide_mouse_cursor;
2753 cx.notify();
2754 }
2755 }
2756
2757 pub fn edit_prediction_in_conflict(&self) -> bool {
2758 if !self.show_edit_predictions_in_menu() {
2759 return false;
2760 }
2761
2762 let showing_completions = self
2763 .context_menu
2764 .borrow()
2765 .as_ref()
2766 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2767
2768 showing_completions
2769 || self.edit_prediction_requires_modifier()
2770 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2771 // bindings to insert tab characters.
2772 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2773 }
2774
2775 pub fn accept_edit_prediction_keybind(
2776 &self,
2777 granularity: EditPredictionGranularity,
2778 window: &mut Window,
2779 cx: &mut App,
2780 ) -> AcceptEditPredictionBinding {
2781 let key_context = self.key_context_internal(true, window, cx);
2782 let in_conflict = self.edit_prediction_in_conflict();
2783
2784 let bindings =
2785 match granularity {
2786 EditPredictionGranularity::Word => window
2787 .bindings_for_action_in_context(&AcceptNextWordEditPrediction, key_context),
2788 EditPredictionGranularity::Line => window
2789 .bindings_for_action_in_context(&AcceptNextLineEditPrediction, key_context),
2790 EditPredictionGranularity::Full => {
2791 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2792 }
2793 };
2794
2795 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2796 !in_conflict
2797 || binding
2798 .keystrokes()
2799 .first()
2800 .is_some_and(|keystroke| keystroke.modifiers().modified())
2801 }))
2802 }
2803
2804 pub fn new_file(
2805 workspace: &mut Workspace,
2806 _: &workspace::NewFile,
2807 window: &mut Window,
2808 cx: &mut Context<Workspace>,
2809 ) {
2810 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2811 "Failed to create buffer",
2812 window,
2813 cx,
2814 |e, _, _| match e.error_code() {
2815 ErrorCode::RemoteUpgradeRequired => Some(format!(
2816 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2817 e.error_tag("required").unwrap_or("the latest version")
2818 )),
2819 _ => None,
2820 },
2821 );
2822 }
2823
2824 pub fn new_in_workspace(
2825 workspace: &mut Workspace,
2826 window: &mut Window,
2827 cx: &mut Context<Workspace>,
2828 ) -> Task<Result<Entity<Editor>>> {
2829 let project = workspace.project().clone();
2830 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2831
2832 cx.spawn_in(window, async move |workspace, cx| {
2833 let buffer = create.await?;
2834 workspace.update_in(cx, |workspace, window, cx| {
2835 let editor =
2836 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2837 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2838 editor
2839 })
2840 })
2841 }
2842
2843 fn new_file_vertical(
2844 workspace: &mut Workspace,
2845 _: &workspace::NewFileSplitVertical,
2846 window: &mut Window,
2847 cx: &mut Context<Workspace>,
2848 ) {
2849 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2850 }
2851
2852 fn new_file_horizontal(
2853 workspace: &mut Workspace,
2854 _: &workspace::NewFileSplitHorizontal,
2855 window: &mut Window,
2856 cx: &mut Context<Workspace>,
2857 ) {
2858 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2859 }
2860
2861 fn new_file_split(
2862 workspace: &mut Workspace,
2863 action: &workspace::NewFileSplit,
2864 window: &mut Window,
2865 cx: &mut Context<Workspace>,
2866 ) {
2867 Self::new_file_in_direction(workspace, action.0, window, cx)
2868 }
2869
2870 fn new_file_in_direction(
2871 workspace: &mut Workspace,
2872 direction: SplitDirection,
2873 window: &mut Window,
2874 cx: &mut Context<Workspace>,
2875 ) {
2876 let project = workspace.project().clone();
2877 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2878
2879 cx.spawn_in(window, async move |workspace, cx| {
2880 let buffer = create.await?;
2881 workspace.update_in(cx, move |workspace, window, cx| {
2882 workspace.split_item(
2883 direction,
2884 Box::new(
2885 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2886 ),
2887 window,
2888 cx,
2889 )
2890 })?;
2891 anyhow::Ok(())
2892 })
2893 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2894 match e.error_code() {
2895 ErrorCode::RemoteUpgradeRequired => Some(format!(
2896 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2897 e.error_tag("required").unwrap_or("the latest version")
2898 )),
2899 _ => None,
2900 }
2901 });
2902 }
2903
2904 pub fn leader_id(&self) -> Option<CollaboratorId> {
2905 self.leader_id
2906 }
2907
2908 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2909 &self.buffer
2910 }
2911
2912 pub fn project(&self) -> Option<&Entity<Project>> {
2913 self.project.as_ref()
2914 }
2915
2916 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2917 self.workspace.as_ref()?.0.upgrade()
2918 }
2919
2920 /// Returns the workspace serialization ID if this editor should be serialized.
2921 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2922 self.workspace
2923 .as_ref()
2924 .filter(|_| self.should_serialize_buffer())
2925 .and_then(|workspace| workspace.1)
2926 }
2927
2928 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2929 self.buffer().read(cx).title(cx)
2930 }
2931
2932 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2933 let git_blame_gutter_max_author_length = self
2934 .render_git_blame_gutter(cx)
2935 .then(|| {
2936 if let Some(blame) = self.blame.as_ref() {
2937 let max_author_length =
2938 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2939 Some(max_author_length)
2940 } else {
2941 None
2942 }
2943 })
2944 .flatten();
2945
2946 EditorSnapshot {
2947 mode: self.mode.clone(),
2948 show_gutter: self.show_gutter,
2949 offset_content: self.offset_content,
2950 show_line_numbers: self.show_line_numbers,
2951 show_git_diff_gutter: self.show_git_diff_gutter,
2952 show_code_actions: self.show_code_actions,
2953 show_runnables: self.show_runnables,
2954 show_breakpoints: self.show_breakpoints,
2955 git_blame_gutter_max_author_length,
2956 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2957 placeholder_display_snapshot: self
2958 .placeholder_display_map
2959 .as_ref()
2960 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2961 scroll_anchor: self.scroll_manager.anchor(),
2962 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2963 is_focused: self.focus_handle.is_focused(window),
2964 current_line_highlight: self
2965 .current_line_highlight
2966 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2967 gutter_hovered: self.gutter_hovered,
2968 }
2969 }
2970
2971 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2972 self.buffer.read(cx).language_at(point, cx)
2973 }
2974
2975 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2976 self.buffer.read(cx).read(cx).file_at(point).cloned()
2977 }
2978
2979 pub fn active_excerpt(
2980 &self,
2981 cx: &App,
2982 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2983 self.buffer
2984 .read(cx)
2985 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2986 }
2987
2988 pub fn mode(&self) -> &EditorMode {
2989 &self.mode
2990 }
2991
2992 pub fn set_mode(&mut self, mode: EditorMode) {
2993 self.mode = mode;
2994 }
2995
2996 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2997 self.collaboration_hub.as_deref()
2998 }
2999
3000 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
3001 self.collaboration_hub = Some(hub);
3002 }
3003
3004 pub fn set_in_project_search(&mut self, in_project_search: bool) {
3005 self.in_project_search = in_project_search;
3006 }
3007
3008 pub fn set_custom_context_menu(
3009 &mut self,
3010 f: impl 'static
3011 + Fn(
3012 &mut Self,
3013 DisplayPoint,
3014 &mut Window,
3015 &mut Context<Self>,
3016 ) -> Option<Entity<ui::ContextMenu>>,
3017 ) {
3018 self.custom_context_menu = Some(Box::new(f))
3019 }
3020
3021 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3022 self.completion_provider = provider;
3023 }
3024
3025 #[cfg(any(test, feature = "test-support"))]
3026 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3027 self.completion_provider.clone()
3028 }
3029
3030 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3031 self.semantics_provider.clone()
3032 }
3033
3034 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3035 self.semantics_provider = provider;
3036 }
3037
3038 pub fn set_edit_prediction_provider<T>(
3039 &mut self,
3040 provider: Option<Entity<T>>,
3041 window: &mut Window,
3042 cx: &mut Context<Self>,
3043 ) where
3044 T: EditPredictionDelegate,
3045 {
3046 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3047 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3048 if this.focus_handle.is_focused(window) {
3049 this.update_visible_edit_prediction(window, cx);
3050 }
3051 }),
3052 provider: Arc::new(provider),
3053 });
3054 self.update_edit_prediction_settings(cx);
3055 self.refresh_edit_prediction(false, false, window, cx);
3056 }
3057
3058 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3059 self.placeholder_display_map
3060 .as_ref()
3061 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3062 }
3063
3064 pub fn set_placeholder_text(
3065 &mut self,
3066 placeholder_text: &str,
3067 window: &mut Window,
3068 cx: &mut Context<Self>,
3069 ) {
3070 let multibuffer = cx
3071 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3072
3073 let style = window.text_style();
3074
3075 self.placeholder_display_map = Some(cx.new(|cx| {
3076 DisplayMap::new(
3077 multibuffer,
3078 style.font(),
3079 style.font_size.to_pixels(window.rem_size()),
3080 None,
3081 FILE_HEADER_HEIGHT,
3082 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3083 Default::default(),
3084 DiagnosticSeverity::Off,
3085 cx,
3086 )
3087 }));
3088 cx.notify();
3089 }
3090
3091 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3092 self.cursor_shape = cursor_shape;
3093
3094 // Disrupt blink for immediate user feedback that the cursor shape has changed
3095 self.blink_manager.update(cx, BlinkManager::show_cursor);
3096
3097 cx.notify();
3098 }
3099
3100 pub fn cursor_shape(&self) -> CursorShape {
3101 self.cursor_shape
3102 }
3103
3104 pub fn set_current_line_highlight(
3105 &mut self,
3106 current_line_highlight: Option<CurrentLineHighlight>,
3107 ) {
3108 self.current_line_highlight = current_line_highlight;
3109 }
3110
3111 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3112 self.collapse_matches = collapse_matches;
3113 }
3114
3115 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3116 if self.collapse_matches {
3117 return range.start..range.start;
3118 }
3119 range.clone()
3120 }
3121
3122 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3123 self.display_map.read(cx).clip_at_line_ends
3124 }
3125
3126 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3127 if self.display_map.read(cx).clip_at_line_ends != clip {
3128 self.display_map
3129 .update(cx, |map, _| map.clip_at_line_ends = clip);
3130 }
3131 }
3132
3133 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3134 self.input_enabled = input_enabled;
3135 }
3136
3137 pub fn set_edit_predictions_hidden_for_vim_mode(
3138 &mut self,
3139 hidden: bool,
3140 window: &mut Window,
3141 cx: &mut Context<Self>,
3142 ) {
3143 if hidden != self.edit_predictions_hidden_for_vim_mode {
3144 self.edit_predictions_hidden_for_vim_mode = hidden;
3145 if hidden {
3146 self.update_visible_edit_prediction(window, cx);
3147 } else {
3148 self.refresh_edit_prediction(true, false, window, cx);
3149 }
3150 }
3151 }
3152
3153 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3154 self.menu_edit_predictions_policy = value;
3155 }
3156
3157 pub fn set_autoindent(&mut self, autoindent: bool) {
3158 if autoindent {
3159 self.autoindent_mode = Some(AutoindentMode::EachLine);
3160 } else {
3161 self.autoindent_mode = None;
3162 }
3163 }
3164
3165 pub fn read_only(&self, cx: &App) -> bool {
3166 self.read_only || self.buffer.read(cx).read_only()
3167 }
3168
3169 pub fn set_read_only(&mut self, read_only: bool) {
3170 self.read_only = read_only;
3171 }
3172
3173 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3174 self.use_autoclose = autoclose;
3175 }
3176
3177 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3178 self.use_auto_surround = auto_surround;
3179 }
3180
3181 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3182 self.auto_replace_emoji_shortcode = auto_replace;
3183 }
3184
3185 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3186 self.buffer_serialization = should_serialize.then(|| {
3187 BufferSerialization::new(
3188 ProjectSettings::get_global(cx)
3189 .session
3190 .restore_unsaved_buffers,
3191 )
3192 })
3193 }
3194
3195 fn should_serialize_buffer(&self) -> bool {
3196 self.buffer_serialization.is_some()
3197 }
3198
3199 pub fn toggle_edit_predictions(
3200 &mut self,
3201 _: &ToggleEditPrediction,
3202 window: &mut Window,
3203 cx: &mut Context<Self>,
3204 ) {
3205 if self.show_edit_predictions_override.is_some() {
3206 self.set_show_edit_predictions(None, window, cx);
3207 } else {
3208 let show_edit_predictions = !self.edit_predictions_enabled();
3209 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3210 }
3211 }
3212
3213 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3214 self.show_completions_on_input_override = show_completions_on_input;
3215 }
3216
3217 pub fn set_show_edit_predictions(
3218 &mut self,
3219 show_edit_predictions: Option<bool>,
3220 window: &mut Window,
3221 cx: &mut Context<Self>,
3222 ) {
3223 self.show_edit_predictions_override = show_edit_predictions;
3224 self.update_edit_prediction_settings(cx);
3225
3226 if let Some(false) = show_edit_predictions {
3227 self.discard_edit_prediction(false, cx);
3228 } else {
3229 self.refresh_edit_prediction(false, true, window, cx);
3230 }
3231 }
3232
3233 fn edit_predictions_disabled_in_scope(
3234 &self,
3235 buffer: &Entity<Buffer>,
3236 buffer_position: language::Anchor,
3237 cx: &App,
3238 ) -> bool {
3239 let snapshot = buffer.read(cx).snapshot();
3240 let settings = snapshot.settings_at(buffer_position, cx);
3241
3242 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3243 return false;
3244 };
3245
3246 scope.override_name().is_some_and(|scope_name| {
3247 settings
3248 .edit_predictions_disabled_in
3249 .iter()
3250 .any(|s| s == scope_name)
3251 })
3252 }
3253
3254 pub fn set_use_modal_editing(&mut self, to: bool) {
3255 self.use_modal_editing = to;
3256 }
3257
3258 pub fn use_modal_editing(&self) -> bool {
3259 self.use_modal_editing
3260 }
3261
3262 fn selections_did_change(
3263 &mut self,
3264 local: bool,
3265 old_cursor_position: &Anchor,
3266 effects: SelectionEffects,
3267 window: &mut Window,
3268 cx: &mut Context<Self>,
3269 ) {
3270 window.invalidate_character_coordinates();
3271
3272 // Copy selections to primary selection buffer
3273 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3274 if local {
3275 let selections = self
3276 .selections
3277 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3278 let buffer_handle = self.buffer.read(cx).read(cx);
3279
3280 let mut text = String::new();
3281 for (index, selection) in selections.iter().enumerate() {
3282 let text_for_selection = buffer_handle
3283 .text_for_range(selection.start..selection.end)
3284 .collect::<String>();
3285
3286 text.push_str(&text_for_selection);
3287 if index != selections.len() - 1 {
3288 text.push('\n');
3289 }
3290 }
3291
3292 if !text.is_empty() {
3293 cx.write_to_primary(ClipboardItem::new_string(text));
3294 }
3295 }
3296
3297 let selection_anchors = self.selections.disjoint_anchors_arc();
3298
3299 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3300 self.buffer.update(cx, |buffer, cx| {
3301 buffer.set_active_selections(
3302 &selection_anchors,
3303 self.selections.line_mode(),
3304 self.cursor_shape,
3305 cx,
3306 )
3307 });
3308 }
3309 let display_map = self
3310 .display_map
3311 .update(cx, |display_map, cx| display_map.snapshot(cx));
3312 let buffer = display_map.buffer_snapshot();
3313 if self.selections.count() == 1 {
3314 self.add_selections_state = None;
3315 }
3316 self.select_next_state = None;
3317 self.select_prev_state = None;
3318 self.select_syntax_node_history.try_clear();
3319 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3320 self.snippet_stack.invalidate(&selection_anchors, buffer);
3321 self.take_rename(false, window, cx);
3322
3323 let newest_selection = self.selections.newest_anchor();
3324 let new_cursor_position = newest_selection.head();
3325 let selection_start = newest_selection.start;
3326
3327 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3328 self.push_to_nav_history(
3329 *old_cursor_position,
3330 Some(new_cursor_position.to_point(buffer)),
3331 false,
3332 effects.nav_history == Some(true),
3333 cx,
3334 );
3335 }
3336
3337 if local {
3338 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3339 self.register_buffer(buffer_id, cx);
3340 }
3341
3342 let mut context_menu = self.context_menu.borrow_mut();
3343 let completion_menu = match context_menu.as_ref() {
3344 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3345 Some(CodeContextMenu::CodeActions(_)) => {
3346 *context_menu = None;
3347 None
3348 }
3349 None => None,
3350 };
3351 let completion_position = completion_menu.map(|menu| menu.initial_position);
3352 drop(context_menu);
3353
3354 if effects.completions
3355 && let Some(completion_position) = completion_position
3356 {
3357 let start_offset = selection_start.to_offset(buffer);
3358 let position_matches = start_offset == completion_position.to_offset(buffer);
3359 let continue_showing = if position_matches {
3360 if self.snippet_stack.is_empty() {
3361 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3362 == Some(CharKind::Word)
3363 } else {
3364 // Snippet choices can be shown even when the cursor is in whitespace.
3365 // Dismissing the menu with actions like backspace is handled by
3366 // invalidation regions.
3367 true
3368 }
3369 } else {
3370 false
3371 };
3372
3373 if continue_showing {
3374 self.open_or_update_completions_menu(None, None, false, window, cx);
3375 } else {
3376 self.hide_context_menu(window, cx);
3377 }
3378 }
3379
3380 hide_hover(self, cx);
3381
3382 if old_cursor_position.to_display_point(&display_map).row()
3383 != new_cursor_position.to_display_point(&display_map).row()
3384 {
3385 self.available_code_actions.take();
3386 }
3387 self.refresh_code_actions(window, cx);
3388 self.refresh_document_highlights(cx);
3389 refresh_linked_ranges(self, window, cx);
3390
3391 self.refresh_selected_text_highlights(false, window, cx);
3392 self.refresh_matching_bracket_highlights(window, cx);
3393 self.update_visible_edit_prediction(window, cx);
3394 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3395 self.inline_blame_popover.take();
3396 if self.git_blame_inline_enabled {
3397 self.start_inline_blame_timer(window, cx);
3398 }
3399 }
3400
3401 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3402 cx.emit(EditorEvent::SelectionsChanged { local });
3403
3404 let selections = &self.selections.disjoint_anchors_arc();
3405 if selections.len() == 1 {
3406 cx.emit(SearchEvent::ActiveMatchChanged)
3407 }
3408 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3409 let inmemory_selections = selections
3410 .iter()
3411 .map(|s| {
3412 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3413 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3414 })
3415 .collect();
3416 self.update_restoration_data(cx, |data| {
3417 data.selections = inmemory_selections;
3418 });
3419
3420 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
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 == RestoreOnStartupBehavior::None
3461 {
3462 return;
3463 }
3464
3465 if !self.buffer().read(cx).is_singleton() {
3466 return;
3467 }
3468
3469 let display_snapshot = self
3470 .display_map
3471 .update(cx, |display_map, cx| display_map.snapshot(cx));
3472 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3473 return;
3474 };
3475 let inmemory_folds = display_snapshot
3476 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3477 .map(|fold| {
3478 fold.range.start.text_anchor.to_point(&snapshot)
3479 ..fold.range.end.text_anchor.to_point(&snapshot)
3480 })
3481 .collect();
3482 self.update_restoration_data(cx, |data| {
3483 data.folds = inmemory_folds;
3484 });
3485
3486 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3487 return;
3488 };
3489 let background_executor = cx.background_executor().clone();
3490 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3491 let db_folds = display_snapshot
3492 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3493 .map(|fold| {
3494 (
3495 fold.range.start.text_anchor.to_offset(&snapshot),
3496 fold.range.end.text_anchor.to_offset(&snapshot),
3497 )
3498 })
3499 .collect();
3500 self.serialize_folds = cx.background_spawn(async move {
3501 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3502 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3503 .await
3504 .with_context(|| {
3505 format!(
3506 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3507 )
3508 })
3509 .log_err();
3510 });
3511 }
3512
3513 pub fn sync_selections(
3514 &mut self,
3515 other: Entity<Editor>,
3516 cx: &mut Context<Self>,
3517 ) -> gpui::Subscription {
3518 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3519 if !other_selections.is_empty() {
3520 self.selections
3521 .change_with(&self.display_snapshot(cx), |selections| {
3522 selections.select_anchors(other_selections);
3523 });
3524 }
3525
3526 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3527 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3528 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3529 if other_selections.is_empty() {
3530 return;
3531 }
3532 let snapshot = this.display_snapshot(cx);
3533 this.selections.change_with(&snapshot, |selections| {
3534 selections.select_anchors(other_selections);
3535 });
3536 }
3537 });
3538
3539 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3540 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3541 let these_selections = this.selections.disjoint_anchors().to_vec();
3542 if these_selections.is_empty() {
3543 return;
3544 }
3545 other.update(cx, |other_editor, cx| {
3546 let snapshot = other_editor.display_snapshot(cx);
3547 other_editor
3548 .selections
3549 .change_with(&snapshot, |selections| {
3550 selections.select_anchors(these_selections);
3551 })
3552 });
3553 }
3554 });
3555
3556 Subscription::join(other_subscription, this_subscription)
3557 }
3558
3559 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3560 if self.buffer().read(cx).is_singleton() {
3561 return;
3562 }
3563 let snapshot = self.buffer.read(cx).snapshot(cx);
3564 let buffer_ids: HashSet<BufferId> = self
3565 .selections
3566 .disjoint_anchor_ranges()
3567 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3568 .collect();
3569 for buffer_id in buffer_ids {
3570 self.unfold_buffer(buffer_id, cx);
3571 }
3572 }
3573
3574 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3575 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3576 /// effects of selection change occur at the end of the transaction.
3577 pub fn change_selections<R>(
3578 &mut self,
3579 effects: SelectionEffects,
3580 window: &mut Window,
3581 cx: &mut Context<Self>,
3582 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3583 ) -> R {
3584 let snapshot = self.display_snapshot(cx);
3585 if let Some(state) = &mut self.deferred_selection_effects_state {
3586 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3587 state.effects.completions = effects.completions;
3588 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3589 let (changed, result) = self.selections.change_with(&snapshot, change);
3590 state.changed |= changed;
3591 return result;
3592 }
3593 let mut state = DeferredSelectionEffectsState {
3594 changed: false,
3595 effects,
3596 old_cursor_position: self.selections.newest_anchor().head(),
3597 history_entry: SelectionHistoryEntry {
3598 selections: self.selections.disjoint_anchors_arc(),
3599 select_next_state: self.select_next_state.clone(),
3600 select_prev_state: self.select_prev_state.clone(),
3601 add_selections_state: self.add_selections_state.clone(),
3602 },
3603 };
3604 let (changed, result) = self.selections.change_with(&snapshot, change);
3605 state.changed = state.changed || changed;
3606 if self.defer_selection_effects {
3607 self.deferred_selection_effects_state = Some(state);
3608 } else {
3609 self.apply_selection_effects(state, window, cx);
3610 }
3611 result
3612 }
3613
3614 /// Defers the effects of selection change, so that the effects of multiple calls to
3615 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3616 /// to selection history and the state of popovers based on selection position aren't
3617 /// erroneously updated.
3618 pub fn with_selection_effects_deferred<R>(
3619 &mut self,
3620 window: &mut Window,
3621 cx: &mut Context<Self>,
3622 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3623 ) -> R {
3624 let already_deferred = self.defer_selection_effects;
3625 self.defer_selection_effects = true;
3626 let result = update(self, window, cx);
3627 if !already_deferred {
3628 self.defer_selection_effects = false;
3629 if let Some(state) = self.deferred_selection_effects_state.take() {
3630 self.apply_selection_effects(state, window, cx);
3631 }
3632 }
3633 result
3634 }
3635
3636 fn apply_selection_effects(
3637 &mut self,
3638 state: DeferredSelectionEffectsState,
3639 window: &mut Window,
3640 cx: &mut Context<Self>,
3641 ) {
3642 if state.changed {
3643 self.selection_history.push(state.history_entry);
3644
3645 if let Some(autoscroll) = state.effects.scroll {
3646 self.request_autoscroll(autoscroll, cx);
3647 }
3648
3649 let old_cursor_position = &state.old_cursor_position;
3650
3651 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3652
3653 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3654 self.show_signature_help(&ShowSignatureHelp, window, cx);
3655 }
3656 }
3657 }
3658
3659 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3660 where
3661 I: IntoIterator<Item = (Range<S>, T)>,
3662 S: ToOffset,
3663 T: Into<Arc<str>>,
3664 {
3665 if self.read_only(cx) {
3666 return;
3667 }
3668
3669 self.buffer
3670 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3671 }
3672
3673 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3674 where
3675 I: IntoIterator<Item = (Range<S>, T)>,
3676 S: ToOffset,
3677 T: Into<Arc<str>>,
3678 {
3679 if self.read_only(cx) {
3680 return;
3681 }
3682
3683 self.buffer.update(cx, |buffer, cx| {
3684 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3685 });
3686 }
3687
3688 pub fn edit_with_block_indent<I, S, T>(
3689 &mut self,
3690 edits: I,
3691 original_indent_columns: Vec<Option<u32>>,
3692 cx: &mut Context<Self>,
3693 ) where
3694 I: IntoIterator<Item = (Range<S>, T)>,
3695 S: ToOffset,
3696 T: Into<Arc<str>>,
3697 {
3698 if self.read_only(cx) {
3699 return;
3700 }
3701
3702 self.buffer.update(cx, |buffer, cx| {
3703 buffer.edit(
3704 edits,
3705 Some(AutoindentMode::Block {
3706 original_indent_columns,
3707 }),
3708 cx,
3709 )
3710 });
3711 }
3712
3713 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3714 self.hide_context_menu(window, cx);
3715
3716 match phase {
3717 SelectPhase::Begin {
3718 position,
3719 add,
3720 click_count,
3721 } => self.begin_selection(position, add, click_count, window, cx),
3722 SelectPhase::BeginColumnar {
3723 position,
3724 goal_column,
3725 reset,
3726 mode,
3727 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3728 SelectPhase::Extend {
3729 position,
3730 click_count,
3731 } => self.extend_selection(position, click_count, window, cx),
3732 SelectPhase::Update {
3733 position,
3734 goal_column,
3735 scroll_delta,
3736 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3737 SelectPhase::End => self.end_selection(window, cx),
3738 }
3739 }
3740
3741 fn extend_selection(
3742 &mut self,
3743 position: DisplayPoint,
3744 click_count: usize,
3745 window: &mut Window,
3746 cx: &mut Context<Self>,
3747 ) {
3748 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3749 let tail = self
3750 .selections
3751 .newest::<MultiBufferOffset>(&display_map)
3752 .tail();
3753 let click_count = click_count.max(match self.selections.select_mode() {
3754 SelectMode::Character => 1,
3755 SelectMode::Word(_) => 2,
3756 SelectMode::Line(_) => 3,
3757 SelectMode::All => 4,
3758 });
3759 self.begin_selection(position, false, click_count, window, cx);
3760
3761 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3762
3763 let current_selection = match self.selections.select_mode() {
3764 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3765 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3766 };
3767
3768 let mut pending_selection = self
3769 .selections
3770 .pending_anchor()
3771 .cloned()
3772 .expect("extend_selection not called with pending selection");
3773
3774 if pending_selection
3775 .start
3776 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3777 == Ordering::Greater
3778 {
3779 pending_selection.start = current_selection.start;
3780 }
3781 if pending_selection
3782 .end
3783 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3784 == Ordering::Less
3785 {
3786 pending_selection.end = current_selection.end;
3787 pending_selection.reversed = true;
3788 }
3789
3790 let mut pending_mode = self.selections.pending_mode().unwrap();
3791 match &mut pending_mode {
3792 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3793 _ => {}
3794 }
3795
3796 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3797 SelectionEffects::scroll(Autoscroll::fit())
3798 } else {
3799 SelectionEffects::no_scroll()
3800 };
3801
3802 self.change_selections(effects, window, cx, |s| {
3803 s.set_pending(pending_selection.clone(), pending_mode);
3804 s.set_is_extending(true);
3805 });
3806 }
3807
3808 fn begin_selection(
3809 &mut self,
3810 position: DisplayPoint,
3811 add: bool,
3812 click_count: usize,
3813 window: &mut Window,
3814 cx: &mut Context<Self>,
3815 ) {
3816 if !self.focus_handle.is_focused(window) {
3817 self.last_focused_descendant = None;
3818 window.focus(&self.focus_handle);
3819 }
3820
3821 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3822 let buffer = display_map.buffer_snapshot();
3823 let position = display_map.clip_point(position, Bias::Left);
3824
3825 let start;
3826 let end;
3827 let mode;
3828 let mut auto_scroll;
3829 match click_count {
3830 1 => {
3831 start = buffer.anchor_before(position.to_point(&display_map));
3832 end = start;
3833 mode = SelectMode::Character;
3834 auto_scroll = true;
3835 }
3836 2 => {
3837 let position = display_map
3838 .clip_point(position, Bias::Left)
3839 .to_offset(&display_map, Bias::Left);
3840 let (range, _) = buffer.surrounding_word(position, None);
3841 start = buffer.anchor_before(range.start);
3842 end = buffer.anchor_before(range.end);
3843 mode = SelectMode::Word(start..end);
3844 auto_scroll = true;
3845 }
3846 3 => {
3847 let position = display_map
3848 .clip_point(position, Bias::Left)
3849 .to_point(&display_map);
3850 let line_start = display_map.prev_line_boundary(position).0;
3851 let next_line_start = buffer.clip_point(
3852 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3853 Bias::Left,
3854 );
3855 start = buffer.anchor_before(line_start);
3856 end = buffer.anchor_before(next_line_start);
3857 mode = SelectMode::Line(start..end);
3858 auto_scroll = true;
3859 }
3860 _ => {
3861 start = buffer.anchor_before(MultiBufferOffset(0));
3862 end = buffer.anchor_before(buffer.len());
3863 mode = SelectMode::All;
3864 auto_scroll = false;
3865 }
3866 }
3867 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3868
3869 let point_to_delete: Option<usize> = {
3870 let selected_points: Vec<Selection<Point>> =
3871 self.selections.disjoint_in_range(start..end, &display_map);
3872
3873 if !add || click_count > 1 {
3874 None
3875 } else if !selected_points.is_empty() {
3876 Some(selected_points[0].id)
3877 } else {
3878 let clicked_point_already_selected =
3879 self.selections.disjoint_anchors().iter().find(|selection| {
3880 selection.start.to_point(buffer) == start.to_point(buffer)
3881 || selection.end.to_point(buffer) == end.to_point(buffer)
3882 });
3883
3884 clicked_point_already_selected.map(|selection| selection.id)
3885 }
3886 };
3887
3888 let selections_count = self.selections.count();
3889 let effects = if auto_scroll {
3890 SelectionEffects::default()
3891 } else {
3892 SelectionEffects::no_scroll()
3893 };
3894
3895 self.change_selections(effects, window, cx, |s| {
3896 if let Some(point_to_delete) = point_to_delete {
3897 s.delete(point_to_delete);
3898
3899 if selections_count == 1 {
3900 s.set_pending_anchor_range(start..end, mode);
3901 }
3902 } else {
3903 if !add {
3904 s.clear_disjoint();
3905 }
3906
3907 s.set_pending_anchor_range(start..end, mode);
3908 }
3909 });
3910 }
3911
3912 fn begin_columnar_selection(
3913 &mut self,
3914 position: DisplayPoint,
3915 goal_column: u32,
3916 reset: bool,
3917 mode: ColumnarMode,
3918 window: &mut Window,
3919 cx: &mut Context<Self>,
3920 ) {
3921 if !self.focus_handle.is_focused(window) {
3922 self.last_focused_descendant = None;
3923 window.focus(&self.focus_handle);
3924 }
3925
3926 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3927
3928 if reset {
3929 let pointer_position = display_map
3930 .buffer_snapshot()
3931 .anchor_before(position.to_point(&display_map));
3932
3933 self.change_selections(
3934 SelectionEffects::scroll(Autoscroll::newest()),
3935 window,
3936 cx,
3937 |s| {
3938 s.clear_disjoint();
3939 s.set_pending_anchor_range(
3940 pointer_position..pointer_position,
3941 SelectMode::Character,
3942 );
3943 },
3944 );
3945 };
3946
3947 let tail = self.selections.newest::<Point>(&display_map).tail();
3948 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3949 self.columnar_selection_state = match mode {
3950 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3951 selection_tail: selection_anchor,
3952 display_point: if reset {
3953 if position.column() != goal_column {
3954 Some(DisplayPoint::new(position.row(), goal_column))
3955 } else {
3956 None
3957 }
3958 } else {
3959 None
3960 },
3961 }),
3962 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3963 selection_tail: selection_anchor,
3964 }),
3965 };
3966
3967 if !reset {
3968 self.select_columns(position, goal_column, &display_map, window, cx);
3969 }
3970 }
3971
3972 fn update_selection(
3973 &mut self,
3974 position: DisplayPoint,
3975 goal_column: u32,
3976 scroll_delta: gpui::Point<f32>,
3977 window: &mut Window,
3978 cx: &mut Context<Self>,
3979 ) {
3980 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3981
3982 if self.columnar_selection_state.is_some() {
3983 self.select_columns(position, goal_column, &display_map, window, cx);
3984 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3985 let buffer = display_map.buffer_snapshot();
3986 let head;
3987 let tail;
3988 let mode = self.selections.pending_mode().unwrap();
3989 match &mode {
3990 SelectMode::Character => {
3991 head = position.to_point(&display_map);
3992 tail = pending.tail().to_point(buffer);
3993 }
3994 SelectMode::Word(original_range) => {
3995 let offset = display_map
3996 .clip_point(position, Bias::Left)
3997 .to_offset(&display_map, Bias::Left);
3998 let original_range = original_range.to_offset(buffer);
3999
4000 let head_offset = if buffer.is_inside_word(offset, None)
4001 || original_range.contains(&offset)
4002 {
4003 let (word_range, _) = buffer.surrounding_word(offset, None);
4004 if word_range.start < original_range.start {
4005 word_range.start
4006 } else {
4007 word_range.end
4008 }
4009 } else {
4010 offset
4011 };
4012
4013 head = head_offset.to_point(buffer);
4014 if head_offset <= original_range.start {
4015 tail = original_range.end.to_point(buffer);
4016 } else {
4017 tail = original_range.start.to_point(buffer);
4018 }
4019 }
4020 SelectMode::Line(original_range) => {
4021 let original_range = original_range.to_point(display_map.buffer_snapshot());
4022
4023 let position = display_map
4024 .clip_point(position, Bias::Left)
4025 .to_point(&display_map);
4026 let line_start = display_map.prev_line_boundary(position).0;
4027 let next_line_start = buffer.clip_point(
4028 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4029 Bias::Left,
4030 );
4031
4032 if line_start < original_range.start {
4033 head = line_start
4034 } else {
4035 head = next_line_start
4036 }
4037
4038 if head <= original_range.start {
4039 tail = original_range.end;
4040 } else {
4041 tail = original_range.start;
4042 }
4043 }
4044 SelectMode::All => {
4045 return;
4046 }
4047 };
4048
4049 if head < tail {
4050 pending.start = buffer.anchor_before(head);
4051 pending.end = buffer.anchor_before(tail);
4052 pending.reversed = true;
4053 } else {
4054 pending.start = buffer.anchor_before(tail);
4055 pending.end = buffer.anchor_before(head);
4056 pending.reversed = false;
4057 }
4058
4059 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4060 s.set_pending(pending.clone(), mode);
4061 });
4062 } else {
4063 log::error!("update_selection dispatched with no pending selection");
4064 return;
4065 }
4066
4067 self.apply_scroll_delta(scroll_delta, window, cx);
4068 cx.notify();
4069 }
4070
4071 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4072 self.columnar_selection_state.take();
4073 if let Some(pending_mode) = self.selections.pending_mode() {
4074 let selections = self
4075 .selections
4076 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4077 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4078 s.select(selections);
4079 s.clear_pending();
4080 if s.is_extending() {
4081 s.set_is_extending(false);
4082 } else {
4083 s.set_select_mode(pending_mode);
4084 }
4085 });
4086 }
4087 }
4088
4089 fn select_columns(
4090 &mut self,
4091 head: DisplayPoint,
4092 goal_column: u32,
4093 display_map: &DisplaySnapshot,
4094 window: &mut Window,
4095 cx: &mut Context<Self>,
4096 ) {
4097 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4098 return;
4099 };
4100
4101 let tail = match columnar_state {
4102 ColumnarSelectionState::FromMouse {
4103 selection_tail,
4104 display_point,
4105 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4106 ColumnarSelectionState::FromSelection { selection_tail } => {
4107 selection_tail.to_display_point(display_map)
4108 }
4109 };
4110
4111 let start_row = cmp::min(tail.row(), head.row());
4112 let end_row = cmp::max(tail.row(), head.row());
4113 let start_column = cmp::min(tail.column(), goal_column);
4114 let end_column = cmp::max(tail.column(), goal_column);
4115 let reversed = start_column < tail.column();
4116
4117 let selection_ranges = (start_row.0..=end_row.0)
4118 .map(DisplayRow)
4119 .filter_map(|row| {
4120 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4121 || start_column <= display_map.line_len(row))
4122 && !display_map.is_block_line(row)
4123 {
4124 let start = display_map
4125 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4126 .to_point(display_map);
4127 let end = display_map
4128 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4129 .to_point(display_map);
4130 if reversed {
4131 Some(end..start)
4132 } else {
4133 Some(start..end)
4134 }
4135 } else {
4136 None
4137 }
4138 })
4139 .collect::<Vec<_>>();
4140 if selection_ranges.is_empty() {
4141 return;
4142 }
4143
4144 let ranges = match columnar_state {
4145 ColumnarSelectionState::FromMouse { .. } => {
4146 let mut non_empty_ranges = selection_ranges
4147 .iter()
4148 .filter(|selection_range| selection_range.start != selection_range.end)
4149 .peekable();
4150 if non_empty_ranges.peek().is_some() {
4151 non_empty_ranges.cloned().collect()
4152 } else {
4153 selection_ranges
4154 }
4155 }
4156 _ => selection_ranges,
4157 };
4158
4159 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4160 s.select_ranges(ranges);
4161 });
4162 cx.notify();
4163 }
4164
4165 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4166 self.selections
4167 .all_adjusted(snapshot)
4168 .iter()
4169 .any(|selection| !selection.is_empty())
4170 }
4171
4172 pub fn has_pending_nonempty_selection(&self) -> bool {
4173 let pending_nonempty_selection = match self.selections.pending_anchor() {
4174 Some(Selection { start, end, .. }) => start != end,
4175 None => false,
4176 };
4177
4178 pending_nonempty_selection
4179 || (self.columnar_selection_state.is_some()
4180 && self.selections.disjoint_anchors().len() > 1)
4181 }
4182
4183 pub fn has_pending_selection(&self) -> bool {
4184 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4185 }
4186
4187 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4188 self.selection_mark_mode = false;
4189 self.selection_drag_state = SelectionDragState::None;
4190
4191 if self.dismiss_menus_and_popups(true, window, cx) {
4192 cx.notify();
4193 return;
4194 }
4195 if self.clear_expanded_diff_hunks(cx) {
4196 cx.notify();
4197 return;
4198 }
4199 if self.show_git_blame_gutter {
4200 self.show_git_blame_gutter = false;
4201 cx.notify();
4202 return;
4203 }
4204
4205 if self.mode.is_full()
4206 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4207 {
4208 cx.notify();
4209 return;
4210 }
4211
4212 cx.propagate();
4213 }
4214
4215 pub fn dismiss_menus_and_popups(
4216 &mut self,
4217 is_user_requested: bool,
4218 window: &mut Window,
4219 cx: &mut Context<Self>,
4220 ) -> bool {
4221 let mut dismissed = false;
4222
4223 dismissed |= self.take_rename(false, window, cx).is_some();
4224 dismissed |= self.hide_blame_popover(true, cx);
4225 dismissed |= hide_hover(self, cx);
4226 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4227 dismissed |= self.hide_context_menu(window, cx).is_some();
4228 dismissed |= self.mouse_context_menu.take().is_some();
4229 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4230 dismissed |= self.snippet_stack.pop().is_some();
4231
4232 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4233 self.dismiss_diagnostics(cx);
4234 dismissed = true;
4235 }
4236
4237 dismissed
4238 }
4239
4240 fn linked_editing_ranges_for(
4241 &self,
4242 selection: Range<text::Anchor>,
4243 cx: &App,
4244 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4245 if self.linked_edit_ranges.is_empty() {
4246 return None;
4247 }
4248 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4249 selection.end.buffer_id.and_then(|end_buffer_id| {
4250 if selection.start.buffer_id != Some(end_buffer_id) {
4251 return None;
4252 }
4253 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4254 let snapshot = buffer.read(cx).snapshot();
4255 self.linked_edit_ranges
4256 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4257 .map(|ranges| (ranges, snapshot, buffer))
4258 })?;
4259 use text::ToOffset as TO;
4260 // find offset from the start of current range to current cursor position
4261 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4262
4263 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4264 let start_difference = start_offset - start_byte_offset;
4265 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4266 let end_difference = end_offset - start_byte_offset;
4267 // Current range has associated linked ranges.
4268 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4269 for range in linked_ranges.iter() {
4270 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4271 let end_offset = start_offset + end_difference;
4272 let start_offset = start_offset + start_difference;
4273 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4274 continue;
4275 }
4276 if self.selections.disjoint_anchor_ranges().any(|s| {
4277 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4278 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4279 {
4280 return false;
4281 }
4282 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4283 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4284 }) {
4285 continue;
4286 }
4287 let start = buffer_snapshot.anchor_after(start_offset);
4288 let end = buffer_snapshot.anchor_after(end_offset);
4289 linked_edits
4290 .entry(buffer.clone())
4291 .or_default()
4292 .push(start..end);
4293 }
4294 Some(linked_edits)
4295 }
4296
4297 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4298 let text: Arc<str> = text.into();
4299
4300 if self.read_only(cx) {
4301 return;
4302 }
4303
4304 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4305
4306 self.unfold_buffers_with_selections(cx);
4307
4308 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4309 let mut bracket_inserted = false;
4310 let mut edits = Vec::new();
4311 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4312 let mut new_selections = Vec::with_capacity(selections.len());
4313 let mut new_autoclose_regions = Vec::new();
4314 let snapshot = self.buffer.read(cx).read(cx);
4315 let mut clear_linked_edit_ranges = false;
4316
4317 for (selection, autoclose_region) in
4318 self.selections_with_autoclose_regions(selections, &snapshot)
4319 {
4320 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4321 // Determine if the inserted text matches the opening or closing
4322 // bracket of any of this language's bracket pairs.
4323 let mut bracket_pair = None;
4324 let mut is_bracket_pair_start = false;
4325 let mut is_bracket_pair_end = false;
4326 if !text.is_empty() {
4327 let mut bracket_pair_matching_end = None;
4328 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4329 // and they are removing the character that triggered IME popup.
4330 for (pair, enabled) in scope.brackets() {
4331 if !pair.close && !pair.surround {
4332 continue;
4333 }
4334
4335 if enabled && pair.start.ends_with(text.as_ref()) {
4336 let prefix_len = pair.start.len() - text.len();
4337 let preceding_text_matches_prefix = prefix_len == 0
4338 || (selection.start.column >= (prefix_len as u32)
4339 && snapshot.contains_str_at(
4340 Point::new(
4341 selection.start.row,
4342 selection.start.column - (prefix_len as u32),
4343 ),
4344 &pair.start[..prefix_len],
4345 ));
4346 if preceding_text_matches_prefix {
4347 bracket_pair = Some(pair.clone());
4348 is_bracket_pair_start = true;
4349 break;
4350 }
4351 }
4352 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4353 {
4354 // take first bracket pair matching end, but don't break in case a later bracket
4355 // pair matches start
4356 bracket_pair_matching_end = Some(pair.clone());
4357 }
4358 }
4359 if let Some(end) = bracket_pair_matching_end
4360 && bracket_pair.is_none()
4361 {
4362 bracket_pair = Some(end);
4363 is_bracket_pair_end = true;
4364 }
4365 }
4366
4367 if let Some(bracket_pair) = bracket_pair {
4368 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4369 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4370 let auto_surround =
4371 self.use_auto_surround && snapshot_settings.use_auto_surround;
4372 if selection.is_empty() {
4373 if is_bracket_pair_start {
4374 // If the inserted text is a suffix of an opening bracket and the
4375 // selection is preceded by the rest of the opening bracket, then
4376 // insert the closing bracket.
4377 let following_text_allows_autoclose = snapshot
4378 .chars_at(selection.start)
4379 .next()
4380 .is_none_or(|c| scope.should_autoclose_before(c));
4381
4382 let preceding_text_allows_autoclose = selection.start.column == 0
4383 || snapshot
4384 .reversed_chars_at(selection.start)
4385 .next()
4386 .is_none_or(|c| {
4387 bracket_pair.start != bracket_pair.end
4388 || !snapshot
4389 .char_classifier_at(selection.start)
4390 .is_word(c)
4391 });
4392
4393 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4394 && bracket_pair.start.len() == 1
4395 {
4396 let target = bracket_pair.start.chars().next().unwrap();
4397 let mut byte_offset = 0u32;
4398 let current_line_count = snapshot
4399 .reversed_chars_at(selection.start)
4400 .take_while(|&c| c != '\n')
4401 .filter(|c| {
4402 byte_offset += c.len_utf8() as u32;
4403 if *c != target {
4404 return false;
4405 }
4406
4407 let point = Point::new(
4408 selection.start.row,
4409 selection.start.column.saturating_sub(byte_offset),
4410 );
4411
4412 let is_enabled = snapshot
4413 .language_scope_at(point)
4414 .and_then(|scope| {
4415 scope
4416 .brackets()
4417 .find(|(pair, _)| {
4418 pair.start == bracket_pair.start
4419 })
4420 .map(|(_, enabled)| enabled)
4421 })
4422 .unwrap_or(true);
4423
4424 let is_delimiter = snapshot
4425 .language_scope_at(Point::new(
4426 point.row,
4427 point.column + 1,
4428 ))
4429 .and_then(|scope| {
4430 scope
4431 .brackets()
4432 .find(|(pair, _)| {
4433 pair.start == bracket_pair.start
4434 })
4435 .map(|(_, enabled)| !enabled)
4436 })
4437 .unwrap_or(false);
4438
4439 is_enabled && !is_delimiter
4440 })
4441 .count();
4442 current_line_count % 2 == 1
4443 } else {
4444 false
4445 };
4446
4447 if autoclose
4448 && bracket_pair.close
4449 && following_text_allows_autoclose
4450 && preceding_text_allows_autoclose
4451 && !is_closing_quote
4452 {
4453 let anchor = snapshot.anchor_before(selection.end);
4454 new_selections.push((selection.map(|_| anchor), text.len()));
4455 new_autoclose_regions.push((
4456 anchor,
4457 text.len(),
4458 selection.id,
4459 bracket_pair.clone(),
4460 ));
4461 edits.push((
4462 selection.range(),
4463 format!("{}{}", text, bracket_pair.end).into(),
4464 ));
4465 bracket_inserted = true;
4466 continue;
4467 }
4468 }
4469
4470 if let Some(region) = autoclose_region {
4471 // If the selection is followed by an auto-inserted closing bracket,
4472 // then don't insert that closing bracket again; just move the selection
4473 // past the closing bracket.
4474 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4475 && text.as_ref() == region.pair.end.as_str()
4476 && snapshot.contains_str_at(region.range.end, text.as_ref());
4477 if should_skip {
4478 let anchor = snapshot.anchor_after(selection.end);
4479 new_selections
4480 .push((selection.map(|_| anchor), region.pair.end.len()));
4481 continue;
4482 }
4483 }
4484
4485 let always_treat_brackets_as_autoclosed = snapshot
4486 .language_settings_at(selection.start, cx)
4487 .always_treat_brackets_as_autoclosed;
4488 if always_treat_brackets_as_autoclosed
4489 && is_bracket_pair_end
4490 && snapshot.contains_str_at(selection.end, text.as_ref())
4491 {
4492 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4493 // and the inserted text is a closing bracket and the selection is followed
4494 // by the closing bracket then move the selection past the closing bracket.
4495 let anchor = snapshot.anchor_after(selection.end);
4496 new_selections.push((selection.map(|_| anchor), text.len()));
4497 continue;
4498 }
4499 }
4500 // If an opening bracket is 1 character long and is typed while
4501 // text is selected, then surround that text with the bracket pair.
4502 else if auto_surround
4503 && bracket_pair.surround
4504 && is_bracket_pair_start
4505 && bracket_pair.start.chars().count() == 1
4506 {
4507 edits.push((selection.start..selection.start, text.clone()));
4508 edits.push((
4509 selection.end..selection.end,
4510 bracket_pair.end.as_str().into(),
4511 ));
4512 bracket_inserted = true;
4513 new_selections.push((
4514 Selection {
4515 id: selection.id,
4516 start: snapshot.anchor_after(selection.start),
4517 end: snapshot.anchor_before(selection.end),
4518 reversed: selection.reversed,
4519 goal: selection.goal,
4520 },
4521 0,
4522 ));
4523 continue;
4524 }
4525 }
4526 }
4527
4528 if self.auto_replace_emoji_shortcode
4529 && selection.is_empty()
4530 && text.as_ref().ends_with(':')
4531 && let Some(possible_emoji_short_code) =
4532 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4533 && !possible_emoji_short_code.is_empty()
4534 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4535 {
4536 let emoji_shortcode_start = Point::new(
4537 selection.start.row,
4538 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4539 );
4540
4541 // Remove shortcode from buffer
4542 edits.push((
4543 emoji_shortcode_start..selection.start,
4544 "".to_string().into(),
4545 ));
4546 new_selections.push((
4547 Selection {
4548 id: selection.id,
4549 start: snapshot.anchor_after(emoji_shortcode_start),
4550 end: snapshot.anchor_before(selection.start),
4551 reversed: selection.reversed,
4552 goal: selection.goal,
4553 },
4554 0,
4555 ));
4556
4557 // Insert emoji
4558 let selection_start_anchor = snapshot.anchor_after(selection.start);
4559 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4560 edits.push((selection.start..selection.end, emoji.to_string().into()));
4561
4562 continue;
4563 }
4564
4565 // If not handling any auto-close operation, then just replace the selected
4566 // text with the given input and move the selection to the end of the
4567 // newly inserted text.
4568 let anchor = snapshot.anchor_after(selection.end);
4569 if !self.linked_edit_ranges.is_empty() {
4570 let start_anchor = snapshot.anchor_before(selection.start);
4571
4572 let is_word_char = text.chars().next().is_none_or(|char| {
4573 let classifier = snapshot
4574 .char_classifier_at(start_anchor.to_offset(&snapshot))
4575 .scope_context(Some(CharScopeContext::LinkedEdit));
4576 classifier.is_word(char)
4577 });
4578
4579 if is_word_char {
4580 if let Some(ranges) = self
4581 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4582 {
4583 for (buffer, edits) in ranges {
4584 linked_edits
4585 .entry(buffer.clone())
4586 .or_default()
4587 .extend(edits.into_iter().map(|range| (range, text.clone())));
4588 }
4589 }
4590 } else {
4591 clear_linked_edit_ranges = true;
4592 }
4593 }
4594
4595 new_selections.push((selection.map(|_| anchor), 0));
4596 edits.push((selection.start..selection.end, text.clone()));
4597 }
4598
4599 drop(snapshot);
4600
4601 self.transact(window, cx, |this, window, cx| {
4602 if clear_linked_edit_ranges {
4603 this.linked_edit_ranges.clear();
4604 }
4605 let initial_buffer_versions =
4606 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4607
4608 this.buffer.update(cx, |buffer, cx| {
4609 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4610 });
4611 for (buffer, edits) in linked_edits {
4612 buffer.update(cx, |buffer, cx| {
4613 let snapshot = buffer.snapshot();
4614 let edits = edits
4615 .into_iter()
4616 .map(|(range, text)| {
4617 use text::ToPoint as TP;
4618 let end_point = TP::to_point(&range.end, &snapshot);
4619 let start_point = TP::to_point(&range.start, &snapshot);
4620 (start_point..end_point, text)
4621 })
4622 .sorted_by_key(|(range, _)| range.start);
4623 buffer.edit(edits, None, cx);
4624 })
4625 }
4626 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4627 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4628 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4629 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4630 new_anchor_selections,
4631 &map,
4632 )
4633 .zip(new_selection_deltas)
4634 .map(|(selection, delta)| Selection {
4635 id: selection.id,
4636 start: selection.start + delta,
4637 end: selection.end + delta,
4638 reversed: selection.reversed,
4639 goal: SelectionGoal::None,
4640 })
4641 .collect::<Vec<_>>();
4642
4643 let mut i = 0;
4644 for (position, delta, selection_id, pair) in new_autoclose_regions {
4645 let position = position.to_offset(map.buffer_snapshot()) + delta;
4646 let start = map.buffer_snapshot().anchor_before(position);
4647 let end = map.buffer_snapshot().anchor_after(position);
4648 while let Some(existing_state) = this.autoclose_regions.get(i) {
4649 match existing_state
4650 .range
4651 .start
4652 .cmp(&start, map.buffer_snapshot())
4653 {
4654 Ordering::Less => i += 1,
4655 Ordering::Greater => break,
4656 Ordering::Equal => {
4657 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4658 Ordering::Less => i += 1,
4659 Ordering::Equal => break,
4660 Ordering::Greater => break,
4661 }
4662 }
4663 }
4664 }
4665 this.autoclose_regions.insert(
4666 i,
4667 AutocloseRegion {
4668 selection_id,
4669 range: start..end,
4670 pair,
4671 },
4672 );
4673 }
4674
4675 let had_active_edit_prediction = this.has_active_edit_prediction();
4676 this.change_selections(
4677 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4678 window,
4679 cx,
4680 |s| s.select(new_selections),
4681 );
4682
4683 if !bracket_inserted
4684 && let Some(on_type_format_task) =
4685 this.trigger_on_type_formatting(text.to_string(), window, cx)
4686 {
4687 on_type_format_task.detach_and_log_err(cx);
4688 }
4689
4690 let editor_settings = EditorSettings::get_global(cx);
4691 if bracket_inserted
4692 && (editor_settings.auto_signature_help
4693 || editor_settings.show_signature_help_after_edits)
4694 {
4695 this.show_signature_help(&ShowSignatureHelp, window, cx);
4696 }
4697
4698 let trigger_in_words =
4699 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4700 if this.hard_wrap.is_some() {
4701 let latest: Range<Point> = this.selections.newest(&map).range();
4702 if latest.is_empty()
4703 && this
4704 .buffer()
4705 .read(cx)
4706 .snapshot(cx)
4707 .line_len(MultiBufferRow(latest.start.row))
4708 == latest.start.column
4709 {
4710 this.rewrap_impl(
4711 RewrapOptions {
4712 override_language_settings: true,
4713 preserve_existing_whitespace: true,
4714 },
4715 cx,
4716 )
4717 }
4718 }
4719 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4720 refresh_linked_ranges(this, window, cx);
4721 this.refresh_edit_prediction(true, false, window, cx);
4722 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4723 });
4724 }
4725
4726 fn find_possible_emoji_shortcode_at_position(
4727 snapshot: &MultiBufferSnapshot,
4728 position: Point,
4729 ) -> Option<String> {
4730 let mut chars = Vec::new();
4731 let mut found_colon = false;
4732 for char in snapshot.reversed_chars_at(position).take(100) {
4733 // Found a possible emoji shortcode in the middle of the buffer
4734 if found_colon {
4735 if char.is_whitespace() {
4736 chars.reverse();
4737 return Some(chars.iter().collect());
4738 }
4739 // If the previous character is not a whitespace, we are in the middle of a word
4740 // and we only want to complete the shortcode if the word is made up of other emojis
4741 let mut containing_word = String::new();
4742 for ch in snapshot
4743 .reversed_chars_at(position)
4744 .skip(chars.len() + 1)
4745 .take(100)
4746 {
4747 if ch.is_whitespace() {
4748 break;
4749 }
4750 containing_word.push(ch);
4751 }
4752 let containing_word = containing_word.chars().rev().collect::<String>();
4753 if util::word_consists_of_emojis(containing_word.as_str()) {
4754 chars.reverse();
4755 return Some(chars.iter().collect());
4756 }
4757 }
4758
4759 if char.is_whitespace() || !char.is_ascii() {
4760 return None;
4761 }
4762 if char == ':' {
4763 found_colon = true;
4764 } else {
4765 chars.push(char);
4766 }
4767 }
4768 // Found a possible emoji shortcode at the beginning of the buffer
4769 chars.reverse();
4770 Some(chars.iter().collect())
4771 }
4772
4773 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4774 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4775 self.transact(window, cx, |this, window, cx| {
4776 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4777 let selections = this
4778 .selections
4779 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4780 let multi_buffer = this.buffer.read(cx);
4781 let buffer = multi_buffer.snapshot(cx);
4782 selections
4783 .iter()
4784 .map(|selection| {
4785 let start_point = selection.start.to_point(&buffer);
4786 let mut existing_indent =
4787 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4788 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4789 let start = selection.start;
4790 let end = selection.end;
4791 let selection_is_empty = start == end;
4792 let language_scope = buffer.language_scope_at(start);
4793 let (
4794 comment_delimiter,
4795 doc_delimiter,
4796 insert_extra_newline,
4797 indent_on_newline,
4798 indent_on_extra_newline,
4799 ) = if let Some(language) = &language_scope {
4800 let mut insert_extra_newline =
4801 insert_extra_newline_brackets(&buffer, start..end, language)
4802 || insert_extra_newline_tree_sitter(&buffer, start..end);
4803
4804 // Comment extension on newline is allowed only for cursor selections
4805 let comment_delimiter = maybe!({
4806 if !selection_is_empty {
4807 return None;
4808 }
4809
4810 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4811 return None;
4812 }
4813
4814 let delimiters = language.line_comment_prefixes();
4815 let max_len_of_delimiter =
4816 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4817 let (snapshot, range) =
4818 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4819
4820 let num_of_whitespaces = snapshot
4821 .chars_for_range(range.clone())
4822 .take_while(|c| c.is_whitespace())
4823 .count();
4824 let comment_candidate = snapshot
4825 .chars_for_range(range.clone())
4826 .skip(num_of_whitespaces)
4827 .take(max_len_of_delimiter)
4828 .collect::<String>();
4829 let (delimiter, trimmed_len) = delimiters
4830 .iter()
4831 .filter_map(|delimiter| {
4832 let prefix = delimiter.trim_end();
4833 if comment_candidate.starts_with(prefix) {
4834 Some((delimiter, prefix.len()))
4835 } else {
4836 None
4837 }
4838 })
4839 .max_by_key(|(_, len)| *len)?;
4840
4841 if let Some(BlockCommentConfig {
4842 start: block_start, ..
4843 }) = language.block_comment()
4844 {
4845 let block_start_trimmed = block_start.trim_end();
4846 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4847 let line_content = snapshot
4848 .chars_for_range(range)
4849 .skip(num_of_whitespaces)
4850 .take(block_start_trimmed.len())
4851 .collect::<String>();
4852
4853 if line_content.starts_with(block_start_trimmed) {
4854 return None;
4855 }
4856 }
4857 }
4858
4859 let cursor_is_placed_after_comment_marker =
4860 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4861 if cursor_is_placed_after_comment_marker {
4862 Some(delimiter.clone())
4863 } else {
4864 None
4865 }
4866 });
4867
4868 let mut indent_on_newline = IndentSize::spaces(0);
4869 let mut indent_on_extra_newline = IndentSize::spaces(0);
4870
4871 let doc_delimiter = maybe!({
4872 if !selection_is_empty {
4873 return None;
4874 }
4875
4876 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4877 return None;
4878 }
4879
4880 let BlockCommentConfig {
4881 start: start_tag,
4882 end: end_tag,
4883 prefix: delimiter,
4884 tab_size: len,
4885 } = language.documentation_comment()?;
4886 let is_within_block_comment = buffer
4887 .language_scope_at(start_point)
4888 .is_some_and(|scope| scope.override_name() == Some("comment"));
4889 if !is_within_block_comment {
4890 return None;
4891 }
4892
4893 let (snapshot, range) =
4894 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4895
4896 let num_of_whitespaces = snapshot
4897 .chars_for_range(range.clone())
4898 .take_while(|c| c.is_whitespace())
4899 .count();
4900
4901 // 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.
4902 let column = start_point.column;
4903 let cursor_is_after_start_tag = {
4904 let start_tag_len = start_tag.len();
4905 let start_tag_line = snapshot
4906 .chars_for_range(range.clone())
4907 .skip(num_of_whitespaces)
4908 .take(start_tag_len)
4909 .collect::<String>();
4910 if start_tag_line.starts_with(start_tag.as_ref()) {
4911 num_of_whitespaces + start_tag_len <= column as usize
4912 } else {
4913 false
4914 }
4915 };
4916
4917 let cursor_is_after_delimiter = {
4918 let delimiter_trim = delimiter.trim_end();
4919 let delimiter_line = snapshot
4920 .chars_for_range(range.clone())
4921 .skip(num_of_whitespaces)
4922 .take(delimiter_trim.len())
4923 .collect::<String>();
4924 if delimiter_line.starts_with(delimiter_trim) {
4925 num_of_whitespaces + delimiter_trim.len() <= column as usize
4926 } else {
4927 false
4928 }
4929 };
4930
4931 let cursor_is_before_end_tag_if_exists = {
4932 let mut char_position = 0u32;
4933 let mut end_tag_offset = None;
4934
4935 'outer: for chunk in snapshot.text_for_range(range) {
4936 if let Some(byte_pos) = chunk.find(&**end_tag) {
4937 let chars_before_match =
4938 chunk[..byte_pos].chars().count() as u32;
4939 end_tag_offset =
4940 Some(char_position + chars_before_match);
4941 break 'outer;
4942 }
4943 char_position += chunk.chars().count() as u32;
4944 }
4945
4946 if let Some(end_tag_offset) = end_tag_offset {
4947 let cursor_is_before_end_tag = column <= end_tag_offset;
4948 if cursor_is_after_start_tag {
4949 if cursor_is_before_end_tag {
4950 insert_extra_newline = true;
4951 }
4952 let cursor_is_at_start_of_end_tag =
4953 column == end_tag_offset;
4954 if cursor_is_at_start_of_end_tag {
4955 indent_on_extra_newline.len = *len;
4956 }
4957 }
4958 cursor_is_before_end_tag
4959 } else {
4960 true
4961 }
4962 };
4963
4964 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4965 && cursor_is_before_end_tag_if_exists
4966 {
4967 if cursor_is_after_start_tag {
4968 indent_on_newline.len = *len;
4969 }
4970 Some(delimiter.clone())
4971 } else {
4972 None
4973 }
4974 });
4975
4976 (
4977 comment_delimiter,
4978 doc_delimiter,
4979 insert_extra_newline,
4980 indent_on_newline,
4981 indent_on_extra_newline,
4982 )
4983 } else {
4984 (
4985 None,
4986 None,
4987 false,
4988 IndentSize::default(),
4989 IndentSize::default(),
4990 )
4991 };
4992
4993 let prevent_auto_indent = doc_delimiter.is_some();
4994 let delimiter = comment_delimiter.or(doc_delimiter);
4995
4996 let capacity_for_delimiter =
4997 delimiter.as_deref().map(str::len).unwrap_or_default();
4998 let mut new_text = String::with_capacity(
4999 1 + capacity_for_delimiter
5000 + existing_indent.len as usize
5001 + indent_on_newline.len as usize
5002 + indent_on_extra_newline.len as usize,
5003 );
5004 new_text.push('\n');
5005 new_text.extend(existing_indent.chars());
5006 new_text.extend(indent_on_newline.chars());
5007
5008 if let Some(delimiter) = &delimiter {
5009 new_text.push_str(delimiter);
5010 }
5011
5012 if insert_extra_newline {
5013 new_text.push('\n');
5014 new_text.extend(existing_indent.chars());
5015 new_text.extend(indent_on_extra_newline.chars());
5016 }
5017
5018 let anchor = buffer.anchor_after(end);
5019 let new_selection = selection.map(|_| anchor);
5020 (
5021 ((start..end, new_text), prevent_auto_indent),
5022 (insert_extra_newline, new_selection),
5023 )
5024 })
5025 .unzip()
5026 };
5027
5028 let mut auto_indent_edits = Vec::new();
5029 let mut edits = Vec::new();
5030 for (edit, prevent_auto_indent) in edits_with_flags {
5031 if prevent_auto_indent {
5032 edits.push(edit);
5033 } else {
5034 auto_indent_edits.push(edit);
5035 }
5036 }
5037 if !edits.is_empty() {
5038 this.edit(edits, cx);
5039 }
5040 if !auto_indent_edits.is_empty() {
5041 this.edit_with_autoindent(auto_indent_edits, cx);
5042 }
5043
5044 let buffer = this.buffer.read(cx).snapshot(cx);
5045 let new_selections = selection_info
5046 .into_iter()
5047 .map(|(extra_newline_inserted, new_selection)| {
5048 let mut cursor = new_selection.end.to_point(&buffer);
5049 if extra_newline_inserted {
5050 cursor.row -= 1;
5051 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5052 }
5053 new_selection.map(|_| cursor)
5054 })
5055 .collect();
5056
5057 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5058 this.refresh_edit_prediction(true, false, window, cx);
5059 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5060 task.detach_and_log_err(cx);
5061 }
5062 });
5063 }
5064
5065 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5067
5068 let buffer = self.buffer.read(cx);
5069 let snapshot = buffer.snapshot(cx);
5070
5071 let mut edits = Vec::new();
5072 let mut rows = Vec::new();
5073
5074 for (rows_inserted, selection) in self
5075 .selections
5076 .all_adjusted(&self.display_snapshot(cx))
5077 .into_iter()
5078 .enumerate()
5079 {
5080 let cursor = selection.head();
5081 let row = cursor.row;
5082
5083 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5084
5085 let newline = "\n".to_string();
5086 edits.push((start_of_line..start_of_line, newline));
5087
5088 rows.push(row + rows_inserted as u32);
5089 }
5090
5091 self.transact(window, cx, |editor, window, cx| {
5092 editor.edit(edits, cx);
5093
5094 editor.change_selections(Default::default(), window, cx, |s| {
5095 let mut index = 0;
5096 s.move_cursors_with(|map, _, _| {
5097 let row = rows[index];
5098 index += 1;
5099
5100 let point = Point::new(row, 0);
5101 let boundary = map.next_line_boundary(point).1;
5102 let clipped = map.clip_point(boundary, Bias::Left);
5103
5104 (clipped, SelectionGoal::None)
5105 });
5106 });
5107
5108 let mut indent_edits = Vec::new();
5109 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5110 for row in rows {
5111 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5112 for (row, indent) in indents {
5113 if indent.len == 0 {
5114 continue;
5115 }
5116
5117 let text = match indent.kind {
5118 IndentKind::Space => " ".repeat(indent.len as usize),
5119 IndentKind::Tab => "\t".repeat(indent.len as usize),
5120 };
5121 let point = Point::new(row.0, 0);
5122 indent_edits.push((point..point, text));
5123 }
5124 }
5125 editor.edit(indent_edits, cx);
5126 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5127 format.detach_and_log_err(cx);
5128 }
5129 });
5130 }
5131
5132 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5134
5135 let buffer = self.buffer.read(cx);
5136 let snapshot = buffer.snapshot(cx);
5137
5138 let mut edits = Vec::new();
5139 let mut rows = Vec::new();
5140 let mut rows_inserted = 0;
5141
5142 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5143 let cursor = selection.head();
5144 let row = cursor.row;
5145
5146 let point = Point::new(row + 1, 0);
5147 let start_of_line = snapshot.clip_point(point, Bias::Left);
5148
5149 let newline = "\n".to_string();
5150 edits.push((start_of_line..start_of_line, newline));
5151
5152 rows_inserted += 1;
5153 rows.push(row + rows_inserted);
5154 }
5155
5156 self.transact(window, cx, |editor, window, cx| {
5157 editor.edit(edits, cx);
5158
5159 editor.change_selections(Default::default(), window, cx, |s| {
5160 let mut index = 0;
5161 s.move_cursors_with(|map, _, _| {
5162 let row = rows[index];
5163 index += 1;
5164
5165 let point = Point::new(row, 0);
5166 let boundary = map.next_line_boundary(point).1;
5167 let clipped = map.clip_point(boundary, Bias::Left);
5168
5169 (clipped, SelectionGoal::None)
5170 });
5171 });
5172
5173 let mut indent_edits = Vec::new();
5174 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5175 for row in rows {
5176 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5177 for (row, indent) in indents {
5178 if indent.len == 0 {
5179 continue;
5180 }
5181
5182 let text = match indent.kind {
5183 IndentKind::Space => " ".repeat(indent.len as usize),
5184 IndentKind::Tab => "\t".repeat(indent.len as usize),
5185 };
5186 let point = Point::new(row.0, 0);
5187 indent_edits.push((point..point, text));
5188 }
5189 }
5190 editor.edit(indent_edits, cx);
5191 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5192 format.detach_and_log_err(cx);
5193 }
5194 });
5195 }
5196
5197 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5198 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5199 original_indent_columns: Vec::new(),
5200 });
5201 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5202 }
5203
5204 fn insert_with_autoindent_mode(
5205 &mut self,
5206 text: &str,
5207 autoindent_mode: Option<AutoindentMode>,
5208 window: &mut Window,
5209 cx: &mut Context<Self>,
5210 ) {
5211 if self.read_only(cx) {
5212 return;
5213 }
5214
5215 let text: Arc<str> = text.into();
5216 self.transact(window, cx, |this, window, cx| {
5217 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5218 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5219 let anchors = {
5220 let snapshot = buffer.read(cx);
5221 old_selections
5222 .iter()
5223 .map(|s| {
5224 let anchor = snapshot.anchor_after(s.head());
5225 s.map(|_| anchor)
5226 })
5227 .collect::<Vec<_>>()
5228 };
5229 buffer.edit(
5230 old_selections
5231 .iter()
5232 .map(|s| (s.start..s.end, text.clone())),
5233 autoindent_mode,
5234 cx,
5235 );
5236 anchors
5237 });
5238
5239 this.change_selections(Default::default(), window, cx, |s| {
5240 s.select_anchors(selection_anchors);
5241 });
5242
5243 cx.notify();
5244 });
5245 }
5246
5247 fn trigger_completion_on_input(
5248 &mut self,
5249 text: &str,
5250 trigger_in_words: bool,
5251 window: &mut Window,
5252 cx: &mut Context<Self>,
5253 ) {
5254 let completions_source = self
5255 .context_menu
5256 .borrow()
5257 .as_ref()
5258 .and_then(|menu| match menu {
5259 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5260 CodeContextMenu::CodeActions(_) => None,
5261 });
5262
5263 match completions_source {
5264 Some(CompletionsMenuSource::Words { .. }) => {
5265 self.open_or_update_completions_menu(
5266 Some(CompletionsMenuSource::Words {
5267 ignore_threshold: false,
5268 }),
5269 None,
5270 trigger_in_words,
5271 window,
5272 cx,
5273 );
5274 }
5275 _ => self.open_or_update_completions_menu(
5276 None,
5277 Some(text.to_owned()).filter(|x| !x.is_empty()),
5278 true,
5279 window,
5280 cx,
5281 ),
5282 }
5283 }
5284
5285 /// If any empty selections is touching the start of its innermost containing autoclose
5286 /// region, expand it to select the brackets.
5287 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5288 let selections = self
5289 .selections
5290 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5291 let buffer = self.buffer.read(cx).read(cx);
5292 let new_selections = self
5293 .selections_with_autoclose_regions(selections, &buffer)
5294 .map(|(mut selection, region)| {
5295 if !selection.is_empty() {
5296 return selection;
5297 }
5298
5299 if let Some(region) = region {
5300 let mut range = region.range.to_offset(&buffer);
5301 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5302 range.start -= region.pair.start.len();
5303 if buffer.contains_str_at(range.start, ®ion.pair.start)
5304 && buffer.contains_str_at(range.end, ®ion.pair.end)
5305 {
5306 range.end += region.pair.end.len();
5307 selection.start = range.start;
5308 selection.end = range.end;
5309
5310 return selection;
5311 }
5312 }
5313 }
5314
5315 let always_treat_brackets_as_autoclosed = buffer
5316 .language_settings_at(selection.start, cx)
5317 .always_treat_brackets_as_autoclosed;
5318
5319 if !always_treat_brackets_as_autoclosed {
5320 return selection;
5321 }
5322
5323 if let Some(scope) = buffer.language_scope_at(selection.start) {
5324 for (pair, enabled) in scope.brackets() {
5325 if !enabled || !pair.close {
5326 continue;
5327 }
5328
5329 if buffer.contains_str_at(selection.start, &pair.end) {
5330 let pair_start_len = pair.start.len();
5331 if buffer.contains_str_at(
5332 selection.start.saturating_sub_usize(pair_start_len),
5333 &pair.start,
5334 ) {
5335 selection.start -= pair_start_len;
5336 selection.end += pair.end.len();
5337
5338 return selection;
5339 }
5340 }
5341 }
5342 }
5343
5344 selection
5345 })
5346 .collect();
5347
5348 drop(buffer);
5349 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5350 selections.select(new_selections)
5351 });
5352 }
5353
5354 /// Iterate the given selections, and for each one, find the smallest surrounding
5355 /// autoclose region. This uses the ordering of the selections and the autoclose
5356 /// regions to avoid repeated comparisons.
5357 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5358 &'a self,
5359 selections: impl IntoIterator<Item = Selection<D>>,
5360 buffer: &'a MultiBufferSnapshot,
5361 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5362 let mut i = 0;
5363 let mut regions = self.autoclose_regions.as_slice();
5364 selections.into_iter().map(move |selection| {
5365 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5366
5367 let mut enclosing = None;
5368 while let Some(pair_state) = regions.get(i) {
5369 if pair_state.range.end.to_offset(buffer) < range.start {
5370 regions = ®ions[i + 1..];
5371 i = 0;
5372 } else if pair_state.range.start.to_offset(buffer) > range.end {
5373 break;
5374 } else {
5375 if pair_state.selection_id == selection.id {
5376 enclosing = Some(pair_state);
5377 }
5378 i += 1;
5379 }
5380 }
5381
5382 (selection, enclosing)
5383 })
5384 }
5385
5386 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5387 fn invalidate_autoclose_regions(
5388 &mut self,
5389 mut selections: &[Selection<Anchor>],
5390 buffer: &MultiBufferSnapshot,
5391 ) {
5392 self.autoclose_regions.retain(|state| {
5393 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5394 return false;
5395 }
5396
5397 let mut i = 0;
5398 while let Some(selection) = selections.get(i) {
5399 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5400 selections = &selections[1..];
5401 continue;
5402 }
5403 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5404 break;
5405 }
5406 if selection.id == state.selection_id {
5407 return true;
5408 } else {
5409 i += 1;
5410 }
5411 }
5412 false
5413 });
5414 }
5415
5416 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5417 let offset = position.to_offset(buffer);
5418 let (word_range, kind) =
5419 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5420 if offset > word_range.start && kind == Some(CharKind::Word) {
5421 Some(
5422 buffer
5423 .text_for_range(word_range.start..offset)
5424 .collect::<String>(),
5425 )
5426 } else {
5427 None
5428 }
5429 }
5430
5431 pub fn visible_excerpts(
5432 &self,
5433 lsp_related_only: bool,
5434 cx: &mut Context<Editor>,
5435 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5436 let project = self.project().cloned();
5437 let multi_buffer = self.buffer().read(cx);
5438 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5439 let multi_buffer_visible_start = self
5440 .scroll_manager
5441 .anchor()
5442 .anchor
5443 .to_point(&multi_buffer_snapshot);
5444 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5445 multi_buffer_visible_start
5446 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5447 Bias::Left,
5448 );
5449 multi_buffer_snapshot
5450 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5451 .into_iter()
5452 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5453 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5454 if !lsp_related_only {
5455 return Some((
5456 excerpt_id,
5457 (
5458 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5459 buffer.version().clone(),
5460 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5461 ),
5462 ));
5463 }
5464
5465 let project = project.as_ref()?.read(cx);
5466 let buffer_file = project::File::from_dyn(buffer.file())?;
5467 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5468 let worktree_entry = buffer_worktree
5469 .read(cx)
5470 .entry_for_id(buffer_file.project_entry_id()?)?;
5471 if worktree_entry.is_ignored {
5472 None
5473 } else {
5474 Some((
5475 excerpt_id,
5476 (
5477 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5478 buffer.version().clone(),
5479 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5480 ),
5481 ))
5482 }
5483 })
5484 .collect()
5485 }
5486
5487 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5488 TextLayoutDetails {
5489 text_system: window.text_system().clone(),
5490 editor_style: self.style.clone().unwrap(),
5491 rem_size: window.rem_size(),
5492 scroll_anchor: self.scroll_manager.anchor(),
5493 visible_rows: self.visible_line_count(),
5494 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5495 }
5496 }
5497
5498 fn trigger_on_type_formatting(
5499 &self,
5500 input: String,
5501 window: &mut Window,
5502 cx: &mut Context<Self>,
5503 ) -> Option<Task<Result<()>>> {
5504 if input.chars().count() != 1 {
5505 return None;
5506 }
5507
5508 let project = self.project()?;
5509 let position = self.selections.newest_anchor().head();
5510 let (buffer, buffer_position) = self
5511 .buffer
5512 .read(cx)
5513 .text_anchor_for_position(position, cx)?;
5514
5515 let settings = language_settings::language_settings(
5516 buffer
5517 .read(cx)
5518 .language_at(buffer_position)
5519 .map(|l| l.name()),
5520 buffer.read(cx).file(),
5521 cx,
5522 );
5523 if !settings.use_on_type_format {
5524 return None;
5525 }
5526
5527 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5528 // hence we do LSP request & edit on host side only — add formats to host's history.
5529 let push_to_lsp_host_history = true;
5530 // If this is not the host, append its history with new edits.
5531 let push_to_client_history = project.read(cx).is_via_collab();
5532
5533 let on_type_formatting = project.update(cx, |project, cx| {
5534 project.on_type_format(
5535 buffer.clone(),
5536 buffer_position,
5537 input,
5538 push_to_lsp_host_history,
5539 cx,
5540 )
5541 });
5542 Some(cx.spawn_in(window, async move |editor, cx| {
5543 if let Some(transaction) = on_type_formatting.await? {
5544 if push_to_client_history {
5545 buffer
5546 .update(cx, |buffer, _| {
5547 buffer.push_transaction(transaction, Instant::now());
5548 buffer.finalize_last_transaction();
5549 })
5550 .ok();
5551 }
5552 editor.update(cx, |editor, cx| {
5553 editor.refresh_document_highlights(cx);
5554 })?;
5555 }
5556 Ok(())
5557 }))
5558 }
5559
5560 pub fn show_word_completions(
5561 &mut self,
5562 _: &ShowWordCompletions,
5563 window: &mut Window,
5564 cx: &mut Context<Self>,
5565 ) {
5566 self.open_or_update_completions_menu(
5567 Some(CompletionsMenuSource::Words {
5568 ignore_threshold: true,
5569 }),
5570 None,
5571 false,
5572 window,
5573 cx,
5574 );
5575 }
5576
5577 pub fn show_completions(
5578 &mut self,
5579 _: &ShowCompletions,
5580 window: &mut Window,
5581 cx: &mut Context<Self>,
5582 ) {
5583 self.open_or_update_completions_menu(None, None, false, window, cx);
5584 }
5585
5586 fn open_or_update_completions_menu(
5587 &mut self,
5588 requested_source: Option<CompletionsMenuSource>,
5589 trigger: Option<String>,
5590 trigger_in_words: bool,
5591 window: &mut Window,
5592 cx: &mut Context<Self>,
5593 ) {
5594 if self.pending_rename.is_some() {
5595 return;
5596 }
5597
5598 let completions_source = self
5599 .context_menu
5600 .borrow()
5601 .as_ref()
5602 .and_then(|menu| match menu {
5603 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5604 CodeContextMenu::CodeActions(_) => None,
5605 });
5606
5607 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5608
5609 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5610 // inserted and selected. To handle that case, the start of the selection is used so that
5611 // the menu starts with all choices.
5612 let position = self
5613 .selections
5614 .newest_anchor()
5615 .start
5616 .bias_right(&multibuffer_snapshot);
5617 if position.diff_base_anchor.is_some() {
5618 return;
5619 }
5620 let buffer_position = multibuffer_snapshot.anchor_before(position);
5621 let Some(buffer) = buffer_position
5622 .text_anchor
5623 .buffer_id
5624 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5625 else {
5626 return;
5627 };
5628 let buffer_snapshot = buffer.read(cx).snapshot();
5629
5630 let menu_is_open = matches!(
5631 self.context_menu.borrow().as_ref(),
5632 Some(CodeContextMenu::Completions(_))
5633 );
5634
5635 let language = buffer_snapshot
5636 .language_at(buffer_position.text_anchor)
5637 .map(|language| language.name());
5638
5639 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5640 let completion_settings = language_settings.completions.clone();
5641
5642 let show_completions_on_input = self
5643 .show_completions_on_input_override
5644 .unwrap_or(language_settings.show_completions_on_input);
5645 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5646 return;
5647 }
5648
5649 let query: Option<Arc<String>> =
5650 Self::completion_query(&multibuffer_snapshot, buffer_position)
5651 .map(|query| query.into());
5652
5653 drop(multibuffer_snapshot);
5654
5655 // Hide the current completions menu when query is empty. Without this, cached
5656 // completions from before the trigger char may be reused (#32774).
5657 if query.is_none() && menu_is_open {
5658 self.hide_context_menu(window, cx);
5659 }
5660
5661 let mut ignore_word_threshold = false;
5662 let provider = match requested_source {
5663 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5664 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5665 ignore_word_threshold = ignore_threshold;
5666 None
5667 }
5668 Some(CompletionsMenuSource::SnippetChoices)
5669 | Some(CompletionsMenuSource::SnippetsOnly) => {
5670 log::error!("bug: SnippetChoices requested_source is not handled");
5671 None
5672 }
5673 };
5674
5675 let sort_completions = provider
5676 .as_ref()
5677 .is_some_and(|provider| provider.sort_completions());
5678
5679 let filter_completions = provider
5680 .as_ref()
5681 .is_none_or(|provider| provider.filter_completions());
5682
5683 let was_snippets_only = matches!(
5684 completions_source,
5685 Some(CompletionsMenuSource::SnippetsOnly)
5686 );
5687
5688 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5689 if filter_completions {
5690 menu.filter(
5691 query.clone().unwrap_or_default(),
5692 buffer_position.text_anchor,
5693 &buffer,
5694 provider.clone(),
5695 window,
5696 cx,
5697 );
5698 }
5699 // When `is_incomplete` is false, no need to re-query completions when the current query
5700 // is a suffix of the initial query.
5701 let was_complete = !menu.is_incomplete;
5702 if was_complete && !was_snippets_only {
5703 // If the new query is a suffix of the old query (typing more characters) and
5704 // the previous result was complete, the existing completions can be filtered.
5705 //
5706 // Note that snippet completions are always complete.
5707 let query_matches = match (&menu.initial_query, &query) {
5708 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5709 (None, _) => true,
5710 _ => false,
5711 };
5712 if query_matches {
5713 let position_matches = if menu.initial_position == position {
5714 true
5715 } else {
5716 let snapshot = self.buffer.read(cx).read(cx);
5717 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5718 };
5719 if position_matches {
5720 return;
5721 }
5722 }
5723 }
5724 };
5725
5726 let Anchor {
5727 excerpt_id: buffer_excerpt_id,
5728 text_anchor: buffer_position,
5729 ..
5730 } = buffer_position;
5731
5732 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5733 buffer_snapshot.surrounding_word(buffer_position, None)
5734 {
5735 let word_to_exclude = buffer_snapshot
5736 .text_for_range(word_range.clone())
5737 .collect::<String>();
5738 (
5739 buffer_snapshot.anchor_before(word_range.start)
5740 ..buffer_snapshot.anchor_after(buffer_position),
5741 Some(word_to_exclude),
5742 )
5743 } else {
5744 (buffer_position..buffer_position, None)
5745 };
5746
5747 let show_completion_documentation = buffer_snapshot
5748 .settings_at(buffer_position, cx)
5749 .show_completion_documentation;
5750
5751 // The document can be large, so stay in reasonable bounds when searching for words,
5752 // otherwise completion pop-up might be slow to appear.
5753 const WORD_LOOKUP_ROWS: u32 = 5_000;
5754 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5755 let min_word_search = buffer_snapshot.clip_point(
5756 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5757 Bias::Left,
5758 );
5759 let max_word_search = buffer_snapshot.clip_point(
5760 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5761 Bias::Right,
5762 );
5763 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5764 ..buffer_snapshot.point_to_offset(max_word_search);
5765
5766 let skip_digits = query
5767 .as_ref()
5768 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5769
5770 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5771 trigger.as_ref().is_none_or(|trigger| {
5772 provider.is_completion_trigger(
5773 &buffer,
5774 position.text_anchor,
5775 trigger,
5776 trigger_in_words,
5777 cx,
5778 )
5779 })
5780 });
5781
5782 let provider_responses = if let Some(provider) = &provider
5783 && load_provider_completions
5784 {
5785 let trigger_character =
5786 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5787 let completion_context = CompletionContext {
5788 trigger_kind: match &trigger_character {
5789 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5790 None => CompletionTriggerKind::INVOKED,
5791 },
5792 trigger_character,
5793 };
5794
5795 provider.completions(
5796 buffer_excerpt_id,
5797 &buffer,
5798 buffer_position,
5799 completion_context,
5800 window,
5801 cx,
5802 )
5803 } else {
5804 Task::ready(Ok(Vec::new()))
5805 };
5806
5807 let load_word_completions = if !self.word_completions_enabled {
5808 false
5809 } else if requested_source
5810 == Some(CompletionsMenuSource::Words {
5811 ignore_threshold: true,
5812 })
5813 {
5814 true
5815 } else {
5816 load_provider_completions
5817 && completion_settings.words != WordsCompletionMode::Disabled
5818 && (ignore_word_threshold || {
5819 let words_min_length = completion_settings.words_min_length;
5820 // check whether word has at least `words_min_length` characters
5821 let query_chars = query.iter().flat_map(|q| q.chars());
5822 query_chars.take(words_min_length).count() == words_min_length
5823 })
5824 };
5825
5826 let mut words = if load_word_completions {
5827 cx.background_spawn({
5828 let buffer_snapshot = buffer_snapshot.clone();
5829 async move {
5830 buffer_snapshot.words_in_range(WordsQuery {
5831 fuzzy_contents: None,
5832 range: word_search_range,
5833 skip_digits,
5834 })
5835 }
5836 })
5837 } else {
5838 Task::ready(BTreeMap::default())
5839 };
5840
5841 let snippets = if let Some(provider) = &provider
5842 && provider.show_snippets()
5843 && let Some(project) = self.project()
5844 {
5845 let char_classifier = buffer_snapshot
5846 .char_classifier_at(buffer_position)
5847 .scope_context(Some(CharScopeContext::Completion));
5848 project.update(cx, |project, cx| {
5849 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5850 })
5851 } else {
5852 Task::ready(Ok(CompletionResponse {
5853 completions: Vec::new(),
5854 display_options: Default::default(),
5855 is_incomplete: false,
5856 }))
5857 };
5858
5859 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5860
5861 let id = post_inc(&mut self.next_completion_id);
5862 let task = cx.spawn_in(window, async move |editor, cx| {
5863 let Ok(()) = editor.update(cx, |this, _| {
5864 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5865 }) else {
5866 return;
5867 };
5868
5869 // TODO: Ideally completions from different sources would be selectively re-queried, so
5870 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5871 let mut completions = Vec::new();
5872 let mut is_incomplete = false;
5873 let mut display_options: Option<CompletionDisplayOptions> = None;
5874 if let Some(provider_responses) = provider_responses.await.log_err()
5875 && !provider_responses.is_empty()
5876 {
5877 for response in provider_responses {
5878 completions.extend(response.completions);
5879 is_incomplete = is_incomplete || response.is_incomplete;
5880 match display_options.as_mut() {
5881 None => {
5882 display_options = Some(response.display_options);
5883 }
5884 Some(options) => options.merge(&response.display_options),
5885 }
5886 }
5887 if completion_settings.words == WordsCompletionMode::Fallback {
5888 words = Task::ready(BTreeMap::default());
5889 }
5890 }
5891 let display_options = display_options.unwrap_or_default();
5892
5893 let mut words = words.await;
5894 if let Some(word_to_exclude) = &word_to_exclude {
5895 words.remove(word_to_exclude);
5896 }
5897 for lsp_completion in &completions {
5898 words.remove(&lsp_completion.new_text);
5899 }
5900 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5901 replace_range: word_replace_range.clone(),
5902 new_text: word.clone(),
5903 label: CodeLabel::plain(word, None),
5904 match_start: None,
5905 snippet_deduplication_key: None,
5906 icon_path: None,
5907 documentation: None,
5908 source: CompletionSource::BufferWord {
5909 word_range,
5910 resolved: false,
5911 },
5912 insert_text_mode: Some(InsertTextMode::AS_IS),
5913 confirm: None,
5914 }));
5915
5916 completions.extend(
5917 snippets
5918 .await
5919 .into_iter()
5920 .flat_map(|response| response.completions),
5921 );
5922
5923 let menu = if completions.is_empty() {
5924 None
5925 } else {
5926 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5927 let languages = editor
5928 .workspace
5929 .as_ref()
5930 .and_then(|(workspace, _)| workspace.upgrade())
5931 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5932 let menu = CompletionsMenu::new(
5933 id,
5934 requested_source.unwrap_or(if load_provider_completions {
5935 CompletionsMenuSource::Normal
5936 } else {
5937 CompletionsMenuSource::SnippetsOnly
5938 }),
5939 sort_completions,
5940 show_completion_documentation,
5941 position,
5942 query.clone(),
5943 is_incomplete,
5944 buffer.clone(),
5945 completions.into(),
5946 editor
5947 .context_menu()
5948 .borrow_mut()
5949 .as_ref()
5950 .map(|menu| menu.primary_scroll_handle()),
5951 display_options,
5952 snippet_sort_order,
5953 languages,
5954 language,
5955 cx,
5956 );
5957
5958 let query = if filter_completions { query } else { None };
5959 let matches_task = menu.do_async_filtering(
5960 query.unwrap_or_default(),
5961 buffer_position,
5962 &buffer,
5963 cx,
5964 );
5965 (menu, matches_task)
5966 }) else {
5967 return;
5968 };
5969
5970 let matches = matches_task.await;
5971
5972 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5973 // Newer menu already set, so exit.
5974 if let Some(CodeContextMenu::Completions(prev_menu)) =
5975 editor.context_menu.borrow().as_ref()
5976 && prev_menu.id > id
5977 {
5978 return;
5979 };
5980
5981 // Only valid to take prev_menu because either the new menu is immediately set
5982 // below, or the menu is hidden.
5983 if let Some(CodeContextMenu::Completions(prev_menu)) =
5984 editor.context_menu.borrow_mut().take()
5985 {
5986 let position_matches =
5987 if prev_menu.initial_position == menu.initial_position {
5988 true
5989 } else {
5990 let snapshot = editor.buffer.read(cx).read(cx);
5991 prev_menu.initial_position.to_offset(&snapshot)
5992 == menu.initial_position.to_offset(&snapshot)
5993 };
5994 if position_matches {
5995 // Preserve markdown cache before `set_filter_results` because it will
5996 // try to populate the documentation cache.
5997 menu.preserve_markdown_cache(prev_menu);
5998 }
5999 };
6000
6001 menu.set_filter_results(matches, provider, window, cx);
6002 }) else {
6003 return;
6004 };
6005
6006 menu.visible().then_some(menu)
6007 };
6008
6009 editor
6010 .update_in(cx, |editor, window, cx| {
6011 if editor.focus_handle.is_focused(window)
6012 && let Some(menu) = menu
6013 {
6014 *editor.context_menu.borrow_mut() =
6015 Some(CodeContextMenu::Completions(menu));
6016
6017 crate::hover_popover::hide_hover(editor, cx);
6018 if editor.show_edit_predictions_in_menu() {
6019 editor.update_visible_edit_prediction(window, cx);
6020 } else {
6021 editor.discard_edit_prediction(false, cx);
6022 }
6023
6024 cx.notify();
6025 return;
6026 }
6027
6028 if editor.completion_tasks.len() <= 1 {
6029 // If there are no more completion tasks and the last menu was empty, we should hide it.
6030 let was_hidden = editor.hide_context_menu(window, cx).is_none();
6031 // If it was already hidden and we don't show edit predictions in the menu,
6032 // we should also show the edit prediction when available.
6033 if was_hidden && editor.show_edit_predictions_in_menu() {
6034 editor.update_visible_edit_prediction(window, cx);
6035 }
6036 }
6037 })
6038 .ok();
6039 });
6040
6041 self.completion_tasks.push((id, task));
6042 }
6043
6044 #[cfg(feature = "test-support")]
6045 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6046 let menu = self.context_menu.borrow();
6047 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6048 let completions = menu.completions.borrow();
6049 Some(completions.to_vec())
6050 } else {
6051 None
6052 }
6053 }
6054
6055 pub fn with_completions_menu_matching_id<R>(
6056 &self,
6057 id: CompletionId,
6058 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6059 ) -> R {
6060 let mut context_menu = self.context_menu.borrow_mut();
6061 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6062 return f(None);
6063 };
6064 if completions_menu.id != id {
6065 return f(None);
6066 }
6067 f(Some(completions_menu))
6068 }
6069
6070 pub fn confirm_completion(
6071 &mut self,
6072 action: &ConfirmCompletion,
6073 window: &mut Window,
6074 cx: &mut Context<Self>,
6075 ) -> Option<Task<Result<()>>> {
6076 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6077 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6078 }
6079
6080 pub fn confirm_completion_insert(
6081 &mut self,
6082 _: &ConfirmCompletionInsert,
6083 window: &mut Window,
6084 cx: &mut Context<Self>,
6085 ) -> Option<Task<Result<()>>> {
6086 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6087 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6088 }
6089
6090 pub fn confirm_completion_replace(
6091 &mut self,
6092 _: &ConfirmCompletionReplace,
6093 window: &mut Window,
6094 cx: &mut Context<Self>,
6095 ) -> Option<Task<Result<()>>> {
6096 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6097 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6098 }
6099
6100 pub fn compose_completion(
6101 &mut self,
6102 action: &ComposeCompletion,
6103 window: &mut Window,
6104 cx: &mut Context<Self>,
6105 ) -> Option<Task<Result<()>>> {
6106 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6107 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6108 }
6109
6110 fn do_completion(
6111 &mut self,
6112 item_ix: Option<usize>,
6113 intent: CompletionIntent,
6114 window: &mut Window,
6115 cx: &mut Context<Editor>,
6116 ) -> Option<Task<Result<()>>> {
6117 use language::ToOffset as _;
6118
6119 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6120 else {
6121 return None;
6122 };
6123
6124 let candidate_id = {
6125 let entries = completions_menu.entries.borrow();
6126 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6127 if self.show_edit_predictions_in_menu() {
6128 self.discard_edit_prediction(true, cx);
6129 }
6130 mat.candidate_id
6131 };
6132
6133 let completion = completions_menu
6134 .completions
6135 .borrow()
6136 .get(candidate_id)?
6137 .clone();
6138 cx.stop_propagation();
6139
6140 let buffer_handle = completions_menu.buffer.clone();
6141
6142 let CompletionEdit {
6143 new_text,
6144 snippet,
6145 replace_range,
6146 } = process_completion_for_edit(
6147 &completion,
6148 intent,
6149 &buffer_handle,
6150 &completions_menu.initial_position.text_anchor,
6151 cx,
6152 );
6153
6154 let buffer = buffer_handle.read(cx);
6155 let snapshot = self.buffer.read(cx).snapshot(cx);
6156 let newest_anchor = self.selections.newest_anchor();
6157 let replace_range_multibuffer = {
6158 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6159 excerpt.map_range_from_buffer(replace_range.clone())
6160 };
6161 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6162 return None;
6163 }
6164
6165 let old_text = buffer
6166 .text_for_range(replace_range.clone())
6167 .collect::<String>();
6168 let lookbehind = newest_anchor
6169 .start
6170 .text_anchor
6171 .to_offset(buffer)
6172 .saturating_sub(replace_range.start.0);
6173 let lookahead = replace_range
6174 .end
6175 .0
6176 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6177 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6178 let suffix = &old_text[lookbehind.min(old_text.len())..];
6179
6180 let selections = self
6181 .selections
6182 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6183 let mut ranges = Vec::new();
6184 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6185
6186 for selection in &selections {
6187 let range = if selection.id == newest_anchor.id {
6188 replace_range_multibuffer.clone()
6189 } else {
6190 let mut range = selection.range();
6191
6192 // if prefix is present, don't duplicate it
6193 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6194 range.start = range.start.saturating_sub_usize(lookbehind);
6195
6196 // if suffix is also present, mimic the newest cursor and replace it
6197 if selection.id != newest_anchor.id
6198 && snapshot.contains_str_at(range.end, suffix)
6199 {
6200 range.end += lookahead;
6201 }
6202 }
6203 range
6204 };
6205
6206 ranges.push(range.clone());
6207
6208 if !self.linked_edit_ranges.is_empty() {
6209 let start_anchor = snapshot.anchor_before(range.start);
6210 let end_anchor = snapshot.anchor_after(range.end);
6211 if let Some(ranges) = self
6212 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6213 {
6214 for (buffer, edits) in ranges {
6215 linked_edits
6216 .entry(buffer.clone())
6217 .or_default()
6218 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6219 }
6220 }
6221 }
6222 }
6223
6224 let common_prefix_len = old_text
6225 .chars()
6226 .zip(new_text.chars())
6227 .take_while(|(a, b)| a == b)
6228 .map(|(a, _)| a.len_utf8())
6229 .sum::<usize>();
6230
6231 cx.emit(EditorEvent::InputHandled {
6232 utf16_range_to_replace: None,
6233 text: new_text[common_prefix_len..].into(),
6234 });
6235
6236 self.transact(window, cx, |editor, window, cx| {
6237 if let Some(mut snippet) = snippet {
6238 snippet.text = new_text.to_string();
6239 editor
6240 .insert_snippet(&ranges, snippet, window, cx)
6241 .log_err();
6242 } else {
6243 editor.buffer.update(cx, |multi_buffer, cx| {
6244 let auto_indent = match completion.insert_text_mode {
6245 Some(InsertTextMode::AS_IS) => None,
6246 _ => editor.autoindent_mode.clone(),
6247 };
6248 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6249 multi_buffer.edit(edits, auto_indent, cx);
6250 });
6251 }
6252 for (buffer, edits) in linked_edits {
6253 buffer.update(cx, |buffer, cx| {
6254 let snapshot = buffer.snapshot();
6255 let edits = edits
6256 .into_iter()
6257 .map(|(range, text)| {
6258 use text::ToPoint as TP;
6259 let end_point = TP::to_point(&range.end, &snapshot);
6260 let start_point = TP::to_point(&range.start, &snapshot);
6261 (start_point..end_point, text)
6262 })
6263 .sorted_by_key(|(range, _)| range.start);
6264 buffer.edit(edits, None, cx);
6265 })
6266 }
6267
6268 editor.refresh_edit_prediction(true, false, window, cx);
6269 });
6270 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6271
6272 let show_new_completions_on_confirm = completion
6273 .confirm
6274 .as_ref()
6275 .is_some_and(|confirm| confirm(intent, window, cx));
6276 if show_new_completions_on_confirm {
6277 self.open_or_update_completions_menu(None, None, false, window, cx);
6278 }
6279
6280 let provider = self.completion_provider.as_ref()?;
6281
6282 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6283 let command = lsp_store.as_ref().and_then(|lsp_store| {
6284 let CompletionSource::Lsp {
6285 lsp_completion,
6286 server_id,
6287 ..
6288 } = &completion.source
6289 else {
6290 return None;
6291 };
6292 let lsp_command = lsp_completion.command.as_ref()?;
6293 let available_commands = lsp_store
6294 .read(cx)
6295 .lsp_server_capabilities
6296 .get(server_id)
6297 .and_then(|server_capabilities| {
6298 server_capabilities
6299 .execute_command_provider
6300 .as_ref()
6301 .map(|options| options.commands.as_slice())
6302 })?;
6303 if available_commands.contains(&lsp_command.command) {
6304 Some(CodeAction {
6305 server_id: *server_id,
6306 range: language::Anchor::MIN..language::Anchor::MIN,
6307 lsp_action: LspAction::Command(lsp_command.clone()),
6308 resolved: false,
6309 })
6310 } else {
6311 None
6312 }
6313 });
6314
6315 drop(completion);
6316 let apply_edits = provider.apply_additional_edits_for_completion(
6317 buffer_handle.clone(),
6318 completions_menu.completions.clone(),
6319 candidate_id,
6320 true,
6321 cx,
6322 );
6323
6324 let editor_settings = EditorSettings::get_global(cx);
6325 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6326 // After the code completion is finished, users often want to know what signatures are needed.
6327 // so we should automatically call signature_help
6328 self.show_signature_help(&ShowSignatureHelp, window, cx);
6329 }
6330
6331 Some(cx.spawn_in(window, async move |editor, cx| {
6332 apply_edits.await?;
6333
6334 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6335 let title = command.lsp_action.title().to_owned();
6336 let project_transaction = lsp_store
6337 .update(cx, |lsp_store, cx| {
6338 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6339 })?
6340 .await
6341 .context("applying post-completion command")?;
6342 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6343 Self::open_project_transaction(
6344 &editor,
6345 workspace.downgrade(),
6346 project_transaction,
6347 title,
6348 cx,
6349 )
6350 .await?;
6351 }
6352 }
6353
6354 Ok(())
6355 }))
6356 }
6357
6358 pub fn toggle_code_actions(
6359 &mut self,
6360 action: &ToggleCodeActions,
6361 window: &mut Window,
6362 cx: &mut Context<Self>,
6363 ) {
6364 let quick_launch = action.quick_launch;
6365 let mut context_menu = self.context_menu.borrow_mut();
6366 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6367 if code_actions.deployed_from == action.deployed_from {
6368 // Toggle if we're selecting the same one
6369 *context_menu = None;
6370 cx.notify();
6371 return;
6372 } else {
6373 // Otherwise, clear it and start a new one
6374 *context_menu = None;
6375 cx.notify();
6376 }
6377 }
6378 drop(context_menu);
6379 let snapshot = self.snapshot(window, cx);
6380 let deployed_from = action.deployed_from.clone();
6381 let action = action.clone();
6382 self.completion_tasks.clear();
6383 self.discard_edit_prediction(false, cx);
6384
6385 let multibuffer_point = match &action.deployed_from {
6386 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6387 DisplayPoint::new(*row, 0).to_point(&snapshot)
6388 }
6389 _ => self
6390 .selections
6391 .newest::<Point>(&snapshot.display_snapshot)
6392 .head(),
6393 };
6394 let Some((buffer, buffer_row)) = snapshot
6395 .buffer_snapshot()
6396 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6397 .and_then(|(buffer_snapshot, range)| {
6398 self.buffer()
6399 .read(cx)
6400 .buffer(buffer_snapshot.remote_id())
6401 .map(|buffer| (buffer, range.start.row))
6402 })
6403 else {
6404 return;
6405 };
6406 let buffer_id = buffer.read(cx).remote_id();
6407 let tasks = self
6408 .tasks
6409 .get(&(buffer_id, buffer_row))
6410 .map(|t| Arc::new(t.to_owned()));
6411
6412 if !self.focus_handle.is_focused(window) {
6413 return;
6414 }
6415 let project = self.project.clone();
6416
6417 let code_actions_task = match deployed_from {
6418 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6419 _ => self.code_actions(buffer_row, window, cx),
6420 };
6421
6422 let runnable_task = match deployed_from {
6423 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6424 _ => {
6425 let mut task_context_task = Task::ready(None);
6426 if let Some(tasks) = &tasks
6427 && let Some(project) = project
6428 {
6429 task_context_task =
6430 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6431 }
6432
6433 cx.spawn_in(window, {
6434 let buffer = buffer.clone();
6435 async move |editor, cx| {
6436 let task_context = task_context_task.await;
6437
6438 let resolved_tasks =
6439 tasks
6440 .zip(task_context.clone())
6441 .map(|(tasks, task_context)| ResolvedTasks {
6442 templates: tasks.resolve(&task_context).collect(),
6443 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6444 multibuffer_point.row,
6445 tasks.column,
6446 )),
6447 });
6448 let debug_scenarios = editor
6449 .update(cx, |editor, cx| {
6450 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6451 })?
6452 .await;
6453 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6454 }
6455 })
6456 }
6457 };
6458
6459 cx.spawn_in(window, async move |editor, cx| {
6460 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6461 let code_actions = code_actions_task.await;
6462 let spawn_straight_away = quick_launch
6463 && resolved_tasks
6464 .as_ref()
6465 .is_some_and(|tasks| tasks.templates.len() == 1)
6466 && code_actions
6467 .as_ref()
6468 .is_none_or(|actions| actions.is_empty())
6469 && debug_scenarios.is_empty();
6470
6471 editor.update_in(cx, |editor, window, cx| {
6472 crate::hover_popover::hide_hover(editor, cx);
6473 let actions = CodeActionContents::new(
6474 resolved_tasks,
6475 code_actions,
6476 debug_scenarios,
6477 task_context.unwrap_or_default(),
6478 );
6479
6480 // Don't show the menu if there are no actions available
6481 if actions.is_empty() {
6482 cx.notify();
6483 return Task::ready(Ok(()));
6484 }
6485
6486 *editor.context_menu.borrow_mut() =
6487 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6488 buffer,
6489 actions,
6490 selected_item: Default::default(),
6491 scroll_handle: UniformListScrollHandle::default(),
6492 deployed_from,
6493 }));
6494 cx.notify();
6495 if spawn_straight_away
6496 && let Some(task) = editor.confirm_code_action(
6497 &ConfirmCodeAction { item_ix: Some(0) },
6498 window,
6499 cx,
6500 )
6501 {
6502 return task;
6503 }
6504
6505 Task::ready(Ok(()))
6506 })
6507 })
6508 .detach_and_log_err(cx);
6509 }
6510
6511 fn debug_scenarios(
6512 &mut self,
6513 resolved_tasks: &Option<ResolvedTasks>,
6514 buffer: &Entity<Buffer>,
6515 cx: &mut App,
6516 ) -> Task<Vec<task::DebugScenario>> {
6517 maybe!({
6518 let project = self.project()?;
6519 let dap_store = project.read(cx).dap_store();
6520 let mut scenarios = vec![];
6521 let resolved_tasks = resolved_tasks.as_ref()?;
6522 let buffer = buffer.read(cx);
6523 let language = buffer.language()?;
6524 let file = buffer.file();
6525 let debug_adapter = language_settings(language.name().into(), file, cx)
6526 .debuggers
6527 .first()
6528 .map(SharedString::from)
6529 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6530
6531 dap_store.update(cx, |dap_store, cx| {
6532 for (_, task) in &resolved_tasks.templates {
6533 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6534 task.original_task().clone(),
6535 debug_adapter.clone().into(),
6536 task.display_label().to_owned().into(),
6537 cx,
6538 );
6539 scenarios.push(maybe_scenario);
6540 }
6541 });
6542 Some(cx.background_spawn(async move {
6543 futures::future::join_all(scenarios)
6544 .await
6545 .into_iter()
6546 .flatten()
6547 .collect::<Vec<_>>()
6548 }))
6549 })
6550 .unwrap_or_else(|| Task::ready(vec![]))
6551 }
6552
6553 fn code_actions(
6554 &mut self,
6555 buffer_row: u32,
6556 window: &mut Window,
6557 cx: &mut Context<Self>,
6558 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6559 let mut task = self.code_actions_task.take();
6560 cx.spawn_in(window, async move |editor, cx| {
6561 while let Some(prev_task) = task {
6562 prev_task.await.log_err();
6563 task = editor
6564 .update(cx, |this, _| this.code_actions_task.take())
6565 .ok()?;
6566 }
6567
6568 editor
6569 .update(cx, |editor, cx| {
6570 editor
6571 .available_code_actions
6572 .clone()
6573 .and_then(|(location, code_actions)| {
6574 let snapshot = location.buffer.read(cx).snapshot();
6575 let point_range = location.range.to_point(&snapshot);
6576 let point_range = point_range.start.row..=point_range.end.row;
6577 if point_range.contains(&buffer_row) {
6578 Some(code_actions)
6579 } else {
6580 None
6581 }
6582 })
6583 })
6584 .ok()
6585 .flatten()
6586 })
6587 }
6588
6589 pub fn confirm_code_action(
6590 &mut self,
6591 action: &ConfirmCodeAction,
6592 window: &mut Window,
6593 cx: &mut Context<Self>,
6594 ) -> Option<Task<Result<()>>> {
6595 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6596
6597 let actions_menu =
6598 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6599 menu
6600 } else {
6601 return None;
6602 };
6603
6604 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6605 let action = actions_menu.actions.get(action_ix)?;
6606 let title = action.label();
6607 let buffer = actions_menu.buffer;
6608 let workspace = self.workspace()?;
6609
6610 match action {
6611 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6612 workspace.update(cx, |workspace, cx| {
6613 workspace.schedule_resolved_task(
6614 task_source_kind,
6615 resolved_task,
6616 false,
6617 window,
6618 cx,
6619 );
6620
6621 Some(Task::ready(Ok(())))
6622 })
6623 }
6624 CodeActionsItem::CodeAction {
6625 excerpt_id,
6626 action,
6627 provider,
6628 } => {
6629 let apply_code_action =
6630 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6631 let workspace = workspace.downgrade();
6632 Some(cx.spawn_in(window, async move |editor, cx| {
6633 let project_transaction = apply_code_action.await?;
6634 Self::open_project_transaction(
6635 &editor,
6636 workspace,
6637 project_transaction,
6638 title,
6639 cx,
6640 )
6641 .await
6642 }))
6643 }
6644 CodeActionsItem::DebugScenario(scenario) => {
6645 let context = actions_menu.actions.context;
6646
6647 workspace.update(cx, |workspace, cx| {
6648 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6649 workspace.start_debug_session(
6650 scenario,
6651 context,
6652 Some(buffer),
6653 None,
6654 window,
6655 cx,
6656 );
6657 });
6658 Some(Task::ready(Ok(())))
6659 }
6660 }
6661 }
6662
6663 pub async fn open_project_transaction(
6664 editor: &WeakEntity<Editor>,
6665 workspace: WeakEntity<Workspace>,
6666 transaction: ProjectTransaction,
6667 title: String,
6668 cx: &mut AsyncWindowContext,
6669 ) -> Result<()> {
6670 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6671 cx.update(|_, cx| {
6672 entries.sort_unstable_by_key(|(buffer, _)| {
6673 buffer.read(cx).file().map(|f| f.path().clone())
6674 });
6675 })?;
6676 if entries.is_empty() {
6677 return Ok(());
6678 }
6679
6680 // If the project transaction's edits are all contained within this editor, then
6681 // avoid opening a new editor to display them.
6682
6683 if let [(buffer, transaction)] = &*entries {
6684 let excerpt = editor.update(cx, |editor, cx| {
6685 editor
6686 .buffer()
6687 .read(cx)
6688 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6689 })?;
6690 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6691 && excerpted_buffer == *buffer
6692 {
6693 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6694 let excerpt_range = excerpt_range.to_offset(buffer);
6695 buffer
6696 .edited_ranges_for_transaction::<usize>(transaction)
6697 .all(|range| {
6698 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6699 })
6700 })?;
6701
6702 if all_edits_within_excerpt {
6703 return Ok(());
6704 }
6705 }
6706 }
6707
6708 let mut ranges_to_highlight = Vec::new();
6709 let excerpt_buffer = cx.new(|cx| {
6710 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6711 for (buffer_handle, transaction) in &entries {
6712 let edited_ranges = buffer_handle
6713 .read(cx)
6714 .edited_ranges_for_transaction::<Point>(transaction)
6715 .collect::<Vec<_>>();
6716 let (ranges, _) = multibuffer.set_excerpts_for_path(
6717 PathKey::for_buffer(buffer_handle, cx),
6718 buffer_handle.clone(),
6719 edited_ranges,
6720 multibuffer_context_lines(cx),
6721 cx,
6722 );
6723
6724 ranges_to_highlight.extend(ranges);
6725 }
6726 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6727 multibuffer
6728 })?;
6729
6730 workspace.update_in(cx, |workspace, window, cx| {
6731 let project = workspace.project().clone();
6732 let editor =
6733 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6734 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6735 editor.update(cx, |editor, cx| {
6736 editor.highlight_background::<Self>(
6737 &ranges_to_highlight,
6738 |_, theme| theme.colors().editor_highlighted_line_background,
6739 cx,
6740 );
6741 });
6742 })?;
6743
6744 Ok(())
6745 }
6746
6747 pub fn clear_code_action_providers(&mut self) {
6748 self.code_action_providers.clear();
6749 self.available_code_actions.take();
6750 }
6751
6752 pub fn add_code_action_provider(
6753 &mut self,
6754 provider: Rc<dyn CodeActionProvider>,
6755 window: &mut Window,
6756 cx: &mut Context<Self>,
6757 ) {
6758 if self
6759 .code_action_providers
6760 .iter()
6761 .any(|existing_provider| existing_provider.id() == provider.id())
6762 {
6763 return;
6764 }
6765
6766 self.code_action_providers.push(provider);
6767 self.refresh_code_actions(window, cx);
6768 }
6769
6770 pub fn remove_code_action_provider(
6771 &mut self,
6772 id: Arc<str>,
6773 window: &mut Window,
6774 cx: &mut Context<Self>,
6775 ) {
6776 self.code_action_providers
6777 .retain(|provider| provider.id() != id);
6778 self.refresh_code_actions(window, cx);
6779 }
6780
6781 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6782 !self.code_action_providers.is_empty()
6783 && EditorSettings::get_global(cx).toolbar.code_actions
6784 }
6785
6786 pub fn has_available_code_actions(&self) -> bool {
6787 self.available_code_actions
6788 .as_ref()
6789 .is_some_and(|(_, actions)| !actions.is_empty())
6790 }
6791
6792 fn render_inline_code_actions(
6793 &self,
6794 icon_size: ui::IconSize,
6795 display_row: DisplayRow,
6796 is_active: bool,
6797 cx: &mut Context<Self>,
6798 ) -> AnyElement {
6799 let show_tooltip = !self.context_menu_visible();
6800 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6801 .icon_size(icon_size)
6802 .shape(ui::IconButtonShape::Square)
6803 .icon_color(ui::Color::Hidden)
6804 .toggle_state(is_active)
6805 .when(show_tooltip, |this| {
6806 this.tooltip({
6807 let focus_handle = self.focus_handle.clone();
6808 move |_window, cx| {
6809 Tooltip::for_action_in(
6810 "Toggle Code Actions",
6811 &ToggleCodeActions {
6812 deployed_from: None,
6813 quick_launch: false,
6814 },
6815 &focus_handle,
6816 cx,
6817 )
6818 }
6819 })
6820 })
6821 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6822 window.focus(&editor.focus_handle(cx));
6823 editor.toggle_code_actions(
6824 &crate::actions::ToggleCodeActions {
6825 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6826 display_row,
6827 )),
6828 quick_launch: false,
6829 },
6830 window,
6831 cx,
6832 );
6833 }))
6834 .into_any_element()
6835 }
6836
6837 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6838 &self.context_menu
6839 }
6840
6841 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6842 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6843 cx.background_executor()
6844 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6845 .await;
6846
6847 let (start_buffer, start, _, end, newest_selection) = this
6848 .update(cx, |this, cx| {
6849 let newest_selection = this.selections.newest_anchor().clone();
6850 if newest_selection.head().diff_base_anchor.is_some() {
6851 return None;
6852 }
6853 let display_snapshot = this.display_snapshot(cx);
6854 let newest_selection_adjusted =
6855 this.selections.newest_adjusted(&display_snapshot);
6856 let buffer = this.buffer.read(cx);
6857
6858 let (start_buffer, start) =
6859 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6860 let (end_buffer, end) =
6861 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6862
6863 Some((start_buffer, start, end_buffer, end, newest_selection))
6864 })?
6865 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6866 .context(
6867 "Expected selection to lie in a single buffer when refreshing code actions",
6868 )?;
6869 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6870 let providers = this.code_action_providers.clone();
6871 let tasks = this
6872 .code_action_providers
6873 .iter()
6874 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6875 .collect::<Vec<_>>();
6876 (providers, tasks)
6877 })?;
6878
6879 let mut actions = Vec::new();
6880 for (provider, provider_actions) in
6881 providers.into_iter().zip(future::join_all(tasks).await)
6882 {
6883 if let Some(provider_actions) = provider_actions.log_err() {
6884 actions.extend(provider_actions.into_iter().map(|action| {
6885 AvailableCodeAction {
6886 excerpt_id: newest_selection.start.excerpt_id,
6887 action,
6888 provider: provider.clone(),
6889 }
6890 }));
6891 }
6892 }
6893
6894 this.update(cx, |this, cx| {
6895 this.available_code_actions = if actions.is_empty() {
6896 None
6897 } else {
6898 Some((
6899 Location {
6900 buffer: start_buffer,
6901 range: start..end,
6902 },
6903 actions.into(),
6904 ))
6905 };
6906 cx.notify();
6907 })
6908 }));
6909 }
6910
6911 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6912 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6913 self.show_git_blame_inline = false;
6914
6915 self.show_git_blame_inline_delay_task =
6916 Some(cx.spawn_in(window, async move |this, cx| {
6917 cx.background_executor().timer(delay).await;
6918
6919 this.update(cx, |this, cx| {
6920 this.show_git_blame_inline = true;
6921 cx.notify();
6922 })
6923 .log_err();
6924 }));
6925 }
6926 }
6927
6928 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6929 let snapshot = self.snapshot(window, cx);
6930 let cursor = self
6931 .selections
6932 .newest::<Point>(&snapshot.display_snapshot)
6933 .head();
6934 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6935 else {
6936 return;
6937 };
6938
6939 if self.blame.is_none() {
6940 self.start_git_blame(true, window, cx);
6941 }
6942 let Some(blame) = self.blame.as_ref() else {
6943 return;
6944 };
6945
6946 let row_info = RowInfo {
6947 buffer_id: Some(buffer.remote_id()),
6948 buffer_row: Some(point.row),
6949 ..Default::default()
6950 };
6951 let Some((buffer, blame_entry)) = blame
6952 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6953 .flatten()
6954 else {
6955 return;
6956 };
6957
6958 let anchor = self.selections.newest_anchor().head();
6959 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6960 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6961 self.show_blame_popover(
6962 buffer,
6963 &blame_entry,
6964 position + last_bounds.origin,
6965 true,
6966 cx,
6967 );
6968 };
6969 }
6970
6971 fn show_blame_popover(
6972 &mut self,
6973 buffer: BufferId,
6974 blame_entry: &BlameEntry,
6975 position: gpui::Point<Pixels>,
6976 ignore_timeout: bool,
6977 cx: &mut Context<Self>,
6978 ) {
6979 if let Some(state) = &mut self.inline_blame_popover {
6980 state.hide_task.take();
6981 } else {
6982 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6983 let blame_entry = blame_entry.clone();
6984 let show_task = cx.spawn(async move |editor, cx| {
6985 if !ignore_timeout {
6986 cx.background_executor()
6987 .timer(std::time::Duration::from_millis(blame_popover_delay))
6988 .await;
6989 }
6990 editor
6991 .update(cx, |editor, cx| {
6992 editor.inline_blame_popover_show_task.take();
6993 let Some(blame) = editor.blame.as_ref() else {
6994 return;
6995 };
6996 let blame = blame.read(cx);
6997 let details = blame.details_for_entry(buffer, &blame_entry);
6998 let markdown = cx.new(|cx| {
6999 Markdown::new(
7000 details
7001 .as_ref()
7002 .map(|message| message.message.clone())
7003 .unwrap_or_default(),
7004 None,
7005 None,
7006 cx,
7007 )
7008 });
7009 editor.inline_blame_popover = Some(InlineBlamePopover {
7010 position,
7011 hide_task: None,
7012 popover_bounds: None,
7013 popover_state: InlineBlamePopoverState {
7014 scroll_handle: ScrollHandle::new(),
7015 commit_message: details,
7016 markdown,
7017 },
7018 keyboard_grace: ignore_timeout,
7019 });
7020 cx.notify();
7021 })
7022 .ok();
7023 });
7024 self.inline_blame_popover_show_task = Some(show_task);
7025 }
7026 }
7027
7028 pub fn has_mouse_context_menu(&self) -> bool {
7029 self.mouse_context_menu.is_some()
7030 }
7031
7032 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
7033 self.inline_blame_popover_show_task.take();
7034 if let Some(state) = &mut self.inline_blame_popover {
7035 let hide_task = cx.spawn(async move |editor, cx| {
7036 if !ignore_timeout {
7037 cx.background_executor()
7038 .timer(std::time::Duration::from_millis(100))
7039 .await;
7040 }
7041 editor
7042 .update(cx, |editor, cx| {
7043 editor.inline_blame_popover.take();
7044 cx.notify();
7045 })
7046 .ok();
7047 });
7048 state.hide_task = Some(hide_task);
7049 true
7050 } else {
7051 false
7052 }
7053 }
7054
7055 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7056 if self.pending_rename.is_some() {
7057 return None;
7058 }
7059
7060 let provider = self.semantics_provider.clone()?;
7061 let buffer = self.buffer.read(cx);
7062 let newest_selection = self.selections.newest_anchor().clone();
7063 let cursor_position = newest_selection.head();
7064 let (cursor_buffer, cursor_buffer_position) =
7065 buffer.text_anchor_for_position(cursor_position, cx)?;
7066 let (tail_buffer, tail_buffer_position) =
7067 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7068 if cursor_buffer != tail_buffer {
7069 return None;
7070 }
7071
7072 let snapshot = cursor_buffer.read(cx).snapshot();
7073 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7074 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7075 if start_word_range != end_word_range {
7076 self.document_highlights_task.take();
7077 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7078 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7079 return None;
7080 }
7081
7082 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7083 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7084 cx.background_executor()
7085 .timer(Duration::from_millis(debounce))
7086 .await;
7087
7088 let highlights = if let Some(highlights) = cx
7089 .update(|cx| {
7090 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7091 })
7092 .ok()
7093 .flatten()
7094 {
7095 highlights.await.log_err()
7096 } else {
7097 None
7098 };
7099
7100 if let Some(highlights) = highlights {
7101 this.update(cx, |this, cx| {
7102 if this.pending_rename.is_some() {
7103 return;
7104 }
7105
7106 let buffer = this.buffer.read(cx);
7107 if buffer
7108 .text_anchor_for_position(cursor_position, cx)
7109 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7110 {
7111 return;
7112 }
7113
7114 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7115 let mut write_ranges = Vec::new();
7116 let mut read_ranges = Vec::new();
7117 for highlight in highlights {
7118 let buffer_id = cursor_buffer.read(cx).remote_id();
7119 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7120 {
7121 let start = highlight
7122 .range
7123 .start
7124 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7125 let end = highlight
7126 .range
7127 .end
7128 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7129 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7130 continue;
7131 }
7132
7133 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7134 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7135 write_ranges.push(range);
7136 } else {
7137 read_ranges.push(range);
7138 }
7139 }
7140 }
7141
7142 this.highlight_background::<DocumentHighlightRead>(
7143 &read_ranges,
7144 |_, theme| theme.colors().editor_document_highlight_read_background,
7145 cx,
7146 );
7147 this.highlight_background::<DocumentHighlightWrite>(
7148 &write_ranges,
7149 |_, theme| theme.colors().editor_document_highlight_write_background,
7150 cx,
7151 );
7152 cx.notify();
7153 })
7154 .log_err();
7155 }
7156 }));
7157 None
7158 }
7159
7160 fn prepare_highlight_query_from_selection(
7161 &mut self,
7162 window: &Window,
7163 cx: &mut Context<Editor>,
7164 ) -> Option<(String, Range<Anchor>)> {
7165 if matches!(self.mode, EditorMode::SingleLine) {
7166 return None;
7167 }
7168 if !EditorSettings::get_global(cx).selection_highlight {
7169 return None;
7170 }
7171 if self.selections.count() != 1 || self.selections.line_mode() {
7172 return None;
7173 }
7174 let snapshot = self.snapshot(window, cx);
7175 let selection = self.selections.newest::<Point>(&snapshot);
7176 // If the selection spans multiple rows OR it is empty
7177 if selection.start.row != selection.end.row
7178 || selection.start.column == selection.end.column
7179 {
7180 return None;
7181 }
7182 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7183 let query = snapshot
7184 .buffer_snapshot()
7185 .text_for_range(selection_anchor_range.clone())
7186 .collect::<String>();
7187 if query.trim().is_empty() {
7188 return None;
7189 }
7190 Some((query, selection_anchor_range))
7191 }
7192
7193 #[ztracing::instrument(skip_all)]
7194 fn update_selection_occurrence_highlights(
7195 &mut self,
7196 query_text: String,
7197 query_range: Range<Anchor>,
7198 multi_buffer_range_to_query: Range<Point>,
7199 use_debounce: bool,
7200 window: &mut Window,
7201 cx: &mut Context<Editor>,
7202 ) -> Task<()> {
7203 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7204 cx.spawn_in(window, async move |editor, cx| {
7205 if use_debounce {
7206 cx.background_executor()
7207 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7208 .await;
7209 }
7210 let match_task = cx.background_spawn(async move {
7211 let buffer_ranges = multi_buffer_snapshot
7212 .range_to_buffer_ranges(multi_buffer_range_to_query)
7213 .into_iter()
7214 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7215 let mut match_ranges = Vec::new();
7216 let Ok(regex) = project::search::SearchQuery::text(
7217 query_text.clone(),
7218 false,
7219 false,
7220 false,
7221 Default::default(),
7222 Default::default(),
7223 false,
7224 None,
7225 ) else {
7226 return Vec::default();
7227 };
7228 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7229 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7230 match_ranges.extend(
7231 regex
7232 .search(
7233 buffer_snapshot,
7234 Some(search_range.start.0..search_range.end.0),
7235 )
7236 .await
7237 .into_iter()
7238 .filter_map(|match_range| {
7239 let match_start = buffer_snapshot
7240 .anchor_after(search_range.start + match_range.start);
7241 let match_end = buffer_snapshot
7242 .anchor_before(search_range.start + match_range.end);
7243 let match_anchor_range =
7244 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7245 (match_anchor_range != query_range).then_some(match_anchor_range)
7246 }),
7247 );
7248 }
7249 match_ranges
7250 });
7251 let match_ranges = match_task.await;
7252 editor
7253 .update_in(cx, |editor, _, cx| {
7254 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7255 if !match_ranges.is_empty() {
7256 editor.highlight_background::<SelectedTextHighlight>(
7257 &match_ranges,
7258 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7259 cx,
7260 )
7261 }
7262 })
7263 .log_err();
7264 })
7265 }
7266
7267 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7268 struct NewlineFold;
7269 let type_id = std::any::TypeId::of::<NewlineFold>();
7270 if !self.mode.is_single_line() {
7271 return;
7272 }
7273 let snapshot = self.snapshot(window, cx);
7274 if snapshot.buffer_snapshot().max_point().row == 0 {
7275 return;
7276 }
7277 let task = cx.background_spawn(async move {
7278 let new_newlines = snapshot
7279 .buffer_chars_at(MultiBufferOffset(0))
7280 .filter_map(|(c, i)| {
7281 if c == '\n' {
7282 Some(
7283 snapshot.buffer_snapshot().anchor_after(i)
7284 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7285 )
7286 } else {
7287 None
7288 }
7289 })
7290 .collect::<Vec<_>>();
7291 let existing_newlines = snapshot
7292 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7293 .filter_map(|fold| {
7294 if fold.placeholder.type_tag == Some(type_id) {
7295 Some(fold.range.start..fold.range.end)
7296 } else {
7297 None
7298 }
7299 })
7300 .collect::<Vec<_>>();
7301
7302 (new_newlines, existing_newlines)
7303 });
7304 self.folding_newlines = cx.spawn(async move |this, cx| {
7305 let (new_newlines, existing_newlines) = task.await;
7306 if new_newlines == existing_newlines {
7307 return;
7308 }
7309 let placeholder = FoldPlaceholder {
7310 render: Arc::new(move |_, _, cx| {
7311 div()
7312 .bg(cx.theme().status().hint_background)
7313 .border_b_1()
7314 .size_full()
7315 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7316 .border_color(cx.theme().status().hint)
7317 .child("\\n")
7318 .into_any()
7319 }),
7320 constrain_width: false,
7321 merge_adjacent: false,
7322 type_tag: Some(type_id),
7323 };
7324 let creases = new_newlines
7325 .into_iter()
7326 .map(|range| Crease::simple(range, placeholder.clone()))
7327 .collect();
7328 this.update(cx, |this, cx| {
7329 this.display_map.update(cx, |display_map, cx| {
7330 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7331 display_map.fold(creases, cx);
7332 });
7333 })
7334 .ok();
7335 });
7336 }
7337
7338 #[ztracing::instrument(skip_all)]
7339 fn refresh_selected_text_highlights(
7340 &mut self,
7341 on_buffer_edit: bool,
7342 window: &mut Window,
7343 cx: &mut Context<Editor>,
7344 ) {
7345 let Some((query_text, query_range)) =
7346 self.prepare_highlight_query_from_selection(window, cx)
7347 else {
7348 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7349 self.quick_selection_highlight_task.take();
7350 self.debounced_selection_highlight_task.take();
7351 return;
7352 };
7353 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7354 if on_buffer_edit
7355 || self
7356 .quick_selection_highlight_task
7357 .as_ref()
7358 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7359 {
7360 let multi_buffer_visible_start = self
7361 .scroll_manager
7362 .anchor()
7363 .anchor
7364 .to_point(&multi_buffer_snapshot);
7365 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7366 multi_buffer_visible_start
7367 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7368 Bias::Left,
7369 );
7370 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7371 self.quick_selection_highlight_task = Some((
7372 query_range.clone(),
7373 self.update_selection_occurrence_highlights(
7374 query_text.clone(),
7375 query_range.clone(),
7376 multi_buffer_visible_range,
7377 false,
7378 window,
7379 cx,
7380 ),
7381 ));
7382 }
7383 if on_buffer_edit
7384 || self
7385 .debounced_selection_highlight_task
7386 .as_ref()
7387 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7388 {
7389 let multi_buffer_start = multi_buffer_snapshot
7390 .anchor_before(MultiBufferOffset(0))
7391 .to_point(&multi_buffer_snapshot);
7392 let multi_buffer_end = multi_buffer_snapshot
7393 .anchor_after(multi_buffer_snapshot.len())
7394 .to_point(&multi_buffer_snapshot);
7395 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7396 self.debounced_selection_highlight_task = Some((
7397 query_range.clone(),
7398 self.update_selection_occurrence_highlights(
7399 query_text,
7400 query_range,
7401 multi_buffer_full_range,
7402 true,
7403 window,
7404 cx,
7405 ),
7406 ));
7407 }
7408 }
7409
7410 pub fn refresh_edit_prediction(
7411 &mut self,
7412 debounce: bool,
7413 user_requested: bool,
7414 window: &mut Window,
7415 cx: &mut Context<Self>,
7416 ) -> Option<()> {
7417 if DisableAiSettings::get_global(cx).disable_ai {
7418 return None;
7419 }
7420
7421 let provider = self.edit_prediction_provider()?;
7422 let cursor = self.selections.newest_anchor().head();
7423 let (buffer, cursor_buffer_position) =
7424 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7425
7426 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7427 self.discard_edit_prediction(false, cx);
7428 return None;
7429 }
7430
7431 self.update_visible_edit_prediction(window, cx);
7432
7433 if !user_requested
7434 && (!self.should_show_edit_predictions()
7435 || !self.is_focused(window)
7436 || buffer.read(cx).is_empty())
7437 {
7438 self.discard_edit_prediction(false, cx);
7439 return None;
7440 }
7441
7442 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7443 Some(())
7444 }
7445
7446 fn show_edit_predictions_in_menu(&self) -> bool {
7447 match self.edit_prediction_settings {
7448 EditPredictionSettings::Disabled => false,
7449 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7450 }
7451 }
7452
7453 pub fn edit_predictions_enabled(&self) -> bool {
7454 match self.edit_prediction_settings {
7455 EditPredictionSettings::Disabled => false,
7456 EditPredictionSettings::Enabled { .. } => true,
7457 }
7458 }
7459
7460 fn edit_prediction_requires_modifier(&self) -> bool {
7461 match self.edit_prediction_settings {
7462 EditPredictionSettings::Disabled => false,
7463 EditPredictionSettings::Enabled {
7464 preview_requires_modifier,
7465 ..
7466 } => preview_requires_modifier,
7467 }
7468 }
7469
7470 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7471 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7472 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7473 self.discard_edit_prediction(false, cx);
7474 } else {
7475 let selection = self.selections.newest_anchor();
7476 let cursor = selection.head();
7477
7478 if let Some((buffer, cursor_buffer_position)) =
7479 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7480 {
7481 self.edit_prediction_settings =
7482 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7483 }
7484 }
7485 }
7486
7487 fn edit_prediction_settings_at_position(
7488 &self,
7489 buffer: &Entity<Buffer>,
7490 buffer_position: language::Anchor,
7491 cx: &App,
7492 ) -> EditPredictionSettings {
7493 if !self.mode.is_full()
7494 || !self.show_edit_predictions_override.unwrap_or(true)
7495 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7496 {
7497 return EditPredictionSettings::Disabled;
7498 }
7499
7500 let buffer = buffer.read(cx);
7501
7502 let file = buffer.file();
7503
7504 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7505 return EditPredictionSettings::Disabled;
7506 };
7507
7508 let by_provider = matches!(
7509 self.menu_edit_predictions_policy,
7510 MenuEditPredictionsPolicy::ByProvider
7511 );
7512
7513 let show_in_menu = by_provider
7514 && self
7515 .edit_prediction_provider
7516 .as_ref()
7517 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7518
7519 let preview_requires_modifier =
7520 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7521
7522 EditPredictionSettings::Enabled {
7523 show_in_menu,
7524 preview_requires_modifier,
7525 }
7526 }
7527
7528 fn should_show_edit_predictions(&self) -> bool {
7529 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7530 }
7531
7532 pub fn edit_prediction_preview_is_active(&self) -> bool {
7533 matches!(
7534 self.edit_prediction_preview,
7535 EditPredictionPreview::Active { .. }
7536 )
7537 }
7538
7539 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7540 let cursor = self.selections.newest_anchor().head();
7541 if let Some((buffer, cursor_position)) =
7542 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7543 {
7544 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7545 } else {
7546 false
7547 }
7548 }
7549
7550 pub fn supports_minimap(&self, cx: &App) -> bool {
7551 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7552 }
7553
7554 fn edit_predictions_enabled_in_buffer(
7555 &self,
7556 buffer: &Entity<Buffer>,
7557 buffer_position: language::Anchor,
7558 cx: &App,
7559 ) -> bool {
7560 maybe!({
7561 if self.read_only(cx) {
7562 return Some(false);
7563 }
7564 let provider = self.edit_prediction_provider()?;
7565 if !provider.is_enabled(buffer, buffer_position, cx) {
7566 return Some(false);
7567 }
7568 let buffer = buffer.read(cx);
7569 let Some(file) = buffer.file() else {
7570 return Some(true);
7571 };
7572 let settings = all_language_settings(Some(file), cx);
7573 Some(settings.edit_predictions_enabled_for_file(file, cx))
7574 })
7575 .unwrap_or(false)
7576 }
7577
7578 fn cycle_edit_prediction(
7579 &mut self,
7580 direction: Direction,
7581 window: &mut Window,
7582 cx: &mut Context<Self>,
7583 ) -> Option<()> {
7584 let provider = self.edit_prediction_provider()?;
7585 let cursor = self.selections.newest_anchor().head();
7586 let (buffer, cursor_buffer_position) =
7587 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7588 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7589 return None;
7590 }
7591
7592 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7593 self.update_visible_edit_prediction(window, cx);
7594
7595 Some(())
7596 }
7597
7598 pub fn show_edit_prediction(
7599 &mut self,
7600 _: &ShowEditPrediction,
7601 window: &mut Window,
7602 cx: &mut Context<Self>,
7603 ) {
7604 if !self.has_active_edit_prediction() {
7605 self.refresh_edit_prediction(false, true, window, cx);
7606 return;
7607 }
7608
7609 self.update_visible_edit_prediction(window, cx);
7610 }
7611
7612 pub fn display_cursor_names(
7613 &mut self,
7614 _: &DisplayCursorNames,
7615 window: &mut Window,
7616 cx: &mut Context<Self>,
7617 ) {
7618 self.show_cursor_names(window, cx);
7619 }
7620
7621 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7622 self.show_cursor_names = true;
7623 cx.notify();
7624 cx.spawn_in(window, async move |this, cx| {
7625 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7626 this.update(cx, |this, cx| {
7627 this.show_cursor_names = false;
7628 cx.notify()
7629 })
7630 .ok()
7631 })
7632 .detach();
7633 }
7634
7635 pub fn next_edit_prediction(
7636 &mut self,
7637 _: &NextEditPrediction,
7638 window: &mut Window,
7639 cx: &mut Context<Self>,
7640 ) {
7641 if self.has_active_edit_prediction() {
7642 self.cycle_edit_prediction(Direction::Next, window, cx);
7643 } else {
7644 let is_copilot_disabled = self
7645 .refresh_edit_prediction(false, true, window, cx)
7646 .is_none();
7647 if is_copilot_disabled {
7648 cx.propagate();
7649 }
7650 }
7651 }
7652
7653 pub fn previous_edit_prediction(
7654 &mut self,
7655 _: &PreviousEditPrediction,
7656 window: &mut Window,
7657 cx: &mut Context<Self>,
7658 ) {
7659 if self.has_active_edit_prediction() {
7660 self.cycle_edit_prediction(Direction::Prev, window, cx);
7661 } else {
7662 let is_copilot_disabled = self
7663 .refresh_edit_prediction(false, true, window, cx)
7664 .is_none();
7665 if is_copilot_disabled {
7666 cx.propagate();
7667 }
7668 }
7669 }
7670
7671 pub fn accept_partial_edit_prediction(
7672 &mut self,
7673 granularity: EditPredictionGranularity,
7674 window: &mut Window,
7675 cx: &mut Context<Self>,
7676 ) {
7677 if self.show_edit_predictions_in_menu() {
7678 self.hide_context_menu(window, cx);
7679 }
7680
7681 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7682 return;
7683 };
7684
7685 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7686 return;
7687 }
7688
7689 match &active_edit_prediction.completion {
7690 EditPrediction::MoveWithin { target, .. } => {
7691 let target = *target;
7692
7693 if matches!(granularity, EditPredictionGranularity::Full) {
7694 if let Some(position_map) = &self.last_position_map {
7695 let target_row = target.to_display_point(&position_map.snapshot).row();
7696 let is_visible = position_map.visible_row_range.contains(&target_row);
7697
7698 if is_visible || !self.edit_prediction_requires_modifier() {
7699 self.unfold_ranges(&[target..target], true, false, cx);
7700 self.change_selections(
7701 SelectionEffects::scroll(Autoscroll::newest()),
7702 window,
7703 cx,
7704 |selections| {
7705 selections.select_anchor_ranges([target..target]);
7706 },
7707 );
7708 self.clear_row_highlights::<EditPredictionPreview>();
7709 self.edit_prediction_preview
7710 .set_previous_scroll_position(None);
7711 } else {
7712 // Highlight and request scroll
7713 self.edit_prediction_preview
7714 .set_previous_scroll_position(Some(
7715 position_map.snapshot.scroll_anchor,
7716 ));
7717 self.highlight_rows::<EditPredictionPreview>(
7718 target..target,
7719 cx.theme().colors().editor_highlighted_line_background,
7720 RowHighlightOptions {
7721 autoscroll: true,
7722 ..Default::default()
7723 },
7724 cx,
7725 );
7726 self.request_autoscroll(Autoscroll::fit(), cx);
7727 }
7728 }
7729 } else {
7730 self.change_selections(
7731 SelectionEffects::scroll(Autoscroll::newest()),
7732 window,
7733 cx,
7734 |selections| {
7735 selections.select_anchor_ranges([target..target]);
7736 },
7737 );
7738 }
7739 }
7740 EditPrediction::MoveOutside { snapshot, target } => {
7741 if let Some(workspace) = self.workspace() {
7742 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7743 .detach_and_log_err(cx);
7744 }
7745 }
7746 EditPrediction::Edit { edits, .. } => {
7747 self.report_edit_prediction_event(
7748 active_edit_prediction.completion_id.clone(),
7749 true,
7750 cx,
7751 );
7752
7753 match granularity {
7754 EditPredictionGranularity::Full => {
7755 if let Some(provider) = self.edit_prediction_provider() {
7756 provider.accept(cx);
7757 }
7758
7759 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7760 let snapshot = self.buffer.read(cx).snapshot(cx);
7761 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7762
7763 self.buffer.update(cx, |buffer, cx| {
7764 buffer.edit(edits.iter().cloned(), None, cx)
7765 });
7766
7767 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7768 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7769 });
7770
7771 let selections = self.selections.disjoint_anchors_arc();
7772 if let Some(transaction_id_now) =
7773 self.buffer.read(cx).last_transaction_id(cx)
7774 {
7775 if transaction_id_prev != Some(transaction_id_now) {
7776 self.selection_history
7777 .insert_transaction(transaction_id_now, selections);
7778 }
7779 }
7780
7781 self.update_visible_edit_prediction(window, cx);
7782 if self.active_edit_prediction.is_none() {
7783 self.refresh_edit_prediction(true, true, window, cx);
7784 }
7785 cx.notify();
7786 }
7787 _ => {
7788 let snapshot = self.buffer.read(cx).snapshot(cx);
7789 let cursor_offset = self
7790 .selections
7791 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7792 .head();
7793
7794 let insertion = edits.iter().find_map(|(range, text)| {
7795 let range = range.to_offset(&snapshot);
7796 if range.is_empty() && range.start == cursor_offset {
7797 Some(text)
7798 } else {
7799 None
7800 }
7801 });
7802
7803 if let Some(text) = insertion {
7804 let text_to_insert = match granularity {
7805 EditPredictionGranularity::Word => {
7806 let mut partial = text
7807 .chars()
7808 .by_ref()
7809 .take_while(|c| c.is_alphabetic())
7810 .collect::<String>();
7811 if partial.is_empty() {
7812 partial = text
7813 .chars()
7814 .by_ref()
7815 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7816 .collect::<String>();
7817 }
7818 partial
7819 }
7820 EditPredictionGranularity::Line => {
7821 if let Some(line) = text.split_inclusive('\n').next() {
7822 line.to_string()
7823 } else {
7824 text.to_string()
7825 }
7826 }
7827 EditPredictionGranularity::Full => unreachable!(),
7828 };
7829
7830 cx.emit(EditorEvent::InputHandled {
7831 utf16_range_to_replace: None,
7832 text: text_to_insert.clone().into(),
7833 });
7834
7835 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7836 self.refresh_edit_prediction(true, true, window, cx);
7837 cx.notify();
7838 } else {
7839 self.accept_partial_edit_prediction(
7840 EditPredictionGranularity::Full,
7841 window,
7842 cx,
7843 );
7844 }
7845 }
7846 }
7847 }
7848 }
7849
7850 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7851 }
7852
7853 pub fn accept_next_word_edit_prediction(
7854 &mut self,
7855 _: &AcceptNextWordEditPrediction,
7856 window: &mut Window,
7857 cx: &mut Context<Self>,
7858 ) {
7859 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7860 }
7861
7862 pub fn accept_next_line_edit_prediction(
7863 &mut self,
7864 _: &AcceptNextLineEditPrediction,
7865 window: &mut Window,
7866 cx: &mut Context<Self>,
7867 ) {
7868 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7869 }
7870
7871 pub fn accept_edit_prediction(
7872 &mut self,
7873 _: &AcceptEditPrediction,
7874 window: &mut Window,
7875 cx: &mut Context<Self>,
7876 ) {
7877 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7878 }
7879
7880 fn discard_edit_prediction(
7881 &mut self,
7882 should_report_edit_prediction_event: bool,
7883 cx: &mut Context<Self>,
7884 ) -> bool {
7885 if should_report_edit_prediction_event {
7886 let completion_id = self
7887 .active_edit_prediction
7888 .as_ref()
7889 .and_then(|active_completion| active_completion.completion_id.clone());
7890
7891 self.report_edit_prediction_event(completion_id, false, cx);
7892 }
7893
7894 if let Some(provider) = self.edit_prediction_provider() {
7895 provider.discard(cx);
7896 }
7897
7898 self.take_active_edit_prediction(cx)
7899 }
7900
7901 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7902 let Some(provider) = self.edit_prediction_provider() else {
7903 return;
7904 };
7905
7906 let Some((_, buffer, _)) = self
7907 .buffer
7908 .read(cx)
7909 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7910 else {
7911 return;
7912 };
7913
7914 let extension = buffer
7915 .read(cx)
7916 .file()
7917 .and_then(|file| Some(file.path().extension()?.to_string()));
7918
7919 let event_type = match accepted {
7920 true => "Edit Prediction Accepted",
7921 false => "Edit Prediction Discarded",
7922 };
7923 telemetry::event!(
7924 event_type,
7925 provider = provider.name(),
7926 prediction_id = id,
7927 suggestion_accepted = accepted,
7928 file_extension = extension,
7929 );
7930 }
7931
7932 fn open_editor_at_anchor(
7933 snapshot: &language::BufferSnapshot,
7934 target: language::Anchor,
7935 workspace: &Entity<Workspace>,
7936 window: &mut Window,
7937 cx: &mut App,
7938 ) -> Task<Result<()>> {
7939 workspace.update(cx, |workspace, cx| {
7940 let path = snapshot.file().map(|file| file.full_path(cx));
7941 let Some(path) =
7942 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7943 else {
7944 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7945 };
7946 let target = text::ToPoint::to_point(&target, snapshot);
7947 let item = workspace.open_path(path, None, true, window, cx);
7948 window.spawn(cx, async move |cx| {
7949 let Some(editor) = item.await?.downcast::<Editor>() else {
7950 return Ok(());
7951 };
7952 editor
7953 .update_in(cx, |editor, window, cx| {
7954 editor.go_to_singleton_buffer_point(target, window, cx);
7955 })
7956 .ok();
7957 anyhow::Ok(())
7958 })
7959 })
7960 }
7961
7962 pub fn has_active_edit_prediction(&self) -> bool {
7963 self.active_edit_prediction.is_some()
7964 }
7965
7966 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7967 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7968 return false;
7969 };
7970
7971 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7972 self.clear_highlights::<EditPredictionHighlight>(cx);
7973 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7974 true
7975 }
7976
7977 /// Returns true when we're displaying the edit prediction popover below the cursor
7978 /// like we are not previewing and the LSP autocomplete menu is visible
7979 /// or we are in `when_holding_modifier` mode.
7980 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7981 if self.edit_prediction_preview_is_active()
7982 || !self.show_edit_predictions_in_menu()
7983 || !self.edit_predictions_enabled()
7984 {
7985 return false;
7986 }
7987
7988 if self.has_visible_completions_menu() {
7989 return true;
7990 }
7991
7992 has_completion && self.edit_prediction_requires_modifier()
7993 }
7994
7995 fn handle_modifiers_changed(
7996 &mut self,
7997 modifiers: Modifiers,
7998 position_map: &PositionMap,
7999 window: &mut Window,
8000 cx: &mut Context<Self>,
8001 ) {
8002 // Ensure that the edit prediction preview is updated, even when not
8003 // enabled, if there's an active edit prediction preview.
8004 if self.show_edit_predictions_in_menu()
8005 || matches!(
8006 self.edit_prediction_preview,
8007 EditPredictionPreview::Active { .. }
8008 )
8009 {
8010 self.update_edit_prediction_preview(&modifiers, window, cx);
8011 }
8012
8013 self.update_selection_mode(&modifiers, position_map, window, cx);
8014
8015 let mouse_position = window.mouse_position();
8016 if !position_map.text_hitbox.is_hovered(window) {
8017 return;
8018 }
8019
8020 self.update_hovered_link(
8021 position_map.point_for_position(mouse_position),
8022 &position_map.snapshot,
8023 modifiers,
8024 window,
8025 cx,
8026 )
8027 }
8028
8029 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8030 match EditorSettings::get_global(cx).multi_cursor_modifier {
8031 MultiCursorModifier::Alt => modifiers.secondary(),
8032 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
8033 }
8034 }
8035
8036 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
8037 match EditorSettings::get_global(cx).multi_cursor_modifier {
8038 MultiCursorModifier::Alt => modifiers.alt,
8039 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8040 }
8041 }
8042
8043 fn columnar_selection_mode(
8044 modifiers: &Modifiers,
8045 cx: &mut Context<Self>,
8046 ) -> Option<ColumnarMode> {
8047 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8048 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8049 Some(ColumnarMode::FromMouse)
8050 } else if Self::is_alt_pressed(modifiers, cx) {
8051 Some(ColumnarMode::FromSelection)
8052 } else {
8053 None
8054 }
8055 } else {
8056 None
8057 }
8058 }
8059
8060 fn update_selection_mode(
8061 &mut self,
8062 modifiers: &Modifiers,
8063 position_map: &PositionMap,
8064 window: &mut Window,
8065 cx: &mut Context<Self>,
8066 ) {
8067 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8068 return;
8069 };
8070 if self.selections.pending_anchor().is_none() {
8071 return;
8072 }
8073
8074 let mouse_position = window.mouse_position();
8075 let point_for_position = position_map.point_for_position(mouse_position);
8076 let position = point_for_position.previous_valid;
8077
8078 self.select(
8079 SelectPhase::BeginColumnar {
8080 position,
8081 reset: false,
8082 mode,
8083 goal_column: point_for_position.exact_unclipped.column(),
8084 },
8085 window,
8086 cx,
8087 );
8088 }
8089
8090 fn update_edit_prediction_preview(
8091 &mut self,
8092 modifiers: &Modifiers,
8093 window: &mut Window,
8094 cx: &mut Context<Self>,
8095 ) {
8096 let mut modifiers_held = false;
8097
8098 // Check bindings for all granularities.
8099 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8100 let granularities = [
8101 EditPredictionGranularity::Full,
8102 EditPredictionGranularity::Line,
8103 EditPredictionGranularity::Word,
8104 ];
8105
8106 for granularity in granularities {
8107 if let Some(keystroke) = self
8108 .accept_edit_prediction_keybind(granularity, window, cx)
8109 .keystroke()
8110 {
8111 modifiers_held = modifiers_held
8112 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8113 }
8114 }
8115
8116 if modifiers_held {
8117 if matches!(
8118 self.edit_prediction_preview,
8119 EditPredictionPreview::Inactive { .. }
8120 ) {
8121 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8122 provider.provider.did_show(cx)
8123 }
8124
8125 self.edit_prediction_preview = EditPredictionPreview::Active {
8126 previous_scroll_position: None,
8127 since: Instant::now(),
8128 };
8129
8130 self.update_visible_edit_prediction(window, cx);
8131 cx.notify();
8132 }
8133 } else if let EditPredictionPreview::Active {
8134 previous_scroll_position,
8135 since,
8136 } = self.edit_prediction_preview
8137 {
8138 if let (Some(previous_scroll_position), Some(position_map)) =
8139 (previous_scroll_position, self.last_position_map.as_ref())
8140 {
8141 self.set_scroll_position(
8142 previous_scroll_position
8143 .scroll_position(&position_map.snapshot.display_snapshot),
8144 window,
8145 cx,
8146 );
8147 }
8148
8149 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8150 released_too_fast: since.elapsed() < Duration::from_millis(200),
8151 };
8152 self.clear_row_highlights::<EditPredictionPreview>();
8153 self.update_visible_edit_prediction(window, cx);
8154 cx.notify();
8155 }
8156 }
8157
8158 fn update_visible_edit_prediction(
8159 &mut self,
8160 _window: &mut Window,
8161 cx: &mut Context<Self>,
8162 ) -> Option<()> {
8163 if DisableAiSettings::get_global(cx).disable_ai {
8164 return None;
8165 }
8166
8167 if self.ime_transaction.is_some() {
8168 self.discard_edit_prediction(false, cx);
8169 return None;
8170 }
8171
8172 let selection = self.selections.newest_anchor();
8173 let cursor = selection.head();
8174 let multibuffer = self.buffer.read(cx).snapshot(cx);
8175 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8176 let excerpt_id = cursor.excerpt_id;
8177
8178 let show_in_menu = self.show_edit_predictions_in_menu();
8179 let completions_menu_has_precedence = !show_in_menu
8180 && (self.context_menu.borrow().is_some()
8181 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8182
8183 if completions_menu_has_precedence
8184 || !offset_selection.is_empty()
8185 || self
8186 .active_edit_prediction
8187 .as_ref()
8188 .is_some_and(|completion| {
8189 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8190 return false;
8191 };
8192 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8193 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8194 !invalidation_range.contains(&offset_selection.head())
8195 })
8196 {
8197 self.discard_edit_prediction(false, cx);
8198 return None;
8199 }
8200
8201 self.take_active_edit_prediction(cx);
8202 let Some(provider) = self.edit_prediction_provider() else {
8203 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8204 return None;
8205 };
8206
8207 let (buffer, cursor_buffer_position) =
8208 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8209
8210 self.edit_prediction_settings =
8211 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8212
8213 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8214
8215 if self.edit_prediction_indent_conflict {
8216 let cursor_point = cursor.to_point(&multibuffer);
8217 let mut suggested_indent = None;
8218 multibuffer.suggested_indents_callback(
8219 cursor_point.row..cursor_point.row + 1,
8220 |_, indent| {
8221 suggested_indent = Some(indent);
8222 ControlFlow::Break(())
8223 },
8224 cx,
8225 );
8226
8227 if let Some(indent) = suggested_indent
8228 && indent.len == cursor_point.column
8229 {
8230 self.edit_prediction_indent_conflict = false;
8231 }
8232 }
8233
8234 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8235
8236 let (completion_id, edits, edit_preview) = match edit_prediction {
8237 edit_prediction_types::EditPrediction::Local {
8238 id,
8239 edits,
8240 edit_preview,
8241 } => (id, edits, edit_preview),
8242 edit_prediction_types::EditPrediction::Jump {
8243 id,
8244 snapshot,
8245 target,
8246 } => {
8247 self.stale_edit_prediction_in_menu = None;
8248 self.active_edit_prediction = Some(EditPredictionState {
8249 inlay_ids: vec![],
8250 completion: EditPrediction::MoveOutside { snapshot, target },
8251 completion_id: id,
8252 invalidation_range: None,
8253 });
8254 cx.notify();
8255 return Some(());
8256 }
8257 };
8258
8259 let edits = edits
8260 .into_iter()
8261 .flat_map(|(range, new_text)| {
8262 Some((
8263 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8264 new_text,
8265 ))
8266 })
8267 .collect::<Vec<_>>();
8268 if edits.is_empty() {
8269 return None;
8270 }
8271
8272 let first_edit_start = edits.first().unwrap().0.start;
8273 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8274 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8275
8276 let last_edit_end = edits.last().unwrap().0.end;
8277 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8278 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8279
8280 let cursor_row = cursor.to_point(&multibuffer).row;
8281
8282 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8283
8284 let mut inlay_ids = Vec::new();
8285 let invalidation_row_range;
8286 let move_invalidation_row_range = if cursor_row < edit_start_row {
8287 Some(cursor_row..edit_end_row)
8288 } else if cursor_row > edit_end_row {
8289 Some(edit_start_row..cursor_row)
8290 } else {
8291 None
8292 };
8293 let supports_jump = self
8294 .edit_prediction_provider
8295 .as_ref()
8296 .map(|provider| provider.provider.supports_jump_to_edit())
8297 .unwrap_or(true);
8298
8299 let is_move = supports_jump
8300 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8301 let completion = if is_move {
8302 invalidation_row_range =
8303 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8304 let target = first_edit_start;
8305 EditPrediction::MoveWithin { target, snapshot }
8306 } else {
8307 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8308 && !self.edit_predictions_hidden_for_vim_mode;
8309
8310 if show_completions_in_buffer {
8311 if let Some(provider) = &self.edit_prediction_provider {
8312 provider.provider.did_show(cx);
8313 }
8314 if edits
8315 .iter()
8316 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8317 {
8318 let mut inlays = Vec::new();
8319 for (range, new_text) in &edits {
8320 let inlay = Inlay::edit_prediction(
8321 post_inc(&mut self.next_inlay_id),
8322 range.start,
8323 new_text.as_ref(),
8324 );
8325 inlay_ids.push(inlay.id);
8326 inlays.push(inlay);
8327 }
8328
8329 self.splice_inlays(&[], inlays, cx);
8330 } else {
8331 let background_color = cx.theme().status().deleted_background;
8332 self.highlight_text::<EditPredictionHighlight>(
8333 edits.iter().map(|(range, _)| range.clone()).collect(),
8334 HighlightStyle {
8335 background_color: Some(background_color),
8336 ..Default::default()
8337 },
8338 cx,
8339 );
8340 }
8341 }
8342
8343 invalidation_row_range = edit_start_row..edit_end_row;
8344
8345 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8346 if provider.show_tab_accept_marker() {
8347 EditDisplayMode::TabAccept
8348 } else {
8349 EditDisplayMode::Inline
8350 }
8351 } else {
8352 EditDisplayMode::DiffPopover
8353 };
8354
8355 EditPrediction::Edit {
8356 edits,
8357 edit_preview,
8358 display_mode,
8359 snapshot,
8360 }
8361 };
8362
8363 let invalidation_range = multibuffer
8364 .anchor_before(Point::new(invalidation_row_range.start, 0))
8365 ..multibuffer.anchor_after(Point::new(
8366 invalidation_row_range.end,
8367 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8368 ));
8369
8370 self.stale_edit_prediction_in_menu = None;
8371 self.active_edit_prediction = Some(EditPredictionState {
8372 inlay_ids,
8373 completion,
8374 completion_id,
8375 invalidation_range: Some(invalidation_range),
8376 });
8377
8378 cx.notify();
8379
8380 Some(())
8381 }
8382
8383 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8384 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8385 }
8386
8387 fn clear_tasks(&mut self) {
8388 self.tasks.clear()
8389 }
8390
8391 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8392 if self.tasks.insert(key, value).is_some() {
8393 // This case should hopefully be rare, but just in case...
8394 log::error!(
8395 "multiple different run targets found on a single line, only the last target will be rendered"
8396 )
8397 }
8398 }
8399
8400 /// Get all display points of breakpoints that will be rendered within editor
8401 ///
8402 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8403 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8404 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8405 fn active_breakpoints(
8406 &self,
8407 range: Range<DisplayRow>,
8408 window: &mut Window,
8409 cx: &mut Context<Self>,
8410 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8411 let mut breakpoint_display_points = HashMap::default();
8412
8413 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8414 return breakpoint_display_points;
8415 };
8416
8417 let snapshot = self.snapshot(window, cx);
8418
8419 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8420 let Some(project) = self.project() else {
8421 return breakpoint_display_points;
8422 };
8423
8424 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8425 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8426
8427 for (buffer_snapshot, range, excerpt_id) in
8428 multi_buffer_snapshot.range_to_buffer_ranges(range)
8429 {
8430 let Some(buffer) = project
8431 .read(cx)
8432 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8433 else {
8434 continue;
8435 };
8436 let breakpoints = breakpoint_store.read(cx).breakpoints(
8437 &buffer,
8438 Some(
8439 buffer_snapshot.anchor_before(range.start)
8440 ..buffer_snapshot.anchor_after(range.end),
8441 ),
8442 buffer_snapshot,
8443 cx,
8444 );
8445 for (breakpoint, state) in breakpoints {
8446 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8447 let position = multi_buffer_anchor
8448 .to_point(&multi_buffer_snapshot)
8449 .to_display_point(&snapshot);
8450
8451 breakpoint_display_points.insert(
8452 position.row(),
8453 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8454 );
8455 }
8456 }
8457
8458 breakpoint_display_points
8459 }
8460
8461 fn breakpoint_context_menu(
8462 &self,
8463 anchor: Anchor,
8464 window: &mut Window,
8465 cx: &mut Context<Self>,
8466 ) -> Entity<ui::ContextMenu> {
8467 let weak_editor = cx.weak_entity();
8468 let focus_handle = self.focus_handle(cx);
8469
8470 let row = self
8471 .buffer
8472 .read(cx)
8473 .snapshot(cx)
8474 .summary_for_anchor::<Point>(&anchor)
8475 .row;
8476
8477 let breakpoint = self
8478 .breakpoint_at_row(row, window, cx)
8479 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8480
8481 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8482 "Edit Log Breakpoint"
8483 } else {
8484 "Set Log Breakpoint"
8485 };
8486
8487 let condition_breakpoint_msg = if breakpoint
8488 .as_ref()
8489 .is_some_and(|bp| bp.1.condition.is_some())
8490 {
8491 "Edit Condition Breakpoint"
8492 } else {
8493 "Set Condition Breakpoint"
8494 };
8495
8496 let hit_condition_breakpoint_msg = if breakpoint
8497 .as_ref()
8498 .is_some_and(|bp| bp.1.hit_condition.is_some())
8499 {
8500 "Edit Hit Condition Breakpoint"
8501 } else {
8502 "Set Hit Condition Breakpoint"
8503 };
8504
8505 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8506 "Unset Breakpoint"
8507 } else {
8508 "Set Breakpoint"
8509 };
8510
8511 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8512
8513 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8514 BreakpointState::Enabled => Some("Disable"),
8515 BreakpointState::Disabled => Some("Enable"),
8516 });
8517
8518 let (anchor, breakpoint) =
8519 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8520
8521 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8522 menu.on_blur_subscription(Subscription::new(|| {}))
8523 .context(focus_handle)
8524 .when(run_to_cursor, |this| {
8525 let weak_editor = weak_editor.clone();
8526 this.entry("Run to cursor", None, move |window, cx| {
8527 weak_editor
8528 .update(cx, |editor, cx| {
8529 editor.change_selections(
8530 SelectionEffects::no_scroll(),
8531 window,
8532 cx,
8533 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8534 );
8535 })
8536 .ok();
8537
8538 window.dispatch_action(Box::new(RunToCursor), cx);
8539 })
8540 .separator()
8541 })
8542 .when_some(toggle_state_msg, |this, msg| {
8543 this.entry(msg, None, {
8544 let weak_editor = weak_editor.clone();
8545 let breakpoint = breakpoint.clone();
8546 move |_window, cx| {
8547 weak_editor
8548 .update(cx, |this, cx| {
8549 this.edit_breakpoint_at_anchor(
8550 anchor,
8551 breakpoint.as_ref().clone(),
8552 BreakpointEditAction::InvertState,
8553 cx,
8554 );
8555 })
8556 .log_err();
8557 }
8558 })
8559 })
8560 .entry(set_breakpoint_msg, None, {
8561 let weak_editor = weak_editor.clone();
8562 let breakpoint = breakpoint.clone();
8563 move |_window, cx| {
8564 weak_editor
8565 .update(cx, |this, cx| {
8566 this.edit_breakpoint_at_anchor(
8567 anchor,
8568 breakpoint.as_ref().clone(),
8569 BreakpointEditAction::Toggle,
8570 cx,
8571 );
8572 })
8573 .log_err();
8574 }
8575 })
8576 .entry(log_breakpoint_msg, None, {
8577 let breakpoint = breakpoint.clone();
8578 let weak_editor = weak_editor.clone();
8579 move |window, cx| {
8580 weak_editor
8581 .update(cx, |this, cx| {
8582 this.add_edit_breakpoint_block(
8583 anchor,
8584 breakpoint.as_ref(),
8585 BreakpointPromptEditAction::Log,
8586 window,
8587 cx,
8588 );
8589 })
8590 .log_err();
8591 }
8592 })
8593 .entry(condition_breakpoint_msg, None, {
8594 let breakpoint = breakpoint.clone();
8595 let weak_editor = weak_editor.clone();
8596 move |window, cx| {
8597 weak_editor
8598 .update(cx, |this, cx| {
8599 this.add_edit_breakpoint_block(
8600 anchor,
8601 breakpoint.as_ref(),
8602 BreakpointPromptEditAction::Condition,
8603 window,
8604 cx,
8605 );
8606 })
8607 .log_err();
8608 }
8609 })
8610 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8611 weak_editor
8612 .update(cx, |this, cx| {
8613 this.add_edit_breakpoint_block(
8614 anchor,
8615 breakpoint.as_ref(),
8616 BreakpointPromptEditAction::HitCondition,
8617 window,
8618 cx,
8619 );
8620 })
8621 .log_err();
8622 })
8623 })
8624 }
8625
8626 fn render_breakpoint(
8627 &self,
8628 position: Anchor,
8629 row: DisplayRow,
8630 breakpoint: &Breakpoint,
8631 state: Option<BreakpointSessionState>,
8632 cx: &mut Context<Self>,
8633 ) -> IconButton {
8634 let is_rejected = state.is_some_and(|s| !s.verified);
8635 // Is it a breakpoint that shows up when hovering over gutter?
8636 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8637 (false, false),
8638 |PhantomBreakpointIndicator {
8639 is_active,
8640 display_row,
8641 collides_with_existing_breakpoint,
8642 }| {
8643 (
8644 is_active && display_row == row,
8645 collides_with_existing_breakpoint,
8646 )
8647 },
8648 );
8649
8650 let (color, icon) = {
8651 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8652 (false, false) => ui::IconName::DebugBreakpoint,
8653 (true, false) => ui::IconName::DebugLogBreakpoint,
8654 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8655 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8656 };
8657
8658 let color = cx.theme().colors();
8659
8660 let color = if is_phantom {
8661 if collides_with_existing {
8662 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8663 } else {
8664 Color::Hint
8665 }
8666 } else if is_rejected {
8667 Color::Disabled
8668 } else {
8669 Color::Debugger
8670 };
8671
8672 (color, icon)
8673 };
8674
8675 let breakpoint = Arc::from(breakpoint.clone());
8676
8677 let alt_as_text = gpui::Keystroke {
8678 modifiers: Modifiers::secondary_key(),
8679 ..Default::default()
8680 };
8681 let primary_action_text = if breakpoint.is_disabled() {
8682 "Enable breakpoint"
8683 } else if is_phantom && !collides_with_existing {
8684 "Set breakpoint"
8685 } else {
8686 "Unset breakpoint"
8687 };
8688 let focus_handle = self.focus_handle.clone();
8689
8690 let meta = if is_rejected {
8691 SharedString::from("No executable code is associated with this line.")
8692 } else if collides_with_existing && !breakpoint.is_disabled() {
8693 SharedString::from(format!(
8694 "{alt_as_text}-click to disable,\nright-click for more options."
8695 ))
8696 } else {
8697 SharedString::from("Right-click for more options.")
8698 };
8699 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8700 .icon_size(IconSize::XSmall)
8701 .size(ui::ButtonSize::None)
8702 .when(is_rejected, |this| {
8703 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8704 })
8705 .icon_color(color)
8706 .style(ButtonStyle::Transparent)
8707 .on_click(cx.listener({
8708 move |editor, event: &ClickEvent, window, cx| {
8709 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8710 BreakpointEditAction::InvertState
8711 } else {
8712 BreakpointEditAction::Toggle
8713 };
8714
8715 window.focus(&editor.focus_handle(cx));
8716 editor.edit_breakpoint_at_anchor(
8717 position,
8718 breakpoint.as_ref().clone(),
8719 edit_action,
8720 cx,
8721 );
8722 }
8723 }))
8724 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8725 editor.set_breakpoint_context_menu(
8726 row,
8727 Some(position),
8728 event.position(),
8729 window,
8730 cx,
8731 );
8732 }))
8733 .tooltip(move |_window, cx| {
8734 Tooltip::with_meta_in(
8735 primary_action_text,
8736 Some(&ToggleBreakpoint),
8737 meta.clone(),
8738 &focus_handle,
8739 cx,
8740 )
8741 })
8742 }
8743
8744 fn build_tasks_context(
8745 project: &Entity<Project>,
8746 buffer: &Entity<Buffer>,
8747 buffer_row: u32,
8748 tasks: &Arc<RunnableTasks>,
8749 cx: &mut Context<Self>,
8750 ) -> Task<Option<task::TaskContext>> {
8751 let position = Point::new(buffer_row, tasks.column);
8752 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8753 let location = Location {
8754 buffer: buffer.clone(),
8755 range: range_start..range_start,
8756 };
8757 // Fill in the environmental variables from the tree-sitter captures
8758 let mut captured_task_variables = TaskVariables::default();
8759 for (capture_name, value) in tasks.extra_variables.clone() {
8760 captured_task_variables.insert(
8761 task::VariableName::Custom(capture_name.into()),
8762 value.clone(),
8763 );
8764 }
8765 project.update(cx, |project, cx| {
8766 project.task_store().update(cx, |task_store, cx| {
8767 task_store.task_context_for_location(captured_task_variables, location, cx)
8768 })
8769 })
8770 }
8771
8772 pub fn spawn_nearest_task(
8773 &mut self,
8774 action: &SpawnNearestTask,
8775 window: &mut Window,
8776 cx: &mut Context<Self>,
8777 ) {
8778 let Some((workspace, _)) = self.workspace.clone() else {
8779 return;
8780 };
8781 let Some(project) = self.project.clone() else {
8782 return;
8783 };
8784
8785 // Try to find a closest, enclosing node using tree-sitter that has a task
8786 let Some((buffer, buffer_row, tasks)) = self
8787 .find_enclosing_node_task(cx)
8788 // Or find the task that's closest in row-distance.
8789 .or_else(|| self.find_closest_task(cx))
8790 else {
8791 return;
8792 };
8793
8794 let reveal_strategy = action.reveal;
8795 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8796 cx.spawn_in(window, async move |_, cx| {
8797 let context = task_context.await?;
8798 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8799
8800 let resolved = &mut resolved_task.resolved;
8801 resolved.reveal = reveal_strategy;
8802
8803 workspace
8804 .update_in(cx, |workspace, window, cx| {
8805 workspace.schedule_resolved_task(
8806 task_source_kind,
8807 resolved_task,
8808 false,
8809 window,
8810 cx,
8811 );
8812 })
8813 .ok()
8814 })
8815 .detach();
8816 }
8817
8818 fn find_closest_task(
8819 &mut self,
8820 cx: &mut Context<Self>,
8821 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8822 let cursor_row = self
8823 .selections
8824 .newest_adjusted(&self.display_snapshot(cx))
8825 .head()
8826 .row;
8827
8828 let ((buffer_id, row), tasks) = self
8829 .tasks
8830 .iter()
8831 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8832
8833 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8834 let tasks = Arc::new(tasks.to_owned());
8835 Some((buffer, *row, tasks))
8836 }
8837
8838 fn find_enclosing_node_task(
8839 &mut self,
8840 cx: &mut Context<Self>,
8841 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8842 let snapshot = self.buffer.read(cx).snapshot(cx);
8843 let offset = self
8844 .selections
8845 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8846 .head();
8847 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8848 let offset = excerpt.map_offset_to_buffer(offset);
8849 let buffer_id = excerpt.buffer().remote_id();
8850
8851 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8852 let mut cursor = layer.node().walk();
8853
8854 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8855 if cursor.node().end_byte() == offset.0 {
8856 cursor.goto_next_sibling();
8857 }
8858 }
8859
8860 // Ascend to the smallest ancestor that contains the range and has a task.
8861 loop {
8862 let node = cursor.node();
8863 let node_range = node.byte_range();
8864 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8865
8866 // Check if this node contains our offset
8867 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8868 // If it contains offset, check for task
8869 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8870 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8871 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8872 }
8873 }
8874
8875 if !cursor.goto_parent() {
8876 break;
8877 }
8878 }
8879 None
8880 }
8881
8882 fn render_run_indicator(
8883 &self,
8884 _style: &EditorStyle,
8885 is_active: bool,
8886 row: DisplayRow,
8887 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8888 cx: &mut Context<Self>,
8889 ) -> IconButton {
8890 let color = Color::Muted;
8891 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8892
8893 IconButton::new(
8894 ("run_indicator", row.0 as usize),
8895 ui::IconName::PlayOutlined,
8896 )
8897 .shape(ui::IconButtonShape::Square)
8898 .icon_size(IconSize::XSmall)
8899 .icon_color(color)
8900 .toggle_state(is_active)
8901 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8902 let quick_launch = match e {
8903 ClickEvent::Keyboard(_) => true,
8904 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8905 };
8906
8907 window.focus(&editor.focus_handle(cx));
8908 editor.toggle_code_actions(
8909 &ToggleCodeActions {
8910 deployed_from: Some(CodeActionSource::RunMenu(row)),
8911 quick_launch,
8912 },
8913 window,
8914 cx,
8915 );
8916 }))
8917 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8918 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8919 }))
8920 }
8921
8922 pub fn context_menu_visible(&self) -> bool {
8923 !self.edit_prediction_preview_is_active()
8924 && self
8925 .context_menu
8926 .borrow()
8927 .as_ref()
8928 .is_some_and(|menu| menu.visible())
8929 }
8930
8931 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8932 self.context_menu
8933 .borrow()
8934 .as_ref()
8935 .map(|menu| menu.origin())
8936 }
8937
8938 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8939 self.context_menu_options = Some(options);
8940 }
8941
8942 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8943 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8944
8945 fn render_edit_prediction_popover(
8946 &mut self,
8947 text_bounds: &Bounds<Pixels>,
8948 content_origin: gpui::Point<Pixels>,
8949 right_margin: Pixels,
8950 editor_snapshot: &EditorSnapshot,
8951 visible_row_range: Range<DisplayRow>,
8952 scroll_top: ScrollOffset,
8953 scroll_bottom: ScrollOffset,
8954 line_layouts: &[LineWithInvisibles],
8955 line_height: Pixels,
8956 scroll_position: gpui::Point<ScrollOffset>,
8957 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8958 newest_selection_head: Option<DisplayPoint>,
8959 editor_width: Pixels,
8960 style: &EditorStyle,
8961 window: &mut Window,
8962 cx: &mut App,
8963 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8964 if self.mode().is_minimap() {
8965 return None;
8966 }
8967 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8968
8969 if self.edit_prediction_visible_in_cursor_popover(true) {
8970 return None;
8971 }
8972
8973 match &active_edit_prediction.completion {
8974 EditPrediction::MoveWithin { target, .. } => {
8975 let target_display_point = target.to_display_point(editor_snapshot);
8976
8977 if self.edit_prediction_requires_modifier() {
8978 if !self.edit_prediction_preview_is_active() {
8979 return None;
8980 }
8981
8982 self.render_edit_prediction_modifier_jump_popover(
8983 text_bounds,
8984 content_origin,
8985 visible_row_range,
8986 line_layouts,
8987 line_height,
8988 scroll_pixel_position,
8989 newest_selection_head,
8990 target_display_point,
8991 window,
8992 cx,
8993 )
8994 } else {
8995 self.render_edit_prediction_eager_jump_popover(
8996 text_bounds,
8997 content_origin,
8998 editor_snapshot,
8999 visible_row_range,
9000 scroll_top,
9001 scroll_bottom,
9002 line_height,
9003 scroll_pixel_position,
9004 target_display_point,
9005 editor_width,
9006 window,
9007 cx,
9008 )
9009 }
9010 }
9011 EditPrediction::Edit {
9012 display_mode: EditDisplayMode::Inline,
9013 ..
9014 } => None,
9015 EditPrediction::Edit {
9016 display_mode: EditDisplayMode::TabAccept,
9017 edits,
9018 ..
9019 } => {
9020 let range = &edits.first()?.0;
9021 let target_display_point = range.end.to_display_point(editor_snapshot);
9022
9023 self.render_edit_prediction_end_of_line_popover(
9024 "Accept",
9025 editor_snapshot,
9026 visible_row_range,
9027 target_display_point,
9028 line_height,
9029 scroll_pixel_position,
9030 content_origin,
9031 editor_width,
9032 window,
9033 cx,
9034 )
9035 }
9036 EditPrediction::Edit {
9037 edits,
9038 edit_preview,
9039 display_mode: EditDisplayMode::DiffPopover,
9040 snapshot,
9041 } => self.render_edit_prediction_diff_popover(
9042 text_bounds,
9043 content_origin,
9044 right_margin,
9045 editor_snapshot,
9046 visible_row_range,
9047 line_layouts,
9048 line_height,
9049 scroll_position,
9050 scroll_pixel_position,
9051 newest_selection_head,
9052 editor_width,
9053 style,
9054 edits,
9055 edit_preview,
9056 snapshot,
9057 window,
9058 cx,
9059 ),
9060 EditPrediction::MoveOutside { snapshot, .. } => {
9061 let mut element = self
9062 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9063 .into_any();
9064
9065 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9066 let origin_x = text_bounds.size.width - size.width - px(30.);
9067 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9068 element.prepaint_at(origin, window, cx);
9069
9070 Some((element, origin))
9071 }
9072 }
9073 }
9074
9075 fn render_edit_prediction_modifier_jump_popover(
9076 &mut self,
9077 text_bounds: &Bounds<Pixels>,
9078 content_origin: gpui::Point<Pixels>,
9079 visible_row_range: Range<DisplayRow>,
9080 line_layouts: &[LineWithInvisibles],
9081 line_height: Pixels,
9082 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9083 newest_selection_head: Option<DisplayPoint>,
9084 target_display_point: DisplayPoint,
9085 window: &mut Window,
9086 cx: &mut App,
9087 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9088 let scrolled_content_origin =
9089 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9090
9091 const SCROLL_PADDING_Y: Pixels = px(12.);
9092
9093 if target_display_point.row() < visible_row_range.start {
9094 return self.render_edit_prediction_scroll_popover(
9095 |_| SCROLL_PADDING_Y,
9096 IconName::ArrowUp,
9097 visible_row_range,
9098 line_layouts,
9099 newest_selection_head,
9100 scrolled_content_origin,
9101 window,
9102 cx,
9103 );
9104 } else if target_display_point.row() >= visible_row_range.end {
9105 return self.render_edit_prediction_scroll_popover(
9106 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9107 IconName::ArrowDown,
9108 visible_row_range,
9109 line_layouts,
9110 newest_selection_head,
9111 scrolled_content_origin,
9112 window,
9113 cx,
9114 );
9115 }
9116
9117 const POLE_WIDTH: Pixels = px(2.);
9118
9119 let line_layout =
9120 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9121 let target_column = target_display_point.column() as usize;
9122
9123 let target_x = line_layout.x_for_index(target_column);
9124 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9125 - scroll_pixel_position.y;
9126
9127 let flag_on_right = target_x < text_bounds.size.width / 2.;
9128
9129 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9130 border_color.l += 0.001;
9131
9132 let mut element = v_flex()
9133 .items_end()
9134 .when(flag_on_right, |el| el.items_start())
9135 .child(if flag_on_right {
9136 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9137 .rounded_bl(px(0.))
9138 .rounded_tl(px(0.))
9139 .border_l_2()
9140 .border_color(border_color)
9141 } else {
9142 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9143 .rounded_br(px(0.))
9144 .rounded_tr(px(0.))
9145 .border_r_2()
9146 .border_color(border_color)
9147 })
9148 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9149 .into_any();
9150
9151 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9152
9153 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9154 - point(
9155 if flag_on_right {
9156 POLE_WIDTH
9157 } else {
9158 size.width - POLE_WIDTH
9159 },
9160 size.height - line_height,
9161 );
9162
9163 origin.x = origin.x.max(content_origin.x);
9164
9165 element.prepaint_at(origin, window, cx);
9166
9167 Some((element, origin))
9168 }
9169
9170 fn render_edit_prediction_scroll_popover(
9171 &mut self,
9172 to_y: impl Fn(Size<Pixels>) -> Pixels,
9173 scroll_icon: IconName,
9174 visible_row_range: Range<DisplayRow>,
9175 line_layouts: &[LineWithInvisibles],
9176 newest_selection_head: Option<DisplayPoint>,
9177 scrolled_content_origin: gpui::Point<Pixels>,
9178 window: &mut Window,
9179 cx: &mut App,
9180 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9181 let mut element = self
9182 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9183 .into_any();
9184
9185 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9186
9187 let cursor = newest_selection_head?;
9188 let cursor_row_layout =
9189 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9190 let cursor_column = cursor.column() as usize;
9191
9192 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9193
9194 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9195
9196 element.prepaint_at(origin, window, cx);
9197 Some((element, origin))
9198 }
9199
9200 fn render_edit_prediction_eager_jump_popover(
9201 &mut self,
9202 text_bounds: &Bounds<Pixels>,
9203 content_origin: gpui::Point<Pixels>,
9204 editor_snapshot: &EditorSnapshot,
9205 visible_row_range: Range<DisplayRow>,
9206 scroll_top: ScrollOffset,
9207 scroll_bottom: ScrollOffset,
9208 line_height: Pixels,
9209 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9210 target_display_point: DisplayPoint,
9211 editor_width: Pixels,
9212 window: &mut Window,
9213 cx: &mut App,
9214 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9215 if target_display_point.row().as_f64() < scroll_top {
9216 let mut element = self
9217 .render_edit_prediction_line_popover(
9218 "Jump to Edit",
9219 Some(IconName::ArrowUp),
9220 window,
9221 cx,
9222 )
9223 .into_any();
9224
9225 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9226 let offset = point(
9227 (text_bounds.size.width - size.width) / 2.,
9228 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9229 );
9230
9231 let origin = text_bounds.origin + offset;
9232 element.prepaint_at(origin, window, cx);
9233 Some((element, origin))
9234 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9235 let mut element = self
9236 .render_edit_prediction_line_popover(
9237 "Jump to Edit",
9238 Some(IconName::ArrowDown),
9239 window,
9240 cx,
9241 )
9242 .into_any();
9243
9244 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9245 let offset = point(
9246 (text_bounds.size.width - size.width) / 2.,
9247 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9248 );
9249
9250 let origin = text_bounds.origin + offset;
9251 element.prepaint_at(origin, window, cx);
9252 Some((element, origin))
9253 } else {
9254 self.render_edit_prediction_end_of_line_popover(
9255 "Jump to Edit",
9256 editor_snapshot,
9257 visible_row_range,
9258 target_display_point,
9259 line_height,
9260 scroll_pixel_position,
9261 content_origin,
9262 editor_width,
9263 window,
9264 cx,
9265 )
9266 }
9267 }
9268
9269 fn render_edit_prediction_end_of_line_popover(
9270 self: &mut Editor,
9271 label: &'static str,
9272 editor_snapshot: &EditorSnapshot,
9273 visible_row_range: Range<DisplayRow>,
9274 target_display_point: DisplayPoint,
9275 line_height: Pixels,
9276 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9277 content_origin: gpui::Point<Pixels>,
9278 editor_width: Pixels,
9279 window: &mut Window,
9280 cx: &mut App,
9281 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9282 let target_line_end = DisplayPoint::new(
9283 target_display_point.row(),
9284 editor_snapshot.line_len(target_display_point.row()),
9285 );
9286
9287 let mut element = self
9288 .render_edit_prediction_line_popover(label, None, window, cx)
9289 .into_any();
9290
9291 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9292
9293 let line_origin =
9294 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9295
9296 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9297 let mut origin = start_point
9298 + line_origin
9299 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9300 origin.x = origin.x.max(content_origin.x);
9301
9302 let max_x = content_origin.x + editor_width - size.width;
9303
9304 if origin.x > max_x {
9305 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9306
9307 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9308 origin.y += offset;
9309 IconName::ArrowUp
9310 } else {
9311 origin.y -= offset;
9312 IconName::ArrowDown
9313 };
9314
9315 element = self
9316 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9317 .into_any();
9318
9319 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9320
9321 origin.x = content_origin.x + editor_width - size.width - px(2.);
9322 }
9323
9324 element.prepaint_at(origin, window, cx);
9325 Some((element, origin))
9326 }
9327
9328 fn render_edit_prediction_diff_popover(
9329 self: &Editor,
9330 text_bounds: &Bounds<Pixels>,
9331 content_origin: gpui::Point<Pixels>,
9332 right_margin: Pixels,
9333 editor_snapshot: &EditorSnapshot,
9334 visible_row_range: Range<DisplayRow>,
9335 line_layouts: &[LineWithInvisibles],
9336 line_height: Pixels,
9337 scroll_position: gpui::Point<ScrollOffset>,
9338 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9339 newest_selection_head: Option<DisplayPoint>,
9340 editor_width: Pixels,
9341 style: &EditorStyle,
9342 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9343 edit_preview: &Option<language::EditPreview>,
9344 snapshot: &language::BufferSnapshot,
9345 window: &mut Window,
9346 cx: &mut App,
9347 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9348 let edit_start = edits
9349 .first()
9350 .unwrap()
9351 .0
9352 .start
9353 .to_display_point(editor_snapshot);
9354 let edit_end = edits
9355 .last()
9356 .unwrap()
9357 .0
9358 .end
9359 .to_display_point(editor_snapshot);
9360
9361 let is_visible = visible_row_range.contains(&edit_start.row())
9362 || visible_row_range.contains(&edit_end.row());
9363 if !is_visible {
9364 return None;
9365 }
9366
9367 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9368 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9369 } else {
9370 // Fallback for providers without edit_preview
9371 crate::edit_prediction_fallback_text(edits, cx)
9372 };
9373
9374 let styled_text = highlighted_edits.to_styled_text(&style.text);
9375 let line_count = highlighted_edits.text.lines().count();
9376
9377 const BORDER_WIDTH: Pixels = px(1.);
9378
9379 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9380 let has_keybind = keybind.is_some();
9381
9382 let mut element = h_flex()
9383 .items_start()
9384 .child(
9385 h_flex()
9386 .bg(cx.theme().colors().editor_background)
9387 .border(BORDER_WIDTH)
9388 .shadow_xs()
9389 .border_color(cx.theme().colors().border)
9390 .rounded_l_lg()
9391 .when(line_count > 1, |el| el.rounded_br_lg())
9392 .pr_1()
9393 .child(styled_text),
9394 )
9395 .child(
9396 h_flex()
9397 .h(line_height + BORDER_WIDTH * 2.)
9398 .px_1p5()
9399 .gap_1()
9400 // Workaround: For some reason, there's a gap if we don't do this
9401 .ml(-BORDER_WIDTH)
9402 .shadow(vec![gpui::BoxShadow {
9403 color: gpui::black().opacity(0.05),
9404 offset: point(px(1.), px(1.)),
9405 blur_radius: px(2.),
9406 spread_radius: px(0.),
9407 }])
9408 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9409 .border(BORDER_WIDTH)
9410 .border_color(cx.theme().colors().border)
9411 .rounded_r_lg()
9412 .id("edit_prediction_diff_popover_keybind")
9413 .when(!has_keybind, |el| {
9414 let status_colors = cx.theme().status();
9415
9416 el.bg(status_colors.error_background)
9417 .border_color(status_colors.error.opacity(0.6))
9418 .child(Icon::new(IconName::Info).color(Color::Error))
9419 .cursor_default()
9420 .hoverable_tooltip(move |_window, cx| {
9421 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9422 })
9423 })
9424 .children(keybind),
9425 )
9426 .into_any();
9427
9428 let longest_row =
9429 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9430 let longest_line_width = if visible_row_range.contains(&longest_row) {
9431 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9432 } else {
9433 layout_line(
9434 longest_row,
9435 editor_snapshot,
9436 style,
9437 editor_width,
9438 |_| false,
9439 window,
9440 cx,
9441 )
9442 .width
9443 };
9444
9445 let viewport_bounds =
9446 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9447 right: -right_margin,
9448 ..Default::default()
9449 });
9450
9451 let x_after_longest = Pixels::from(
9452 ScrollPixelOffset::from(
9453 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9454 ) - scroll_pixel_position.x,
9455 );
9456
9457 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9458
9459 // Fully visible if it can be displayed within the window (allow overlapping other
9460 // panes). However, this is only allowed if the popover starts within text_bounds.
9461 let can_position_to_the_right = x_after_longest < text_bounds.right()
9462 && x_after_longest + element_bounds.width < viewport_bounds.right();
9463
9464 let mut origin = if can_position_to_the_right {
9465 point(
9466 x_after_longest,
9467 text_bounds.origin.y
9468 + Pixels::from(
9469 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9470 - scroll_pixel_position.y,
9471 ),
9472 )
9473 } else {
9474 let cursor_row = newest_selection_head.map(|head| head.row());
9475 let above_edit = edit_start
9476 .row()
9477 .0
9478 .checked_sub(line_count as u32)
9479 .map(DisplayRow);
9480 let below_edit = Some(edit_end.row() + 1);
9481 let above_cursor =
9482 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9483 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9484
9485 // Place the edit popover adjacent to the edit if there is a location
9486 // available that is onscreen and does not obscure the cursor. Otherwise,
9487 // place it adjacent to the cursor.
9488 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9489 .into_iter()
9490 .flatten()
9491 .find(|&start_row| {
9492 let end_row = start_row + line_count as u32;
9493 visible_row_range.contains(&start_row)
9494 && visible_row_range.contains(&end_row)
9495 && cursor_row
9496 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9497 })?;
9498
9499 content_origin
9500 + point(
9501 Pixels::from(-scroll_pixel_position.x),
9502 Pixels::from(
9503 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9504 ),
9505 )
9506 };
9507
9508 origin.x -= BORDER_WIDTH;
9509
9510 window.defer_draw(element, origin, 1);
9511
9512 // Do not return an element, since it will already be drawn due to defer_draw.
9513 None
9514 }
9515
9516 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9517 px(30.)
9518 }
9519
9520 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9521 if self.read_only(cx) {
9522 cx.theme().players().read_only()
9523 } else {
9524 self.style.as_ref().unwrap().local_player
9525 }
9526 }
9527
9528 fn render_edit_prediction_accept_keybind(
9529 &self,
9530 window: &mut Window,
9531 cx: &mut App,
9532 ) -> Option<AnyElement> {
9533 let accept_binding =
9534 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9535 let accept_keystroke = accept_binding.keystroke()?;
9536
9537 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9538
9539 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9540 Color::Accent
9541 } else {
9542 Color::Muted
9543 };
9544
9545 h_flex()
9546 .px_0p5()
9547 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9548 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9549 .text_size(TextSize::XSmall.rems(cx))
9550 .child(h_flex().children(ui::render_modifiers(
9551 accept_keystroke.modifiers(),
9552 PlatformStyle::platform(),
9553 Some(modifiers_color),
9554 Some(IconSize::XSmall.rems().into()),
9555 true,
9556 )))
9557 .when(is_platform_style_mac, |parent| {
9558 parent.child(accept_keystroke.key().to_string())
9559 })
9560 .when(!is_platform_style_mac, |parent| {
9561 parent.child(
9562 Key::new(
9563 util::capitalize(accept_keystroke.key()),
9564 Some(Color::Default),
9565 )
9566 .size(Some(IconSize::XSmall.rems().into())),
9567 )
9568 })
9569 .into_any()
9570 .into()
9571 }
9572
9573 fn render_edit_prediction_line_popover(
9574 &self,
9575 label: impl Into<SharedString>,
9576 icon: Option<IconName>,
9577 window: &mut Window,
9578 cx: &mut App,
9579 ) -> Stateful<Div> {
9580 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9581
9582 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9583 let has_keybind = keybind.is_some();
9584
9585 h_flex()
9586 .id("ep-line-popover")
9587 .py_0p5()
9588 .pl_1()
9589 .pr(padding_right)
9590 .gap_1()
9591 .rounded_md()
9592 .border_1()
9593 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9594 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9595 .shadow_xs()
9596 .when(!has_keybind, |el| {
9597 let status_colors = cx.theme().status();
9598
9599 el.bg(status_colors.error_background)
9600 .border_color(status_colors.error.opacity(0.6))
9601 .pl_2()
9602 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9603 .cursor_default()
9604 .hoverable_tooltip(move |_window, cx| {
9605 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9606 })
9607 })
9608 .children(keybind)
9609 .child(
9610 Label::new(label)
9611 .size(LabelSize::Small)
9612 .when(!has_keybind, |el| {
9613 el.color(cx.theme().status().error.into()).strikethrough()
9614 }),
9615 )
9616 .when(!has_keybind, |el| {
9617 el.child(
9618 h_flex().ml_1().child(
9619 Icon::new(IconName::Info)
9620 .size(IconSize::Small)
9621 .color(cx.theme().status().error.into()),
9622 ),
9623 )
9624 })
9625 .when_some(icon, |element, icon| {
9626 element.child(
9627 div()
9628 .mt(px(1.5))
9629 .child(Icon::new(icon).size(IconSize::Small)),
9630 )
9631 })
9632 }
9633
9634 fn render_edit_prediction_jump_outside_popover(
9635 &self,
9636 snapshot: &BufferSnapshot,
9637 window: &mut Window,
9638 cx: &mut App,
9639 ) -> Stateful<Div> {
9640 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9641 let has_keybind = keybind.is_some();
9642
9643 let file_name = snapshot
9644 .file()
9645 .map(|file| SharedString::new(file.file_name(cx)))
9646 .unwrap_or(SharedString::new_static("untitled"));
9647
9648 h_flex()
9649 .id("ep-jump-outside-popover")
9650 .py_1()
9651 .px_2()
9652 .gap_1()
9653 .rounded_md()
9654 .border_1()
9655 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9656 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9657 .shadow_xs()
9658 .when(!has_keybind, |el| {
9659 let status_colors = cx.theme().status();
9660
9661 el.bg(status_colors.error_background)
9662 .border_color(status_colors.error.opacity(0.6))
9663 .pl_2()
9664 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9665 .cursor_default()
9666 .hoverable_tooltip(move |_window, cx| {
9667 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9668 })
9669 })
9670 .children(keybind)
9671 .child(
9672 Label::new(file_name)
9673 .size(LabelSize::Small)
9674 .buffer_font(cx)
9675 .when(!has_keybind, |el| {
9676 el.color(cx.theme().status().error.into()).strikethrough()
9677 }),
9678 )
9679 .when(!has_keybind, |el| {
9680 el.child(
9681 h_flex().ml_1().child(
9682 Icon::new(IconName::Info)
9683 .size(IconSize::Small)
9684 .color(cx.theme().status().error.into()),
9685 ),
9686 )
9687 })
9688 .child(
9689 div()
9690 .mt(px(1.5))
9691 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9692 )
9693 }
9694
9695 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9696 let accent_color = cx.theme().colors().text_accent;
9697 let editor_bg_color = cx.theme().colors().editor_background;
9698 editor_bg_color.blend(accent_color.opacity(0.1))
9699 }
9700
9701 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9702 let accent_color = cx.theme().colors().text_accent;
9703 let editor_bg_color = cx.theme().colors().editor_background;
9704 editor_bg_color.blend(accent_color.opacity(0.6))
9705 }
9706 fn get_prediction_provider_icon_name(
9707 provider: &Option<RegisteredEditPredictionDelegate>,
9708 ) -> IconName {
9709 match provider {
9710 Some(provider) => match provider.provider.name() {
9711 "copilot" => IconName::Copilot,
9712 "supermaven" => IconName::Supermaven,
9713 _ => IconName::ZedPredict,
9714 },
9715 None => IconName::ZedPredict,
9716 }
9717 }
9718
9719 fn render_edit_prediction_cursor_popover(
9720 &self,
9721 min_width: Pixels,
9722 max_width: Pixels,
9723 cursor_point: Point,
9724 style: &EditorStyle,
9725 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9726 _window: &Window,
9727 cx: &mut Context<Editor>,
9728 ) -> Option<AnyElement> {
9729 let provider = self.edit_prediction_provider.as_ref()?;
9730 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9731
9732 let is_refreshing = provider.provider.is_refreshing(cx);
9733
9734 fn pending_completion_container(icon: IconName) -> Div {
9735 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9736 }
9737
9738 let completion = match &self.active_edit_prediction {
9739 Some(prediction) => {
9740 if !self.has_visible_completions_menu() {
9741 const RADIUS: Pixels = px(6.);
9742 const BORDER_WIDTH: Pixels = px(1.);
9743
9744 return Some(
9745 h_flex()
9746 .elevation_2(cx)
9747 .border(BORDER_WIDTH)
9748 .border_color(cx.theme().colors().border)
9749 .when(accept_keystroke.is_none(), |el| {
9750 el.border_color(cx.theme().status().error)
9751 })
9752 .rounded(RADIUS)
9753 .rounded_tl(px(0.))
9754 .overflow_hidden()
9755 .child(div().px_1p5().child(match &prediction.completion {
9756 EditPrediction::MoveWithin { target, snapshot } => {
9757 use text::ToPoint as _;
9758 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9759 {
9760 Icon::new(IconName::ZedPredictDown)
9761 } else {
9762 Icon::new(IconName::ZedPredictUp)
9763 }
9764 }
9765 EditPrediction::MoveOutside { .. } => {
9766 // TODO [zeta2] custom icon for external jump?
9767 Icon::new(provider_icon)
9768 }
9769 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9770 }))
9771 .child(
9772 h_flex()
9773 .gap_1()
9774 .py_1()
9775 .px_2()
9776 .rounded_r(RADIUS - BORDER_WIDTH)
9777 .border_l_1()
9778 .border_color(cx.theme().colors().border)
9779 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9780 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9781 el.child(
9782 Label::new("Hold")
9783 .size(LabelSize::Small)
9784 .when(accept_keystroke.is_none(), |el| {
9785 el.strikethrough()
9786 })
9787 .line_height_style(LineHeightStyle::UiLabel),
9788 )
9789 })
9790 .id("edit_prediction_cursor_popover_keybind")
9791 .when(accept_keystroke.is_none(), |el| {
9792 let status_colors = cx.theme().status();
9793
9794 el.bg(status_colors.error_background)
9795 .border_color(status_colors.error.opacity(0.6))
9796 .child(Icon::new(IconName::Info).color(Color::Error))
9797 .cursor_default()
9798 .hoverable_tooltip(move |_window, cx| {
9799 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9800 .into()
9801 })
9802 })
9803 .when_some(
9804 accept_keystroke.as_ref(),
9805 |el, accept_keystroke| {
9806 el.child(h_flex().children(ui::render_modifiers(
9807 accept_keystroke.modifiers(),
9808 PlatformStyle::platform(),
9809 Some(Color::Default),
9810 Some(IconSize::XSmall.rems().into()),
9811 false,
9812 )))
9813 },
9814 ),
9815 )
9816 .into_any(),
9817 );
9818 }
9819
9820 self.render_edit_prediction_cursor_popover_preview(
9821 prediction,
9822 cursor_point,
9823 style,
9824 cx,
9825 )?
9826 }
9827
9828 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9829 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9830 stale_completion,
9831 cursor_point,
9832 style,
9833 cx,
9834 )?,
9835
9836 None => pending_completion_container(provider_icon)
9837 .child(Label::new("...").size(LabelSize::Small)),
9838 },
9839
9840 None => pending_completion_container(provider_icon)
9841 .child(Label::new("...").size(LabelSize::Small)),
9842 };
9843
9844 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9845 completion
9846 .with_animation(
9847 "loading-completion",
9848 Animation::new(Duration::from_secs(2))
9849 .repeat()
9850 .with_easing(pulsating_between(0.4, 0.8)),
9851 |label, delta| label.opacity(delta),
9852 )
9853 .into_any_element()
9854 } else {
9855 completion.into_any_element()
9856 };
9857
9858 let has_completion = self.active_edit_prediction.is_some();
9859
9860 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9861 Some(
9862 h_flex()
9863 .min_w(min_width)
9864 .max_w(max_width)
9865 .flex_1()
9866 .elevation_2(cx)
9867 .border_color(cx.theme().colors().border)
9868 .child(
9869 div()
9870 .flex_1()
9871 .py_1()
9872 .px_2()
9873 .overflow_hidden()
9874 .child(completion),
9875 )
9876 .when_some(accept_keystroke, |el, accept_keystroke| {
9877 if !accept_keystroke.modifiers().modified() {
9878 return el;
9879 }
9880
9881 el.child(
9882 h_flex()
9883 .h_full()
9884 .border_l_1()
9885 .rounded_r_lg()
9886 .border_color(cx.theme().colors().border)
9887 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9888 .gap_1()
9889 .py_1()
9890 .px_2()
9891 .child(
9892 h_flex()
9893 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9894 .when(is_platform_style_mac, |parent| parent.gap_1())
9895 .child(h_flex().children(ui::render_modifiers(
9896 accept_keystroke.modifiers(),
9897 PlatformStyle::platform(),
9898 Some(if !has_completion {
9899 Color::Muted
9900 } else {
9901 Color::Default
9902 }),
9903 None,
9904 false,
9905 ))),
9906 )
9907 .child(Label::new("Preview").into_any_element())
9908 .opacity(if has_completion { 1.0 } else { 0.4 }),
9909 )
9910 })
9911 .into_any(),
9912 )
9913 }
9914
9915 fn render_edit_prediction_cursor_popover_preview(
9916 &self,
9917 completion: &EditPredictionState,
9918 cursor_point: Point,
9919 style: &EditorStyle,
9920 cx: &mut Context<Editor>,
9921 ) -> Option<Div> {
9922 use text::ToPoint as _;
9923
9924 fn render_relative_row_jump(
9925 prefix: impl Into<String>,
9926 current_row: u32,
9927 target_row: u32,
9928 ) -> Div {
9929 let (row_diff, arrow) = if target_row < current_row {
9930 (current_row - target_row, IconName::ArrowUp)
9931 } else {
9932 (target_row - current_row, IconName::ArrowDown)
9933 };
9934
9935 h_flex()
9936 .child(
9937 Label::new(format!("{}{}", prefix.into(), row_diff))
9938 .color(Color::Muted)
9939 .size(LabelSize::Small),
9940 )
9941 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9942 }
9943
9944 let supports_jump = self
9945 .edit_prediction_provider
9946 .as_ref()
9947 .map(|provider| provider.provider.supports_jump_to_edit())
9948 .unwrap_or(true);
9949
9950 match &completion.completion {
9951 EditPrediction::MoveWithin {
9952 target, snapshot, ..
9953 } => {
9954 if !supports_jump {
9955 return None;
9956 }
9957
9958 Some(
9959 h_flex()
9960 .px_2()
9961 .gap_2()
9962 .flex_1()
9963 .child(
9964 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9965 Icon::new(IconName::ZedPredictDown)
9966 } else {
9967 Icon::new(IconName::ZedPredictUp)
9968 },
9969 )
9970 .child(Label::new("Jump to Edit")),
9971 )
9972 }
9973 EditPrediction::MoveOutside { snapshot, .. } => {
9974 let file_name = snapshot
9975 .file()
9976 .map(|file| file.file_name(cx))
9977 .unwrap_or("untitled");
9978 Some(
9979 h_flex()
9980 .px_2()
9981 .gap_2()
9982 .flex_1()
9983 .child(Icon::new(IconName::ZedPredict))
9984 .child(Label::new(format!("Jump to {file_name}"))),
9985 )
9986 }
9987 EditPrediction::Edit {
9988 edits,
9989 edit_preview,
9990 snapshot,
9991 display_mode: _,
9992 } => {
9993 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9994
9995 let (highlighted_edits, has_more_lines) =
9996 if let Some(edit_preview) = edit_preview.as_ref() {
9997 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9998 .first_line_preview()
9999 } else {
10000 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
10001 };
10002
10003 let styled_text = gpui::StyledText::new(highlighted_edits.text)
10004 .with_default_highlights(&style.text, highlighted_edits.highlights);
10005
10006 let preview = h_flex()
10007 .gap_1()
10008 .min_w_16()
10009 .child(styled_text)
10010 .when(has_more_lines, |parent| parent.child("…"));
10011
10012 let left = if supports_jump && first_edit_row != cursor_point.row {
10013 render_relative_row_jump("", cursor_point.row, first_edit_row)
10014 .into_any_element()
10015 } else {
10016 let icon_name =
10017 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
10018 Icon::new(icon_name).into_any_element()
10019 };
10020
10021 Some(
10022 h_flex()
10023 .h_full()
10024 .flex_1()
10025 .gap_2()
10026 .pr_1()
10027 .overflow_x_hidden()
10028 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
10029 .child(left)
10030 .child(preview),
10031 )
10032 }
10033 }
10034 }
10035
10036 pub fn render_context_menu(
10037 &mut self,
10038 max_height_in_lines: u32,
10039 window: &mut Window,
10040 cx: &mut Context<Editor>,
10041 ) -> Option<AnyElement> {
10042 let menu = self.context_menu.borrow();
10043 let menu = menu.as_ref()?;
10044 if !menu.visible() {
10045 return None;
10046 };
10047 self.style
10048 .as_ref()
10049 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10050 }
10051
10052 fn render_context_menu_aside(
10053 &mut self,
10054 max_size: Size<Pixels>,
10055 window: &mut Window,
10056 cx: &mut Context<Editor>,
10057 ) -> Option<AnyElement> {
10058 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10059 if menu.visible() {
10060 menu.render_aside(max_size, window, cx)
10061 } else {
10062 None
10063 }
10064 })
10065 }
10066
10067 fn hide_context_menu(
10068 &mut self,
10069 window: &mut Window,
10070 cx: &mut Context<Self>,
10071 ) -> Option<CodeContextMenu> {
10072 cx.notify();
10073 self.completion_tasks.clear();
10074 let context_menu = self.context_menu.borrow_mut().take();
10075 self.stale_edit_prediction_in_menu.take();
10076 self.update_visible_edit_prediction(window, cx);
10077 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10078 && let Some(completion_provider) = &self.completion_provider
10079 {
10080 completion_provider.selection_changed(None, window, cx);
10081 }
10082 context_menu
10083 }
10084
10085 fn show_snippet_choices(
10086 &mut self,
10087 choices: &Vec<String>,
10088 selection: Range<Anchor>,
10089 cx: &mut Context<Self>,
10090 ) {
10091 let Some((_, buffer, _)) = self
10092 .buffer()
10093 .read(cx)
10094 .excerpt_containing(selection.start, cx)
10095 else {
10096 return;
10097 };
10098 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10099 else {
10100 return;
10101 };
10102 if buffer != end_buffer {
10103 log::error!("expected anchor range to have matching buffer IDs");
10104 return;
10105 }
10106
10107 let id = post_inc(&mut self.next_completion_id);
10108 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10109 let mut context_menu = self.context_menu.borrow_mut();
10110 let old_menu = context_menu.take();
10111 *context_menu = Some(CodeContextMenu::Completions(
10112 CompletionsMenu::new_snippet_choices(
10113 id,
10114 true,
10115 choices,
10116 selection,
10117 buffer,
10118 old_menu.map(|menu| menu.primary_scroll_handle()),
10119 snippet_sort_order,
10120 ),
10121 ));
10122 }
10123
10124 pub fn insert_snippet(
10125 &mut self,
10126 insertion_ranges: &[Range<MultiBufferOffset>],
10127 snippet: Snippet,
10128 window: &mut Window,
10129 cx: &mut Context<Self>,
10130 ) -> Result<()> {
10131 struct Tabstop<T> {
10132 is_end_tabstop: bool,
10133 ranges: Vec<Range<T>>,
10134 choices: Option<Vec<String>>,
10135 }
10136
10137 let tabstops = self.buffer.update(cx, |buffer, cx| {
10138 let snippet_text: Arc<str> = snippet.text.clone().into();
10139 let edits = insertion_ranges
10140 .iter()
10141 .cloned()
10142 .map(|range| (range, snippet_text.clone()));
10143 let autoindent_mode = AutoindentMode::Block {
10144 original_indent_columns: Vec::new(),
10145 };
10146 buffer.edit(edits, Some(autoindent_mode), cx);
10147
10148 let snapshot = &*buffer.read(cx);
10149 let snippet = &snippet;
10150 snippet
10151 .tabstops
10152 .iter()
10153 .map(|tabstop| {
10154 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10155 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10156 });
10157 let mut tabstop_ranges = tabstop
10158 .ranges
10159 .iter()
10160 .flat_map(|tabstop_range| {
10161 let mut delta = 0_isize;
10162 insertion_ranges.iter().map(move |insertion_range| {
10163 let insertion_start = insertion_range.start + delta;
10164 delta += snippet.text.len() as isize
10165 - (insertion_range.end - insertion_range.start) as isize;
10166
10167 let start =
10168 (insertion_start + tabstop_range.start).min(snapshot.len());
10169 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10170 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10171 })
10172 })
10173 .collect::<Vec<_>>();
10174 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10175
10176 Tabstop {
10177 is_end_tabstop,
10178 ranges: tabstop_ranges,
10179 choices: tabstop.choices.clone(),
10180 }
10181 })
10182 .collect::<Vec<_>>()
10183 });
10184 if let Some(tabstop) = tabstops.first() {
10185 self.change_selections(Default::default(), window, cx, |s| {
10186 // Reverse order so that the first range is the newest created selection.
10187 // Completions will use it and autoscroll will prioritize it.
10188 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10189 });
10190
10191 if let Some(choices) = &tabstop.choices
10192 && let Some(selection) = tabstop.ranges.first()
10193 {
10194 self.show_snippet_choices(choices, selection.clone(), cx)
10195 }
10196
10197 // If we're already at the last tabstop and it's at the end of the snippet,
10198 // we're done, we don't need to keep the state around.
10199 if !tabstop.is_end_tabstop {
10200 let choices = tabstops
10201 .iter()
10202 .map(|tabstop| tabstop.choices.clone())
10203 .collect();
10204
10205 let ranges = tabstops
10206 .into_iter()
10207 .map(|tabstop| tabstop.ranges)
10208 .collect::<Vec<_>>();
10209
10210 self.snippet_stack.push(SnippetState {
10211 active_index: 0,
10212 ranges,
10213 choices,
10214 });
10215 }
10216
10217 // Check whether the just-entered snippet ends with an auto-closable bracket.
10218 if self.autoclose_regions.is_empty() {
10219 let snapshot = self.buffer.read(cx).snapshot(cx);
10220 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10221 let selection_head = selection.head();
10222 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10223 continue;
10224 };
10225
10226 let mut bracket_pair = None;
10227 let max_lookup_length = scope
10228 .brackets()
10229 .map(|(pair, _)| {
10230 pair.start
10231 .as_str()
10232 .chars()
10233 .count()
10234 .max(pair.end.as_str().chars().count())
10235 })
10236 .max();
10237 if let Some(max_lookup_length) = max_lookup_length {
10238 let next_text = snapshot
10239 .chars_at(selection_head)
10240 .take(max_lookup_length)
10241 .collect::<String>();
10242 let prev_text = snapshot
10243 .reversed_chars_at(selection_head)
10244 .take(max_lookup_length)
10245 .collect::<String>();
10246
10247 for (pair, enabled) in scope.brackets() {
10248 if enabled
10249 && pair.close
10250 && prev_text.starts_with(pair.start.as_str())
10251 && next_text.starts_with(pair.end.as_str())
10252 {
10253 bracket_pair = Some(pair.clone());
10254 break;
10255 }
10256 }
10257 }
10258
10259 if let Some(pair) = bracket_pair {
10260 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10261 let autoclose_enabled =
10262 self.use_autoclose && snapshot_settings.use_autoclose;
10263 if autoclose_enabled {
10264 let start = snapshot.anchor_after(selection_head);
10265 let end = snapshot.anchor_after(selection_head);
10266 self.autoclose_regions.push(AutocloseRegion {
10267 selection_id: selection.id,
10268 range: start..end,
10269 pair,
10270 });
10271 }
10272 }
10273 }
10274 }
10275 }
10276 Ok(())
10277 }
10278
10279 pub fn move_to_next_snippet_tabstop(
10280 &mut self,
10281 window: &mut Window,
10282 cx: &mut Context<Self>,
10283 ) -> bool {
10284 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10285 }
10286
10287 pub fn move_to_prev_snippet_tabstop(
10288 &mut self,
10289 window: &mut Window,
10290 cx: &mut Context<Self>,
10291 ) -> bool {
10292 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10293 }
10294
10295 pub fn move_to_snippet_tabstop(
10296 &mut self,
10297 bias: Bias,
10298 window: &mut Window,
10299 cx: &mut Context<Self>,
10300 ) -> bool {
10301 if let Some(mut snippet) = self.snippet_stack.pop() {
10302 match bias {
10303 Bias::Left => {
10304 if snippet.active_index > 0 {
10305 snippet.active_index -= 1;
10306 } else {
10307 self.snippet_stack.push(snippet);
10308 return false;
10309 }
10310 }
10311 Bias::Right => {
10312 if snippet.active_index + 1 < snippet.ranges.len() {
10313 snippet.active_index += 1;
10314 } else {
10315 self.snippet_stack.push(snippet);
10316 return false;
10317 }
10318 }
10319 }
10320 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10321 self.change_selections(Default::default(), window, cx, |s| {
10322 // Reverse order so that the first range is the newest created selection.
10323 // Completions will use it and autoscroll will prioritize it.
10324 s.select_ranges(current_ranges.iter().rev().cloned())
10325 });
10326
10327 if let Some(choices) = &snippet.choices[snippet.active_index]
10328 && let Some(selection) = current_ranges.first()
10329 {
10330 self.show_snippet_choices(choices, selection.clone(), cx);
10331 }
10332
10333 // If snippet state is not at the last tabstop, push it back on the stack
10334 if snippet.active_index + 1 < snippet.ranges.len() {
10335 self.snippet_stack.push(snippet);
10336 }
10337 return true;
10338 }
10339 }
10340
10341 false
10342 }
10343
10344 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10345 self.transact(window, cx, |this, window, cx| {
10346 this.select_all(&SelectAll, window, cx);
10347 this.insert("", window, cx);
10348 });
10349 }
10350
10351 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10352 if self.read_only(cx) {
10353 return;
10354 }
10355 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10356 self.transact(window, cx, |this, window, cx| {
10357 this.select_autoclose_pair(window, cx);
10358
10359 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10360
10361 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10362 if !this.linked_edit_ranges.is_empty() {
10363 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10364 let snapshot = this.buffer.read(cx).snapshot(cx);
10365
10366 for selection in selections.iter() {
10367 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10368 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10369 if selection_start.buffer_id != selection_end.buffer_id {
10370 continue;
10371 }
10372 if let Some(ranges) =
10373 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10374 {
10375 for (buffer, entries) in ranges {
10376 linked_ranges.entry(buffer).or_default().extend(entries);
10377 }
10378 }
10379 }
10380 }
10381
10382 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10383 for selection in &mut selections {
10384 if selection.is_empty() {
10385 let old_head = selection.head();
10386 let mut new_head =
10387 movement::left(&display_map, old_head.to_display_point(&display_map))
10388 .to_point(&display_map);
10389 if let Some((buffer, line_buffer_range)) = display_map
10390 .buffer_snapshot()
10391 .buffer_line_for_row(MultiBufferRow(old_head.row))
10392 {
10393 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10394 let indent_len = match indent_size.kind {
10395 IndentKind::Space => {
10396 buffer.settings_at(line_buffer_range.start, cx).tab_size
10397 }
10398 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10399 };
10400 if old_head.column <= indent_size.len && old_head.column > 0 {
10401 let indent_len = indent_len.get();
10402 new_head = cmp::min(
10403 new_head,
10404 MultiBufferPoint::new(
10405 old_head.row,
10406 ((old_head.column - 1) / indent_len) * indent_len,
10407 ),
10408 );
10409 }
10410 }
10411
10412 selection.set_head(new_head, SelectionGoal::None);
10413 }
10414 }
10415
10416 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10417 this.insert("", window, cx);
10418 let empty_str: Arc<str> = Arc::from("");
10419 for (buffer, edits) in linked_ranges {
10420 let snapshot = buffer.read(cx).snapshot();
10421 use text::ToPoint as TP;
10422
10423 let edits = edits
10424 .into_iter()
10425 .map(|range| {
10426 let end_point = TP::to_point(&range.end, &snapshot);
10427 let mut start_point = TP::to_point(&range.start, &snapshot);
10428
10429 if end_point == start_point {
10430 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10431 .saturating_sub(1);
10432 start_point =
10433 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10434 };
10435
10436 (start_point..end_point, empty_str.clone())
10437 })
10438 .sorted_by_key(|(range, _)| range.start)
10439 .collect::<Vec<_>>();
10440 buffer.update(cx, |this, cx| {
10441 this.edit(edits, None, cx);
10442 })
10443 }
10444 this.refresh_edit_prediction(true, false, window, cx);
10445 refresh_linked_ranges(this, window, cx);
10446 });
10447 }
10448
10449 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10450 if self.read_only(cx) {
10451 return;
10452 }
10453 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10454 self.transact(window, cx, |this, window, cx| {
10455 this.change_selections(Default::default(), window, cx, |s| {
10456 s.move_with(|map, selection| {
10457 if selection.is_empty() {
10458 let cursor = movement::right(map, selection.head());
10459 selection.end = cursor;
10460 selection.reversed = true;
10461 selection.goal = SelectionGoal::None;
10462 }
10463 })
10464 });
10465 this.insert("", window, cx);
10466 this.refresh_edit_prediction(true, false, window, cx);
10467 });
10468 }
10469
10470 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10471 if self.mode.is_single_line() {
10472 cx.propagate();
10473 return;
10474 }
10475
10476 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10477 if self.move_to_prev_snippet_tabstop(window, cx) {
10478 return;
10479 }
10480 self.outdent(&Outdent, window, cx);
10481 }
10482
10483 pub fn next_snippet_tabstop(
10484 &mut self,
10485 _: &NextSnippetTabstop,
10486 window: &mut Window,
10487 cx: &mut Context<Self>,
10488 ) {
10489 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10490 cx.propagate();
10491 return;
10492 }
10493
10494 if self.move_to_next_snippet_tabstop(window, cx) {
10495 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10496 return;
10497 }
10498 cx.propagate();
10499 }
10500
10501 pub fn previous_snippet_tabstop(
10502 &mut self,
10503 _: &PreviousSnippetTabstop,
10504 window: &mut Window,
10505 cx: &mut Context<Self>,
10506 ) {
10507 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10508 cx.propagate();
10509 return;
10510 }
10511
10512 if self.move_to_prev_snippet_tabstop(window, cx) {
10513 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10514 return;
10515 }
10516 cx.propagate();
10517 }
10518
10519 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10520 if self.mode.is_single_line() {
10521 cx.propagate();
10522 return;
10523 }
10524
10525 if self.move_to_next_snippet_tabstop(window, cx) {
10526 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10527 return;
10528 }
10529 if self.read_only(cx) {
10530 return;
10531 }
10532 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10533 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10534 let buffer = self.buffer.read(cx);
10535 let snapshot = buffer.snapshot(cx);
10536 let rows_iter = selections.iter().map(|s| s.head().row);
10537 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10538
10539 let has_some_cursor_in_whitespace = selections
10540 .iter()
10541 .filter(|selection| selection.is_empty())
10542 .any(|selection| {
10543 let cursor = selection.head();
10544 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10545 cursor.column < current_indent.len
10546 });
10547
10548 let mut edits = Vec::new();
10549 let mut prev_edited_row = 0;
10550 let mut row_delta = 0;
10551 for selection in &mut selections {
10552 if selection.start.row != prev_edited_row {
10553 row_delta = 0;
10554 }
10555 prev_edited_row = selection.end.row;
10556
10557 // If the selection is non-empty, then increase the indentation of the selected lines.
10558 if !selection.is_empty() {
10559 row_delta =
10560 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10561 continue;
10562 }
10563
10564 let cursor = selection.head();
10565 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10566 if let Some(suggested_indent) =
10567 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10568 {
10569 // Don't do anything if already at suggested indent
10570 // and there is any other cursor which is not
10571 if has_some_cursor_in_whitespace
10572 && cursor.column == current_indent.len
10573 && current_indent.len == suggested_indent.len
10574 {
10575 continue;
10576 }
10577
10578 // Adjust line and move cursor to suggested indent
10579 // if cursor is not at suggested indent
10580 if cursor.column < suggested_indent.len
10581 && cursor.column <= current_indent.len
10582 && current_indent.len <= suggested_indent.len
10583 {
10584 selection.start = Point::new(cursor.row, suggested_indent.len);
10585 selection.end = selection.start;
10586 if row_delta == 0 {
10587 edits.extend(Buffer::edit_for_indent_size_adjustment(
10588 cursor.row,
10589 current_indent,
10590 suggested_indent,
10591 ));
10592 row_delta = suggested_indent.len - current_indent.len;
10593 }
10594 continue;
10595 }
10596
10597 // If current indent is more than suggested indent
10598 // only move cursor to current indent and skip indent
10599 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10600 selection.start = Point::new(cursor.row, current_indent.len);
10601 selection.end = selection.start;
10602 continue;
10603 }
10604 }
10605
10606 // Otherwise, insert a hard or soft tab.
10607 let settings = buffer.language_settings_at(cursor, cx);
10608 let tab_size = if settings.hard_tabs {
10609 IndentSize::tab()
10610 } else {
10611 let tab_size = settings.tab_size.get();
10612 let indent_remainder = snapshot
10613 .text_for_range(Point::new(cursor.row, 0)..cursor)
10614 .flat_map(str::chars)
10615 .fold(row_delta % tab_size, |counter: u32, c| {
10616 if c == '\t' {
10617 0
10618 } else {
10619 (counter + 1) % tab_size
10620 }
10621 });
10622
10623 let chars_to_next_tab_stop = tab_size - indent_remainder;
10624 IndentSize::spaces(chars_to_next_tab_stop)
10625 };
10626 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10627 selection.end = selection.start;
10628 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10629 row_delta += tab_size.len;
10630 }
10631
10632 self.transact(window, cx, |this, window, cx| {
10633 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10634 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10635 this.refresh_edit_prediction(true, false, window, cx);
10636 });
10637 }
10638
10639 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10640 if self.read_only(cx) {
10641 return;
10642 }
10643 if self.mode.is_single_line() {
10644 cx.propagate();
10645 return;
10646 }
10647
10648 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10649 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10650 let mut prev_edited_row = 0;
10651 let mut row_delta = 0;
10652 let mut edits = Vec::new();
10653 let buffer = self.buffer.read(cx);
10654 let snapshot = buffer.snapshot(cx);
10655 for selection in &mut selections {
10656 if selection.start.row != prev_edited_row {
10657 row_delta = 0;
10658 }
10659 prev_edited_row = selection.end.row;
10660
10661 row_delta =
10662 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10663 }
10664
10665 self.transact(window, cx, |this, window, cx| {
10666 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10667 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10668 });
10669 }
10670
10671 fn indent_selection(
10672 buffer: &MultiBuffer,
10673 snapshot: &MultiBufferSnapshot,
10674 selection: &mut Selection<Point>,
10675 edits: &mut Vec<(Range<Point>, String)>,
10676 delta_for_start_row: u32,
10677 cx: &App,
10678 ) -> u32 {
10679 let settings = buffer.language_settings_at(selection.start, cx);
10680 let tab_size = settings.tab_size.get();
10681 let indent_kind = if settings.hard_tabs {
10682 IndentKind::Tab
10683 } else {
10684 IndentKind::Space
10685 };
10686 let mut start_row = selection.start.row;
10687 let mut end_row = selection.end.row + 1;
10688
10689 // If a selection ends at the beginning of a line, don't indent
10690 // that last line.
10691 if selection.end.column == 0 && selection.end.row > selection.start.row {
10692 end_row -= 1;
10693 }
10694
10695 // Avoid re-indenting a row that has already been indented by a
10696 // previous selection, but still update this selection's column
10697 // to reflect that indentation.
10698 if delta_for_start_row > 0 {
10699 start_row += 1;
10700 selection.start.column += delta_for_start_row;
10701 if selection.end.row == selection.start.row {
10702 selection.end.column += delta_for_start_row;
10703 }
10704 }
10705
10706 let mut delta_for_end_row = 0;
10707 let has_multiple_rows = start_row + 1 != end_row;
10708 for row in start_row..end_row {
10709 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10710 let indent_delta = match (current_indent.kind, indent_kind) {
10711 (IndentKind::Space, IndentKind::Space) => {
10712 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10713 IndentSize::spaces(columns_to_next_tab_stop)
10714 }
10715 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10716 (_, IndentKind::Tab) => IndentSize::tab(),
10717 };
10718
10719 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10720 0
10721 } else {
10722 selection.start.column
10723 };
10724 let row_start = Point::new(row, start);
10725 edits.push((
10726 row_start..row_start,
10727 indent_delta.chars().collect::<String>(),
10728 ));
10729
10730 // Update this selection's endpoints to reflect the indentation.
10731 if row == selection.start.row {
10732 selection.start.column += indent_delta.len;
10733 }
10734 if row == selection.end.row {
10735 selection.end.column += indent_delta.len;
10736 delta_for_end_row = indent_delta.len;
10737 }
10738 }
10739
10740 if selection.start.row == selection.end.row {
10741 delta_for_start_row + delta_for_end_row
10742 } else {
10743 delta_for_end_row
10744 }
10745 }
10746
10747 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10748 if self.read_only(cx) {
10749 return;
10750 }
10751 if self.mode.is_single_line() {
10752 cx.propagate();
10753 return;
10754 }
10755
10756 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10757 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10758 let selections = self.selections.all::<Point>(&display_map);
10759 let mut deletion_ranges = Vec::new();
10760 let mut last_outdent = None;
10761 {
10762 let buffer = self.buffer.read(cx);
10763 let snapshot = buffer.snapshot(cx);
10764 for selection in &selections {
10765 let settings = buffer.language_settings_at(selection.start, cx);
10766 let tab_size = settings.tab_size.get();
10767 let mut rows = selection.spanned_rows(false, &display_map);
10768
10769 // Avoid re-outdenting a row that has already been outdented by a
10770 // previous selection.
10771 if let Some(last_row) = last_outdent
10772 && last_row == rows.start
10773 {
10774 rows.start = rows.start.next_row();
10775 }
10776 let has_multiple_rows = rows.len() > 1;
10777 for row in rows.iter_rows() {
10778 let indent_size = snapshot.indent_size_for_line(row);
10779 if indent_size.len > 0 {
10780 let deletion_len = match indent_size.kind {
10781 IndentKind::Space => {
10782 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10783 if columns_to_prev_tab_stop == 0 {
10784 tab_size
10785 } else {
10786 columns_to_prev_tab_stop
10787 }
10788 }
10789 IndentKind::Tab => 1,
10790 };
10791 let start = if has_multiple_rows
10792 || deletion_len > selection.start.column
10793 || indent_size.len < selection.start.column
10794 {
10795 0
10796 } else {
10797 selection.start.column - deletion_len
10798 };
10799 deletion_ranges.push(
10800 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10801 );
10802 last_outdent = Some(row);
10803 }
10804 }
10805 }
10806 }
10807
10808 self.transact(window, cx, |this, window, cx| {
10809 this.buffer.update(cx, |buffer, cx| {
10810 let empty_str: Arc<str> = Arc::default();
10811 buffer.edit(
10812 deletion_ranges
10813 .into_iter()
10814 .map(|range| (range, empty_str.clone())),
10815 None,
10816 cx,
10817 );
10818 });
10819 let selections = this
10820 .selections
10821 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10822 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10823 });
10824 }
10825
10826 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10827 if self.read_only(cx) {
10828 return;
10829 }
10830 if self.mode.is_single_line() {
10831 cx.propagate();
10832 return;
10833 }
10834
10835 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10836 let selections = self
10837 .selections
10838 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10839 .into_iter()
10840 .map(|s| s.range());
10841
10842 self.transact(window, cx, |this, window, cx| {
10843 this.buffer.update(cx, |buffer, cx| {
10844 buffer.autoindent_ranges(selections, cx);
10845 });
10846 let selections = this
10847 .selections
10848 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10849 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10850 });
10851 }
10852
10853 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10854 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10855 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10856 let selections = self.selections.all::<Point>(&display_map);
10857
10858 let mut new_cursors = Vec::new();
10859 let mut edit_ranges = Vec::new();
10860 let mut selections = selections.iter().peekable();
10861 while let Some(selection) = selections.next() {
10862 let mut rows = selection.spanned_rows(false, &display_map);
10863
10864 // Accumulate contiguous regions of rows that we want to delete.
10865 while let Some(next_selection) = selections.peek() {
10866 let next_rows = next_selection.spanned_rows(false, &display_map);
10867 if next_rows.start <= rows.end {
10868 rows.end = next_rows.end;
10869 selections.next().unwrap();
10870 } else {
10871 break;
10872 }
10873 }
10874
10875 let buffer = display_map.buffer_snapshot();
10876 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10877 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10878 // If there's a line after the range, delete the \n from the end of the row range
10879 (
10880 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10881 rows.end,
10882 )
10883 } else {
10884 // If there isn't a line after the range, delete the \n from the line before the
10885 // start of the row range
10886 edit_start = edit_start.saturating_sub_usize(1);
10887 (buffer.len(), rows.start.previous_row())
10888 };
10889
10890 let text_layout_details = self.text_layout_details(window);
10891 let x = display_map.x_for_display_point(
10892 selection.head().to_display_point(&display_map),
10893 &text_layout_details,
10894 );
10895 let row = Point::new(target_row.0, 0)
10896 .to_display_point(&display_map)
10897 .row();
10898 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10899
10900 new_cursors.push((
10901 selection.id,
10902 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10903 SelectionGoal::None,
10904 ));
10905 edit_ranges.push(edit_start..edit_end);
10906 }
10907
10908 self.transact(window, cx, |this, window, cx| {
10909 let buffer = this.buffer.update(cx, |buffer, cx| {
10910 let empty_str: Arc<str> = Arc::default();
10911 buffer.edit(
10912 edit_ranges
10913 .into_iter()
10914 .map(|range| (range, empty_str.clone())),
10915 None,
10916 cx,
10917 );
10918 buffer.snapshot(cx)
10919 });
10920 let new_selections = new_cursors
10921 .into_iter()
10922 .map(|(id, cursor, goal)| {
10923 let cursor = cursor.to_point(&buffer);
10924 Selection {
10925 id,
10926 start: cursor,
10927 end: cursor,
10928 reversed: false,
10929 goal,
10930 }
10931 })
10932 .collect();
10933
10934 this.change_selections(Default::default(), window, cx, |s| {
10935 s.select(new_selections);
10936 });
10937 });
10938 }
10939
10940 pub fn join_lines_impl(
10941 &mut self,
10942 insert_whitespace: bool,
10943 window: &mut Window,
10944 cx: &mut Context<Self>,
10945 ) {
10946 if self.read_only(cx) {
10947 return;
10948 }
10949 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10950 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10951 let start = MultiBufferRow(selection.start.row);
10952 // Treat single line selections as if they include the next line. Otherwise this action
10953 // would do nothing for single line selections individual cursors.
10954 let end = if selection.start.row == selection.end.row {
10955 MultiBufferRow(selection.start.row + 1)
10956 } else {
10957 MultiBufferRow(selection.end.row)
10958 };
10959
10960 if let Some(last_row_range) = row_ranges.last_mut()
10961 && start <= last_row_range.end
10962 {
10963 last_row_range.end = end;
10964 continue;
10965 }
10966 row_ranges.push(start..end);
10967 }
10968
10969 let snapshot = self.buffer.read(cx).snapshot(cx);
10970 let mut cursor_positions = Vec::new();
10971 for row_range in &row_ranges {
10972 let anchor = snapshot.anchor_before(Point::new(
10973 row_range.end.previous_row().0,
10974 snapshot.line_len(row_range.end.previous_row()),
10975 ));
10976 cursor_positions.push(anchor..anchor);
10977 }
10978
10979 self.transact(window, cx, |this, window, cx| {
10980 for row_range in row_ranges.into_iter().rev() {
10981 for row in row_range.iter_rows().rev() {
10982 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10983 let next_line_row = row.next_row();
10984 let indent = snapshot.indent_size_for_line(next_line_row);
10985 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10986
10987 let replace =
10988 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10989 " "
10990 } else {
10991 ""
10992 };
10993
10994 this.buffer.update(cx, |buffer, cx| {
10995 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10996 });
10997 }
10998 }
10999
11000 this.change_selections(Default::default(), window, cx, |s| {
11001 s.select_anchor_ranges(cursor_positions)
11002 });
11003 });
11004 }
11005
11006 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
11007 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11008 self.join_lines_impl(true, window, cx);
11009 }
11010
11011 pub fn sort_lines_case_sensitive(
11012 &mut self,
11013 _: &SortLinesCaseSensitive,
11014 window: &mut Window,
11015 cx: &mut Context<Self>,
11016 ) {
11017 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
11018 }
11019
11020 pub fn sort_lines_by_length(
11021 &mut self,
11022 _: &SortLinesByLength,
11023 window: &mut Window,
11024 cx: &mut Context<Self>,
11025 ) {
11026 self.manipulate_immutable_lines(window, cx, |lines| {
11027 lines.sort_by_key(|&line| line.chars().count())
11028 })
11029 }
11030
11031 pub fn sort_lines_case_insensitive(
11032 &mut self,
11033 _: &SortLinesCaseInsensitive,
11034 window: &mut Window,
11035 cx: &mut Context<Self>,
11036 ) {
11037 self.manipulate_immutable_lines(window, cx, |lines| {
11038 lines.sort_by_key(|line| line.to_lowercase())
11039 })
11040 }
11041
11042 pub fn unique_lines_case_insensitive(
11043 &mut self,
11044 _: &UniqueLinesCaseInsensitive,
11045 window: &mut Window,
11046 cx: &mut Context<Self>,
11047 ) {
11048 self.manipulate_immutable_lines(window, cx, |lines| {
11049 let mut seen = HashSet::default();
11050 lines.retain(|line| seen.insert(line.to_lowercase()));
11051 })
11052 }
11053
11054 pub fn unique_lines_case_sensitive(
11055 &mut self,
11056 _: &UniqueLinesCaseSensitive,
11057 window: &mut Window,
11058 cx: &mut Context<Self>,
11059 ) {
11060 self.manipulate_immutable_lines(window, cx, |lines| {
11061 let mut seen = HashSet::default();
11062 lines.retain(|line| seen.insert(*line));
11063 })
11064 }
11065
11066 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11067 let snapshot = self.buffer.read(cx).snapshot(cx);
11068 for selection in self.selections.disjoint_anchors_arc().iter() {
11069 if snapshot
11070 .language_at(selection.start)
11071 .and_then(|lang| lang.config().wrap_characters.as_ref())
11072 .is_some()
11073 {
11074 return true;
11075 }
11076 }
11077 false
11078 }
11079
11080 fn wrap_selections_in_tag(
11081 &mut self,
11082 _: &WrapSelectionsInTag,
11083 window: &mut Window,
11084 cx: &mut Context<Self>,
11085 ) {
11086 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11087
11088 let snapshot = self.buffer.read(cx).snapshot(cx);
11089
11090 let mut edits = Vec::new();
11091 let mut boundaries = Vec::new();
11092
11093 for selection in self
11094 .selections
11095 .all_adjusted(&self.display_snapshot(cx))
11096 .iter()
11097 {
11098 let Some(wrap_config) = snapshot
11099 .language_at(selection.start)
11100 .and_then(|lang| lang.config().wrap_characters.clone())
11101 else {
11102 continue;
11103 };
11104
11105 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11106 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11107
11108 let start_before = snapshot.anchor_before(selection.start);
11109 let end_after = snapshot.anchor_after(selection.end);
11110
11111 edits.push((start_before..start_before, open_tag));
11112 edits.push((end_after..end_after, close_tag));
11113
11114 boundaries.push((
11115 start_before,
11116 end_after,
11117 wrap_config.start_prefix.len(),
11118 wrap_config.end_suffix.len(),
11119 ));
11120 }
11121
11122 if edits.is_empty() {
11123 return;
11124 }
11125
11126 self.transact(window, cx, |this, window, cx| {
11127 let buffer = this.buffer.update(cx, |buffer, cx| {
11128 buffer.edit(edits, None, cx);
11129 buffer.snapshot(cx)
11130 });
11131
11132 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11133 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11134 boundaries.into_iter()
11135 {
11136 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11137 let close_offset = end_after
11138 .to_offset(&buffer)
11139 .saturating_sub_usize(end_suffix_len);
11140 new_selections.push(open_offset..open_offset);
11141 new_selections.push(close_offset..close_offset);
11142 }
11143
11144 this.change_selections(Default::default(), window, cx, |s| {
11145 s.select_ranges(new_selections);
11146 });
11147
11148 this.request_autoscroll(Autoscroll::fit(), cx);
11149 });
11150 }
11151
11152 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11153 let Some(project) = self.project.clone() else {
11154 return;
11155 };
11156 self.reload(project, window, cx)
11157 .detach_and_notify_err(window, cx);
11158 }
11159
11160 pub fn restore_file(
11161 &mut self,
11162 _: &::git::RestoreFile,
11163 window: &mut Window,
11164 cx: &mut Context<Self>,
11165 ) {
11166 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11167 let mut buffer_ids = HashSet::default();
11168 let snapshot = self.buffer().read(cx).snapshot(cx);
11169 for selection in self
11170 .selections
11171 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11172 {
11173 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11174 }
11175
11176 let buffer = self.buffer().read(cx);
11177 let ranges = buffer_ids
11178 .into_iter()
11179 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11180 .collect::<Vec<_>>();
11181
11182 self.restore_hunks_in_ranges(ranges, window, cx);
11183 }
11184
11185 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11186 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11187 let selections = self
11188 .selections
11189 .all(&self.display_snapshot(cx))
11190 .into_iter()
11191 .map(|s| s.range())
11192 .collect();
11193 self.restore_hunks_in_ranges(selections, window, cx);
11194 }
11195
11196 pub fn restore_hunks_in_ranges(
11197 &mut self,
11198 ranges: Vec<Range<Point>>,
11199 window: &mut Window,
11200 cx: &mut Context<Editor>,
11201 ) {
11202 let mut revert_changes = HashMap::default();
11203 let chunk_by = self
11204 .snapshot(window, cx)
11205 .hunks_for_ranges(ranges)
11206 .into_iter()
11207 .chunk_by(|hunk| hunk.buffer_id);
11208 for (buffer_id, hunks) in &chunk_by {
11209 let hunks = hunks.collect::<Vec<_>>();
11210 for hunk in &hunks {
11211 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11212 }
11213 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11214 }
11215 drop(chunk_by);
11216 if !revert_changes.is_empty() {
11217 self.transact(window, cx, |editor, window, cx| {
11218 editor.restore(revert_changes, window, cx);
11219 });
11220 }
11221 }
11222
11223 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11224 if let Some(status) = self
11225 .addons
11226 .iter()
11227 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11228 {
11229 return Some(status);
11230 }
11231 self.project
11232 .as_ref()?
11233 .read(cx)
11234 .status_for_buffer_id(buffer_id, cx)
11235 }
11236
11237 pub fn open_active_item_in_terminal(
11238 &mut self,
11239 _: &OpenInTerminal,
11240 window: &mut Window,
11241 cx: &mut Context<Self>,
11242 ) {
11243 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11244 let project_path = buffer.read(cx).project_path(cx)?;
11245 let project = self.project()?.read(cx);
11246 let entry = project.entry_for_path(&project_path, cx)?;
11247 let parent = match &entry.canonical_path {
11248 Some(canonical_path) => canonical_path.to_path_buf(),
11249 None => project.absolute_path(&project_path, cx)?,
11250 }
11251 .parent()?
11252 .to_path_buf();
11253 Some(parent)
11254 }) {
11255 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11256 }
11257 }
11258
11259 fn set_breakpoint_context_menu(
11260 &mut self,
11261 display_row: DisplayRow,
11262 position: Option<Anchor>,
11263 clicked_point: gpui::Point<Pixels>,
11264 window: &mut Window,
11265 cx: &mut Context<Self>,
11266 ) {
11267 let source = self
11268 .buffer
11269 .read(cx)
11270 .snapshot(cx)
11271 .anchor_before(Point::new(display_row.0, 0u32));
11272
11273 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11274
11275 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11276 self,
11277 source,
11278 clicked_point,
11279 context_menu,
11280 window,
11281 cx,
11282 );
11283 }
11284
11285 fn add_edit_breakpoint_block(
11286 &mut self,
11287 anchor: Anchor,
11288 breakpoint: &Breakpoint,
11289 edit_action: BreakpointPromptEditAction,
11290 window: &mut Window,
11291 cx: &mut Context<Self>,
11292 ) {
11293 let weak_editor = cx.weak_entity();
11294 let bp_prompt = cx.new(|cx| {
11295 BreakpointPromptEditor::new(
11296 weak_editor,
11297 anchor,
11298 breakpoint.clone(),
11299 edit_action,
11300 window,
11301 cx,
11302 )
11303 });
11304
11305 let height = bp_prompt.update(cx, |this, cx| {
11306 this.prompt
11307 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11308 });
11309 let cloned_prompt = bp_prompt.clone();
11310 let blocks = vec![BlockProperties {
11311 style: BlockStyle::Sticky,
11312 placement: BlockPlacement::Above(anchor),
11313 height: Some(height),
11314 render: Arc::new(move |cx| {
11315 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11316 cloned_prompt.clone().into_any_element()
11317 }),
11318 priority: 0,
11319 }];
11320
11321 let focus_handle = bp_prompt.focus_handle(cx);
11322 window.focus(&focus_handle);
11323
11324 let block_ids = self.insert_blocks(blocks, None, cx);
11325 bp_prompt.update(cx, |prompt, _| {
11326 prompt.add_block_ids(block_ids);
11327 });
11328 }
11329
11330 pub(crate) fn breakpoint_at_row(
11331 &self,
11332 row: u32,
11333 window: &mut Window,
11334 cx: &mut Context<Self>,
11335 ) -> Option<(Anchor, Breakpoint)> {
11336 let snapshot = self.snapshot(window, cx);
11337 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11338
11339 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11340 }
11341
11342 pub(crate) fn breakpoint_at_anchor(
11343 &self,
11344 breakpoint_position: Anchor,
11345 snapshot: &EditorSnapshot,
11346 cx: &mut Context<Self>,
11347 ) -> Option<(Anchor, Breakpoint)> {
11348 let buffer = self
11349 .buffer
11350 .read(cx)
11351 .buffer_for_anchor(breakpoint_position, cx)?;
11352
11353 let enclosing_excerpt = breakpoint_position.excerpt_id;
11354 let buffer_snapshot = buffer.read(cx).snapshot();
11355
11356 let row = buffer_snapshot
11357 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11358 .row;
11359
11360 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11361 let anchor_end = snapshot
11362 .buffer_snapshot()
11363 .anchor_after(Point::new(row, line_len));
11364
11365 self.breakpoint_store
11366 .as_ref()?
11367 .read_with(cx, |breakpoint_store, cx| {
11368 breakpoint_store
11369 .breakpoints(
11370 &buffer,
11371 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11372 &buffer_snapshot,
11373 cx,
11374 )
11375 .next()
11376 .and_then(|(bp, _)| {
11377 let breakpoint_row = buffer_snapshot
11378 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11379 .row;
11380
11381 if breakpoint_row == row {
11382 snapshot
11383 .buffer_snapshot()
11384 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11385 .map(|position| (position, bp.bp.clone()))
11386 } else {
11387 None
11388 }
11389 })
11390 })
11391 }
11392
11393 pub fn edit_log_breakpoint(
11394 &mut self,
11395 _: &EditLogBreakpoint,
11396 window: &mut Window,
11397 cx: &mut Context<Self>,
11398 ) {
11399 if self.breakpoint_store.is_none() {
11400 return;
11401 }
11402
11403 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11404 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11405 message: None,
11406 state: BreakpointState::Enabled,
11407 condition: None,
11408 hit_condition: None,
11409 });
11410
11411 self.add_edit_breakpoint_block(
11412 anchor,
11413 &breakpoint,
11414 BreakpointPromptEditAction::Log,
11415 window,
11416 cx,
11417 );
11418 }
11419 }
11420
11421 fn breakpoints_at_cursors(
11422 &self,
11423 window: &mut Window,
11424 cx: &mut Context<Self>,
11425 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11426 let snapshot = self.snapshot(window, cx);
11427 let cursors = self
11428 .selections
11429 .disjoint_anchors_arc()
11430 .iter()
11431 .map(|selection| {
11432 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11433
11434 let breakpoint_position = self
11435 .breakpoint_at_row(cursor_position.row, window, cx)
11436 .map(|bp| bp.0)
11437 .unwrap_or_else(|| {
11438 snapshot
11439 .display_snapshot
11440 .buffer_snapshot()
11441 .anchor_after(Point::new(cursor_position.row, 0))
11442 });
11443
11444 let breakpoint = self
11445 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11446 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11447
11448 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11449 })
11450 // 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.
11451 .collect::<HashMap<Anchor, _>>();
11452
11453 cursors.into_iter().collect()
11454 }
11455
11456 pub fn enable_breakpoint(
11457 &mut self,
11458 _: &crate::actions::EnableBreakpoint,
11459 window: &mut Window,
11460 cx: &mut Context<Self>,
11461 ) {
11462 if self.breakpoint_store.is_none() {
11463 return;
11464 }
11465
11466 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11467 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11468 continue;
11469 };
11470 self.edit_breakpoint_at_anchor(
11471 anchor,
11472 breakpoint,
11473 BreakpointEditAction::InvertState,
11474 cx,
11475 );
11476 }
11477 }
11478
11479 pub fn disable_breakpoint(
11480 &mut self,
11481 _: &crate::actions::DisableBreakpoint,
11482 window: &mut Window,
11483 cx: &mut Context<Self>,
11484 ) {
11485 if self.breakpoint_store.is_none() {
11486 return;
11487 }
11488
11489 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11490 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11491 continue;
11492 };
11493 self.edit_breakpoint_at_anchor(
11494 anchor,
11495 breakpoint,
11496 BreakpointEditAction::InvertState,
11497 cx,
11498 );
11499 }
11500 }
11501
11502 pub fn toggle_breakpoint(
11503 &mut self,
11504 _: &crate::actions::ToggleBreakpoint,
11505 window: &mut Window,
11506 cx: &mut Context<Self>,
11507 ) {
11508 if self.breakpoint_store.is_none() {
11509 return;
11510 }
11511
11512 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11513 if let Some(breakpoint) = breakpoint {
11514 self.edit_breakpoint_at_anchor(
11515 anchor,
11516 breakpoint,
11517 BreakpointEditAction::Toggle,
11518 cx,
11519 );
11520 } else {
11521 self.edit_breakpoint_at_anchor(
11522 anchor,
11523 Breakpoint::new_standard(),
11524 BreakpointEditAction::Toggle,
11525 cx,
11526 );
11527 }
11528 }
11529 }
11530
11531 pub fn edit_breakpoint_at_anchor(
11532 &mut self,
11533 breakpoint_position: Anchor,
11534 breakpoint: Breakpoint,
11535 edit_action: BreakpointEditAction,
11536 cx: &mut Context<Self>,
11537 ) {
11538 let Some(breakpoint_store) = &self.breakpoint_store else {
11539 return;
11540 };
11541
11542 let Some(buffer) = self
11543 .buffer
11544 .read(cx)
11545 .buffer_for_anchor(breakpoint_position, cx)
11546 else {
11547 return;
11548 };
11549
11550 breakpoint_store.update(cx, |breakpoint_store, cx| {
11551 breakpoint_store.toggle_breakpoint(
11552 buffer,
11553 BreakpointWithPosition {
11554 position: breakpoint_position.text_anchor,
11555 bp: breakpoint,
11556 },
11557 edit_action,
11558 cx,
11559 );
11560 });
11561
11562 cx.notify();
11563 }
11564
11565 #[cfg(any(test, feature = "test-support"))]
11566 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11567 self.breakpoint_store.clone()
11568 }
11569
11570 pub fn prepare_restore_change(
11571 &self,
11572 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11573 hunk: &MultiBufferDiffHunk,
11574 cx: &mut App,
11575 ) -> Option<()> {
11576 if hunk.is_created_file() {
11577 return None;
11578 }
11579 let buffer = self.buffer.read(cx);
11580 let diff = buffer.diff_for(hunk.buffer_id)?;
11581 let buffer = buffer.buffer(hunk.buffer_id)?;
11582 let buffer = buffer.read(cx);
11583 let original_text = diff
11584 .read(cx)
11585 .base_text()
11586 .as_rope()
11587 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11588 let buffer_snapshot = buffer.snapshot();
11589 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11590 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11591 probe
11592 .0
11593 .start
11594 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11595 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11596 }) {
11597 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11598 Some(())
11599 } else {
11600 None
11601 }
11602 }
11603
11604 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11605 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11606 }
11607
11608 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11609 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11610 }
11611
11612 pub fn rotate_selections_forward(
11613 &mut self,
11614 _: &RotateSelectionsForward,
11615 window: &mut Window,
11616 cx: &mut Context<Self>,
11617 ) {
11618 self.rotate_selections(window, cx, false)
11619 }
11620
11621 pub fn rotate_selections_backward(
11622 &mut self,
11623 _: &RotateSelectionsBackward,
11624 window: &mut Window,
11625 cx: &mut Context<Self>,
11626 ) {
11627 self.rotate_selections(window, cx, true)
11628 }
11629
11630 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11631 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11632 let display_snapshot = self.display_snapshot(cx);
11633 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11634
11635 if selections.len() < 2 {
11636 return;
11637 }
11638
11639 let (edits, new_selections) = {
11640 let buffer = self.buffer.read(cx).read(cx);
11641 let has_selections = selections.iter().any(|s| !s.is_empty());
11642 if has_selections {
11643 let mut selected_texts: Vec<String> = selections
11644 .iter()
11645 .map(|selection| {
11646 buffer
11647 .text_for_range(selection.start..selection.end)
11648 .collect()
11649 })
11650 .collect();
11651
11652 if reverse {
11653 selected_texts.rotate_left(1);
11654 } else {
11655 selected_texts.rotate_right(1);
11656 }
11657
11658 let mut offset_delta: i64 = 0;
11659 let mut new_selections = Vec::new();
11660 let edits: Vec<_> = selections
11661 .iter()
11662 .zip(selected_texts.iter())
11663 .map(|(selection, new_text)| {
11664 let old_len = (selection.end.0 - selection.start.0) as i64;
11665 let new_len = new_text.len() as i64;
11666 let adjusted_start =
11667 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11668 let adjusted_end =
11669 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11670
11671 new_selections.push(Selection {
11672 id: selection.id,
11673 start: adjusted_start,
11674 end: adjusted_end,
11675 reversed: selection.reversed,
11676 goal: selection.goal,
11677 });
11678
11679 offset_delta += new_len - old_len;
11680 (selection.start..selection.end, new_text.clone())
11681 })
11682 .collect();
11683 (edits, new_selections)
11684 } else {
11685 let mut all_rows: Vec<u32> = selections
11686 .iter()
11687 .map(|selection| buffer.offset_to_point(selection.start).row)
11688 .collect();
11689 all_rows.sort_unstable();
11690 all_rows.dedup();
11691
11692 if all_rows.len() < 2 {
11693 return;
11694 }
11695
11696 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11697 .iter()
11698 .map(|&row| {
11699 let start = Point::new(row, 0);
11700 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11701 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11702 })
11703 .collect();
11704
11705 let mut line_texts: Vec<String> = line_ranges
11706 .iter()
11707 .map(|range| buffer.text_for_range(range.clone()).collect())
11708 .collect();
11709
11710 if reverse {
11711 line_texts.rotate_left(1);
11712 } else {
11713 line_texts.rotate_right(1);
11714 }
11715
11716 let edits = line_ranges
11717 .iter()
11718 .zip(line_texts.iter())
11719 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11720 .collect();
11721
11722 let num_rows = all_rows.len();
11723 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11724 .iter()
11725 .enumerate()
11726 .map(|(i, &row)| (row, i))
11727 .collect();
11728
11729 // Compute new line start offsets after rotation (handles CRLF)
11730 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11731 let first_line_start = line_ranges[0].start.0;
11732 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11733 for text in line_texts.iter().take(num_rows - 1) {
11734 let prev_start = *new_line_starts.last().unwrap();
11735 new_line_starts.push(prev_start + text.len() + newline_len);
11736 }
11737
11738 let new_selections = selections
11739 .iter()
11740 .map(|selection| {
11741 let point = buffer.offset_to_point(selection.start);
11742 let old_index = row_to_index[&point.row];
11743 let new_index = if reverse {
11744 (old_index + num_rows - 1) % num_rows
11745 } else {
11746 (old_index + 1) % num_rows
11747 };
11748 let new_offset =
11749 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11750 Selection {
11751 id: selection.id,
11752 start: new_offset,
11753 end: new_offset,
11754 reversed: selection.reversed,
11755 goal: selection.goal,
11756 }
11757 })
11758 .collect();
11759
11760 (edits, new_selections)
11761 }
11762 };
11763
11764 self.transact(window, cx, |this, window, cx| {
11765 this.buffer.update(cx, |buffer, cx| {
11766 buffer.edit(edits, None, cx);
11767 });
11768 this.change_selections(Default::default(), window, cx, |s| {
11769 s.select(new_selections);
11770 });
11771 });
11772 }
11773
11774 fn manipulate_lines<M>(
11775 &mut self,
11776 window: &mut Window,
11777 cx: &mut Context<Self>,
11778 mut manipulate: M,
11779 ) where
11780 M: FnMut(&str) -> LineManipulationResult,
11781 {
11782 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11783
11784 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11785 let buffer = self.buffer.read(cx).snapshot(cx);
11786
11787 let mut edits = Vec::new();
11788
11789 let selections = self.selections.all::<Point>(&display_map);
11790 let mut selections = selections.iter().peekable();
11791 let mut contiguous_row_selections = Vec::new();
11792 let mut new_selections = Vec::new();
11793 let mut added_lines = 0;
11794 let mut removed_lines = 0;
11795
11796 while let Some(selection) = selections.next() {
11797 let (start_row, end_row) = consume_contiguous_rows(
11798 &mut contiguous_row_selections,
11799 selection,
11800 &display_map,
11801 &mut selections,
11802 );
11803
11804 let start_point = Point::new(start_row.0, 0);
11805 let end_point = Point::new(
11806 end_row.previous_row().0,
11807 buffer.line_len(end_row.previous_row()),
11808 );
11809 let text = buffer
11810 .text_for_range(start_point..end_point)
11811 .collect::<String>();
11812
11813 let LineManipulationResult {
11814 new_text,
11815 line_count_before,
11816 line_count_after,
11817 } = manipulate(&text);
11818
11819 edits.push((start_point..end_point, new_text));
11820
11821 // Selections must change based on added and removed line count
11822 let start_row =
11823 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11824 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11825 new_selections.push(Selection {
11826 id: selection.id,
11827 start: start_row,
11828 end: end_row,
11829 goal: SelectionGoal::None,
11830 reversed: selection.reversed,
11831 });
11832
11833 if line_count_after > line_count_before {
11834 added_lines += line_count_after - line_count_before;
11835 } else if line_count_before > line_count_after {
11836 removed_lines += line_count_before - line_count_after;
11837 }
11838 }
11839
11840 self.transact(window, cx, |this, window, cx| {
11841 let buffer = this.buffer.update(cx, |buffer, cx| {
11842 buffer.edit(edits, None, cx);
11843 buffer.snapshot(cx)
11844 });
11845
11846 // Recalculate offsets on newly edited buffer
11847 let new_selections = new_selections
11848 .iter()
11849 .map(|s| {
11850 let start_point = Point::new(s.start.0, 0);
11851 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11852 Selection {
11853 id: s.id,
11854 start: buffer.point_to_offset(start_point),
11855 end: buffer.point_to_offset(end_point),
11856 goal: s.goal,
11857 reversed: s.reversed,
11858 }
11859 })
11860 .collect();
11861
11862 this.change_selections(Default::default(), window, cx, |s| {
11863 s.select(new_selections);
11864 });
11865
11866 this.request_autoscroll(Autoscroll::fit(), cx);
11867 });
11868 }
11869
11870 fn manipulate_immutable_lines<Fn>(
11871 &mut self,
11872 window: &mut Window,
11873 cx: &mut Context<Self>,
11874 mut callback: Fn,
11875 ) where
11876 Fn: FnMut(&mut Vec<&str>),
11877 {
11878 self.manipulate_lines(window, cx, |text| {
11879 let mut lines: Vec<&str> = text.split('\n').collect();
11880 let line_count_before = lines.len();
11881
11882 callback(&mut lines);
11883
11884 LineManipulationResult {
11885 new_text: lines.join("\n"),
11886 line_count_before,
11887 line_count_after: lines.len(),
11888 }
11889 });
11890 }
11891
11892 fn manipulate_mutable_lines<Fn>(
11893 &mut self,
11894 window: &mut Window,
11895 cx: &mut Context<Self>,
11896 mut callback: Fn,
11897 ) where
11898 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11899 {
11900 self.manipulate_lines(window, cx, |text| {
11901 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11902 let line_count_before = lines.len();
11903
11904 callback(&mut lines);
11905
11906 LineManipulationResult {
11907 new_text: lines.join("\n"),
11908 line_count_before,
11909 line_count_after: lines.len(),
11910 }
11911 });
11912 }
11913
11914 pub fn convert_indentation_to_spaces(
11915 &mut self,
11916 _: &ConvertIndentationToSpaces,
11917 window: &mut Window,
11918 cx: &mut Context<Self>,
11919 ) {
11920 let settings = self.buffer.read(cx).language_settings(cx);
11921 let tab_size = settings.tab_size.get() as usize;
11922
11923 self.manipulate_mutable_lines(window, cx, |lines| {
11924 // Allocates a reasonably sized scratch buffer once for the whole loop
11925 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11926 // Avoids recomputing spaces that could be inserted many times
11927 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11928 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11929 .collect();
11930
11931 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11932 let mut chars = line.as_ref().chars();
11933 let mut col = 0;
11934 let mut changed = false;
11935
11936 for ch in chars.by_ref() {
11937 match ch {
11938 ' ' => {
11939 reindented_line.push(' ');
11940 col += 1;
11941 }
11942 '\t' => {
11943 // \t are converted to spaces depending on the current column
11944 let spaces_len = tab_size - (col % tab_size);
11945 reindented_line.extend(&space_cache[spaces_len - 1]);
11946 col += spaces_len;
11947 changed = true;
11948 }
11949 _ => {
11950 // If we dont append before break, the character is consumed
11951 reindented_line.push(ch);
11952 break;
11953 }
11954 }
11955 }
11956
11957 if !changed {
11958 reindented_line.clear();
11959 continue;
11960 }
11961 // Append the rest of the line and replace old reference with new one
11962 reindented_line.extend(chars);
11963 *line = Cow::Owned(reindented_line.clone());
11964 reindented_line.clear();
11965 }
11966 });
11967 }
11968
11969 pub fn convert_indentation_to_tabs(
11970 &mut self,
11971 _: &ConvertIndentationToTabs,
11972 window: &mut Window,
11973 cx: &mut Context<Self>,
11974 ) {
11975 let settings = self.buffer.read(cx).language_settings(cx);
11976 let tab_size = settings.tab_size.get() as usize;
11977
11978 self.manipulate_mutable_lines(window, cx, |lines| {
11979 // Allocates a reasonably sized buffer once for the whole loop
11980 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11981 // Avoids recomputing spaces that could be inserted many times
11982 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11983 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11984 .collect();
11985
11986 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11987 let mut chars = line.chars();
11988 let mut spaces_count = 0;
11989 let mut first_non_indent_char = None;
11990 let mut changed = false;
11991
11992 for ch in chars.by_ref() {
11993 match ch {
11994 ' ' => {
11995 // Keep track of spaces. Append \t when we reach tab_size
11996 spaces_count += 1;
11997 changed = true;
11998 if spaces_count == tab_size {
11999 reindented_line.push('\t');
12000 spaces_count = 0;
12001 }
12002 }
12003 '\t' => {
12004 reindented_line.push('\t');
12005 spaces_count = 0;
12006 }
12007 _ => {
12008 // Dont append it yet, we might have remaining spaces
12009 first_non_indent_char = Some(ch);
12010 break;
12011 }
12012 }
12013 }
12014
12015 if !changed {
12016 reindented_line.clear();
12017 continue;
12018 }
12019 // Remaining spaces that didn't make a full tab stop
12020 if spaces_count > 0 {
12021 reindented_line.extend(&space_cache[spaces_count - 1]);
12022 }
12023 // If we consume an extra character that was not indentation, add it back
12024 if let Some(extra_char) = first_non_indent_char {
12025 reindented_line.push(extra_char);
12026 }
12027 // Append the rest of the line and replace old reference with new one
12028 reindented_line.extend(chars);
12029 *line = Cow::Owned(reindented_line.clone());
12030 reindented_line.clear();
12031 }
12032 });
12033 }
12034
12035 pub fn convert_to_upper_case(
12036 &mut self,
12037 _: &ConvertToUpperCase,
12038 window: &mut Window,
12039 cx: &mut Context<Self>,
12040 ) {
12041 self.manipulate_text(window, cx, |text| text.to_uppercase())
12042 }
12043
12044 pub fn convert_to_lower_case(
12045 &mut self,
12046 _: &ConvertToLowerCase,
12047 window: &mut Window,
12048 cx: &mut Context<Self>,
12049 ) {
12050 self.manipulate_text(window, cx, |text| text.to_lowercase())
12051 }
12052
12053 pub fn convert_to_title_case(
12054 &mut self,
12055 _: &ConvertToTitleCase,
12056 window: &mut Window,
12057 cx: &mut Context<Self>,
12058 ) {
12059 self.manipulate_text(window, cx, |text| {
12060 text.split('\n')
12061 .map(|line| line.to_case(Case::Title))
12062 .join("\n")
12063 })
12064 }
12065
12066 pub fn convert_to_snake_case(
12067 &mut self,
12068 _: &ConvertToSnakeCase,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12073 }
12074
12075 pub fn convert_to_kebab_case(
12076 &mut self,
12077 _: &ConvertToKebabCase,
12078 window: &mut Window,
12079 cx: &mut Context<Self>,
12080 ) {
12081 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12082 }
12083
12084 pub fn convert_to_upper_camel_case(
12085 &mut self,
12086 _: &ConvertToUpperCamelCase,
12087 window: &mut Window,
12088 cx: &mut Context<Self>,
12089 ) {
12090 self.manipulate_text(window, cx, |text| {
12091 text.split('\n')
12092 .map(|line| line.to_case(Case::UpperCamel))
12093 .join("\n")
12094 })
12095 }
12096
12097 pub fn convert_to_lower_camel_case(
12098 &mut self,
12099 _: &ConvertToLowerCamelCase,
12100 window: &mut Window,
12101 cx: &mut Context<Self>,
12102 ) {
12103 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12104 }
12105
12106 pub fn convert_to_opposite_case(
12107 &mut self,
12108 _: &ConvertToOppositeCase,
12109 window: &mut Window,
12110 cx: &mut Context<Self>,
12111 ) {
12112 self.manipulate_text(window, cx, |text| {
12113 text.chars()
12114 .fold(String::with_capacity(text.len()), |mut t, c| {
12115 if c.is_uppercase() {
12116 t.extend(c.to_lowercase());
12117 } else {
12118 t.extend(c.to_uppercase());
12119 }
12120 t
12121 })
12122 })
12123 }
12124
12125 pub fn convert_to_sentence_case(
12126 &mut self,
12127 _: &ConvertToSentenceCase,
12128 window: &mut Window,
12129 cx: &mut Context<Self>,
12130 ) {
12131 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12132 }
12133
12134 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12135 self.manipulate_text(window, cx, |text| {
12136 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12137 if has_upper_case_characters {
12138 text.to_lowercase()
12139 } else {
12140 text.to_uppercase()
12141 }
12142 })
12143 }
12144
12145 pub fn convert_to_rot13(
12146 &mut self,
12147 _: &ConvertToRot13,
12148 window: &mut Window,
12149 cx: &mut Context<Self>,
12150 ) {
12151 self.manipulate_text(window, cx, |text| {
12152 text.chars()
12153 .map(|c| match c {
12154 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12155 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12156 _ => c,
12157 })
12158 .collect()
12159 })
12160 }
12161
12162 pub fn convert_to_rot47(
12163 &mut self,
12164 _: &ConvertToRot47,
12165 window: &mut Window,
12166 cx: &mut Context<Self>,
12167 ) {
12168 self.manipulate_text(window, cx, |text| {
12169 text.chars()
12170 .map(|c| {
12171 let code_point = c as u32;
12172 if code_point >= 33 && code_point <= 126 {
12173 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12174 }
12175 c
12176 })
12177 .collect()
12178 })
12179 }
12180
12181 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12182 where
12183 Fn: FnMut(&str) -> String,
12184 {
12185 let buffer = self.buffer.read(cx).snapshot(cx);
12186
12187 let mut new_selections = Vec::new();
12188 let mut edits = Vec::new();
12189 let mut selection_adjustment = 0isize;
12190
12191 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12192 let selection_is_empty = selection.is_empty();
12193
12194 let (start, end) = if selection_is_empty {
12195 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12196 (word_range.start, word_range.end)
12197 } else {
12198 (
12199 buffer.point_to_offset(selection.start),
12200 buffer.point_to_offset(selection.end),
12201 )
12202 };
12203
12204 let text = buffer.text_for_range(start..end).collect::<String>();
12205 let old_length = text.len() as isize;
12206 let text = callback(&text);
12207
12208 new_selections.push(Selection {
12209 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12210 end: MultiBufferOffset(
12211 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12212 ),
12213 goal: SelectionGoal::None,
12214 id: selection.id,
12215 reversed: selection.reversed,
12216 });
12217
12218 selection_adjustment += old_length - text.len() as isize;
12219
12220 edits.push((start..end, text));
12221 }
12222
12223 self.transact(window, cx, |this, window, cx| {
12224 this.buffer.update(cx, |buffer, cx| {
12225 buffer.edit(edits, None, cx);
12226 });
12227
12228 this.change_selections(Default::default(), window, cx, |s| {
12229 s.select(new_selections);
12230 });
12231
12232 this.request_autoscroll(Autoscroll::fit(), cx);
12233 });
12234 }
12235
12236 pub fn move_selection_on_drop(
12237 &mut self,
12238 selection: &Selection<Anchor>,
12239 target: DisplayPoint,
12240 is_cut: bool,
12241 window: &mut Window,
12242 cx: &mut Context<Self>,
12243 ) {
12244 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12245 let buffer = display_map.buffer_snapshot();
12246 let mut edits = Vec::new();
12247 let insert_point = display_map
12248 .clip_point(target, Bias::Left)
12249 .to_point(&display_map);
12250 let text = buffer
12251 .text_for_range(selection.start..selection.end)
12252 .collect::<String>();
12253 if is_cut {
12254 edits.push(((selection.start..selection.end), String::new()));
12255 }
12256 let insert_anchor = buffer.anchor_before(insert_point);
12257 edits.push(((insert_anchor..insert_anchor), text));
12258 let last_edit_start = insert_anchor.bias_left(buffer);
12259 let last_edit_end = insert_anchor.bias_right(buffer);
12260 self.transact(window, cx, |this, window, cx| {
12261 this.buffer.update(cx, |buffer, cx| {
12262 buffer.edit(edits, None, cx);
12263 });
12264 this.change_selections(Default::default(), window, cx, |s| {
12265 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12266 });
12267 });
12268 }
12269
12270 pub fn clear_selection_drag_state(&mut self) {
12271 self.selection_drag_state = SelectionDragState::None;
12272 }
12273
12274 pub fn duplicate(
12275 &mut self,
12276 upwards: bool,
12277 whole_lines: bool,
12278 window: &mut Window,
12279 cx: &mut Context<Self>,
12280 ) {
12281 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12282
12283 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12284 let buffer = display_map.buffer_snapshot();
12285 let selections = self.selections.all::<Point>(&display_map);
12286
12287 let mut edits = Vec::new();
12288 let mut selections_iter = selections.iter().peekable();
12289 while let Some(selection) = selections_iter.next() {
12290 let mut rows = selection.spanned_rows(false, &display_map);
12291 // duplicate line-wise
12292 if whole_lines || selection.start == selection.end {
12293 // Avoid duplicating the same lines twice.
12294 while let Some(next_selection) = selections_iter.peek() {
12295 let next_rows = next_selection.spanned_rows(false, &display_map);
12296 if next_rows.start < rows.end {
12297 rows.end = next_rows.end;
12298 selections_iter.next().unwrap();
12299 } else {
12300 break;
12301 }
12302 }
12303
12304 // Copy the text from the selected row region and splice it either at the start
12305 // or end of the region.
12306 let start = Point::new(rows.start.0, 0);
12307 let end = Point::new(
12308 rows.end.previous_row().0,
12309 buffer.line_len(rows.end.previous_row()),
12310 );
12311
12312 let mut text = buffer.text_for_range(start..end).collect::<String>();
12313
12314 let insert_location = if upwards {
12315 // When duplicating upward, we need to insert before the current line.
12316 // If we're on the last line and it doesn't end with a newline,
12317 // we need to add a newline before the duplicated content.
12318 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12319 && buffer.max_point().column > 0
12320 && !text.ends_with('\n');
12321
12322 if needs_leading_newline {
12323 text.insert(0, '\n');
12324 end
12325 } else {
12326 text.push('\n');
12327 Point::new(rows.start.0, 0)
12328 }
12329 } else {
12330 text.push('\n');
12331 start
12332 };
12333 edits.push((insert_location..insert_location, text));
12334 } else {
12335 // duplicate character-wise
12336 let start = selection.start;
12337 let end = selection.end;
12338 let text = buffer.text_for_range(start..end).collect::<String>();
12339 edits.push((selection.end..selection.end, text));
12340 }
12341 }
12342
12343 self.transact(window, cx, |this, window, cx| {
12344 this.buffer.update(cx, |buffer, cx| {
12345 buffer.edit(edits, None, cx);
12346 });
12347
12348 // When duplicating upward with whole lines, move the cursor to the duplicated line
12349 if upwards && whole_lines {
12350 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12351
12352 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12353 let mut new_ranges = Vec::new();
12354 let selections = s.all::<Point>(&display_map);
12355 let mut selections_iter = selections.iter().peekable();
12356
12357 while let Some(first_selection) = selections_iter.next() {
12358 // Group contiguous selections together to find the total row span
12359 let mut group_selections = vec![first_selection];
12360 let mut rows = first_selection.spanned_rows(false, &display_map);
12361
12362 while let Some(next_selection) = selections_iter.peek() {
12363 let next_rows = next_selection.spanned_rows(false, &display_map);
12364 if next_rows.start < rows.end {
12365 rows.end = next_rows.end;
12366 group_selections.push(selections_iter.next().unwrap());
12367 } else {
12368 break;
12369 }
12370 }
12371
12372 let row_count = rows.end.0 - rows.start.0;
12373
12374 // Move all selections in this group up by the total number of duplicated rows
12375 for selection in group_selections {
12376 let new_start = Point::new(
12377 selection.start.row.saturating_sub(row_count),
12378 selection.start.column,
12379 );
12380
12381 let new_end = Point::new(
12382 selection.end.row.saturating_sub(row_count),
12383 selection.end.column,
12384 );
12385
12386 new_ranges.push(new_start..new_end);
12387 }
12388 }
12389
12390 s.select_ranges(new_ranges);
12391 });
12392 }
12393
12394 this.request_autoscroll(Autoscroll::fit(), cx);
12395 });
12396 }
12397
12398 pub fn duplicate_line_up(
12399 &mut self,
12400 _: &DuplicateLineUp,
12401 window: &mut Window,
12402 cx: &mut Context<Self>,
12403 ) {
12404 self.duplicate(true, true, window, cx);
12405 }
12406
12407 pub fn duplicate_line_down(
12408 &mut self,
12409 _: &DuplicateLineDown,
12410 window: &mut Window,
12411 cx: &mut Context<Self>,
12412 ) {
12413 self.duplicate(false, true, window, cx);
12414 }
12415
12416 pub fn duplicate_selection(
12417 &mut self,
12418 _: &DuplicateSelection,
12419 window: &mut Window,
12420 cx: &mut Context<Self>,
12421 ) {
12422 self.duplicate(false, false, window, cx);
12423 }
12424
12425 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12426 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12427 if self.mode.is_single_line() {
12428 cx.propagate();
12429 return;
12430 }
12431
12432 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12433 let buffer = self.buffer.read(cx).snapshot(cx);
12434
12435 let mut edits = Vec::new();
12436 let mut unfold_ranges = Vec::new();
12437 let mut refold_creases = Vec::new();
12438
12439 let selections = self.selections.all::<Point>(&display_map);
12440 let mut selections = selections.iter().peekable();
12441 let mut contiguous_row_selections = Vec::new();
12442 let mut new_selections = Vec::new();
12443
12444 while let Some(selection) = selections.next() {
12445 // Find all the selections that span a contiguous row range
12446 let (start_row, end_row) = consume_contiguous_rows(
12447 &mut contiguous_row_selections,
12448 selection,
12449 &display_map,
12450 &mut selections,
12451 );
12452
12453 // Move the text spanned by the row range to be before the line preceding the row range
12454 if start_row.0 > 0 {
12455 let range_to_move = Point::new(
12456 start_row.previous_row().0,
12457 buffer.line_len(start_row.previous_row()),
12458 )
12459 ..Point::new(
12460 end_row.previous_row().0,
12461 buffer.line_len(end_row.previous_row()),
12462 );
12463 let insertion_point = display_map
12464 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12465 .0;
12466
12467 // Don't move lines across excerpts
12468 if buffer
12469 .excerpt_containing(insertion_point..range_to_move.end)
12470 .is_some()
12471 {
12472 let text = buffer
12473 .text_for_range(range_to_move.clone())
12474 .flat_map(|s| s.chars())
12475 .skip(1)
12476 .chain(['\n'])
12477 .collect::<String>();
12478
12479 edits.push((
12480 buffer.anchor_after(range_to_move.start)
12481 ..buffer.anchor_before(range_to_move.end),
12482 String::new(),
12483 ));
12484 let insertion_anchor = buffer.anchor_after(insertion_point);
12485 edits.push((insertion_anchor..insertion_anchor, text));
12486
12487 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12488
12489 // Move selections up
12490 new_selections.extend(contiguous_row_selections.drain(..).map(
12491 |mut selection| {
12492 selection.start.row -= row_delta;
12493 selection.end.row -= row_delta;
12494 selection
12495 },
12496 ));
12497
12498 // Move folds up
12499 unfold_ranges.push(range_to_move.clone());
12500 for fold in display_map.folds_in_range(
12501 buffer.anchor_before(range_to_move.start)
12502 ..buffer.anchor_after(range_to_move.end),
12503 ) {
12504 let mut start = fold.range.start.to_point(&buffer);
12505 let mut end = fold.range.end.to_point(&buffer);
12506 start.row -= row_delta;
12507 end.row -= row_delta;
12508 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12509 }
12510 }
12511 }
12512
12513 // If we didn't move line(s), preserve the existing selections
12514 new_selections.append(&mut contiguous_row_selections);
12515 }
12516
12517 self.transact(window, cx, |this, window, cx| {
12518 this.unfold_ranges(&unfold_ranges, true, true, cx);
12519 this.buffer.update(cx, |buffer, cx| {
12520 for (range, text) in edits {
12521 buffer.edit([(range, text)], None, cx);
12522 }
12523 });
12524 this.fold_creases(refold_creases, true, window, cx);
12525 this.change_selections(Default::default(), window, cx, |s| {
12526 s.select(new_selections);
12527 })
12528 });
12529 }
12530
12531 pub fn move_line_down(
12532 &mut self,
12533 _: &MoveLineDown,
12534 window: &mut Window,
12535 cx: &mut Context<Self>,
12536 ) {
12537 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12538 if self.mode.is_single_line() {
12539 cx.propagate();
12540 return;
12541 }
12542
12543 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12544 let buffer = self.buffer.read(cx).snapshot(cx);
12545
12546 let mut edits = Vec::new();
12547 let mut unfold_ranges = Vec::new();
12548 let mut refold_creases = Vec::new();
12549
12550 let selections = self.selections.all::<Point>(&display_map);
12551 let mut selections = selections.iter().peekable();
12552 let mut contiguous_row_selections = Vec::new();
12553 let mut new_selections = Vec::new();
12554
12555 while let Some(selection) = selections.next() {
12556 // Find all the selections that span a contiguous row range
12557 let (start_row, end_row) = consume_contiguous_rows(
12558 &mut contiguous_row_selections,
12559 selection,
12560 &display_map,
12561 &mut selections,
12562 );
12563
12564 // Move the text spanned by the row range to be after the last line of the row range
12565 if end_row.0 <= buffer.max_point().row {
12566 let range_to_move =
12567 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12568 let insertion_point = display_map
12569 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12570 .0;
12571
12572 // Don't move lines across excerpt boundaries
12573 if buffer
12574 .excerpt_containing(range_to_move.start..insertion_point)
12575 .is_some()
12576 {
12577 let mut text = String::from("\n");
12578 text.extend(buffer.text_for_range(range_to_move.clone()));
12579 text.pop(); // Drop trailing newline
12580 edits.push((
12581 buffer.anchor_after(range_to_move.start)
12582 ..buffer.anchor_before(range_to_move.end),
12583 String::new(),
12584 ));
12585 let insertion_anchor = buffer.anchor_after(insertion_point);
12586 edits.push((insertion_anchor..insertion_anchor, text));
12587
12588 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12589
12590 // Move selections down
12591 new_selections.extend(contiguous_row_selections.drain(..).map(
12592 |mut selection| {
12593 selection.start.row += row_delta;
12594 selection.end.row += row_delta;
12595 selection
12596 },
12597 ));
12598
12599 // Move folds down
12600 unfold_ranges.push(range_to_move.clone());
12601 for fold in display_map.folds_in_range(
12602 buffer.anchor_before(range_to_move.start)
12603 ..buffer.anchor_after(range_to_move.end),
12604 ) {
12605 let mut start = fold.range.start.to_point(&buffer);
12606 let mut end = fold.range.end.to_point(&buffer);
12607 start.row += row_delta;
12608 end.row += row_delta;
12609 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12610 }
12611 }
12612 }
12613
12614 // If we didn't move line(s), preserve the existing selections
12615 new_selections.append(&mut contiguous_row_selections);
12616 }
12617
12618 self.transact(window, cx, |this, window, cx| {
12619 this.unfold_ranges(&unfold_ranges, true, true, cx);
12620 this.buffer.update(cx, |buffer, cx| {
12621 for (range, text) in edits {
12622 buffer.edit([(range, text)], None, cx);
12623 }
12624 });
12625 this.fold_creases(refold_creases, true, window, cx);
12626 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12627 });
12628 }
12629
12630 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12631 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12632 let text_layout_details = &self.text_layout_details(window);
12633 self.transact(window, cx, |this, window, cx| {
12634 let edits = this.change_selections(Default::default(), window, cx, |s| {
12635 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12636 s.move_with(|display_map, selection| {
12637 if !selection.is_empty() {
12638 return;
12639 }
12640
12641 let mut head = selection.head();
12642 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12643 if head.column() == display_map.line_len(head.row()) {
12644 transpose_offset = display_map
12645 .buffer_snapshot()
12646 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12647 }
12648
12649 if transpose_offset == MultiBufferOffset(0) {
12650 return;
12651 }
12652
12653 *head.column_mut() += 1;
12654 head = display_map.clip_point(head, Bias::Right);
12655 let goal = SelectionGoal::HorizontalPosition(
12656 display_map
12657 .x_for_display_point(head, text_layout_details)
12658 .into(),
12659 );
12660 selection.collapse_to(head, goal);
12661
12662 let transpose_start = display_map
12663 .buffer_snapshot()
12664 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12665 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12666 let transpose_end = display_map
12667 .buffer_snapshot()
12668 .clip_offset(transpose_offset + 1usize, Bias::Right);
12669 if let Some(ch) = display_map
12670 .buffer_snapshot()
12671 .chars_at(transpose_start)
12672 .next()
12673 {
12674 edits.push((transpose_start..transpose_offset, String::new()));
12675 edits.push((transpose_end..transpose_end, ch.to_string()));
12676 }
12677 }
12678 });
12679 edits
12680 });
12681 this.buffer
12682 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12683 let selections = this
12684 .selections
12685 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12686 this.change_selections(Default::default(), window, cx, |s| {
12687 s.select(selections);
12688 });
12689 });
12690 }
12691
12692 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12693 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12694 if self.mode.is_single_line() {
12695 cx.propagate();
12696 return;
12697 }
12698
12699 self.rewrap_impl(RewrapOptions::default(), cx)
12700 }
12701
12702 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12703 let buffer = self.buffer.read(cx).snapshot(cx);
12704 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12705
12706 #[derive(Clone, Debug, PartialEq)]
12707 enum CommentFormat {
12708 /// single line comment, with prefix for line
12709 Line(String),
12710 /// single line within a block comment, with prefix for line
12711 BlockLine(String),
12712 /// a single line of a block comment that includes the initial delimiter
12713 BlockCommentWithStart(BlockCommentConfig),
12714 /// a single line of a block comment that includes the ending delimiter
12715 BlockCommentWithEnd(BlockCommentConfig),
12716 }
12717
12718 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12719 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12720 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12721 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12722 .peekable();
12723
12724 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12725 row
12726 } else {
12727 return Vec::new();
12728 };
12729
12730 let language_settings = buffer.language_settings_at(selection.head(), cx);
12731 let language_scope = buffer.language_scope_at(selection.head());
12732
12733 let indent_and_prefix_for_row =
12734 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12735 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12736 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12737 &language_scope
12738 {
12739 let indent_end = Point::new(row, indent.len);
12740 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12741 let line_text_after_indent = buffer
12742 .text_for_range(indent_end..line_end)
12743 .collect::<String>();
12744
12745 let is_within_comment_override = buffer
12746 .language_scope_at(indent_end)
12747 .is_some_and(|scope| scope.override_name() == Some("comment"));
12748 let comment_delimiters = if is_within_comment_override {
12749 // we are within a comment syntax node, but we don't
12750 // yet know what kind of comment: block, doc or line
12751 match (
12752 language_scope.documentation_comment(),
12753 language_scope.block_comment(),
12754 ) {
12755 (Some(config), _) | (_, Some(config))
12756 if buffer.contains_str_at(indent_end, &config.start) =>
12757 {
12758 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12759 }
12760 (Some(config), _) | (_, Some(config))
12761 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12762 {
12763 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12764 }
12765 (Some(config), _) | (_, Some(config))
12766 if buffer.contains_str_at(indent_end, &config.prefix) =>
12767 {
12768 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12769 }
12770 (_, _) => language_scope
12771 .line_comment_prefixes()
12772 .iter()
12773 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12774 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12775 }
12776 } else {
12777 // we not in an overridden comment node, but we may
12778 // be within a non-overridden line comment node
12779 language_scope
12780 .line_comment_prefixes()
12781 .iter()
12782 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12783 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12784 };
12785
12786 let rewrap_prefix = language_scope
12787 .rewrap_prefixes()
12788 .iter()
12789 .find_map(|prefix_regex| {
12790 prefix_regex.find(&line_text_after_indent).map(|mat| {
12791 if mat.start() == 0 {
12792 Some(mat.as_str().to_string())
12793 } else {
12794 None
12795 }
12796 })
12797 })
12798 .flatten();
12799 (comment_delimiters, rewrap_prefix)
12800 } else {
12801 (None, None)
12802 };
12803 (indent, comment_prefix, rewrap_prefix)
12804 };
12805
12806 let mut ranges = Vec::new();
12807 let from_empty_selection = selection.is_empty();
12808
12809 let mut current_range_start = first_row;
12810 let mut prev_row = first_row;
12811 let (
12812 mut current_range_indent,
12813 mut current_range_comment_delimiters,
12814 mut current_range_rewrap_prefix,
12815 ) = indent_and_prefix_for_row(first_row);
12816
12817 for row in non_blank_rows_iter.skip(1) {
12818 let has_paragraph_break = row > prev_row + 1;
12819
12820 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12821 indent_and_prefix_for_row(row);
12822
12823 let has_indent_change = row_indent != current_range_indent;
12824 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12825
12826 let has_boundary_change = has_comment_change
12827 || row_rewrap_prefix.is_some()
12828 || (has_indent_change && current_range_comment_delimiters.is_some());
12829
12830 if has_paragraph_break || has_boundary_change {
12831 ranges.push((
12832 language_settings.clone(),
12833 Point::new(current_range_start, 0)
12834 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12835 current_range_indent,
12836 current_range_comment_delimiters.clone(),
12837 current_range_rewrap_prefix.clone(),
12838 from_empty_selection,
12839 ));
12840 current_range_start = row;
12841 current_range_indent = row_indent;
12842 current_range_comment_delimiters = row_comment_delimiters;
12843 current_range_rewrap_prefix = row_rewrap_prefix;
12844 }
12845 prev_row = row;
12846 }
12847
12848 ranges.push((
12849 language_settings.clone(),
12850 Point::new(current_range_start, 0)
12851 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12852 current_range_indent,
12853 current_range_comment_delimiters,
12854 current_range_rewrap_prefix,
12855 from_empty_selection,
12856 ));
12857
12858 ranges
12859 });
12860
12861 let mut edits = Vec::new();
12862 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12863
12864 for (
12865 language_settings,
12866 wrap_range,
12867 mut indent_size,
12868 comment_prefix,
12869 rewrap_prefix,
12870 from_empty_selection,
12871 ) in wrap_ranges
12872 {
12873 let mut start_row = wrap_range.start.row;
12874 let mut end_row = wrap_range.end.row;
12875
12876 // Skip selections that overlap with a range that has already been rewrapped.
12877 let selection_range = start_row..end_row;
12878 if rewrapped_row_ranges
12879 .iter()
12880 .any(|range| range.overlaps(&selection_range))
12881 {
12882 continue;
12883 }
12884
12885 let tab_size = language_settings.tab_size;
12886
12887 let (line_prefix, inside_comment) = match &comment_prefix {
12888 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12889 (Some(prefix.as_str()), true)
12890 }
12891 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12892 (Some(prefix.as_ref()), true)
12893 }
12894 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12895 start: _,
12896 end: _,
12897 prefix,
12898 tab_size,
12899 })) => {
12900 indent_size.len += tab_size;
12901 (Some(prefix.as_ref()), true)
12902 }
12903 None => (None, false),
12904 };
12905 let indent_prefix = indent_size.chars().collect::<String>();
12906 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12907
12908 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12909 RewrapBehavior::InComments => inside_comment,
12910 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12911 RewrapBehavior::Anywhere => true,
12912 };
12913
12914 let should_rewrap = options.override_language_settings
12915 || allow_rewrap_based_on_language
12916 || self.hard_wrap.is_some();
12917 if !should_rewrap {
12918 continue;
12919 }
12920
12921 if from_empty_selection {
12922 'expand_upwards: while start_row > 0 {
12923 let prev_row = start_row - 1;
12924 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12925 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12926 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12927 {
12928 start_row = prev_row;
12929 } else {
12930 break 'expand_upwards;
12931 }
12932 }
12933
12934 'expand_downwards: while end_row < buffer.max_point().row {
12935 let next_row = end_row + 1;
12936 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12937 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12938 && !buffer.is_line_blank(MultiBufferRow(next_row))
12939 {
12940 end_row = next_row;
12941 } else {
12942 break 'expand_downwards;
12943 }
12944 }
12945 }
12946
12947 let start = Point::new(start_row, 0);
12948 let start_offset = ToOffset::to_offset(&start, &buffer);
12949 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12950 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12951 let mut first_line_delimiter = None;
12952 let mut last_line_delimiter = None;
12953 let Some(lines_without_prefixes) = selection_text
12954 .lines()
12955 .enumerate()
12956 .map(|(ix, line)| {
12957 let line_trimmed = line.trim_start();
12958 if rewrap_prefix.is_some() && ix > 0 {
12959 Ok(line_trimmed)
12960 } else if let Some(
12961 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12962 start,
12963 prefix,
12964 end,
12965 tab_size,
12966 })
12967 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12968 start,
12969 prefix,
12970 end,
12971 tab_size,
12972 }),
12973 ) = &comment_prefix
12974 {
12975 let line_trimmed = line_trimmed
12976 .strip_prefix(start.as_ref())
12977 .map(|s| {
12978 let mut indent_size = indent_size;
12979 indent_size.len -= tab_size;
12980 let indent_prefix: String = indent_size.chars().collect();
12981 first_line_delimiter = Some((indent_prefix, start));
12982 s.trim_start()
12983 })
12984 .unwrap_or(line_trimmed);
12985 let line_trimmed = line_trimmed
12986 .strip_suffix(end.as_ref())
12987 .map(|s| {
12988 last_line_delimiter = Some(end);
12989 s.trim_end()
12990 })
12991 .unwrap_or(line_trimmed);
12992 let line_trimmed = line_trimmed
12993 .strip_prefix(prefix.as_ref())
12994 .unwrap_or(line_trimmed);
12995 Ok(line_trimmed)
12996 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12997 line_trimmed.strip_prefix(prefix).with_context(|| {
12998 format!("line did not start with prefix {prefix:?}: {line:?}")
12999 })
13000 } else {
13001 line_trimmed
13002 .strip_prefix(&line_prefix.trim_start())
13003 .with_context(|| {
13004 format!("line did not start with prefix {line_prefix:?}: {line:?}")
13005 })
13006 }
13007 })
13008 .collect::<Result<Vec<_>, _>>()
13009 .log_err()
13010 else {
13011 continue;
13012 };
13013
13014 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
13015 buffer
13016 .language_settings_at(Point::new(start_row, 0), cx)
13017 .preferred_line_length as usize
13018 });
13019
13020 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
13021 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
13022 } else {
13023 line_prefix.clone()
13024 };
13025
13026 let wrapped_text = {
13027 let mut wrapped_text = wrap_with_prefix(
13028 line_prefix,
13029 subsequent_lines_prefix,
13030 lines_without_prefixes.join("\n"),
13031 wrap_column,
13032 tab_size,
13033 options.preserve_existing_whitespace,
13034 );
13035
13036 if let Some((indent, delimiter)) = first_line_delimiter {
13037 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
13038 }
13039 if let Some(last_line) = last_line_delimiter {
13040 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13041 }
13042
13043 wrapped_text
13044 };
13045
13046 // TODO: should always use char-based diff while still supporting cursor behavior that
13047 // matches vim.
13048 let mut diff_options = DiffOptions::default();
13049 if options.override_language_settings {
13050 diff_options.max_word_diff_len = 0;
13051 diff_options.max_word_diff_line_count = 0;
13052 } else {
13053 diff_options.max_word_diff_len = usize::MAX;
13054 diff_options.max_word_diff_line_count = usize::MAX;
13055 }
13056
13057 for (old_range, new_text) in
13058 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13059 {
13060 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13061 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13062 edits.push((edit_start..edit_end, new_text));
13063 }
13064
13065 rewrapped_row_ranges.push(start_row..=end_row);
13066 }
13067
13068 self.buffer
13069 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13070 }
13071
13072 pub fn cut_common(
13073 &mut self,
13074 cut_no_selection_line: bool,
13075 window: &mut Window,
13076 cx: &mut Context<Self>,
13077 ) -> ClipboardItem {
13078 let mut text = String::new();
13079 let buffer = self.buffer.read(cx).snapshot(cx);
13080 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13081 let mut clipboard_selections = Vec::with_capacity(selections.len());
13082 {
13083 let max_point = buffer.max_point();
13084 let mut is_first = true;
13085 let mut prev_selection_was_entire_line = false;
13086 for selection in &mut selections {
13087 let is_entire_line =
13088 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13089 if is_entire_line {
13090 selection.start = Point::new(selection.start.row, 0);
13091 if !selection.is_empty() && selection.end.column == 0 {
13092 selection.end = cmp::min(max_point, selection.end);
13093 } else {
13094 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13095 }
13096 selection.goal = SelectionGoal::None;
13097 }
13098 if is_first {
13099 is_first = false;
13100 } else if !prev_selection_was_entire_line {
13101 text += "\n";
13102 }
13103 prev_selection_was_entire_line = is_entire_line;
13104 let mut len = 0;
13105 for chunk in buffer.text_for_range(selection.start..selection.end) {
13106 text.push_str(chunk);
13107 len += chunk.len();
13108 }
13109
13110 clipboard_selections.push(ClipboardSelection::for_buffer(
13111 len,
13112 is_entire_line,
13113 selection.range(),
13114 &buffer,
13115 self.project.as_ref(),
13116 cx,
13117 ));
13118 }
13119 }
13120
13121 self.transact(window, cx, |this, window, cx| {
13122 this.change_selections(Default::default(), window, cx, |s| {
13123 s.select(selections);
13124 });
13125 this.insert("", window, cx);
13126 });
13127 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13128 }
13129
13130 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13131 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13132 let item = self.cut_common(true, window, cx);
13133 cx.write_to_clipboard(item);
13134 }
13135
13136 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13137 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13138 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13139 s.move_with(|snapshot, sel| {
13140 if sel.is_empty() {
13141 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13142 }
13143 if sel.is_empty() {
13144 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13145 }
13146 });
13147 });
13148 let item = self.cut_common(false, window, cx);
13149 cx.set_global(KillRing(item))
13150 }
13151
13152 pub fn kill_ring_yank(
13153 &mut self,
13154 _: &KillRingYank,
13155 window: &mut Window,
13156 cx: &mut Context<Self>,
13157 ) {
13158 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13159 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13160 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13161 (kill_ring.text().to_string(), kill_ring.metadata_json())
13162 } else {
13163 return;
13164 }
13165 } else {
13166 return;
13167 };
13168 self.do_paste(&text, metadata, false, window, cx);
13169 }
13170
13171 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13172 self.do_copy(true, cx);
13173 }
13174
13175 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13176 self.do_copy(false, cx);
13177 }
13178
13179 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13180 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13181 let buffer = self.buffer.read(cx).read(cx);
13182 let mut text = String::new();
13183
13184 let mut clipboard_selections = Vec::with_capacity(selections.len());
13185 {
13186 let max_point = buffer.max_point();
13187 let mut is_first = true;
13188 let mut prev_selection_was_entire_line = false;
13189 for selection in &selections {
13190 let mut start = selection.start;
13191 let mut end = selection.end;
13192 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13193 let mut add_trailing_newline = false;
13194 if is_entire_line {
13195 start = Point::new(start.row, 0);
13196 let next_line_start = Point::new(end.row + 1, 0);
13197 if next_line_start <= max_point {
13198 end = next_line_start;
13199 } else {
13200 // We're on the last line without a trailing newline.
13201 // Copy to the end of the line and add a newline afterwards.
13202 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13203 add_trailing_newline = true;
13204 }
13205 }
13206
13207 let mut trimmed_selections = Vec::new();
13208 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13209 let row = MultiBufferRow(start.row);
13210 let first_indent = buffer.indent_size_for_line(row);
13211 if first_indent.len == 0 || start.column > first_indent.len {
13212 trimmed_selections.push(start..end);
13213 } else {
13214 trimmed_selections.push(
13215 Point::new(row.0, first_indent.len)
13216 ..Point::new(row.0, buffer.line_len(row)),
13217 );
13218 for row in start.row + 1..=end.row {
13219 let mut line_len = buffer.line_len(MultiBufferRow(row));
13220 if row == end.row {
13221 line_len = end.column;
13222 }
13223 if line_len == 0 {
13224 trimmed_selections
13225 .push(Point::new(row, 0)..Point::new(row, line_len));
13226 continue;
13227 }
13228 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13229 if row_indent_size.len >= first_indent.len {
13230 trimmed_selections.push(
13231 Point::new(row, first_indent.len)..Point::new(row, line_len),
13232 );
13233 } else {
13234 trimmed_selections.clear();
13235 trimmed_selections.push(start..end);
13236 break;
13237 }
13238 }
13239 }
13240 } else {
13241 trimmed_selections.push(start..end);
13242 }
13243
13244 for trimmed_range in trimmed_selections {
13245 if is_first {
13246 is_first = false;
13247 } else if !prev_selection_was_entire_line {
13248 text += "\n";
13249 }
13250 prev_selection_was_entire_line = is_entire_line;
13251 let mut len = 0;
13252 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13253 text.push_str(chunk);
13254 len += chunk.len();
13255 }
13256 if add_trailing_newline {
13257 text.push('\n');
13258 len += 1;
13259 }
13260 clipboard_selections.push(ClipboardSelection::for_buffer(
13261 len,
13262 is_entire_line,
13263 trimmed_range,
13264 &buffer,
13265 self.project.as_ref(),
13266 cx,
13267 ));
13268 }
13269 }
13270 }
13271
13272 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13273 text,
13274 clipboard_selections,
13275 ));
13276 }
13277
13278 pub fn do_paste(
13279 &mut self,
13280 text: &String,
13281 clipboard_selections: Option<Vec<ClipboardSelection>>,
13282 handle_entire_lines: bool,
13283 window: &mut Window,
13284 cx: &mut Context<Self>,
13285 ) {
13286 if self.read_only(cx) {
13287 return;
13288 }
13289
13290 let clipboard_text = Cow::Borrowed(text.as_str());
13291
13292 self.transact(window, cx, |this, window, cx| {
13293 let had_active_edit_prediction = this.has_active_edit_prediction();
13294 let display_map = this.display_snapshot(cx);
13295 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13296 let cursor_offset = this
13297 .selections
13298 .last::<MultiBufferOffset>(&display_map)
13299 .head();
13300
13301 if let Some(mut clipboard_selections) = clipboard_selections {
13302 let all_selections_were_entire_line =
13303 clipboard_selections.iter().all(|s| s.is_entire_line);
13304 let first_selection_indent_column =
13305 clipboard_selections.first().map(|s| s.first_line_indent);
13306 if clipboard_selections.len() != old_selections.len() {
13307 clipboard_selections.drain(..);
13308 }
13309 let mut auto_indent_on_paste = true;
13310
13311 this.buffer.update(cx, |buffer, cx| {
13312 let snapshot = buffer.read(cx);
13313 auto_indent_on_paste = snapshot
13314 .language_settings_at(cursor_offset, cx)
13315 .auto_indent_on_paste;
13316
13317 let mut start_offset = 0;
13318 let mut edits = Vec::new();
13319 let mut original_indent_columns = Vec::new();
13320 for (ix, selection) in old_selections.iter().enumerate() {
13321 let to_insert;
13322 let entire_line;
13323 let original_indent_column;
13324 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13325 let end_offset = start_offset + clipboard_selection.len;
13326 to_insert = &clipboard_text[start_offset..end_offset];
13327 entire_line = clipboard_selection.is_entire_line;
13328 start_offset = if entire_line {
13329 end_offset
13330 } else {
13331 end_offset + 1
13332 };
13333 original_indent_column = Some(clipboard_selection.first_line_indent);
13334 } else {
13335 to_insert = &*clipboard_text;
13336 entire_line = all_selections_were_entire_line;
13337 original_indent_column = first_selection_indent_column
13338 }
13339
13340 let (range, to_insert) =
13341 if selection.is_empty() && handle_entire_lines && entire_line {
13342 // If the corresponding selection was empty when this slice of the
13343 // clipboard text was written, then the entire line containing the
13344 // selection was copied. If this selection is also currently empty,
13345 // then paste the line before the current line of the buffer.
13346 let column = selection.start.to_point(&snapshot).column as usize;
13347 let line_start = selection.start - column;
13348 (line_start..line_start, Cow::Borrowed(to_insert))
13349 } else {
13350 let language = snapshot.language_at(selection.head());
13351 let range = selection.range();
13352 if let Some(language) = language
13353 && language.name() == "Markdown".into()
13354 {
13355 edit_for_markdown_paste(
13356 &snapshot,
13357 range,
13358 to_insert,
13359 url::Url::parse(to_insert).ok(),
13360 )
13361 } else {
13362 (range, Cow::Borrowed(to_insert))
13363 }
13364 };
13365
13366 edits.push((range, to_insert));
13367 original_indent_columns.push(original_indent_column);
13368 }
13369 drop(snapshot);
13370
13371 buffer.edit(
13372 edits,
13373 if auto_indent_on_paste {
13374 Some(AutoindentMode::Block {
13375 original_indent_columns,
13376 })
13377 } else {
13378 None
13379 },
13380 cx,
13381 );
13382 });
13383
13384 let selections = this
13385 .selections
13386 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13387 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13388 } else {
13389 let url = url::Url::parse(&clipboard_text).ok();
13390
13391 let auto_indent_mode = if !clipboard_text.is_empty() {
13392 Some(AutoindentMode::Block {
13393 original_indent_columns: Vec::new(),
13394 })
13395 } else {
13396 None
13397 };
13398
13399 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13400 let snapshot = buffer.snapshot(cx);
13401
13402 let anchors = old_selections
13403 .iter()
13404 .map(|s| {
13405 let anchor = snapshot.anchor_after(s.head());
13406 s.map(|_| anchor)
13407 })
13408 .collect::<Vec<_>>();
13409
13410 let mut edits = Vec::new();
13411
13412 for selection in old_selections.iter() {
13413 let language = snapshot.language_at(selection.head());
13414 let range = selection.range();
13415
13416 let (edit_range, edit_text) = if let Some(language) = language
13417 && language.name() == "Markdown".into()
13418 {
13419 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13420 } else {
13421 (range, clipboard_text.clone())
13422 };
13423
13424 edits.push((edit_range, edit_text));
13425 }
13426
13427 drop(snapshot);
13428 buffer.edit(edits, auto_indent_mode, cx);
13429
13430 anchors
13431 });
13432
13433 this.change_selections(Default::default(), window, cx, |s| {
13434 s.select_anchors(selection_anchors);
13435 });
13436 }
13437
13438 // 🤔 | .. | show_in_menu |
13439 // | .. | true true
13440 // | had_edit_prediction | false true
13441
13442 let trigger_in_words =
13443 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13444
13445 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13446 });
13447 }
13448
13449 pub fn diff_clipboard_with_selection(
13450 &mut self,
13451 _: &DiffClipboardWithSelection,
13452 window: &mut Window,
13453 cx: &mut Context<Self>,
13454 ) {
13455 let selections = self
13456 .selections
13457 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13458
13459 if selections.is_empty() {
13460 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13461 return;
13462 };
13463
13464 let clipboard_text = match cx.read_from_clipboard() {
13465 Some(item) => match item.entries().first() {
13466 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13467 _ => None,
13468 },
13469 None => None,
13470 };
13471
13472 let Some(clipboard_text) = clipboard_text else {
13473 log::warn!("Clipboard doesn't contain text.");
13474 return;
13475 };
13476
13477 window.dispatch_action(
13478 Box::new(DiffClipboardWithSelectionData {
13479 clipboard_text,
13480 editor: cx.entity(),
13481 }),
13482 cx,
13483 );
13484 }
13485
13486 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13487 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13488 if let Some(item) = cx.read_from_clipboard() {
13489 let entries = item.entries();
13490
13491 match entries.first() {
13492 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13493 // of all the pasted entries.
13494 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13495 .do_paste(
13496 clipboard_string.text(),
13497 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13498 true,
13499 window,
13500 cx,
13501 ),
13502 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13503 }
13504 }
13505 }
13506
13507 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13508 if self.read_only(cx) {
13509 return;
13510 }
13511
13512 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13513
13514 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13515 if let Some((selections, _)) =
13516 self.selection_history.transaction(transaction_id).cloned()
13517 {
13518 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13519 s.select_anchors(selections.to_vec());
13520 });
13521 } else {
13522 log::error!(
13523 "No entry in selection_history found for undo. \
13524 This may correspond to a bug where undo does not update the selection. \
13525 If this is occurring, please add details to \
13526 https://github.com/zed-industries/zed/issues/22692"
13527 );
13528 }
13529 self.request_autoscroll(Autoscroll::fit(), cx);
13530 self.unmark_text(window, cx);
13531 self.refresh_edit_prediction(true, false, window, cx);
13532 cx.emit(EditorEvent::Edited { transaction_id });
13533 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13534 }
13535 }
13536
13537 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13538 if self.read_only(cx) {
13539 return;
13540 }
13541
13542 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13543
13544 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13545 if let Some((_, Some(selections))) =
13546 self.selection_history.transaction(transaction_id).cloned()
13547 {
13548 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13549 s.select_anchors(selections.to_vec());
13550 });
13551 } else {
13552 log::error!(
13553 "No entry in selection_history found for redo. \
13554 This may correspond to a bug where undo does not update the selection. \
13555 If this is occurring, please add details to \
13556 https://github.com/zed-industries/zed/issues/22692"
13557 );
13558 }
13559 self.request_autoscroll(Autoscroll::fit(), cx);
13560 self.unmark_text(window, cx);
13561 self.refresh_edit_prediction(true, false, window, cx);
13562 cx.emit(EditorEvent::Edited { transaction_id });
13563 }
13564 }
13565
13566 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13567 self.buffer
13568 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13569 }
13570
13571 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13572 self.buffer
13573 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13574 }
13575
13576 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13577 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13578 self.change_selections(Default::default(), window, cx, |s| {
13579 s.move_with(|map, selection| {
13580 let cursor = if selection.is_empty() {
13581 movement::left(map, selection.start)
13582 } else {
13583 selection.start
13584 };
13585 selection.collapse_to(cursor, SelectionGoal::None);
13586 });
13587 })
13588 }
13589
13590 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13592 self.change_selections(Default::default(), window, cx, |s| {
13593 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13594 })
13595 }
13596
13597 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13598 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13599 self.change_selections(Default::default(), window, cx, |s| {
13600 s.move_with(|map, selection| {
13601 let cursor = if selection.is_empty() {
13602 movement::right(map, selection.end)
13603 } else {
13604 selection.end
13605 };
13606 selection.collapse_to(cursor, SelectionGoal::None)
13607 });
13608 })
13609 }
13610
13611 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13613 self.change_selections(Default::default(), window, cx, |s| {
13614 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13615 });
13616 }
13617
13618 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13619 if self.take_rename(true, window, cx).is_some() {
13620 return;
13621 }
13622
13623 if self.mode.is_single_line() {
13624 cx.propagate();
13625 return;
13626 }
13627
13628 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13629
13630 let text_layout_details = &self.text_layout_details(window);
13631 let selection_count = self.selections.count();
13632 let first_selection = self.selections.first_anchor();
13633
13634 self.change_selections(Default::default(), window, cx, |s| {
13635 s.move_with(|map, selection| {
13636 if !selection.is_empty() {
13637 selection.goal = SelectionGoal::None;
13638 }
13639 let (cursor, goal) = movement::up(
13640 map,
13641 selection.start,
13642 selection.goal,
13643 false,
13644 text_layout_details,
13645 );
13646 selection.collapse_to(cursor, goal);
13647 });
13648 });
13649
13650 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13651 {
13652 cx.propagate();
13653 }
13654 }
13655
13656 pub fn move_up_by_lines(
13657 &mut self,
13658 action: &MoveUpByLines,
13659 window: &mut Window,
13660 cx: &mut Context<Self>,
13661 ) {
13662 if self.take_rename(true, window, cx).is_some() {
13663 return;
13664 }
13665
13666 if self.mode.is_single_line() {
13667 cx.propagate();
13668 return;
13669 }
13670
13671 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13672
13673 let text_layout_details = &self.text_layout_details(window);
13674
13675 self.change_selections(Default::default(), window, cx, |s| {
13676 s.move_with(|map, selection| {
13677 if !selection.is_empty() {
13678 selection.goal = SelectionGoal::None;
13679 }
13680 let (cursor, goal) = movement::up_by_rows(
13681 map,
13682 selection.start,
13683 action.lines,
13684 selection.goal,
13685 false,
13686 text_layout_details,
13687 );
13688 selection.collapse_to(cursor, goal);
13689 });
13690 })
13691 }
13692
13693 pub fn move_down_by_lines(
13694 &mut self,
13695 action: &MoveDownByLines,
13696 window: &mut Window,
13697 cx: &mut Context<Self>,
13698 ) {
13699 if self.take_rename(true, window, cx).is_some() {
13700 return;
13701 }
13702
13703 if self.mode.is_single_line() {
13704 cx.propagate();
13705 return;
13706 }
13707
13708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13709
13710 let text_layout_details = &self.text_layout_details(window);
13711
13712 self.change_selections(Default::default(), window, cx, |s| {
13713 s.move_with(|map, selection| {
13714 if !selection.is_empty() {
13715 selection.goal = SelectionGoal::None;
13716 }
13717 let (cursor, goal) = movement::down_by_rows(
13718 map,
13719 selection.start,
13720 action.lines,
13721 selection.goal,
13722 false,
13723 text_layout_details,
13724 );
13725 selection.collapse_to(cursor, goal);
13726 });
13727 })
13728 }
13729
13730 pub fn select_down_by_lines(
13731 &mut self,
13732 action: &SelectDownByLines,
13733 window: &mut Window,
13734 cx: &mut Context<Self>,
13735 ) {
13736 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13737 let text_layout_details = &self.text_layout_details(window);
13738 self.change_selections(Default::default(), window, cx, |s| {
13739 s.move_heads_with(|map, head, goal| {
13740 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13741 })
13742 })
13743 }
13744
13745 pub fn select_up_by_lines(
13746 &mut self,
13747 action: &SelectUpByLines,
13748 window: &mut Window,
13749 cx: &mut Context<Self>,
13750 ) {
13751 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13752 let text_layout_details = &self.text_layout_details(window);
13753 self.change_selections(Default::default(), window, cx, |s| {
13754 s.move_heads_with(|map, head, goal| {
13755 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13756 })
13757 })
13758 }
13759
13760 pub fn select_page_up(
13761 &mut self,
13762 _: &SelectPageUp,
13763 window: &mut Window,
13764 cx: &mut Context<Self>,
13765 ) {
13766 let Some(row_count) = self.visible_row_count() else {
13767 return;
13768 };
13769
13770 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13771
13772 let text_layout_details = &self.text_layout_details(window);
13773
13774 self.change_selections(Default::default(), window, cx, |s| {
13775 s.move_heads_with(|map, head, goal| {
13776 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13777 })
13778 })
13779 }
13780
13781 pub fn move_page_up(
13782 &mut self,
13783 action: &MovePageUp,
13784 window: &mut Window,
13785 cx: &mut Context<Self>,
13786 ) {
13787 if self.take_rename(true, window, cx).is_some() {
13788 return;
13789 }
13790
13791 if self
13792 .context_menu
13793 .borrow_mut()
13794 .as_mut()
13795 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13796 .unwrap_or(false)
13797 {
13798 return;
13799 }
13800
13801 if matches!(self.mode, EditorMode::SingleLine) {
13802 cx.propagate();
13803 return;
13804 }
13805
13806 let Some(row_count) = self.visible_row_count() else {
13807 return;
13808 };
13809
13810 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13811
13812 let effects = if action.center_cursor {
13813 SelectionEffects::scroll(Autoscroll::center())
13814 } else {
13815 SelectionEffects::default()
13816 };
13817
13818 let text_layout_details = &self.text_layout_details(window);
13819
13820 self.change_selections(effects, window, cx, |s| {
13821 s.move_with(|map, selection| {
13822 if !selection.is_empty() {
13823 selection.goal = SelectionGoal::None;
13824 }
13825 let (cursor, goal) = movement::up_by_rows(
13826 map,
13827 selection.end,
13828 row_count,
13829 selection.goal,
13830 false,
13831 text_layout_details,
13832 );
13833 selection.collapse_to(cursor, goal);
13834 });
13835 });
13836 }
13837
13838 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13839 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13840 let text_layout_details = &self.text_layout_details(window);
13841 self.change_selections(Default::default(), window, cx, |s| {
13842 s.move_heads_with(|map, head, goal| {
13843 movement::up(map, head, goal, false, text_layout_details)
13844 })
13845 })
13846 }
13847
13848 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13849 self.take_rename(true, window, cx);
13850
13851 if self.mode.is_single_line() {
13852 cx.propagate();
13853 return;
13854 }
13855
13856 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13857
13858 let text_layout_details = &self.text_layout_details(window);
13859 let selection_count = self.selections.count();
13860 let first_selection = self.selections.first_anchor();
13861
13862 self.change_selections(Default::default(), window, cx, |s| {
13863 s.move_with(|map, selection| {
13864 if !selection.is_empty() {
13865 selection.goal = SelectionGoal::None;
13866 }
13867 let (cursor, goal) = movement::down(
13868 map,
13869 selection.end,
13870 selection.goal,
13871 false,
13872 text_layout_details,
13873 );
13874 selection.collapse_to(cursor, goal);
13875 });
13876 });
13877
13878 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13879 {
13880 cx.propagate();
13881 }
13882 }
13883
13884 pub fn select_page_down(
13885 &mut self,
13886 _: &SelectPageDown,
13887 window: &mut Window,
13888 cx: &mut Context<Self>,
13889 ) {
13890 let Some(row_count) = self.visible_row_count() else {
13891 return;
13892 };
13893
13894 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13895
13896 let text_layout_details = &self.text_layout_details(window);
13897
13898 self.change_selections(Default::default(), window, cx, |s| {
13899 s.move_heads_with(|map, head, goal| {
13900 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13901 })
13902 })
13903 }
13904
13905 pub fn move_page_down(
13906 &mut self,
13907 action: &MovePageDown,
13908 window: &mut Window,
13909 cx: &mut Context<Self>,
13910 ) {
13911 if self.take_rename(true, window, cx).is_some() {
13912 return;
13913 }
13914
13915 if self
13916 .context_menu
13917 .borrow_mut()
13918 .as_mut()
13919 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13920 .unwrap_or(false)
13921 {
13922 return;
13923 }
13924
13925 if matches!(self.mode, EditorMode::SingleLine) {
13926 cx.propagate();
13927 return;
13928 }
13929
13930 let Some(row_count) = self.visible_row_count() else {
13931 return;
13932 };
13933
13934 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13935
13936 let effects = if action.center_cursor {
13937 SelectionEffects::scroll(Autoscroll::center())
13938 } else {
13939 SelectionEffects::default()
13940 };
13941
13942 let text_layout_details = &self.text_layout_details(window);
13943 self.change_selections(effects, window, cx, |s| {
13944 s.move_with(|map, selection| {
13945 if !selection.is_empty() {
13946 selection.goal = SelectionGoal::None;
13947 }
13948 let (cursor, goal) = movement::down_by_rows(
13949 map,
13950 selection.end,
13951 row_count,
13952 selection.goal,
13953 false,
13954 text_layout_details,
13955 );
13956 selection.collapse_to(cursor, goal);
13957 });
13958 });
13959 }
13960
13961 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13962 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13963 let text_layout_details = &self.text_layout_details(window);
13964 self.change_selections(Default::default(), window, cx, |s| {
13965 s.move_heads_with(|map, head, goal| {
13966 movement::down(map, head, goal, false, text_layout_details)
13967 })
13968 });
13969 }
13970
13971 pub fn context_menu_first(
13972 &mut self,
13973 _: &ContextMenuFirst,
13974 window: &mut Window,
13975 cx: &mut Context<Self>,
13976 ) {
13977 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13978 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13979 }
13980 }
13981
13982 pub fn context_menu_prev(
13983 &mut self,
13984 _: &ContextMenuPrevious,
13985 window: &mut Window,
13986 cx: &mut Context<Self>,
13987 ) {
13988 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13989 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13990 }
13991 }
13992
13993 pub fn context_menu_next(
13994 &mut self,
13995 _: &ContextMenuNext,
13996 window: &mut Window,
13997 cx: &mut Context<Self>,
13998 ) {
13999 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14000 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
14001 }
14002 }
14003
14004 pub fn context_menu_last(
14005 &mut self,
14006 _: &ContextMenuLast,
14007 window: &mut Window,
14008 cx: &mut Context<Self>,
14009 ) {
14010 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
14011 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
14012 }
14013 }
14014
14015 pub fn signature_help_prev(
14016 &mut self,
14017 _: &SignatureHelpPrevious,
14018 _: &mut Window,
14019 cx: &mut Context<Self>,
14020 ) {
14021 if let Some(popover) = self.signature_help_state.popover_mut() {
14022 if popover.current_signature == 0 {
14023 popover.current_signature = popover.signatures.len() - 1;
14024 } else {
14025 popover.current_signature -= 1;
14026 }
14027 cx.notify();
14028 }
14029 }
14030
14031 pub fn signature_help_next(
14032 &mut self,
14033 _: &SignatureHelpNext,
14034 _: &mut Window,
14035 cx: &mut Context<Self>,
14036 ) {
14037 if let Some(popover) = self.signature_help_state.popover_mut() {
14038 if popover.current_signature + 1 == popover.signatures.len() {
14039 popover.current_signature = 0;
14040 } else {
14041 popover.current_signature += 1;
14042 }
14043 cx.notify();
14044 }
14045 }
14046
14047 pub fn move_to_previous_word_start(
14048 &mut self,
14049 _: &MoveToPreviousWordStart,
14050 window: &mut Window,
14051 cx: &mut Context<Self>,
14052 ) {
14053 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14054 self.change_selections(Default::default(), window, cx, |s| {
14055 s.move_cursors_with(|map, head, _| {
14056 (
14057 movement::previous_word_start(map, head),
14058 SelectionGoal::None,
14059 )
14060 });
14061 })
14062 }
14063
14064 pub fn move_to_previous_subword_start(
14065 &mut self,
14066 _: &MoveToPreviousSubwordStart,
14067 window: &mut Window,
14068 cx: &mut Context<Self>,
14069 ) {
14070 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14071 self.change_selections(Default::default(), window, cx, |s| {
14072 s.move_cursors_with(|map, head, _| {
14073 (
14074 movement::previous_subword_start(map, head),
14075 SelectionGoal::None,
14076 )
14077 });
14078 })
14079 }
14080
14081 pub fn select_to_previous_word_start(
14082 &mut self,
14083 _: &SelectToPreviousWordStart,
14084 window: &mut Window,
14085 cx: &mut Context<Self>,
14086 ) {
14087 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14088 self.change_selections(Default::default(), window, cx, |s| {
14089 s.move_heads_with(|map, head, _| {
14090 (
14091 movement::previous_word_start(map, head),
14092 SelectionGoal::None,
14093 )
14094 });
14095 })
14096 }
14097
14098 pub fn select_to_previous_subword_start(
14099 &mut self,
14100 _: &SelectToPreviousSubwordStart,
14101 window: &mut Window,
14102 cx: &mut Context<Self>,
14103 ) {
14104 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14105 self.change_selections(Default::default(), window, cx, |s| {
14106 s.move_heads_with(|map, head, _| {
14107 (
14108 movement::previous_subword_start(map, head),
14109 SelectionGoal::None,
14110 )
14111 });
14112 })
14113 }
14114
14115 pub fn delete_to_previous_word_start(
14116 &mut self,
14117 action: &DeleteToPreviousWordStart,
14118 window: &mut Window,
14119 cx: &mut Context<Self>,
14120 ) {
14121 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14122 self.transact(window, cx, |this, window, cx| {
14123 this.select_autoclose_pair(window, cx);
14124 this.change_selections(Default::default(), window, cx, |s| {
14125 s.move_with(|map, selection| {
14126 if selection.is_empty() {
14127 let mut cursor = if action.ignore_newlines {
14128 movement::previous_word_start(map, selection.head())
14129 } else {
14130 movement::previous_word_start_or_newline(map, selection.head())
14131 };
14132 cursor = movement::adjust_greedy_deletion(
14133 map,
14134 selection.head(),
14135 cursor,
14136 action.ignore_brackets,
14137 );
14138 selection.set_head(cursor, SelectionGoal::None);
14139 }
14140 });
14141 });
14142 this.insert("", window, cx);
14143 });
14144 }
14145
14146 pub fn delete_to_previous_subword_start(
14147 &mut self,
14148 _: &DeleteToPreviousSubwordStart,
14149 window: &mut Window,
14150 cx: &mut Context<Self>,
14151 ) {
14152 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14153 self.transact(window, cx, |this, window, cx| {
14154 this.select_autoclose_pair(window, cx);
14155 this.change_selections(Default::default(), window, cx, |s| {
14156 s.move_with(|map, selection| {
14157 if selection.is_empty() {
14158 let mut cursor = movement::previous_subword_start(map, selection.head());
14159 cursor =
14160 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14161 selection.set_head(cursor, SelectionGoal::None);
14162 }
14163 });
14164 });
14165 this.insert("", window, cx);
14166 });
14167 }
14168
14169 pub fn move_to_next_word_end(
14170 &mut self,
14171 _: &MoveToNextWordEnd,
14172 window: &mut Window,
14173 cx: &mut Context<Self>,
14174 ) {
14175 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14176 self.change_selections(Default::default(), window, cx, |s| {
14177 s.move_cursors_with(|map, head, _| {
14178 (movement::next_word_end(map, head), SelectionGoal::None)
14179 });
14180 })
14181 }
14182
14183 pub fn move_to_next_subword_end(
14184 &mut self,
14185 _: &MoveToNextSubwordEnd,
14186 window: &mut Window,
14187 cx: &mut Context<Self>,
14188 ) {
14189 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14190 self.change_selections(Default::default(), window, cx, |s| {
14191 s.move_cursors_with(|map, head, _| {
14192 (movement::next_subword_end(map, head), SelectionGoal::None)
14193 });
14194 })
14195 }
14196
14197 pub fn select_to_next_word_end(
14198 &mut self,
14199 _: &SelectToNextWordEnd,
14200 window: &mut Window,
14201 cx: &mut Context<Self>,
14202 ) {
14203 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14204 self.change_selections(Default::default(), window, cx, |s| {
14205 s.move_heads_with(|map, head, _| {
14206 (movement::next_word_end(map, head), SelectionGoal::None)
14207 });
14208 })
14209 }
14210
14211 pub fn select_to_next_subword_end(
14212 &mut self,
14213 _: &SelectToNextSubwordEnd,
14214 window: &mut Window,
14215 cx: &mut Context<Self>,
14216 ) {
14217 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14218 self.change_selections(Default::default(), window, cx, |s| {
14219 s.move_heads_with(|map, head, _| {
14220 (movement::next_subword_end(map, head), SelectionGoal::None)
14221 });
14222 })
14223 }
14224
14225 pub fn delete_to_next_word_end(
14226 &mut self,
14227 action: &DeleteToNextWordEnd,
14228 window: &mut Window,
14229 cx: &mut Context<Self>,
14230 ) {
14231 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14232 self.transact(window, cx, |this, window, cx| {
14233 this.change_selections(Default::default(), window, cx, |s| {
14234 s.move_with(|map, selection| {
14235 if selection.is_empty() {
14236 let mut cursor = if action.ignore_newlines {
14237 movement::next_word_end(map, selection.head())
14238 } else {
14239 movement::next_word_end_or_newline(map, selection.head())
14240 };
14241 cursor = movement::adjust_greedy_deletion(
14242 map,
14243 selection.head(),
14244 cursor,
14245 action.ignore_brackets,
14246 );
14247 selection.set_head(cursor, SelectionGoal::None);
14248 }
14249 });
14250 });
14251 this.insert("", window, cx);
14252 });
14253 }
14254
14255 pub fn delete_to_next_subword_end(
14256 &mut self,
14257 _: &DeleteToNextSubwordEnd,
14258 window: &mut Window,
14259 cx: &mut Context<Self>,
14260 ) {
14261 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14262 self.transact(window, cx, |this, window, cx| {
14263 this.change_selections(Default::default(), window, cx, |s| {
14264 s.move_with(|map, selection| {
14265 if selection.is_empty() {
14266 let mut cursor = movement::next_subword_end(map, selection.head());
14267 cursor =
14268 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14269 selection.set_head(cursor, SelectionGoal::None);
14270 }
14271 });
14272 });
14273 this.insert("", window, cx);
14274 });
14275 }
14276
14277 pub fn move_to_beginning_of_line(
14278 &mut self,
14279 action: &MoveToBeginningOfLine,
14280 window: &mut Window,
14281 cx: &mut Context<Self>,
14282 ) {
14283 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14284 self.change_selections(Default::default(), window, cx, |s| {
14285 s.move_cursors_with(|map, head, _| {
14286 (
14287 movement::indented_line_beginning(
14288 map,
14289 head,
14290 action.stop_at_soft_wraps,
14291 action.stop_at_indent,
14292 ),
14293 SelectionGoal::None,
14294 )
14295 });
14296 })
14297 }
14298
14299 pub fn select_to_beginning_of_line(
14300 &mut self,
14301 action: &SelectToBeginningOfLine,
14302 window: &mut Window,
14303 cx: &mut Context<Self>,
14304 ) {
14305 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14306 self.change_selections(Default::default(), window, cx, |s| {
14307 s.move_heads_with(|map, head, _| {
14308 (
14309 movement::indented_line_beginning(
14310 map,
14311 head,
14312 action.stop_at_soft_wraps,
14313 action.stop_at_indent,
14314 ),
14315 SelectionGoal::None,
14316 )
14317 });
14318 });
14319 }
14320
14321 pub fn delete_to_beginning_of_line(
14322 &mut self,
14323 action: &DeleteToBeginningOfLine,
14324 window: &mut Window,
14325 cx: &mut Context<Self>,
14326 ) {
14327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14328 self.transact(window, cx, |this, window, cx| {
14329 this.change_selections(Default::default(), window, cx, |s| {
14330 s.move_with(|_, selection| {
14331 selection.reversed = true;
14332 });
14333 });
14334
14335 this.select_to_beginning_of_line(
14336 &SelectToBeginningOfLine {
14337 stop_at_soft_wraps: false,
14338 stop_at_indent: action.stop_at_indent,
14339 },
14340 window,
14341 cx,
14342 );
14343 this.backspace(&Backspace, window, cx);
14344 });
14345 }
14346
14347 pub fn move_to_end_of_line(
14348 &mut self,
14349 action: &MoveToEndOfLine,
14350 window: &mut Window,
14351 cx: &mut Context<Self>,
14352 ) {
14353 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14354 self.change_selections(Default::default(), window, cx, |s| {
14355 s.move_cursors_with(|map, head, _| {
14356 (
14357 movement::line_end(map, head, action.stop_at_soft_wraps),
14358 SelectionGoal::None,
14359 )
14360 });
14361 })
14362 }
14363
14364 pub fn select_to_end_of_line(
14365 &mut self,
14366 action: &SelectToEndOfLine,
14367 window: &mut Window,
14368 cx: &mut Context<Self>,
14369 ) {
14370 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14371 self.change_selections(Default::default(), window, cx, |s| {
14372 s.move_heads_with(|map, head, _| {
14373 (
14374 movement::line_end(map, head, action.stop_at_soft_wraps),
14375 SelectionGoal::None,
14376 )
14377 });
14378 })
14379 }
14380
14381 pub fn delete_to_end_of_line(
14382 &mut self,
14383 _: &DeleteToEndOfLine,
14384 window: &mut Window,
14385 cx: &mut Context<Self>,
14386 ) {
14387 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14388 self.transact(window, cx, |this, window, cx| {
14389 this.select_to_end_of_line(
14390 &SelectToEndOfLine {
14391 stop_at_soft_wraps: false,
14392 },
14393 window,
14394 cx,
14395 );
14396 this.delete(&Delete, window, cx);
14397 });
14398 }
14399
14400 pub fn cut_to_end_of_line(
14401 &mut self,
14402 action: &CutToEndOfLine,
14403 window: &mut Window,
14404 cx: &mut Context<Self>,
14405 ) {
14406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14407 self.transact(window, cx, |this, window, cx| {
14408 this.select_to_end_of_line(
14409 &SelectToEndOfLine {
14410 stop_at_soft_wraps: false,
14411 },
14412 window,
14413 cx,
14414 );
14415 if !action.stop_at_newlines {
14416 this.change_selections(Default::default(), window, cx, |s| {
14417 s.move_with(|_, sel| {
14418 if sel.is_empty() {
14419 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14420 }
14421 });
14422 });
14423 }
14424 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14425 let item = this.cut_common(false, window, cx);
14426 cx.write_to_clipboard(item);
14427 });
14428 }
14429
14430 pub fn move_to_start_of_paragraph(
14431 &mut self,
14432 _: &MoveToStartOfParagraph,
14433 window: &mut Window,
14434 cx: &mut Context<Self>,
14435 ) {
14436 if matches!(self.mode, EditorMode::SingleLine) {
14437 cx.propagate();
14438 return;
14439 }
14440 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14441 self.change_selections(Default::default(), window, cx, |s| {
14442 s.move_with(|map, selection| {
14443 selection.collapse_to(
14444 movement::start_of_paragraph(map, selection.head(), 1),
14445 SelectionGoal::None,
14446 )
14447 });
14448 })
14449 }
14450
14451 pub fn move_to_end_of_paragraph(
14452 &mut self,
14453 _: &MoveToEndOfParagraph,
14454 window: &mut Window,
14455 cx: &mut Context<Self>,
14456 ) {
14457 if matches!(self.mode, EditorMode::SingleLine) {
14458 cx.propagate();
14459 return;
14460 }
14461 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14462 self.change_selections(Default::default(), window, cx, |s| {
14463 s.move_with(|map, selection| {
14464 selection.collapse_to(
14465 movement::end_of_paragraph(map, selection.head(), 1),
14466 SelectionGoal::None,
14467 )
14468 });
14469 })
14470 }
14471
14472 pub fn select_to_start_of_paragraph(
14473 &mut self,
14474 _: &SelectToStartOfParagraph,
14475 window: &mut Window,
14476 cx: &mut Context<Self>,
14477 ) {
14478 if matches!(self.mode, EditorMode::SingleLine) {
14479 cx.propagate();
14480 return;
14481 }
14482 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14483 self.change_selections(Default::default(), window, cx, |s| {
14484 s.move_heads_with(|map, head, _| {
14485 (
14486 movement::start_of_paragraph(map, head, 1),
14487 SelectionGoal::None,
14488 )
14489 });
14490 })
14491 }
14492
14493 pub fn select_to_end_of_paragraph(
14494 &mut self,
14495 _: &SelectToEndOfParagraph,
14496 window: &mut Window,
14497 cx: &mut Context<Self>,
14498 ) {
14499 if matches!(self.mode, EditorMode::SingleLine) {
14500 cx.propagate();
14501 return;
14502 }
14503 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14504 self.change_selections(Default::default(), window, cx, |s| {
14505 s.move_heads_with(|map, head, _| {
14506 (
14507 movement::end_of_paragraph(map, head, 1),
14508 SelectionGoal::None,
14509 )
14510 });
14511 })
14512 }
14513
14514 pub fn move_to_start_of_excerpt(
14515 &mut self,
14516 _: &MoveToStartOfExcerpt,
14517 window: &mut Window,
14518 cx: &mut Context<Self>,
14519 ) {
14520 if matches!(self.mode, EditorMode::SingleLine) {
14521 cx.propagate();
14522 return;
14523 }
14524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14525 self.change_selections(Default::default(), window, cx, |s| {
14526 s.move_with(|map, selection| {
14527 selection.collapse_to(
14528 movement::start_of_excerpt(
14529 map,
14530 selection.head(),
14531 workspace::searchable::Direction::Prev,
14532 ),
14533 SelectionGoal::None,
14534 )
14535 });
14536 })
14537 }
14538
14539 pub fn move_to_start_of_next_excerpt(
14540 &mut self,
14541 _: &MoveToStartOfNextExcerpt,
14542 window: &mut Window,
14543 cx: &mut Context<Self>,
14544 ) {
14545 if matches!(self.mode, EditorMode::SingleLine) {
14546 cx.propagate();
14547 return;
14548 }
14549
14550 self.change_selections(Default::default(), window, cx, |s| {
14551 s.move_with(|map, selection| {
14552 selection.collapse_to(
14553 movement::start_of_excerpt(
14554 map,
14555 selection.head(),
14556 workspace::searchable::Direction::Next,
14557 ),
14558 SelectionGoal::None,
14559 )
14560 });
14561 })
14562 }
14563
14564 pub fn move_to_end_of_excerpt(
14565 &mut self,
14566 _: &MoveToEndOfExcerpt,
14567 window: &mut Window,
14568 cx: &mut Context<Self>,
14569 ) {
14570 if matches!(self.mode, EditorMode::SingleLine) {
14571 cx.propagate();
14572 return;
14573 }
14574 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14575 self.change_selections(Default::default(), window, cx, |s| {
14576 s.move_with(|map, selection| {
14577 selection.collapse_to(
14578 movement::end_of_excerpt(
14579 map,
14580 selection.head(),
14581 workspace::searchable::Direction::Next,
14582 ),
14583 SelectionGoal::None,
14584 )
14585 });
14586 })
14587 }
14588
14589 pub fn move_to_end_of_previous_excerpt(
14590 &mut self,
14591 _: &MoveToEndOfPreviousExcerpt,
14592 window: &mut Window,
14593 cx: &mut Context<Self>,
14594 ) {
14595 if matches!(self.mode, EditorMode::SingleLine) {
14596 cx.propagate();
14597 return;
14598 }
14599 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14600 self.change_selections(Default::default(), window, cx, |s| {
14601 s.move_with(|map, selection| {
14602 selection.collapse_to(
14603 movement::end_of_excerpt(
14604 map,
14605 selection.head(),
14606 workspace::searchable::Direction::Prev,
14607 ),
14608 SelectionGoal::None,
14609 )
14610 });
14611 })
14612 }
14613
14614 pub fn select_to_start_of_excerpt(
14615 &mut self,
14616 _: &SelectToStartOfExcerpt,
14617 window: &mut Window,
14618 cx: &mut Context<Self>,
14619 ) {
14620 if matches!(self.mode, EditorMode::SingleLine) {
14621 cx.propagate();
14622 return;
14623 }
14624 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14625 self.change_selections(Default::default(), window, cx, |s| {
14626 s.move_heads_with(|map, head, _| {
14627 (
14628 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14629 SelectionGoal::None,
14630 )
14631 });
14632 })
14633 }
14634
14635 pub fn select_to_start_of_next_excerpt(
14636 &mut self,
14637 _: &SelectToStartOfNextExcerpt,
14638 window: &mut Window,
14639 cx: &mut Context<Self>,
14640 ) {
14641 if matches!(self.mode, EditorMode::SingleLine) {
14642 cx.propagate();
14643 return;
14644 }
14645 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14646 self.change_selections(Default::default(), window, cx, |s| {
14647 s.move_heads_with(|map, head, _| {
14648 (
14649 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14650 SelectionGoal::None,
14651 )
14652 });
14653 })
14654 }
14655
14656 pub fn select_to_end_of_excerpt(
14657 &mut self,
14658 _: &SelectToEndOfExcerpt,
14659 window: &mut Window,
14660 cx: &mut Context<Self>,
14661 ) {
14662 if matches!(self.mode, EditorMode::SingleLine) {
14663 cx.propagate();
14664 return;
14665 }
14666 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14667 self.change_selections(Default::default(), window, cx, |s| {
14668 s.move_heads_with(|map, head, _| {
14669 (
14670 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14671 SelectionGoal::None,
14672 )
14673 });
14674 })
14675 }
14676
14677 pub fn select_to_end_of_previous_excerpt(
14678 &mut self,
14679 _: &SelectToEndOfPreviousExcerpt,
14680 window: &mut Window,
14681 cx: &mut Context<Self>,
14682 ) {
14683 if matches!(self.mode, EditorMode::SingleLine) {
14684 cx.propagate();
14685 return;
14686 }
14687 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14688 self.change_selections(Default::default(), window, cx, |s| {
14689 s.move_heads_with(|map, head, _| {
14690 (
14691 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14692 SelectionGoal::None,
14693 )
14694 });
14695 })
14696 }
14697
14698 pub fn move_to_beginning(
14699 &mut self,
14700 _: &MoveToBeginning,
14701 window: &mut Window,
14702 cx: &mut Context<Self>,
14703 ) {
14704 if matches!(self.mode, EditorMode::SingleLine) {
14705 cx.propagate();
14706 return;
14707 }
14708 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14709 self.change_selections(Default::default(), window, cx, |s| {
14710 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14711 });
14712 }
14713
14714 pub fn select_to_beginning(
14715 &mut self,
14716 _: &SelectToBeginning,
14717 window: &mut Window,
14718 cx: &mut Context<Self>,
14719 ) {
14720 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14721 selection.set_head(Point::zero(), SelectionGoal::None);
14722 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14723 self.change_selections(Default::default(), window, cx, |s| {
14724 s.select(vec![selection]);
14725 });
14726 }
14727
14728 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14729 if matches!(self.mode, EditorMode::SingleLine) {
14730 cx.propagate();
14731 return;
14732 }
14733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14734 let cursor = self.buffer.read(cx).read(cx).len();
14735 self.change_selections(Default::default(), window, cx, |s| {
14736 s.select_ranges(vec![cursor..cursor])
14737 });
14738 }
14739
14740 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14741 self.nav_history = nav_history;
14742 }
14743
14744 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14745 self.nav_history.as_ref()
14746 }
14747
14748 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14749 self.push_to_nav_history(
14750 self.selections.newest_anchor().head(),
14751 None,
14752 false,
14753 true,
14754 cx,
14755 );
14756 }
14757
14758 fn push_to_nav_history(
14759 &mut self,
14760 cursor_anchor: Anchor,
14761 new_position: Option<Point>,
14762 is_deactivate: bool,
14763 always: bool,
14764 cx: &mut Context<Self>,
14765 ) {
14766 if let Some(nav_history) = self.nav_history.as_mut() {
14767 let buffer = self.buffer.read(cx).read(cx);
14768 let cursor_position = cursor_anchor.to_point(&buffer);
14769 let scroll_state = self.scroll_manager.anchor();
14770 let scroll_top_row = scroll_state.top_row(&buffer);
14771 drop(buffer);
14772
14773 if let Some(new_position) = new_position {
14774 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14775 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14776 return;
14777 }
14778 }
14779
14780 nav_history.push(
14781 Some(NavigationData {
14782 cursor_anchor,
14783 cursor_position,
14784 scroll_anchor: scroll_state,
14785 scroll_top_row,
14786 }),
14787 cx,
14788 );
14789 cx.emit(EditorEvent::PushedToNavHistory {
14790 anchor: cursor_anchor,
14791 is_deactivate,
14792 })
14793 }
14794 }
14795
14796 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14797 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14798 let buffer = self.buffer.read(cx).snapshot(cx);
14799 let mut selection = self
14800 .selections
14801 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14802 selection.set_head(buffer.len(), SelectionGoal::None);
14803 self.change_selections(Default::default(), window, cx, |s| {
14804 s.select(vec![selection]);
14805 });
14806 }
14807
14808 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14809 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14810 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14811 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14812 });
14813 }
14814
14815 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14816 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14817 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14818 let mut selections = self.selections.all::<Point>(&display_map);
14819 let max_point = display_map.buffer_snapshot().max_point();
14820 for selection in &mut selections {
14821 let rows = selection.spanned_rows(true, &display_map);
14822 selection.start = Point::new(rows.start.0, 0);
14823 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14824 selection.reversed = false;
14825 }
14826 self.change_selections(Default::default(), window, cx, |s| {
14827 s.select(selections);
14828 });
14829 }
14830
14831 pub fn split_selection_into_lines(
14832 &mut self,
14833 action: &SplitSelectionIntoLines,
14834 window: &mut Window,
14835 cx: &mut Context<Self>,
14836 ) {
14837 let selections = self
14838 .selections
14839 .all::<Point>(&self.display_snapshot(cx))
14840 .into_iter()
14841 .map(|selection| selection.start..selection.end)
14842 .collect::<Vec<_>>();
14843 self.unfold_ranges(&selections, true, true, cx);
14844
14845 let mut new_selection_ranges = Vec::new();
14846 {
14847 let buffer = self.buffer.read(cx).read(cx);
14848 for selection in selections {
14849 for row in selection.start.row..selection.end.row {
14850 let line_start = Point::new(row, 0);
14851 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14852
14853 if action.keep_selections {
14854 // Keep the selection range for each line
14855 let selection_start = if row == selection.start.row {
14856 selection.start
14857 } else {
14858 line_start
14859 };
14860 new_selection_ranges.push(selection_start..line_end);
14861 } else {
14862 // Collapse to cursor at end of line
14863 new_selection_ranges.push(line_end..line_end);
14864 }
14865 }
14866
14867 let is_multiline_selection = selection.start.row != selection.end.row;
14868 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14869 // so this action feels more ergonomic when paired with other selection operations
14870 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14871 if !should_skip_last {
14872 if action.keep_selections {
14873 if is_multiline_selection {
14874 let line_start = Point::new(selection.end.row, 0);
14875 new_selection_ranges.push(line_start..selection.end);
14876 } else {
14877 new_selection_ranges.push(selection.start..selection.end);
14878 }
14879 } else {
14880 new_selection_ranges.push(selection.end..selection.end);
14881 }
14882 }
14883 }
14884 }
14885 self.change_selections(Default::default(), window, cx, |s| {
14886 s.select_ranges(new_selection_ranges);
14887 });
14888 }
14889
14890 pub fn add_selection_above(
14891 &mut self,
14892 action: &AddSelectionAbove,
14893 window: &mut Window,
14894 cx: &mut Context<Self>,
14895 ) {
14896 self.add_selection(true, action.skip_soft_wrap, window, cx);
14897 }
14898
14899 pub fn add_selection_below(
14900 &mut self,
14901 action: &AddSelectionBelow,
14902 window: &mut Window,
14903 cx: &mut Context<Self>,
14904 ) {
14905 self.add_selection(false, action.skip_soft_wrap, window, cx);
14906 }
14907
14908 fn add_selection(
14909 &mut self,
14910 above: bool,
14911 skip_soft_wrap: bool,
14912 window: &mut Window,
14913 cx: &mut Context<Self>,
14914 ) {
14915 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14916
14917 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14918 let all_selections = self.selections.all::<Point>(&display_map);
14919 let text_layout_details = self.text_layout_details(window);
14920
14921 let (mut columnar_selections, new_selections_to_columnarize) = {
14922 if let Some(state) = self.add_selections_state.as_ref() {
14923 let columnar_selection_ids: HashSet<_> = state
14924 .groups
14925 .iter()
14926 .flat_map(|group| group.stack.iter())
14927 .copied()
14928 .collect();
14929
14930 all_selections
14931 .into_iter()
14932 .partition(|s| columnar_selection_ids.contains(&s.id))
14933 } else {
14934 (Vec::new(), all_selections)
14935 }
14936 };
14937
14938 let mut state = self
14939 .add_selections_state
14940 .take()
14941 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14942
14943 for selection in new_selections_to_columnarize {
14944 let range = selection.display_range(&display_map).sorted();
14945 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14946 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14947 let positions = start_x.min(end_x)..start_x.max(end_x);
14948 let mut stack = Vec::new();
14949 for row in range.start.row().0..=range.end.row().0 {
14950 if let Some(selection) = self.selections.build_columnar_selection(
14951 &display_map,
14952 DisplayRow(row),
14953 &positions,
14954 selection.reversed,
14955 &text_layout_details,
14956 ) {
14957 stack.push(selection.id);
14958 columnar_selections.push(selection);
14959 }
14960 }
14961 if !stack.is_empty() {
14962 if above {
14963 stack.reverse();
14964 }
14965 state.groups.push(AddSelectionsGroup { above, stack });
14966 }
14967 }
14968
14969 let mut final_selections = Vec::new();
14970 let end_row = if above {
14971 DisplayRow(0)
14972 } else {
14973 display_map.max_point().row()
14974 };
14975
14976 let mut last_added_item_per_group = HashMap::default();
14977 for group in state.groups.iter_mut() {
14978 if let Some(last_id) = group.stack.last() {
14979 last_added_item_per_group.insert(*last_id, group);
14980 }
14981 }
14982
14983 for selection in columnar_selections {
14984 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14985 if above == group.above {
14986 let range = selection.display_range(&display_map).sorted();
14987 debug_assert_eq!(range.start.row(), range.end.row());
14988 let mut row = range.start.row();
14989 let positions =
14990 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14991 Pixels::from(start)..Pixels::from(end)
14992 } else {
14993 let start_x =
14994 display_map.x_for_display_point(range.start, &text_layout_details);
14995 let end_x =
14996 display_map.x_for_display_point(range.end, &text_layout_details);
14997 start_x.min(end_x)..start_x.max(end_x)
14998 };
14999
15000 let mut maybe_new_selection = None;
15001 let direction = if above { -1 } else { 1 };
15002
15003 while row != end_row {
15004 if skip_soft_wrap {
15005 row = display_map
15006 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
15007 .row();
15008 } else if above {
15009 row.0 -= 1;
15010 } else {
15011 row.0 += 1;
15012 }
15013
15014 if let Some(new_selection) = self.selections.build_columnar_selection(
15015 &display_map,
15016 row,
15017 &positions,
15018 selection.reversed,
15019 &text_layout_details,
15020 ) {
15021 maybe_new_selection = Some(new_selection);
15022 break;
15023 }
15024 }
15025
15026 if let Some(new_selection) = maybe_new_selection {
15027 group.stack.push(new_selection.id);
15028 if above {
15029 final_selections.push(new_selection);
15030 final_selections.push(selection);
15031 } else {
15032 final_selections.push(selection);
15033 final_selections.push(new_selection);
15034 }
15035 } else {
15036 final_selections.push(selection);
15037 }
15038 } else {
15039 group.stack.pop();
15040 }
15041 } else {
15042 final_selections.push(selection);
15043 }
15044 }
15045
15046 self.change_selections(Default::default(), window, cx, |s| {
15047 s.select(final_selections);
15048 });
15049
15050 let final_selection_ids: HashSet<_> = self
15051 .selections
15052 .all::<Point>(&display_map)
15053 .iter()
15054 .map(|s| s.id)
15055 .collect();
15056 state.groups.retain_mut(|group| {
15057 // selections might get merged above so we remove invalid items from stacks
15058 group.stack.retain(|id| final_selection_ids.contains(id));
15059
15060 // single selection in stack can be treated as initial state
15061 group.stack.len() > 1
15062 });
15063
15064 if !state.groups.is_empty() {
15065 self.add_selections_state = Some(state);
15066 }
15067 }
15068
15069 pub fn insert_snippet_at_selections(
15070 &mut self,
15071 action: &InsertSnippet,
15072 window: &mut Window,
15073 cx: &mut Context<Self>,
15074 ) {
15075 self.try_insert_snippet_at_selections(action, window, cx)
15076 .log_err();
15077 }
15078
15079 fn try_insert_snippet_at_selections(
15080 &mut self,
15081 action: &InsertSnippet,
15082 window: &mut Window,
15083 cx: &mut Context<Self>,
15084 ) -> Result<()> {
15085 let insertion_ranges = self
15086 .selections
15087 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15088 .into_iter()
15089 .map(|selection| selection.range())
15090 .collect_vec();
15091
15092 let snippet = if let Some(snippet_body) = &action.snippet {
15093 if action.language.is_none() && action.name.is_none() {
15094 Snippet::parse(snippet_body)?
15095 } else {
15096 bail!("`snippet` is mutually exclusive with `language` and `name`")
15097 }
15098 } else if let Some(name) = &action.name {
15099 let project = self.project().context("no project")?;
15100 let snippet_store = project.read(cx).snippets().read(cx);
15101 let snippet = snippet_store
15102 .snippets_for(action.language.clone(), cx)
15103 .into_iter()
15104 .find(|snippet| snippet.name == *name)
15105 .context("snippet not found")?;
15106 Snippet::parse(&snippet.body)?
15107 } else {
15108 // todo(andrew): open modal to select snippet
15109 bail!("`name` or `snippet` is required")
15110 };
15111
15112 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15113 }
15114
15115 fn select_match_ranges(
15116 &mut self,
15117 range: Range<MultiBufferOffset>,
15118 reversed: bool,
15119 replace_newest: bool,
15120 auto_scroll: Option<Autoscroll>,
15121 window: &mut Window,
15122 cx: &mut Context<Editor>,
15123 ) {
15124 self.unfold_ranges(
15125 std::slice::from_ref(&range),
15126 false,
15127 auto_scroll.is_some(),
15128 cx,
15129 );
15130 let effects = if let Some(scroll) = auto_scroll {
15131 SelectionEffects::scroll(scroll)
15132 } else {
15133 SelectionEffects::no_scroll()
15134 };
15135 self.change_selections(effects, window, cx, |s| {
15136 if replace_newest {
15137 s.delete(s.newest_anchor().id);
15138 }
15139 if reversed {
15140 s.insert_range(range.end..range.start);
15141 } else {
15142 s.insert_range(range);
15143 }
15144 });
15145 }
15146
15147 pub fn select_next_match_internal(
15148 &mut self,
15149 display_map: &DisplaySnapshot,
15150 replace_newest: bool,
15151 autoscroll: Option<Autoscroll>,
15152 window: &mut Window,
15153 cx: &mut Context<Self>,
15154 ) -> Result<()> {
15155 let buffer = display_map.buffer_snapshot();
15156 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15157 if let Some(mut select_next_state) = self.select_next_state.take() {
15158 let query = &select_next_state.query;
15159 if !select_next_state.done {
15160 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15161 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15162 let mut next_selected_range = None;
15163
15164 let bytes_after_last_selection =
15165 buffer.bytes_in_range(last_selection.end..buffer.len());
15166 let bytes_before_first_selection =
15167 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15168 let query_matches = query
15169 .stream_find_iter(bytes_after_last_selection)
15170 .map(|result| (last_selection.end, result))
15171 .chain(
15172 query
15173 .stream_find_iter(bytes_before_first_selection)
15174 .map(|result| (MultiBufferOffset(0), result)),
15175 );
15176
15177 for (start_offset, query_match) in query_matches {
15178 let query_match = query_match.unwrap(); // can only fail due to I/O
15179 let offset_range =
15180 start_offset + query_match.start()..start_offset + query_match.end();
15181
15182 if !select_next_state.wordwise
15183 || (!buffer.is_inside_word(offset_range.start, None)
15184 && !buffer.is_inside_word(offset_range.end, None))
15185 {
15186 let idx = selections
15187 .partition_point(|selection| selection.end <= offset_range.start);
15188 let overlaps = selections
15189 .get(idx)
15190 .map_or(false, |selection| selection.start < offset_range.end);
15191
15192 if !overlaps {
15193 next_selected_range = Some(offset_range);
15194 break;
15195 }
15196 }
15197 }
15198
15199 if let Some(next_selected_range) = next_selected_range {
15200 self.select_match_ranges(
15201 next_selected_range,
15202 last_selection.reversed,
15203 replace_newest,
15204 autoscroll,
15205 window,
15206 cx,
15207 );
15208 } else {
15209 select_next_state.done = true;
15210 }
15211 }
15212
15213 self.select_next_state = Some(select_next_state);
15214 } else {
15215 let mut only_carets = true;
15216 let mut same_text_selected = true;
15217 let mut selected_text = None;
15218
15219 let mut selections_iter = selections.iter().peekable();
15220 while let Some(selection) = selections_iter.next() {
15221 if selection.start != selection.end {
15222 only_carets = false;
15223 }
15224
15225 if same_text_selected {
15226 if selected_text.is_none() {
15227 selected_text =
15228 Some(buffer.text_for_range(selection.range()).collect::<String>());
15229 }
15230
15231 if let Some(next_selection) = selections_iter.peek() {
15232 if next_selection.len() == selection.len() {
15233 let next_selected_text = buffer
15234 .text_for_range(next_selection.range())
15235 .collect::<String>();
15236 if Some(next_selected_text) != selected_text {
15237 same_text_selected = false;
15238 selected_text = None;
15239 }
15240 } else {
15241 same_text_selected = false;
15242 selected_text = None;
15243 }
15244 }
15245 }
15246 }
15247
15248 if only_carets {
15249 for selection in &mut selections {
15250 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15251 selection.start = word_range.start;
15252 selection.end = word_range.end;
15253 selection.goal = SelectionGoal::None;
15254 selection.reversed = false;
15255 self.select_match_ranges(
15256 selection.start..selection.end,
15257 selection.reversed,
15258 replace_newest,
15259 autoscroll,
15260 window,
15261 cx,
15262 );
15263 }
15264
15265 if selections.len() == 1 {
15266 let selection = selections
15267 .last()
15268 .expect("ensured that there's only one selection");
15269 let query = buffer
15270 .text_for_range(selection.start..selection.end)
15271 .collect::<String>();
15272 let is_empty = query.is_empty();
15273 let select_state = SelectNextState {
15274 query: self.build_query(&[query], cx)?,
15275 wordwise: true,
15276 done: is_empty,
15277 };
15278 self.select_next_state = Some(select_state);
15279 } else {
15280 self.select_next_state = None;
15281 }
15282 } else if let Some(selected_text) = selected_text {
15283 self.select_next_state = Some(SelectNextState {
15284 query: self.build_query(&[selected_text], cx)?,
15285 wordwise: false,
15286 done: false,
15287 });
15288 self.select_next_match_internal(
15289 display_map,
15290 replace_newest,
15291 autoscroll,
15292 window,
15293 cx,
15294 )?;
15295 }
15296 }
15297 Ok(())
15298 }
15299
15300 pub fn select_all_matches(
15301 &mut self,
15302 _action: &SelectAllMatches,
15303 window: &mut Window,
15304 cx: &mut Context<Self>,
15305 ) -> Result<()> {
15306 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15307
15308 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15309
15310 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15311 let Some(select_next_state) = self.select_next_state.as_mut() else {
15312 return Ok(());
15313 };
15314 if select_next_state.done {
15315 return Ok(());
15316 }
15317
15318 let mut new_selections = Vec::new();
15319
15320 let reversed = self
15321 .selections
15322 .oldest::<MultiBufferOffset>(&display_map)
15323 .reversed;
15324 let buffer = display_map.buffer_snapshot();
15325 let query_matches = select_next_state
15326 .query
15327 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15328
15329 for query_match in query_matches.into_iter() {
15330 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15331 let offset_range = if reversed {
15332 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15333 } else {
15334 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15335 };
15336
15337 if !select_next_state.wordwise
15338 || (!buffer.is_inside_word(offset_range.start, None)
15339 && !buffer.is_inside_word(offset_range.end, None))
15340 {
15341 new_selections.push(offset_range.start..offset_range.end);
15342 }
15343 }
15344
15345 select_next_state.done = true;
15346
15347 if new_selections.is_empty() {
15348 log::error!("bug: new_selections is empty in select_all_matches");
15349 return Ok(());
15350 }
15351
15352 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15353 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15354 selections.select_ranges(new_selections)
15355 });
15356
15357 Ok(())
15358 }
15359
15360 pub fn select_next(
15361 &mut self,
15362 action: &SelectNext,
15363 window: &mut Window,
15364 cx: &mut Context<Self>,
15365 ) -> Result<()> {
15366 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15367 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15368 self.select_next_match_internal(
15369 &display_map,
15370 action.replace_newest,
15371 Some(Autoscroll::newest()),
15372 window,
15373 cx,
15374 )?;
15375 Ok(())
15376 }
15377
15378 pub fn select_previous(
15379 &mut self,
15380 action: &SelectPrevious,
15381 window: &mut Window,
15382 cx: &mut Context<Self>,
15383 ) -> Result<()> {
15384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15385 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15386 let buffer = display_map.buffer_snapshot();
15387 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15388 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15389 let query = &select_prev_state.query;
15390 if !select_prev_state.done {
15391 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15392 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15393 let mut next_selected_range = None;
15394 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15395 let bytes_before_last_selection =
15396 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15397 let bytes_after_first_selection =
15398 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15399 let query_matches = query
15400 .stream_find_iter(bytes_before_last_selection)
15401 .map(|result| (last_selection.start, result))
15402 .chain(
15403 query
15404 .stream_find_iter(bytes_after_first_selection)
15405 .map(|result| (buffer.len(), result)),
15406 );
15407 for (end_offset, query_match) in query_matches {
15408 let query_match = query_match.unwrap(); // can only fail due to I/O
15409 let offset_range =
15410 end_offset - query_match.end()..end_offset - query_match.start();
15411
15412 if !select_prev_state.wordwise
15413 || (!buffer.is_inside_word(offset_range.start, None)
15414 && !buffer.is_inside_word(offset_range.end, None))
15415 {
15416 next_selected_range = Some(offset_range);
15417 break;
15418 }
15419 }
15420
15421 if let Some(next_selected_range) = next_selected_range {
15422 self.select_match_ranges(
15423 next_selected_range,
15424 last_selection.reversed,
15425 action.replace_newest,
15426 Some(Autoscroll::newest()),
15427 window,
15428 cx,
15429 );
15430 } else {
15431 select_prev_state.done = true;
15432 }
15433 }
15434
15435 self.select_prev_state = Some(select_prev_state);
15436 } else {
15437 let mut only_carets = true;
15438 let mut same_text_selected = true;
15439 let mut selected_text = None;
15440
15441 let mut selections_iter = selections.iter().peekable();
15442 while let Some(selection) = selections_iter.next() {
15443 if selection.start != selection.end {
15444 only_carets = false;
15445 }
15446
15447 if same_text_selected {
15448 if selected_text.is_none() {
15449 selected_text =
15450 Some(buffer.text_for_range(selection.range()).collect::<String>());
15451 }
15452
15453 if let Some(next_selection) = selections_iter.peek() {
15454 if next_selection.len() == selection.len() {
15455 let next_selected_text = buffer
15456 .text_for_range(next_selection.range())
15457 .collect::<String>();
15458 if Some(next_selected_text) != selected_text {
15459 same_text_selected = false;
15460 selected_text = None;
15461 }
15462 } else {
15463 same_text_selected = false;
15464 selected_text = None;
15465 }
15466 }
15467 }
15468 }
15469
15470 if only_carets {
15471 for selection in &mut selections {
15472 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15473 selection.start = word_range.start;
15474 selection.end = word_range.end;
15475 selection.goal = SelectionGoal::None;
15476 selection.reversed = false;
15477 self.select_match_ranges(
15478 selection.start..selection.end,
15479 selection.reversed,
15480 action.replace_newest,
15481 Some(Autoscroll::newest()),
15482 window,
15483 cx,
15484 );
15485 }
15486 if selections.len() == 1 {
15487 let selection = selections
15488 .last()
15489 .expect("ensured that there's only one selection");
15490 let query = buffer
15491 .text_for_range(selection.start..selection.end)
15492 .collect::<String>();
15493 let is_empty = query.is_empty();
15494 let select_state = SelectNextState {
15495 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15496 wordwise: true,
15497 done: is_empty,
15498 };
15499 self.select_prev_state = Some(select_state);
15500 } else {
15501 self.select_prev_state = None;
15502 }
15503 } else if let Some(selected_text) = selected_text {
15504 self.select_prev_state = Some(SelectNextState {
15505 query: self
15506 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15507 wordwise: false,
15508 done: false,
15509 });
15510 self.select_previous(action, window, cx)?;
15511 }
15512 }
15513 Ok(())
15514 }
15515
15516 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15517 /// setting the case sensitivity based on the global
15518 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15519 /// editor's settings.
15520 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15521 where
15522 I: IntoIterator<Item = P>,
15523 P: AsRef<[u8]>,
15524 {
15525 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15526 || EditorSettings::get_global(cx).search.case_sensitive,
15527 |value| value,
15528 );
15529
15530 let mut builder = AhoCorasickBuilder::new();
15531 builder.ascii_case_insensitive(!case_sensitive);
15532 builder.build(patterns)
15533 }
15534
15535 pub fn find_next_match(
15536 &mut self,
15537 _: &FindNextMatch,
15538 window: &mut Window,
15539 cx: &mut Context<Self>,
15540 ) -> Result<()> {
15541 let selections = self.selections.disjoint_anchors_arc();
15542 match selections.first() {
15543 Some(first) if selections.len() >= 2 => {
15544 self.change_selections(Default::default(), window, cx, |s| {
15545 s.select_ranges([first.range()]);
15546 });
15547 }
15548 _ => self.select_next(
15549 &SelectNext {
15550 replace_newest: true,
15551 },
15552 window,
15553 cx,
15554 )?,
15555 }
15556 Ok(())
15557 }
15558
15559 pub fn find_previous_match(
15560 &mut self,
15561 _: &FindPreviousMatch,
15562 window: &mut Window,
15563 cx: &mut Context<Self>,
15564 ) -> Result<()> {
15565 let selections = self.selections.disjoint_anchors_arc();
15566 match selections.last() {
15567 Some(last) if selections.len() >= 2 => {
15568 self.change_selections(Default::default(), window, cx, |s| {
15569 s.select_ranges([last.range()]);
15570 });
15571 }
15572 _ => self.select_previous(
15573 &SelectPrevious {
15574 replace_newest: true,
15575 },
15576 window,
15577 cx,
15578 )?,
15579 }
15580 Ok(())
15581 }
15582
15583 pub fn toggle_comments(
15584 &mut self,
15585 action: &ToggleComments,
15586 window: &mut Window,
15587 cx: &mut Context<Self>,
15588 ) {
15589 if self.read_only(cx) {
15590 return;
15591 }
15592 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15593 let text_layout_details = &self.text_layout_details(window);
15594 self.transact(window, cx, |this, window, cx| {
15595 let mut selections = this
15596 .selections
15597 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15598 let mut edits = Vec::new();
15599 let mut selection_edit_ranges = Vec::new();
15600 let mut last_toggled_row = None;
15601 let snapshot = this.buffer.read(cx).read(cx);
15602 let empty_str: Arc<str> = Arc::default();
15603 let mut suffixes_inserted = Vec::new();
15604 let ignore_indent = action.ignore_indent;
15605
15606 fn comment_prefix_range(
15607 snapshot: &MultiBufferSnapshot,
15608 row: MultiBufferRow,
15609 comment_prefix: &str,
15610 comment_prefix_whitespace: &str,
15611 ignore_indent: bool,
15612 ) -> Range<Point> {
15613 let indent_size = if ignore_indent {
15614 0
15615 } else {
15616 snapshot.indent_size_for_line(row).len
15617 };
15618
15619 let start = Point::new(row.0, indent_size);
15620
15621 let mut line_bytes = snapshot
15622 .bytes_in_range(start..snapshot.max_point())
15623 .flatten()
15624 .copied();
15625
15626 // If this line currently begins with the line comment prefix, then record
15627 // the range containing the prefix.
15628 if line_bytes
15629 .by_ref()
15630 .take(comment_prefix.len())
15631 .eq(comment_prefix.bytes())
15632 {
15633 // Include any whitespace that matches the comment prefix.
15634 let matching_whitespace_len = line_bytes
15635 .zip(comment_prefix_whitespace.bytes())
15636 .take_while(|(a, b)| a == b)
15637 .count() as u32;
15638 let end = Point::new(
15639 start.row,
15640 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15641 );
15642 start..end
15643 } else {
15644 start..start
15645 }
15646 }
15647
15648 fn comment_suffix_range(
15649 snapshot: &MultiBufferSnapshot,
15650 row: MultiBufferRow,
15651 comment_suffix: &str,
15652 comment_suffix_has_leading_space: bool,
15653 ) -> Range<Point> {
15654 let end = Point::new(row.0, snapshot.line_len(row));
15655 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15656
15657 let mut line_end_bytes = snapshot
15658 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15659 .flatten()
15660 .copied();
15661
15662 let leading_space_len = if suffix_start_column > 0
15663 && line_end_bytes.next() == Some(b' ')
15664 && comment_suffix_has_leading_space
15665 {
15666 1
15667 } else {
15668 0
15669 };
15670
15671 // If this line currently begins with the line comment prefix, then record
15672 // the range containing the prefix.
15673 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15674 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15675 start..end
15676 } else {
15677 end..end
15678 }
15679 }
15680
15681 // TODO: Handle selections that cross excerpts
15682 for selection in &mut selections {
15683 let start_column = snapshot
15684 .indent_size_for_line(MultiBufferRow(selection.start.row))
15685 .len;
15686 let language = if let Some(language) =
15687 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15688 {
15689 language
15690 } else {
15691 continue;
15692 };
15693
15694 selection_edit_ranges.clear();
15695
15696 // If multiple selections contain a given row, avoid processing that
15697 // row more than once.
15698 let mut start_row = MultiBufferRow(selection.start.row);
15699 if last_toggled_row == Some(start_row) {
15700 start_row = start_row.next_row();
15701 }
15702 let end_row =
15703 if selection.end.row > selection.start.row && selection.end.column == 0 {
15704 MultiBufferRow(selection.end.row - 1)
15705 } else {
15706 MultiBufferRow(selection.end.row)
15707 };
15708 last_toggled_row = Some(end_row);
15709
15710 if start_row > end_row {
15711 continue;
15712 }
15713
15714 // If the language has line comments, toggle those.
15715 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15716
15717 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15718 if ignore_indent {
15719 full_comment_prefixes = full_comment_prefixes
15720 .into_iter()
15721 .map(|s| Arc::from(s.trim_end()))
15722 .collect();
15723 }
15724
15725 if !full_comment_prefixes.is_empty() {
15726 let first_prefix = full_comment_prefixes
15727 .first()
15728 .expect("prefixes is non-empty");
15729 let prefix_trimmed_lengths = full_comment_prefixes
15730 .iter()
15731 .map(|p| p.trim_end_matches(' ').len())
15732 .collect::<SmallVec<[usize; 4]>>();
15733
15734 let mut all_selection_lines_are_comments = true;
15735
15736 for row in start_row.0..=end_row.0 {
15737 let row = MultiBufferRow(row);
15738 if start_row < end_row && snapshot.is_line_blank(row) {
15739 continue;
15740 }
15741
15742 let prefix_range = full_comment_prefixes
15743 .iter()
15744 .zip(prefix_trimmed_lengths.iter().copied())
15745 .map(|(prefix, trimmed_prefix_len)| {
15746 comment_prefix_range(
15747 snapshot.deref(),
15748 row,
15749 &prefix[..trimmed_prefix_len],
15750 &prefix[trimmed_prefix_len..],
15751 ignore_indent,
15752 )
15753 })
15754 .max_by_key(|range| range.end.column - range.start.column)
15755 .expect("prefixes is non-empty");
15756
15757 if prefix_range.is_empty() {
15758 all_selection_lines_are_comments = false;
15759 }
15760
15761 selection_edit_ranges.push(prefix_range);
15762 }
15763
15764 if all_selection_lines_are_comments {
15765 edits.extend(
15766 selection_edit_ranges
15767 .iter()
15768 .cloned()
15769 .map(|range| (range, empty_str.clone())),
15770 );
15771 } else {
15772 let min_column = selection_edit_ranges
15773 .iter()
15774 .map(|range| range.start.column)
15775 .min()
15776 .unwrap_or(0);
15777 edits.extend(selection_edit_ranges.iter().map(|range| {
15778 let position = Point::new(range.start.row, min_column);
15779 (position..position, first_prefix.clone())
15780 }));
15781 }
15782 } else if let Some(BlockCommentConfig {
15783 start: full_comment_prefix,
15784 end: comment_suffix,
15785 ..
15786 }) = language.block_comment()
15787 {
15788 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15789 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15790 let prefix_range = comment_prefix_range(
15791 snapshot.deref(),
15792 start_row,
15793 comment_prefix,
15794 comment_prefix_whitespace,
15795 ignore_indent,
15796 );
15797 let suffix_range = comment_suffix_range(
15798 snapshot.deref(),
15799 end_row,
15800 comment_suffix.trim_start_matches(' '),
15801 comment_suffix.starts_with(' '),
15802 );
15803
15804 if prefix_range.is_empty() || suffix_range.is_empty() {
15805 edits.push((
15806 prefix_range.start..prefix_range.start,
15807 full_comment_prefix.clone(),
15808 ));
15809 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15810 suffixes_inserted.push((end_row, comment_suffix.len()));
15811 } else {
15812 edits.push((prefix_range, empty_str.clone()));
15813 edits.push((suffix_range, empty_str.clone()));
15814 }
15815 } else {
15816 continue;
15817 }
15818 }
15819
15820 drop(snapshot);
15821 this.buffer.update(cx, |buffer, cx| {
15822 buffer.edit(edits, None, cx);
15823 });
15824
15825 // Adjust selections so that they end before any comment suffixes that
15826 // were inserted.
15827 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15828 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15829 let snapshot = this.buffer.read(cx).read(cx);
15830 for selection in &mut selections {
15831 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15832 match row.cmp(&MultiBufferRow(selection.end.row)) {
15833 Ordering::Less => {
15834 suffixes_inserted.next();
15835 continue;
15836 }
15837 Ordering::Greater => break,
15838 Ordering::Equal => {
15839 if selection.end.column == snapshot.line_len(row) {
15840 if selection.is_empty() {
15841 selection.start.column -= suffix_len as u32;
15842 }
15843 selection.end.column -= suffix_len as u32;
15844 }
15845 break;
15846 }
15847 }
15848 }
15849 }
15850
15851 drop(snapshot);
15852 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15853
15854 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15855 let selections_on_single_row = selections.windows(2).all(|selections| {
15856 selections[0].start.row == selections[1].start.row
15857 && selections[0].end.row == selections[1].end.row
15858 && selections[0].start.row == selections[0].end.row
15859 });
15860 let selections_selecting = selections
15861 .iter()
15862 .any(|selection| selection.start != selection.end);
15863 let advance_downwards = action.advance_downwards
15864 && selections_on_single_row
15865 && !selections_selecting
15866 && !matches!(this.mode, EditorMode::SingleLine);
15867
15868 if advance_downwards {
15869 let snapshot = this.buffer.read(cx).snapshot(cx);
15870
15871 this.change_selections(Default::default(), window, cx, |s| {
15872 s.move_cursors_with(|display_snapshot, display_point, _| {
15873 let mut point = display_point.to_point(display_snapshot);
15874 point.row += 1;
15875 point = snapshot.clip_point(point, Bias::Left);
15876 let display_point = point.to_display_point(display_snapshot);
15877 let goal = SelectionGoal::HorizontalPosition(
15878 display_snapshot
15879 .x_for_display_point(display_point, text_layout_details)
15880 .into(),
15881 );
15882 (display_point, goal)
15883 })
15884 });
15885 }
15886 });
15887 }
15888
15889 pub fn select_enclosing_symbol(
15890 &mut self,
15891 _: &SelectEnclosingSymbol,
15892 window: &mut Window,
15893 cx: &mut Context<Self>,
15894 ) {
15895 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15896
15897 let buffer = self.buffer.read(cx).snapshot(cx);
15898 let old_selections = self
15899 .selections
15900 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15901 .into_boxed_slice();
15902
15903 fn update_selection(
15904 selection: &Selection<MultiBufferOffset>,
15905 buffer_snap: &MultiBufferSnapshot,
15906 ) -> Option<Selection<MultiBufferOffset>> {
15907 let cursor = selection.head();
15908 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15909 for symbol in symbols.iter().rev() {
15910 let start = symbol.range.start.to_offset(buffer_snap);
15911 let end = symbol.range.end.to_offset(buffer_snap);
15912 let new_range = start..end;
15913 if start < selection.start || end > selection.end {
15914 return Some(Selection {
15915 id: selection.id,
15916 start: new_range.start,
15917 end: new_range.end,
15918 goal: SelectionGoal::None,
15919 reversed: selection.reversed,
15920 });
15921 }
15922 }
15923 None
15924 }
15925
15926 let mut selected_larger_symbol = false;
15927 let new_selections = old_selections
15928 .iter()
15929 .map(|selection| match update_selection(selection, &buffer) {
15930 Some(new_selection) => {
15931 if new_selection.range() != selection.range() {
15932 selected_larger_symbol = true;
15933 }
15934 new_selection
15935 }
15936 None => selection.clone(),
15937 })
15938 .collect::<Vec<_>>();
15939
15940 if selected_larger_symbol {
15941 self.change_selections(Default::default(), window, cx, |s| {
15942 s.select(new_selections);
15943 });
15944 }
15945 }
15946
15947 pub fn select_larger_syntax_node(
15948 &mut self,
15949 _: &SelectLargerSyntaxNode,
15950 window: &mut Window,
15951 cx: &mut Context<Self>,
15952 ) {
15953 let Some(visible_row_count) = self.visible_row_count() else {
15954 return;
15955 };
15956 let old_selections: Box<[_]> = self
15957 .selections
15958 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15959 .into();
15960 if old_selections.is_empty() {
15961 return;
15962 }
15963
15964 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15965
15966 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15967 let buffer = self.buffer.read(cx).snapshot(cx);
15968
15969 let mut selected_larger_node = false;
15970 let mut new_selections = old_selections
15971 .iter()
15972 .map(|selection| {
15973 let old_range = selection.start..selection.end;
15974
15975 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15976 // manually select word at selection
15977 if ["string_content", "inline"].contains(&node.kind()) {
15978 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15979 // ignore if word is already selected
15980 if !word_range.is_empty() && old_range != word_range {
15981 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15982 // only select word if start and end point belongs to same word
15983 if word_range == last_word_range {
15984 selected_larger_node = true;
15985 return Selection {
15986 id: selection.id,
15987 start: word_range.start,
15988 end: word_range.end,
15989 goal: SelectionGoal::None,
15990 reversed: selection.reversed,
15991 };
15992 }
15993 }
15994 }
15995 }
15996
15997 let mut new_range = old_range.clone();
15998 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15999 new_range = range;
16000 if !node.is_named() {
16001 continue;
16002 }
16003 if !display_map.intersects_fold(new_range.start)
16004 && !display_map.intersects_fold(new_range.end)
16005 {
16006 break;
16007 }
16008 }
16009
16010 selected_larger_node |= new_range != old_range;
16011 Selection {
16012 id: selection.id,
16013 start: new_range.start,
16014 end: new_range.end,
16015 goal: SelectionGoal::None,
16016 reversed: selection.reversed,
16017 }
16018 })
16019 .collect::<Vec<_>>();
16020
16021 if !selected_larger_node {
16022 return; // don't put this call in the history
16023 }
16024
16025 // scroll based on transformation done to the last selection created by the user
16026 let (last_old, last_new) = old_selections
16027 .last()
16028 .zip(new_selections.last().cloned())
16029 .expect("old_selections isn't empty");
16030
16031 // revert selection
16032 let is_selection_reversed = {
16033 let should_newest_selection_be_reversed = last_old.start != last_new.start;
16034 new_selections.last_mut().expect("checked above").reversed =
16035 should_newest_selection_be_reversed;
16036 should_newest_selection_be_reversed
16037 };
16038
16039 if selected_larger_node {
16040 self.select_syntax_node_history.disable_clearing = true;
16041 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16042 s.select(new_selections.clone());
16043 });
16044 self.select_syntax_node_history.disable_clearing = false;
16045 }
16046
16047 let start_row = last_new.start.to_display_point(&display_map).row().0;
16048 let end_row = last_new.end.to_display_point(&display_map).row().0;
16049 let selection_height = end_row - start_row + 1;
16050 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16051
16052 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16053 let scroll_behavior = if fits_on_the_screen {
16054 self.request_autoscroll(Autoscroll::fit(), cx);
16055 SelectSyntaxNodeScrollBehavior::FitSelection
16056 } else if is_selection_reversed {
16057 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16058 SelectSyntaxNodeScrollBehavior::CursorTop
16059 } else {
16060 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16061 SelectSyntaxNodeScrollBehavior::CursorBottom
16062 };
16063
16064 self.select_syntax_node_history.push((
16065 old_selections,
16066 scroll_behavior,
16067 is_selection_reversed,
16068 ));
16069 }
16070
16071 pub fn select_smaller_syntax_node(
16072 &mut self,
16073 _: &SelectSmallerSyntaxNode,
16074 window: &mut Window,
16075 cx: &mut Context<Self>,
16076 ) {
16077 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16078
16079 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16080 self.select_syntax_node_history.pop()
16081 {
16082 if let Some(selection) = selections.last_mut() {
16083 selection.reversed = is_selection_reversed;
16084 }
16085
16086 self.select_syntax_node_history.disable_clearing = true;
16087 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16088 s.select(selections.to_vec());
16089 });
16090 self.select_syntax_node_history.disable_clearing = false;
16091
16092 match scroll_behavior {
16093 SelectSyntaxNodeScrollBehavior::CursorTop => {
16094 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16095 }
16096 SelectSyntaxNodeScrollBehavior::FitSelection => {
16097 self.request_autoscroll(Autoscroll::fit(), cx);
16098 }
16099 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16100 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16101 }
16102 }
16103 }
16104 }
16105
16106 pub fn unwrap_syntax_node(
16107 &mut self,
16108 _: &UnwrapSyntaxNode,
16109 window: &mut Window,
16110 cx: &mut Context<Self>,
16111 ) {
16112 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16113
16114 let buffer = self.buffer.read(cx).snapshot(cx);
16115 let selections = self
16116 .selections
16117 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16118 .into_iter()
16119 // subtracting the offset requires sorting
16120 .sorted_by_key(|i| i.start);
16121
16122 let full_edits = selections
16123 .into_iter()
16124 .filter_map(|selection| {
16125 let child = if selection.is_empty()
16126 && let Some((_, ancestor_range)) =
16127 buffer.syntax_ancestor(selection.start..selection.end)
16128 {
16129 ancestor_range
16130 } else {
16131 selection.range()
16132 };
16133
16134 let mut parent = child.clone();
16135 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16136 parent = ancestor_range;
16137 if parent.start < child.start || parent.end > child.end {
16138 break;
16139 }
16140 }
16141
16142 if parent == child {
16143 return None;
16144 }
16145 let text = buffer.text_for_range(child).collect::<String>();
16146 Some((selection.id, parent, text))
16147 })
16148 .collect::<Vec<_>>();
16149 if full_edits.is_empty() {
16150 return;
16151 }
16152
16153 self.transact(window, cx, |this, window, cx| {
16154 this.buffer.update(cx, |buffer, cx| {
16155 buffer.edit(
16156 full_edits
16157 .iter()
16158 .map(|(_, p, t)| (p.clone(), t.clone()))
16159 .collect::<Vec<_>>(),
16160 None,
16161 cx,
16162 );
16163 });
16164 this.change_selections(Default::default(), window, cx, |s| {
16165 let mut offset = 0;
16166 let mut selections = vec![];
16167 for (id, parent, text) in full_edits {
16168 let start = parent.start - offset;
16169 offset += (parent.end - parent.start) - text.len();
16170 selections.push(Selection {
16171 id,
16172 start,
16173 end: start + text.len(),
16174 reversed: false,
16175 goal: Default::default(),
16176 });
16177 }
16178 s.select(selections);
16179 });
16180 });
16181 }
16182
16183 pub fn select_next_syntax_node(
16184 &mut self,
16185 _: &SelectNextSyntaxNode,
16186 window: &mut Window,
16187 cx: &mut Context<Self>,
16188 ) {
16189 let old_selections: Box<[_]> = self
16190 .selections
16191 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16192 .into();
16193 if old_selections.is_empty() {
16194 return;
16195 }
16196
16197 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16198
16199 let buffer = self.buffer.read(cx).snapshot(cx);
16200 let mut selected_sibling = false;
16201
16202 let new_selections = old_selections
16203 .iter()
16204 .map(|selection| {
16205 let old_range = selection.start..selection.end;
16206
16207 let old_range =
16208 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16209 let excerpt = buffer.excerpt_containing(old_range.clone());
16210
16211 if let Some(mut excerpt) = excerpt
16212 && let Some(node) = excerpt
16213 .buffer()
16214 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16215 {
16216 let new_range = excerpt.map_range_from_buffer(
16217 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16218 );
16219 selected_sibling = true;
16220 Selection {
16221 id: selection.id,
16222 start: new_range.start,
16223 end: new_range.end,
16224 goal: SelectionGoal::None,
16225 reversed: selection.reversed,
16226 }
16227 } else {
16228 selection.clone()
16229 }
16230 })
16231 .collect::<Vec<_>>();
16232
16233 if selected_sibling {
16234 self.change_selections(
16235 SelectionEffects::scroll(Autoscroll::fit()),
16236 window,
16237 cx,
16238 |s| {
16239 s.select(new_selections);
16240 },
16241 );
16242 }
16243 }
16244
16245 pub fn select_prev_syntax_node(
16246 &mut self,
16247 _: &SelectPreviousSyntaxNode,
16248 window: &mut Window,
16249 cx: &mut Context<Self>,
16250 ) {
16251 let old_selections: Box<[_]> = self
16252 .selections
16253 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16254 .into();
16255 if old_selections.is_empty() {
16256 return;
16257 }
16258
16259 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16260
16261 let buffer = self.buffer.read(cx).snapshot(cx);
16262 let mut selected_sibling = false;
16263
16264 let new_selections = old_selections
16265 .iter()
16266 .map(|selection| {
16267 let old_range = selection.start..selection.end;
16268 let old_range =
16269 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16270 let excerpt = buffer.excerpt_containing(old_range.clone());
16271
16272 if let Some(mut excerpt) = excerpt
16273 && let Some(node) = excerpt
16274 .buffer()
16275 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16276 {
16277 let new_range = excerpt.map_range_from_buffer(
16278 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16279 );
16280 selected_sibling = true;
16281 Selection {
16282 id: selection.id,
16283 start: new_range.start,
16284 end: new_range.end,
16285 goal: SelectionGoal::None,
16286 reversed: selection.reversed,
16287 }
16288 } else {
16289 selection.clone()
16290 }
16291 })
16292 .collect::<Vec<_>>();
16293
16294 if selected_sibling {
16295 self.change_selections(
16296 SelectionEffects::scroll(Autoscroll::fit()),
16297 window,
16298 cx,
16299 |s| {
16300 s.select(new_selections);
16301 },
16302 );
16303 }
16304 }
16305
16306 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16307 if !EditorSettings::get_global(cx).gutter.runnables {
16308 self.clear_tasks();
16309 return Task::ready(());
16310 }
16311 let project = self.project().map(Entity::downgrade);
16312 let task_sources = self.lsp_task_sources(cx);
16313 let multi_buffer = self.buffer.downgrade();
16314 cx.spawn_in(window, async move |editor, cx| {
16315 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16316 let Some(project) = project.and_then(|p| p.upgrade()) else {
16317 return;
16318 };
16319 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16320 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16321 }) else {
16322 return;
16323 };
16324
16325 let hide_runnables = project
16326 .update(cx, |project, _| project.is_via_collab())
16327 .unwrap_or(true);
16328 if hide_runnables {
16329 return;
16330 }
16331 let new_rows =
16332 cx.background_spawn({
16333 let snapshot = display_snapshot.clone();
16334 async move {
16335 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16336 }
16337 })
16338 .await;
16339 let Ok(lsp_tasks) =
16340 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16341 else {
16342 return;
16343 };
16344 let lsp_tasks = lsp_tasks.await;
16345
16346 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16347 lsp_tasks
16348 .into_iter()
16349 .flat_map(|(kind, tasks)| {
16350 tasks.into_iter().filter_map(move |(location, task)| {
16351 Some((kind.clone(), location?, task))
16352 })
16353 })
16354 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16355 let buffer = location.target.buffer;
16356 let buffer_snapshot = buffer.read(cx).snapshot();
16357 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16358 |(excerpt_id, snapshot, _)| {
16359 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16360 display_snapshot
16361 .buffer_snapshot()
16362 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16363 } else {
16364 None
16365 }
16366 },
16367 );
16368 if let Some(offset) = offset {
16369 let task_buffer_range =
16370 location.target.range.to_point(&buffer_snapshot);
16371 let context_buffer_range =
16372 task_buffer_range.to_offset(&buffer_snapshot);
16373 let context_range = BufferOffset(context_buffer_range.start)
16374 ..BufferOffset(context_buffer_range.end);
16375
16376 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16377 .or_insert_with(|| RunnableTasks {
16378 templates: Vec::new(),
16379 offset,
16380 column: task_buffer_range.start.column,
16381 extra_variables: HashMap::default(),
16382 context_range,
16383 })
16384 .templates
16385 .push((kind, task.original_task().clone()));
16386 }
16387
16388 acc
16389 })
16390 }) else {
16391 return;
16392 };
16393
16394 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16395 buffer.language_settings(cx).tasks.prefer_lsp
16396 }) else {
16397 return;
16398 };
16399
16400 let rows = Self::runnable_rows(
16401 project,
16402 display_snapshot,
16403 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16404 new_rows,
16405 cx.clone(),
16406 )
16407 .await;
16408 editor
16409 .update(cx, |editor, _| {
16410 editor.clear_tasks();
16411 for (key, mut value) in rows {
16412 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16413 value.templates.extend(lsp_tasks.templates);
16414 }
16415
16416 editor.insert_tasks(key, value);
16417 }
16418 for (key, value) in lsp_tasks_by_rows {
16419 editor.insert_tasks(key, value);
16420 }
16421 })
16422 .ok();
16423 })
16424 }
16425 fn fetch_runnable_ranges(
16426 snapshot: &DisplaySnapshot,
16427 range: Range<Anchor>,
16428 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16429 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16430 }
16431
16432 fn runnable_rows(
16433 project: Entity<Project>,
16434 snapshot: DisplaySnapshot,
16435 prefer_lsp: bool,
16436 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16437 cx: AsyncWindowContext,
16438 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16439 cx.spawn(async move |cx| {
16440 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16441 for (run_range, mut runnable) in runnable_ranges {
16442 let Some(tasks) = cx
16443 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16444 .ok()
16445 else {
16446 continue;
16447 };
16448 let mut tasks = tasks.await;
16449
16450 if prefer_lsp {
16451 tasks.retain(|(task_kind, _)| {
16452 !matches!(task_kind, TaskSourceKind::Language { .. })
16453 });
16454 }
16455 if tasks.is_empty() {
16456 continue;
16457 }
16458
16459 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16460 let Some(row) = snapshot
16461 .buffer_snapshot()
16462 .buffer_line_for_row(MultiBufferRow(point.row))
16463 .map(|(_, range)| range.start.row)
16464 else {
16465 continue;
16466 };
16467
16468 let context_range =
16469 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16470 runnable_rows.push((
16471 (runnable.buffer_id, row),
16472 RunnableTasks {
16473 templates: tasks,
16474 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16475 context_range,
16476 column: point.column,
16477 extra_variables: runnable.extra_captures,
16478 },
16479 ));
16480 }
16481 runnable_rows
16482 })
16483 }
16484
16485 fn templates_with_tags(
16486 project: &Entity<Project>,
16487 runnable: &mut Runnable,
16488 cx: &mut App,
16489 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16490 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16491 let (worktree_id, file) = project
16492 .buffer_for_id(runnable.buffer, cx)
16493 .and_then(|buffer| buffer.read(cx).file())
16494 .map(|file| (file.worktree_id(cx), file.clone()))
16495 .unzip();
16496
16497 (
16498 project.task_store().read(cx).task_inventory().cloned(),
16499 worktree_id,
16500 file,
16501 )
16502 });
16503
16504 let tags = mem::take(&mut runnable.tags);
16505 let language = runnable.language.clone();
16506 cx.spawn(async move |cx| {
16507 let mut templates_with_tags = Vec::new();
16508 if let Some(inventory) = inventory {
16509 for RunnableTag(tag) in tags {
16510 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16511 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16512 }) else {
16513 return templates_with_tags;
16514 };
16515 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16516 move |(_, template)| {
16517 template.tags.iter().any(|source_tag| source_tag == &tag)
16518 },
16519 ));
16520 }
16521 }
16522 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16523
16524 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16525 // Strongest source wins; if we have worktree tag binding, prefer that to
16526 // global and language bindings;
16527 // if we have a global binding, prefer that to language binding.
16528 let first_mismatch = templates_with_tags
16529 .iter()
16530 .position(|(tag_source, _)| tag_source != leading_tag_source);
16531 if let Some(index) = first_mismatch {
16532 templates_with_tags.truncate(index);
16533 }
16534 }
16535
16536 templates_with_tags
16537 })
16538 }
16539
16540 pub fn move_to_enclosing_bracket(
16541 &mut self,
16542 _: &MoveToEnclosingBracket,
16543 window: &mut Window,
16544 cx: &mut Context<Self>,
16545 ) {
16546 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16547 self.change_selections(Default::default(), window, cx, |s| {
16548 s.move_offsets_with(|snapshot, selection| {
16549 let Some(enclosing_bracket_ranges) =
16550 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16551 else {
16552 return;
16553 };
16554
16555 let mut best_length = usize::MAX;
16556 let mut best_inside = false;
16557 let mut best_in_bracket_range = false;
16558 let mut best_destination = None;
16559 for (open, close) in enclosing_bracket_ranges {
16560 let close = close.to_inclusive();
16561 let length = *close.end() - open.start;
16562 let inside = selection.start >= open.end && selection.end <= *close.start();
16563 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16564 || close.contains(&selection.head());
16565
16566 // If best is next to a bracket and current isn't, skip
16567 if !in_bracket_range && best_in_bracket_range {
16568 continue;
16569 }
16570
16571 // Prefer smaller lengths unless best is inside and current isn't
16572 if length > best_length && (best_inside || !inside) {
16573 continue;
16574 }
16575
16576 best_length = length;
16577 best_inside = inside;
16578 best_in_bracket_range = in_bracket_range;
16579 best_destination = Some(
16580 if close.contains(&selection.start) && close.contains(&selection.end) {
16581 if inside { open.end } else { open.start }
16582 } else if inside {
16583 *close.start()
16584 } else {
16585 *close.end()
16586 },
16587 );
16588 }
16589
16590 if let Some(destination) = best_destination {
16591 selection.collapse_to(destination, SelectionGoal::None);
16592 }
16593 })
16594 });
16595 }
16596
16597 pub fn undo_selection(
16598 &mut self,
16599 _: &UndoSelection,
16600 window: &mut Window,
16601 cx: &mut Context<Self>,
16602 ) {
16603 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16604 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16605 self.selection_history.mode = SelectionHistoryMode::Undoing;
16606 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16607 this.end_selection(window, cx);
16608 this.change_selections(
16609 SelectionEffects::scroll(Autoscroll::newest()),
16610 window,
16611 cx,
16612 |s| s.select_anchors(entry.selections.to_vec()),
16613 );
16614 });
16615 self.selection_history.mode = SelectionHistoryMode::Normal;
16616
16617 self.select_next_state = entry.select_next_state;
16618 self.select_prev_state = entry.select_prev_state;
16619 self.add_selections_state = entry.add_selections_state;
16620 }
16621 }
16622
16623 pub fn redo_selection(
16624 &mut self,
16625 _: &RedoSelection,
16626 window: &mut Window,
16627 cx: &mut Context<Self>,
16628 ) {
16629 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16630 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16631 self.selection_history.mode = SelectionHistoryMode::Redoing;
16632 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16633 this.end_selection(window, cx);
16634 this.change_selections(
16635 SelectionEffects::scroll(Autoscroll::newest()),
16636 window,
16637 cx,
16638 |s| s.select_anchors(entry.selections.to_vec()),
16639 );
16640 });
16641 self.selection_history.mode = SelectionHistoryMode::Normal;
16642
16643 self.select_next_state = entry.select_next_state;
16644 self.select_prev_state = entry.select_prev_state;
16645 self.add_selections_state = entry.add_selections_state;
16646 }
16647 }
16648
16649 pub fn expand_excerpts(
16650 &mut self,
16651 action: &ExpandExcerpts,
16652 _: &mut Window,
16653 cx: &mut Context<Self>,
16654 ) {
16655 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16656 }
16657
16658 pub fn expand_excerpts_down(
16659 &mut self,
16660 action: &ExpandExcerptsDown,
16661 _: &mut Window,
16662 cx: &mut Context<Self>,
16663 ) {
16664 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16665 }
16666
16667 pub fn expand_excerpts_up(
16668 &mut self,
16669 action: &ExpandExcerptsUp,
16670 _: &mut Window,
16671 cx: &mut Context<Self>,
16672 ) {
16673 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16674 }
16675
16676 pub fn expand_excerpts_for_direction(
16677 &mut self,
16678 lines: u32,
16679 direction: ExpandExcerptDirection,
16680
16681 cx: &mut Context<Self>,
16682 ) {
16683 let selections = self.selections.disjoint_anchors_arc();
16684
16685 let lines = if lines == 0 {
16686 EditorSettings::get_global(cx).expand_excerpt_lines
16687 } else {
16688 lines
16689 };
16690
16691 self.buffer.update(cx, |buffer, cx| {
16692 let snapshot = buffer.snapshot(cx);
16693 let mut excerpt_ids = selections
16694 .iter()
16695 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16696 .collect::<Vec<_>>();
16697 excerpt_ids.sort();
16698 excerpt_ids.dedup();
16699 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16700 })
16701 }
16702
16703 pub fn expand_excerpt(
16704 &mut self,
16705 excerpt: ExcerptId,
16706 direction: ExpandExcerptDirection,
16707 window: &mut Window,
16708 cx: &mut Context<Self>,
16709 ) {
16710 let current_scroll_position = self.scroll_position(cx);
16711 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16712 let mut scroll = None;
16713
16714 if direction == ExpandExcerptDirection::Down {
16715 let multi_buffer = self.buffer.read(cx);
16716 let snapshot = multi_buffer.snapshot(cx);
16717 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16718 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16719 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16720 {
16721 let buffer_snapshot = buffer.read(cx).snapshot();
16722 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16723 let last_row = buffer_snapshot.max_point().row;
16724 let lines_below = last_row.saturating_sub(excerpt_end_row);
16725 if lines_below >= lines_to_expand {
16726 scroll = Some(
16727 current_scroll_position
16728 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16729 );
16730 }
16731 }
16732 }
16733 if direction == ExpandExcerptDirection::Up
16734 && self
16735 .buffer
16736 .read(cx)
16737 .snapshot(cx)
16738 .excerpt_before(excerpt)
16739 .is_none()
16740 {
16741 scroll = Some(current_scroll_position);
16742 }
16743
16744 self.buffer.update(cx, |buffer, cx| {
16745 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16746 });
16747
16748 if let Some(new_scroll_position) = scroll {
16749 self.set_scroll_position(new_scroll_position, window, cx);
16750 }
16751 }
16752
16753 pub fn go_to_singleton_buffer_point(
16754 &mut self,
16755 point: Point,
16756 window: &mut Window,
16757 cx: &mut Context<Self>,
16758 ) {
16759 self.go_to_singleton_buffer_range(point..point, window, cx);
16760 }
16761
16762 pub fn go_to_singleton_buffer_range(
16763 &mut self,
16764 range: Range<Point>,
16765 window: &mut Window,
16766 cx: &mut Context<Self>,
16767 ) {
16768 let multibuffer = self.buffer().read(cx);
16769 let Some(buffer) = multibuffer.as_singleton() else {
16770 return;
16771 };
16772 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16773 return;
16774 };
16775 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16776 return;
16777 };
16778 self.change_selections(
16779 SelectionEffects::default().nav_history(true),
16780 window,
16781 cx,
16782 |s| s.select_anchor_ranges([start..end]),
16783 );
16784 }
16785
16786 pub fn go_to_diagnostic(
16787 &mut self,
16788 action: &GoToDiagnostic,
16789 window: &mut Window,
16790 cx: &mut Context<Self>,
16791 ) {
16792 if !self.diagnostics_enabled() {
16793 return;
16794 }
16795 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16796 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16797 }
16798
16799 pub fn go_to_prev_diagnostic(
16800 &mut self,
16801 action: &GoToPreviousDiagnostic,
16802 window: &mut Window,
16803 cx: &mut Context<Self>,
16804 ) {
16805 if !self.diagnostics_enabled() {
16806 return;
16807 }
16808 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16809 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16810 }
16811
16812 pub fn go_to_diagnostic_impl(
16813 &mut self,
16814 direction: Direction,
16815 severity: GoToDiagnosticSeverityFilter,
16816 window: &mut Window,
16817 cx: &mut Context<Self>,
16818 ) {
16819 let buffer = self.buffer.read(cx).snapshot(cx);
16820 let selection = self
16821 .selections
16822 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16823
16824 let mut active_group_id = None;
16825 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16826 && active_group.active_range.start.to_offset(&buffer) == selection.start
16827 {
16828 active_group_id = Some(active_group.group_id);
16829 }
16830
16831 fn filtered<'a>(
16832 severity: GoToDiagnosticSeverityFilter,
16833 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16834 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16835 diagnostics
16836 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16837 .filter(|entry| entry.range.start != entry.range.end)
16838 .filter(|entry| !entry.diagnostic.is_unnecessary)
16839 }
16840
16841 let before = filtered(
16842 severity,
16843 buffer
16844 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16845 .filter(|entry| entry.range.start <= selection.start),
16846 );
16847 let after = filtered(
16848 severity,
16849 buffer
16850 .diagnostics_in_range(selection.start..buffer.len())
16851 .filter(|entry| entry.range.start >= selection.start),
16852 );
16853
16854 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16855 if direction == Direction::Prev {
16856 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16857 {
16858 for diagnostic in prev_diagnostics.into_iter().rev() {
16859 if diagnostic.range.start != selection.start
16860 || active_group_id
16861 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16862 {
16863 found = Some(diagnostic);
16864 break 'outer;
16865 }
16866 }
16867 }
16868 } else {
16869 for diagnostic in after.chain(before) {
16870 if diagnostic.range.start != selection.start
16871 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16872 {
16873 found = Some(diagnostic);
16874 break;
16875 }
16876 }
16877 }
16878 let Some(next_diagnostic) = found else {
16879 return;
16880 };
16881
16882 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16883 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16884 return;
16885 };
16886 let snapshot = self.snapshot(window, cx);
16887 if snapshot.intersects_fold(next_diagnostic.range.start) {
16888 self.unfold_ranges(
16889 std::slice::from_ref(&next_diagnostic.range),
16890 true,
16891 false,
16892 cx,
16893 );
16894 }
16895 self.change_selections(Default::default(), window, cx, |s| {
16896 s.select_ranges(vec![
16897 next_diagnostic.range.start..next_diagnostic.range.start,
16898 ])
16899 });
16900 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16901 self.refresh_edit_prediction(false, true, window, cx);
16902 }
16903
16904 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16905 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16906 let snapshot = self.snapshot(window, cx);
16907 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16908 self.go_to_hunk_before_or_after_position(
16909 &snapshot,
16910 selection.head(),
16911 Direction::Next,
16912 window,
16913 cx,
16914 );
16915 }
16916
16917 pub fn go_to_hunk_before_or_after_position(
16918 &mut self,
16919 snapshot: &EditorSnapshot,
16920 position: Point,
16921 direction: Direction,
16922 window: &mut Window,
16923 cx: &mut Context<Editor>,
16924 ) {
16925 let row = if direction == Direction::Next {
16926 self.hunk_after_position(snapshot, position)
16927 .map(|hunk| hunk.row_range.start)
16928 } else {
16929 self.hunk_before_position(snapshot, position)
16930 };
16931
16932 if let Some(row) = row {
16933 let destination = Point::new(row.0, 0);
16934 let autoscroll = Autoscroll::center();
16935
16936 self.unfold_ranges(&[destination..destination], false, false, cx);
16937 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16938 s.select_ranges([destination..destination]);
16939 });
16940 }
16941 }
16942
16943 fn hunk_after_position(
16944 &mut self,
16945 snapshot: &EditorSnapshot,
16946 position: Point,
16947 ) -> Option<MultiBufferDiffHunk> {
16948 snapshot
16949 .buffer_snapshot()
16950 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16951 .find(|hunk| hunk.row_range.start.0 > position.row)
16952 .or_else(|| {
16953 snapshot
16954 .buffer_snapshot()
16955 .diff_hunks_in_range(Point::zero()..position)
16956 .find(|hunk| hunk.row_range.end.0 < position.row)
16957 })
16958 }
16959
16960 fn go_to_prev_hunk(
16961 &mut self,
16962 _: &GoToPreviousHunk,
16963 window: &mut Window,
16964 cx: &mut Context<Self>,
16965 ) {
16966 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16967 let snapshot = self.snapshot(window, cx);
16968 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16969 self.go_to_hunk_before_or_after_position(
16970 &snapshot,
16971 selection.head(),
16972 Direction::Prev,
16973 window,
16974 cx,
16975 );
16976 }
16977
16978 fn hunk_before_position(
16979 &mut self,
16980 snapshot: &EditorSnapshot,
16981 position: Point,
16982 ) -> Option<MultiBufferRow> {
16983 snapshot
16984 .buffer_snapshot()
16985 .diff_hunk_before(position)
16986 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16987 }
16988
16989 fn go_to_next_change(
16990 &mut self,
16991 _: &GoToNextChange,
16992 window: &mut Window,
16993 cx: &mut Context<Self>,
16994 ) {
16995 if let Some(selections) = self
16996 .change_list
16997 .next_change(1, Direction::Next)
16998 .map(|s| s.to_vec())
16999 {
17000 self.change_selections(Default::default(), window, cx, |s| {
17001 let map = s.display_snapshot();
17002 s.select_display_ranges(selections.iter().map(|a| {
17003 let point = a.to_display_point(&map);
17004 point..point
17005 }))
17006 })
17007 }
17008 }
17009
17010 fn go_to_previous_change(
17011 &mut self,
17012 _: &GoToPreviousChange,
17013 window: &mut Window,
17014 cx: &mut Context<Self>,
17015 ) {
17016 if let Some(selections) = self
17017 .change_list
17018 .next_change(1, Direction::Prev)
17019 .map(|s| s.to_vec())
17020 {
17021 self.change_selections(Default::default(), window, cx, |s| {
17022 let map = s.display_snapshot();
17023 s.select_display_ranges(selections.iter().map(|a| {
17024 let point = a.to_display_point(&map);
17025 point..point
17026 }))
17027 })
17028 }
17029 }
17030
17031 pub fn go_to_next_document_highlight(
17032 &mut self,
17033 _: &GoToNextDocumentHighlight,
17034 window: &mut Window,
17035 cx: &mut Context<Self>,
17036 ) {
17037 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
17038 }
17039
17040 pub fn go_to_prev_document_highlight(
17041 &mut self,
17042 _: &GoToPreviousDocumentHighlight,
17043 window: &mut Window,
17044 cx: &mut Context<Self>,
17045 ) {
17046 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17047 }
17048
17049 pub fn go_to_document_highlight_before_or_after_position(
17050 &mut self,
17051 direction: Direction,
17052 window: &mut Window,
17053 cx: &mut Context<Editor>,
17054 ) {
17055 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17056 let snapshot = self.snapshot(window, cx);
17057 let buffer = &snapshot.buffer_snapshot();
17058 let position = self
17059 .selections
17060 .newest::<Point>(&snapshot.display_snapshot)
17061 .head();
17062 let anchor_position = buffer.anchor_after(position);
17063
17064 // Get all document highlights (both read and write)
17065 let mut all_highlights = Vec::new();
17066
17067 if let Some((_, read_highlights)) = self
17068 .background_highlights
17069 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17070 {
17071 all_highlights.extend(read_highlights.iter());
17072 }
17073
17074 if let Some((_, write_highlights)) = self
17075 .background_highlights
17076 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17077 {
17078 all_highlights.extend(write_highlights.iter());
17079 }
17080
17081 if all_highlights.is_empty() {
17082 return;
17083 }
17084
17085 // Sort highlights by position
17086 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17087
17088 let target_highlight = match direction {
17089 Direction::Next => {
17090 // Find the first highlight after the current position
17091 all_highlights
17092 .iter()
17093 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17094 }
17095 Direction::Prev => {
17096 // Find the last highlight before the current position
17097 all_highlights
17098 .iter()
17099 .rev()
17100 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17101 }
17102 };
17103
17104 if let Some(highlight) = target_highlight {
17105 let destination = highlight.start.to_point(buffer);
17106 let autoscroll = Autoscroll::center();
17107
17108 self.unfold_ranges(&[destination..destination], false, false, cx);
17109 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17110 s.select_ranges([destination..destination]);
17111 });
17112 }
17113 }
17114
17115 fn go_to_line<T: 'static>(
17116 &mut self,
17117 position: Anchor,
17118 highlight_color: Option<Hsla>,
17119 window: &mut Window,
17120 cx: &mut Context<Self>,
17121 ) {
17122 let snapshot = self.snapshot(window, cx).display_snapshot;
17123 let position = position.to_point(&snapshot.buffer_snapshot());
17124 let start = snapshot
17125 .buffer_snapshot()
17126 .clip_point(Point::new(position.row, 0), Bias::Left);
17127 let end = start + Point::new(1, 0);
17128 let start = snapshot.buffer_snapshot().anchor_before(start);
17129 let end = snapshot.buffer_snapshot().anchor_before(end);
17130
17131 self.highlight_rows::<T>(
17132 start..end,
17133 highlight_color
17134 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17135 Default::default(),
17136 cx,
17137 );
17138
17139 if self.buffer.read(cx).is_singleton() {
17140 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17141 }
17142 }
17143
17144 pub fn go_to_definition(
17145 &mut self,
17146 _: &GoToDefinition,
17147 window: &mut Window,
17148 cx: &mut Context<Self>,
17149 ) -> Task<Result<Navigated>> {
17150 let definition =
17151 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17152 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17153 cx.spawn_in(window, async move |editor, cx| {
17154 if definition.await? == Navigated::Yes {
17155 return Ok(Navigated::Yes);
17156 }
17157 match fallback_strategy {
17158 GoToDefinitionFallback::None => Ok(Navigated::No),
17159 GoToDefinitionFallback::FindAllReferences => {
17160 match editor.update_in(cx, |editor, window, cx| {
17161 editor.find_all_references(&FindAllReferences::default(), window, cx)
17162 })? {
17163 Some(references) => references.await,
17164 None => Ok(Navigated::No),
17165 }
17166 }
17167 }
17168 })
17169 }
17170
17171 pub fn go_to_declaration(
17172 &mut self,
17173 _: &GoToDeclaration,
17174 window: &mut Window,
17175 cx: &mut Context<Self>,
17176 ) -> Task<Result<Navigated>> {
17177 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17178 }
17179
17180 pub fn go_to_declaration_split(
17181 &mut self,
17182 _: &GoToDeclaration,
17183 window: &mut Window,
17184 cx: &mut Context<Self>,
17185 ) -> Task<Result<Navigated>> {
17186 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17187 }
17188
17189 pub fn go_to_implementation(
17190 &mut self,
17191 _: &GoToImplementation,
17192 window: &mut Window,
17193 cx: &mut Context<Self>,
17194 ) -> Task<Result<Navigated>> {
17195 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17196 }
17197
17198 pub fn go_to_implementation_split(
17199 &mut self,
17200 _: &GoToImplementationSplit,
17201 window: &mut Window,
17202 cx: &mut Context<Self>,
17203 ) -> Task<Result<Navigated>> {
17204 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17205 }
17206
17207 pub fn go_to_type_definition(
17208 &mut self,
17209 _: &GoToTypeDefinition,
17210 window: &mut Window,
17211 cx: &mut Context<Self>,
17212 ) -> Task<Result<Navigated>> {
17213 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17214 }
17215
17216 pub fn go_to_definition_split(
17217 &mut self,
17218 _: &GoToDefinitionSplit,
17219 window: &mut Window,
17220 cx: &mut Context<Self>,
17221 ) -> Task<Result<Navigated>> {
17222 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17223 }
17224
17225 pub fn go_to_type_definition_split(
17226 &mut self,
17227 _: &GoToTypeDefinitionSplit,
17228 window: &mut Window,
17229 cx: &mut Context<Self>,
17230 ) -> Task<Result<Navigated>> {
17231 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17232 }
17233
17234 fn go_to_definition_of_kind(
17235 &mut self,
17236 kind: GotoDefinitionKind,
17237 split: bool,
17238 window: &mut Window,
17239 cx: &mut Context<Self>,
17240 ) -> Task<Result<Navigated>> {
17241 let Some(provider) = self.semantics_provider.clone() else {
17242 return Task::ready(Ok(Navigated::No));
17243 };
17244 let head = self
17245 .selections
17246 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17247 .head();
17248 let buffer = self.buffer.read(cx);
17249 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17250 return Task::ready(Ok(Navigated::No));
17251 };
17252 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17253 return Task::ready(Ok(Navigated::No));
17254 };
17255
17256 cx.spawn_in(window, async move |editor, cx| {
17257 let Some(definitions) = definitions.await? else {
17258 return Ok(Navigated::No);
17259 };
17260 let navigated = editor
17261 .update_in(cx, |editor, window, cx| {
17262 editor.navigate_to_hover_links(
17263 Some(kind),
17264 definitions
17265 .into_iter()
17266 .filter(|location| {
17267 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17268 })
17269 .map(HoverLink::Text)
17270 .collect::<Vec<_>>(),
17271 split,
17272 window,
17273 cx,
17274 )
17275 })?
17276 .await?;
17277 anyhow::Ok(navigated)
17278 })
17279 }
17280
17281 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17282 let selection = self.selections.newest_anchor();
17283 let head = selection.head();
17284 let tail = selection.tail();
17285
17286 let Some((buffer, start_position)) =
17287 self.buffer.read(cx).text_anchor_for_position(head, cx)
17288 else {
17289 return;
17290 };
17291
17292 let end_position = if head != tail {
17293 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17294 return;
17295 };
17296 Some(pos)
17297 } else {
17298 None
17299 };
17300
17301 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17302 let url = if let Some(end_pos) = end_position {
17303 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17304 } else {
17305 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17306 };
17307
17308 if let Some(url) = url {
17309 cx.update(|window, cx| {
17310 if parse_zed_link(&url, cx).is_some() {
17311 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17312 } else {
17313 cx.open_url(&url);
17314 }
17315 })?;
17316 }
17317
17318 anyhow::Ok(())
17319 });
17320
17321 url_finder.detach();
17322 }
17323
17324 pub fn open_selected_filename(
17325 &mut self,
17326 _: &OpenSelectedFilename,
17327 window: &mut Window,
17328 cx: &mut Context<Self>,
17329 ) {
17330 let Some(workspace) = self.workspace() else {
17331 return;
17332 };
17333
17334 let position = self.selections.newest_anchor().head();
17335
17336 let Some((buffer, buffer_position)) =
17337 self.buffer.read(cx).text_anchor_for_position(position, cx)
17338 else {
17339 return;
17340 };
17341
17342 let project = self.project.clone();
17343
17344 cx.spawn_in(window, async move |_, cx| {
17345 let result = find_file(&buffer, project, buffer_position, cx).await;
17346
17347 if let Some((_, path)) = result {
17348 workspace
17349 .update_in(cx, |workspace, window, cx| {
17350 workspace.open_resolved_path(path, window, cx)
17351 })?
17352 .await?;
17353 }
17354 anyhow::Ok(())
17355 })
17356 .detach();
17357 }
17358
17359 pub(crate) fn navigate_to_hover_links(
17360 &mut self,
17361 kind: Option<GotoDefinitionKind>,
17362 definitions: Vec<HoverLink>,
17363 split: bool,
17364 window: &mut Window,
17365 cx: &mut Context<Editor>,
17366 ) -> Task<Result<Navigated>> {
17367 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17368 let mut first_url_or_file = None;
17369 let definitions: Vec<_> = definitions
17370 .into_iter()
17371 .filter_map(|def| match def {
17372 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17373 HoverLink::InlayHint(lsp_location, server_id) => {
17374 let computation =
17375 self.compute_target_location(lsp_location, server_id, window, cx);
17376 Some(cx.background_spawn(computation))
17377 }
17378 HoverLink::Url(url) => {
17379 first_url_or_file = Some(Either::Left(url));
17380 None
17381 }
17382 HoverLink::File(path) => {
17383 first_url_or_file = Some(Either::Right(path));
17384 None
17385 }
17386 })
17387 .collect();
17388
17389 let workspace = self.workspace();
17390
17391 cx.spawn_in(window, async move |editor, cx| {
17392 let locations: Vec<Location> = future::join_all(definitions)
17393 .await
17394 .into_iter()
17395 .filter_map(|location| location.transpose())
17396 .collect::<Result<_>>()
17397 .context("location tasks")?;
17398 let mut locations = cx.update(|_, cx| {
17399 locations
17400 .into_iter()
17401 .map(|location| {
17402 let buffer = location.buffer.read(cx);
17403 (location.buffer, location.range.to_point(buffer))
17404 })
17405 .into_group_map()
17406 })?;
17407 let mut num_locations = 0;
17408 for ranges in locations.values_mut() {
17409 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17410 ranges.dedup();
17411 num_locations += ranges.len();
17412 }
17413
17414 if num_locations > 1 {
17415 let tab_kind = match kind {
17416 Some(GotoDefinitionKind::Implementation) => "Implementations",
17417 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17418 Some(GotoDefinitionKind::Declaration) => "Declarations",
17419 Some(GotoDefinitionKind::Type) => "Types",
17420 };
17421 let title = editor
17422 .update_in(cx, |_, _, cx| {
17423 let target = locations
17424 .iter()
17425 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17426 .map(|(buffer, location)| {
17427 buffer
17428 .read(cx)
17429 .text_for_range(location.clone())
17430 .collect::<String>()
17431 })
17432 .filter(|text| !text.contains('\n'))
17433 .unique()
17434 .take(3)
17435 .join(", ");
17436 if target.is_empty() {
17437 tab_kind.to_owned()
17438 } else {
17439 format!("{tab_kind} for {target}")
17440 }
17441 })
17442 .context("buffer title")?;
17443
17444 let Some(workspace) = workspace else {
17445 return Ok(Navigated::No);
17446 };
17447
17448 let opened = workspace
17449 .update_in(cx, |workspace, window, cx| {
17450 let allow_preview = PreviewTabsSettings::get_global(cx)
17451 .enable_preview_multibuffer_from_code_navigation;
17452 Self::open_locations_in_multibuffer(
17453 workspace,
17454 locations,
17455 title,
17456 split,
17457 allow_preview,
17458 MultibufferSelectionMode::First,
17459 window,
17460 cx,
17461 )
17462 })
17463 .is_ok();
17464
17465 anyhow::Ok(Navigated::from_bool(opened))
17466 } else if num_locations == 0 {
17467 // If there is one url or file, open it directly
17468 match first_url_or_file {
17469 Some(Either::Left(url)) => {
17470 cx.update(|_, cx| cx.open_url(&url))?;
17471 Ok(Navigated::Yes)
17472 }
17473 Some(Either::Right(path)) => {
17474 // TODO(andrew): respect preview tab settings
17475 // `enable_keep_preview_on_code_navigation` and
17476 // `enable_preview_file_from_code_navigation`
17477 let Some(workspace) = workspace else {
17478 return Ok(Navigated::No);
17479 };
17480 workspace
17481 .update_in(cx, |workspace, window, cx| {
17482 workspace.open_resolved_path(path, window, cx)
17483 })?
17484 .await?;
17485 Ok(Navigated::Yes)
17486 }
17487 None => Ok(Navigated::No),
17488 }
17489 } else {
17490 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17491 let target_range = target_ranges.first().unwrap().clone();
17492
17493 editor.update_in(cx, |editor, window, cx| {
17494 let range = target_range.to_point(target_buffer.read(cx));
17495 let range = editor.range_for_match(&range);
17496 let range = collapse_multiline_range(range);
17497
17498 if !split
17499 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17500 {
17501 editor.go_to_singleton_buffer_range(range, window, cx);
17502 } else {
17503 let Some(workspace) = workspace else {
17504 return Navigated::No;
17505 };
17506 let pane = workspace.read(cx).active_pane().clone();
17507 window.defer(cx, move |window, cx| {
17508 let target_editor: Entity<Self> =
17509 workspace.update(cx, |workspace, cx| {
17510 let pane = if split {
17511 workspace.adjacent_pane(window, cx)
17512 } else {
17513 workspace.active_pane().clone()
17514 };
17515
17516 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17517 let keep_old_preview = preview_tabs_settings
17518 .enable_keep_preview_on_code_navigation;
17519 let allow_new_preview = preview_tabs_settings
17520 .enable_preview_file_from_code_navigation;
17521
17522 workspace.open_project_item(
17523 pane,
17524 target_buffer.clone(),
17525 true,
17526 true,
17527 keep_old_preview,
17528 allow_new_preview,
17529 window,
17530 cx,
17531 )
17532 });
17533 target_editor.update(cx, |target_editor, cx| {
17534 // When selecting a definition in a different buffer, disable the nav history
17535 // to avoid creating a history entry at the previous cursor location.
17536 pane.update(cx, |pane, _| pane.disable_history());
17537 target_editor.go_to_singleton_buffer_range(range, window, cx);
17538 pane.update(cx, |pane, _| pane.enable_history());
17539 });
17540 });
17541 }
17542 Navigated::Yes
17543 })
17544 }
17545 })
17546 }
17547
17548 fn compute_target_location(
17549 &self,
17550 lsp_location: lsp::Location,
17551 server_id: LanguageServerId,
17552 window: &mut Window,
17553 cx: &mut Context<Self>,
17554 ) -> Task<anyhow::Result<Option<Location>>> {
17555 let Some(project) = self.project.clone() else {
17556 return Task::ready(Ok(None));
17557 };
17558
17559 cx.spawn_in(window, async move |editor, cx| {
17560 let location_task = editor.update(cx, |_, cx| {
17561 project.update(cx, |project, cx| {
17562 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17563 })
17564 })?;
17565 let location = Some({
17566 let target_buffer_handle = location_task.await.context("open local buffer")?;
17567 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17568 let target_start = target_buffer
17569 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17570 let target_end = target_buffer
17571 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17572 target_buffer.anchor_after(target_start)
17573 ..target_buffer.anchor_before(target_end)
17574 })?;
17575 Location {
17576 buffer: target_buffer_handle,
17577 range,
17578 }
17579 });
17580 Ok(location)
17581 })
17582 }
17583
17584 fn go_to_next_reference(
17585 &mut self,
17586 _: &GoToNextReference,
17587 window: &mut Window,
17588 cx: &mut Context<Self>,
17589 ) {
17590 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17591 if let Some(task) = task {
17592 task.detach();
17593 };
17594 }
17595
17596 fn go_to_prev_reference(
17597 &mut self,
17598 _: &GoToPreviousReference,
17599 window: &mut Window,
17600 cx: &mut Context<Self>,
17601 ) {
17602 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17603 if let Some(task) = task {
17604 task.detach();
17605 };
17606 }
17607
17608 pub fn go_to_reference_before_or_after_position(
17609 &mut self,
17610 direction: Direction,
17611 count: usize,
17612 window: &mut Window,
17613 cx: &mut Context<Self>,
17614 ) -> Option<Task<Result<()>>> {
17615 let selection = self.selections.newest_anchor();
17616 let head = selection.head();
17617
17618 let multi_buffer = self.buffer.read(cx);
17619
17620 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17621 let workspace = self.workspace()?;
17622 let project = workspace.read(cx).project().clone();
17623 let references =
17624 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17625 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17626 let Some(locations) = references.await? else {
17627 return Ok(());
17628 };
17629
17630 if locations.is_empty() {
17631 // totally normal - the cursor may be on something which is not
17632 // a symbol (e.g. a keyword)
17633 log::info!("no references found under cursor");
17634 return Ok(());
17635 }
17636
17637 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17638
17639 let (locations, current_location_index) =
17640 multi_buffer.update(cx, |multi_buffer, cx| {
17641 let mut locations = locations
17642 .into_iter()
17643 .filter_map(|loc| {
17644 let start = multi_buffer.buffer_anchor_to_anchor(
17645 &loc.buffer,
17646 loc.range.start,
17647 cx,
17648 )?;
17649 let end = multi_buffer.buffer_anchor_to_anchor(
17650 &loc.buffer,
17651 loc.range.end,
17652 cx,
17653 )?;
17654 Some(start..end)
17655 })
17656 .collect::<Vec<_>>();
17657
17658 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17659 // There is an O(n) implementation, but given this list will be
17660 // small (usually <100 items), the extra O(log(n)) factor isn't
17661 // worth the (surprisingly large amount of) extra complexity.
17662 locations
17663 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17664
17665 let head_offset = head.to_offset(&multi_buffer_snapshot);
17666
17667 let current_location_index = locations.iter().position(|loc| {
17668 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17669 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17670 });
17671
17672 (locations, current_location_index)
17673 })?;
17674
17675 let Some(current_location_index) = current_location_index else {
17676 // This indicates something has gone wrong, because we already
17677 // handle the "no references" case above
17678 log::error!(
17679 "failed to find current reference under cursor. Total references: {}",
17680 locations.len()
17681 );
17682 return Ok(());
17683 };
17684
17685 let destination_location_index = match direction {
17686 Direction::Next => (current_location_index + count) % locations.len(),
17687 Direction::Prev => {
17688 (current_location_index + locations.len() - count % locations.len())
17689 % locations.len()
17690 }
17691 };
17692
17693 // TODO(cameron): is this needed?
17694 // the thinking is to avoid "jumping to the current location" (avoid
17695 // polluting "jumplist" in vim terms)
17696 if current_location_index == destination_location_index {
17697 return Ok(());
17698 }
17699
17700 let Range { start, end } = locations[destination_location_index];
17701
17702 editor.update_in(cx, |editor, window, cx| {
17703 let effects = SelectionEffects::default();
17704
17705 editor.unfold_ranges(&[start..end], false, false, cx);
17706 editor.change_selections(effects, window, cx, |s| {
17707 s.select_ranges([start..start]);
17708 });
17709 })?;
17710
17711 Ok(())
17712 }))
17713 }
17714
17715 pub fn find_all_references(
17716 &mut self,
17717 action: &FindAllReferences,
17718 window: &mut Window,
17719 cx: &mut Context<Self>,
17720 ) -> Option<Task<Result<Navigated>>> {
17721 let always_open_multibuffer = action.always_open_multibuffer;
17722 let selection = self.selections.newest_anchor();
17723 let multi_buffer = self.buffer.read(cx);
17724 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17725 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17726 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17727 let head = selection_offset.head();
17728
17729 let head_anchor = multi_buffer_snapshot.anchor_at(
17730 head,
17731 if head < selection_offset.tail() {
17732 Bias::Right
17733 } else {
17734 Bias::Left
17735 },
17736 );
17737
17738 match self
17739 .find_all_references_task_sources
17740 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17741 {
17742 Ok(_) => {
17743 log::info!(
17744 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17745 );
17746 return None;
17747 }
17748 Err(i) => {
17749 self.find_all_references_task_sources.insert(i, head_anchor);
17750 }
17751 }
17752
17753 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17754 let workspace = self.workspace()?;
17755 let project = workspace.read(cx).project().clone();
17756 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17757 Some(cx.spawn_in(window, async move |editor, cx| {
17758 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17759 if let Ok(i) = editor
17760 .find_all_references_task_sources
17761 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17762 {
17763 editor.find_all_references_task_sources.remove(i);
17764 }
17765 });
17766
17767 let Some(locations) = references.await? else {
17768 return anyhow::Ok(Navigated::No);
17769 };
17770 let mut locations = cx.update(|_, cx| {
17771 locations
17772 .into_iter()
17773 .map(|location| {
17774 let buffer = location.buffer.read(cx);
17775 (location.buffer, location.range.to_point(buffer))
17776 })
17777 // if special-casing the single-match case, remove ranges
17778 // that intersect current selection
17779 .filter(|(location_buffer, location)| {
17780 if always_open_multibuffer || &buffer != location_buffer {
17781 return true;
17782 }
17783
17784 !location.contains_inclusive(&selection_point.range())
17785 })
17786 .into_group_map()
17787 })?;
17788 if locations.is_empty() {
17789 return anyhow::Ok(Navigated::No);
17790 }
17791 for ranges in locations.values_mut() {
17792 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17793 ranges.dedup();
17794 }
17795 let mut num_locations = 0;
17796 for ranges in locations.values_mut() {
17797 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17798 ranges.dedup();
17799 num_locations += ranges.len();
17800 }
17801
17802 if num_locations == 1 && !always_open_multibuffer {
17803 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17804 let target_range = target_ranges.first().unwrap().clone();
17805
17806 return editor.update_in(cx, |editor, window, cx| {
17807 let range = target_range.to_point(target_buffer.read(cx));
17808 let range = editor.range_for_match(&range);
17809 let range = range.start..range.start;
17810
17811 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17812 editor.go_to_singleton_buffer_range(range, window, cx);
17813 } else {
17814 let pane = workspace.read(cx).active_pane().clone();
17815 window.defer(cx, move |window, cx| {
17816 let target_editor: Entity<Self> =
17817 workspace.update(cx, |workspace, cx| {
17818 let pane = workspace.active_pane().clone();
17819
17820 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17821 let keep_old_preview = preview_tabs_settings
17822 .enable_keep_preview_on_code_navigation;
17823 let allow_new_preview = preview_tabs_settings
17824 .enable_preview_file_from_code_navigation;
17825
17826 workspace.open_project_item(
17827 pane,
17828 target_buffer.clone(),
17829 true,
17830 true,
17831 keep_old_preview,
17832 allow_new_preview,
17833 window,
17834 cx,
17835 )
17836 });
17837 target_editor.update(cx, |target_editor, cx| {
17838 // When selecting a definition in a different buffer, disable the nav history
17839 // to avoid creating a history entry at the previous cursor location.
17840 pane.update(cx, |pane, _| pane.disable_history());
17841 target_editor.go_to_singleton_buffer_range(range, window, cx);
17842 pane.update(cx, |pane, _| pane.enable_history());
17843 });
17844 });
17845 }
17846 Navigated::No
17847 });
17848 }
17849
17850 workspace.update_in(cx, |workspace, window, cx| {
17851 let target = locations
17852 .iter()
17853 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17854 .map(|(buffer, location)| {
17855 buffer
17856 .read(cx)
17857 .text_for_range(location.clone())
17858 .collect::<String>()
17859 })
17860 .filter(|text| !text.contains('\n'))
17861 .unique()
17862 .take(3)
17863 .join(", ");
17864 let title = if target.is_empty() {
17865 "References".to_owned()
17866 } else {
17867 format!("References to {target}")
17868 };
17869 let allow_preview = PreviewTabsSettings::get_global(cx)
17870 .enable_preview_multibuffer_from_code_navigation;
17871 Self::open_locations_in_multibuffer(
17872 workspace,
17873 locations,
17874 title,
17875 false,
17876 allow_preview,
17877 MultibufferSelectionMode::First,
17878 window,
17879 cx,
17880 );
17881 Navigated::Yes
17882 })
17883 }))
17884 }
17885
17886 /// Opens a multibuffer with the given project locations in it.
17887 pub fn open_locations_in_multibuffer(
17888 workspace: &mut Workspace,
17889 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17890 title: String,
17891 split: bool,
17892 allow_preview: bool,
17893 multibuffer_selection_mode: MultibufferSelectionMode,
17894 window: &mut Window,
17895 cx: &mut Context<Workspace>,
17896 ) {
17897 if locations.is_empty() {
17898 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17899 return;
17900 }
17901
17902 let capability = workspace.project().read(cx).capability();
17903 let mut ranges = <Vec<Range<Anchor>>>::new();
17904
17905 // a key to find existing multibuffer editors with the same set of locations
17906 // to prevent us from opening more and more multibuffer tabs for searches and the like
17907 let mut key = (title.clone(), vec![]);
17908 let excerpt_buffer = cx.new(|cx| {
17909 let key = &mut key.1;
17910 let mut multibuffer = MultiBuffer::new(capability);
17911 for (buffer, mut ranges_for_buffer) in locations {
17912 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17913 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17914 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17915 PathKey::for_buffer(&buffer, cx),
17916 buffer.clone(),
17917 ranges_for_buffer,
17918 multibuffer_context_lines(cx),
17919 cx,
17920 );
17921 ranges.extend(new_ranges)
17922 }
17923
17924 multibuffer.with_title(title)
17925 });
17926 let existing = workspace.active_pane().update(cx, |pane, cx| {
17927 pane.items()
17928 .filter_map(|item| item.downcast::<Editor>())
17929 .find(|editor| {
17930 editor
17931 .read(cx)
17932 .lookup_key
17933 .as_ref()
17934 .and_then(|it| {
17935 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17936 })
17937 .is_some_and(|it| *it == key)
17938 })
17939 });
17940 let was_existing = existing.is_some();
17941 let editor = existing.unwrap_or_else(|| {
17942 cx.new(|cx| {
17943 let mut editor = Editor::for_multibuffer(
17944 excerpt_buffer,
17945 Some(workspace.project().clone()),
17946 window,
17947 cx,
17948 );
17949 editor.lookup_key = Some(Box::new(key));
17950 editor
17951 })
17952 });
17953 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17954 MultibufferSelectionMode::First => {
17955 if let Some(first_range) = ranges.first() {
17956 editor.change_selections(
17957 SelectionEffects::no_scroll(),
17958 window,
17959 cx,
17960 |selections| {
17961 selections.clear_disjoint();
17962 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17963 },
17964 );
17965 }
17966 editor.highlight_background::<Self>(
17967 &ranges,
17968 |_, theme| theme.colors().editor_highlighted_line_background,
17969 cx,
17970 );
17971 }
17972 MultibufferSelectionMode::All => {
17973 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17974 selections.clear_disjoint();
17975 selections.select_anchor_ranges(ranges);
17976 });
17977 }
17978 });
17979
17980 let item = Box::new(editor);
17981
17982 let pane = if split {
17983 workspace.adjacent_pane(window, cx)
17984 } else {
17985 workspace.active_pane().clone()
17986 };
17987 let activate_pane = split;
17988
17989 let mut destination_index = None;
17990 pane.update(cx, |pane, cx| {
17991 if allow_preview && !was_existing {
17992 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17993 }
17994 if was_existing && !allow_preview {
17995 pane.unpreview_item_if_preview(item.item_id());
17996 }
17997 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17998 });
17999 }
18000
18001 pub fn rename(
18002 &mut self,
18003 _: &Rename,
18004 window: &mut Window,
18005 cx: &mut Context<Self>,
18006 ) -> Option<Task<Result<()>>> {
18007 use language::ToOffset as _;
18008
18009 let provider = self.semantics_provider.clone()?;
18010 let selection = self.selections.newest_anchor().clone();
18011 let (cursor_buffer, cursor_buffer_position) = self
18012 .buffer
18013 .read(cx)
18014 .text_anchor_for_position(selection.head(), cx)?;
18015 let (tail_buffer, cursor_buffer_position_end) = self
18016 .buffer
18017 .read(cx)
18018 .text_anchor_for_position(selection.tail(), cx)?;
18019 if tail_buffer != cursor_buffer {
18020 return None;
18021 }
18022
18023 let snapshot = cursor_buffer.read(cx).snapshot();
18024 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
18025 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
18026 let prepare_rename = provider
18027 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
18028 .unwrap_or_else(|| Task::ready(Ok(None)));
18029 drop(snapshot);
18030
18031 Some(cx.spawn_in(window, async move |this, cx| {
18032 let rename_range = if let Some(range) = prepare_rename.await? {
18033 Some(range)
18034 } else {
18035 this.update(cx, |this, cx| {
18036 let buffer = this.buffer.read(cx).snapshot(cx);
18037 let mut buffer_highlights = this
18038 .document_highlights_for_position(selection.head(), &buffer)
18039 .filter(|highlight| {
18040 highlight.start.excerpt_id == selection.head().excerpt_id
18041 && highlight.end.excerpt_id == selection.head().excerpt_id
18042 });
18043 buffer_highlights
18044 .next()
18045 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18046 })?
18047 };
18048 if let Some(rename_range) = rename_range {
18049 this.update_in(cx, |this, window, cx| {
18050 let snapshot = cursor_buffer.read(cx).snapshot();
18051 let rename_buffer_range = rename_range.to_offset(&snapshot);
18052 let cursor_offset_in_rename_range =
18053 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18054 let cursor_offset_in_rename_range_end =
18055 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18056
18057 this.take_rename(false, window, cx);
18058 let buffer = this.buffer.read(cx).read(cx);
18059 let cursor_offset = selection.head().to_offset(&buffer);
18060 let rename_start =
18061 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18062 let rename_end = rename_start + rename_buffer_range.len();
18063 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18064 let mut old_highlight_id = None;
18065 let old_name: Arc<str> = buffer
18066 .chunks(rename_start..rename_end, true)
18067 .map(|chunk| {
18068 if old_highlight_id.is_none() {
18069 old_highlight_id = chunk.syntax_highlight_id;
18070 }
18071 chunk.text
18072 })
18073 .collect::<String>()
18074 .into();
18075
18076 drop(buffer);
18077
18078 // Position the selection in the rename editor so that it matches the current selection.
18079 this.show_local_selections = false;
18080 let rename_editor = cx.new(|cx| {
18081 let mut editor = Editor::single_line(window, cx);
18082 editor.buffer.update(cx, |buffer, cx| {
18083 buffer.edit(
18084 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18085 None,
18086 cx,
18087 )
18088 });
18089 let cursor_offset_in_rename_range =
18090 MultiBufferOffset(cursor_offset_in_rename_range);
18091 let cursor_offset_in_rename_range_end =
18092 MultiBufferOffset(cursor_offset_in_rename_range_end);
18093 let rename_selection_range = match cursor_offset_in_rename_range
18094 .cmp(&cursor_offset_in_rename_range_end)
18095 {
18096 Ordering::Equal => {
18097 editor.select_all(&SelectAll, window, cx);
18098 return editor;
18099 }
18100 Ordering::Less => {
18101 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18102 }
18103 Ordering::Greater => {
18104 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18105 }
18106 };
18107 if rename_selection_range.end.0 > old_name.len() {
18108 editor.select_all(&SelectAll, window, cx);
18109 } else {
18110 editor.change_selections(Default::default(), window, cx, |s| {
18111 s.select_ranges([rename_selection_range]);
18112 });
18113 }
18114 editor
18115 });
18116 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18117 if e == &EditorEvent::Focused {
18118 cx.emit(EditorEvent::FocusedIn)
18119 }
18120 })
18121 .detach();
18122
18123 let write_highlights =
18124 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18125 let read_highlights =
18126 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18127 let ranges = write_highlights
18128 .iter()
18129 .flat_map(|(_, ranges)| ranges.iter())
18130 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18131 .cloned()
18132 .collect();
18133
18134 this.highlight_text::<Rename>(
18135 ranges,
18136 HighlightStyle {
18137 fade_out: Some(0.6),
18138 ..Default::default()
18139 },
18140 cx,
18141 );
18142 let rename_focus_handle = rename_editor.focus_handle(cx);
18143 window.focus(&rename_focus_handle);
18144 let block_id = this.insert_blocks(
18145 [BlockProperties {
18146 style: BlockStyle::Flex,
18147 placement: BlockPlacement::Below(range.start),
18148 height: Some(1),
18149 render: Arc::new({
18150 let rename_editor = rename_editor.clone();
18151 move |cx: &mut BlockContext| {
18152 let mut text_style = cx.editor_style.text.clone();
18153 if let Some(highlight_style) = old_highlight_id
18154 .and_then(|h| h.style(&cx.editor_style.syntax))
18155 {
18156 text_style = text_style.highlight(highlight_style);
18157 }
18158 div()
18159 .block_mouse_except_scroll()
18160 .pl(cx.anchor_x)
18161 .child(EditorElement::new(
18162 &rename_editor,
18163 EditorStyle {
18164 background: cx.theme().system().transparent,
18165 local_player: cx.editor_style.local_player,
18166 text: text_style,
18167 scrollbar_width: cx.editor_style.scrollbar_width,
18168 syntax: cx.editor_style.syntax.clone(),
18169 status: cx.editor_style.status.clone(),
18170 inlay_hints_style: HighlightStyle {
18171 font_weight: Some(FontWeight::BOLD),
18172 ..make_inlay_hints_style(cx.app)
18173 },
18174 edit_prediction_styles: make_suggestion_styles(
18175 cx.app,
18176 ),
18177 ..EditorStyle::default()
18178 },
18179 ))
18180 .into_any_element()
18181 }
18182 }),
18183 priority: 0,
18184 }],
18185 Some(Autoscroll::fit()),
18186 cx,
18187 )[0];
18188 this.pending_rename = Some(RenameState {
18189 range,
18190 old_name,
18191 editor: rename_editor,
18192 block_id,
18193 });
18194 })?;
18195 }
18196
18197 Ok(())
18198 }))
18199 }
18200
18201 pub fn confirm_rename(
18202 &mut self,
18203 _: &ConfirmRename,
18204 window: &mut Window,
18205 cx: &mut Context<Self>,
18206 ) -> Option<Task<Result<()>>> {
18207 let rename = self.take_rename(false, window, cx)?;
18208 let workspace = self.workspace()?.downgrade();
18209 let (buffer, start) = self
18210 .buffer
18211 .read(cx)
18212 .text_anchor_for_position(rename.range.start, cx)?;
18213 let (end_buffer, _) = self
18214 .buffer
18215 .read(cx)
18216 .text_anchor_for_position(rename.range.end, cx)?;
18217 if buffer != end_buffer {
18218 return None;
18219 }
18220
18221 let old_name = rename.old_name;
18222 let new_name = rename.editor.read(cx).text(cx);
18223
18224 let rename = self.semantics_provider.as_ref()?.perform_rename(
18225 &buffer,
18226 start,
18227 new_name.clone(),
18228 cx,
18229 )?;
18230
18231 Some(cx.spawn_in(window, async move |editor, cx| {
18232 let project_transaction = rename.await?;
18233 Self::open_project_transaction(
18234 &editor,
18235 workspace,
18236 project_transaction,
18237 format!("Rename: {} → {}", old_name, new_name),
18238 cx,
18239 )
18240 .await?;
18241
18242 editor.update(cx, |editor, cx| {
18243 editor.refresh_document_highlights(cx);
18244 })?;
18245 Ok(())
18246 }))
18247 }
18248
18249 fn take_rename(
18250 &mut self,
18251 moving_cursor: bool,
18252 window: &mut Window,
18253 cx: &mut Context<Self>,
18254 ) -> Option<RenameState> {
18255 let rename = self.pending_rename.take()?;
18256 if rename.editor.focus_handle(cx).is_focused(window) {
18257 window.focus(&self.focus_handle);
18258 }
18259
18260 self.remove_blocks(
18261 [rename.block_id].into_iter().collect(),
18262 Some(Autoscroll::fit()),
18263 cx,
18264 );
18265 self.clear_highlights::<Rename>(cx);
18266 self.show_local_selections = true;
18267
18268 if moving_cursor {
18269 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18270 editor
18271 .selections
18272 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18273 .head()
18274 });
18275
18276 // Update the selection to match the position of the selection inside
18277 // the rename editor.
18278 let snapshot = self.buffer.read(cx).read(cx);
18279 let rename_range = rename.range.to_offset(&snapshot);
18280 let cursor_in_editor = snapshot
18281 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18282 .min(rename_range.end);
18283 drop(snapshot);
18284
18285 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18286 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18287 });
18288 } else {
18289 self.refresh_document_highlights(cx);
18290 }
18291
18292 Some(rename)
18293 }
18294
18295 pub fn pending_rename(&self) -> Option<&RenameState> {
18296 self.pending_rename.as_ref()
18297 }
18298
18299 fn format(
18300 &mut self,
18301 _: &Format,
18302 window: &mut Window,
18303 cx: &mut Context<Self>,
18304 ) -> Option<Task<Result<()>>> {
18305 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18306
18307 let project = match &self.project {
18308 Some(project) => project.clone(),
18309 None => return None,
18310 };
18311
18312 Some(self.perform_format(
18313 project,
18314 FormatTrigger::Manual,
18315 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18316 window,
18317 cx,
18318 ))
18319 }
18320
18321 fn format_selections(
18322 &mut self,
18323 _: &FormatSelections,
18324 window: &mut Window,
18325 cx: &mut Context<Self>,
18326 ) -> Option<Task<Result<()>>> {
18327 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18328
18329 let project = match &self.project {
18330 Some(project) => project.clone(),
18331 None => return None,
18332 };
18333
18334 let ranges = self
18335 .selections
18336 .all_adjusted(&self.display_snapshot(cx))
18337 .into_iter()
18338 .map(|selection| selection.range())
18339 .collect_vec();
18340
18341 Some(self.perform_format(
18342 project,
18343 FormatTrigger::Manual,
18344 FormatTarget::Ranges(ranges),
18345 window,
18346 cx,
18347 ))
18348 }
18349
18350 fn perform_format(
18351 &mut self,
18352 project: Entity<Project>,
18353 trigger: FormatTrigger,
18354 target: FormatTarget,
18355 window: &mut Window,
18356 cx: &mut Context<Self>,
18357 ) -> Task<Result<()>> {
18358 let buffer = self.buffer.clone();
18359 let (buffers, target) = match target {
18360 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18361 FormatTarget::Ranges(selection_ranges) => {
18362 let multi_buffer = buffer.read(cx);
18363 let snapshot = multi_buffer.read(cx);
18364 let mut buffers = HashSet::default();
18365 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18366 BTreeMap::new();
18367 for selection_range in selection_ranges {
18368 for (buffer, buffer_range, _) in
18369 snapshot.range_to_buffer_ranges(selection_range)
18370 {
18371 let buffer_id = buffer.remote_id();
18372 let start = buffer.anchor_before(buffer_range.start);
18373 let end = buffer.anchor_after(buffer_range.end);
18374 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18375 buffer_id_to_ranges
18376 .entry(buffer_id)
18377 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18378 .or_insert_with(|| vec![start..end]);
18379 }
18380 }
18381 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18382 }
18383 };
18384
18385 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18386 let selections_prev = transaction_id_prev
18387 .and_then(|transaction_id_prev| {
18388 // default to selections as they were after the last edit, if we have them,
18389 // instead of how they are now.
18390 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18391 // will take you back to where you made the last edit, instead of staying where you scrolled
18392 self.selection_history
18393 .transaction(transaction_id_prev)
18394 .map(|t| t.0.clone())
18395 })
18396 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18397
18398 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18399 let format = project.update(cx, |project, cx| {
18400 project.format(buffers, target, true, trigger, cx)
18401 });
18402
18403 cx.spawn_in(window, async move |editor, cx| {
18404 let transaction = futures::select_biased! {
18405 transaction = format.log_err().fuse() => transaction,
18406 () = timeout => {
18407 log::warn!("timed out waiting for formatting");
18408 None
18409 }
18410 };
18411
18412 buffer
18413 .update(cx, |buffer, cx| {
18414 if let Some(transaction) = transaction
18415 && !buffer.is_singleton()
18416 {
18417 buffer.push_transaction(&transaction.0, cx);
18418 }
18419 cx.notify();
18420 })
18421 .ok();
18422
18423 if let Some(transaction_id_now) =
18424 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18425 {
18426 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18427 if has_new_transaction {
18428 _ = editor.update(cx, |editor, _| {
18429 editor
18430 .selection_history
18431 .insert_transaction(transaction_id_now, selections_prev);
18432 });
18433 }
18434 }
18435
18436 Ok(())
18437 })
18438 }
18439
18440 fn organize_imports(
18441 &mut self,
18442 _: &OrganizeImports,
18443 window: &mut Window,
18444 cx: &mut Context<Self>,
18445 ) -> Option<Task<Result<()>>> {
18446 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18447 let project = match &self.project {
18448 Some(project) => project.clone(),
18449 None => return None,
18450 };
18451 Some(self.perform_code_action_kind(
18452 project,
18453 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18454 window,
18455 cx,
18456 ))
18457 }
18458
18459 fn perform_code_action_kind(
18460 &mut self,
18461 project: Entity<Project>,
18462 kind: CodeActionKind,
18463 window: &mut Window,
18464 cx: &mut Context<Self>,
18465 ) -> Task<Result<()>> {
18466 let buffer = self.buffer.clone();
18467 let buffers = buffer.read(cx).all_buffers();
18468 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18469 let apply_action = project.update(cx, |project, cx| {
18470 project.apply_code_action_kind(buffers, kind, true, cx)
18471 });
18472 cx.spawn_in(window, async move |_, cx| {
18473 let transaction = futures::select_biased! {
18474 () = timeout => {
18475 log::warn!("timed out waiting for executing code action");
18476 None
18477 }
18478 transaction = apply_action.log_err().fuse() => transaction,
18479 };
18480 buffer
18481 .update(cx, |buffer, cx| {
18482 // check if we need this
18483 if let Some(transaction) = transaction
18484 && !buffer.is_singleton()
18485 {
18486 buffer.push_transaction(&transaction.0, cx);
18487 }
18488 cx.notify();
18489 })
18490 .ok();
18491 Ok(())
18492 })
18493 }
18494
18495 pub fn restart_language_server(
18496 &mut self,
18497 _: &RestartLanguageServer,
18498 _: &mut Window,
18499 cx: &mut Context<Self>,
18500 ) {
18501 if let Some(project) = self.project.clone() {
18502 self.buffer.update(cx, |multi_buffer, cx| {
18503 project.update(cx, |project, cx| {
18504 project.restart_language_servers_for_buffers(
18505 multi_buffer.all_buffers().into_iter().collect(),
18506 HashSet::default(),
18507 cx,
18508 );
18509 });
18510 })
18511 }
18512 }
18513
18514 pub fn stop_language_server(
18515 &mut self,
18516 _: &StopLanguageServer,
18517 _: &mut Window,
18518 cx: &mut Context<Self>,
18519 ) {
18520 if let Some(project) = self.project.clone() {
18521 self.buffer.update(cx, |multi_buffer, cx| {
18522 project.update(cx, |project, cx| {
18523 project.stop_language_servers_for_buffers(
18524 multi_buffer.all_buffers().into_iter().collect(),
18525 HashSet::default(),
18526 cx,
18527 );
18528 });
18529 });
18530 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18531 }
18532 }
18533
18534 fn cancel_language_server_work(
18535 workspace: &mut Workspace,
18536 _: &actions::CancelLanguageServerWork,
18537 _: &mut Window,
18538 cx: &mut Context<Workspace>,
18539 ) {
18540 let project = workspace.project();
18541 let buffers = workspace
18542 .active_item(cx)
18543 .and_then(|item| item.act_as::<Editor>(cx))
18544 .map_or(HashSet::default(), |editor| {
18545 editor.read(cx).buffer.read(cx).all_buffers()
18546 });
18547 project.update(cx, |project, cx| {
18548 project.cancel_language_server_work_for_buffers(buffers, cx);
18549 });
18550 }
18551
18552 fn show_character_palette(
18553 &mut self,
18554 _: &ShowCharacterPalette,
18555 window: &mut Window,
18556 _: &mut Context<Self>,
18557 ) {
18558 window.show_character_palette();
18559 }
18560
18561 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18562 if !self.diagnostics_enabled() {
18563 return;
18564 }
18565
18566 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18567 let buffer = self.buffer.read(cx).snapshot(cx);
18568 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18569 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18570 let is_valid = buffer
18571 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18572 .any(|entry| {
18573 entry.diagnostic.is_primary
18574 && !entry.range.is_empty()
18575 && entry.range.start == primary_range_start
18576 && entry.diagnostic.message == active_diagnostics.active_message
18577 });
18578
18579 if !is_valid {
18580 self.dismiss_diagnostics(cx);
18581 }
18582 }
18583 }
18584
18585 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18586 match &self.active_diagnostics {
18587 ActiveDiagnostic::Group(group) => Some(group),
18588 _ => None,
18589 }
18590 }
18591
18592 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18593 if !self.diagnostics_enabled() {
18594 return;
18595 }
18596 self.dismiss_diagnostics(cx);
18597 self.active_diagnostics = ActiveDiagnostic::All;
18598 }
18599
18600 fn activate_diagnostics(
18601 &mut self,
18602 buffer_id: BufferId,
18603 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18604 window: &mut Window,
18605 cx: &mut Context<Self>,
18606 ) {
18607 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18608 return;
18609 }
18610 self.dismiss_diagnostics(cx);
18611 let snapshot = self.snapshot(window, cx);
18612 let buffer = self.buffer.read(cx).snapshot(cx);
18613 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18614 return;
18615 };
18616
18617 let diagnostic_group = buffer
18618 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18619 .collect::<Vec<_>>();
18620
18621 let language_registry = self
18622 .project()
18623 .map(|project| project.read(cx).languages().clone());
18624
18625 let blocks = renderer.render_group(
18626 diagnostic_group,
18627 buffer_id,
18628 snapshot,
18629 cx.weak_entity(),
18630 language_registry,
18631 cx,
18632 );
18633
18634 let blocks = self.display_map.update(cx, |display_map, cx| {
18635 display_map.insert_blocks(blocks, cx).into_iter().collect()
18636 });
18637 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18638 active_range: buffer.anchor_before(diagnostic.range.start)
18639 ..buffer.anchor_after(diagnostic.range.end),
18640 active_message: diagnostic.diagnostic.message.clone(),
18641 group_id: diagnostic.diagnostic.group_id,
18642 blocks,
18643 });
18644 cx.notify();
18645 }
18646
18647 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18648 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18649 return;
18650 };
18651
18652 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18653 if let ActiveDiagnostic::Group(group) = prev {
18654 self.display_map.update(cx, |display_map, cx| {
18655 display_map.remove_blocks(group.blocks, cx);
18656 });
18657 cx.notify();
18658 }
18659 }
18660
18661 /// Disable inline diagnostics rendering for this editor.
18662 pub fn disable_inline_diagnostics(&mut self) {
18663 self.inline_diagnostics_enabled = false;
18664 self.inline_diagnostics_update = Task::ready(());
18665 self.inline_diagnostics.clear();
18666 }
18667
18668 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18669 self.diagnostics_enabled = false;
18670 self.dismiss_diagnostics(cx);
18671 self.inline_diagnostics_update = Task::ready(());
18672 self.inline_diagnostics.clear();
18673 }
18674
18675 pub fn disable_word_completions(&mut self) {
18676 self.word_completions_enabled = false;
18677 }
18678
18679 pub fn diagnostics_enabled(&self) -> bool {
18680 self.diagnostics_enabled && self.mode.is_full()
18681 }
18682
18683 pub fn inline_diagnostics_enabled(&self) -> bool {
18684 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18685 }
18686
18687 pub fn show_inline_diagnostics(&self) -> bool {
18688 self.show_inline_diagnostics
18689 }
18690
18691 pub fn toggle_inline_diagnostics(
18692 &mut self,
18693 _: &ToggleInlineDiagnostics,
18694 window: &mut Window,
18695 cx: &mut Context<Editor>,
18696 ) {
18697 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18698 self.refresh_inline_diagnostics(false, window, cx);
18699 }
18700
18701 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18702 self.diagnostics_max_severity = severity;
18703 self.display_map.update(cx, |display_map, _| {
18704 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18705 });
18706 }
18707
18708 pub fn toggle_diagnostics(
18709 &mut self,
18710 _: &ToggleDiagnostics,
18711 window: &mut Window,
18712 cx: &mut Context<Editor>,
18713 ) {
18714 if !self.diagnostics_enabled() {
18715 return;
18716 }
18717
18718 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18719 EditorSettings::get_global(cx)
18720 .diagnostics_max_severity
18721 .filter(|severity| severity != &DiagnosticSeverity::Off)
18722 .unwrap_or(DiagnosticSeverity::Hint)
18723 } else {
18724 DiagnosticSeverity::Off
18725 };
18726 self.set_max_diagnostics_severity(new_severity, cx);
18727 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18728 self.active_diagnostics = ActiveDiagnostic::None;
18729 self.inline_diagnostics_update = Task::ready(());
18730 self.inline_diagnostics.clear();
18731 } else {
18732 self.refresh_inline_diagnostics(false, window, cx);
18733 }
18734
18735 cx.notify();
18736 }
18737
18738 pub fn toggle_minimap(
18739 &mut self,
18740 _: &ToggleMinimap,
18741 window: &mut Window,
18742 cx: &mut Context<Editor>,
18743 ) {
18744 if self.supports_minimap(cx) {
18745 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18746 }
18747 }
18748
18749 fn refresh_inline_diagnostics(
18750 &mut self,
18751 debounce: bool,
18752 window: &mut Window,
18753 cx: &mut Context<Self>,
18754 ) {
18755 let max_severity = ProjectSettings::get_global(cx)
18756 .diagnostics
18757 .inline
18758 .max_severity
18759 .unwrap_or(self.diagnostics_max_severity);
18760
18761 if !self.inline_diagnostics_enabled()
18762 || !self.diagnostics_enabled()
18763 || !self.show_inline_diagnostics
18764 || max_severity == DiagnosticSeverity::Off
18765 {
18766 self.inline_diagnostics_update = Task::ready(());
18767 self.inline_diagnostics.clear();
18768 return;
18769 }
18770
18771 let debounce_ms = ProjectSettings::get_global(cx)
18772 .diagnostics
18773 .inline
18774 .update_debounce_ms;
18775 let debounce = if debounce && debounce_ms > 0 {
18776 Some(Duration::from_millis(debounce_ms))
18777 } else {
18778 None
18779 };
18780 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18781 if let Some(debounce) = debounce {
18782 cx.background_executor().timer(debounce).await;
18783 }
18784 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18785 editor
18786 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18787 .ok()
18788 }) else {
18789 return;
18790 };
18791
18792 let new_inline_diagnostics = cx
18793 .background_spawn(async move {
18794 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18795 for diagnostic_entry in
18796 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18797 {
18798 let message = diagnostic_entry
18799 .diagnostic
18800 .message
18801 .split_once('\n')
18802 .map(|(line, _)| line)
18803 .map(SharedString::new)
18804 .unwrap_or_else(|| {
18805 SharedString::new(&*diagnostic_entry.diagnostic.message)
18806 });
18807 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18808 let (Ok(i) | Err(i)) = inline_diagnostics
18809 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18810 inline_diagnostics.insert(
18811 i,
18812 (
18813 start_anchor,
18814 InlineDiagnostic {
18815 message,
18816 group_id: diagnostic_entry.diagnostic.group_id,
18817 start: diagnostic_entry.range.start.to_point(&snapshot),
18818 is_primary: diagnostic_entry.diagnostic.is_primary,
18819 severity: diagnostic_entry.diagnostic.severity,
18820 },
18821 ),
18822 );
18823 }
18824 inline_diagnostics
18825 })
18826 .await;
18827
18828 editor
18829 .update(cx, |editor, cx| {
18830 editor.inline_diagnostics = new_inline_diagnostics;
18831 cx.notify();
18832 })
18833 .ok();
18834 });
18835 }
18836
18837 fn pull_diagnostics(
18838 &mut self,
18839 buffer_id: Option<BufferId>,
18840 window: &Window,
18841 cx: &mut Context<Self>,
18842 ) -> Option<()> {
18843 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18844 return None;
18845 }
18846 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18847 .diagnostics
18848 .lsp_pull_diagnostics;
18849 if !pull_diagnostics_settings.enabled {
18850 return None;
18851 }
18852 let project = self.project()?.downgrade();
18853
18854 let mut edited_buffer_ids = HashSet::default();
18855 let mut edited_worktree_ids = HashSet::default();
18856 let edited_buffers = match buffer_id {
18857 Some(buffer_id) => {
18858 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18859 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18860 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18861 edited_worktree_ids.insert(worktree_id);
18862 vec![buffer]
18863 }
18864 None => self
18865 .buffer()
18866 .read(cx)
18867 .all_buffers()
18868 .into_iter()
18869 .filter(|buffer| {
18870 let buffer = buffer.read(cx);
18871 match buffer.file().map(|f| f.worktree_id(cx)) {
18872 Some(worktree_id) => {
18873 edited_buffer_ids.insert(buffer.remote_id());
18874 edited_worktree_ids.insert(worktree_id);
18875 true
18876 }
18877 None => false,
18878 }
18879 })
18880 .collect::<Vec<_>>(),
18881 };
18882
18883 if edited_buffers.is_empty() {
18884 self.pull_diagnostics_task = Task::ready(());
18885 self.pull_diagnostics_background_task = Task::ready(());
18886 return None;
18887 }
18888
18889 let mut already_used_buffers = HashSet::default();
18890 let related_open_buffers = self
18891 .workspace
18892 .as_ref()
18893 .and_then(|(workspace, _)| workspace.upgrade())
18894 .into_iter()
18895 .flat_map(|workspace| workspace.read(cx).panes())
18896 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18897 .filter(|editor| editor != &cx.entity())
18898 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18899 .filter(|buffer| {
18900 let buffer = buffer.read(cx);
18901 let buffer_id = buffer.remote_id();
18902 if already_used_buffers.insert(buffer_id) {
18903 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18904 return !edited_buffer_ids.contains(&buffer_id)
18905 && !edited_worktree_ids.contains(&worktree_id);
18906 }
18907 }
18908 false
18909 })
18910 .collect::<Vec<_>>();
18911
18912 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18913 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18914 if buffers.is_empty() {
18915 return Task::ready(());
18916 }
18917 let project_weak = project.clone();
18918 cx.spawn_in(window, async move |_, cx| {
18919 cx.background_executor().timer(delay).await;
18920
18921 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18922 buffers
18923 .into_iter()
18924 .filter_map(|buffer| {
18925 project_weak
18926 .update(cx, |project, cx| {
18927 project.lsp_store().update(cx, |lsp_store, cx| {
18928 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18929 })
18930 })
18931 .ok()
18932 })
18933 .collect::<FuturesUnordered<_>>()
18934 }) else {
18935 return;
18936 };
18937
18938 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18939 if let Err(e) = pull_task {
18940 log::error!("Failed to update project diagnostics: {e:#}");
18941 }
18942 }
18943 })
18944 };
18945
18946 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18947 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18948
18949 Some(())
18950 }
18951
18952 pub fn set_selections_from_remote(
18953 &mut self,
18954 selections: Vec<Selection<Anchor>>,
18955 pending_selection: Option<Selection<Anchor>>,
18956 window: &mut Window,
18957 cx: &mut Context<Self>,
18958 ) {
18959 let old_cursor_position = self.selections.newest_anchor().head();
18960 self.selections
18961 .change_with(&self.display_snapshot(cx), |s| {
18962 s.select_anchors(selections);
18963 if let Some(pending_selection) = pending_selection {
18964 s.set_pending(pending_selection, SelectMode::Character);
18965 } else {
18966 s.clear_pending();
18967 }
18968 });
18969 self.selections_did_change(
18970 false,
18971 &old_cursor_position,
18972 SelectionEffects::default(),
18973 window,
18974 cx,
18975 );
18976 }
18977
18978 pub fn transact(
18979 &mut self,
18980 window: &mut Window,
18981 cx: &mut Context<Self>,
18982 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18983 ) -> Option<TransactionId> {
18984 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18985 this.start_transaction_at(Instant::now(), window, cx);
18986 update(this, window, cx);
18987 this.end_transaction_at(Instant::now(), cx)
18988 })
18989 }
18990
18991 pub fn start_transaction_at(
18992 &mut self,
18993 now: Instant,
18994 window: &mut Window,
18995 cx: &mut Context<Self>,
18996 ) -> Option<TransactionId> {
18997 self.end_selection(window, cx);
18998 if let Some(tx_id) = self
18999 .buffer
19000 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
19001 {
19002 self.selection_history
19003 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
19004 cx.emit(EditorEvent::TransactionBegun {
19005 transaction_id: tx_id,
19006 });
19007 Some(tx_id)
19008 } else {
19009 None
19010 }
19011 }
19012
19013 pub fn end_transaction_at(
19014 &mut self,
19015 now: Instant,
19016 cx: &mut Context<Self>,
19017 ) -> Option<TransactionId> {
19018 if let Some(transaction_id) = self
19019 .buffer
19020 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
19021 {
19022 if let Some((_, end_selections)) =
19023 self.selection_history.transaction_mut(transaction_id)
19024 {
19025 *end_selections = Some(self.selections.disjoint_anchors_arc());
19026 } else {
19027 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
19028 }
19029
19030 cx.emit(EditorEvent::Edited { transaction_id });
19031 Some(transaction_id)
19032 } else {
19033 None
19034 }
19035 }
19036
19037 pub fn modify_transaction_selection_history(
19038 &mut self,
19039 transaction_id: TransactionId,
19040 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19041 ) -> bool {
19042 self.selection_history
19043 .transaction_mut(transaction_id)
19044 .map(modify)
19045 .is_some()
19046 }
19047
19048 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19049 if self.selection_mark_mode {
19050 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19051 s.move_with(|_, sel| {
19052 sel.collapse_to(sel.head(), SelectionGoal::None);
19053 });
19054 })
19055 }
19056 self.selection_mark_mode = true;
19057 cx.notify();
19058 }
19059
19060 pub fn swap_selection_ends(
19061 &mut self,
19062 _: &actions::SwapSelectionEnds,
19063 window: &mut Window,
19064 cx: &mut Context<Self>,
19065 ) {
19066 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19067 s.move_with(|_, sel| {
19068 if sel.start != sel.end {
19069 sel.reversed = !sel.reversed
19070 }
19071 });
19072 });
19073 self.request_autoscroll(Autoscroll::newest(), cx);
19074 cx.notify();
19075 }
19076
19077 pub fn toggle_focus(
19078 workspace: &mut Workspace,
19079 _: &actions::ToggleFocus,
19080 window: &mut Window,
19081 cx: &mut Context<Workspace>,
19082 ) {
19083 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19084 return;
19085 };
19086 workspace.activate_item(&item, true, true, window, cx);
19087 }
19088
19089 pub fn toggle_fold(
19090 &mut self,
19091 _: &actions::ToggleFold,
19092 window: &mut Window,
19093 cx: &mut Context<Self>,
19094 ) {
19095 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19096 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19097 let selection = self.selections.newest::<Point>(&display_map);
19098
19099 let range = if selection.is_empty() {
19100 let point = selection.head().to_display_point(&display_map);
19101 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19102 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19103 .to_point(&display_map);
19104 start..end
19105 } else {
19106 selection.range()
19107 };
19108 if display_map.folds_in_range(range).next().is_some() {
19109 self.unfold_lines(&Default::default(), window, cx)
19110 } else {
19111 self.fold(&Default::default(), window, cx)
19112 }
19113 } else {
19114 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19115 let buffer_ids: HashSet<_> = self
19116 .selections
19117 .disjoint_anchor_ranges()
19118 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19119 .collect();
19120
19121 let should_unfold = buffer_ids
19122 .iter()
19123 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19124
19125 for buffer_id in buffer_ids {
19126 if should_unfold {
19127 self.unfold_buffer(buffer_id, cx);
19128 } else {
19129 self.fold_buffer(buffer_id, cx);
19130 }
19131 }
19132 }
19133 }
19134
19135 pub fn toggle_fold_recursive(
19136 &mut self,
19137 _: &actions::ToggleFoldRecursive,
19138 window: &mut Window,
19139 cx: &mut Context<Self>,
19140 ) {
19141 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19142
19143 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19144 let range = if selection.is_empty() {
19145 let point = selection.head().to_display_point(&display_map);
19146 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19147 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19148 .to_point(&display_map);
19149 start..end
19150 } else {
19151 selection.range()
19152 };
19153 if display_map.folds_in_range(range).next().is_some() {
19154 self.unfold_recursive(&Default::default(), window, cx)
19155 } else {
19156 self.fold_recursive(&Default::default(), window, cx)
19157 }
19158 }
19159
19160 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19161 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19162 let mut to_fold = Vec::new();
19163 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19164 let selections = self.selections.all_adjusted(&display_map);
19165
19166 for selection in selections {
19167 let range = selection.range().sorted();
19168 let buffer_start_row = range.start.row;
19169
19170 if range.start.row != range.end.row {
19171 let mut found = false;
19172 let mut row = range.start.row;
19173 while row <= range.end.row {
19174 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19175 {
19176 found = true;
19177 row = crease.range().end.row + 1;
19178 to_fold.push(crease);
19179 } else {
19180 row += 1
19181 }
19182 }
19183 if found {
19184 continue;
19185 }
19186 }
19187
19188 for row in (0..=range.start.row).rev() {
19189 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19190 && crease.range().end.row >= buffer_start_row
19191 {
19192 to_fold.push(crease);
19193 if row <= range.start.row {
19194 break;
19195 }
19196 }
19197 }
19198 }
19199
19200 self.fold_creases(to_fold, true, window, cx);
19201 } else {
19202 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19203 let buffer_ids = self
19204 .selections
19205 .disjoint_anchor_ranges()
19206 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19207 .collect::<HashSet<_>>();
19208 for buffer_id in buffer_ids {
19209 self.fold_buffer(buffer_id, cx);
19210 }
19211 }
19212 }
19213
19214 pub fn toggle_fold_all(
19215 &mut self,
19216 _: &actions::ToggleFoldAll,
19217 window: &mut Window,
19218 cx: &mut Context<Self>,
19219 ) {
19220 if self.buffer.read(cx).is_singleton() {
19221 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19222 let has_folds = display_map
19223 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19224 .next()
19225 .is_some();
19226
19227 if has_folds {
19228 self.unfold_all(&actions::UnfoldAll, window, cx);
19229 } else {
19230 self.fold_all(&actions::FoldAll, window, cx);
19231 }
19232 } else {
19233 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19234 let should_unfold = buffer_ids
19235 .iter()
19236 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19237
19238 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19239 editor
19240 .update_in(cx, |editor, _, cx| {
19241 for buffer_id in buffer_ids {
19242 if should_unfold {
19243 editor.unfold_buffer(buffer_id, cx);
19244 } else {
19245 editor.fold_buffer(buffer_id, cx);
19246 }
19247 }
19248 })
19249 .ok();
19250 });
19251 }
19252 }
19253
19254 fn fold_at_level(
19255 &mut self,
19256 fold_at: &FoldAtLevel,
19257 window: &mut Window,
19258 cx: &mut Context<Self>,
19259 ) {
19260 if !self.buffer.read(cx).is_singleton() {
19261 return;
19262 }
19263
19264 let fold_at_level = fold_at.0;
19265 let snapshot = self.buffer.read(cx).snapshot(cx);
19266 let mut to_fold = Vec::new();
19267 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19268
19269 let row_ranges_to_keep: Vec<Range<u32>> = self
19270 .selections
19271 .all::<Point>(&self.display_snapshot(cx))
19272 .into_iter()
19273 .map(|sel| sel.start.row..sel.end.row)
19274 .collect();
19275
19276 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19277 while start_row < end_row {
19278 match self
19279 .snapshot(window, cx)
19280 .crease_for_buffer_row(MultiBufferRow(start_row))
19281 {
19282 Some(crease) => {
19283 let nested_start_row = crease.range().start.row + 1;
19284 let nested_end_row = crease.range().end.row;
19285
19286 if current_level < fold_at_level {
19287 stack.push((nested_start_row, nested_end_row, current_level + 1));
19288 } else if current_level == fold_at_level {
19289 // Fold iff there is no selection completely contained within the fold region
19290 if !row_ranges_to_keep.iter().any(|selection| {
19291 selection.end >= nested_start_row
19292 && selection.start <= nested_end_row
19293 }) {
19294 to_fold.push(crease);
19295 }
19296 }
19297
19298 start_row = nested_end_row + 1;
19299 }
19300 None => start_row += 1,
19301 }
19302 }
19303 }
19304
19305 self.fold_creases(to_fold, true, window, cx);
19306 }
19307
19308 pub fn fold_at_level_1(
19309 &mut self,
19310 _: &actions::FoldAtLevel1,
19311 window: &mut Window,
19312 cx: &mut Context<Self>,
19313 ) {
19314 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19315 }
19316
19317 pub fn fold_at_level_2(
19318 &mut self,
19319 _: &actions::FoldAtLevel2,
19320 window: &mut Window,
19321 cx: &mut Context<Self>,
19322 ) {
19323 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19324 }
19325
19326 pub fn fold_at_level_3(
19327 &mut self,
19328 _: &actions::FoldAtLevel3,
19329 window: &mut Window,
19330 cx: &mut Context<Self>,
19331 ) {
19332 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19333 }
19334
19335 pub fn fold_at_level_4(
19336 &mut self,
19337 _: &actions::FoldAtLevel4,
19338 window: &mut Window,
19339 cx: &mut Context<Self>,
19340 ) {
19341 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19342 }
19343
19344 pub fn fold_at_level_5(
19345 &mut self,
19346 _: &actions::FoldAtLevel5,
19347 window: &mut Window,
19348 cx: &mut Context<Self>,
19349 ) {
19350 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19351 }
19352
19353 pub fn fold_at_level_6(
19354 &mut self,
19355 _: &actions::FoldAtLevel6,
19356 window: &mut Window,
19357 cx: &mut Context<Self>,
19358 ) {
19359 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19360 }
19361
19362 pub fn fold_at_level_7(
19363 &mut self,
19364 _: &actions::FoldAtLevel7,
19365 window: &mut Window,
19366 cx: &mut Context<Self>,
19367 ) {
19368 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19369 }
19370
19371 pub fn fold_at_level_8(
19372 &mut self,
19373 _: &actions::FoldAtLevel8,
19374 window: &mut Window,
19375 cx: &mut Context<Self>,
19376 ) {
19377 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19378 }
19379
19380 pub fn fold_at_level_9(
19381 &mut self,
19382 _: &actions::FoldAtLevel9,
19383 window: &mut Window,
19384 cx: &mut Context<Self>,
19385 ) {
19386 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19387 }
19388
19389 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19390 if self.buffer.read(cx).is_singleton() {
19391 let mut fold_ranges = Vec::new();
19392 let snapshot = self.buffer.read(cx).snapshot(cx);
19393
19394 for row in 0..snapshot.max_row().0 {
19395 if let Some(foldable_range) = self
19396 .snapshot(window, cx)
19397 .crease_for_buffer_row(MultiBufferRow(row))
19398 {
19399 fold_ranges.push(foldable_range);
19400 }
19401 }
19402
19403 self.fold_creases(fold_ranges, true, window, cx);
19404 } else {
19405 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19406 editor
19407 .update_in(cx, |editor, _, cx| {
19408 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19409 editor.fold_buffer(buffer_id, cx);
19410 }
19411 })
19412 .ok();
19413 });
19414 }
19415 }
19416
19417 pub fn fold_function_bodies(
19418 &mut self,
19419 _: &actions::FoldFunctionBodies,
19420 window: &mut Window,
19421 cx: &mut Context<Self>,
19422 ) {
19423 let snapshot = self.buffer.read(cx).snapshot(cx);
19424
19425 let ranges = snapshot
19426 .text_object_ranges(
19427 MultiBufferOffset(0)..snapshot.len(),
19428 TreeSitterOptions::default(),
19429 )
19430 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19431 .collect::<Vec<_>>();
19432
19433 let creases = ranges
19434 .into_iter()
19435 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19436 .collect();
19437
19438 self.fold_creases(creases, true, window, cx);
19439 }
19440
19441 pub fn fold_recursive(
19442 &mut self,
19443 _: &actions::FoldRecursive,
19444 window: &mut Window,
19445 cx: &mut Context<Self>,
19446 ) {
19447 let mut to_fold = Vec::new();
19448 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19449 let selections = self.selections.all_adjusted(&display_map);
19450
19451 for selection in selections {
19452 let range = selection.range().sorted();
19453 let buffer_start_row = range.start.row;
19454
19455 if range.start.row != range.end.row {
19456 let mut found = false;
19457 for row in range.start.row..=range.end.row {
19458 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19459 found = true;
19460 to_fold.push(crease);
19461 }
19462 }
19463 if found {
19464 continue;
19465 }
19466 }
19467
19468 for row in (0..=range.start.row).rev() {
19469 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19470 if crease.range().end.row >= buffer_start_row {
19471 to_fold.push(crease);
19472 } else {
19473 break;
19474 }
19475 }
19476 }
19477 }
19478
19479 self.fold_creases(to_fold, true, window, cx);
19480 }
19481
19482 pub fn fold_at(
19483 &mut self,
19484 buffer_row: MultiBufferRow,
19485 window: &mut Window,
19486 cx: &mut Context<Self>,
19487 ) {
19488 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19489
19490 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19491 let autoscroll = self
19492 .selections
19493 .all::<Point>(&display_map)
19494 .iter()
19495 .any(|selection| crease.range().overlaps(&selection.range()));
19496
19497 self.fold_creases(vec![crease], autoscroll, window, cx);
19498 }
19499 }
19500
19501 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19502 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19504 let buffer = display_map.buffer_snapshot();
19505 let selections = self.selections.all::<Point>(&display_map);
19506 let ranges = selections
19507 .iter()
19508 .map(|s| {
19509 let range = s.display_range(&display_map).sorted();
19510 let mut start = range.start.to_point(&display_map);
19511 let mut end = range.end.to_point(&display_map);
19512 start.column = 0;
19513 end.column = buffer.line_len(MultiBufferRow(end.row));
19514 start..end
19515 })
19516 .collect::<Vec<_>>();
19517
19518 self.unfold_ranges(&ranges, true, true, cx);
19519 } else {
19520 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19521 let buffer_ids = self
19522 .selections
19523 .disjoint_anchor_ranges()
19524 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19525 .collect::<HashSet<_>>();
19526 for buffer_id in buffer_ids {
19527 self.unfold_buffer(buffer_id, cx);
19528 }
19529 }
19530 }
19531
19532 pub fn unfold_recursive(
19533 &mut self,
19534 _: &UnfoldRecursive,
19535 _window: &mut Window,
19536 cx: &mut Context<Self>,
19537 ) {
19538 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19539 let selections = self.selections.all::<Point>(&display_map);
19540 let ranges = selections
19541 .iter()
19542 .map(|s| {
19543 let mut range = s.display_range(&display_map).sorted();
19544 *range.start.column_mut() = 0;
19545 *range.end.column_mut() = display_map.line_len(range.end.row());
19546 let start = range.start.to_point(&display_map);
19547 let end = range.end.to_point(&display_map);
19548 start..end
19549 })
19550 .collect::<Vec<_>>();
19551
19552 self.unfold_ranges(&ranges, true, true, cx);
19553 }
19554
19555 pub fn unfold_at(
19556 &mut self,
19557 buffer_row: MultiBufferRow,
19558 _window: &mut Window,
19559 cx: &mut Context<Self>,
19560 ) {
19561 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19562
19563 let intersection_range = Point::new(buffer_row.0, 0)
19564 ..Point::new(
19565 buffer_row.0,
19566 display_map.buffer_snapshot().line_len(buffer_row),
19567 );
19568
19569 let autoscroll = self
19570 .selections
19571 .all::<Point>(&display_map)
19572 .iter()
19573 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19574
19575 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19576 }
19577
19578 pub fn unfold_all(
19579 &mut self,
19580 _: &actions::UnfoldAll,
19581 _window: &mut Window,
19582 cx: &mut Context<Self>,
19583 ) {
19584 if self.buffer.read(cx).is_singleton() {
19585 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19586 self.unfold_ranges(
19587 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19588 true,
19589 true,
19590 cx,
19591 );
19592 } else {
19593 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19594 editor
19595 .update(cx, |editor, cx| {
19596 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19597 editor.unfold_buffer(buffer_id, cx);
19598 }
19599 })
19600 .ok();
19601 });
19602 }
19603 }
19604
19605 pub fn fold_selected_ranges(
19606 &mut self,
19607 _: &FoldSelectedRanges,
19608 window: &mut Window,
19609 cx: &mut Context<Self>,
19610 ) {
19611 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19612 let selections = self.selections.all_adjusted(&display_map);
19613 let ranges = selections
19614 .into_iter()
19615 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19616 .collect::<Vec<_>>();
19617 self.fold_creases(ranges, true, window, cx);
19618 }
19619
19620 pub fn fold_ranges<T: ToOffset + Clone>(
19621 &mut self,
19622 ranges: Vec<Range<T>>,
19623 auto_scroll: bool,
19624 window: &mut Window,
19625 cx: &mut Context<Self>,
19626 ) {
19627 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19628 let ranges = ranges
19629 .into_iter()
19630 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19631 .collect::<Vec<_>>();
19632 self.fold_creases(ranges, auto_scroll, window, cx);
19633 }
19634
19635 pub fn fold_creases<T: ToOffset + Clone>(
19636 &mut self,
19637 creases: Vec<Crease<T>>,
19638 auto_scroll: bool,
19639 _window: &mut Window,
19640 cx: &mut Context<Self>,
19641 ) {
19642 if creases.is_empty() {
19643 return;
19644 }
19645
19646 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19647
19648 if auto_scroll {
19649 self.request_autoscroll(Autoscroll::fit(), cx);
19650 }
19651
19652 cx.notify();
19653
19654 self.scrollbar_marker_state.dirty = true;
19655 self.folds_did_change(cx);
19656 }
19657
19658 /// Removes any folds whose ranges intersect any of the given ranges.
19659 pub fn unfold_ranges<T: ToOffset + Clone>(
19660 &mut self,
19661 ranges: &[Range<T>],
19662 inclusive: bool,
19663 auto_scroll: bool,
19664 cx: &mut Context<Self>,
19665 ) {
19666 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19667 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19668 });
19669 self.folds_did_change(cx);
19670 }
19671
19672 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19673 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19674 return;
19675 }
19676
19677 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19678 self.display_map.update(cx, |display_map, cx| {
19679 display_map.fold_buffers([buffer_id], cx)
19680 });
19681
19682 let snapshot = self.display_snapshot(cx);
19683 self.selections.change_with(&snapshot, |selections| {
19684 selections.remove_selections_from_buffer(buffer_id);
19685 });
19686
19687 cx.emit(EditorEvent::BufferFoldToggled {
19688 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19689 folded: true,
19690 });
19691 cx.notify();
19692 }
19693
19694 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19695 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19696 return;
19697 }
19698 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19699 self.display_map.update(cx, |display_map, cx| {
19700 display_map.unfold_buffers([buffer_id], cx);
19701 });
19702 cx.emit(EditorEvent::BufferFoldToggled {
19703 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19704 folded: false,
19705 });
19706 cx.notify();
19707 }
19708
19709 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19710 self.display_map.read(cx).is_buffer_folded(buffer)
19711 }
19712
19713 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19714 self.display_map.read(cx).folded_buffers()
19715 }
19716
19717 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19718 self.display_map.update(cx, |display_map, cx| {
19719 display_map.disable_header_for_buffer(buffer_id, cx);
19720 });
19721 cx.notify();
19722 }
19723
19724 /// Removes any folds with the given ranges.
19725 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19726 &mut self,
19727 ranges: &[Range<T>],
19728 type_id: TypeId,
19729 auto_scroll: bool,
19730 cx: &mut Context<Self>,
19731 ) {
19732 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19733 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19734 });
19735 self.folds_did_change(cx);
19736 }
19737
19738 fn remove_folds_with<T: ToOffset + Clone>(
19739 &mut self,
19740 ranges: &[Range<T>],
19741 auto_scroll: bool,
19742 cx: &mut Context<Self>,
19743 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19744 ) {
19745 if ranges.is_empty() {
19746 return;
19747 }
19748
19749 let mut buffers_affected = HashSet::default();
19750 let multi_buffer = self.buffer().read(cx);
19751 for range in ranges {
19752 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19753 buffers_affected.insert(buffer.read(cx).remote_id());
19754 };
19755 }
19756
19757 self.display_map.update(cx, update);
19758
19759 if auto_scroll {
19760 self.request_autoscroll(Autoscroll::fit(), cx);
19761 }
19762
19763 cx.notify();
19764 self.scrollbar_marker_state.dirty = true;
19765 self.active_indent_guides_state.dirty = true;
19766 }
19767
19768 pub fn update_renderer_widths(
19769 &mut self,
19770 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19771 cx: &mut Context<Self>,
19772 ) -> bool {
19773 self.display_map
19774 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19775 }
19776
19777 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19778 self.display_map.read(cx).fold_placeholder.clone()
19779 }
19780
19781 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19782 self.use_base_text_line_numbers = show;
19783 }
19784
19785 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19786 self.buffer.update(cx, |buffer, cx| {
19787 buffer.set_all_diff_hunks_expanded(cx);
19788 });
19789 }
19790
19791 pub fn expand_all_diff_hunks(
19792 &mut self,
19793 _: &ExpandAllDiffHunks,
19794 _window: &mut Window,
19795 cx: &mut Context<Self>,
19796 ) {
19797 self.buffer.update(cx, |buffer, cx| {
19798 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19799 });
19800 }
19801
19802 pub fn collapse_all_diff_hunks(
19803 &mut self,
19804 _: &CollapseAllDiffHunks,
19805 _window: &mut Window,
19806 cx: &mut Context<Self>,
19807 ) {
19808 self.buffer.update(cx, |buffer, cx| {
19809 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19810 });
19811 }
19812
19813 pub fn toggle_selected_diff_hunks(
19814 &mut self,
19815 _: &ToggleSelectedDiffHunks,
19816 _window: &mut Window,
19817 cx: &mut Context<Self>,
19818 ) {
19819 let ranges: Vec<_> = self
19820 .selections
19821 .disjoint_anchors()
19822 .iter()
19823 .map(|s| s.range())
19824 .collect();
19825 self.toggle_diff_hunks_in_ranges(ranges, cx);
19826 }
19827
19828 pub fn diff_hunks_in_ranges<'a>(
19829 &'a self,
19830 ranges: &'a [Range<Anchor>],
19831 buffer: &'a MultiBufferSnapshot,
19832 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19833 ranges.iter().flat_map(move |range| {
19834 let end_excerpt_id = range.end.excerpt_id;
19835 let range = range.to_point(buffer);
19836 let mut peek_end = range.end;
19837 if range.end.row < buffer.max_row().0 {
19838 peek_end = Point::new(range.end.row + 1, 0);
19839 }
19840 buffer
19841 .diff_hunks_in_range(range.start..peek_end)
19842 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19843 })
19844 }
19845
19846 pub fn has_stageable_diff_hunks_in_ranges(
19847 &self,
19848 ranges: &[Range<Anchor>],
19849 snapshot: &MultiBufferSnapshot,
19850 ) -> bool {
19851 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19852 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19853 }
19854
19855 pub fn toggle_staged_selected_diff_hunks(
19856 &mut self,
19857 _: &::git::ToggleStaged,
19858 _: &mut Window,
19859 cx: &mut Context<Self>,
19860 ) {
19861 let snapshot = self.buffer.read(cx).snapshot(cx);
19862 let ranges: Vec<_> = self
19863 .selections
19864 .disjoint_anchors()
19865 .iter()
19866 .map(|s| s.range())
19867 .collect();
19868 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19869 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19870 }
19871
19872 pub fn set_render_diff_hunk_controls(
19873 &mut self,
19874 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19875 cx: &mut Context<Self>,
19876 ) {
19877 self.render_diff_hunk_controls = render_diff_hunk_controls;
19878 cx.notify();
19879 }
19880
19881 pub fn stage_and_next(
19882 &mut self,
19883 _: &::git::StageAndNext,
19884 window: &mut Window,
19885 cx: &mut Context<Self>,
19886 ) {
19887 self.do_stage_or_unstage_and_next(true, window, cx);
19888 }
19889
19890 pub fn unstage_and_next(
19891 &mut self,
19892 _: &::git::UnstageAndNext,
19893 window: &mut Window,
19894 cx: &mut Context<Self>,
19895 ) {
19896 self.do_stage_or_unstage_and_next(false, window, cx);
19897 }
19898
19899 pub fn stage_or_unstage_diff_hunks(
19900 &mut self,
19901 stage: bool,
19902 ranges: Vec<Range<Anchor>>,
19903 cx: &mut Context<Self>,
19904 ) {
19905 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19906 cx.spawn(async move |this, cx| {
19907 task.await?;
19908 this.update(cx, |this, cx| {
19909 let snapshot = this.buffer.read(cx).snapshot(cx);
19910 let chunk_by = this
19911 .diff_hunks_in_ranges(&ranges, &snapshot)
19912 .chunk_by(|hunk| hunk.buffer_id);
19913 for (buffer_id, hunks) in &chunk_by {
19914 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19915 }
19916 })
19917 })
19918 .detach_and_log_err(cx);
19919 }
19920
19921 fn save_buffers_for_ranges_if_needed(
19922 &mut self,
19923 ranges: &[Range<Anchor>],
19924 cx: &mut Context<Editor>,
19925 ) -> Task<Result<()>> {
19926 let multibuffer = self.buffer.read(cx);
19927 let snapshot = multibuffer.read(cx);
19928 let buffer_ids: HashSet<_> = ranges
19929 .iter()
19930 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19931 .collect();
19932 drop(snapshot);
19933
19934 let mut buffers = HashSet::default();
19935 for buffer_id in buffer_ids {
19936 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19937 let buffer = buffer_entity.read(cx);
19938 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19939 {
19940 buffers.insert(buffer_entity);
19941 }
19942 }
19943 }
19944
19945 if let Some(project) = &self.project {
19946 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19947 } else {
19948 Task::ready(Ok(()))
19949 }
19950 }
19951
19952 fn do_stage_or_unstage_and_next(
19953 &mut self,
19954 stage: bool,
19955 window: &mut Window,
19956 cx: &mut Context<Self>,
19957 ) {
19958 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19959
19960 if ranges.iter().any(|range| range.start != range.end) {
19961 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19962 return;
19963 }
19964
19965 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19966 let snapshot = self.snapshot(window, cx);
19967 let position = self
19968 .selections
19969 .newest::<Point>(&snapshot.display_snapshot)
19970 .head();
19971 let mut row = snapshot
19972 .buffer_snapshot()
19973 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19974 .find(|hunk| hunk.row_range.start.0 > position.row)
19975 .map(|hunk| hunk.row_range.start);
19976
19977 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19978 // Outside of the project diff editor, wrap around to the beginning.
19979 if !all_diff_hunks_expanded {
19980 row = row.or_else(|| {
19981 snapshot
19982 .buffer_snapshot()
19983 .diff_hunks_in_range(Point::zero()..position)
19984 .find(|hunk| hunk.row_range.end.0 < position.row)
19985 .map(|hunk| hunk.row_range.start)
19986 });
19987 }
19988
19989 if let Some(row) = row {
19990 let destination = Point::new(row.0, 0);
19991 let autoscroll = Autoscroll::center();
19992
19993 self.unfold_ranges(&[destination..destination], false, false, cx);
19994 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19995 s.select_ranges([destination..destination]);
19996 });
19997 }
19998 }
19999
20000 fn do_stage_or_unstage(
20001 &self,
20002 stage: bool,
20003 buffer_id: BufferId,
20004 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
20005 cx: &mut App,
20006 ) -> Option<()> {
20007 let project = self.project()?;
20008 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
20009 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
20010 let buffer_snapshot = buffer.read(cx).snapshot();
20011 let file_exists = buffer_snapshot
20012 .file()
20013 .is_some_and(|file| file.disk_state().exists());
20014 diff.update(cx, |diff, cx| {
20015 diff.stage_or_unstage_hunks(
20016 stage,
20017 &hunks
20018 .map(|hunk| buffer_diff::DiffHunk {
20019 buffer_range: hunk.buffer_range,
20020 // We don't need to pass in word diffs here because they're only used for rendering and
20021 // this function changes internal state
20022 base_word_diffs: Vec::default(),
20023 buffer_word_diffs: Vec::default(),
20024 diff_base_byte_range: hunk.diff_base_byte_range.start.0
20025 ..hunk.diff_base_byte_range.end.0,
20026 secondary_status: hunk.secondary_status,
20027 range: Point::zero()..Point::zero(), // unused
20028 })
20029 .collect::<Vec<_>>(),
20030 &buffer_snapshot,
20031 file_exists,
20032 cx,
20033 )
20034 });
20035 None
20036 }
20037
20038 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
20039 let ranges: Vec<_> = self
20040 .selections
20041 .disjoint_anchors()
20042 .iter()
20043 .map(|s| s.range())
20044 .collect();
20045 self.buffer
20046 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20047 }
20048
20049 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20050 self.buffer.update(cx, |buffer, cx| {
20051 let ranges = vec![Anchor::min()..Anchor::max()];
20052 if !buffer.all_diff_hunks_expanded()
20053 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20054 {
20055 buffer.collapse_diff_hunks(ranges, cx);
20056 true
20057 } else {
20058 false
20059 }
20060 })
20061 }
20062
20063 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20064 if self.buffer.read(cx).all_diff_hunks_expanded() {
20065 return true;
20066 }
20067 let ranges = vec![Anchor::min()..Anchor::max()];
20068 self.buffer
20069 .read(cx)
20070 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20071 }
20072
20073 fn toggle_diff_hunks_in_ranges(
20074 &mut self,
20075 ranges: Vec<Range<Anchor>>,
20076 cx: &mut Context<Editor>,
20077 ) {
20078 self.buffer.update(cx, |buffer, cx| {
20079 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20080 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20081 })
20082 }
20083
20084 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20085 self.buffer.update(cx, |buffer, cx| {
20086 let snapshot = buffer.snapshot(cx);
20087 let excerpt_id = range.end.excerpt_id;
20088 let point_range = range.to_point(&snapshot);
20089 let expand = !buffer.single_hunk_is_expanded(range, cx);
20090 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20091 })
20092 }
20093
20094 pub(crate) fn apply_all_diff_hunks(
20095 &mut self,
20096 _: &ApplyAllDiffHunks,
20097 window: &mut Window,
20098 cx: &mut Context<Self>,
20099 ) {
20100 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20101
20102 let buffers = self.buffer.read(cx).all_buffers();
20103 for branch_buffer in buffers {
20104 branch_buffer.update(cx, |branch_buffer, cx| {
20105 branch_buffer.merge_into_base(Vec::new(), cx);
20106 });
20107 }
20108
20109 if let Some(project) = self.project.clone() {
20110 self.save(
20111 SaveOptions {
20112 format: true,
20113 autosave: false,
20114 },
20115 project,
20116 window,
20117 cx,
20118 )
20119 .detach_and_log_err(cx);
20120 }
20121 }
20122
20123 pub(crate) fn apply_selected_diff_hunks(
20124 &mut self,
20125 _: &ApplyDiffHunk,
20126 window: &mut Window,
20127 cx: &mut Context<Self>,
20128 ) {
20129 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20130 let snapshot = self.snapshot(window, cx);
20131 let hunks = snapshot.hunks_for_ranges(
20132 self.selections
20133 .all(&snapshot.display_snapshot)
20134 .into_iter()
20135 .map(|selection| selection.range()),
20136 );
20137 let mut ranges_by_buffer = HashMap::default();
20138 self.transact(window, cx, |editor, _window, cx| {
20139 for hunk in hunks {
20140 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20141 ranges_by_buffer
20142 .entry(buffer.clone())
20143 .or_insert_with(Vec::new)
20144 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20145 }
20146 }
20147
20148 for (buffer, ranges) in ranges_by_buffer {
20149 buffer.update(cx, |buffer, cx| {
20150 buffer.merge_into_base(ranges, cx);
20151 });
20152 }
20153 });
20154
20155 if let Some(project) = self.project.clone() {
20156 self.save(
20157 SaveOptions {
20158 format: true,
20159 autosave: false,
20160 },
20161 project,
20162 window,
20163 cx,
20164 )
20165 .detach_and_log_err(cx);
20166 }
20167 }
20168
20169 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20170 if hovered != self.gutter_hovered {
20171 self.gutter_hovered = hovered;
20172 cx.notify();
20173 }
20174 }
20175
20176 pub fn insert_blocks(
20177 &mut self,
20178 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20179 autoscroll: Option<Autoscroll>,
20180 cx: &mut Context<Self>,
20181 ) -> Vec<CustomBlockId> {
20182 let blocks = self
20183 .display_map
20184 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20185 if let Some(autoscroll) = autoscroll {
20186 self.request_autoscroll(autoscroll, cx);
20187 }
20188 cx.notify();
20189 blocks
20190 }
20191
20192 pub fn resize_blocks(
20193 &mut self,
20194 heights: HashMap<CustomBlockId, u32>,
20195 autoscroll: Option<Autoscroll>,
20196 cx: &mut Context<Self>,
20197 ) {
20198 self.display_map
20199 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20200 if let Some(autoscroll) = autoscroll {
20201 self.request_autoscroll(autoscroll, cx);
20202 }
20203 cx.notify();
20204 }
20205
20206 pub fn replace_blocks(
20207 &mut self,
20208 renderers: HashMap<CustomBlockId, RenderBlock>,
20209 autoscroll: Option<Autoscroll>,
20210 cx: &mut Context<Self>,
20211 ) {
20212 self.display_map
20213 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20214 if let Some(autoscroll) = autoscroll {
20215 self.request_autoscroll(autoscroll, cx);
20216 }
20217 cx.notify();
20218 }
20219
20220 pub fn remove_blocks(
20221 &mut self,
20222 block_ids: HashSet<CustomBlockId>,
20223 autoscroll: Option<Autoscroll>,
20224 cx: &mut Context<Self>,
20225 ) {
20226 self.display_map.update(cx, |display_map, cx| {
20227 display_map.remove_blocks(block_ids, cx)
20228 });
20229 if let Some(autoscroll) = autoscroll {
20230 self.request_autoscroll(autoscroll, cx);
20231 }
20232 cx.notify();
20233 }
20234
20235 pub fn row_for_block(
20236 &self,
20237 block_id: CustomBlockId,
20238 cx: &mut Context<Self>,
20239 ) -> Option<DisplayRow> {
20240 self.display_map
20241 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20242 }
20243
20244 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20245 self.focused_block = Some(focused_block);
20246 }
20247
20248 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20249 self.focused_block.take()
20250 }
20251
20252 pub fn insert_creases(
20253 &mut self,
20254 creases: impl IntoIterator<Item = Crease<Anchor>>,
20255 cx: &mut Context<Self>,
20256 ) -> Vec<CreaseId> {
20257 self.display_map
20258 .update(cx, |map, cx| map.insert_creases(creases, cx))
20259 }
20260
20261 pub fn remove_creases(
20262 &mut self,
20263 ids: impl IntoIterator<Item = CreaseId>,
20264 cx: &mut Context<Self>,
20265 ) -> Vec<(CreaseId, Range<Anchor>)> {
20266 self.display_map
20267 .update(cx, |map, cx| map.remove_creases(ids, cx))
20268 }
20269
20270 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20271 self.display_map
20272 .update(cx, |map, cx| map.snapshot(cx))
20273 .longest_row()
20274 }
20275
20276 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20277 self.display_map
20278 .update(cx, |map, cx| map.snapshot(cx))
20279 .max_point()
20280 }
20281
20282 pub fn text(&self, cx: &App) -> String {
20283 self.buffer.read(cx).read(cx).text()
20284 }
20285
20286 pub fn is_empty(&self, cx: &App) -> bool {
20287 self.buffer.read(cx).read(cx).is_empty()
20288 }
20289
20290 pub fn text_option(&self, cx: &App) -> Option<String> {
20291 let text = self.text(cx);
20292 let text = text.trim();
20293
20294 if text.is_empty() {
20295 return None;
20296 }
20297
20298 Some(text.to_string())
20299 }
20300
20301 pub fn set_text(
20302 &mut self,
20303 text: impl Into<Arc<str>>,
20304 window: &mut Window,
20305 cx: &mut Context<Self>,
20306 ) {
20307 self.transact(window, cx, |this, _, cx| {
20308 this.buffer
20309 .read(cx)
20310 .as_singleton()
20311 .expect("you can only call set_text on editors for singleton buffers")
20312 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20313 });
20314 }
20315
20316 pub fn display_text(&self, cx: &mut App) -> String {
20317 self.display_map
20318 .update(cx, |map, cx| map.snapshot(cx))
20319 .text()
20320 }
20321
20322 fn create_minimap(
20323 &self,
20324 minimap_settings: MinimapSettings,
20325 window: &mut Window,
20326 cx: &mut Context<Self>,
20327 ) -> Option<Entity<Self>> {
20328 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20329 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20330 }
20331
20332 fn initialize_new_minimap(
20333 &self,
20334 minimap_settings: MinimapSettings,
20335 window: &mut Window,
20336 cx: &mut Context<Self>,
20337 ) -> Entity<Self> {
20338 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20339
20340 let mut minimap = Editor::new_internal(
20341 EditorMode::Minimap {
20342 parent: cx.weak_entity(),
20343 },
20344 self.buffer.clone(),
20345 None,
20346 Some(self.display_map.clone()),
20347 window,
20348 cx,
20349 );
20350 minimap.scroll_manager.clone_state(&self.scroll_manager);
20351 minimap.set_text_style_refinement(TextStyleRefinement {
20352 font_size: Some(MINIMAP_FONT_SIZE),
20353 font_weight: Some(MINIMAP_FONT_WEIGHT),
20354 ..Default::default()
20355 });
20356 minimap.update_minimap_configuration(minimap_settings, cx);
20357 cx.new(|_| minimap)
20358 }
20359
20360 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20361 let current_line_highlight = minimap_settings
20362 .current_line_highlight
20363 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20364 self.set_current_line_highlight(Some(current_line_highlight));
20365 }
20366
20367 pub fn minimap(&self) -> Option<&Entity<Self>> {
20368 self.minimap
20369 .as_ref()
20370 .filter(|_| self.minimap_visibility.visible())
20371 }
20372
20373 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20374 let mut wrap_guides = smallvec![];
20375
20376 if self.show_wrap_guides == Some(false) {
20377 return wrap_guides;
20378 }
20379
20380 let settings = self.buffer.read(cx).language_settings(cx);
20381 if settings.show_wrap_guides {
20382 match self.soft_wrap_mode(cx) {
20383 SoftWrap::Column(soft_wrap) => {
20384 wrap_guides.push((soft_wrap as usize, true));
20385 }
20386 SoftWrap::Bounded(soft_wrap) => {
20387 wrap_guides.push((soft_wrap as usize, true));
20388 }
20389 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20390 }
20391 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20392 }
20393
20394 wrap_guides
20395 }
20396
20397 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20398 let settings = self.buffer.read(cx).language_settings(cx);
20399 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20400 match mode {
20401 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20402 SoftWrap::None
20403 }
20404 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20405 language_settings::SoftWrap::PreferredLineLength => {
20406 SoftWrap::Column(settings.preferred_line_length)
20407 }
20408 language_settings::SoftWrap::Bounded => {
20409 SoftWrap::Bounded(settings.preferred_line_length)
20410 }
20411 }
20412 }
20413
20414 pub fn set_soft_wrap_mode(
20415 &mut self,
20416 mode: language_settings::SoftWrap,
20417
20418 cx: &mut Context<Self>,
20419 ) {
20420 self.soft_wrap_mode_override = Some(mode);
20421 cx.notify();
20422 }
20423
20424 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20425 self.hard_wrap = hard_wrap;
20426 cx.notify();
20427 }
20428
20429 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20430 self.text_style_refinement = Some(style);
20431 }
20432
20433 /// called by the Element so we know what style we were most recently rendered with.
20434 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20435 // We intentionally do not inform the display map about the minimap style
20436 // so that wrapping is not recalculated and stays consistent for the editor
20437 // and its linked minimap.
20438 if !self.mode.is_minimap() {
20439 let font = style.text.font();
20440 let font_size = style.text.font_size.to_pixels(window.rem_size());
20441 let display_map = self
20442 .placeholder_display_map
20443 .as_ref()
20444 .filter(|_| self.is_empty(cx))
20445 .unwrap_or(&self.display_map);
20446
20447 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20448 }
20449 self.style = Some(style);
20450 }
20451
20452 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20453 if self.style.is_none() {
20454 self.style = Some(self.create_style(cx));
20455 }
20456 self.style.as_ref().unwrap()
20457 }
20458
20459 // Called by the element. This method is not designed to be called outside of the editor
20460 // element's layout code because it does not notify when rewrapping is computed synchronously.
20461 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20462 if self.is_empty(cx) {
20463 self.placeholder_display_map
20464 .as_ref()
20465 .map_or(false, |display_map| {
20466 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20467 })
20468 } else {
20469 self.display_map
20470 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20471 }
20472 }
20473
20474 pub fn set_soft_wrap(&mut self) {
20475 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20476 }
20477
20478 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20479 if self.soft_wrap_mode_override.is_some() {
20480 self.soft_wrap_mode_override.take();
20481 } else {
20482 let soft_wrap = match self.soft_wrap_mode(cx) {
20483 SoftWrap::GitDiff => return,
20484 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20485 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20486 language_settings::SoftWrap::None
20487 }
20488 };
20489 self.soft_wrap_mode_override = Some(soft_wrap);
20490 }
20491 cx.notify();
20492 }
20493
20494 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20495 let Some(workspace) = self.workspace() else {
20496 return;
20497 };
20498 let fs = workspace.read(cx).app_state().fs.clone();
20499 let current_show = TabBarSettings::get_global(cx).show;
20500 update_settings_file(fs, cx, move |setting, _| {
20501 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20502 });
20503 }
20504
20505 pub fn toggle_indent_guides(
20506 &mut self,
20507 _: &ToggleIndentGuides,
20508 _: &mut Window,
20509 cx: &mut Context<Self>,
20510 ) {
20511 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20512 self.buffer
20513 .read(cx)
20514 .language_settings(cx)
20515 .indent_guides
20516 .enabled
20517 });
20518 self.show_indent_guides = Some(!currently_enabled);
20519 cx.notify();
20520 }
20521
20522 fn should_show_indent_guides(&self) -> Option<bool> {
20523 self.show_indent_guides
20524 }
20525
20526 pub fn disable_indent_guides_for_buffer(
20527 &mut self,
20528 buffer_id: BufferId,
20529 cx: &mut Context<Self>,
20530 ) {
20531 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20532 cx.notify();
20533 }
20534
20535 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20536 self.buffers_with_disabled_indent_guides
20537 .contains(&buffer_id)
20538 }
20539
20540 pub fn toggle_line_numbers(
20541 &mut self,
20542 _: &ToggleLineNumbers,
20543 _: &mut Window,
20544 cx: &mut Context<Self>,
20545 ) {
20546 let mut editor_settings = EditorSettings::get_global(cx).clone();
20547 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20548 EditorSettings::override_global(editor_settings, cx);
20549 }
20550
20551 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20552 if let Some(show_line_numbers) = self.show_line_numbers {
20553 return show_line_numbers;
20554 }
20555 EditorSettings::get_global(cx).gutter.line_numbers
20556 }
20557
20558 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20559 match (
20560 self.use_relative_line_numbers,
20561 EditorSettings::get_global(cx).relative_line_numbers,
20562 ) {
20563 (None, setting) => setting,
20564 (Some(false), _) => RelativeLineNumbers::Disabled,
20565 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20566 (Some(true), _) => RelativeLineNumbers::Enabled,
20567 }
20568 }
20569
20570 pub fn toggle_relative_line_numbers(
20571 &mut self,
20572 _: &ToggleRelativeLineNumbers,
20573 _: &mut Window,
20574 cx: &mut Context<Self>,
20575 ) {
20576 let is_relative = self.relative_line_numbers(cx);
20577 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20578 }
20579
20580 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20581 self.use_relative_line_numbers = is_relative;
20582 cx.notify();
20583 }
20584
20585 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20586 self.show_gutter = show_gutter;
20587 cx.notify();
20588 }
20589
20590 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20591 self.show_scrollbars = ScrollbarAxes {
20592 horizontal: show,
20593 vertical: show,
20594 };
20595 cx.notify();
20596 }
20597
20598 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20599 self.show_scrollbars.vertical = show;
20600 cx.notify();
20601 }
20602
20603 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20604 self.show_scrollbars.horizontal = show;
20605 cx.notify();
20606 }
20607
20608 pub fn set_minimap_visibility(
20609 &mut self,
20610 minimap_visibility: MinimapVisibility,
20611 window: &mut Window,
20612 cx: &mut Context<Self>,
20613 ) {
20614 if self.minimap_visibility != minimap_visibility {
20615 if minimap_visibility.visible() && self.minimap.is_none() {
20616 let minimap_settings = EditorSettings::get_global(cx).minimap;
20617 self.minimap =
20618 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20619 }
20620 self.minimap_visibility = minimap_visibility;
20621 cx.notify();
20622 }
20623 }
20624
20625 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20626 self.set_show_scrollbars(false, cx);
20627 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20628 }
20629
20630 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20631 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20632 }
20633
20634 /// Normally the text in full mode and auto height editors is padded on the
20635 /// left side by roughly half a character width for improved hit testing.
20636 ///
20637 /// Use this method to disable this for cases where this is not wanted (e.g.
20638 /// if you want to align the editor text with some other text above or below)
20639 /// or if you want to add this padding to single-line editors.
20640 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20641 self.offset_content = offset_content;
20642 cx.notify();
20643 }
20644
20645 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20646 self.show_line_numbers = Some(show_line_numbers);
20647 cx.notify();
20648 }
20649
20650 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20651 self.disable_expand_excerpt_buttons = true;
20652 cx.notify();
20653 }
20654
20655 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20656 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20657 cx.notify();
20658 }
20659
20660 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20661 self.show_code_actions = Some(show_code_actions);
20662 cx.notify();
20663 }
20664
20665 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20666 self.show_runnables = Some(show_runnables);
20667 cx.notify();
20668 }
20669
20670 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20671 self.show_breakpoints = Some(show_breakpoints);
20672 cx.notify();
20673 }
20674
20675 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20676 if self.display_map.read(cx).masked != masked {
20677 self.display_map.update(cx, |map, _| map.masked = masked);
20678 }
20679 cx.notify()
20680 }
20681
20682 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20683 self.show_wrap_guides = Some(show_wrap_guides);
20684 cx.notify();
20685 }
20686
20687 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20688 self.show_indent_guides = Some(show_indent_guides);
20689 cx.notify();
20690 }
20691
20692 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20693 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20694 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20695 && let Some(dir) = file.abs_path(cx).parent()
20696 {
20697 return Some(dir.to_owned());
20698 }
20699 }
20700
20701 None
20702 }
20703
20704 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20705 self.active_excerpt(cx)?
20706 .1
20707 .read(cx)
20708 .file()
20709 .and_then(|f| f.as_local())
20710 }
20711
20712 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20713 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20714 let buffer = buffer.read(cx);
20715 if let Some(project_path) = buffer.project_path(cx) {
20716 let project = self.project()?.read(cx);
20717 project.absolute_path(&project_path, cx)
20718 } else {
20719 buffer
20720 .file()
20721 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20722 }
20723 })
20724 }
20725
20726 pub fn reveal_in_finder(
20727 &mut self,
20728 _: &RevealInFileManager,
20729 _window: &mut Window,
20730 cx: &mut Context<Self>,
20731 ) {
20732 if let Some(target) = self.target_file(cx) {
20733 cx.reveal_path(&target.abs_path(cx));
20734 }
20735 }
20736
20737 pub fn copy_path(
20738 &mut self,
20739 _: &zed_actions::workspace::CopyPath,
20740 _window: &mut Window,
20741 cx: &mut Context<Self>,
20742 ) {
20743 if let Some(path) = self.target_file_abs_path(cx)
20744 && let Some(path) = path.to_str()
20745 {
20746 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20747 } else {
20748 cx.propagate();
20749 }
20750 }
20751
20752 pub fn copy_relative_path(
20753 &mut self,
20754 _: &zed_actions::workspace::CopyRelativePath,
20755 _window: &mut Window,
20756 cx: &mut Context<Self>,
20757 ) {
20758 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20759 let project = self.project()?.read(cx);
20760 let path = buffer.read(cx).file()?.path();
20761 let path = path.display(project.path_style(cx));
20762 Some(path)
20763 }) {
20764 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20765 } else {
20766 cx.propagate();
20767 }
20768 }
20769
20770 /// Returns the project path for the editor's buffer, if any buffer is
20771 /// opened in the editor.
20772 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20773 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20774 buffer.read(cx).project_path(cx)
20775 } else {
20776 None
20777 }
20778 }
20779
20780 // Returns true if the editor handled a go-to-line request
20781 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20782 maybe!({
20783 let breakpoint_store = self.breakpoint_store.as_ref()?;
20784
20785 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20786 else {
20787 self.clear_row_highlights::<ActiveDebugLine>();
20788 return None;
20789 };
20790
20791 let position = active_stack_frame.position;
20792 let buffer_id = position.buffer_id?;
20793 let snapshot = self
20794 .project
20795 .as_ref()?
20796 .read(cx)
20797 .buffer_for_id(buffer_id, cx)?
20798 .read(cx)
20799 .snapshot();
20800
20801 let mut handled = false;
20802 for (id, ExcerptRange { context, .. }) in
20803 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20804 {
20805 if context.start.cmp(&position, &snapshot).is_ge()
20806 || context.end.cmp(&position, &snapshot).is_lt()
20807 {
20808 continue;
20809 }
20810 let snapshot = self.buffer.read(cx).snapshot(cx);
20811 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20812
20813 handled = true;
20814 self.clear_row_highlights::<ActiveDebugLine>();
20815
20816 self.go_to_line::<ActiveDebugLine>(
20817 multibuffer_anchor,
20818 Some(cx.theme().colors().editor_debugger_active_line_background),
20819 window,
20820 cx,
20821 );
20822
20823 cx.notify();
20824 }
20825
20826 handled.then_some(())
20827 })
20828 .is_some()
20829 }
20830
20831 pub fn copy_file_name_without_extension(
20832 &mut self,
20833 _: &CopyFileNameWithoutExtension,
20834 _: &mut Window,
20835 cx: &mut Context<Self>,
20836 ) {
20837 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20838 let file = buffer.read(cx).file()?;
20839 file.path().file_stem()
20840 }) {
20841 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20842 }
20843 }
20844
20845 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20846 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20847 let file = buffer.read(cx).file()?;
20848 Some(file.file_name(cx))
20849 }) {
20850 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20851 }
20852 }
20853
20854 pub fn toggle_git_blame(
20855 &mut self,
20856 _: &::git::Blame,
20857 window: &mut Window,
20858 cx: &mut Context<Self>,
20859 ) {
20860 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20861
20862 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20863 self.start_git_blame(true, window, cx);
20864 }
20865
20866 cx.notify();
20867 }
20868
20869 pub fn toggle_git_blame_inline(
20870 &mut self,
20871 _: &ToggleGitBlameInline,
20872 window: &mut Window,
20873 cx: &mut Context<Self>,
20874 ) {
20875 self.toggle_git_blame_inline_internal(true, window, cx);
20876 cx.notify();
20877 }
20878
20879 pub fn open_git_blame_commit(
20880 &mut self,
20881 _: &OpenGitBlameCommit,
20882 window: &mut Window,
20883 cx: &mut Context<Self>,
20884 ) {
20885 self.open_git_blame_commit_internal(window, cx);
20886 }
20887
20888 fn open_git_blame_commit_internal(
20889 &mut self,
20890 window: &mut Window,
20891 cx: &mut Context<Self>,
20892 ) -> Option<()> {
20893 let blame = self.blame.as_ref()?;
20894 let snapshot = self.snapshot(window, cx);
20895 let cursor = self
20896 .selections
20897 .newest::<Point>(&snapshot.display_snapshot)
20898 .head();
20899 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20900 let (_, blame_entry) = blame
20901 .update(cx, |blame, cx| {
20902 blame
20903 .blame_for_rows(
20904 &[RowInfo {
20905 buffer_id: Some(buffer.remote_id()),
20906 buffer_row: Some(point.row),
20907 ..Default::default()
20908 }],
20909 cx,
20910 )
20911 .next()
20912 })
20913 .flatten()?;
20914 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20915 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20916 let workspace = self.workspace()?.downgrade();
20917 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20918 None
20919 }
20920
20921 pub fn git_blame_inline_enabled(&self) -> bool {
20922 self.git_blame_inline_enabled
20923 }
20924
20925 pub fn toggle_selection_menu(
20926 &mut self,
20927 _: &ToggleSelectionMenu,
20928 _: &mut Window,
20929 cx: &mut Context<Self>,
20930 ) {
20931 self.show_selection_menu = self
20932 .show_selection_menu
20933 .map(|show_selections_menu| !show_selections_menu)
20934 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20935
20936 cx.notify();
20937 }
20938
20939 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20940 self.show_selection_menu
20941 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20942 }
20943
20944 fn start_git_blame(
20945 &mut self,
20946 user_triggered: bool,
20947 window: &mut Window,
20948 cx: &mut Context<Self>,
20949 ) {
20950 if let Some(project) = self.project() {
20951 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20952 && buffer.read(cx).file().is_none()
20953 {
20954 return;
20955 }
20956
20957 let focused = self.focus_handle(cx).contains_focused(window, cx);
20958
20959 let project = project.clone();
20960 let blame = cx
20961 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20962 self.blame_subscription =
20963 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20964 self.blame = Some(blame);
20965 }
20966 }
20967
20968 fn toggle_git_blame_inline_internal(
20969 &mut self,
20970 user_triggered: bool,
20971 window: &mut Window,
20972 cx: &mut Context<Self>,
20973 ) {
20974 if self.git_blame_inline_enabled {
20975 self.git_blame_inline_enabled = false;
20976 self.show_git_blame_inline = false;
20977 self.show_git_blame_inline_delay_task.take();
20978 } else {
20979 self.git_blame_inline_enabled = true;
20980 self.start_git_blame_inline(user_triggered, window, cx);
20981 }
20982
20983 cx.notify();
20984 }
20985
20986 fn start_git_blame_inline(
20987 &mut self,
20988 user_triggered: bool,
20989 window: &mut Window,
20990 cx: &mut Context<Self>,
20991 ) {
20992 self.start_git_blame(user_triggered, window, cx);
20993
20994 if ProjectSettings::get_global(cx)
20995 .git
20996 .inline_blame_delay()
20997 .is_some()
20998 {
20999 self.start_inline_blame_timer(window, cx);
21000 } else {
21001 self.show_git_blame_inline = true
21002 }
21003 }
21004
21005 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
21006 self.blame.as_ref()
21007 }
21008
21009 pub fn show_git_blame_gutter(&self) -> bool {
21010 self.show_git_blame_gutter
21011 }
21012
21013 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
21014 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
21015 }
21016
21017 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
21018 self.show_git_blame_inline
21019 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
21020 && !self.newest_selection_head_on_empty_line(cx)
21021 && self.has_blame_entries(cx)
21022 }
21023
21024 fn has_blame_entries(&self, cx: &App) -> bool {
21025 self.blame()
21026 .is_some_and(|blame| blame.read(cx).has_generated_entries())
21027 }
21028
21029 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
21030 let cursor_anchor = self.selections.newest_anchor().head();
21031
21032 let snapshot = self.buffer.read(cx).snapshot(cx);
21033 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
21034
21035 snapshot.line_len(buffer_row) == 0
21036 }
21037
21038 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
21039 let buffer_and_selection = maybe!({
21040 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21041 let selection_range = selection.range();
21042
21043 let multi_buffer = self.buffer().read(cx);
21044 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21045 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21046
21047 let (buffer, range, _) = if selection.reversed {
21048 buffer_ranges.first()
21049 } else {
21050 buffer_ranges.last()
21051 }?;
21052
21053 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21054 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21055
21056 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21057 let selection = start_row_in_buffer..end_row_in_buffer;
21058
21059 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21060 };
21061
21062 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21063
21064 Some((
21065 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21066 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
21067 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
21068 ))
21069 });
21070
21071 let Some((buffer, selection)) = buffer_and_selection else {
21072 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21073 };
21074
21075 let Some(project) = self.project() else {
21076 return Task::ready(Err(anyhow!("editor does not have project")));
21077 };
21078
21079 project.update(cx, |project, cx| {
21080 project.get_permalink_to_line(&buffer, selection, cx)
21081 })
21082 }
21083
21084 pub fn copy_permalink_to_line(
21085 &mut self,
21086 _: &CopyPermalinkToLine,
21087 window: &mut Window,
21088 cx: &mut Context<Self>,
21089 ) {
21090 let permalink_task = self.get_permalink_to_line(cx);
21091 let workspace = self.workspace();
21092
21093 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21094 Ok(permalink) => {
21095 cx.update(|_, cx| {
21096 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21097 })
21098 .ok();
21099 }
21100 Err(err) => {
21101 let message = format!("Failed to copy permalink: {err}");
21102
21103 anyhow::Result::<()>::Err(err).log_err();
21104
21105 if let Some(workspace) = workspace {
21106 workspace
21107 .update_in(cx, |workspace, _, cx| {
21108 struct CopyPermalinkToLine;
21109
21110 workspace.show_toast(
21111 Toast::new(
21112 NotificationId::unique::<CopyPermalinkToLine>(),
21113 message,
21114 ),
21115 cx,
21116 )
21117 })
21118 .ok();
21119 }
21120 }
21121 })
21122 .detach();
21123 }
21124
21125 pub fn copy_file_location(
21126 &mut self,
21127 _: &CopyFileLocation,
21128 _: &mut Window,
21129 cx: &mut Context<Self>,
21130 ) {
21131 let selection = self
21132 .selections
21133 .newest::<Point>(&self.display_snapshot(cx))
21134 .start
21135 .row
21136 + 1;
21137 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21138 let project = self.project()?.read(cx);
21139 let file = buffer.read(cx).file()?;
21140 let path = file.path().display(project.path_style(cx));
21141
21142 Some(format!("{path}:{selection}"))
21143 }) {
21144 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21145 }
21146 }
21147
21148 pub fn open_permalink_to_line(
21149 &mut self,
21150 _: &OpenPermalinkToLine,
21151 window: &mut Window,
21152 cx: &mut Context<Self>,
21153 ) {
21154 let permalink_task = self.get_permalink_to_line(cx);
21155 let workspace = self.workspace();
21156
21157 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21158 Ok(permalink) => {
21159 cx.update(|_, cx| {
21160 cx.open_url(permalink.as_ref());
21161 })
21162 .ok();
21163 }
21164 Err(err) => {
21165 let message = format!("Failed to open permalink: {err}");
21166
21167 anyhow::Result::<()>::Err(err).log_err();
21168
21169 if let Some(workspace) = workspace {
21170 workspace
21171 .update(cx, |workspace, cx| {
21172 struct OpenPermalinkToLine;
21173
21174 workspace.show_toast(
21175 Toast::new(
21176 NotificationId::unique::<OpenPermalinkToLine>(),
21177 message,
21178 ),
21179 cx,
21180 )
21181 })
21182 .ok();
21183 }
21184 }
21185 })
21186 .detach();
21187 }
21188
21189 pub fn insert_uuid_v4(
21190 &mut self,
21191 _: &InsertUuidV4,
21192 window: &mut Window,
21193 cx: &mut Context<Self>,
21194 ) {
21195 self.insert_uuid(UuidVersion::V4, window, cx);
21196 }
21197
21198 pub fn insert_uuid_v7(
21199 &mut self,
21200 _: &InsertUuidV7,
21201 window: &mut Window,
21202 cx: &mut Context<Self>,
21203 ) {
21204 self.insert_uuid(UuidVersion::V7, window, cx);
21205 }
21206
21207 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21208 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21209 self.transact(window, cx, |this, window, cx| {
21210 let edits = this
21211 .selections
21212 .all::<Point>(&this.display_snapshot(cx))
21213 .into_iter()
21214 .map(|selection| {
21215 let uuid = match version {
21216 UuidVersion::V4 => uuid::Uuid::new_v4(),
21217 UuidVersion::V7 => uuid::Uuid::now_v7(),
21218 };
21219
21220 (selection.range(), uuid.to_string())
21221 });
21222 this.edit(edits, cx);
21223 this.refresh_edit_prediction(true, false, window, cx);
21224 });
21225 }
21226
21227 pub fn open_selections_in_multibuffer(
21228 &mut self,
21229 _: &OpenSelectionsInMultibuffer,
21230 window: &mut Window,
21231 cx: &mut Context<Self>,
21232 ) {
21233 let multibuffer = self.buffer.read(cx);
21234
21235 let Some(buffer) = multibuffer.as_singleton() else {
21236 return;
21237 };
21238
21239 let Some(workspace) = self.workspace() else {
21240 return;
21241 };
21242
21243 let title = multibuffer.title(cx).to_string();
21244
21245 let locations = self
21246 .selections
21247 .all_anchors(&self.display_snapshot(cx))
21248 .iter()
21249 .map(|selection| {
21250 (
21251 buffer.clone(),
21252 (selection.start.text_anchor..selection.end.text_anchor)
21253 .to_point(buffer.read(cx)),
21254 )
21255 })
21256 .into_group_map();
21257
21258 cx.spawn_in(window, async move |_, cx| {
21259 workspace.update_in(cx, |workspace, window, cx| {
21260 Self::open_locations_in_multibuffer(
21261 workspace,
21262 locations,
21263 format!("Selections for '{title}'"),
21264 false,
21265 false,
21266 MultibufferSelectionMode::All,
21267 window,
21268 cx,
21269 );
21270 })
21271 })
21272 .detach();
21273 }
21274
21275 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21276 /// last highlight added will be used.
21277 ///
21278 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21279 pub fn highlight_rows<T: 'static>(
21280 &mut self,
21281 range: Range<Anchor>,
21282 color: Hsla,
21283 options: RowHighlightOptions,
21284 cx: &mut Context<Self>,
21285 ) {
21286 let snapshot = self.buffer().read(cx).snapshot(cx);
21287 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21288 let ix = row_highlights.binary_search_by(|highlight| {
21289 Ordering::Equal
21290 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21291 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21292 });
21293
21294 if let Err(mut ix) = ix {
21295 let index = post_inc(&mut self.highlight_order);
21296
21297 // If this range intersects with the preceding highlight, then merge it with
21298 // the preceding highlight. Otherwise insert a new highlight.
21299 let mut merged = false;
21300 if ix > 0 {
21301 let prev_highlight = &mut row_highlights[ix - 1];
21302 if prev_highlight
21303 .range
21304 .end
21305 .cmp(&range.start, &snapshot)
21306 .is_ge()
21307 {
21308 ix -= 1;
21309 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21310 prev_highlight.range.end = range.end;
21311 }
21312 merged = true;
21313 prev_highlight.index = index;
21314 prev_highlight.color = color;
21315 prev_highlight.options = options;
21316 }
21317 }
21318
21319 if !merged {
21320 row_highlights.insert(
21321 ix,
21322 RowHighlight {
21323 range,
21324 index,
21325 color,
21326 options,
21327 type_id: TypeId::of::<T>(),
21328 },
21329 );
21330 }
21331
21332 // If any of the following highlights intersect with this one, merge them.
21333 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21334 let highlight = &row_highlights[ix];
21335 if next_highlight
21336 .range
21337 .start
21338 .cmp(&highlight.range.end, &snapshot)
21339 .is_le()
21340 {
21341 if next_highlight
21342 .range
21343 .end
21344 .cmp(&highlight.range.end, &snapshot)
21345 .is_gt()
21346 {
21347 row_highlights[ix].range.end = next_highlight.range.end;
21348 }
21349 row_highlights.remove(ix + 1);
21350 } else {
21351 break;
21352 }
21353 }
21354 }
21355 }
21356
21357 /// Remove any highlighted row ranges of the given type that intersect the
21358 /// given ranges.
21359 pub fn remove_highlighted_rows<T: 'static>(
21360 &mut self,
21361 ranges_to_remove: Vec<Range<Anchor>>,
21362 cx: &mut Context<Self>,
21363 ) {
21364 let snapshot = self.buffer().read(cx).snapshot(cx);
21365 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21366 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21367 row_highlights.retain(|highlight| {
21368 while let Some(range_to_remove) = ranges_to_remove.peek() {
21369 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21370 Ordering::Less | Ordering::Equal => {
21371 ranges_to_remove.next();
21372 }
21373 Ordering::Greater => {
21374 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21375 Ordering::Less | Ordering::Equal => {
21376 return false;
21377 }
21378 Ordering::Greater => break,
21379 }
21380 }
21381 }
21382 }
21383
21384 true
21385 })
21386 }
21387
21388 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21389 pub fn clear_row_highlights<T: 'static>(&mut self) {
21390 self.highlighted_rows.remove(&TypeId::of::<T>());
21391 }
21392
21393 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21394 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21395 self.highlighted_rows
21396 .get(&TypeId::of::<T>())
21397 .map_or(&[] as &[_], |vec| vec.as_slice())
21398 .iter()
21399 .map(|highlight| (highlight.range.clone(), highlight.color))
21400 }
21401
21402 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21403 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21404 /// Allows to ignore certain kinds of highlights.
21405 pub fn highlighted_display_rows(
21406 &self,
21407 window: &mut Window,
21408 cx: &mut App,
21409 ) -> BTreeMap<DisplayRow, LineHighlight> {
21410 let snapshot = self.snapshot(window, cx);
21411 let mut used_highlight_orders = HashMap::default();
21412 self.highlighted_rows
21413 .iter()
21414 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21415 .fold(
21416 BTreeMap::<DisplayRow, LineHighlight>::new(),
21417 |mut unique_rows, highlight| {
21418 let start = highlight.range.start.to_display_point(&snapshot);
21419 let end = highlight.range.end.to_display_point(&snapshot);
21420 let start_row = start.row().0;
21421 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21422 {
21423 end.row().0.saturating_sub(1)
21424 } else {
21425 end.row().0
21426 };
21427 for row in start_row..=end_row {
21428 let used_index =
21429 used_highlight_orders.entry(row).or_insert(highlight.index);
21430 if highlight.index >= *used_index {
21431 *used_index = highlight.index;
21432 unique_rows.insert(
21433 DisplayRow(row),
21434 LineHighlight {
21435 include_gutter: highlight.options.include_gutter,
21436 border: None,
21437 background: highlight.color.into(),
21438 type_id: Some(highlight.type_id),
21439 },
21440 );
21441 }
21442 }
21443 unique_rows
21444 },
21445 )
21446 }
21447
21448 pub fn highlighted_display_row_for_autoscroll(
21449 &self,
21450 snapshot: &DisplaySnapshot,
21451 ) -> Option<DisplayRow> {
21452 self.highlighted_rows
21453 .values()
21454 .flat_map(|highlighted_rows| highlighted_rows.iter())
21455 .filter_map(|highlight| {
21456 if highlight.options.autoscroll {
21457 Some(highlight.range.start.to_display_point(snapshot).row())
21458 } else {
21459 None
21460 }
21461 })
21462 .min()
21463 }
21464
21465 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21466 self.highlight_background::<SearchWithinRange>(
21467 ranges,
21468 |_, colors| colors.colors().editor_document_highlight_read_background,
21469 cx,
21470 )
21471 }
21472
21473 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21474 self.breadcrumb_header = Some(new_header);
21475 }
21476
21477 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21478 self.clear_background_highlights::<SearchWithinRange>(cx);
21479 }
21480
21481 pub fn highlight_background<T: 'static>(
21482 &mut self,
21483 ranges: &[Range<Anchor>],
21484 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21485 cx: &mut Context<Self>,
21486 ) {
21487 self.background_highlights.insert(
21488 HighlightKey::Type(TypeId::of::<T>()),
21489 (Arc::new(color_fetcher), Arc::from(ranges)),
21490 );
21491 self.scrollbar_marker_state.dirty = true;
21492 cx.notify();
21493 }
21494
21495 pub fn highlight_background_key<T: 'static>(
21496 &mut self,
21497 key: usize,
21498 ranges: &[Range<Anchor>],
21499 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21500 cx: &mut Context<Self>,
21501 ) {
21502 self.background_highlights.insert(
21503 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21504 (Arc::new(color_fetcher), Arc::from(ranges)),
21505 );
21506 self.scrollbar_marker_state.dirty = true;
21507 cx.notify();
21508 }
21509
21510 pub fn clear_background_highlights<T: 'static>(
21511 &mut self,
21512 cx: &mut Context<Self>,
21513 ) -> Option<BackgroundHighlight> {
21514 let text_highlights = self
21515 .background_highlights
21516 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21517 if !text_highlights.1.is_empty() {
21518 self.scrollbar_marker_state.dirty = true;
21519 cx.notify();
21520 }
21521 Some(text_highlights)
21522 }
21523
21524 pub fn highlight_gutter<T: 'static>(
21525 &mut self,
21526 ranges: impl Into<Vec<Range<Anchor>>>,
21527 color_fetcher: fn(&App) -> Hsla,
21528 cx: &mut Context<Self>,
21529 ) {
21530 self.gutter_highlights
21531 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21532 cx.notify();
21533 }
21534
21535 pub fn clear_gutter_highlights<T: 'static>(
21536 &mut self,
21537 cx: &mut Context<Self>,
21538 ) -> Option<GutterHighlight> {
21539 cx.notify();
21540 self.gutter_highlights.remove(&TypeId::of::<T>())
21541 }
21542
21543 pub fn insert_gutter_highlight<T: 'static>(
21544 &mut self,
21545 range: Range<Anchor>,
21546 color_fetcher: fn(&App) -> Hsla,
21547 cx: &mut Context<Self>,
21548 ) {
21549 let snapshot = self.buffer().read(cx).snapshot(cx);
21550 let mut highlights = self
21551 .gutter_highlights
21552 .remove(&TypeId::of::<T>())
21553 .map(|(_, highlights)| highlights)
21554 .unwrap_or_default();
21555 let ix = highlights.binary_search_by(|highlight| {
21556 Ordering::Equal
21557 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21558 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21559 });
21560 if let Err(ix) = ix {
21561 highlights.insert(ix, range);
21562 }
21563 self.gutter_highlights
21564 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21565 }
21566
21567 pub fn remove_gutter_highlights<T: 'static>(
21568 &mut self,
21569 ranges_to_remove: Vec<Range<Anchor>>,
21570 cx: &mut Context<Self>,
21571 ) {
21572 let snapshot = self.buffer().read(cx).snapshot(cx);
21573 let Some((color_fetcher, mut gutter_highlights)) =
21574 self.gutter_highlights.remove(&TypeId::of::<T>())
21575 else {
21576 return;
21577 };
21578 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21579 gutter_highlights.retain(|highlight| {
21580 while let Some(range_to_remove) = ranges_to_remove.peek() {
21581 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21582 Ordering::Less | Ordering::Equal => {
21583 ranges_to_remove.next();
21584 }
21585 Ordering::Greater => {
21586 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21587 Ordering::Less | Ordering::Equal => {
21588 return false;
21589 }
21590 Ordering::Greater => break,
21591 }
21592 }
21593 }
21594 }
21595
21596 true
21597 });
21598 self.gutter_highlights
21599 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21600 }
21601
21602 #[cfg(feature = "test-support")]
21603 pub fn all_text_highlights(
21604 &self,
21605 window: &mut Window,
21606 cx: &mut Context<Self>,
21607 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21608 let snapshot = self.snapshot(window, cx);
21609 self.display_map.update(cx, |display_map, _| {
21610 display_map
21611 .all_text_highlights()
21612 .map(|highlight| {
21613 let (style, ranges) = highlight.as_ref();
21614 (
21615 *style,
21616 ranges
21617 .iter()
21618 .map(|range| range.clone().to_display_points(&snapshot))
21619 .collect(),
21620 )
21621 })
21622 .collect()
21623 })
21624 }
21625
21626 #[cfg(feature = "test-support")]
21627 pub fn all_text_background_highlights(
21628 &self,
21629 window: &mut Window,
21630 cx: &mut Context<Self>,
21631 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21632 let snapshot = self.snapshot(window, cx);
21633 let buffer = &snapshot.buffer_snapshot();
21634 let start = buffer.anchor_before(MultiBufferOffset(0));
21635 let end = buffer.anchor_after(buffer.len());
21636 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21637 }
21638
21639 #[cfg(any(test, feature = "test-support"))]
21640 pub fn sorted_background_highlights_in_range(
21641 &self,
21642 search_range: Range<Anchor>,
21643 display_snapshot: &DisplaySnapshot,
21644 theme: &Theme,
21645 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21646 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21647 res.sort_by(|a, b| {
21648 a.0.start
21649 .cmp(&b.0.start)
21650 .then_with(|| a.0.end.cmp(&b.0.end))
21651 .then_with(|| a.1.cmp(&b.1))
21652 });
21653 res
21654 }
21655
21656 #[cfg(feature = "test-support")]
21657 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21658 let snapshot = self.buffer().read(cx).snapshot(cx);
21659
21660 let highlights = self
21661 .background_highlights
21662 .get(&HighlightKey::Type(TypeId::of::<
21663 items::BufferSearchHighlights,
21664 >()));
21665
21666 if let Some((_color, ranges)) = highlights {
21667 ranges
21668 .iter()
21669 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21670 .collect_vec()
21671 } else {
21672 vec![]
21673 }
21674 }
21675
21676 fn document_highlights_for_position<'a>(
21677 &'a self,
21678 position: Anchor,
21679 buffer: &'a MultiBufferSnapshot,
21680 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21681 let read_highlights = self
21682 .background_highlights
21683 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21684 .map(|h| &h.1);
21685 let write_highlights = self
21686 .background_highlights
21687 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21688 .map(|h| &h.1);
21689 let left_position = position.bias_left(buffer);
21690 let right_position = position.bias_right(buffer);
21691 read_highlights
21692 .into_iter()
21693 .chain(write_highlights)
21694 .flat_map(move |ranges| {
21695 let start_ix = match ranges.binary_search_by(|probe| {
21696 let cmp = probe.end.cmp(&left_position, buffer);
21697 if cmp.is_ge() {
21698 Ordering::Greater
21699 } else {
21700 Ordering::Less
21701 }
21702 }) {
21703 Ok(i) | Err(i) => i,
21704 };
21705
21706 ranges[start_ix..]
21707 .iter()
21708 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21709 })
21710 }
21711
21712 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21713 self.background_highlights
21714 .get(&HighlightKey::Type(TypeId::of::<T>()))
21715 .is_some_and(|(_, highlights)| !highlights.is_empty())
21716 }
21717
21718 /// Returns all background highlights for a given range.
21719 ///
21720 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21721 pub fn background_highlights_in_range(
21722 &self,
21723 search_range: Range<Anchor>,
21724 display_snapshot: &DisplaySnapshot,
21725 theme: &Theme,
21726 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21727 let mut results = Vec::new();
21728 for (color_fetcher, ranges) in self.background_highlights.values() {
21729 let start_ix = match ranges.binary_search_by(|probe| {
21730 let cmp = probe
21731 .end
21732 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21733 if cmp.is_gt() {
21734 Ordering::Greater
21735 } else {
21736 Ordering::Less
21737 }
21738 }) {
21739 Ok(i) | Err(i) => i,
21740 };
21741 for (index, range) in ranges[start_ix..].iter().enumerate() {
21742 if range
21743 .start
21744 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21745 .is_ge()
21746 {
21747 break;
21748 }
21749
21750 let color = color_fetcher(&(start_ix + index), theme);
21751 let start = range.start.to_display_point(display_snapshot);
21752 let end = range.end.to_display_point(display_snapshot);
21753 results.push((start..end, color))
21754 }
21755 }
21756 results
21757 }
21758
21759 pub fn gutter_highlights_in_range(
21760 &self,
21761 search_range: Range<Anchor>,
21762 display_snapshot: &DisplaySnapshot,
21763 cx: &App,
21764 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21765 let mut results = Vec::new();
21766 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21767 let color = color_fetcher(cx);
21768 let start_ix = match ranges.binary_search_by(|probe| {
21769 let cmp = probe
21770 .end
21771 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21772 if cmp.is_gt() {
21773 Ordering::Greater
21774 } else {
21775 Ordering::Less
21776 }
21777 }) {
21778 Ok(i) | Err(i) => i,
21779 };
21780 for range in &ranges[start_ix..] {
21781 if range
21782 .start
21783 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21784 .is_ge()
21785 {
21786 break;
21787 }
21788
21789 let start = range.start.to_display_point(display_snapshot);
21790 let end = range.end.to_display_point(display_snapshot);
21791 results.push((start..end, color))
21792 }
21793 }
21794 results
21795 }
21796
21797 /// Get the text ranges corresponding to the redaction query
21798 pub fn redacted_ranges(
21799 &self,
21800 search_range: Range<Anchor>,
21801 display_snapshot: &DisplaySnapshot,
21802 cx: &App,
21803 ) -> Vec<Range<DisplayPoint>> {
21804 display_snapshot
21805 .buffer_snapshot()
21806 .redacted_ranges(search_range, |file| {
21807 if let Some(file) = file {
21808 file.is_private()
21809 && EditorSettings::get(
21810 Some(SettingsLocation {
21811 worktree_id: file.worktree_id(cx),
21812 path: file.path().as_ref(),
21813 }),
21814 cx,
21815 )
21816 .redact_private_values
21817 } else {
21818 false
21819 }
21820 })
21821 .map(|range| {
21822 range.start.to_display_point(display_snapshot)
21823 ..range.end.to_display_point(display_snapshot)
21824 })
21825 .collect()
21826 }
21827
21828 pub fn highlight_text_key<T: 'static>(
21829 &mut self,
21830 key: usize,
21831 ranges: Vec<Range<Anchor>>,
21832 style: HighlightStyle,
21833 merge: bool,
21834 cx: &mut Context<Self>,
21835 ) {
21836 self.display_map.update(cx, |map, cx| {
21837 map.highlight_text(
21838 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21839 ranges,
21840 style,
21841 merge,
21842 cx,
21843 );
21844 });
21845 cx.notify();
21846 }
21847
21848 pub fn highlight_text<T: 'static>(
21849 &mut self,
21850 ranges: Vec<Range<Anchor>>,
21851 style: HighlightStyle,
21852 cx: &mut Context<Self>,
21853 ) {
21854 self.display_map.update(cx, |map, cx| {
21855 map.highlight_text(
21856 HighlightKey::Type(TypeId::of::<T>()),
21857 ranges,
21858 style,
21859 false,
21860 cx,
21861 )
21862 });
21863 cx.notify();
21864 }
21865
21866 pub fn text_highlights<'a, T: 'static>(
21867 &'a self,
21868 cx: &'a App,
21869 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21870 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21871 }
21872
21873 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21874 let cleared = self
21875 .display_map
21876 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21877 if cleared {
21878 cx.notify();
21879 }
21880 }
21881
21882 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21883 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21884 && self.focus_handle.is_focused(window)
21885 }
21886
21887 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21888 self.show_cursor_when_unfocused = is_enabled;
21889 cx.notify();
21890 }
21891
21892 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21893 cx.notify();
21894 }
21895
21896 fn on_debug_session_event(
21897 &mut self,
21898 _session: Entity<Session>,
21899 event: &SessionEvent,
21900 cx: &mut Context<Self>,
21901 ) {
21902 if let SessionEvent::InvalidateInlineValue = event {
21903 self.refresh_inline_values(cx);
21904 }
21905 }
21906
21907 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21908 let Some(project) = self.project.clone() else {
21909 return;
21910 };
21911
21912 if !self.inline_value_cache.enabled {
21913 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21914 self.splice_inlays(&inlays, Vec::new(), cx);
21915 return;
21916 }
21917
21918 let current_execution_position = self
21919 .highlighted_rows
21920 .get(&TypeId::of::<ActiveDebugLine>())
21921 .and_then(|lines| lines.last().map(|line| line.range.end));
21922
21923 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21924 let inline_values = editor
21925 .update(cx, |editor, cx| {
21926 let Some(current_execution_position) = current_execution_position else {
21927 return Some(Task::ready(Ok(Vec::new())));
21928 };
21929
21930 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21931 let snapshot = buffer.snapshot(cx);
21932
21933 let excerpt = snapshot.excerpt_containing(
21934 current_execution_position..current_execution_position,
21935 )?;
21936
21937 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21938 })?;
21939
21940 let range =
21941 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21942
21943 project.inline_values(buffer, range, cx)
21944 })
21945 .ok()
21946 .flatten()?
21947 .await
21948 .context("refreshing debugger inlays")
21949 .log_err()?;
21950
21951 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21952
21953 for (buffer_id, inline_value) in inline_values
21954 .into_iter()
21955 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21956 {
21957 buffer_inline_values
21958 .entry(buffer_id)
21959 .or_default()
21960 .push(inline_value);
21961 }
21962
21963 editor
21964 .update(cx, |editor, cx| {
21965 let snapshot = editor.buffer.read(cx).snapshot(cx);
21966 let mut new_inlays = Vec::default();
21967
21968 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21969 let buffer_id = buffer_snapshot.remote_id();
21970 buffer_inline_values
21971 .get(&buffer_id)
21972 .into_iter()
21973 .flatten()
21974 .for_each(|hint| {
21975 let inlay = Inlay::debugger(
21976 post_inc(&mut editor.next_inlay_id),
21977 Anchor::in_buffer(excerpt_id, hint.position),
21978 hint.text(),
21979 );
21980 if !inlay.text().chars().contains(&'\n') {
21981 new_inlays.push(inlay);
21982 }
21983 });
21984 }
21985
21986 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21987 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21988
21989 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21990 })
21991 .ok()?;
21992 Some(())
21993 });
21994 }
21995
21996 fn on_buffer_event(
21997 &mut self,
21998 multibuffer: &Entity<MultiBuffer>,
21999 event: &multi_buffer::Event,
22000 window: &mut Window,
22001 cx: &mut Context<Self>,
22002 ) {
22003 match event {
22004 multi_buffer::Event::Edited { edited_buffer } => {
22005 self.scrollbar_marker_state.dirty = true;
22006 self.active_indent_guides_state.dirty = true;
22007 self.refresh_active_diagnostics(cx);
22008 self.refresh_code_actions(window, cx);
22009 self.refresh_single_line_folds(window, cx);
22010 self.refresh_matching_bracket_highlights(window, cx);
22011 if self.has_active_edit_prediction() {
22012 self.update_visible_edit_prediction(window, cx);
22013 }
22014
22015 if let Some(buffer) = edited_buffer {
22016 if buffer.read(cx).file().is_none() {
22017 cx.emit(EditorEvent::TitleChanged);
22018 }
22019
22020 if self.project.is_some() {
22021 let buffer_id = buffer.read(cx).remote_id();
22022 self.register_buffer(buffer_id, cx);
22023 self.update_lsp_data(Some(buffer_id), window, cx);
22024 self.refresh_inlay_hints(
22025 InlayHintRefreshReason::BufferEdited(buffer_id),
22026 cx,
22027 );
22028 }
22029 }
22030
22031 cx.emit(EditorEvent::BufferEdited);
22032 cx.emit(SearchEvent::MatchesInvalidated);
22033
22034 let Some(project) = &self.project else { return };
22035 let (telemetry, is_via_ssh) = {
22036 let project = project.read(cx);
22037 let telemetry = project.client().telemetry().clone();
22038 let is_via_ssh = project.is_via_remote_server();
22039 (telemetry, is_via_ssh)
22040 };
22041 telemetry.log_edit_event("editor", is_via_ssh);
22042 }
22043 multi_buffer::Event::ExcerptsAdded {
22044 buffer,
22045 predecessor,
22046 excerpts,
22047 } => {
22048 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22049 let buffer_id = buffer.read(cx).remote_id();
22050 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22051 && let Some(project) = &self.project
22052 {
22053 update_uncommitted_diff_for_buffer(
22054 cx.entity(),
22055 project,
22056 [buffer.clone()],
22057 self.buffer.clone(),
22058 cx,
22059 )
22060 .detach();
22061 }
22062 self.update_lsp_data(Some(buffer_id), window, cx);
22063 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22064 self.colorize_brackets(false, cx);
22065 cx.emit(EditorEvent::ExcerptsAdded {
22066 buffer: buffer.clone(),
22067 predecessor: *predecessor,
22068 excerpts: excerpts.clone(),
22069 });
22070 }
22071 multi_buffer::Event::ExcerptsRemoved {
22072 ids,
22073 removed_buffer_ids,
22074 } => {
22075 if let Some(inlay_hints) = &mut self.inlay_hints {
22076 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22077 }
22078 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22079 for buffer_id in removed_buffer_ids {
22080 self.registered_buffers.remove(buffer_id);
22081 }
22082 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22083 cx.emit(EditorEvent::ExcerptsRemoved {
22084 ids: ids.clone(),
22085 removed_buffer_ids: removed_buffer_ids.clone(),
22086 });
22087 }
22088 multi_buffer::Event::ExcerptsEdited {
22089 excerpt_ids,
22090 buffer_ids,
22091 } => {
22092 self.display_map.update(cx, |map, cx| {
22093 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22094 });
22095 cx.emit(EditorEvent::ExcerptsEdited {
22096 ids: excerpt_ids.clone(),
22097 });
22098 }
22099 multi_buffer::Event::ExcerptsExpanded { ids } => {
22100 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22101 self.refresh_document_highlights(cx);
22102 for id in ids {
22103 self.fetched_tree_sitter_chunks.remove(id);
22104 }
22105 self.colorize_brackets(false, cx);
22106 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22107 }
22108 multi_buffer::Event::Reparsed(buffer_id) => {
22109 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22110 self.refresh_selected_text_highlights(true, window, cx);
22111 self.colorize_brackets(true, cx);
22112 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22113
22114 cx.emit(EditorEvent::Reparsed(*buffer_id));
22115 }
22116 multi_buffer::Event::DiffHunksToggled => {
22117 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22118 }
22119 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22120 if !is_fresh_language {
22121 self.registered_buffers.remove(&buffer_id);
22122 }
22123 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22124 cx.emit(EditorEvent::Reparsed(*buffer_id));
22125 cx.notify();
22126 }
22127 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22128 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22129 multi_buffer::Event::FileHandleChanged
22130 | multi_buffer::Event::Reloaded
22131 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22132 multi_buffer::Event::DiagnosticsUpdated => {
22133 self.update_diagnostics_state(window, cx);
22134 }
22135 _ => {}
22136 };
22137 }
22138
22139 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22140 if !self.diagnostics_enabled() {
22141 return;
22142 }
22143 self.refresh_active_diagnostics(cx);
22144 self.refresh_inline_diagnostics(true, window, cx);
22145 self.scrollbar_marker_state.dirty = true;
22146 cx.notify();
22147 }
22148
22149 pub fn start_temporary_diff_override(&mut self) {
22150 self.load_diff_task.take();
22151 self.temporary_diff_override = true;
22152 }
22153
22154 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22155 self.temporary_diff_override = false;
22156 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22157 self.buffer.update(cx, |buffer, cx| {
22158 buffer.set_all_diff_hunks_collapsed(cx);
22159 });
22160
22161 if let Some(project) = self.project.clone() {
22162 self.load_diff_task = Some(
22163 update_uncommitted_diff_for_buffer(
22164 cx.entity(),
22165 &project,
22166 self.buffer.read(cx).all_buffers(),
22167 self.buffer.clone(),
22168 cx,
22169 )
22170 .shared(),
22171 );
22172 }
22173 }
22174
22175 fn on_display_map_changed(
22176 &mut self,
22177 _: Entity<DisplayMap>,
22178 _: &mut Window,
22179 cx: &mut Context<Self>,
22180 ) {
22181 cx.notify();
22182 }
22183
22184 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22185 if !self.mode.is_full() {
22186 return None;
22187 }
22188
22189 let theme_settings = theme::ThemeSettings::get_global(cx);
22190 let theme = cx.theme();
22191 let accent_colors = theme.accents().clone();
22192
22193 let accent_overrides = theme_settings
22194 .theme_overrides
22195 .get(theme.name.as_ref())
22196 .map(|theme_style| &theme_style.accents)
22197 .into_iter()
22198 .flatten()
22199 .chain(
22200 theme_settings
22201 .experimental_theme_overrides
22202 .as_ref()
22203 .map(|overrides| &overrides.accents)
22204 .into_iter()
22205 .flatten(),
22206 )
22207 .flat_map(|accent| accent.0.clone())
22208 .collect();
22209
22210 Some(AccentData {
22211 colors: accent_colors,
22212 overrides: accent_overrides,
22213 })
22214 }
22215
22216 fn fetch_applicable_language_settings(
22217 &self,
22218 cx: &App,
22219 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22220 if !self.mode.is_full() {
22221 return HashMap::default();
22222 }
22223
22224 self.buffer().read(cx).all_buffers().into_iter().fold(
22225 HashMap::default(),
22226 |mut acc, buffer| {
22227 let buffer = buffer.read(cx);
22228 let language = buffer.language().map(|language| language.name());
22229 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22230 let file = buffer.file();
22231 v.insert(language_settings(language, file, cx).into_owned());
22232 }
22233 acc
22234 },
22235 )
22236 }
22237
22238 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22239 let new_language_settings = self.fetch_applicable_language_settings(cx);
22240 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22241 self.applicable_language_settings = new_language_settings;
22242
22243 let new_accents = self.fetch_accent_data(cx);
22244 let accents_changed = new_accents != self.accent_data;
22245 self.accent_data = new_accents;
22246
22247 if self.diagnostics_enabled() {
22248 let new_severity = EditorSettings::get_global(cx)
22249 .diagnostics_max_severity
22250 .unwrap_or(DiagnosticSeverity::Hint);
22251 self.set_max_diagnostics_severity(new_severity, cx);
22252 }
22253 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22254 self.update_edit_prediction_settings(cx);
22255 self.refresh_edit_prediction(true, false, window, cx);
22256 self.refresh_inline_values(cx);
22257 self.refresh_inlay_hints(
22258 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22259 self.selections.newest_anchor().head(),
22260 &self.buffer.read(cx).snapshot(cx),
22261 cx,
22262 )),
22263 cx,
22264 );
22265
22266 let old_cursor_shape = self.cursor_shape;
22267 let old_show_breadcrumbs = self.show_breadcrumbs;
22268
22269 {
22270 let editor_settings = EditorSettings::get_global(cx);
22271 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22272 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22273 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22274 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22275 }
22276
22277 if old_cursor_shape != self.cursor_shape {
22278 cx.emit(EditorEvent::CursorShapeChanged);
22279 }
22280
22281 if old_show_breadcrumbs != self.show_breadcrumbs {
22282 cx.emit(EditorEvent::BreadcrumbsChanged);
22283 }
22284
22285 let project_settings = ProjectSettings::get_global(cx);
22286 self.buffer_serialization = self
22287 .should_serialize_buffer()
22288 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22289
22290 if self.mode.is_full() {
22291 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22292 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22293 if self.show_inline_diagnostics != show_inline_diagnostics {
22294 self.show_inline_diagnostics = show_inline_diagnostics;
22295 self.refresh_inline_diagnostics(false, window, cx);
22296 }
22297
22298 if self.git_blame_inline_enabled != inline_blame_enabled {
22299 self.toggle_git_blame_inline_internal(false, window, cx);
22300 }
22301
22302 let minimap_settings = EditorSettings::get_global(cx).minimap;
22303 if self.minimap_visibility != MinimapVisibility::Disabled {
22304 if self.minimap_visibility.settings_visibility()
22305 != minimap_settings.minimap_enabled()
22306 {
22307 self.set_minimap_visibility(
22308 MinimapVisibility::for_mode(self.mode(), cx),
22309 window,
22310 cx,
22311 );
22312 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22313 minimap_entity.update(cx, |minimap_editor, cx| {
22314 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22315 })
22316 }
22317 }
22318
22319 if language_settings_changed || accents_changed {
22320 self.colorize_brackets(true, cx);
22321 }
22322
22323 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22324 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22325 }) {
22326 if !inlay_splice.is_empty() {
22327 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22328 }
22329 self.refresh_colors_for_visible_range(None, window, cx);
22330 }
22331 }
22332
22333 cx.notify();
22334 }
22335
22336 pub fn set_searchable(&mut self, searchable: bool) {
22337 self.searchable = searchable;
22338 }
22339
22340 pub fn searchable(&self) -> bool {
22341 self.searchable
22342 }
22343
22344 pub fn open_excerpts_in_split(
22345 &mut self,
22346 _: &OpenExcerptsSplit,
22347 window: &mut Window,
22348 cx: &mut Context<Self>,
22349 ) {
22350 self.open_excerpts_common(None, true, window, cx)
22351 }
22352
22353 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22354 self.open_excerpts_common(None, false, window, cx)
22355 }
22356
22357 fn open_excerpts_common(
22358 &mut self,
22359 jump_data: Option<JumpData>,
22360 split: bool,
22361 window: &mut Window,
22362 cx: &mut Context<Self>,
22363 ) {
22364 let Some(workspace) = self.workspace() else {
22365 cx.propagate();
22366 return;
22367 };
22368
22369 if self.buffer.read(cx).is_singleton() {
22370 cx.propagate();
22371 return;
22372 }
22373
22374 let mut new_selections_by_buffer = HashMap::default();
22375 match &jump_data {
22376 Some(JumpData::MultiBufferPoint {
22377 excerpt_id,
22378 position,
22379 anchor,
22380 line_offset_from_top,
22381 }) => {
22382 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22383 if let Some(buffer) = multi_buffer_snapshot
22384 .buffer_id_for_excerpt(*excerpt_id)
22385 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22386 {
22387 let buffer_snapshot = buffer.read(cx).snapshot();
22388 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22389 language::ToPoint::to_point(anchor, &buffer_snapshot)
22390 } else {
22391 buffer_snapshot.clip_point(*position, Bias::Left)
22392 };
22393 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22394 new_selections_by_buffer.insert(
22395 buffer,
22396 (
22397 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22398 Some(*line_offset_from_top),
22399 ),
22400 );
22401 }
22402 }
22403 Some(JumpData::MultiBufferRow {
22404 row,
22405 line_offset_from_top,
22406 }) => {
22407 let point = MultiBufferPoint::new(row.0, 0);
22408 if let Some((buffer, buffer_point, _)) =
22409 self.buffer.read(cx).point_to_buffer_point(point, cx)
22410 {
22411 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22412 new_selections_by_buffer
22413 .entry(buffer)
22414 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22415 .0
22416 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22417 }
22418 }
22419 None => {
22420 let selections = self
22421 .selections
22422 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22423 let multi_buffer = self.buffer.read(cx);
22424 for selection in selections {
22425 for (snapshot, range, _, anchor) in multi_buffer
22426 .snapshot(cx)
22427 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22428 {
22429 if let Some(anchor) = anchor {
22430 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22431 else {
22432 continue;
22433 };
22434 let offset = text::ToOffset::to_offset(
22435 &anchor.text_anchor,
22436 &buffer_handle.read(cx).snapshot(),
22437 );
22438 let range = BufferOffset(offset)..BufferOffset(offset);
22439 new_selections_by_buffer
22440 .entry(buffer_handle)
22441 .or_insert((Vec::new(), None))
22442 .0
22443 .push(range)
22444 } else {
22445 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22446 else {
22447 continue;
22448 };
22449 new_selections_by_buffer
22450 .entry(buffer_handle)
22451 .or_insert((Vec::new(), None))
22452 .0
22453 .push(range)
22454 }
22455 }
22456 }
22457 }
22458 }
22459
22460 new_selections_by_buffer
22461 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22462
22463 if new_selections_by_buffer.is_empty() {
22464 return;
22465 }
22466
22467 // We defer the pane interaction because we ourselves are a workspace item
22468 // and activating a new item causes the pane to call a method on us reentrantly,
22469 // which panics if we're on the stack.
22470 window.defer(cx, move |window, cx| {
22471 workspace.update(cx, |workspace, cx| {
22472 let pane = if split {
22473 workspace.adjacent_pane(window, cx)
22474 } else {
22475 workspace.active_pane().clone()
22476 };
22477
22478 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22479 let buffer_read = buffer.read(cx);
22480 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22481 (true, project::File::from_dyn(Some(file)).is_some())
22482 } else {
22483 (false, false)
22484 };
22485
22486 // If project file is none workspace.open_project_item will fail to open the excerpt
22487 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22488 // so we check if there's a tab match in that case first
22489 let editor = (!has_file || !is_project_file)
22490 .then(|| {
22491 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22492 // so `workspace.open_project_item` will never find them, always opening a new editor.
22493 // Instead, we try to activate the existing editor in the pane first.
22494 let (editor, pane_item_index, pane_item_id) =
22495 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22496 let editor = item.downcast::<Editor>()?;
22497 let singleton_buffer =
22498 editor.read(cx).buffer().read(cx).as_singleton()?;
22499 if singleton_buffer == buffer {
22500 Some((editor, i, item.item_id()))
22501 } else {
22502 None
22503 }
22504 })?;
22505 pane.update(cx, |pane, cx| {
22506 pane.activate_item(pane_item_index, true, true, window, cx);
22507 if !PreviewTabsSettings::get_global(cx)
22508 .enable_preview_from_multibuffer
22509 {
22510 pane.unpreview_item_if_preview(pane_item_id);
22511 }
22512 });
22513 Some(editor)
22514 })
22515 .flatten()
22516 .unwrap_or_else(|| {
22517 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22518 .enable_keep_preview_on_code_navigation;
22519 let allow_new_preview =
22520 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22521 workspace.open_project_item::<Self>(
22522 pane.clone(),
22523 buffer,
22524 true,
22525 true,
22526 keep_old_preview,
22527 allow_new_preview,
22528 window,
22529 cx,
22530 )
22531 });
22532
22533 editor.update(cx, |editor, cx| {
22534 if has_file && !is_project_file {
22535 editor.set_read_only(true);
22536 }
22537 let autoscroll = match scroll_offset {
22538 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22539 None => Autoscroll::newest(),
22540 };
22541 let nav_history = editor.nav_history.take();
22542 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22543 let Some((&excerpt_id, _, buffer_snapshot)) =
22544 multibuffer_snapshot.as_singleton()
22545 else {
22546 return;
22547 };
22548 editor.change_selections(
22549 SelectionEffects::scroll(autoscroll),
22550 window,
22551 cx,
22552 |s| {
22553 s.select_ranges(ranges.into_iter().map(|range| {
22554 let range = buffer_snapshot.anchor_before(range.start)
22555 ..buffer_snapshot.anchor_after(range.end);
22556 multibuffer_snapshot
22557 .anchor_range_in_excerpt(excerpt_id, range)
22558 .unwrap()
22559 }));
22560 },
22561 );
22562 editor.nav_history = nav_history;
22563 });
22564 }
22565 })
22566 });
22567 }
22568
22569 // Allow opening excerpts for buffers that either belong to the current project
22570 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22571 // are also supported so tests and other in-memory views keep working.
22572 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22573 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22574 }
22575
22576 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22577 let snapshot = self.buffer.read(cx).read(cx);
22578 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22579 Some(
22580 ranges
22581 .iter()
22582 .map(move |range| {
22583 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22584 })
22585 .collect(),
22586 )
22587 }
22588
22589 fn selection_replacement_ranges(
22590 &self,
22591 range: Range<MultiBufferOffsetUtf16>,
22592 cx: &mut App,
22593 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22594 let selections = self
22595 .selections
22596 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22597 let newest_selection = selections
22598 .iter()
22599 .max_by_key(|selection| selection.id)
22600 .unwrap();
22601 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22602 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22603 let snapshot = self.buffer.read(cx).read(cx);
22604 selections
22605 .into_iter()
22606 .map(|mut selection| {
22607 selection.start.0.0 =
22608 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22609 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22610 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22611 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22612 })
22613 .collect()
22614 }
22615
22616 fn report_editor_event(
22617 &self,
22618 reported_event: ReportEditorEvent,
22619 file_extension: Option<String>,
22620 cx: &App,
22621 ) {
22622 if cfg!(any(test, feature = "test-support")) {
22623 return;
22624 }
22625
22626 let Some(project) = &self.project else { return };
22627
22628 // If None, we are in a file without an extension
22629 let file = self
22630 .buffer
22631 .read(cx)
22632 .as_singleton()
22633 .and_then(|b| b.read(cx).file());
22634 let file_extension = file_extension.or(file
22635 .as_ref()
22636 .and_then(|file| Path::new(file.file_name(cx)).extension())
22637 .and_then(|e| e.to_str())
22638 .map(|a| a.to_string()));
22639
22640 let vim_mode_enabled = self.is_vim_mode_enabled(cx);
22641 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22642 let copilot_enabled = edit_predictions_provider
22643 == language::language_settings::EditPredictionProvider::Copilot;
22644 let copilot_enabled_for_language = self
22645 .buffer
22646 .read(cx)
22647 .language_settings(cx)
22648 .show_edit_predictions;
22649
22650 let project = project.read(cx);
22651 let event_type = reported_event.event_type();
22652
22653 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22654 telemetry::event!(
22655 event_type,
22656 type = if auto_saved {"autosave"} else {"manual"},
22657 file_extension,
22658 vim_mode_enabled,
22659 copilot_enabled,
22660 copilot_enabled_for_language,
22661 edit_predictions_provider,
22662 is_via_ssh = project.is_via_remote_server(),
22663 );
22664 } else {
22665 telemetry::event!(
22666 event_type,
22667 file_extension,
22668 vim_mode_enabled,
22669 copilot_enabled,
22670 copilot_enabled_for_language,
22671 edit_predictions_provider,
22672 is_via_ssh = project.is_via_remote_server(),
22673 );
22674 };
22675 }
22676
22677 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22678 /// with each line being an array of {text, highlight} objects.
22679 fn copy_highlight_json(
22680 &mut self,
22681 _: &CopyHighlightJson,
22682 window: &mut Window,
22683 cx: &mut Context<Self>,
22684 ) {
22685 #[derive(Serialize)]
22686 struct Chunk<'a> {
22687 text: String,
22688 highlight: Option<&'a str>,
22689 }
22690
22691 let snapshot = self.buffer.read(cx).snapshot(cx);
22692 let range = self
22693 .selected_text_range(false, window, cx)
22694 .and_then(|selection| {
22695 if selection.range.is_empty() {
22696 None
22697 } else {
22698 Some(
22699 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22700 selection.range.start,
22701 )))
22702 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22703 selection.range.end,
22704 ))),
22705 )
22706 }
22707 })
22708 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22709
22710 let chunks = snapshot.chunks(range, true);
22711 let mut lines = Vec::new();
22712 let mut line: VecDeque<Chunk> = VecDeque::new();
22713
22714 let Some(style) = self.style.as_ref() else {
22715 return;
22716 };
22717
22718 for chunk in chunks {
22719 let highlight = chunk
22720 .syntax_highlight_id
22721 .and_then(|id| id.name(&style.syntax));
22722 let mut chunk_lines = chunk.text.split('\n').peekable();
22723 while let Some(text) = chunk_lines.next() {
22724 let mut merged_with_last_token = false;
22725 if let Some(last_token) = line.back_mut()
22726 && last_token.highlight == highlight
22727 {
22728 last_token.text.push_str(text);
22729 merged_with_last_token = true;
22730 }
22731
22732 if !merged_with_last_token {
22733 line.push_back(Chunk {
22734 text: text.into(),
22735 highlight,
22736 });
22737 }
22738
22739 if chunk_lines.peek().is_some() {
22740 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22741 line.pop_front();
22742 }
22743 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22744 line.pop_back();
22745 }
22746
22747 lines.push(mem::take(&mut line));
22748 }
22749 }
22750 }
22751
22752 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22753 return;
22754 };
22755 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22756 }
22757
22758 pub fn open_context_menu(
22759 &mut self,
22760 _: &OpenContextMenu,
22761 window: &mut Window,
22762 cx: &mut Context<Self>,
22763 ) {
22764 self.request_autoscroll(Autoscroll::newest(), cx);
22765 let position = self
22766 .selections
22767 .newest_display(&self.display_snapshot(cx))
22768 .start;
22769 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22770 }
22771
22772 pub fn replay_insert_event(
22773 &mut self,
22774 text: &str,
22775 relative_utf16_range: Option<Range<isize>>,
22776 window: &mut Window,
22777 cx: &mut Context<Self>,
22778 ) {
22779 if !self.input_enabled {
22780 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22781 return;
22782 }
22783 if let Some(relative_utf16_range) = relative_utf16_range {
22784 let selections = self
22785 .selections
22786 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22787 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22788 let new_ranges = selections.into_iter().map(|range| {
22789 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22790 range
22791 .head()
22792 .0
22793 .0
22794 .saturating_add_signed(relative_utf16_range.start),
22795 ));
22796 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22797 range
22798 .head()
22799 .0
22800 .0
22801 .saturating_add_signed(relative_utf16_range.end),
22802 ));
22803 start..end
22804 });
22805 s.select_ranges(new_ranges);
22806 });
22807 }
22808
22809 self.handle_input(text, window, cx);
22810 }
22811
22812 pub fn is_focused(&self, window: &Window) -> bool {
22813 self.focus_handle.is_focused(window)
22814 }
22815
22816 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22817 cx.emit(EditorEvent::Focused);
22818
22819 if let Some(descendant) = self
22820 .last_focused_descendant
22821 .take()
22822 .and_then(|descendant| descendant.upgrade())
22823 {
22824 window.focus(&descendant);
22825 } else {
22826 if let Some(blame) = self.blame.as_ref() {
22827 blame.update(cx, GitBlame::focus)
22828 }
22829
22830 self.blink_manager.update(cx, BlinkManager::enable);
22831 self.show_cursor_names(window, cx);
22832 self.buffer.update(cx, |buffer, cx| {
22833 buffer.finalize_last_transaction(cx);
22834 if self.leader_id.is_none() {
22835 buffer.set_active_selections(
22836 &self.selections.disjoint_anchors_arc(),
22837 self.selections.line_mode(),
22838 self.cursor_shape,
22839 cx,
22840 );
22841 }
22842 });
22843
22844 if let Some(position_map) = self.last_position_map.clone() {
22845 EditorElement::mouse_moved(
22846 self,
22847 &MouseMoveEvent {
22848 position: window.mouse_position(),
22849 pressed_button: None,
22850 modifiers: window.modifiers(),
22851 },
22852 &position_map,
22853 window,
22854 cx,
22855 );
22856 }
22857 }
22858 }
22859
22860 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22861 cx.emit(EditorEvent::FocusedIn)
22862 }
22863
22864 fn handle_focus_out(
22865 &mut self,
22866 event: FocusOutEvent,
22867 _window: &mut Window,
22868 cx: &mut Context<Self>,
22869 ) {
22870 if event.blurred != self.focus_handle {
22871 self.last_focused_descendant = Some(event.blurred);
22872 }
22873 self.selection_drag_state = SelectionDragState::None;
22874 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22875 }
22876
22877 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22878 self.blink_manager.update(cx, BlinkManager::disable);
22879 self.buffer
22880 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22881
22882 if let Some(blame) = self.blame.as_ref() {
22883 blame.update(cx, GitBlame::blur)
22884 }
22885 if !self.hover_state.focused(window, cx) {
22886 hide_hover(self, cx);
22887 }
22888 if !self
22889 .context_menu
22890 .borrow()
22891 .as_ref()
22892 .is_some_and(|context_menu| context_menu.focused(window, cx))
22893 {
22894 self.hide_context_menu(window, cx);
22895 }
22896 self.take_active_edit_prediction(cx);
22897 cx.emit(EditorEvent::Blurred);
22898 cx.notify();
22899 }
22900
22901 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22902 let mut pending: String = window
22903 .pending_input_keystrokes()
22904 .into_iter()
22905 .flatten()
22906 .filter_map(|keystroke| keystroke.key_char.clone())
22907 .collect();
22908
22909 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22910 pending = "".to_string();
22911 }
22912
22913 let existing_pending = self
22914 .text_highlights::<PendingInput>(cx)
22915 .map(|(_, ranges)| ranges.to_vec());
22916 if existing_pending.is_none() && pending.is_empty() {
22917 return;
22918 }
22919 let transaction =
22920 self.transact(window, cx, |this, window, cx| {
22921 let selections = this
22922 .selections
22923 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22924 let edits = selections
22925 .iter()
22926 .map(|selection| (selection.end..selection.end, pending.clone()));
22927 this.edit(edits, cx);
22928 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22929 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22930 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22931 }));
22932 });
22933 if let Some(existing_ranges) = existing_pending {
22934 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22935 this.edit(edits, cx);
22936 }
22937 });
22938
22939 let snapshot = self.snapshot(window, cx);
22940 let ranges = self
22941 .selections
22942 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22943 .into_iter()
22944 .map(|selection| {
22945 snapshot.buffer_snapshot().anchor_after(selection.end)
22946 ..snapshot
22947 .buffer_snapshot()
22948 .anchor_before(selection.end + pending.len())
22949 })
22950 .collect();
22951
22952 if pending.is_empty() {
22953 self.clear_highlights::<PendingInput>(cx);
22954 } else {
22955 self.highlight_text::<PendingInput>(
22956 ranges,
22957 HighlightStyle {
22958 underline: Some(UnderlineStyle {
22959 thickness: px(1.),
22960 color: None,
22961 wavy: false,
22962 }),
22963 ..Default::default()
22964 },
22965 cx,
22966 );
22967 }
22968
22969 self.ime_transaction = self.ime_transaction.or(transaction);
22970 if let Some(transaction) = self.ime_transaction {
22971 self.buffer.update(cx, |buffer, cx| {
22972 buffer.group_until_transaction(transaction, cx);
22973 });
22974 }
22975
22976 if self.text_highlights::<PendingInput>(cx).is_none() {
22977 self.ime_transaction.take();
22978 }
22979 }
22980
22981 pub fn register_action_renderer(
22982 &mut self,
22983 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22984 ) -> Subscription {
22985 let id = self.next_editor_action_id.post_inc();
22986 self.editor_actions
22987 .borrow_mut()
22988 .insert(id, Box::new(listener));
22989
22990 let editor_actions = self.editor_actions.clone();
22991 Subscription::new(move || {
22992 editor_actions.borrow_mut().remove(&id);
22993 })
22994 }
22995
22996 pub fn register_action<A: Action>(
22997 &mut self,
22998 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22999 ) -> Subscription {
23000 let id = self.next_editor_action_id.post_inc();
23001 let listener = Arc::new(listener);
23002 self.editor_actions.borrow_mut().insert(
23003 id,
23004 Box::new(move |_, window, _| {
23005 let listener = listener.clone();
23006 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
23007 let action = action.downcast_ref().unwrap();
23008 if phase == DispatchPhase::Bubble {
23009 listener(action, window, cx)
23010 }
23011 })
23012 }),
23013 );
23014
23015 let editor_actions = self.editor_actions.clone();
23016 Subscription::new(move || {
23017 editor_actions.borrow_mut().remove(&id);
23018 })
23019 }
23020
23021 pub fn file_header_size(&self) -> u32 {
23022 FILE_HEADER_HEIGHT
23023 }
23024
23025 pub fn restore(
23026 &mut self,
23027 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
23028 window: &mut Window,
23029 cx: &mut Context<Self>,
23030 ) {
23031 self.buffer().update(cx, |multi_buffer, cx| {
23032 for (buffer_id, changes) in revert_changes {
23033 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
23034 buffer.update(cx, |buffer, cx| {
23035 buffer.edit(
23036 changes
23037 .into_iter()
23038 .map(|(range, text)| (range, text.to_string())),
23039 None,
23040 cx,
23041 );
23042 });
23043 }
23044 }
23045 });
23046 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23047 selections.refresh()
23048 });
23049 }
23050
23051 pub fn to_pixel_point(
23052 &mut self,
23053 source: multi_buffer::Anchor,
23054 editor_snapshot: &EditorSnapshot,
23055 window: &mut Window,
23056 cx: &App,
23057 ) -> Option<gpui::Point<Pixels>> {
23058 let source_point = source.to_display_point(editor_snapshot);
23059 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23060 }
23061
23062 pub fn display_to_pixel_point(
23063 &mut self,
23064 source: DisplayPoint,
23065 editor_snapshot: &EditorSnapshot,
23066 window: &mut Window,
23067 cx: &App,
23068 ) -> Option<gpui::Point<Pixels>> {
23069 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23070 let text_layout_details = self.text_layout_details(window);
23071 let scroll_top = text_layout_details
23072 .scroll_anchor
23073 .scroll_position(editor_snapshot)
23074 .y;
23075
23076 if source.row().as_f64() < scroll_top.floor() {
23077 return None;
23078 }
23079 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23080 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23081 Some(gpui::Point::new(source_x, source_y))
23082 }
23083
23084 pub fn has_visible_completions_menu(&self) -> bool {
23085 !self.edit_prediction_preview_is_active()
23086 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23087 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23088 })
23089 }
23090
23091 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23092 if self.mode.is_minimap() {
23093 return;
23094 }
23095 self.addons
23096 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23097 }
23098
23099 pub fn unregister_addon<T: Addon>(&mut self) {
23100 self.addons.remove(&std::any::TypeId::of::<T>());
23101 }
23102
23103 pub fn addon<T: Addon>(&self) -> Option<&T> {
23104 let type_id = std::any::TypeId::of::<T>();
23105 self.addons
23106 .get(&type_id)
23107 .and_then(|item| item.to_any().downcast_ref::<T>())
23108 }
23109
23110 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23111 let type_id = std::any::TypeId::of::<T>();
23112 self.addons
23113 .get_mut(&type_id)
23114 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23115 }
23116
23117 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23118 let text_layout_details = self.text_layout_details(window);
23119 let style = &text_layout_details.editor_style;
23120 let font_id = window.text_system().resolve_font(&style.text.font());
23121 let font_size = style.text.font_size.to_pixels(window.rem_size());
23122 let line_height = style.text.line_height_in_pixels(window.rem_size());
23123 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23124 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23125
23126 CharacterDimensions {
23127 em_width,
23128 em_advance,
23129 line_height,
23130 }
23131 }
23132
23133 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23134 self.load_diff_task.clone()
23135 }
23136
23137 fn read_metadata_from_db(
23138 &mut self,
23139 item_id: u64,
23140 workspace_id: WorkspaceId,
23141 window: &mut Window,
23142 cx: &mut Context<Editor>,
23143 ) {
23144 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23145 && !self.mode.is_minimap()
23146 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
23147 {
23148 let buffer_snapshot = OnceCell::new();
23149
23150 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23151 && !folds.is_empty()
23152 {
23153 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23154 self.fold_ranges(
23155 folds
23156 .into_iter()
23157 .map(|(start, end)| {
23158 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23159 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23160 })
23161 .collect(),
23162 false,
23163 window,
23164 cx,
23165 );
23166 }
23167
23168 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23169 && !selections.is_empty()
23170 {
23171 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23172 // skip adding the initial selection to selection history
23173 self.selection_history.mode = SelectionHistoryMode::Skipping;
23174 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23175 s.select_ranges(selections.into_iter().map(|(start, end)| {
23176 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23177 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23178 }));
23179 });
23180 self.selection_history.mode = SelectionHistoryMode::Normal;
23181 };
23182 }
23183
23184 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23185 }
23186
23187 fn update_lsp_data(
23188 &mut self,
23189 for_buffer: Option<BufferId>,
23190 window: &mut Window,
23191 cx: &mut Context<'_, Self>,
23192 ) {
23193 self.pull_diagnostics(for_buffer, window, cx);
23194 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23195 }
23196
23197 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23198 if self.ignore_lsp_data() {
23199 return;
23200 }
23201 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23202 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23203 }
23204 }
23205
23206 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23207 if self.ignore_lsp_data() {
23208 return;
23209 }
23210
23211 if !self.registered_buffers.contains_key(&buffer_id)
23212 && let Some(project) = self.project.as_ref()
23213 {
23214 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23215 project.update(cx, |project, cx| {
23216 self.registered_buffers.insert(
23217 buffer_id,
23218 project.register_buffer_with_language_servers(&buffer, cx),
23219 );
23220 });
23221 } else {
23222 self.registered_buffers.remove(&buffer_id);
23223 }
23224 }
23225 }
23226
23227 fn ignore_lsp_data(&self) -> bool {
23228 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23229 // skip any LSP updates for it.
23230 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23231 }
23232
23233 fn create_style(&self, cx: &App) -> EditorStyle {
23234 let settings = ThemeSettings::get_global(cx);
23235
23236 let mut text_style = match self.mode {
23237 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23238 color: cx.theme().colors().editor_foreground,
23239 font_family: settings.ui_font.family.clone(),
23240 font_features: settings.ui_font.features.clone(),
23241 font_fallbacks: settings.ui_font.fallbacks.clone(),
23242 font_size: rems(0.875).into(),
23243 font_weight: settings.ui_font.weight,
23244 line_height: relative(settings.buffer_line_height.value()),
23245 ..Default::default()
23246 },
23247 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23248 color: cx.theme().colors().editor_foreground,
23249 font_family: settings.buffer_font.family.clone(),
23250 font_features: settings.buffer_font.features.clone(),
23251 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23252 font_size: settings.buffer_font_size(cx).into(),
23253 font_weight: settings.buffer_font.weight,
23254 line_height: relative(settings.buffer_line_height.value()),
23255 ..Default::default()
23256 },
23257 };
23258 if let Some(text_style_refinement) = &self.text_style_refinement {
23259 text_style.refine(text_style_refinement)
23260 }
23261
23262 let background = match self.mode {
23263 EditorMode::SingleLine => cx.theme().system().transparent,
23264 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23265 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23266 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23267 };
23268
23269 EditorStyle {
23270 background,
23271 border: cx.theme().colors().border,
23272 local_player: cx.theme().players().local(),
23273 text: text_style,
23274 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23275 syntax: cx.theme().syntax().clone(),
23276 status: cx.theme().status().clone(),
23277 inlay_hints_style: make_inlay_hints_style(cx),
23278 edit_prediction_styles: make_suggestion_styles(cx),
23279 unnecessary_code_fade: settings.unnecessary_code_fade,
23280 show_underlines: self.diagnostics_enabled(),
23281 }
23282 }
23283
23284 /// Returns the value of the `vim_mode` setting, defaulting `false` if the
23285 /// setting is not set.
23286 pub(crate) fn is_vim_mode_enabled(&self, cx: &App) -> bool {
23287 VimModeSetting::try_get(cx)
23288 .map(|vim_mode| vim_mode.0)
23289 .unwrap_or(false)
23290 }
23291}
23292
23293fn edit_for_markdown_paste<'a>(
23294 buffer: &MultiBufferSnapshot,
23295 range: Range<MultiBufferOffset>,
23296 to_insert: &'a str,
23297 url: Option<url::Url>,
23298) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23299 if url.is_none() {
23300 return (range, Cow::Borrowed(to_insert));
23301 };
23302
23303 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23304
23305 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23306 Cow::Borrowed(to_insert)
23307 } else {
23308 Cow::Owned(format!("[{old_text}]({to_insert})"))
23309 };
23310 (range, new_text)
23311}
23312
23313fn process_completion_for_edit(
23314 completion: &Completion,
23315 intent: CompletionIntent,
23316 buffer: &Entity<Buffer>,
23317 cursor_position: &text::Anchor,
23318 cx: &mut Context<Editor>,
23319) -> CompletionEdit {
23320 let buffer = buffer.read(cx);
23321 let buffer_snapshot = buffer.snapshot();
23322 let (snippet, new_text) = if completion.is_snippet() {
23323 let mut snippet_source = completion.new_text.clone();
23324 // Workaround for typescript language server issues so that methods don't expand within
23325 // strings and functions with type expressions. The previous point is used because the query
23326 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23327 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23328 let previous_point = if previous_point.column > 0 {
23329 cursor_position.to_previous_offset(&buffer_snapshot)
23330 } else {
23331 cursor_position.to_offset(&buffer_snapshot)
23332 };
23333 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23334 && scope.prefers_label_for_snippet_in_completion()
23335 && let Some(label) = completion.label()
23336 && matches!(
23337 completion.kind(),
23338 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23339 )
23340 {
23341 snippet_source = label;
23342 }
23343 match Snippet::parse(&snippet_source).log_err() {
23344 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23345 None => (None, completion.new_text.clone()),
23346 }
23347 } else {
23348 (None, completion.new_text.clone())
23349 };
23350
23351 let mut range_to_replace = {
23352 let replace_range = &completion.replace_range;
23353 if let CompletionSource::Lsp {
23354 insert_range: Some(insert_range),
23355 ..
23356 } = &completion.source
23357 {
23358 debug_assert_eq!(
23359 insert_range.start, replace_range.start,
23360 "insert_range and replace_range should start at the same position"
23361 );
23362 debug_assert!(
23363 insert_range
23364 .start
23365 .cmp(cursor_position, &buffer_snapshot)
23366 .is_le(),
23367 "insert_range should start before or at cursor position"
23368 );
23369 debug_assert!(
23370 replace_range
23371 .start
23372 .cmp(cursor_position, &buffer_snapshot)
23373 .is_le(),
23374 "replace_range should start before or at cursor position"
23375 );
23376
23377 let should_replace = match intent {
23378 CompletionIntent::CompleteWithInsert => false,
23379 CompletionIntent::CompleteWithReplace => true,
23380 CompletionIntent::Complete | CompletionIntent::Compose => {
23381 let insert_mode =
23382 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23383 .completions
23384 .lsp_insert_mode;
23385 match insert_mode {
23386 LspInsertMode::Insert => false,
23387 LspInsertMode::Replace => true,
23388 LspInsertMode::ReplaceSubsequence => {
23389 let mut text_to_replace = buffer.chars_for_range(
23390 buffer.anchor_before(replace_range.start)
23391 ..buffer.anchor_after(replace_range.end),
23392 );
23393 let mut current_needle = text_to_replace.next();
23394 for haystack_ch in completion.label.text.chars() {
23395 if let Some(needle_ch) = current_needle
23396 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23397 {
23398 current_needle = text_to_replace.next();
23399 }
23400 }
23401 current_needle.is_none()
23402 }
23403 LspInsertMode::ReplaceSuffix => {
23404 if replace_range
23405 .end
23406 .cmp(cursor_position, &buffer_snapshot)
23407 .is_gt()
23408 {
23409 let range_after_cursor = *cursor_position..replace_range.end;
23410 let text_after_cursor = buffer
23411 .text_for_range(
23412 buffer.anchor_before(range_after_cursor.start)
23413 ..buffer.anchor_after(range_after_cursor.end),
23414 )
23415 .collect::<String>()
23416 .to_ascii_lowercase();
23417 completion
23418 .label
23419 .text
23420 .to_ascii_lowercase()
23421 .ends_with(&text_after_cursor)
23422 } else {
23423 true
23424 }
23425 }
23426 }
23427 }
23428 };
23429
23430 if should_replace {
23431 replace_range.clone()
23432 } else {
23433 insert_range.clone()
23434 }
23435 } else {
23436 replace_range.clone()
23437 }
23438 };
23439
23440 if range_to_replace
23441 .end
23442 .cmp(cursor_position, &buffer_snapshot)
23443 .is_lt()
23444 {
23445 range_to_replace.end = *cursor_position;
23446 }
23447
23448 let replace_range = range_to_replace.to_offset(buffer);
23449 CompletionEdit {
23450 new_text,
23451 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23452 snippet,
23453 }
23454}
23455
23456struct CompletionEdit {
23457 new_text: String,
23458 replace_range: Range<BufferOffset>,
23459 snippet: Option<Snippet>,
23460}
23461
23462fn insert_extra_newline_brackets(
23463 buffer: &MultiBufferSnapshot,
23464 range: Range<MultiBufferOffset>,
23465 language: &language::LanguageScope,
23466) -> bool {
23467 let leading_whitespace_len = buffer
23468 .reversed_chars_at(range.start)
23469 .take_while(|c| c.is_whitespace() && *c != '\n')
23470 .map(|c| c.len_utf8())
23471 .sum::<usize>();
23472 let trailing_whitespace_len = buffer
23473 .chars_at(range.end)
23474 .take_while(|c| c.is_whitespace() && *c != '\n')
23475 .map(|c| c.len_utf8())
23476 .sum::<usize>();
23477 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23478
23479 language.brackets().any(|(pair, enabled)| {
23480 let pair_start = pair.start.trim_end();
23481 let pair_end = pair.end.trim_start();
23482
23483 enabled
23484 && pair.newline
23485 && buffer.contains_str_at(range.end, pair_end)
23486 && buffer.contains_str_at(
23487 range.start.saturating_sub_usize(pair_start.len()),
23488 pair_start,
23489 )
23490 })
23491}
23492
23493fn insert_extra_newline_tree_sitter(
23494 buffer: &MultiBufferSnapshot,
23495 range: Range<MultiBufferOffset>,
23496) -> bool {
23497 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23498 [(buffer, range, _)] => (*buffer, range.clone()),
23499 _ => return false,
23500 };
23501 let pair = {
23502 let mut result: Option<BracketMatch<usize>> = None;
23503
23504 for pair in buffer
23505 .all_bracket_ranges(range.start.0..range.end.0)
23506 .filter(move |pair| {
23507 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23508 })
23509 {
23510 let len = pair.close_range.end - pair.open_range.start;
23511
23512 if let Some(existing) = &result {
23513 let existing_len = existing.close_range.end - existing.open_range.start;
23514 if len > existing_len {
23515 continue;
23516 }
23517 }
23518
23519 result = Some(pair);
23520 }
23521
23522 result
23523 };
23524 let Some(pair) = pair else {
23525 return false;
23526 };
23527 pair.newline_only
23528 && buffer
23529 .chars_for_range(pair.open_range.end..range.start.0)
23530 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23531 .all(|c| c.is_whitespace() && c != '\n')
23532}
23533
23534fn update_uncommitted_diff_for_buffer(
23535 editor: Entity<Editor>,
23536 project: &Entity<Project>,
23537 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23538 buffer: Entity<MultiBuffer>,
23539 cx: &mut App,
23540) -> Task<()> {
23541 let mut tasks = Vec::new();
23542 project.update(cx, |project, cx| {
23543 for buffer in buffers {
23544 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23545 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23546 }
23547 }
23548 });
23549 cx.spawn(async move |cx| {
23550 let diffs = future::join_all(tasks).await;
23551 if editor
23552 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23553 .unwrap_or(false)
23554 {
23555 return;
23556 }
23557
23558 buffer
23559 .update(cx, |buffer, cx| {
23560 for diff in diffs.into_iter().flatten() {
23561 buffer.add_diff(diff, cx);
23562 }
23563 })
23564 .ok();
23565 })
23566}
23567
23568fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23569 let tab_size = tab_size.get() as usize;
23570 let mut width = offset;
23571
23572 for ch in text.chars() {
23573 width += if ch == '\t' {
23574 tab_size - (width % tab_size)
23575 } else {
23576 1
23577 };
23578 }
23579
23580 width - offset
23581}
23582
23583#[cfg(test)]
23584mod tests {
23585 use super::*;
23586
23587 #[test]
23588 fn test_string_size_with_expanded_tabs() {
23589 let nz = |val| NonZeroU32::new(val).unwrap();
23590 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23591 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23592 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23593 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23594 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23595 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23596 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23597 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23598 }
23599}
23600
23601/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23602struct WordBreakingTokenizer<'a> {
23603 input: &'a str,
23604}
23605
23606impl<'a> WordBreakingTokenizer<'a> {
23607 fn new(input: &'a str) -> Self {
23608 Self { input }
23609 }
23610}
23611
23612fn is_char_ideographic(ch: char) -> bool {
23613 use unicode_script::Script::*;
23614 use unicode_script::UnicodeScript;
23615 matches!(ch.script(), Han | Tangut | Yi)
23616}
23617
23618fn is_grapheme_ideographic(text: &str) -> bool {
23619 text.chars().any(is_char_ideographic)
23620}
23621
23622fn is_grapheme_whitespace(text: &str) -> bool {
23623 text.chars().any(|x| x.is_whitespace())
23624}
23625
23626fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23627 text.chars()
23628 .next()
23629 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23630}
23631
23632#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23633enum WordBreakToken<'a> {
23634 Word { token: &'a str, grapheme_len: usize },
23635 InlineWhitespace { token: &'a str, grapheme_len: usize },
23636 Newline,
23637}
23638
23639impl<'a> Iterator for WordBreakingTokenizer<'a> {
23640 /// Yields a span, the count of graphemes in the token, and whether it was
23641 /// whitespace. Note that it also breaks at word boundaries.
23642 type Item = WordBreakToken<'a>;
23643
23644 fn next(&mut self) -> Option<Self::Item> {
23645 use unicode_segmentation::UnicodeSegmentation;
23646 if self.input.is_empty() {
23647 return None;
23648 }
23649
23650 let mut iter = self.input.graphemes(true).peekable();
23651 let mut offset = 0;
23652 let mut grapheme_len = 0;
23653 if let Some(first_grapheme) = iter.next() {
23654 let is_newline = first_grapheme == "\n";
23655 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23656 offset += first_grapheme.len();
23657 grapheme_len += 1;
23658 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23659 if let Some(grapheme) = iter.peek().copied()
23660 && should_stay_with_preceding_ideograph(grapheme)
23661 {
23662 offset += grapheme.len();
23663 grapheme_len += 1;
23664 }
23665 } else {
23666 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23667 let mut next_word_bound = words.peek().copied();
23668 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23669 next_word_bound = words.next();
23670 }
23671 while let Some(grapheme) = iter.peek().copied() {
23672 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23673 break;
23674 };
23675 if is_grapheme_whitespace(grapheme) != is_whitespace
23676 || (grapheme == "\n") != is_newline
23677 {
23678 break;
23679 };
23680 offset += grapheme.len();
23681 grapheme_len += 1;
23682 iter.next();
23683 }
23684 }
23685 let token = &self.input[..offset];
23686 self.input = &self.input[offset..];
23687 if token == "\n" {
23688 Some(WordBreakToken::Newline)
23689 } else if is_whitespace {
23690 Some(WordBreakToken::InlineWhitespace {
23691 token,
23692 grapheme_len,
23693 })
23694 } else {
23695 Some(WordBreakToken::Word {
23696 token,
23697 grapheme_len,
23698 })
23699 }
23700 } else {
23701 None
23702 }
23703 }
23704}
23705
23706#[test]
23707fn test_word_breaking_tokenizer() {
23708 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23709 ("", &[]),
23710 (" ", &[whitespace(" ", 2)]),
23711 ("Ʒ", &[word("Ʒ", 1)]),
23712 ("Ǽ", &[word("Ǽ", 1)]),
23713 ("⋑", &[word("⋑", 1)]),
23714 ("⋑⋑", &[word("⋑⋑", 2)]),
23715 (
23716 "原理,进而",
23717 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23718 ),
23719 (
23720 "hello world",
23721 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23722 ),
23723 (
23724 "hello, world",
23725 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23726 ),
23727 (
23728 " hello world",
23729 &[
23730 whitespace(" ", 2),
23731 word("hello", 5),
23732 whitespace(" ", 1),
23733 word("world", 5),
23734 ],
23735 ),
23736 (
23737 "这是什么 \n 钢笔",
23738 &[
23739 word("这", 1),
23740 word("是", 1),
23741 word("什", 1),
23742 word("么", 1),
23743 whitespace(" ", 1),
23744 newline(),
23745 whitespace(" ", 1),
23746 word("钢", 1),
23747 word("笔", 1),
23748 ],
23749 ),
23750 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23751 ];
23752
23753 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23754 WordBreakToken::Word {
23755 token,
23756 grapheme_len,
23757 }
23758 }
23759
23760 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23761 WordBreakToken::InlineWhitespace {
23762 token,
23763 grapheme_len,
23764 }
23765 }
23766
23767 fn newline() -> WordBreakToken<'static> {
23768 WordBreakToken::Newline
23769 }
23770
23771 for (input, result) in tests {
23772 assert_eq!(
23773 WordBreakingTokenizer::new(input)
23774 .collect::<Vec<_>>()
23775 .as_slice(),
23776 *result,
23777 );
23778 }
23779}
23780
23781fn wrap_with_prefix(
23782 first_line_prefix: String,
23783 subsequent_lines_prefix: String,
23784 unwrapped_text: String,
23785 wrap_column: usize,
23786 tab_size: NonZeroU32,
23787 preserve_existing_whitespace: bool,
23788) -> String {
23789 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23790 let subsequent_lines_prefix_len =
23791 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23792 let mut wrapped_text = String::new();
23793 let mut current_line = first_line_prefix;
23794 let mut is_first_line = true;
23795
23796 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23797 let mut current_line_len = first_line_prefix_len;
23798 let mut in_whitespace = false;
23799 for token in tokenizer {
23800 let have_preceding_whitespace = in_whitespace;
23801 match token {
23802 WordBreakToken::Word {
23803 token,
23804 grapheme_len,
23805 } => {
23806 in_whitespace = false;
23807 let current_prefix_len = if is_first_line {
23808 first_line_prefix_len
23809 } else {
23810 subsequent_lines_prefix_len
23811 };
23812 if current_line_len + grapheme_len > wrap_column
23813 && current_line_len != current_prefix_len
23814 {
23815 wrapped_text.push_str(current_line.trim_end());
23816 wrapped_text.push('\n');
23817 is_first_line = false;
23818 current_line = subsequent_lines_prefix.clone();
23819 current_line_len = subsequent_lines_prefix_len;
23820 }
23821 current_line.push_str(token);
23822 current_line_len += grapheme_len;
23823 }
23824 WordBreakToken::InlineWhitespace {
23825 mut token,
23826 mut grapheme_len,
23827 } => {
23828 in_whitespace = true;
23829 if have_preceding_whitespace && !preserve_existing_whitespace {
23830 continue;
23831 }
23832 if !preserve_existing_whitespace {
23833 // Keep a single whitespace grapheme as-is
23834 if let Some(first) =
23835 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23836 {
23837 token = first;
23838 } else {
23839 token = " ";
23840 }
23841 grapheme_len = 1;
23842 }
23843 let current_prefix_len = if is_first_line {
23844 first_line_prefix_len
23845 } else {
23846 subsequent_lines_prefix_len
23847 };
23848 if current_line_len + grapheme_len > wrap_column {
23849 wrapped_text.push_str(current_line.trim_end());
23850 wrapped_text.push('\n');
23851 is_first_line = false;
23852 current_line = subsequent_lines_prefix.clone();
23853 current_line_len = subsequent_lines_prefix_len;
23854 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23855 current_line.push_str(token);
23856 current_line_len += grapheme_len;
23857 }
23858 }
23859 WordBreakToken::Newline => {
23860 in_whitespace = true;
23861 let current_prefix_len = if is_first_line {
23862 first_line_prefix_len
23863 } else {
23864 subsequent_lines_prefix_len
23865 };
23866 if preserve_existing_whitespace {
23867 wrapped_text.push_str(current_line.trim_end());
23868 wrapped_text.push('\n');
23869 is_first_line = false;
23870 current_line = subsequent_lines_prefix.clone();
23871 current_line_len = subsequent_lines_prefix_len;
23872 } else if have_preceding_whitespace {
23873 continue;
23874 } else if current_line_len + 1 > wrap_column
23875 && current_line_len != current_prefix_len
23876 {
23877 wrapped_text.push_str(current_line.trim_end());
23878 wrapped_text.push('\n');
23879 is_first_line = false;
23880 current_line = subsequent_lines_prefix.clone();
23881 current_line_len = subsequent_lines_prefix_len;
23882 } else if current_line_len != current_prefix_len {
23883 current_line.push(' ');
23884 current_line_len += 1;
23885 }
23886 }
23887 }
23888 }
23889
23890 if !current_line.is_empty() {
23891 wrapped_text.push_str(¤t_line);
23892 }
23893 wrapped_text
23894}
23895
23896#[test]
23897fn test_wrap_with_prefix() {
23898 assert_eq!(
23899 wrap_with_prefix(
23900 "# ".to_string(),
23901 "# ".to_string(),
23902 "abcdefg".to_string(),
23903 4,
23904 NonZeroU32::new(4).unwrap(),
23905 false,
23906 ),
23907 "# abcdefg"
23908 );
23909 assert_eq!(
23910 wrap_with_prefix(
23911 "".to_string(),
23912 "".to_string(),
23913 "\thello world".to_string(),
23914 8,
23915 NonZeroU32::new(4).unwrap(),
23916 false,
23917 ),
23918 "hello\nworld"
23919 );
23920 assert_eq!(
23921 wrap_with_prefix(
23922 "// ".to_string(),
23923 "// ".to_string(),
23924 "xx \nyy zz aa bb cc".to_string(),
23925 12,
23926 NonZeroU32::new(4).unwrap(),
23927 false,
23928 ),
23929 "// xx yy zz\n// aa bb cc"
23930 );
23931 assert_eq!(
23932 wrap_with_prefix(
23933 String::new(),
23934 String::new(),
23935 "这是什么 \n 钢笔".to_string(),
23936 3,
23937 NonZeroU32::new(4).unwrap(),
23938 false,
23939 ),
23940 "这是什\n么 钢\n笔"
23941 );
23942 assert_eq!(
23943 wrap_with_prefix(
23944 String::new(),
23945 String::new(),
23946 format!("foo{}bar", '\u{2009}'), // thin space
23947 80,
23948 NonZeroU32::new(4).unwrap(),
23949 false,
23950 ),
23951 format!("foo{}bar", '\u{2009}')
23952 );
23953}
23954
23955pub trait CollaborationHub {
23956 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23957 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23958 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23959}
23960
23961impl CollaborationHub for Entity<Project> {
23962 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23963 self.read(cx).collaborators()
23964 }
23965
23966 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23967 self.read(cx).user_store().read(cx).participant_indices()
23968 }
23969
23970 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23971 let this = self.read(cx);
23972 let user_ids = this.collaborators().values().map(|c| c.user_id);
23973 this.user_store().read(cx).participant_names(user_ids, cx)
23974 }
23975}
23976
23977pub trait SemanticsProvider {
23978 fn hover(
23979 &self,
23980 buffer: &Entity<Buffer>,
23981 position: text::Anchor,
23982 cx: &mut App,
23983 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23984
23985 fn inline_values(
23986 &self,
23987 buffer_handle: Entity<Buffer>,
23988 range: Range<text::Anchor>,
23989 cx: &mut App,
23990 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23991
23992 fn applicable_inlay_chunks(
23993 &self,
23994 buffer: &Entity<Buffer>,
23995 ranges: &[Range<text::Anchor>],
23996 cx: &mut App,
23997 ) -> Vec<Range<BufferRow>>;
23998
23999 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
24000
24001 fn inlay_hints(
24002 &self,
24003 invalidate: InvalidationStrategy,
24004 buffer: Entity<Buffer>,
24005 ranges: Vec<Range<text::Anchor>>,
24006 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24007 cx: &mut App,
24008 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
24009
24010 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
24011
24012 fn document_highlights(
24013 &self,
24014 buffer: &Entity<Buffer>,
24015 position: text::Anchor,
24016 cx: &mut App,
24017 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
24018
24019 fn definitions(
24020 &self,
24021 buffer: &Entity<Buffer>,
24022 position: text::Anchor,
24023 kind: GotoDefinitionKind,
24024 cx: &mut App,
24025 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
24026
24027 fn range_for_rename(
24028 &self,
24029 buffer: &Entity<Buffer>,
24030 position: text::Anchor,
24031 cx: &mut App,
24032 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
24033
24034 fn perform_rename(
24035 &self,
24036 buffer: &Entity<Buffer>,
24037 position: text::Anchor,
24038 new_name: String,
24039 cx: &mut App,
24040 ) -> Option<Task<Result<ProjectTransaction>>>;
24041}
24042
24043pub trait CompletionProvider {
24044 fn completions(
24045 &self,
24046 excerpt_id: ExcerptId,
24047 buffer: &Entity<Buffer>,
24048 buffer_position: text::Anchor,
24049 trigger: CompletionContext,
24050 window: &mut Window,
24051 cx: &mut Context<Editor>,
24052 ) -> Task<Result<Vec<CompletionResponse>>>;
24053
24054 fn resolve_completions(
24055 &self,
24056 _buffer: Entity<Buffer>,
24057 _completion_indices: Vec<usize>,
24058 _completions: Rc<RefCell<Box<[Completion]>>>,
24059 _cx: &mut Context<Editor>,
24060 ) -> Task<Result<bool>> {
24061 Task::ready(Ok(false))
24062 }
24063
24064 fn apply_additional_edits_for_completion(
24065 &self,
24066 _buffer: Entity<Buffer>,
24067 _completions: Rc<RefCell<Box<[Completion]>>>,
24068 _completion_index: usize,
24069 _push_to_history: bool,
24070 _cx: &mut Context<Editor>,
24071 ) -> Task<Result<Option<language::Transaction>>> {
24072 Task::ready(Ok(None))
24073 }
24074
24075 fn is_completion_trigger(
24076 &self,
24077 buffer: &Entity<Buffer>,
24078 position: language::Anchor,
24079 text: &str,
24080 trigger_in_words: bool,
24081 cx: &mut Context<Editor>,
24082 ) -> bool;
24083
24084 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24085
24086 fn sort_completions(&self) -> bool {
24087 true
24088 }
24089
24090 fn filter_completions(&self) -> bool {
24091 true
24092 }
24093
24094 fn show_snippets(&self) -> bool {
24095 false
24096 }
24097}
24098
24099pub trait CodeActionProvider {
24100 fn id(&self) -> Arc<str>;
24101
24102 fn code_actions(
24103 &self,
24104 buffer: &Entity<Buffer>,
24105 range: Range<text::Anchor>,
24106 window: &mut Window,
24107 cx: &mut App,
24108 ) -> Task<Result<Vec<CodeAction>>>;
24109
24110 fn apply_code_action(
24111 &self,
24112 buffer_handle: Entity<Buffer>,
24113 action: CodeAction,
24114 excerpt_id: ExcerptId,
24115 push_to_history: bool,
24116 window: &mut Window,
24117 cx: &mut App,
24118 ) -> Task<Result<ProjectTransaction>>;
24119}
24120
24121impl CodeActionProvider for Entity<Project> {
24122 fn id(&self) -> Arc<str> {
24123 "project".into()
24124 }
24125
24126 fn code_actions(
24127 &self,
24128 buffer: &Entity<Buffer>,
24129 range: Range<text::Anchor>,
24130 _window: &mut Window,
24131 cx: &mut App,
24132 ) -> Task<Result<Vec<CodeAction>>> {
24133 self.update(cx, |project, cx| {
24134 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24135 let code_actions = project.code_actions(buffer, range, None, cx);
24136 cx.background_spawn(async move {
24137 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24138 Ok(code_lens_actions
24139 .context("code lens fetch")?
24140 .into_iter()
24141 .flatten()
24142 .chain(
24143 code_actions
24144 .context("code action fetch")?
24145 .into_iter()
24146 .flatten(),
24147 )
24148 .collect())
24149 })
24150 })
24151 }
24152
24153 fn apply_code_action(
24154 &self,
24155 buffer_handle: Entity<Buffer>,
24156 action: CodeAction,
24157 _excerpt_id: ExcerptId,
24158 push_to_history: bool,
24159 _window: &mut Window,
24160 cx: &mut App,
24161 ) -> Task<Result<ProjectTransaction>> {
24162 self.update(cx, |project, cx| {
24163 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24164 })
24165 }
24166}
24167
24168fn snippet_completions(
24169 project: &Project,
24170 buffer: &Entity<Buffer>,
24171 buffer_anchor: text::Anchor,
24172 classifier: CharClassifier,
24173 cx: &mut App,
24174) -> Task<Result<CompletionResponse>> {
24175 let languages = buffer.read(cx).languages_at(buffer_anchor);
24176 let snippet_store = project.snippets().read(cx);
24177
24178 let scopes: Vec<_> = languages
24179 .iter()
24180 .filter_map(|language| {
24181 let language_name = language.lsp_id();
24182 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24183
24184 if snippets.is_empty() {
24185 None
24186 } else {
24187 Some((language.default_scope(), snippets))
24188 }
24189 })
24190 .collect();
24191
24192 if scopes.is_empty() {
24193 return Task::ready(Ok(CompletionResponse {
24194 completions: vec![],
24195 display_options: CompletionDisplayOptions::default(),
24196 is_incomplete: false,
24197 }));
24198 }
24199
24200 let snapshot = buffer.read(cx).text_snapshot();
24201 let executor = cx.background_executor().clone();
24202
24203 cx.background_spawn(async move {
24204 let is_word_char = |c| classifier.is_word(c);
24205
24206 let mut is_incomplete = false;
24207 let mut completions: Vec<Completion> = Vec::new();
24208
24209 const MAX_PREFIX_LEN: usize = 128;
24210 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24211 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24212 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24213
24214 let max_buffer_window: String = snapshot
24215 .text_for_range(window_start..buffer_offset)
24216 .collect();
24217
24218 if max_buffer_window.is_empty() {
24219 return Ok(CompletionResponse {
24220 completions: vec![],
24221 display_options: CompletionDisplayOptions::default(),
24222 is_incomplete: true,
24223 });
24224 }
24225
24226 for (_scope, snippets) in scopes.into_iter() {
24227 // Sort snippets by word count to match longer snippet prefixes first.
24228 let mut sorted_snippet_candidates = snippets
24229 .iter()
24230 .enumerate()
24231 .flat_map(|(snippet_ix, snippet)| {
24232 snippet
24233 .prefix
24234 .iter()
24235 .enumerate()
24236 .map(move |(prefix_ix, prefix)| {
24237 let word_count =
24238 snippet_candidate_suffixes(prefix, is_word_char).count();
24239 ((snippet_ix, prefix_ix), prefix, word_count)
24240 })
24241 })
24242 .collect_vec();
24243 sorted_snippet_candidates
24244 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24245
24246 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24247
24248 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24249 .take(
24250 sorted_snippet_candidates
24251 .first()
24252 .map(|(_, _, word_count)| *word_count)
24253 .unwrap_or_default(),
24254 )
24255 .collect_vec();
24256
24257 const MAX_RESULTS: usize = 100;
24258 // Each match also remembers how many characters from the buffer it consumed
24259 let mut matches: Vec<(StringMatch, usize)> = vec![];
24260
24261 let mut snippet_list_cutoff_index = 0;
24262 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24263 let word_count = buffer_index + 1;
24264 // Increase `snippet_list_cutoff_index` until we have all of the
24265 // snippets with sufficiently many words.
24266 while sorted_snippet_candidates
24267 .get(snippet_list_cutoff_index)
24268 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24269 *snippet_word_count >= word_count
24270 })
24271 {
24272 snippet_list_cutoff_index += 1;
24273 }
24274
24275 // Take only the candidates with at least `word_count` many words
24276 let snippet_candidates_at_word_len =
24277 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24278
24279 let candidates = snippet_candidates_at_word_len
24280 .iter()
24281 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24282 .enumerate() // index in `sorted_snippet_candidates`
24283 // First char must match
24284 .filter(|(_ix, prefix)| {
24285 itertools::equal(
24286 prefix
24287 .chars()
24288 .next()
24289 .into_iter()
24290 .flat_map(|c| c.to_lowercase()),
24291 buffer_window
24292 .chars()
24293 .next()
24294 .into_iter()
24295 .flat_map(|c| c.to_lowercase()),
24296 )
24297 })
24298 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24299 .collect::<Vec<StringMatchCandidate>>();
24300
24301 matches.extend(
24302 fuzzy::match_strings(
24303 &candidates,
24304 &buffer_window,
24305 buffer_window.chars().any(|c| c.is_uppercase()),
24306 true,
24307 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24308 &Default::default(),
24309 executor.clone(),
24310 )
24311 .await
24312 .into_iter()
24313 .map(|string_match| (string_match, buffer_window.len())),
24314 );
24315
24316 if matches.len() >= MAX_RESULTS {
24317 break;
24318 }
24319 }
24320
24321 let to_lsp = |point: &text::Anchor| {
24322 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24323 point_to_lsp(end)
24324 };
24325 let lsp_end = to_lsp(&buffer_anchor);
24326
24327 if matches.len() >= MAX_RESULTS {
24328 is_incomplete = true;
24329 }
24330
24331 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24332 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24333 sorted_snippet_candidates[string_match.candidate_id];
24334 let snippet = &snippets[snippet_index];
24335 let start = buffer_offset - buffer_window_len;
24336 let start = snapshot.anchor_before(start);
24337 let range = start..buffer_anchor;
24338 let lsp_start = to_lsp(&start);
24339 let lsp_range = lsp::Range {
24340 start: lsp_start,
24341 end: lsp_end,
24342 };
24343 Completion {
24344 replace_range: range,
24345 new_text: snippet.body.clone(),
24346 source: CompletionSource::Lsp {
24347 insert_range: None,
24348 server_id: LanguageServerId(usize::MAX),
24349 resolved: true,
24350 lsp_completion: Box::new(lsp::CompletionItem {
24351 label: snippet.prefix.first().unwrap().clone(),
24352 kind: Some(CompletionItemKind::SNIPPET),
24353 label_details: snippet.description.as_ref().map(|description| {
24354 lsp::CompletionItemLabelDetails {
24355 detail: Some(description.clone()),
24356 description: None,
24357 }
24358 }),
24359 insert_text_format: Some(InsertTextFormat::SNIPPET),
24360 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24361 lsp::InsertReplaceEdit {
24362 new_text: snippet.body.clone(),
24363 insert: lsp_range,
24364 replace: lsp_range,
24365 },
24366 )),
24367 filter_text: Some(snippet.body.clone()),
24368 sort_text: Some(char::MAX.to_string()),
24369 ..lsp::CompletionItem::default()
24370 }),
24371 lsp_defaults: None,
24372 },
24373 label: CodeLabel {
24374 text: matching_prefix.clone(),
24375 runs: Vec::new(),
24376 filter_range: 0..matching_prefix.len(),
24377 },
24378 icon_path: None,
24379 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24380 single_line: snippet.name.clone().into(),
24381 plain_text: snippet
24382 .description
24383 .clone()
24384 .map(|description| description.into()),
24385 }),
24386 insert_text_mode: None,
24387 confirm: None,
24388 match_start: Some(start),
24389 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24390 }
24391 }));
24392 }
24393
24394 Ok(CompletionResponse {
24395 completions,
24396 display_options: CompletionDisplayOptions::default(),
24397 is_incomplete,
24398 })
24399 })
24400}
24401
24402impl CompletionProvider for Entity<Project> {
24403 fn completions(
24404 &self,
24405 _excerpt_id: ExcerptId,
24406 buffer: &Entity<Buffer>,
24407 buffer_position: text::Anchor,
24408 options: CompletionContext,
24409 _window: &mut Window,
24410 cx: &mut Context<Editor>,
24411 ) -> Task<Result<Vec<CompletionResponse>>> {
24412 self.update(cx, |project, cx| {
24413 let task = project.completions(buffer, buffer_position, options, cx);
24414 cx.background_spawn(task)
24415 })
24416 }
24417
24418 fn resolve_completions(
24419 &self,
24420 buffer: Entity<Buffer>,
24421 completion_indices: Vec<usize>,
24422 completions: Rc<RefCell<Box<[Completion]>>>,
24423 cx: &mut Context<Editor>,
24424 ) -> Task<Result<bool>> {
24425 self.update(cx, |project, cx| {
24426 project.lsp_store().update(cx, |lsp_store, cx| {
24427 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24428 })
24429 })
24430 }
24431
24432 fn apply_additional_edits_for_completion(
24433 &self,
24434 buffer: Entity<Buffer>,
24435 completions: Rc<RefCell<Box<[Completion]>>>,
24436 completion_index: usize,
24437 push_to_history: bool,
24438 cx: &mut Context<Editor>,
24439 ) -> Task<Result<Option<language::Transaction>>> {
24440 self.update(cx, |project, cx| {
24441 project.lsp_store().update(cx, |lsp_store, cx| {
24442 lsp_store.apply_additional_edits_for_completion(
24443 buffer,
24444 completions,
24445 completion_index,
24446 push_to_history,
24447 cx,
24448 )
24449 })
24450 })
24451 }
24452
24453 fn is_completion_trigger(
24454 &self,
24455 buffer: &Entity<Buffer>,
24456 position: language::Anchor,
24457 text: &str,
24458 trigger_in_words: bool,
24459 cx: &mut Context<Editor>,
24460 ) -> bool {
24461 let mut chars = text.chars();
24462 let char = if let Some(char) = chars.next() {
24463 char
24464 } else {
24465 return false;
24466 };
24467 if chars.next().is_some() {
24468 return false;
24469 }
24470
24471 let buffer = buffer.read(cx);
24472 let snapshot = buffer.snapshot();
24473 let classifier = snapshot
24474 .char_classifier_at(position)
24475 .scope_context(Some(CharScopeContext::Completion));
24476 if trigger_in_words && classifier.is_word(char) {
24477 return true;
24478 }
24479
24480 buffer.completion_triggers().contains(text)
24481 }
24482
24483 fn show_snippets(&self) -> bool {
24484 true
24485 }
24486}
24487
24488impl SemanticsProvider for Entity<Project> {
24489 fn hover(
24490 &self,
24491 buffer: &Entity<Buffer>,
24492 position: text::Anchor,
24493 cx: &mut App,
24494 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24495 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24496 }
24497
24498 fn document_highlights(
24499 &self,
24500 buffer: &Entity<Buffer>,
24501 position: text::Anchor,
24502 cx: &mut App,
24503 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24504 Some(self.update(cx, |project, cx| {
24505 project.document_highlights(buffer, position, cx)
24506 }))
24507 }
24508
24509 fn definitions(
24510 &self,
24511 buffer: &Entity<Buffer>,
24512 position: text::Anchor,
24513 kind: GotoDefinitionKind,
24514 cx: &mut App,
24515 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24516 Some(self.update(cx, |project, cx| match kind {
24517 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24518 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24519 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24520 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24521 }))
24522 }
24523
24524 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24525 self.update(cx, |project, cx| {
24526 if project
24527 .active_debug_session(cx)
24528 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24529 {
24530 return true;
24531 }
24532
24533 buffer.update(cx, |buffer, cx| {
24534 project.any_language_server_supports_inlay_hints(buffer, cx)
24535 })
24536 })
24537 }
24538
24539 fn inline_values(
24540 &self,
24541 buffer_handle: Entity<Buffer>,
24542 range: Range<text::Anchor>,
24543 cx: &mut App,
24544 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24545 self.update(cx, |project, cx| {
24546 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24547
24548 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24549 })
24550 }
24551
24552 fn applicable_inlay_chunks(
24553 &self,
24554 buffer: &Entity<Buffer>,
24555 ranges: &[Range<text::Anchor>],
24556 cx: &mut App,
24557 ) -> Vec<Range<BufferRow>> {
24558 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24559 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24560 })
24561 }
24562
24563 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24564 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24565 lsp_store.invalidate_inlay_hints(for_buffers)
24566 });
24567 }
24568
24569 fn inlay_hints(
24570 &self,
24571 invalidate: InvalidationStrategy,
24572 buffer: Entity<Buffer>,
24573 ranges: Vec<Range<text::Anchor>>,
24574 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24575 cx: &mut App,
24576 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24577 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24578 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24579 }))
24580 }
24581
24582 fn range_for_rename(
24583 &self,
24584 buffer: &Entity<Buffer>,
24585 position: text::Anchor,
24586 cx: &mut App,
24587 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24588 Some(self.update(cx, |project, cx| {
24589 let buffer = buffer.clone();
24590 let task = project.prepare_rename(buffer.clone(), position, cx);
24591 cx.spawn(async move |_, cx| {
24592 Ok(match task.await? {
24593 PrepareRenameResponse::Success(range) => Some(range),
24594 PrepareRenameResponse::InvalidPosition => None,
24595 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24596 // Fallback on using TreeSitter info to determine identifier range
24597 buffer.read_with(cx, |buffer, _| {
24598 let snapshot = buffer.snapshot();
24599 let (range, kind) = snapshot.surrounding_word(position, None);
24600 if kind != Some(CharKind::Word) {
24601 return None;
24602 }
24603 Some(
24604 snapshot.anchor_before(range.start)
24605 ..snapshot.anchor_after(range.end),
24606 )
24607 })?
24608 }
24609 })
24610 })
24611 }))
24612 }
24613
24614 fn perform_rename(
24615 &self,
24616 buffer: &Entity<Buffer>,
24617 position: text::Anchor,
24618 new_name: String,
24619 cx: &mut App,
24620 ) -> Option<Task<Result<ProjectTransaction>>> {
24621 Some(self.update(cx, |project, cx| {
24622 project.perform_rename(buffer.clone(), position, new_name, cx)
24623 }))
24624 }
24625}
24626
24627fn consume_contiguous_rows(
24628 contiguous_row_selections: &mut Vec<Selection<Point>>,
24629 selection: &Selection<Point>,
24630 display_map: &DisplaySnapshot,
24631 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24632) -> (MultiBufferRow, MultiBufferRow) {
24633 contiguous_row_selections.push(selection.clone());
24634 let start_row = starting_row(selection, display_map);
24635 let mut end_row = ending_row(selection, display_map);
24636
24637 while let Some(next_selection) = selections.peek() {
24638 if next_selection.start.row <= end_row.0 {
24639 end_row = ending_row(next_selection, display_map);
24640 contiguous_row_selections.push(selections.next().unwrap().clone());
24641 } else {
24642 break;
24643 }
24644 }
24645 (start_row, end_row)
24646}
24647
24648fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24649 if selection.start.column > 0 {
24650 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24651 } else {
24652 MultiBufferRow(selection.start.row)
24653 }
24654}
24655
24656fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24657 if next_selection.end.column > 0 || next_selection.is_empty() {
24658 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24659 } else {
24660 MultiBufferRow(next_selection.end.row)
24661 }
24662}
24663
24664impl EditorSnapshot {
24665 pub fn remote_selections_in_range<'a>(
24666 &'a self,
24667 range: &'a Range<Anchor>,
24668 collaboration_hub: &dyn CollaborationHub,
24669 cx: &'a App,
24670 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24671 let participant_names = collaboration_hub.user_names(cx);
24672 let participant_indices = collaboration_hub.user_participant_indices(cx);
24673 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24674 let collaborators_by_replica_id = collaborators_by_peer_id
24675 .values()
24676 .map(|collaborator| (collaborator.replica_id, collaborator))
24677 .collect::<HashMap<_, _>>();
24678 self.buffer_snapshot()
24679 .selections_in_range(range, false)
24680 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24681 if replica_id == ReplicaId::AGENT {
24682 Some(RemoteSelection {
24683 replica_id,
24684 selection,
24685 cursor_shape,
24686 line_mode,
24687 collaborator_id: CollaboratorId::Agent,
24688 user_name: Some("Agent".into()),
24689 color: cx.theme().players().agent(),
24690 })
24691 } else {
24692 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24693 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24694 let user_name = participant_names.get(&collaborator.user_id).cloned();
24695 Some(RemoteSelection {
24696 replica_id,
24697 selection,
24698 cursor_shape,
24699 line_mode,
24700 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24701 user_name,
24702 color: if let Some(index) = participant_index {
24703 cx.theme().players().color_for_participant(index.0)
24704 } else {
24705 cx.theme().players().absent()
24706 },
24707 })
24708 }
24709 })
24710 }
24711
24712 pub fn hunks_for_ranges(
24713 &self,
24714 ranges: impl IntoIterator<Item = Range<Point>>,
24715 ) -> Vec<MultiBufferDiffHunk> {
24716 let mut hunks = Vec::new();
24717 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24718 HashMap::default();
24719 for query_range in ranges {
24720 let query_rows =
24721 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24722 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24723 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24724 ) {
24725 // Include deleted hunks that are adjacent to the query range, because
24726 // otherwise they would be missed.
24727 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24728 if hunk.status().is_deleted() {
24729 intersects_range |= hunk.row_range.start == query_rows.end;
24730 intersects_range |= hunk.row_range.end == query_rows.start;
24731 }
24732 if intersects_range {
24733 if !processed_buffer_rows
24734 .entry(hunk.buffer_id)
24735 .or_default()
24736 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24737 {
24738 continue;
24739 }
24740 hunks.push(hunk);
24741 }
24742 }
24743 }
24744
24745 hunks
24746 }
24747
24748 fn display_diff_hunks_for_rows<'a>(
24749 &'a self,
24750 display_rows: Range<DisplayRow>,
24751 folded_buffers: &'a HashSet<BufferId>,
24752 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24753 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24754 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24755
24756 self.buffer_snapshot()
24757 .diff_hunks_in_range(buffer_start..buffer_end)
24758 .filter_map(|hunk| {
24759 if folded_buffers.contains(&hunk.buffer_id) {
24760 return None;
24761 }
24762
24763 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24764 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24765
24766 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24767 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24768
24769 let display_hunk = if hunk_display_start.column() != 0 {
24770 DisplayDiffHunk::Folded {
24771 display_row: hunk_display_start.row(),
24772 }
24773 } else {
24774 let mut end_row = hunk_display_end.row();
24775 if hunk_display_end.column() > 0 {
24776 end_row.0 += 1;
24777 }
24778 let is_created_file = hunk.is_created_file();
24779
24780 DisplayDiffHunk::Unfolded {
24781 status: hunk.status(),
24782 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24783 ..hunk.diff_base_byte_range.end.0,
24784 word_diffs: hunk.word_diffs,
24785 display_row_range: hunk_display_start.row()..end_row,
24786 multi_buffer_range: Anchor::range_in_buffer(
24787 hunk.excerpt_id,
24788 hunk.buffer_range,
24789 ),
24790 is_created_file,
24791 }
24792 };
24793
24794 Some(display_hunk)
24795 })
24796 }
24797
24798 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24799 self.display_snapshot
24800 .buffer_snapshot()
24801 .language_at(position)
24802 }
24803
24804 pub fn is_focused(&self) -> bool {
24805 self.is_focused
24806 }
24807
24808 pub fn placeholder_text(&self) -> Option<String> {
24809 self.placeholder_display_snapshot
24810 .as_ref()
24811 .map(|display_map| display_map.text())
24812 }
24813
24814 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24815 self.scroll_anchor.scroll_position(&self.display_snapshot)
24816 }
24817
24818 pub fn gutter_dimensions(
24819 &self,
24820 font_id: FontId,
24821 font_size: Pixels,
24822 style: &EditorStyle,
24823 window: &mut Window,
24824 cx: &App,
24825 ) -> GutterDimensions {
24826 if self.show_gutter
24827 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24828 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24829 {
24830 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24831 matches!(
24832 ProjectSettings::get_global(cx).git.git_gutter,
24833 GitGutterSetting::TrackedFiles
24834 )
24835 });
24836 let gutter_settings = EditorSettings::get_global(cx).gutter;
24837 let show_line_numbers = self
24838 .show_line_numbers
24839 .unwrap_or(gutter_settings.line_numbers);
24840 let line_gutter_width = if show_line_numbers {
24841 // Avoid flicker-like gutter resizes when the line number gains another digit by
24842 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24843 let min_width_for_number_on_gutter =
24844 ch_advance * gutter_settings.min_line_number_digits as f32;
24845 self.max_line_number_width(style, window)
24846 .max(min_width_for_number_on_gutter)
24847 } else {
24848 0.0.into()
24849 };
24850
24851 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24852 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24853
24854 let git_blame_entries_width =
24855 self.git_blame_gutter_max_author_length
24856 .map(|max_author_length| {
24857 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24858 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24859
24860 /// The number of characters to dedicate to gaps and margins.
24861 const SPACING_WIDTH: usize = 4;
24862
24863 let max_char_count = max_author_length.min(renderer.max_author_length())
24864 + ::git::SHORT_SHA_LENGTH
24865 + MAX_RELATIVE_TIMESTAMP.len()
24866 + SPACING_WIDTH;
24867
24868 ch_advance * max_char_count
24869 });
24870
24871 let is_singleton = self.buffer_snapshot().is_singleton();
24872
24873 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24874 left_padding += if !is_singleton {
24875 ch_width * 4.0
24876 } else if show_runnables || show_breakpoints {
24877 ch_width * 3.0
24878 } else if show_git_gutter && show_line_numbers {
24879 ch_width * 2.0
24880 } else if show_git_gutter || show_line_numbers {
24881 ch_width
24882 } else {
24883 px(0.)
24884 };
24885
24886 let shows_folds = is_singleton && gutter_settings.folds;
24887
24888 let right_padding = if shows_folds && show_line_numbers {
24889 ch_width * 4.0
24890 } else if shows_folds || (!is_singleton && show_line_numbers) {
24891 ch_width * 3.0
24892 } else if show_line_numbers {
24893 ch_width
24894 } else {
24895 px(0.)
24896 };
24897
24898 GutterDimensions {
24899 left_padding,
24900 right_padding,
24901 width: line_gutter_width + left_padding + right_padding,
24902 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24903 git_blame_entries_width,
24904 }
24905 } else if self.offset_content {
24906 GutterDimensions::default_with_margin(font_id, font_size, cx)
24907 } else {
24908 GutterDimensions::default()
24909 }
24910 }
24911
24912 pub fn render_crease_toggle(
24913 &self,
24914 buffer_row: MultiBufferRow,
24915 row_contains_cursor: bool,
24916 editor: Entity<Editor>,
24917 window: &mut Window,
24918 cx: &mut App,
24919 ) -> Option<AnyElement> {
24920 let folded = self.is_line_folded(buffer_row);
24921 let mut is_foldable = false;
24922
24923 if let Some(crease) = self
24924 .crease_snapshot
24925 .query_row(buffer_row, self.buffer_snapshot())
24926 {
24927 is_foldable = true;
24928 match crease {
24929 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24930 if let Some(render_toggle) = render_toggle {
24931 let toggle_callback =
24932 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24933 if folded {
24934 editor.update(cx, |editor, cx| {
24935 editor.fold_at(buffer_row, window, cx)
24936 });
24937 } else {
24938 editor.update(cx, |editor, cx| {
24939 editor.unfold_at(buffer_row, window, cx)
24940 });
24941 }
24942 });
24943 return Some((render_toggle)(
24944 buffer_row,
24945 folded,
24946 toggle_callback,
24947 window,
24948 cx,
24949 ));
24950 }
24951 }
24952 }
24953 }
24954
24955 is_foldable |= self.starts_indent(buffer_row);
24956
24957 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24958 Some(
24959 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24960 .toggle_state(folded)
24961 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24962 if folded {
24963 this.unfold_at(buffer_row, window, cx);
24964 } else {
24965 this.fold_at(buffer_row, window, cx);
24966 }
24967 }))
24968 .into_any_element(),
24969 )
24970 } else {
24971 None
24972 }
24973 }
24974
24975 pub fn render_crease_trailer(
24976 &self,
24977 buffer_row: MultiBufferRow,
24978 window: &mut Window,
24979 cx: &mut App,
24980 ) -> Option<AnyElement> {
24981 let folded = self.is_line_folded(buffer_row);
24982 if let Crease::Inline { render_trailer, .. } = self
24983 .crease_snapshot
24984 .query_row(buffer_row, self.buffer_snapshot())?
24985 {
24986 let render_trailer = render_trailer.as_ref()?;
24987 Some(render_trailer(buffer_row, folded, window, cx))
24988 } else {
24989 None
24990 }
24991 }
24992
24993 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
24994 let digit_count = self.widest_line_number().ilog10() + 1;
24995 column_pixels(style, digit_count as usize, window)
24996 }
24997}
24998
24999pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
25000 let font_size = style.text.font_size.to_pixels(window.rem_size());
25001 let layout = window.text_system().shape_line(
25002 SharedString::from(" ".repeat(column)),
25003 font_size,
25004 &[TextRun {
25005 len: column,
25006 font: style.text.font(),
25007 color: Hsla::default(),
25008 ..Default::default()
25009 }],
25010 None,
25011 );
25012
25013 layout.width
25014}
25015
25016impl Deref for EditorSnapshot {
25017 type Target = DisplaySnapshot;
25018
25019 fn deref(&self) -> &Self::Target {
25020 &self.display_snapshot
25021 }
25022}
25023
25024#[derive(Clone, Debug, PartialEq, Eq)]
25025pub enum EditorEvent {
25026 InputIgnored {
25027 text: Arc<str>,
25028 },
25029 InputHandled {
25030 utf16_range_to_replace: Option<Range<isize>>,
25031 text: Arc<str>,
25032 },
25033 ExcerptsAdded {
25034 buffer: Entity<Buffer>,
25035 predecessor: ExcerptId,
25036 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
25037 },
25038 ExcerptsRemoved {
25039 ids: Vec<ExcerptId>,
25040 removed_buffer_ids: Vec<BufferId>,
25041 },
25042 BufferFoldToggled {
25043 ids: Vec<ExcerptId>,
25044 folded: bool,
25045 },
25046 ExcerptsEdited {
25047 ids: Vec<ExcerptId>,
25048 },
25049 ExcerptsExpanded {
25050 ids: Vec<ExcerptId>,
25051 },
25052 BufferEdited,
25053 Edited {
25054 transaction_id: clock::Lamport,
25055 },
25056 Reparsed(BufferId),
25057 Focused,
25058 FocusedIn,
25059 Blurred,
25060 DirtyChanged,
25061 Saved,
25062 TitleChanged,
25063 SelectionsChanged {
25064 local: bool,
25065 },
25066 ScrollPositionChanged {
25067 local: bool,
25068 autoscroll: bool,
25069 },
25070 TransactionUndone {
25071 transaction_id: clock::Lamport,
25072 },
25073 TransactionBegun {
25074 transaction_id: clock::Lamport,
25075 },
25076 CursorShapeChanged,
25077 BreadcrumbsChanged,
25078 PushedToNavHistory {
25079 anchor: Anchor,
25080 is_deactivate: bool,
25081 },
25082}
25083
25084impl EventEmitter<EditorEvent> for Editor {}
25085
25086impl Focusable for Editor {
25087 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25088 self.focus_handle.clone()
25089 }
25090}
25091
25092impl Render for Editor {
25093 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25094 EditorElement::new(&cx.entity(), self.create_style(cx))
25095 }
25096}
25097
25098impl EntityInputHandler for Editor {
25099 fn text_for_range(
25100 &mut self,
25101 range_utf16: Range<usize>,
25102 adjusted_range: &mut Option<Range<usize>>,
25103 _: &mut Window,
25104 cx: &mut Context<Self>,
25105 ) -> Option<String> {
25106 let snapshot = self.buffer.read(cx).read(cx);
25107 let start = snapshot.clip_offset_utf16(
25108 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25109 Bias::Left,
25110 );
25111 let end = snapshot.clip_offset_utf16(
25112 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25113 Bias::Right,
25114 );
25115 if (start.0.0..end.0.0) != range_utf16 {
25116 adjusted_range.replace(start.0.0..end.0.0);
25117 }
25118 Some(snapshot.text_for_range(start..end).collect())
25119 }
25120
25121 fn selected_text_range(
25122 &mut self,
25123 ignore_disabled_input: bool,
25124 _: &mut Window,
25125 cx: &mut Context<Self>,
25126 ) -> Option<UTF16Selection> {
25127 // Prevent the IME menu from appearing when holding down an alphabetic key
25128 // while input is disabled.
25129 if !ignore_disabled_input && !self.input_enabled {
25130 return None;
25131 }
25132
25133 let selection = self
25134 .selections
25135 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25136 let range = selection.range();
25137
25138 Some(UTF16Selection {
25139 range: range.start.0.0..range.end.0.0,
25140 reversed: selection.reversed,
25141 })
25142 }
25143
25144 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25145 let snapshot = self.buffer.read(cx).read(cx);
25146 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25147 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25148 }
25149
25150 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25151 self.clear_highlights::<InputComposition>(cx);
25152 self.ime_transaction.take();
25153 }
25154
25155 fn replace_text_in_range(
25156 &mut self,
25157 range_utf16: Option<Range<usize>>,
25158 text: &str,
25159 window: &mut Window,
25160 cx: &mut Context<Self>,
25161 ) {
25162 if !self.input_enabled {
25163 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25164 return;
25165 }
25166
25167 self.transact(window, cx, |this, window, cx| {
25168 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25169 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25170 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25171 Some(this.selection_replacement_ranges(range_utf16, cx))
25172 } else {
25173 this.marked_text_ranges(cx)
25174 };
25175
25176 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25177 let newest_selection_id = this.selections.newest_anchor().id;
25178 this.selections
25179 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25180 .iter()
25181 .zip(ranges_to_replace.iter())
25182 .find_map(|(selection, range)| {
25183 if selection.id == newest_selection_id {
25184 Some(
25185 (range.start.0.0 as isize - selection.head().0.0 as isize)
25186 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25187 )
25188 } else {
25189 None
25190 }
25191 })
25192 });
25193
25194 cx.emit(EditorEvent::InputHandled {
25195 utf16_range_to_replace: range_to_replace,
25196 text: text.into(),
25197 });
25198
25199 if let Some(new_selected_ranges) = new_selected_ranges {
25200 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25201 selections.select_ranges(new_selected_ranges)
25202 });
25203 this.backspace(&Default::default(), window, cx);
25204 }
25205
25206 this.handle_input(text, window, cx);
25207 });
25208
25209 if let Some(transaction) = self.ime_transaction {
25210 self.buffer.update(cx, |buffer, cx| {
25211 buffer.group_until_transaction(transaction, cx);
25212 });
25213 }
25214
25215 self.unmark_text(window, cx);
25216 }
25217
25218 fn replace_and_mark_text_in_range(
25219 &mut self,
25220 range_utf16: Option<Range<usize>>,
25221 text: &str,
25222 new_selected_range_utf16: Option<Range<usize>>,
25223 window: &mut Window,
25224 cx: &mut Context<Self>,
25225 ) {
25226 if !self.input_enabled {
25227 return;
25228 }
25229
25230 let transaction = self.transact(window, cx, |this, window, cx| {
25231 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25232 let snapshot = this.buffer.read(cx).read(cx);
25233 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25234 for marked_range in &mut marked_ranges {
25235 marked_range.end = marked_range.start + relative_range_utf16.end;
25236 marked_range.start += relative_range_utf16.start;
25237 marked_range.start =
25238 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25239 marked_range.end =
25240 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25241 }
25242 }
25243 Some(marked_ranges)
25244 } else if let Some(range_utf16) = range_utf16 {
25245 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25246 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25247 Some(this.selection_replacement_ranges(range_utf16, cx))
25248 } else {
25249 None
25250 };
25251
25252 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25253 let newest_selection_id = this.selections.newest_anchor().id;
25254 this.selections
25255 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25256 .iter()
25257 .zip(ranges_to_replace.iter())
25258 .find_map(|(selection, range)| {
25259 if selection.id == newest_selection_id {
25260 Some(
25261 (range.start.0.0 as isize - selection.head().0.0 as isize)
25262 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25263 )
25264 } else {
25265 None
25266 }
25267 })
25268 });
25269
25270 cx.emit(EditorEvent::InputHandled {
25271 utf16_range_to_replace: range_to_replace,
25272 text: text.into(),
25273 });
25274
25275 if let Some(ranges) = ranges_to_replace {
25276 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25277 s.select_ranges(ranges)
25278 });
25279 }
25280
25281 let marked_ranges = {
25282 let snapshot = this.buffer.read(cx).read(cx);
25283 this.selections
25284 .disjoint_anchors_arc()
25285 .iter()
25286 .map(|selection| {
25287 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25288 })
25289 .collect::<Vec<_>>()
25290 };
25291
25292 if text.is_empty() {
25293 this.unmark_text(window, cx);
25294 } else {
25295 this.highlight_text::<InputComposition>(
25296 marked_ranges.clone(),
25297 HighlightStyle {
25298 underline: Some(UnderlineStyle {
25299 thickness: px(1.),
25300 color: None,
25301 wavy: false,
25302 }),
25303 ..Default::default()
25304 },
25305 cx,
25306 );
25307 }
25308
25309 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25310 let use_autoclose = this.use_autoclose;
25311 let use_auto_surround = this.use_auto_surround;
25312 this.set_use_autoclose(false);
25313 this.set_use_auto_surround(false);
25314 this.handle_input(text, window, cx);
25315 this.set_use_autoclose(use_autoclose);
25316 this.set_use_auto_surround(use_auto_surround);
25317
25318 if let Some(new_selected_range) = new_selected_range_utf16 {
25319 let snapshot = this.buffer.read(cx).read(cx);
25320 let new_selected_ranges = marked_ranges
25321 .into_iter()
25322 .map(|marked_range| {
25323 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25324 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25325 insertion_start.0 + new_selected_range.start,
25326 ));
25327 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25328 insertion_start.0 + new_selected_range.end,
25329 ));
25330 snapshot.clip_offset_utf16(new_start, Bias::Left)
25331 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25332 })
25333 .collect::<Vec<_>>();
25334
25335 drop(snapshot);
25336 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25337 selections.select_ranges(new_selected_ranges)
25338 });
25339 }
25340 });
25341
25342 self.ime_transaction = self.ime_transaction.or(transaction);
25343 if let Some(transaction) = self.ime_transaction {
25344 self.buffer.update(cx, |buffer, cx| {
25345 buffer.group_until_transaction(transaction, cx);
25346 });
25347 }
25348
25349 if self.text_highlights::<InputComposition>(cx).is_none() {
25350 self.ime_transaction.take();
25351 }
25352 }
25353
25354 fn bounds_for_range(
25355 &mut self,
25356 range_utf16: Range<usize>,
25357 element_bounds: gpui::Bounds<Pixels>,
25358 window: &mut Window,
25359 cx: &mut Context<Self>,
25360 ) -> Option<gpui::Bounds<Pixels>> {
25361 let text_layout_details = self.text_layout_details(window);
25362 let CharacterDimensions {
25363 em_width,
25364 em_advance,
25365 line_height,
25366 } = self.character_dimensions(window);
25367
25368 let snapshot = self.snapshot(window, cx);
25369 let scroll_position = snapshot.scroll_position();
25370 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25371
25372 let start =
25373 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25374 let x = Pixels::from(
25375 ScrollOffset::from(
25376 snapshot.x_for_display_point(start, &text_layout_details)
25377 + self.gutter_dimensions.full_width(),
25378 ) - scroll_left,
25379 );
25380 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25381
25382 Some(Bounds {
25383 origin: element_bounds.origin + point(x, y),
25384 size: size(em_width, line_height),
25385 })
25386 }
25387
25388 fn character_index_for_point(
25389 &mut self,
25390 point: gpui::Point<Pixels>,
25391 _window: &mut Window,
25392 _cx: &mut Context<Self>,
25393 ) -> Option<usize> {
25394 let position_map = self.last_position_map.as_ref()?;
25395 if !position_map.text_hitbox.contains(&point) {
25396 return None;
25397 }
25398 let display_point = position_map.point_for_position(point).previous_valid;
25399 let anchor = position_map
25400 .snapshot
25401 .display_point_to_anchor(display_point, Bias::Left);
25402 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25403 Some(utf16_offset.0.0)
25404 }
25405
25406 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25407 self.input_enabled
25408 }
25409}
25410
25411trait SelectionExt {
25412 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25413 fn spanned_rows(
25414 &self,
25415 include_end_if_at_line_start: bool,
25416 map: &DisplaySnapshot,
25417 ) -> Range<MultiBufferRow>;
25418}
25419
25420impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25421 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25422 let start = self
25423 .start
25424 .to_point(map.buffer_snapshot())
25425 .to_display_point(map);
25426 let end = self
25427 .end
25428 .to_point(map.buffer_snapshot())
25429 .to_display_point(map);
25430 if self.reversed {
25431 end..start
25432 } else {
25433 start..end
25434 }
25435 }
25436
25437 fn spanned_rows(
25438 &self,
25439 include_end_if_at_line_start: bool,
25440 map: &DisplaySnapshot,
25441 ) -> Range<MultiBufferRow> {
25442 let start = self.start.to_point(map.buffer_snapshot());
25443 let mut end = self.end.to_point(map.buffer_snapshot());
25444 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25445 end.row -= 1;
25446 }
25447
25448 let buffer_start = map.prev_line_boundary(start).0;
25449 let buffer_end = map.next_line_boundary(end).0;
25450 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25451 }
25452}
25453
25454impl<T: InvalidationRegion> InvalidationStack<T> {
25455 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25456 where
25457 S: Clone + ToOffset,
25458 {
25459 while let Some(region) = self.last() {
25460 let all_selections_inside_invalidation_ranges =
25461 if selections.len() == region.ranges().len() {
25462 selections
25463 .iter()
25464 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25465 .all(|(selection, invalidation_range)| {
25466 let head = selection.head().to_offset(buffer);
25467 invalidation_range.start <= head && invalidation_range.end >= head
25468 })
25469 } else {
25470 false
25471 };
25472
25473 if all_selections_inside_invalidation_ranges {
25474 break;
25475 } else {
25476 self.pop();
25477 }
25478 }
25479 }
25480}
25481
25482impl<T> Default for InvalidationStack<T> {
25483 fn default() -> Self {
25484 Self(Default::default())
25485 }
25486}
25487
25488impl<T> Deref for InvalidationStack<T> {
25489 type Target = Vec<T>;
25490
25491 fn deref(&self) -> &Self::Target {
25492 &self.0
25493 }
25494}
25495
25496impl<T> DerefMut for InvalidationStack<T> {
25497 fn deref_mut(&mut self) -> &mut Self::Target {
25498 &mut self.0
25499 }
25500}
25501
25502impl InvalidationRegion for SnippetState {
25503 fn ranges(&self) -> &[Range<Anchor>] {
25504 &self.ranges[self.active_index]
25505 }
25506}
25507
25508fn edit_prediction_edit_text(
25509 current_snapshot: &BufferSnapshot,
25510 edits: &[(Range<Anchor>, impl AsRef<str>)],
25511 edit_preview: &EditPreview,
25512 include_deletions: bool,
25513 cx: &App,
25514) -> HighlightedText {
25515 let edits = edits
25516 .iter()
25517 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25518 .collect::<Vec<_>>();
25519
25520 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25521}
25522
25523fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25524 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25525 // Just show the raw edit text with basic styling
25526 let mut text = String::new();
25527 let mut highlights = Vec::new();
25528
25529 let insertion_highlight_style = HighlightStyle {
25530 color: Some(cx.theme().colors().text),
25531 ..Default::default()
25532 };
25533
25534 for (_, edit_text) in edits {
25535 let start_offset = text.len();
25536 text.push_str(edit_text);
25537 let end_offset = text.len();
25538
25539 if start_offset < end_offset {
25540 highlights.push((start_offset..end_offset, insertion_highlight_style));
25541 }
25542 }
25543
25544 HighlightedText {
25545 text: text.into(),
25546 highlights,
25547 }
25548}
25549
25550pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25551 match severity {
25552 lsp::DiagnosticSeverity::ERROR => colors.error,
25553 lsp::DiagnosticSeverity::WARNING => colors.warning,
25554 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25555 lsp::DiagnosticSeverity::HINT => colors.info,
25556 _ => colors.ignored,
25557 }
25558}
25559
25560pub fn styled_runs_for_code_label<'a>(
25561 label: &'a CodeLabel,
25562 syntax_theme: &'a theme::SyntaxTheme,
25563 local_player: &'a theme::PlayerColor,
25564) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25565 let fade_out = HighlightStyle {
25566 fade_out: Some(0.35),
25567 ..Default::default()
25568 };
25569
25570 let mut prev_end = label.filter_range.end;
25571 label
25572 .runs
25573 .iter()
25574 .enumerate()
25575 .flat_map(move |(ix, (range, highlight_id))| {
25576 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25577 HighlightStyle {
25578 color: Some(local_player.cursor),
25579 ..Default::default()
25580 }
25581 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25582 HighlightStyle {
25583 background_color: Some(local_player.selection),
25584 ..Default::default()
25585 }
25586 } else if let Some(style) = highlight_id.style(syntax_theme) {
25587 style
25588 } else {
25589 return Default::default();
25590 };
25591 let muted_style = style.highlight(fade_out);
25592
25593 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25594 if range.start >= label.filter_range.end {
25595 if range.start > prev_end {
25596 runs.push((prev_end..range.start, fade_out));
25597 }
25598 runs.push((range.clone(), muted_style));
25599 } else if range.end <= label.filter_range.end {
25600 runs.push((range.clone(), style));
25601 } else {
25602 runs.push((range.start..label.filter_range.end, style));
25603 runs.push((label.filter_range.end..range.end, muted_style));
25604 }
25605 prev_end = cmp::max(prev_end, range.end);
25606
25607 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25608 runs.push((prev_end..label.text.len(), fade_out));
25609 }
25610
25611 runs
25612 })
25613}
25614
25615pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25616 let mut prev_index = 0;
25617 let mut prev_codepoint: Option<char> = None;
25618 text.char_indices()
25619 .chain([(text.len(), '\0')])
25620 .filter_map(move |(index, codepoint)| {
25621 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25622 let is_boundary = index == text.len()
25623 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25624 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25625 if is_boundary {
25626 let chunk = &text[prev_index..index];
25627 prev_index = index;
25628 Some(chunk)
25629 } else {
25630 None
25631 }
25632 })
25633}
25634
25635/// Given a string of text immediately before the cursor, iterates over possible
25636/// strings a snippet could match to. More precisely: returns an iterator over
25637/// suffixes of `text` created by splitting at word boundaries (before & after
25638/// every non-word character).
25639///
25640/// Shorter suffixes are returned first.
25641pub(crate) fn snippet_candidate_suffixes(
25642 text: &str,
25643 is_word_char: impl Fn(char) -> bool,
25644) -> impl std::iter::Iterator<Item = &str> {
25645 let mut prev_index = text.len();
25646 let mut prev_codepoint = None;
25647 text.char_indices()
25648 .rev()
25649 .chain([(0, '\0')])
25650 .filter_map(move |(index, codepoint)| {
25651 let prev_index = std::mem::replace(&mut prev_index, index);
25652 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25653 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25654 None
25655 } else {
25656 let chunk = &text[prev_index..]; // go to end of string
25657 Some(chunk)
25658 }
25659 })
25660}
25661
25662pub trait RangeToAnchorExt: Sized {
25663 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25664
25665 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25666 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25667 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25668 }
25669}
25670
25671impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25672 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25673 let start_offset = self.start.to_offset(snapshot);
25674 let end_offset = self.end.to_offset(snapshot);
25675 if start_offset == end_offset {
25676 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25677 } else {
25678 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25679 }
25680 }
25681}
25682
25683pub trait RowExt {
25684 fn as_f64(&self) -> f64;
25685
25686 fn next_row(&self) -> Self;
25687
25688 fn previous_row(&self) -> Self;
25689
25690 fn minus(&self, other: Self) -> u32;
25691}
25692
25693impl RowExt for DisplayRow {
25694 fn as_f64(&self) -> f64 {
25695 self.0 as _
25696 }
25697
25698 fn next_row(&self) -> Self {
25699 Self(self.0 + 1)
25700 }
25701
25702 fn previous_row(&self) -> Self {
25703 Self(self.0.saturating_sub(1))
25704 }
25705
25706 fn minus(&self, other: Self) -> u32 {
25707 self.0 - other.0
25708 }
25709}
25710
25711impl RowExt for MultiBufferRow {
25712 fn as_f64(&self) -> f64 {
25713 self.0 as _
25714 }
25715
25716 fn next_row(&self) -> Self {
25717 Self(self.0 + 1)
25718 }
25719
25720 fn previous_row(&self) -> Self {
25721 Self(self.0.saturating_sub(1))
25722 }
25723
25724 fn minus(&self, other: Self) -> u32 {
25725 self.0 - other.0
25726 }
25727}
25728
25729trait RowRangeExt {
25730 type Row;
25731
25732 fn len(&self) -> usize;
25733
25734 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25735}
25736
25737impl RowRangeExt for Range<MultiBufferRow> {
25738 type Row = MultiBufferRow;
25739
25740 fn len(&self) -> usize {
25741 (self.end.0 - self.start.0) as usize
25742 }
25743
25744 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25745 (self.start.0..self.end.0).map(MultiBufferRow)
25746 }
25747}
25748
25749impl RowRangeExt for Range<DisplayRow> {
25750 type Row = DisplayRow;
25751
25752 fn len(&self) -> usize {
25753 (self.end.0 - self.start.0) as usize
25754 }
25755
25756 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25757 (self.start.0..self.end.0).map(DisplayRow)
25758 }
25759}
25760
25761/// If select range has more than one line, we
25762/// just point the cursor to range.start.
25763fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25764 if range.start.row == range.end.row {
25765 range
25766 } else {
25767 range.start..range.start
25768 }
25769}
25770pub struct KillRing(ClipboardItem);
25771impl Global for KillRing {}
25772
25773const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25774
25775enum BreakpointPromptEditAction {
25776 Log,
25777 Condition,
25778 HitCondition,
25779}
25780
25781struct BreakpointPromptEditor {
25782 pub(crate) prompt: Entity<Editor>,
25783 editor: WeakEntity<Editor>,
25784 breakpoint_anchor: Anchor,
25785 breakpoint: Breakpoint,
25786 edit_action: BreakpointPromptEditAction,
25787 block_ids: HashSet<CustomBlockId>,
25788 editor_margins: Arc<Mutex<EditorMargins>>,
25789 _subscriptions: Vec<Subscription>,
25790}
25791
25792impl BreakpointPromptEditor {
25793 const MAX_LINES: u8 = 4;
25794
25795 fn new(
25796 editor: WeakEntity<Editor>,
25797 breakpoint_anchor: Anchor,
25798 breakpoint: Breakpoint,
25799 edit_action: BreakpointPromptEditAction,
25800 window: &mut Window,
25801 cx: &mut Context<Self>,
25802 ) -> Self {
25803 let base_text = match edit_action {
25804 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25805 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25806 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25807 }
25808 .map(|msg| msg.to_string())
25809 .unwrap_or_default();
25810
25811 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25812 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25813
25814 let prompt = cx.new(|cx| {
25815 let mut prompt = Editor::new(
25816 EditorMode::AutoHeight {
25817 min_lines: 1,
25818 max_lines: Some(Self::MAX_LINES as usize),
25819 },
25820 buffer,
25821 None,
25822 window,
25823 cx,
25824 );
25825 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25826 prompt.set_show_cursor_when_unfocused(false, cx);
25827 prompt.set_placeholder_text(
25828 match edit_action {
25829 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25830 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25831 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25832 },
25833 window,
25834 cx,
25835 );
25836
25837 prompt
25838 });
25839
25840 Self {
25841 prompt,
25842 editor,
25843 breakpoint_anchor,
25844 breakpoint,
25845 edit_action,
25846 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25847 block_ids: Default::default(),
25848 _subscriptions: vec![],
25849 }
25850 }
25851
25852 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25853 self.block_ids.extend(block_ids)
25854 }
25855
25856 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25857 if let Some(editor) = self.editor.upgrade() {
25858 let message = self
25859 .prompt
25860 .read(cx)
25861 .buffer
25862 .read(cx)
25863 .as_singleton()
25864 .expect("A multi buffer in breakpoint prompt isn't possible")
25865 .read(cx)
25866 .as_rope()
25867 .to_string();
25868
25869 editor.update(cx, |editor, cx| {
25870 editor.edit_breakpoint_at_anchor(
25871 self.breakpoint_anchor,
25872 self.breakpoint.clone(),
25873 match self.edit_action {
25874 BreakpointPromptEditAction::Log => {
25875 BreakpointEditAction::EditLogMessage(message.into())
25876 }
25877 BreakpointPromptEditAction::Condition => {
25878 BreakpointEditAction::EditCondition(message.into())
25879 }
25880 BreakpointPromptEditAction::HitCondition => {
25881 BreakpointEditAction::EditHitCondition(message.into())
25882 }
25883 },
25884 cx,
25885 );
25886
25887 editor.remove_blocks(self.block_ids.clone(), None, cx);
25888 cx.focus_self(window);
25889 });
25890 }
25891 }
25892
25893 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25894 self.editor
25895 .update(cx, |editor, cx| {
25896 editor.remove_blocks(self.block_ids.clone(), None, cx);
25897 window.focus(&editor.focus_handle);
25898 })
25899 .log_err();
25900 }
25901
25902 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25903 let settings = ThemeSettings::get_global(cx);
25904 let text_style = TextStyle {
25905 color: if self.prompt.read(cx).read_only(cx) {
25906 cx.theme().colors().text_disabled
25907 } else {
25908 cx.theme().colors().text
25909 },
25910 font_family: settings.buffer_font.family.clone(),
25911 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25912 font_size: settings.buffer_font_size(cx).into(),
25913 font_weight: settings.buffer_font.weight,
25914 line_height: relative(settings.buffer_line_height.value()),
25915 ..Default::default()
25916 };
25917 EditorElement::new(
25918 &self.prompt,
25919 EditorStyle {
25920 background: cx.theme().colors().editor_background,
25921 local_player: cx.theme().players().local(),
25922 text: text_style,
25923 ..Default::default()
25924 },
25925 )
25926 }
25927}
25928
25929impl Render for BreakpointPromptEditor {
25930 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25931 let editor_margins = *self.editor_margins.lock();
25932 let gutter_dimensions = editor_margins.gutter;
25933 h_flex()
25934 .key_context("Editor")
25935 .bg(cx.theme().colors().editor_background)
25936 .border_y_1()
25937 .border_color(cx.theme().status().info_border)
25938 .size_full()
25939 .py(window.line_height() / 2.5)
25940 .on_action(cx.listener(Self::confirm))
25941 .on_action(cx.listener(Self::cancel))
25942 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25943 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25944 }
25945}
25946
25947impl Focusable for BreakpointPromptEditor {
25948 fn focus_handle(&self, cx: &App) -> FocusHandle {
25949 self.prompt.focus_handle(cx)
25950 }
25951}
25952
25953fn all_edits_insertions_or_deletions(
25954 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25955 snapshot: &MultiBufferSnapshot,
25956) -> bool {
25957 let mut all_insertions = true;
25958 let mut all_deletions = true;
25959
25960 for (range, new_text) in edits.iter() {
25961 let range_is_empty = range.to_offset(snapshot).is_empty();
25962 let text_is_empty = new_text.is_empty();
25963
25964 if range_is_empty != text_is_empty {
25965 if range_is_empty {
25966 all_deletions = false;
25967 } else {
25968 all_insertions = false;
25969 }
25970 } else {
25971 return false;
25972 }
25973
25974 if !all_insertions && !all_deletions {
25975 return false;
25976 }
25977 }
25978 all_insertions || all_deletions
25979}
25980
25981struct MissingEditPredictionKeybindingTooltip;
25982
25983impl Render for MissingEditPredictionKeybindingTooltip {
25984 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25985 ui::tooltip_container(cx, |container, cx| {
25986 container
25987 .flex_shrink_0()
25988 .max_w_80()
25989 .min_h(rems_from_px(124.))
25990 .justify_between()
25991 .child(
25992 v_flex()
25993 .flex_1()
25994 .text_ui_sm(cx)
25995 .child(Label::new("Conflict with Accept Keybinding"))
25996 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25997 )
25998 .child(
25999 h_flex()
26000 .pb_1()
26001 .gap_1()
26002 .items_end()
26003 .w_full()
26004 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
26005 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
26006 }))
26007 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
26008 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
26009 })),
26010 )
26011 })
26012 }
26013}
26014
26015#[derive(Debug, Clone, Copy, PartialEq)]
26016pub struct LineHighlight {
26017 pub background: Background,
26018 pub border: Option<gpui::Hsla>,
26019 pub include_gutter: bool,
26020 pub type_id: Option<TypeId>,
26021}
26022
26023struct LineManipulationResult {
26024 pub new_text: String,
26025 pub line_count_before: usize,
26026 pub line_count_after: usize,
26027}
26028
26029fn render_diff_hunk_controls(
26030 row: u32,
26031 status: &DiffHunkStatus,
26032 hunk_range: Range<Anchor>,
26033 is_created_file: bool,
26034 line_height: Pixels,
26035 editor: &Entity<Editor>,
26036 _window: &mut Window,
26037 cx: &mut App,
26038) -> AnyElement {
26039 h_flex()
26040 .h(line_height)
26041 .mr_1()
26042 .gap_1()
26043 .px_0p5()
26044 .pb_1()
26045 .border_x_1()
26046 .border_b_1()
26047 .border_color(cx.theme().colors().border_variant)
26048 .rounded_b_lg()
26049 .bg(cx.theme().colors().editor_background)
26050 .gap_1()
26051 .block_mouse_except_scroll()
26052 .shadow_md()
26053 .child(if status.has_secondary_hunk() {
26054 Button::new(("stage", row as u64), "Stage")
26055 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26056 .tooltip({
26057 let focus_handle = editor.focus_handle(cx);
26058 move |_window, cx| {
26059 Tooltip::for_action_in(
26060 "Stage Hunk",
26061 &::git::ToggleStaged,
26062 &focus_handle,
26063 cx,
26064 )
26065 }
26066 })
26067 .on_click({
26068 let editor = editor.clone();
26069 move |_event, _window, cx| {
26070 editor.update(cx, |editor, cx| {
26071 editor.stage_or_unstage_diff_hunks(
26072 true,
26073 vec![hunk_range.start..hunk_range.start],
26074 cx,
26075 );
26076 });
26077 }
26078 })
26079 } else {
26080 Button::new(("unstage", row as u64), "Unstage")
26081 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26082 .tooltip({
26083 let focus_handle = editor.focus_handle(cx);
26084 move |_window, cx| {
26085 Tooltip::for_action_in(
26086 "Unstage Hunk",
26087 &::git::ToggleStaged,
26088 &focus_handle,
26089 cx,
26090 )
26091 }
26092 })
26093 .on_click({
26094 let editor = editor.clone();
26095 move |_event, _window, cx| {
26096 editor.update(cx, |editor, cx| {
26097 editor.stage_or_unstage_diff_hunks(
26098 false,
26099 vec![hunk_range.start..hunk_range.start],
26100 cx,
26101 );
26102 });
26103 }
26104 })
26105 })
26106 .child(
26107 Button::new(("restore", row as u64), "Restore")
26108 .tooltip({
26109 let focus_handle = editor.focus_handle(cx);
26110 move |_window, cx| {
26111 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26112 }
26113 })
26114 .on_click({
26115 let editor = editor.clone();
26116 move |_event, window, cx| {
26117 editor.update(cx, |editor, cx| {
26118 let snapshot = editor.snapshot(window, cx);
26119 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26120 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26121 });
26122 }
26123 })
26124 .disabled(is_created_file),
26125 )
26126 .when(
26127 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26128 |el| {
26129 el.child(
26130 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26131 .shape(IconButtonShape::Square)
26132 .icon_size(IconSize::Small)
26133 // .disabled(!has_multiple_hunks)
26134 .tooltip({
26135 let focus_handle = editor.focus_handle(cx);
26136 move |_window, cx| {
26137 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26138 }
26139 })
26140 .on_click({
26141 let editor = editor.clone();
26142 move |_event, window, cx| {
26143 editor.update(cx, |editor, cx| {
26144 let snapshot = editor.snapshot(window, cx);
26145 let position =
26146 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26147 editor.go_to_hunk_before_or_after_position(
26148 &snapshot,
26149 position,
26150 Direction::Next,
26151 window,
26152 cx,
26153 );
26154 editor.expand_selected_diff_hunks(cx);
26155 });
26156 }
26157 }),
26158 )
26159 .child(
26160 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26161 .shape(IconButtonShape::Square)
26162 .icon_size(IconSize::Small)
26163 // .disabled(!has_multiple_hunks)
26164 .tooltip({
26165 let focus_handle = editor.focus_handle(cx);
26166 move |_window, cx| {
26167 Tooltip::for_action_in(
26168 "Previous Hunk",
26169 &GoToPreviousHunk,
26170 &focus_handle,
26171 cx,
26172 )
26173 }
26174 })
26175 .on_click({
26176 let editor = editor.clone();
26177 move |_event, window, cx| {
26178 editor.update(cx, |editor, cx| {
26179 let snapshot = editor.snapshot(window, cx);
26180 let point =
26181 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26182 editor.go_to_hunk_before_or_after_position(
26183 &snapshot,
26184 point,
26185 Direction::Prev,
26186 window,
26187 cx,
26188 );
26189 editor.expand_selected_diff_hunks(cx);
26190 });
26191 }
26192 }),
26193 )
26194 },
26195 )
26196 .into_any_element()
26197}
26198
26199pub fn multibuffer_context_lines(cx: &App) -> u32 {
26200 EditorSettings::try_get(cx)
26201 .map(|settings| settings.excerpt_context_lines)
26202 .unwrap_or(2)
26203 .min(32)
26204}