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 current_line_count = snapshot
4398 .reversed_chars_at(selection.start)
4399 .take_while(|&c| c != '\n')
4400 .filter(|&c| c == target)
4401 .count();
4402 current_line_count % 2 == 1
4403 } else {
4404 false
4405 };
4406
4407 if autoclose
4408 && bracket_pair.close
4409 && following_text_allows_autoclose
4410 && preceding_text_allows_autoclose
4411 && !is_closing_quote
4412 {
4413 let anchor = snapshot.anchor_before(selection.end);
4414 new_selections.push((selection.map(|_| anchor), text.len()));
4415 new_autoclose_regions.push((
4416 anchor,
4417 text.len(),
4418 selection.id,
4419 bracket_pair.clone(),
4420 ));
4421 edits.push((
4422 selection.range(),
4423 format!("{}{}", text, bracket_pair.end).into(),
4424 ));
4425 bracket_inserted = true;
4426 continue;
4427 }
4428 }
4429
4430 if let Some(region) = autoclose_region {
4431 // If the selection is followed by an auto-inserted closing bracket,
4432 // then don't insert that closing bracket again; just move the selection
4433 // past the closing bracket.
4434 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4435 && text.as_ref() == region.pair.end.as_str()
4436 && snapshot.contains_str_at(region.range.end, text.as_ref());
4437 if should_skip {
4438 let anchor = snapshot.anchor_after(selection.end);
4439 new_selections
4440 .push((selection.map(|_| anchor), region.pair.end.len()));
4441 continue;
4442 }
4443 }
4444
4445 let always_treat_brackets_as_autoclosed = snapshot
4446 .language_settings_at(selection.start, cx)
4447 .always_treat_brackets_as_autoclosed;
4448 if always_treat_brackets_as_autoclosed
4449 && is_bracket_pair_end
4450 && snapshot.contains_str_at(selection.end, text.as_ref())
4451 {
4452 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4453 // and the inserted text is a closing bracket and the selection is followed
4454 // by the closing bracket then move the selection past the closing bracket.
4455 let anchor = snapshot.anchor_after(selection.end);
4456 new_selections.push((selection.map(|_| anchor), text.len()));
4457 continue;
4458 }
4459 }
4460 // If an opening bracket is 1 character long and is typed while
4461 // text is selected, then surround that text with the bracket pair.
4462 else if auto_surround
4463 && bracket_pair.surround
4464 && is_bracket_pair_start
4465 && bracket_pair.start.chars().count() == 1
4466 {
4467 edits.push((selection.start..selection.start, text.clone()));
4468 edits.push((
4469 selection.end..selection.end,
4470 bracket_pair.end.as_str().into(),
4471 ));
4472 bracket_inserted = true;
4473 new_selections.push((
4474 Selection {
4475 id: selection.id,
4476 start: snapshot.anchor_after(selection.start),
4477 end: snapshot.anchor_before(selection.end),
4478 reversed: selection.reversed,
4479 goal: selection.goal,
4480 },
4481 0,
4482 ));
4483 continue;
4484 }
4485 }
4486 }
4487
4488 if self.auto_replace_emoji_shortcode
4489 && selection.is_empty()
4490 && text.as_ref().ends_with(':')
4491 && let Some(possible_emoji_short_code) =
4492 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4493 && !possible_emoji_short_code.is_empty()
4494 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4495 {
4496 let emoji_shortcode_start = Point::new(
4497 selection.start.row,
4498 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4499 );
4500
4501 // Remove shortcode from buffer
4502 edits.push((
4503 emoji_shortcode_start..selection.start,
4504 "".to_string().into(),
4505 ));
4506 new_selections.push((
4507 Selection {
4508 id: selection.id,
4509 start: snapshot.anchor_after(emoji_shortcode_start),
4510 end: snapshot.anchor_before(selection.start),
4511 reversed: selection.reversed,
4512 goal: selection.goal,
4513 },
4514 0,
4515 ));
4516
4517 // Insert emoji
4518 let selection_start_anchor = snapshot.anchor_after(selection.start);
4519 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4520 edits.push((selection.start..selection.end, emoji.to_string().into()));
4521
4522 continue;
4523 }
4524
4525 // If not handling any auto-close operation, then just replace the selected
4526 // text with the given input and move the selection to the end of the
4527 // newly inserted text.
4528 let anchor = snapshot.anchor_after(selection.end);
4529 if !self.linked_edit_ranges.is_empty() {
4530 let start_anchor = snapshot.anchor_before(selection.start);
4531
4532 let is_word_char = text.chars().next().is_none_or(|char| {
4533 let classifier = snapshot
4534 .char_classifier_at(start_anchor.to_offset(&snapshot))
4535 .scope_context(Some(CharScopeContext::LinkedEdit));
4536 classifier.is_word(char)
4537 });
4538
4539 if is_word_char {
4540 if let Some(ranges) = self
4541 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4542 {
4543 for (buffer, edits) in ranges {
4544 linked_edits
4545 .entry(buffer.clone())
4546 .or_default()
4547 .extend(edits.into_iter().map(|range| (range, text.clone())));
4548 }
4549 }
4550 } else {
4551 clear_linked_edit_ranges = true;
4552 }
4553 }
4554
4555 new_selections.push((selection.map(|_| anchor), 0));
4556 edits.push((selection.start..selection.end, text.clone()));
4557 }
4558
4559 drop(snapshot);
4560
4561 self.transact(window, cx, |this, window, cx| {
4562 if clear_linked_edit_ranges {
4563 this.linked_edit_ranges.clear();
4564 }
4565 let initial_buffer_versions =
4566 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4567
4568 this.buffer.update(cx, |buffer, cx| {
4569 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4570 });
4571 for (buffer, edits) in linked_edits {
4572 buffer.update(cx, |buffer, cx| {
4573 let snapshot = buffer.snapshot();
4574 let edits = edits
4575 .into_iter()
4576 .map(|(range, text)| {
4577 use text::ToPoint as TP;
4578 let end_point = TP::to_point(&range.end, &snapshot);
4579 let start_point = TP::to_point(&range.start, &snapshot);
4580 (start_point..end_point, text)
4581 })
4582 .sorted_by_key(|(range, _)| range.start);
4583 buffer.edit(edits, None, cx);
4584 })
4585 }
4586 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4587 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4588 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4589 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4590 new_anchor_selections,
4591 &map,
4592 )
4593 .zip(new_selection_deltas)
4594 .map(|(selection, delta)| Selection {
4595 id: selection.id,
4596 start: selection.start + delta,
4597 end: selection.end + delta,
4598 reversed: selection.reversed,
4599 goal: SelectionGoal::None,
4600 })
4601 .collect::<Vec<_>>();
4602
4603 let mut i = 0;
4604 for (position, delta, selection_id, pair) in new_autoclose_regions {
4605 let position = position.to_offset(map.buffer_snapshot()) + delta;
4606 let start = map.buffer_snapshot().anchor_before(position);
4607 let end = map.buffer_snapshot().anchor_after(position);
4608 while let Some(existing_state) = this.autoclose_regions.get(i) {
4609 match existing_state
4610 .range
4611 .start
4612 .cmp(&start, map.buffer_snapshot())
4613 {
4614 Ordering::Less => i += 1,
4615 Ordering::Greater => break,
4616 Ordering::Equal => {
4617 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4618 Ordering::Less => i += 1,
4619 Ordering::Equal => break,
4620 Ordering::Greater => break,
4621 }
4622 }
4623 }
4624 }
4625 this.autoclose_regions.insert(
4626 i,
4627 AutocloseRegion {
4628 selection_id,
4629 range: start..end,
4630 pair,
4631 },
4632 );
4633 }
4634
4635 let had_active_edit_prediction = this.has_active_edit_prediction();
4636 this.change_selections(
4637 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4638 window,
4639 cx,
4640 |s| s.select(new_selections),
4641 );
4642
4643 if !bracket_inserted
4644 && let Some(on_type_format_task) =
4645 this.trigger_on_type_formatting(text.to_string(), window, cx)
4646 {
4647 on_type_format_task.detach_and_log_err(cx);
4648 }
4649
4650 let editor_settings = EditorSettings::get_global(cx);
4651 if bracket_inserted
4652 && (editor_settings.auto_signature_help
4653 || editor_settings.show_signature_help_after_edits)
4654 {
4655 this.show_signature_help(&ShowSignatureHelp, window, cx);
4656 }
4657
4658 let trigger_in_words =
4659 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4660 if this.hard_wrap.is_some() {
4661 let latest: Range<Point> = this.selections.newest(&map).range();
4662 if latest.is_empty()
4663 && this
4664 .buffer()
4665 .read(cx)
4666 .snapshot(cx)
4667 .line_len(MultiBufferRow(latest.start.row))
4668 == latest.start.column
4669 {
4670 this.rewrap_impl(
4671 RewrapOptions {
4672 override_language_settings: true,
4673 preserve_existing_whitespace: true,
4674 },
4675 cx,
4676 )
4677 }
4678 }
4679 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4680 refresh_linked_ranges(this, window, cx);
4681 this.refresh_edit_prediction(true, false, window, cx);
4682 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4683 });
4684 }
4685
4686 fn find_possible_emoji_shortcode_at_position(
4687 snapshot: &MultiBufferSnapshot,
4688 position: Point,
4689 ) -> Option<String> {
4690 let mut chars = Vec::new();
4691 let mut found_colon = false;
4692 for char in snapshot.reversed_chars_at(position).take(100) {
4693 // Found a possible emoji shortcode in the middle of the buffer
4694 if found_colon {
4695 if char.is_whitespace() {
4696 chars.reverse();
4697 return Some(chars.iter().collect());
4698 }
4699 // If the previous character is not a whitespace, we are in the middle of a word
4700 // and we only want to complete the shortcode if the word is made up of other emojis
4701 let mut containing_word = String::new();
4702 for ch in snapshot
4703 .reversed_chars_at(position)
4704 .skip(chars.len() + 1)
4705 .take(100)
4706 {
4707 if ch.is_whitespace() {
4708 break;
4709 }
4710 containing_word.push(ch);
4711 }
4712 let containing_word = containing_word.chars().rev().collect::<String>();
4713 if util::word_consists_of_emojis(containing_word.as_str()) {
4714 chars.reverse();
4715 return Some(chars.iter().collect());
4716 }
4717 }
4718
4719 if char.is_whitespace() || !char.is_ascii() {
4720 return None;
4721 }
4722 if char == ':' {
4723 found_colon = true;
4724 } else {
4725 chars.push(char);
4726 }
4727 }
4728 // Found a possible emoji shortcode at the beginning of the buffer
4729 chars.reverse();
4730 Some(chars.iter().collect())
4731 }
4732
4733 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4734 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4735 self.transact(window, cx, |this, window, cx| {
4736 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4737 let selections = this
4738 .selections
4739 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4740 let multi_buffer = this.buffer.read(cx);
4741 let buffer = multi_buffer.snapshot(cx);
4742 selections
4743 .iter()
4744 .map(|selection| {
4745 let start_point = selection.start.to_point(&buffer);
4746 let mut existing_indent =
4747 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4748 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4749 let start = selection.start;
4750 let end = selection.end;
4751 let selection_is_empty = start == end;
4752 let language_scope = buffer.language_scope_at(start);
4753 let (
4754 comment_delimiter,
4755 doc_delimiter,
4756 insert_extra_newline,
4757 indent_on_newline,
4758 indent_on_extra_newline,
4759 ) = if let Some(language) = &language_scope {
4760 let mut insert_extra_newline =
4761 insert_extra_newline_brackets(&buffer, start..end, language)
4762 || insert_extra_newline_tree_sitter(&buffer, start..end);
4763
4764 // Comment extension on newline is allowed only for cursor selections
4765 let comment_delimiter = maybe!({
4766 if !selection_is_empty {
4767 return None;
4768 }
4769
4770 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4771 return None;
4772 }
4773
4774 let delimiters = language.line_comment_prefixes();
4775 let max_len_of_delimiter =
4776 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4777 let (snapshot, range) =
4778 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4779
4780 let num_of_whitespaces = snapshot
4781 .chars_for_range(range.clone())
4782 .take_while(|c| c.is_whitespace())
4783 .count();
4784 let comment_candidate = snapshot
4785 .chars_for_range(range.clone())
4786 .skip(num_of_whitespaces)
4787 .take(max_len_of_delimiter)
4788 .collect::<String>();
4789 let (delimiter, trimmed_len) = delimiters
4790 .iter()
4791 .filter_map(|delimiter| {
4792 let prefix = delimiter.trim_end();
4793 if comment_candidate.starts_with(prefix) {
4794 Some((delimiter, prefix.len()))
4795 } else {
4796 None
4797 }
4798 })
4799 .max_by_key(|(_, len)| *len)?;
4800
4801 if let Some(BlockCommentConfig {
4802 start: block_start, ..
4803 }) = language.block_comment()
4804 {
4805 let block_start_trimmed = block_start.trim_end();
4806 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4807 let line_content = snapshot
4808 .chars_for_range(range)
4809 .skip(num_of_whitespaces)
4810 .take(block_start_trimmed.len())
4811 .collect::<String>();
4812
4813 if line_content.starts_with(block_start_trimmed) {
4814 return None;
4815 }
4816 }
4817 }
4818
4819 let cursor_is_placed_after_comment_marker =
4820 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4821 if cursor_is_placed_after_comment_marker {
4822 Some(delimiter.clone())
4823 } else {
4824 None
4825 }
4826 });
4827
4828 let mut indent_on_newline = IndentSize::spaces(0);
4829 let mut indent_on_extra_newline = IndentSize::spaces(0);
4830
4831 let doc_delimiter = maybe!({
4832 if !selection_is_empty {
4833 return None;
4834 }
4835
4836 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4837 return None;
4838 }
4839
4840 let BlockCommentConfig {
4841 start: start_tag,
4842 end: end_tag,
4843 prefix: delimiter,
4844 tab_size: len,
4845 } = language.documentation_comment()?;
4846 let is_within_block_comment = buffer
4847 .language_scope_at(start_point)
4848 .is_some_and(|scope| scope.override_name() == Some("comment"));
4849 if !is_within_block_comment {
4850 return None;
4851 }
4852
4853 let (snapshot, range) =
4854 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4855
4856 let num_of_whitespaces = snapshot
4857 .chars_for_range(range.clone())
4858 .take_while(|c| c.is_whitespace())
4859 .count();
4860
4861 // 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.
4862 let column = start_point.column;
4863 let cursor_is_after_start_tag = {
4864 let start_tag_len = start_tag.len();
4865 let start_tag_line = snapshot
4866 .chars_for_range(range.clone())
4867 .skip(num_of_whitespaces)
4868 .take(start_tag_len)
4869 .collect::<String>();
4870 if start_tag_line.starts_with(start_tag.as_ref()) {
4871 num_of_whitespaces + start_tag_len <= column as usize
4872 } else {
4873 false
4874 }
4875 };
4876
4877 let cursor_is_after_delimiter = {
4878 let delimiter_trim = delimiter.trim_end();
4879 let delimiter_line = snapshot
4880 .chars_for_range(range.clone())
4881 .skip(num_of_whitespaces)
4882 .take(delimiter_trim.len())
4883 .collect::<String>();
4884 if delimiter_line.starts_with(delimiter_trim) {
4885 num_of_whitespaces + delimiter_trim.len() <= column as usize
4886 } else {
4887 false
4888 }
4889 };
4890
4891 let cursor_is_before_end_tag_if_exists = {
4892 let mut char_position = 0u32;
4893 let mut end_tag_offset = None;
4894
4895 'outer: for chunk in snapshot.text_for_range(range) {
4896 if let Some(byte_pos) = chunk.find(&**end_tag) {
4897 let chars_before_match =
4898 chunk[..byte_pos].chars().count() as u32;
4899 end_tag_offset =
4900 Some(char_position + chars_before_match);
4901 break 'outer;
4902 }
4903 char_position += chunk.chars().count() as u32;
4904 }
4905
4906 if let Some(end_tag_offset) = end_tag_offset {
4907 let cursor_is_before_end_tag = column <= end_tag_offset;
4908 if cursor_is_after_start_tag {
4909 if cursor_is_before_end_tag {
4910 insert_extra_newline = true;
4911 }
4912 let cursor_is_at_start_of_end_tag =
4913 column == end_tag_offset;
4914 if cursor_is_at_start_of_end_tag {
4915 indent_on_extra_newline.len = *len;
4916 }
4917 }
4918 cursor_is_before_end_tag
4919 } else {
4920 true
4921 }
4922 };
4923
4924 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4925 && cursor_is_before_end_tag_if_exists
4926 {
4927 if cursor_is_after_start_tag {
4928 indent_on_newline.len = *len;
4929 }
4930 Some(delimiter.clone())
4931 } else {
4932 None
4933 }
4934 });
4935
4936 (
4937 comment_delimiter,
4938 doc_delimiter,
4939 insert_extra_newline,
4940 indent_on_newline,
4941 indent_on_extra_newline,
4942 )
4943 } else {
4944 (
4945 None,
4946 None,
4947 false,
4948 IndentSize::default(),
4949 IndentSize::default(),
4950 )
4951 };
4952
4953 let prevent_auto_indent = doc_delimiter.is_some();
4954 let delimiter = comment_delimiter.or(doc_delimiter);
4955
4956 let capacity_for_delimiter =
4957 delimiter.as_deref().map(str::len).unwrap_or_default();
4958 let mut new_text = String::with_capacity(
4959 1 + capacity_for_delimiter
4960 + existing_indent.len as usize
4961 + indent_on_newline.len as usize
4962 + indent_on_extra_newline.len as usize,
4963 );
4964 new_text.push('\n');
4965 new_text.extend(existing_indent.chars());
4966 new_text.extend(indent_on_newline.chars());
4967
4968 if let Some(delimiter) = &delimiter {
4969 new_text.push_str(delimiter);
4970 }
4971
4972 if insert_extra_newline {
4973 new_text.push('\n');
4974 new_text.extend(existing_indent.chars());
4975 new_text.extend(indent_on_extra_newline.chars());
4976 }
4977
4978 let anchor = buffer.anchor_after(end);
4979 let new_selection = selection.map(|_| anchor);
4980 (
4981 ((start..end, new_text), prevent_auto_indent),
4982 (insert_extra_newline, new_selection),
4983 )
4984 })
4985 .unzip()
4986 };
4987
4988 let mut auto_indent_edits = Vec::new();
4989 let mut edits = Vec::new();
4990 for (edit, prevent_auto_indent) in edits_with_flags {
4991 if prevent_auto_indent {
4992 edits.push(edit);
4993 } else {
4994 auto_indent_edits.push(edit);
4995 }
4996 }
4997 if !edits.is_empty() {
4998 this.edit(edits, cx);
4999 }
5000 if !auto_indent_edits.is_empty() {
5001 this.edit_with_autoindent(auto_indent_edits, cx);
5002 }
5003
5004 let buffer = this.buffer.read(cx).snapshot(cx);
5005 let new_selections = selection_info
5006 .into_iter()
5007 .map(|(extra_newline_inserted, new_selection)| {
5008 let mut cursor = new_selection.end.to_point(&buffer);
5009 if extra_newline_inserted {
5010 cursor.row -= 1;
5011 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5012 }
5013 new_selection.map(|_| cursor)
5014 })
5015 .collect();
5016
5017 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5018 this.refresh_edit_prediction(true, false, window, cx);
5019 if let Some(task) = this.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5020 task.detach_and_log_err(cx);
5021 }
5022 });
5023 }
5024
5025 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5026 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5027
5028 let buffer = self.buffer.read(cx);
5029 let snapshot = buffer.snapshot(cx);
5030
5031 let mut edits = Vec::new();
5032 let mut rows = Vec::new();
5033
5034 for (rows_inserted, selection) in self
5035 .selections
5036 .all_adjusted(&self.display_snapshot(cx))
5037 .into_iter()
5038 .enumerate()
5039 {
5040 let cursor = selection.head();
5041 let row = cursor.row;
5042
5043 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5044
5045 let newline = "\n".to_string();
5046 edits.push((start_of_line..start_of_line, newline));
5047
5048 rows.push(row + rows_inserted as u32);
5049 }
5050
5051 self.transact(window, cx, |editor, window, cx| {
5052 editor.edit(edits, cx);
5053
5054 editor.change_selections(Default::default(), window, cx, |s| {
5055 let mut index = 0;
5056 s.move_cursors_with(|map, _, _| {
5057 let row = rows[index];
5058 index += 1;
5059
5060 let point = Point::new(row, 0);
5061 let boundary = map.next_line_boundary(point).1;
5062 let clipped = map.clip_point(boundary, Bias::Left);
5063
5064 (clipped, SelectionGoal::None)
5065 });
5066 });
5067
5068 let mut indent_edits = Vec::new();
5069 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5070 for row in rows {
5071 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5072 for (row, indent) in indents {
5073 if indent.len == 0 {
5074 continue;
5075 }
5076
5077 let text = match indent.kind {
5078 IndentKind::Space => " ".repeat(indent.len as usize),
5079 IndentKind::Tab => "\t".repeat(indent.len as usize),
5080 };
5081 let point = Point::new(row.0, 0);
5082 indent_edits.push((point..point, text));
5083 }
5084 }
5085 editor.edit(indent_edits, cx);
5086 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5087 format.detach_and_log_err(cx);
5088 }
5089 });
5090 }
5091
5092 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5093 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5094
5095 let buffer = self.buffer.read(cx);
5096 let snapshot = buffer.snapshot(cx);
5097
5098 let mut edits = Vec::new();
5099 let mut rows = Vec::new();
5100 let mut rows_inserted = 0;
5101
5102 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5103 let cursor = selection.head();
5104 let row = cursor.row;
5105
5106 let point = Point::new(row + 1, 0);
5107 let start_of_line = snapshot.clip_point(point, Bias::Left);
5108
5109 let newline = "\n".to_string();
5110 edits.push((start_of_line..start_of_line, newline));
5111
5112 rows_inserted += 1;
5113 rows.push(row + rows_inserted);
5114 }
5115
5116 self.transact(window, cx, |editor, window, cx| {
5117 editor.edit(edits, cx);
5118
5119 editor.change_selections(Default::default(), window, cx, |s| {
5120 let mut index = 0;
5121 s.move_cursors_with(|map, _, _| {
5122 let row = rows[index];
5123 index += 1;
5124
5125 let point = Point::new(row, 0);
5126 let boundary = map.next_line_boundary(point).1;
5127 let clipped = map.clip_point(boundary, Bias::Left);
5128
5129 (clipped, SelectionGoal::None)
5130 });
5131 });
5132
5133 let mut indent_edits = Vec::new();
5134 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5135 for row in rows {
5136 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5137 for (row, indent) in indents {
5138 if indent.len == 0 {
5139 continue;
5140 }
5141
5142 let text = match indent.kind {
5143 IndentKind::Space => " ".repeat(indent.len as usize),
5144 IndentKind::Tab => "\t".repeat(indent.len as usize),
5145 };
5146 let point = Point::new(row.0, 0);
5147 indent_edits.push((point..point, text));
5148 }
5149 }
5150 editor.edit(indent_edits, cx);
5151 if let Some(format) = editor.trigger_on_type_formatting("\n".to_owned(), window, cx) {
5152 format.detach_and_log_err(cx);
5153 }
5154 });
5155 }
5156
5157 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5158 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5159 original_indent_columns: Vec::new(),
5160 });
5161 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5162 }
5163
5164 fn insert_with_autoindent_mode(
5165 &mut self,
5166 text: &str,
5167 autoindent_mode: Option<AutoindentMode>,
5168 window: &mut Window,
5169 cx: &mut Context<Self>,
5170 ) {
5171 if self.read_only(cx) {
5172 return;
5173 }
5174
5175 let text: Arc<str> = text.into();
5176 self.transact(window, cx, |this, window, cx| {
5177 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5178 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5179 let anchors = {
5180 let snapshot = buffer.read(cx);
5181 old_selections
5182 .iter()
5183 .map(|s| {
5184 let anchor = snapshot.anchor_after(s.head());
5185 s.map(|_| anchor)
5186 })
5187 .collect::<Vec<_>>()
5188 };
5189 buffer.edit(
5190 old_selections
5191 .iter()
5192 .map(|s| (s.start..s.end, text.clone())),
5193 autoindent_mode,
5194 cx,
5195 );
5196 anchors
5197 });
5198
5199 this.change_selections(Default::default(), window, cx, |s| {
5200 s.select_anchors(selection_anchors);
5201 });
5202
5203 cx.notify();
5204 });
5205 }
5206
5207 fn trigger_completion_on_input(
5208 &mut self,
5209 text: &str,
5210 trigger_in_words: bool,
5211 window: &mut Window,
5212 cx: &mut Context<Self>,
5213 ) {
5214 let completions_source = self
5215 .context_menu
5216 .borrow()
5217 .as_ref()
5218 .and_then(|menu| match menu {
5219 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5220 CodeContextMenu::CodeActions(_) => None,
5221 });
5222
5223 match completions_source {
5224 Some(CompletionsMenuSource::Words { .. }) => {
5225 self.open_or_update_completions_menu(
5226 Some(CompletionsMenuSource::Words {
5227 ignore_threshold: false,
5228 }),
5229 None,
5230 trigger_in_words,
5231 window,
5232 cx,
5233 );
5234 }
5235 _ => self.open_or_update_completions_menu(
5236 None,
5237 Some(text.to_owned()).filter(|x| !x.is_empty()),
5238 true,
5239 window,
5240 cx,
5241 ),
5242 }
5243 }
5244
5245 /// If any empty selections is touching the start of its innermost containing autoclose
5246 /// region, expand it to select the brackets.
5247 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5248 let selections = self
5249 .selections
5250 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5251 let buffer = self.buffer.read(cx).read(cx);
5252 let new_selections = self
5253 .selections_with_autoclose_regions(selections, &buffer)
5254 .map(|(mut selection, region)| {
5255 if !selection.is_empty() {
5256 return selection;
5257 }
5258
5259 if let Some(region) = region {
5260 let mut range = region.range.to_offset(&buffer);
5261 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5262 range.start -= region.pair.start.len();
5263 if buffer.contains_str_at(range.start, ®ion.pair.start)
5264 && buffer.contains_str_at(range.end, ®ion.pair.end)
5265 {
5266 range.end += region.pair.end.len();
5267 selection.start = range.start;
5268 selection.end = range.end;
5269
5270 return selection;
5271 }
5272 }
5273 }
5274
5275 let always_treat_brackets_as_autoclosed = buffer
5276 .language_settings_at(selection.start, cx)
5277 .always_treat_brackets_as_autoclosed;
5278
5279 if !always_treat_brackets_as_autoclosed {
5280 return selection;
5281 }
5282
5283 if let Some(scope) = buffer.language_scope_at(selection.start) {
5284 for (pair, enabled) in scope.brackets() {
5285 if !enabled || !pair.close {
5286 continue;
5287 }
5288
5289 if buffer.contains_str_at(selection.start, &pair.end) {
5290 let pair_start_len = pair.start.len();
5291 if buffer.contains_str_at(
5292 selection.start.saturating_sub_usize(pair_start_len),
5293 &pair.start,
5294 ) {
5295 selection.start -= pair_start_len;
5296 selection.end += pair.end.len();
5297
5298 return selection;
5299 }
5300 }
5301 }
5302 }
5303
5304 selection
5305 })
5306 .collect();
5307
5308 drop(buffer);
5309 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5310 selections.select(new_selections)
5311 });
5312 }
5313
5314 /// Iterate the given selections, and for each one, find the smallest surrounding
5315 /// autoclose region. This uses the ordering of the selections and the autoclose
5316 /// regions to avoid repeated comparisons.
5317 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5318 &'a self,
5319 selections: impl IntoIterator<Item = Selection<D>>,
5320 buffer: &'a MultiBufferSnapshot,
5321 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5322 let mut i = 0;
5323 let mut regions = self.autoclose_regions.as_slice();
5324 selections.into_iter().map(move |selection| {
5325 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5326
5327 let mut enclosing = None;
5328 while let Some(pair_state) = regions.get(i) {
5329 if pair_state.range.end.to_offset(buffer) < range.start {
5330 regions = ®ions[i + 1..];
5331 i = 0;
5332 } else if pair_state.range.start.to_offset(buffer) > range.end {
5333 break;
5334 } else {
5335 if pair_state.selection_id == selection.id {
5336 enclosing = Some(pair_state);
5337 }
5338 i += 1;
5339 }
5340 }
5341
5342 (selection, enclosing)
5343 })
5344 }
5345
5346 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5347 fn invalidate_autoclose_regions(
5348 &mut self,
5349 mut selections: &[Selection<Anchor>],
5350 buffer: &MultiBufferSnapshot,
5351 ) {
5352 self.autoclose_regions.retain(|state| {
5353 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5354 return false;
5355 }
5356
5357 let mut i = 0;
5358 while let Some(selection) = selections.get(i) {
5359 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5360 selections = &selections[1..];
5361 continue;
5362 }
5363 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5364 break;
5365 }
5366 if selection.id == state.selection_id {
5367 return true;
5368 } else {
5369 i += 1;
5370 }
5371 }
5372 false
5373 });
5374 }
5375
5376 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5377 let offset = position.to_offset(buffer);
5378 let (word_range, kind) =
5379 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5380 if offset > word_range.start && kind == Some(CharKind::Word) {
5381 Some(
5382 buffer
5383 .text_for_range(word_range.start..offset)
5384 .collect::<String>(),
5385 )
5386 } else {
5387 None
5388 }
5389 }
5390
5391 pub fn visible_excerpts(
5392 &self,
5393 lsp_related_only: bool,
5394 cx: &mut Context<Editor>,
5395 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5396 let project = self.project().cloned();
5397 let multi_buffer = self.buffer().read(cx);
5398 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5399 let multi_buffer_visible_start = self
5400 .scroll_manager
5401 .anchor()
5402 .anchor
5403 .to_point(&multi_buffer_snapshot);
5404 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5405 multi_buffer_visible_start
5406 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5407 Bias::Left,
5408 );
5409 multi_buffer_snapshot
5410 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5411 .into_iter()
5412 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5413 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5414 if !lsp_related_only {
5415 return Some((
5416 excerpt_id,
5417 (
5418 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5419 buffer.version().clone(),
5420 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5421 ),
5422 ));
5423 }
5424
5425 let project = project.as_ref()?.read(cx);
5426 let buffer_file = project::File::from_dyn(buffer.file())?;
5427 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5428 let worktree_entry = buffer_worktree
5429 .read(cx)
5430 .entry_for_id(buffer_file.project_entry_id()?)?;
5431 if worktree_entry.is_ignored {
5432 None
5433 } else {
5434 Some((
5435 excerpt_id,
5436 (
5437 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5438 buffer.version().clone(),
5439 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5440 ),
5441 ))
5442 }
5443 })
5444 .collect()
5445 }
5446
5447 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5448 TextLayoutDetails {
5449 text_system: window.text_system().clone(),
5450 editor_style: self.style.clone().unwrap(),
5451 rem_size: window.rem_size(),
5452 scroll_anchor: self.scroll_manager.anchor(),
5453 visible_rows: self.visible_line_count(),
5454 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5455 }
5456 }
5457
5458 fn trigger_on_type_formatting(
5459 &self,
5460 input: String,
5461 window: &mut Window,
5462 cx: &mut Context<Self>,
5463 ) -> Option<Task<Result<()>>> {
5464 if input.chars().count() != 1 {
5465 return None;
5466 }
5467
5468 let project = self.project()?;
5469 let position = self.selections.newest_anchor().head();
5470 let (buffer, buffer_position) = self
5471 .buffer
5472 .read(cx)
5473 .text_anchor_for_position(position, cx)?;
5474
5475 let settings = language_settings::language_settings(
5476 buffer
5477 .read(cx)
5478 .language_at(buffer_position)
5479 .map(|l| l.name()),
5480 buffer.read(cx).file(),
5481 cx,
5482 );
5483 if !settings.use_on_type_format {
5484 return None;
5485 }
5486
5487 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5488 // hence we do LSP request & edit on host side only — add formats to host's history.
5489 let push_to_lsp_host_history = true;
5490 // If this is not the host, append its history with new edits.
5491 let push_to_client_history = project.read(cx).is_via_collab();
5492
5493 let on_type_formatting = project.update(cx, |project, cx| {
5494 project.on_type_format(
5495 buffer.clone(),
5496 buffer_position,
5497 input,
5498 push_to_lsp_host_history,
5499 cx,
5500 )
5501 });
5502 Some(cx.spawn_in(window, async move |editor, cx| {
5503 if let Some(transaction) = on_type_formatting.await? {
5504 if push_to_client_history {
5505 buffer
5506 .update(cx, |buffer, _| {
5507 buffer.push_transaction(transaction, Instant::now());
5508 buffer.finalize_last_transaction();
5509 })
5510 .ok();
5511 }
5512 editor.update(cx, |editor, cx| {
5513 editor.refresh_document_highlights(cx);
5514 })?;
5515 }
5516 Ok(())
5517 }))
5518 }
5519
5520 pub fn show_word_completions(
5521 &mut self,
5522 _: &ShowWordCompletions,
5523 window: &mut Window,
5524 cx: &mut Context<Self>,
5525 ) {
5526 self.open_or_update_completions_menu(
5527 Some(CompletionsMenuSource::Words {
5528 ignore_threshold: true,
5529 }),
5530 None,
5531 false,
5532 window,
5533 cx,
5534 );
5535 }
5536
5537 pub fn show_completions(
5538 &mut self,
5539 _: &ShowCompletions,
5540 window: &mut Window,
5541 cx: &mut Context<Self>,
5542 ) {
5543 self.open_or_update_completions_menu(None, None, false, window, cx);
5544 }
5545
5546 fn open_or_update_completions_menu(
5547 &mut self,
5548 requested_source: Option<CompletionsMenuSource>,
5549 trigger: Option<String>,
5550 trigger_in_words: bool,
5551 window: &mut Window,
5552 cx: &mut Context<Self>,
5553 ) {
5554 if self.pending_rename.is_some() {
5555 return;
5556 }
5557
5558 let completions_source = self
5559 .context_menu
5560 .borrow()
5561 .as_ref()
5562 .and_then(|menu| match menu {
5563 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5564 CodeContextMenu::CodeActions(_) => None,
5565 });
5566
5567 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5568
5569 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5570 // inserted and selected. To handle that case, the start of the selection is used so that
5571 // the menu starts with all choices.
5572 let position = self
5573 .selections
5574 .newest_anchor()
5575 .start
5576 .bias_right(&multibuffer_snapshot);
5577 if position.diff_base_anchor.is_some() {
5578 return;
5579 }
5580 let buffer_position = multibuffer_snapshot.anchor_before(position);
5581 let Some(buffer) = buffer_position
5582 .text_anchor
5583 .buffer_id
5584 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5585 else {
5586 return;
5587 };
5588 let buffer_snapshot = buffer.read(cx).snapshot();
5589
5590 let menu_is_open = matches!(
5591 self.context_menu.borrow().as_ref(),
5592 Some(CodeContextMenu::Completions(_))
5593 );
5594
5595 let language = buffer_snapshot
5596 .language_at(buffer_position.text_anchor)
5597 .map(|language| language.name());
5598
5599 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5600 let completion_settings = language_settings.completions.clone();
5601
5602 let show_completions_on_input = self
5603 .show_completions_on_input_override
5604 .unwrap_or(language_settings.show_completions_on_input);
5605 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5606 return;
5607 }
5608
5609 let query: Option<Arc<String>> =
5610 Self::completion_query(&multibuffer_snapshot, buffer_position)
5611 .map(|query| query.into());
5612
5613 drop(multibuffer_snapshot);
5614
5615 // Hide the current completions menu when query is empty. Without this, cached
5616 // completions from before the trigger char may be reused (#32774).
5617 if query.is_none() && menu_is_open {
5618 self.hide_context_menu(window, cx);
5619 }
5620
5621 let mut ignore_word_threshold = false;
5622 let provider = match requested_source {
5623 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5624 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5625 ignore_word_threshold = ignore_threshold;
5626 None
5627 }
5628 Some(CompletionsMenuSource::SnippetChoices)
5629 | Some(CompletionsMenuSource::SnippetsOnly) => {
5630 log::error!("bug: SnippetChoices requested_source is not handled");
5631 None
5632 }
5633 };
5634
5635 let sort_completions = provider
5636 .as_ref()
5637 .is_some_and(|provider| provider.sort_completions());
5638
5639 let filter_completions = provider
5640 .as_ref()
5641 .is_none_or(|provider| provider.filter_completions());
5642
5643 let was_snippets_only = matches!(
5644 completions_source,
5645 Some(CompletionsMenuSource::SnippetsOnly)
5646 );
5647
5648 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5649 if filter_completions {
5650 menu.filter(
5651 query.clone().unwrap_or_default(),
5652 buffer_position.text_anchor,
5653 &buffer,
5654 provider.clone(),
5655 window,
5656 cx,
5657 );
5658 }
5659 // When `is_incomplete` is false, no need to re-query completions when the current query
5660 // is a suffix of the initial query.
5661 let was_complete = !menu.is_incomplete;
5662 if was_complete && !was_snippets_only {
5663 // If the new query is a suffix of the old query (typing more characters) and
5664 // the previous result was complete, the existing completions can be filtered.
5665 //
5666 // Note that snippet completions are always complete.
5667 let query_matches = match (&menu.initial_query, &query) {
5668 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5669 (None, _) => true,
5670 _ => false,
5671 };
5672 if query_matches {
5673 let position_matches = if menu.initial_position == position {
5674 true
5675 } else {
5676 let snapshot = self.buffer.read(cx).read(cx);
5677 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5678 };
5679 if position_matches {
5680 return;
5681 }
5682 }
5683 }
5684 };
5685
5686 let Anchor {
5687 excerpt_id: buffer_excerpt_id,
5688 text_anchor: buffer_position,
5689 ..
5690 } = buffer_position;
5691
5692 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5693 buffer_snapshot.surrounding_word(buffer_position, None)
5694 {
5695 let word_to_exclude = buffer_snapshot
5696 .text_for_range(word_range.clone())
5697 .collect::<String>();
5698 (
5699 buffer_snapshot.anchor_before(word_range.start)
5700 ..buffer_snapshot.anchor_after(buffer_position),
5701 Some(word_to_exclude),
5702 )
5703 } else {
5704 (buffer_position..buffer_position, None)
5705 };
5706
5707 let show_completion_documentation = buffer_snapshot
5708 .settings_at(buffer_position, cx)
5709 .show_completion_documentation;
5710
5711 // The document can be large, so stay in reasonable bounds when searching for words,
5712 // otherwise completion pop-up might be slow to appear.
5713 const WORD_LOOKUP_ROWS: u32 = 5_000;
5714 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5715 let min_word_search = buffer_snapshot.clip_point(
5716 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5717 Bias::Left,
5718 );
5719 let max_word_search = buffer_snapshot.clip_point(
5720 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5721 Bias::Right,
5722 );
5723 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5724 ..buffer_snapshot.point_to_offset(max_word_search);
5725
5726 let skip_digits = query
5727 .as_ref()
5728 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5729
5730 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5731 trigger.as_ref().is_none_or(|trigger| {
5732 provider.is_completion_trigger(
5733 &buffer,
5734 position.text_anchor,
5735 trigger,
5736 trigger_in_words,
5737 cx,
5738 )
5739 })
5740 });
5741
5742 let provider_responses = if let Some(provider) = &provider
5743 && load_provider_completions
5744 {
5745 let trigger_character =
5746 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5747 let completion_context = CompletionContext {
5748 trigger_kind: match &trigger_character {
5749 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5750 None => CompletionTriggerKind::INVOKED,
5751 },
5752 trigger_character,
5753 };
5754
5755 provider.completions(
5756 buffer_excerpt_id,
5757 &buffer,
5758 buffer_position,
5759 completion_context,
5760 window,
5761 cx,
5762 )
5763 } else {
5764 Task::ready(Ok(Vec::new()))
5765 };
5766
5767 let load_word_completions = if !self.word_completions_enabled {
5768 false
5769 } else if requested_source
5770 == Some(CompletionsMenuSource::Words {
5771 ignore_threshold: true,
5772 })
5773 {
5774 true
5775 } else {
5776 load_provider_completions
5777 && completion_settings.words != WordsCompletionMode::Disabled
5778 && (ignore_word_threshold || {
5779 let words_min_length = completion_settings.words_min_length;
5780 // check whether word has at least `words_min_length` characters
5781 let query_chars = query.iter().flat_map(|q| q.chars());
5782 query_chars.take(words_min_length).count() == words_min_length
5783 })
5784 };
5785
5786 let mut words = if load_word_completions {
5787 cx.background_spawn({
5788 let buffer_snapshot = buffer_snapshot.clone();
5789 async move {
5790 buffer_snapshot.words_in_range(WordsQuery {
5791 fuzzy_contents: None,
5792 range: word_search_range,
5793 skip_digits,
5794 })
5795 }
5796 })
5797 } else {
5798 Task::ready(BTreeMap::default())
5799 };
5800
5801 let snippets = if let Some(provider) = &provider
5802 && provider.show_snippets()
5803 && let Some(project) = self.project()
5804 {
5805 let char_classifier = buffer_snapshot
5806 .char_classifier_at(buffer_position)
5807 .scope_context(Some(CharScopeContext::Completion));
5808 project.update(cx, |project, cx| {
5809 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5810 })
5811 } else {
5812 Task::ready(Ok(CompletionResponse {
5813 completions: Vec::new(),
5814 display_options: Default::default(),
5815 is_incomplete: false,
5816 }))
5817 };
5818
5819 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5820
5821 let id = post_inc(&mut self.next_completion_id);
5822 let task = cx.spawn_in(window, async move |editor, cx| {
5823 let Ok(()) = editor.update(cx, |this, _| {
5824 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5825 }) else {
5826 return;
5827 };
5828
5829 // TODO: Ideally completions from different sources would be selectively re-queried, so
5830 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5831 let mut completions = Vec::new();
5832 let mut is_incomplete = false;
5833 let mut display_options: Option<CompletionDisplayOptions> = None;
5834 if let Some(provider_responses) = provider_responses.await.log_err()
5835 && !provider_responses.is_empty()
5836 {
5837 for response in provider_responses {
5838 completions.extend(response.completions);
5839 is_incomplete = is_incomplete || response.is_incomplete;
5840 match display_options.as_mut() {
5841 None => {
5842 display_options = Some(response.display_options);
5843 }
5844 Some(options) => options.merge(&response.display_options),
5845 }
5846 }
5847 if completion_settings.words == WordsCompletionMode::Fallback {
5848 words = Task::ready(BTreeMap::default());
5849 }
5850 }
5851 let display_options = display_options.unwrap_or_default();
5852
5853 let mut words = words.await;
5854 if let Some(word_to_exclude) = &word_to_exclude {
5855 words.remove(word_to_exclude);
5856 }
5857 for lsp_completion in &completions {
5858 words.remove(&lsp_completion.new_text);
5859 }
5860 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5861 replace_range: word_replace_range.clone(),
5862 new_text: word.clone(),
5863 label: CodeLabel::plain(word, None),
5864 match_start: None,
5865 snippet_deduplication_key: None,
5866 icon_path: None,
5867 documentation: None,
5868 source: CompletionSource::BufferWord {
5869 word_range,
5870 resolved: false,
5871 },
5872 insert_text_mode: Some(InsertTextMode::AS_IS),
5873 confirm: None,
5874 }));
5875
5876 completions.extend(
5877 snippets
5878 .await
5879 .into_iter()
5880 .flat_map(|response| response.completions),
5881 );
5882
5883 let menu = if completions.is_empty() {
5884 None
5885 } else {
5886 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5887 let languages = editor
5888 .workspace
5889 .as_ref()
5890 .and_then(|(workspace, _)| workspace.upgrade())
5891 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5892 let menu = CompletionsMenu::new(
5893 id,
5894 requested_source.unwrap_or(if load_provider_completions {
5895 CompletionsMenuSource::Normal
5896 } else {
5897 CompletionsMenuSource::SnippetsOnly
5898 }),
5899 sort_completions,
5900 show_completion_documentation,
5901 position,
5902 query.clone(),
5903 is_incomplete,
5904 buffer.clone(),
5905 completions.into(),
5906 editor
5907 .context_menu()
5908 .borrow_mut()
5909 .as_ref()
5910 .map(|menu| menu.primary_scroll_handle()),
5911 display_options,
5912 snippet_sort_order,
5913 languages,
5914 language,
5915 cx,
5916 );
5917
5918 let query = if filter_completions { query } else { None };
5919 let matches_task = menu.do_async_filtering(
5920 query.unwrap_or_default(),
5921 buffer_position,
5922 &buffer,
5923 cx,
5924 );
5925 (menu, matches_task)
5926 }) else {
5927 return;
5928 };
5929
5930 let matches = matches_task.await;
5931
5932 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5933 // Newer menu already set, so exit.
5934 if let Some(CodeContextMenu::Completions(prev_menu)) =
5935 editor.context_menu.borrow().as_ref()
5936 && prev_menu.id > id
5937 {
5938 return;
5939 };
5940
5941 // Only valid to take prev_menu because either the new menu is immediately set
5942 // below, or the menu is hidden.
5943 if let Some(CodeContextMenu::Completions(prev_menu)) =
5944 editor.context_menu.borrow_mut().take()
5945 {
5946 let position_matches =
5947 if prev_menu.initial_position == menu.initial_position {
5948 true
5949 } else {
5950 let snapshot = editor.buffer.read(cx).read(cx);
5951 prev_menu.initial_position.to_offset(&snapshot)
5952 == menu.initial_position.to_offset(&snapshot)
5953 };
5954 if position_matches {
5955 // Preserve markdown cache before `set_filter_results` because it will
5956 // try to populate the documentation cache.
5957 menu.preserve_markdown_cache(prev_menu);
5958 }
5959 };
5960
5961 menu.set_filter_results(matches, provider, window, cx);
5962 }) else {
5963 return;
5964 };
5965
5966 menu.visible().then_some(menu)
5967 };
5968
5969 editor
5970 .update_in(cx, |editor, window, cx| {
5971 if editor.focus_handle.is_focused(window)
5972 && let Some(menu) = menu
5973 {
5974 *editor.context_menu.borrow_mut() =
5975 Some(CodeContextMenu::Completions(menu));
5976
5977 crate::hover_popover::hide_hover(editor, cx);
5978 if editor.show_edit_predictions_in_menu() {
5979 editor.update_visible_edit_prediction(window, cx);
5980 } else {
5981 editor.discard_edit_prediction(false, cx);
5982 }
5983
5984 cx.notify();
5985 return;
5986 }
5987
5988 if editor.completion_tasks.len() <= 1 {
5989 // If there are no more completion tasks and the last menu was empty, we should hide it.
5990 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5991 // If it was already hidden and we don't show edit predictions in the menu,
5992 // we should also show the edit prediction when available.
5993 if was_hidden && editor.show_edit_predictions_in_menu() {
5994 editor.update_visible_edit_prediction(window, cx);
5995 }
5996 }
5997 })
5998 .ok();
5999 });
6000
6001 self.completion_tasks.push((id, task));
6002 }
6003
6004 #[cfg(feature = "test-support")]
6005 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
6006 let menu = self.context_menu.borrow();
6007 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
6008 let completions = menu.completions.borrow();
6009 Some(completions.to_vec())
6010 } else {
6011 None
6012 }
6013 }
6014
6015 pub fn with_completions_menu_matching_id<R>(
6016 &self,
6017 id: CompletionId,
6018 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6019 ) -> R {
6020 let mut context_menu = self.context_menu.borrow_mut();
6021 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6022 return f(None);
6023 };
6024 if completions_menu.id != id {
6025 return f(None);
6026 }
6027 f(Some(completions_menu))
6028 }
6029
6030 pub fn confirm_completion(
6031 &mut self,
6032 action: &ConfirmCompletion,
6033 window: &mut Window,
6034 cx: &mut Context<Self>,
6035 ) -> Option<Task<Result<()>>> {
6036 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6037 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6038 }
6039
6040 pub fn confirm_completion_insert(
6041 &mut self,
6042 _: &ConfirmCompletionInsert,
6043 window: &mut Window,
6044 cx: &mut Context<Self>,
6045 ) -> Option<Task<Result<()>>> {
6046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6047 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6048 }
6049
6050 pub fn confirm_completion_replace(
6051 &mut self,
6052 _: &ConfirmCompletionReplace,
6053 window: &mut Window,
6054 cx: &mut Context<Self>,
6055 ) -> Option<Task<Result<()>>> {
6056 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6057 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6058 }
6059
6060 pub fn compose_completion(
6061 &mut self,
6062 action: &ComposeCompletion,
6063 window: &mut Window,
6064 cx: &mut Context<Self>,
6065 ) -> Option<Task<Result<()>>> {
6066 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6067 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6068 }
6069
6070 fn do_completion(
6071 &mut self,
6072 item_ix: Option<usize>,
6073 intent: CompletionIntent,
6074 window: &mut Window,
6075 cx: &mut Context<Editor>,
6076 ) -> Option<Task<Result<()>>> {
6077 use language::ToOffset as _;
6078
6079 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6080 else {
6081 return None;
6082 };
6083
6084 let candidate_id = {
6085 let entries = completions_menu.entries.borrow();
6086 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6087 if self.show_edit_predictions_in_menu() {
6088 self.discard_edit_prediction(true, cx);
6089 }
6090 mat.candidate_id
6091 };
6092
6093 let completion = completions_menu
6094 .completions
6095 .borrow()
6096 .get(candidate_id)?
6097 .clone();
6098 cx.stop_propagation();
6099
6100 let buffer_handle = completions_menu.buffer.clone();
6101
6102 let CompletionEdit {
6103 new_text,
6104 snippet,
6105 replace_range,
6106 } = process_completion_for_edit(
6107 &completion,
6108 intent,
6109 &buffer_handle,
6110 &completions_menu.initial_position.text_anchor,
6111 cx,
6112 );
6113
6114 let buffer = buffer_handle.read(cx);
6115 let snapshot = self.buffer.read(cx).snapshot(cx);
6116 let newest_anchor = self.selections.newest_anchor();
6117 let replace_range_multibuffer = {
6118 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6119 excerpt.map_range_from_buffer(replace_range.clone())
6120 };
6121 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6122 return None;
6123 }
6124
6125 let old_text = buffer
6126 .text_for_range(replace_range.clone())
6127 .collect::<String>();
6128 let lookbehind = newest_anchor
6129 .start
6130 .text_anchor
6131 .to_offset(buffer)
6132 .saturating_sub(replace_range.start.0);
6133 let lookahead = replace_range
6134 .end
6135 .0
6136 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6137 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6138 let suffix = &old_text[lookbehind.min(old_text.len())..];
6139
6140 let selections = self
6141 .selections
6142 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6143 let mut ranges = Vec::new();
6144 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6145
6146 for selection in &selections {
6147 let range = if selection.id == newest_anchor.id {
6148 replace_range_multibuffer.clone()
6149 } else {
6150 let mut range = selection.range();
6151
6152 // if prefix is present, don't duplicate it
6153 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6154 range.start = range.start.saturating_sub_usize(lookbehind);
6155
6156 // if suffix is also present, mimic the newest cursor and replace it
6157 if selection.id != newest_anchor.id
6158 && snapshot.contains_str_at(range.end, suffix)
6159 {
6160 range.end += lookahead;
6161 }
6162 }
6163 range
6164 };
6165
6166 ranges.push(range.clone());
6167
6168 if !self.linked_edit_ranges.is_empty() {
6169 let start_anchor = snapshot.anchor_before(range.start);
6170 let end_anchor = snapshot.anchor_after(range.end);
6171 if let Some(ranges) = self
6172 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6173 {
6174 for (buffer, edits) in ranges {
6175 linked_edits
6176 .entry(buffer.clone())
6177 .or_default()
6178 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6179 }
6180 }
6181 }
6182 }
6183
6184 let common_prefix_len = old_text
6185 .chars()
6186 .zip(new_text.chars())
6187 .take_while(|(a, b)| a == b)
6188 .map(|(a, _)| a.len_utf8())
6189 .sum::<usize>();
6190
6191 cx.emit(EditorEvent::InputHandled {
6192 utf16_range_to_replace: None,
6193 text: new_text[common_prefix_len..].into(),
6194 });
6195
6196 self.transact(window, cx, |editor, window, cx| {
6197 if let Some(mut snippet) = snippet {
6198 snippet.text = new_text.to_string();
6199 editor
6200 .insert_snippet(&ranges, snippet, window, cx)
6201 .log_err();
6202 } else {
6203 editor.buffer.update(cx, |multi_buffer, cx| {
6204 let auto_indent = match completion.insert_text_mode {
6205 Some(InsertTextMode::AS_IS) => None,
6206 _ => editor.autoindent_mode.clone(),
6207 };
6208 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6209 multi_buffer.edit(edits, auto_indent, cx);
6210 });
6211 }
6212 for (buffer, edits) in linked_edits {
6213 buffer.update(cx, |buffer, cx| {
6214 let snapshot = buffer.snapshot();
6215 let edits = edits
6216 .into_iter()
6217 .map(|(range, text)| {
6218 use text::ToPoint as TP;
6219 let end_point = TP::to_point(&range.end, &snapshot);
6220 let start_point = TP::to_point(&range.start, &snapshot);
6221 (start_point..end_point, text)
6222 })
6223 .sorted_by_key(|(range, _)| range.start);
6224 buffer.edit(edits, None, cx);
6225 })
6226 }
6227
6228 editor.refresh_edit_prediction(true, false, window, cx);
6229 });
6230 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6231
6232 let show_new_completions_on_confirm = completion
6233 .confirm
6234 .as_ref()
6235 .is_some_and(|confirm| confirm(intent, window, cx));
6236 if show_new_completions_on_confirm {
6237 self.open_or_update_completions_menu(None, None, false, window, cx);
6238 }
6239
6240 let provider = self.completion_provider.as_ref()?;
6241
6242 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6243 let command = lsp_store.as_ref().and_then(|lsp_store| {
6244 let CompletionSource::Lsp {
6245 lsp_completion,
6246 server_id,
6247 ..
6248 } = &completion.source
6249 else {
6250 return None;
6251 };
6252 let lsp_command = lsp_completion.command.as_ref()?;
6253 let available_commands = lsp_store
6254 .read(cx)
6255 .lsp_server_capabilities
6256 .get(server_id)
6257 .and_then(|server_capabilities| {
6258 server_capabilities
6259 .execute_command_provider
6260 .as_ref()
6261 .map(|options| options.commands.as_slice())
6262 })?;
6263 if available_commands.contains(&lsp_command.command) {
6264 Some(CodeAction {
6265 server_id: *server_id,
6266 range: language::Anchor::MIN..language::Anchor::MIN,
6267 lsp_action: LspAction::Command(lsp_command.clone()),
6268 resolved: false,
6269 })
6270 } else {
6271 None
6272 }
6273 });
6274
6275 drop(completion);
6276 let apply_edits = provider.apply_additional_edits_for_completion(
6277 buffer_handle.clone(),
6278 completions_menu.completions.clone(),
6279 candidate_id,
6280 true,
6281 cx,
6282 );
6283
6284 let editor_settings = EditorSettings::get_global(cx);
6285 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6286 // After the code completion is finished, users often want to know what signatures are needed.
6287 // so we should automatically call signature_help
6288 self.show_signature_help(&ShowSignatureHelp, window, cx);
6289 }
6290
6291 Some(cx.spawn_in(window, async move |editor, cx| {
6292 apply_edits.await?;
6293
6294 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6295 let title = command.lsp_action.title().to_owned();
6296 let project_transaction = lsp_store
6297 .update(cx, |lsp_store, cx| {
6298 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6299 })?
6300 .await
6301 .context("applying post-completion command")?;
6302 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6303 Self::open_project_transaction(
6304 &editor,
6305 workspace.downgrade(),
6306 project_transaction,
6307 title,
6308 cx,
6309 )
6310 .await?;
6311 }
6312 }
6313
6314 Ok(())
6315 }))
6316 }
6317
6318 pub fn toggle_code_actions(
6319 &mut self,
6320 action: &ToggleCodeActions,
6321 window: &mut Window,
6322 cx: &mut Context<Self>,
6323 ) {
6324 let quick_launch = action.quick_launch;
6325 let mut context_menu = self.context_menu.borrow_mut();
6326 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6327 if code_actions.deployed_from == action.deployed_from {
6328 // Toggle if we're selecting the same one
6329 *context_menu = None;
6330 cx.notify();
6331 return;
6332 } else {
6333 // Otherwise, clear it and start a new one
6334 *context_menu = None;
6335 cx.notify();
6336 }
6337 }
6338 drop(context_menu);
6339 let snapshot = self.snapshot(window, cx);
6340 let deployed_from = action.deployed_from.clone();
6341 let action = action.clone();
6342 self.completion_tasks.clear();
6343 self.discard_edit_prediction(false, cx);
6344
6345 let multibuffer_point = match &action.deployed_from {
6346 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6347 DisplayPoint::new(*row, 0).to_point(&snapshot)
6348 }
6349 _ => self
6350 .selections
6351 .newest::<Point>(&snapshot.display_snapshot)
6352 .head(),
6353 };
6354 let Some((buffer, buffer_row)) = snapshot
6355 .buffer_snapshot()
6356 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6357 .and_then(|(buffer_snapshot, range)| {
6358 self.buffer()
6359 .read(cx)
6360 .buffer(buffer_snapshot.remote_id())
6361 .map(|buffer| (buffer, range.start.row))
6362 })
6363 else {
6364 return;
6365 };
6366 let buffer_id = buffer.read(cx).remote_id();
6367 let tasks = self
6368 .tasks
6369 .get(&(buffer_id, buffer_row))
6370 .map(|t| Arc::new(t.to_owned()));
6371
6372 if !self.focus_handle.is_focused(window) {
6373 return;
6374 }
6375 let project = self.project.clone();
6376
6377 let code_actions_task = match deployed_from {
6378 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6379 _ => self.code_actions(buffer_row, window, cx),
6380 };
6381
6382 let runnable_task = match deployed_from {
6383 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6384 _ => {
6385 let mut task_context_task = Task::ready(None);
6386 if let Some(tasks) = &tasks
6387 && let Some(project) = project
6388 {
6389 task_context_task =
6390 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6391 }
6392
6393 cx.spawn_in(window, {
6394 let buffer = buffer.clone();
6395 async move |editor, cx| {
6396 let task_context = task_context_task.await;
6397
6398 let resolved_tasks =
6399 tasks
6400 .zip(task_context.clone())
6401 .map(|(tasks, task_context)| ResolvedTasks {
6402 templates: tasks.resolve(&task_context).collect(),
6403 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6404 multibuffer_point.row,
6405 tasks.column,
6406 )),
6407 });
6408 let debug_scenarios = editor
6409 .update(cx, |editor, cx| {
6410 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6411 })?
6412 .await;
6413 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6414 }
6415 })
6416 }
6417 };
6418
6419 cx.spawn_in(window, async move |editor, cx| {
6420 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6421 let code_actions = code_actions_task.await;
6422 let spawn_straight_away = quick_launch
6423 && resolved_tasks
6424 .as_ref()
6425 .is_some_and(|tasks| tasks.templates.len() == 1)
6426 && code_actions
6427 .as_ref()
6428 .is_none_or(|actions| actions.is_empty())
6429 && debug_scenarios.is_empty();
6430
6431 editor.update_in(cx, |editor, window, cx| {
6432 crate::hover_popover::hide_hover(editor, cx);
6433 let actions = CodeActionContents::new(
6434 resolved_tasks,
6435 code_actions,
6436 debug_scenarios,
6437 task_context.unwrap_or_default(),
6438 );
6439
6440 // Don't show the menu if there are no actions available
6441 if actions.is_empty() {
6442 cx.notify();
6443 return Task::ready(Ok(()));
6444 }
6445
6446 *editor.context_menu.borrow_mut() =
6447 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6448 buffer,
6449 actions,
6450 selected_item: Default::default(),
6451 scroll_handle: UniformListScrollHandle::default(),
6452 deployed_from,
6453 }));
6454 cx.notify();
6455 if spawn_straight_away
6456 && let Some(task) = editor.confirm_code_action(
6457 &ConfirmCodeAction { item_ix: Some(0) },
6458 window,
6459 cx,
6460 )
6461 {
6462 return task;
6463 }
6464
6465 Task::ready(Ok(()))
6466 })
6467 })
6468 .detach_and_log_err(cx);
6469 }
6470
6471 fn debug_scenarios(
6472 &mut self,
6473 resolved_tasks: &Option<ResolvedTasks>,
6474 buffer: &Entity<Buffer>,
6475 cx: &mut App,
6476 ) -> Task<Vec<task::DebugScenario>> {
6477 maybe!({
6478 let project = self.project()?;
6479 let dap_store = project.read(cx).dap_store();
6480 let mut scenarios = vec![];
6481 let resolved_tasks = resolved_tasks.as_ref()?;
6482 let buffer = buffer.read(cx);
6483 let language = buffer.language()?;
6484 let file = buffer.file();
6485 let debug_adapter = language_settings(language.name().into(), file, cx)
6486 .debuggers
6487 .first()
6488 .map(SharedString::from)
6489 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6490
6491 dap_store.update(cx, |dap_store, cx| {
6492 for (_, task) in &resolved_tasks.templates {
6493 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6494 task.original_task().clone(),
6495 debug_adapter.clone().into(),
6496 task.display_label().to_owned().into(),
6497 cx,
6498 );
6499 scenarios.push(maybe_scenario);
6500 }
6501 });
6502 Some(cx.background_spawn(async move {
6503 futures::future::join_all(scenarios)
6504 .await
6505 .into_iter()
6506 .flatten()
6507 .collect::<Vec<_>>()
6508 }))
6509 })
6510 .unwrap_or_else(|| Task::ready(vec![]))
6511 }
6512
6513 fn code_actions(
6514 &mut self,
6515 buffer_row: u32,
6516 window: &mut Window,
6517 cx: &mut Context<Self>,
6518 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6519 let mut task = self.code_actions_task.take();
6520 cx.spawn_in(window, async move |editor, cx| {
6521 while let Some(prev_task) = task {
6522 prev_task.await.log_err();
6523 task = editor
6524 .update(cx, |this, _| this.code_actions_task.take())
6525 .ok()?;
6526 }
6527
6528 editor
6529 .update(cx, |editor, cx| {
6530 editor
6531 .available_code_actions
6532 .clone()
6533 .and_then(|(location, code_actions)| {
6534 let snapshot = location.buffer.read(cx).snapshot();
6535 let point_range = location.range.to_point(&snapshot);
6536 let point_range = point_range.start.row..=point_range.end.row;
6537 if point_range.contains(&buffer_row) {
6538 Some(code_actions)
6539 } else {
6540 None
6541 }
6542 })
6543 })
6544 .ok()
6545 .flatten()
6546 })
6547 }
6548
6549 pub fn confirm_code_action(
6550 &mut self,
6551 action: &ConfirmCodeAction,
6552 window: &mut Window,
6553 cx: &mut Context<Self>,
6554 ) -> Option<Task<Result<()>>> {
6555 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6556
6557 let actions_menu =
6558 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6559 menu
6560 } else {
6561 return None;
6562 };
6563
6564 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6565 let action = actions_menu.actions.get(action_ix)?;
6566 let title = action.label();
6567 let buffer = actions_menu.buffer;
6568 let workspace = self.workspace()?;
6569
6570 match action {
6571 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6572 workspace.update(cx, |workspace, cx| {
6573 workspace.schedule_resolved_task(
6574 task_source_kind,
6575 resolved_task,
6576 false,
6577 window,
6578 cx,
6579 );
6580
6581 Some(Task::ready(Ok(())))
6582 })
6583 }
6584 CodeActionsItem::CodeAction {
6585 excerpt_id,
6586 action,
6587 provider,
6588 } => {
6589 let apply_code_action =
6590 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6591 let workspace = workspace.downgrade();
6592 Some(cx.spawn_in(window, async move |editor, cx| {
6593 let project_transaction = apply_code_action.await?;
6594 Self::open_project_transaction(
6595 &editor,
6596 workspace,
6597 project_transaction,
6598 title,
6599 cx,
6600 )
6601 .await
6602 }))
6603 }
6604 CodeActionsItem::DebugScenario(scenario) => {
6605 let context = actions_menu.actions.context;
6606
6607 workspace.update(cx, |workspace, cx| {
6608 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6609 workspace.start_debug_session(
6610 scenario,
6611 context,
6612 Some(buffer),
6613 None,
6614 window,
6615 cx,
6616 );
6617 });
6618 Some(Task::ready(Ok(())))
6619 }
6620 }
6621 }
6622
6623 pub async fn open_project_transaction(
6624 editor: &WeakEntity<Editor>,
6625 workspace: WeakEntity<Workspace>,
6626 transaction: ProjectTransaction,
6627 title: String,
6628 cx: &mut AsyncWindowContext,
6629 ) -> Result<()> {
6630 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6631 cx.update(|_, cx| {
6632 entries.sort_unstable_by_key(|(buffer, _)| {
6633 buffer.read(cx).file().map(|f| f.path().clone())
6634 });
6635 })?;
6636 if entries.is_empty() {
6637 return Ok(());
6638 }
6639
6640 // If the project transaction's edits are all contained within this editor, then
6641 // avoid opening a new editor to display them.
6642
6643 if let [(buffer, transaction)] = &*entries {
6644 let excerpt = editor.update(cx, |editor, cx| {
6645 editor
6646 .buffer()
6647 .read(cx)
6648 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6649 })?;
6650 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6651 && excerpted_buffer == *buffer
6652 {
6653 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6654 let excerpt_range = excerpt_range.to_offset(buffer);
6655 buffer
6656 .edited_ranges_for_transaction::<usize>(transaction)
6657 .all(|range| {
6658 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6659 })
6660 })?;
6661
6662 if all_edits_within_excerpt {
6663 return Ok(());
6664 }
6665 }
6666 }
6667
6668 let mut ranges_to_highlight = Vec::new();
6669 let excerpt_buffer = cx.new(|cx| {
6670 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6671 for (buffer_handle, transaction) in &entries {
6672 let edited_ranges = buffer_handle
6673 .read(cx)
6674 .edited_ranges_for_transaction::<Point>(transaction)
6675 .collect::<Vec<_>>();
6676 let (ranges, _) = multibuffer.set_excerpts_for_path(
6677 PathKey::for_buffer(buffer_handle, cx),
6678 buffer_handle.clone(),
6679 edited_ranges,
6680 multibuffer_context_lines(cx),
6681 cx,
6682 );
6683
6684 ranges_to_highlight.extend(ranges);
6685 }
6686 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6687 multibuffer
6688 })?;
6689
6690 workspace.update_in(cx, |workspace, window, cx| {
6691 let project = workspace.project().clone();
6692 let editor =
6693 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6694 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6695 editor.update(cx, |editor, cx| {
6696 editor.highlight_background::<Self>(
6697 &ranges_to_highlight,
6698 |_, theme| theme.colors().editor_highlighted_line_background,
6699 cx,
6700 );
6701 });
6702 })?;
6703
6704 Ok(())
6705 }
6706
6707 pub fn clear_code_action_providers(&mut self) {
6708 self.code_action_providers.clear();
6709 self.available_code_actions.take();
6710 }
6711
6712 pub fn add_code_action_provider(
6713 &mut self,
6714 provider: Rc<dyn CodeActionProvider>,
6715 window: &mut Window,
6716 cx: &mut Context<Self>,
6717 ) {
6718 if self
6719 .code_action_providers
6720 .iter()
6721 .any(|existing_provider| existing_provider.id() == provider.id())
6722 {
6723 return;
6724 }
6725
6726 self.code_action_providers.push(provider);
6727 self.refresh_code_actions(window, cx);
6728 }
6729
6730 pub fn remove_code_action_provider(
6731 &mut self,
6732 id: Arc<str>,
6733 window: &mut Window,
6734 cx: &mut Context<Self>,
6735 ) {
6736 self.code_action_providers
6737 .retain(|provider| provider.id() != id);
6738 self.refresh_code_actions(window, cx);
6739 }
6740
6741 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6742 !self.code_action_providers.is_empty()
6743 && EditorSettings::get_global(cx).toolbar.code_actions
6744 }
6745
6746 pub fn has_available_code_actions(&self) -> bool {
6747 self.available_code_actions
6748 .as_ref()
6749 .is_some_and(|(_, actions)| !actions.is_empty())
6750 }
6751
6752 fn render_inline_code_actions(
6753 &self,
6754 icon_size: ui::IconSize,
6755 display_row: DisplayRow,
6756 is_active: bool,
6757 cx: &mut Context<Self>,
6758 ) -> AnyElement {
6759 let show_tooltip = !self.context_menu_visible();
6760 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6761 .icon_size(icon_size)
6762 .shape(ui::IconButtonShape::Square)
6763 .icon_color(ui::Color::Hidden)
6764 .toggle_state(is_active)
6765 .when(show_tooltip, |this| {
6766 this.tooltip({
6767 let focus_handle = self.focus_handle.clone();
6768 move |_window, cx| {
6769 Tooltip::for_action_in(
6770 "Toggle Code Actions",
6771 &ToggleCodeActions {
6772 deployed_from: None,
6773 quick_launch: false,
6774 },
6775 &focus_handle,
6776 cx,
6777 )
6778 }
6779 })
6780 })
6781 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6782 window.focus(&editor.focus_handle(cx));
6783 editor.toggle_code_actions(
6784 &crate::actions::ToggleCodeActions {
6785 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6786 display_row,
6787 )),
6788 quick_launch: false,
6789 },
6790 window,
6791 cx,
6792 );
6793 }))
6794 .into_any_element()
6795 }
6796
6797 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6798 &self.context_menu
6799 }
6800
6801 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6802 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6803 cx.background_executor()
6804 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6805 .await;
6806
6807 let (start_buffer, start, _, end, newest_selection) = this
6808 .update(cx, |this, cx| {
6809 let newest_selection = this.selections.newest_anchor().clone();
6810 if newest_selection.head().diff_base_anchor.is_some() {
6811 return None;
6812 }
6813 let display_snapshot = this.display_snapshot(cx);
6814 let newest_selection_adjusted =
6815 this.selections.newest_adjusted(&display_snapshot);
6816 let buffer = this.buffer.read(cx);
6817
6818 let (start_buffer, start) =
6819 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6820 let (end_buffer, end) =
6821 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6822
6823 Some((start_buffer, start, end_buffer, end, newest_selection))
6824 })?
6825 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6826 .context(
6827 "Expected selection to lie in a single buffer when refreshing code actions",
6828 )?;
6829 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6830 let providers = this.code_action_providers.clone();
6831 let tasks = this
6832 .code_action_providers
6833 .iter()
6834 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6835 .collect::<Vec<_>>();
6836 (providers, tasks)
6837 })?;
6838
6839 let mut actions = Vec::new();
6840 for (provider, provider_actions) in
6841 providers.into_iter().zip(future::join_all(tasks).await)
6842 {
6843 if let Some(provider_actions) = provider_actions.log_err() {
6844 actions.extend(provider_actions.into_iter().map(|action| {
6845 AvailableCodeAction {
6846 excerpt_id: newest_selection.start.excerpt_id,
6847 action,
6848 provider: provider.clone(),
6849 }
6850 }));
6851 }
6852 }
6853
6854 this.update(cx, |this, cx| {
6855 this.available_code_actions = if actions.is_empty() {
6856 None
6857 } else {
6858 Some((
6859 Location {
6860 buffer: start_buffer,
6861 range: start..end,
6862 },
6863 actions.into(),
6864 ))
6865 };
6866 cx.notify();
6867 })
6868 }));
6869 }
6870
6871 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6872 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6873 self.show_git_blame_inline = false;
6874
6875 self.show_git_blame_inline_delay_task =
6876 Some(cx.spawn_in(window, async move |this, cx| {
6877 cx.background_executor().timer(delay).await;
6878
6879 this.update(cx, |this, cx| {
6880 this.show_git_blame_inline = true;
6881 cx.notify();
6882 })
6883 .log_err();
6884 }));
6885 }
6886 }
6887
6888 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6889 let snapshot = self.snapshot(window, cx);
6890 let cursor = self
6891 .selections
6892 .newest::<Point>(&snapshot.display_snapshot)
6893 .head();
6894 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6895 else {
6896 return;
6897 };
6898
6899 if self.blame.is_none() {
6900 self.start_git_blame(true, window, cx);
6901 }
6902 let Some(blame) = self.blame.as_ref() else {
6903 return;
6904 };
6905
6906 let row_info = RowInfo {
6907 buffer_id: Some(buffer.remote_id()),
6908 buffer_row: Some(point.row),
6909 ..Default::default()
6910 };
6911 let Some((buffer, blame_entry)) = blame
6912 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6913 .flatten()
6914 else {
6915 return;
6916 };
6917
6918 let anchor = self.selections.newest_anchor().head();
6919 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6920 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6921 self.show_blame_popover(
6922 buffer,
6923 &blame_entry,
6924 position + last_bounds.origin,
6925 true,
6926 cx,
6927 );
6928 };
6929 }
6930
6931 fn show_blame_popover(
6932 &mut self,
6933 buffer: BufferId,
6934 blame_entry: &BlameEntry,
6935 position: gpui::Point<Pixels>,
6936 ignore_timeout: bool,
6937 cx: &mut Context<Self>,
6938 ) {
6939 if let Some(state) = &mut self.inline_blame_popover {
6940 state.hide_task.take();
6941 } else {
6942 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6943 let blame_entry = blame_entry.clone();
6944 let show_task = cx.spawn(async move |editor, cx| {
6945 if !ignore_timeout {
6946 cx.background_executor()
6947 .timer(std::time::Duration::from_millis(blame_popover_delay))
6948 .await;
6949 }
6950 editor
6951 .update(cx, |editor, cx| {
6952 editor.inline_blame_popover_show_task.take();
6953 let Some(blame) = editor.blame.as_ref() else {
6954 return;
6955 };
6956 let blame = blame.read(cx);
6957 let details = blame.details_for_entry(buffer, &blame_entry);
6958 let markdown = cx.new(|cx| {
6959 Markdown::new(
6960 details
6961 .as_ref()
6962 .map(|message| message.message.clone())
6963 .unwrap_or_default(),
6964 None,
6965 None,
6966 cx,
6967 )
6968 });
6969 editor.inline_blame_popover = Some(InlineBlamePopover {
6970 position,
6971 hide_task: None,
6972 popover_bounds: None,
6973 popover_state: InlineBlamePopoverState {
6974 scroll_handle: ScrollHandle::new(),
6975 commit_message: details,
6976 markdown,
6977 },
6978 keyboard_grace: ignore_timeout,
6979 });
6980 cx.notify();
6981 })
6982 .ok();
6983 });
6984 self.inline_blame_popover_show_task = Some(show_task);
6985 }
6986 }
6987
6988 pub fn has_mouse_context_menu(&self) -> bool {
6989 self.mouse_context_menu.is_some()
6990 }
6991
6992 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6993 self.inline_blame_popover_show_task.take();
6994 if let Some(state) = &mut self.inline_blame_popover {
6995 let hide_task = cx.spawn(async move |editor, cx| {
6996 if !ignore_timeout {
6997 cx.background_executor()
6998 .timer(std::time::Duration::from_millis(100))
6999 .await;
7000 }
7001 editor
7002 .update(cx, |editor, cx| {
7003 editor.inline_blame_popover.take();
7004 cx.notify();
7005 })
7006 .ok();
7007 });
7008 state.hide_task = Some(hide_task);
7009 true
7010 } else {
7011 false
7012 }
7013 }
7014
7015 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7016 if self.pending_rename.is_some() {
7017 return None;
7018 }
7019
7020 let provider = self.semantics_provider.clone()?;
7021 let buffer = self.buffer.read(cx);
7022 let newest_selection = self.selections.newest_anchor().clone();
7023 let cursor_position = newest_selection.head();
7024 let (cursor_buffer, cursor_buffer_position) =
7025 buffer.text_anchor_for_position(cursor_position, cx)?;
7026 let (tail_buffer, tail_buffer_position) =
7027 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7028 if cursor_buffer != tail_buffer {
7029 return None;
7030 }
7031
7032 let snapshot = cursor_buffer.read(cx).snapshot();
7033 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7034 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7035 if start_word_range != end_word_range {
7036 self.document_highlights_task.take();
7037 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7038 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7039 return None;
7040 }
7041
7042 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7043 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7044 cx.background_executor()
7045 .timer(Duration::from_millis(debounce))
7046 .await;
7047
7048 let highlights = if let Some(highlights) = cx
7049 .update(|cx| {
7050 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7051 })
7052 .ok()
7053 .flatten()
7054 {
7055 highlights.await.log_err()
7056 } else {
7057 None
7058 };
7059
7060 if let Some(highlights) = highlights {
7061 this.update(cx, |this, cx| {
7062 if this.pending_rename.is_some() {
7063 return;
7064 }
7065
7066 let buffer = this.buffer.read(cx);
7067 if buffer
7068 .text_anchor_for_position(cursor_position, cx)
7069 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7070 {
7071 return;
7072 }
7073
7074 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7075 let mut write_ranges = Vec::new();
7076 let mut read_ranges = Vec::new();
7077 for highlight in highlights {
7078 let buffer_id = cursor_buffer.read(cx).remote_id();
7079 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7080 {
7081 let start = highlight
7082 .range
7083 .start
7084 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7085 let end = highlight
7086 .range
7087 .end
7088 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7089 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7090 continue;
7091 }
7092
7093 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7094 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7095 write_ranges.push(range);
7096 } else {
7097 read_ranges.push(range);
7098 }
7099 }
7100 }
7101
7102 this.highlight_background::<DocumentHighlightRead>(
7103 &read_ranges,
7104 |_, theme| theme.colors().editor_document_highlight_read_background,
7105 cx,
7106 );
7107 this.highlight_background::<DocumentHighlightWrite>(
7108 &write_ranges,
7109 |_, theme| theme.colors().editor_document_highlight_write_background,
7110 cx,
7111 );
7112 cx.notify();
7113 })
7114 .log_err();
7115 }
7116 }));
7117 None
7118 }
7119
7120 fn prepare_highlight_query_from_selection(
7121 &mut self,
7122 window: &Window,
7123 cx: &mut Context<Editor>,
7124 ) -> Option<(String, Range<Anchor>)> {
7125 if matches!(self.mode, EditorMode::SingleLine) {
7126 return None;
7127 }
7128 if !EditorSettings::get_global(cx).selection_highlight {
7129 return None;
7130 }
7131 if self.selections.count() != 1 || self.selections.line_mode() {
7132 return None;
7133 }
7134 let snapshot = self.snapshot(window, cx);
7135 let selection = self.selections.newest::<Point>(&snapshot);
7136 // If the selection spans multiple rows OR it is empty
7137 if selection.start.row != selection.end.row
7138 || selection.start.column == selection.end.column
7139 {
7140 return None;
7141 }
7142 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7143 let query = snapshot
7144 .buffer_snapshot()
7145 .text_for_range(selection_anchor_range.clone())
7146 .collect::<String>();
7147 if query.trim().is_empty() {
7148 return None;
7149 }
7150 Some((query, selection_anchor_range))
7151 }
7152
7153 #[ztracing::instrument(skip_all)]
7154 fn update_selection_occurrence_highlights(
7155 &mut self,
7156 query_text: String,
7157 query_range: Range<Anchor>,
7158 multi_buffer_range_to_query: Range<Point>,
7159 use_debounce: bool,
7160 window: &mut Window,
7161 cx: &mut Context<Editor>,
7162 ) -> Task<()> {
7163 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7164 cx.spawn_in(window, async move |editor, cx| {
7165 if use_debounce {
7166 cx.background_executor()
7167 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7168 .await;
7169 }
7170 let match_task = cx.background_spawn(async move {
7171 let buffer_ranges = multi_buffer_snapshot
7172 .range_to_buffer_ranges(multi_buffer_range_to_query)
7173 .into_iter()
7174 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7175 let mut match_ranges = Vec::new();
7176 let Ok(regex) = project::search::SearchQuery::text(
7177 query_text.clone(),
7178 false,
7179 false,
7180 false,
7181 Default::default(),
7182 Default::default(),
7183 false,
7184 None,
7185 ) else {
7186 return Vec::default();
7187 };
7188 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7189 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7190 match_ranges.extend(
7191 regex
7192 .search(
7193 buffer_snapshot,
7194 Some(search_range.start.0..search_range.end.0),
7195 )
7196 .await
7197 .into_iter()
7198 .filter_map(|match_range| {
7199 let match_start = buffer_snapshot
7200 .anchor_after(search_range.start + match_range.start);
7201 let match_end = buffer_snapshot
7202 .anchor_before(search_range.start + match_range.end);
7203 let match_anchor_range =
7204 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7205 (match_anchor_range != query_range).then_some(match_anchor_range)
7206 }),
7207 );
7208 }
7209 match_ranges
7210 });
7211 let match_ranges = match_task.await;
7212 editor
7213 .update_in(cx, |editor, _, cx| {
7214 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7215 if !match_ranges.is_empty() {
7216 editor.highlight_background::<SelectedTextHighlight>(
7217 &match_ranges,
7218 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7219 cx,
7220 )
7221 }
7222 })
7223 .log_err();
7224 })
7225 }
7226
7227 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7228 struct NewlineFold;
7229 let type_id = std::any::TypeId::of::<NewlineFold>();
7230 if !self.mode.is_single_line() {
7231 return;
7232 }
7233 let snapshot = self.snapshot(window, cx);
7234 if snapshot.buffer_snapshot().max_point().row == 0 {
7235 return;
7236 }
7237 let task = cx.background_spawn(async move {
7238 let new_newlines = snapshot
7239 .buffer_chars_at(MultiBufferOffset(0))
7240 .filter_map(|(c, i)| {
7241 if c == '\n' {
7242 Some(
7243 snapshot.buffer_snapshot().anchor_after(i)
7244 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7245 )
7246 } else {
7247 None
7248 }
7249 })
7250 .collect::<Vec<_>>();
7251 let existing_newlines = snapshot
7252 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7253 .filter_map(|fold| {
7254 if fold.placeholder.type_tag == Some(type_id) {
7255 Some(fold.range.start..fold.range.end)
7256 } else {
7257 None
7258 }
7259 })
7260 .collect::<Vec<_>>();
7261
7262 (new_newlines, existing_newlines)
7263 });
7264 self.folding_newlines = cx.spawn(async move |this, cx| {
7265 let (new_newlines, existing_newlines) = task.await;
7266 if new_newlines == existing_newlines {
7267 return;
7268 }
7269 let placeholder = FoldPlaceholder {
7270 render: Arc::new(move |_, _, cx| {
7271 div()
7272 .bg(cx.theme().status().hint_background)
7273 .border_b_1()
7274 .size_full()
7275 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7276 .border_color(cx.theme().status().hint)
7277 .child("\\n")
7278 .into_any()
7279 }),
7280 constrain_width: false,
7281 merge_adjacent: false,
7282 type_tag: Some(type_id),
7283 };
7284 let creases = new_newlines
7285 .into_iter()
7286 .map(|range| Crease::simple(range, placeholder.clone()))
7287 .collect();
7288 this.update(cx, |this, cx| {
7289 this.display_map.update(cx, |display_map, cx| {
7290 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7291 display_map.fold(creases, cx);
7292 });
7293 })
7294 .ok();
7295 });
7296 }
7297
7298 #[ztracing::instrument(skip_all)]
7299 fn refresh_selected_text_highlights(
7300 &mut self,
7301 on_buffer_edit: bool,
7302 window: &mut Window,
7303 cx: &mut Context<Editor>,
7304 ) {
7305 let Some((query_text, query_range)) =
7306 self.prepare_highlight_query_from_selection(window, cx)
7307 else {
7308 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7309 self.quick_selection_highlight_task.take();
7310 self.debounced_selection_highlight_task.take();
7311 return;
7312 };
7313 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7314 if on_buffer_edit
7315 || self
7316 .quick_selection_highlight_task
7317 .as_ref()
7318 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7319 {
7320 let multi_buffer_visible_start = self
7321 .scroll_manager
7322 .anchor()
7323 .anchor
7324 .to_point(&multi_buffer_snapshot);
7325 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7326 multi_buffer_visible_start
7327 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7328 Bias::Left,
7329 );
7330 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7331 self.quick_selection_highlight_task = Some((
7332 query_range.clone(),
7333 self.update_selection_occurrence_highlights(
7334 query_text.clone(),
7335 query_range.clone(),
7336 multi_buffer_visible_range,
7337 false,
7338 window,
7339 cx,
7340 ),
7341 ));
7342 }
7343 if on_buffer_edit
7344 || self
7345 .debounced_selection_highlight_task
7346 .as_ref()
7347 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7348 {
7349 let multi_buffer_start = multi_buffer_snapshot
7350 .anchor_before(MultiBufferOffset(0))
7351 .to_point(&multi_buffer_snapshot);
7352 let multi_buffer_end = multi_buffer_snapshot
7353 .anchor_after(multi_buffer_snapshot.len())
7354 .to_point(&multi_buffer_snapshot);
7355 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7356 self.debounced_selection_highlight_task = Some((
7357 query_range.clone(),
7358 self.update_selection_occurrence_highlights(
7359 query_text,
7360 query_range,
7361 multi_buffer_full_range,
7362 true,
7363 window,
7364 cx,
7365 ),
7366 ));
7367 }
7368 }
7369
7370 pub fn refresh_edit_prediction(
7371 &mut self,
7372 debounce: bool,
7373 user_requested: bool,
7374 window: &mut Window,
7375 cx: &mut Context<Self>,
7376 ) -> Option<()> {
7377 if DisableAiSettings::get_global(cx).disable_ai {
7378 return None;
7379 }
7380
7381 let provider = self.edit_prediction_provider()?;
7382 let cursor = self.selections.newest_anchor().head();
7383 let (buffer, cursor_buffer_position) =
7384 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7385
7386 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7387 self.discard_edit_prediction(false, cx);
7388 return None;
7389 }
7390
7391 self.update_visible_edit_prediction(window, cx);
7392
7393 if !user_requested
7394 && (!self.should_show_edit_predictions()
7395 || !self.is_focused(window)
7396 || buffer.read(cx).is_empty())
7397 {
7398 self.discard_edit_prediction(false, cx);
7399 return None;
7400 }
7401
7402 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7403 Some(())
7404 }
7405
7406 fn show_edit_predictions_in_menu(&self) -> bool {
7407 match self.edit_prediction_settings {
7408 EditPredictionSettings::Disabled => false,
7409 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7410 }
7411 }
7412
7413 pub fn edit_predictions_enabled(&self) -> bool {
7414 match self.edit_prediction_settings {
7415 EditPredictionSettings::Disabled => false,
7416 EditPredictionSettings::Enabled { .. } => true,
7417 }
7418 }
7419
7420 fn edit_prediction_requires_modifier(&self) -> bool {
7421 match self.edit_prediction_settings {
7422 EditPredictionSettings::Disabled => false,
7423 EditPredictionSettings::Enabled {
7424 preview_requires_modifier,
7425 ..
7426 } => preview_requires_modifier,
7427 }
7428 }
7429
7430 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7431 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7432 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7433 self.discard_edit_prediction(false, cx);
7434 } else {
7435 let selection = self.selections.newest_anchor();
7436 let cursor = selection.head();
7437
7438 if let Some((buffer, cursor_buffer_position)) =
7439 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7440 {
7441 self.edit_prediction_settings =
7442 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7443 }
7444 }
7445 }
7446
7447 fn edit_prediction_settings_at_position(
7448 &self,
7449 buffer: &Entity<Buffer>,
7450 buffer_position: language::Anchor,
7451 cx: &App,
7452 ) -> EditPredictionSettings {
7453 if !self.mode.is_full()
7454 || !self.show_edit_predictions_override.unwrap_or(true)
7455 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7456 {
7457 return EditPredictionSettings::Disabled;
7458 }
7459
7460 let buffer = buffer.read(cx);
7461
7462 let file = buffer.file();
7463
7464 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7465 return EditPredictionSettings::Disabled;
7466 };
7467
7468 let by_provider = matches!(
7469 self.menu_edit_predictions_policy,
7470 MenuEditPredictionsPolicy::ByProvider
7471 );
7472
7473 let show_in_menu = by_provider
7474 && self
7475 .edit_prediction_provider
7476 .as_ref()
7477 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7478
7479 let preview_requires_modifier =
7480 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7481
7482 EditPredictionSettings::Enabled {
7483 show_in_menu,
7484 preview_requires_modifier,
7485 }
7486 }
7487
7488 fn should_show_edit_predictions(&self) -> bool {
7489 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7490 }
7491
7492 pub fn edit_prediction_preview_is_active(&self) -> bool {
7493 matches!(
7494 self.edit_prediction_preview,
7495 EditPredictionPreview::Active { .. }
7496 )
7497 }
7498
7499 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7500 let cursor = self.selections.newest_anchor().head();
7501 if let Some((buffer, cursor_position)) =
7502 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7503 {
7504 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7505 } else {
7506 false
7507 }
7508 }
7509
7510 pub fn supports_minimap(&self, cx: &App) -> bool {
7511 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7512 }
7513
7514 fn edit_predictions_enabled_in_buffer(
7515 &self,
7516 buffer: &Entity<Buffer>,
7517 buffer_position: language::Anchor,
7518 cx: &App,
7519 ) -> bool {
7520 maybe!({
7521 if self.read_only(cx) {
7522 return Some(false);
7523 }
7524 let provider = self.edit_prediction_provider()?;
7525 if !provider.is_enabled(buffer, buffer_position, cx) {
7526 return Some(false);
7527 }
7528 let buffer = buffer.read(cx);
7529 let Some(file) = buffer.file() else {
7530 return Some(true);
7531 };
7532 let settings = all_language_settings(Some(file), cx);
7533 Some(settings.edit_predictions_enabled_for_file(file, cx))
7534 })
7535 .unwrap_or(false)
7536 }
7537
7538 fn cycle_edit_prediction(
7539 &mut self,
7540 direction: Direction,
7541 window: &mut Window,
7542 cx: &mut Context<Self>,
7543 ) -> Option<()> {
7544 let provider = self.edit_prediction_provider()?;
7545 let cursor = self.selections.newest_anchor().head();
7546 let (buffer, cursor_buffer_position) =
7547 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7548 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7549 return None;
7550 }
7551
7552 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7553 self.update_visible_edit_prediction(window, cx);
7554
7555 Some(())
7556 }
7557
7558 pub fn show_edit_prediction(
7559 &mut self,
7560 _: &ShowEditPrediction,
7561 window: &mut Window,
7562 cx: &mut Context<Self>,
7563 ) {
7564 if !self.has_active_edit_prediction() {
7565 self.refresh_edit_prediction(false, true, window, cx);
7566 return;
7567 }
7568
7569 self.update_visible_edit_prediction(window, cx);
7570 }
7571
7572 pub fn display_cursor_names(
7573 &mut self,
7574 _: &DisplayCursorNames,
7575 window: &mut Window,
7576 cx: &mut Context<Self>,
7577 ) {
7578 self.show_cursor_names(window, cx);
7579 }
7580
7581 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7582 self.show_cursor_names = true;
7583 cx.notify();
7584 cx.spawn_in(window, async move |this, cx| {
7585 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7586 this.update(cx, |this, cx| {
7587 this.show_cursor_names = false;
7588 cx.notify()
7589 })
7590 .ok()
7591 })
7592 .detach();
7593 }
7594
7595 pub fn next_edit_prediction(
7596 &mut self,
7597 _: &NextEditPrediction,
7598 window: &mut Window,
7599 cx: &mut Context<Self>,
7600 ) {
7601 if self.has_active_edit_prediction() {
7602 self.cycle_edit_prediction(Direction::Next, window, cx);
7603 } else {
7604 let is_copilot_disabled = self
7605 .refresh_edit_prediction(false, true, window, cx)
7606 .is_none();
7607 if is_copilot_disabled {
7608 cx.propagate();
7609 }
7610 }
7611 }
7612
7613 pub fn previous_edit_prediction(
7614 &mut self,
7615 _: &PreviousEditPrediction,
7616 window: &mut Window,
7617 cx: &mut Context<Self>,
7618 ) {
7619 if self.has_active_edit_prediction() {
7620 self.cycle_edit_prediction(Direction::Prev, window, cx);
7621 } else {
7622 let is_copilot_disabled = self
7623 .refresh_edit_prediction(false, true, window, cx)
7624 .is_none();
7625 if is_copilot_disabled {
7626 cx.propagate();
7627 }
7628 }
7629 }
7630
7631 pub fn accept_partial_edit_prediction(
7632 &mut self,
7633 granularity: EditPredictionGranularity,
7634 window: &mut Window,
7635 cx: &mut Context<Self>,
7636 ) {
7637 if self.show_edit_predictions_in_menu() {
7638 self.hide_context_menu(window, cx);
7639 }
7640
7641 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7642 return;
7643 };
7644
7645 if !matches!(granularity, EditPredictionGranularity::Full) && self.selections.count() != 1 {
7646 return;
7647 }
7648
7649 match &active_edit_prediction.completion {
7650 EditPrediction::MoveWithin { target, .. } => {
7651 let target = *target;
7652
7653 if matches!(granularity, EditPredictionGranularity::Full) {
7654 if let Some(position_map) = &self.last_position_map {
7655 let target_row = target.to_display_point(&position_map.snapshot).row();
7656 let is_visible = position_map.visible_row_range.contains(&target_row);
7657
7658 if is_visible || !self.edit_prediction_requires_modifier() {
7659 self.unfold_ranges(&[target..target], true, false, cx);
7660 self.change_selections(
7661 SelectionEffects::scroll(Autoscroll::newest()),
7662 window,
7663 cx,
7664 |selections| {
7665 selections.select_anchor_ranges([target..target]);
7666 },
7667 );
7668 self.clear_row_highlights::<EditPredictionPreview>();
7669 self.edit_prediction_preview
7670 .set_previous_scroll_position(None);
7671 } else {
7672 // Highlight and request scroll
7673 self.edit_prediction_preview
7674 .set_previous_scroll_position(Some(
7675 position_map.snapshot.scroll_anchor,
7676 ));
7677 self.highlight_rows::<EditPredictionPreview>(
7678 target..target,
7679 cx.theme().colors().editor_highlighted_line_background,
7680 RowHighlightOptions {
7681 autoscroll: true,
7682 ..Default::default()
7683 },
7684 cx,
7685 );
7686 self.request_autoscroll(Autoscroll::fit(), cx);
7687 }
7688 }
7689 } else {
7690 self.change_selections(
7691 SelectionEffects::scroll(Autoscroll::newest()),
7692 window,
7693 cx,
7694 |selections| {
7695 selections.select_anchor_ranges([target..target]);
7696 },
7697 );
7698 }
7699 }
7700 EditPrediction::MoveOutside { snapshot, target } => {
7701 if let Some(workspace) = self.workspace() {
7702 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7703 .detach_and_log_err(cx);
7704 }
7705 }
7706 EditPrediction::Edit { edits, .. } => {
7707 self.report_edit_prediction_event(
7708 active_edit_prediction.completion_id.clone(),
7709 true,
7710 cx,
7711 );
7712
7713 match granularity {
7714 EditPredictionGranularity::Full => {
7715 if let Some(provider) = self.edit_prediction_provider() {
7716 provider.accept(cx);
7717 }
7718
7719 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7720 let snapshot = self.buffer.read(cx).snapshot(cx);
7721 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7722
7723 self.buffer.update(cx, |buffer, cx| {
7724 buffer.edit(edits.iter().cloned(), None, cx)
7725 });
7726
7727 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7728 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7729 });
7730
7731 let selections = self.selections.disjoint_anchors_arc();
7732 if let Some(transaction_id_now) =
7733 self.buffer.read(cx).last_transaction_id(cx)
7734 {
7735 if transaction_id_prev != Some(transaction_id_now) {
7736 self.selection_history
7737 .insert_transaction(transaction_id_now, selections);
7738 }
7739 }
7740
7741 self.update_visible_edit_prediction(window, cx);
7742 if self.active_edit_prediction.is_none() {
7743 self.refresh_edit_prediction(true, true, window, cx);
7744 }
7745 cx.notify();
7746 }
7747 _ => {
7748 let snapshot = self.buffer.read(cx).snapshot(cx);
7749 let cursor_offset = self
7750 .selections
7751 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7752 .head();
7753
7754 let insertion = edits.iter().find_map(|(range, text)| {
7755 let range = range.to_offset(&snapshot);
7756 if range.is_empty() && range.start == cursor_offset {
7757 Some(text)
7758 } else {
7759 None
7760 }
7761 });
7762
7763 if let Some(text) = insertion {
7764 let text_to_insert = match granularity {
7765 EditPredictionGranularity::Word => {
7766 let mut partial = text
7767 .chars()
7768 .by_ref()
7769 .take_while(|c| c.is_alphabetic())
7770 .collect::<String>();
7771 if partial.is_empty() {
7772 partial = text
7773 .chars()
7774 .by_ref()
7775 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7776 .collect::<String>();
7777 }
7778 partial
7779 }
7780 EditPredictionGranularity::Line => {
7781 if let Some(line) = text.split_inclusive('\n').next() {
7782 line.to_string()
7783 } else {
7784 text.to_string()
7785 }
7786 }
7787 EditPredictionGranularity::Full => unreachable!(),
7788 };
7789
7790 cx.emit(EditorEvent::InputHandled {
7791 utf16_range_to_replace: None,
7792 text: text_to_insert.clone().into(),
7793 });
7794
7795 self.insert_with_autoindent_mode(&text_to_insert, None, window, cx);
7796 self.refresh_edit_prediction(true, true, window, cx);
7797 cx.notify();
7798 } else {
7799 self.accept_partial_edit_prediction(
7800 EditPredictionGranularity::Full,
7801 window,
7802 cx,
7803 );
7804 }
7805 }
7806 }
7807 }
7808 }
7809
7810 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7811 }
7812
7813 pub fn accept_next_word_edit_prediction(
7814 &mut self,
7815 _: &AcceptNextWordEditPrediction,
7816 window: &mut Window,
7817 cx: &mut Context<Self>,
7818 ) {
7819 self.accept_partial_edit_prediction(EditPredictionGranularity::Word, window, cx);
7820 }
7821
7822 pub fn accept_next_line_edit_prediction(
7823 &mut self,
7824 _: &AcceptNextLineEditPrediction,
7825 window: &mut Window,
7826 cx: &mut Context<Self>,
7827 ) {
7828 self.accept_partial_edit_prediction(EditPredictionGranularity::Line, window, cx);
7829 }
7830
7831 pub fn accept_edit_prediction(
7832 &mut self,
7833 _: &AcceptEditPrediction,
7834 window: &mut Window,
7835 cx: &mut Context<Self>,
7836 ) {
7837 self.accept_partial_edit_prediction(EditPredictionGranularity::Full, window, cx);
7838 }
7839
7840 fn discard_edit_prediction(
7841 &mut self,
7842 should_report_edit_prediction_event: bool,
7843 cx: &mut Context<Self>,
7844 ) -> bool {
7845 if should_report_edit_prediction_event {
7846 let completion_id = self
7847 .active_edit_prediction
7848 .as_ref()
7849 .and_then(|active_completion| active_completion.completion_id.clone());
7850
7851 self.report_edit_prediction_event(completion_id, false, cx);
7852 }
7853
7854 if let Some(provider) = self.edit_prediction_provider() {
7855 provider.discard(cx);
7856 }
7857
7858 self.take_active_edit_prediction(cx)
7859 }
7860
7861 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7862 let Some(provider) = self.edit_prediction_provider() else {
7863 return;
7864 };
7865
7866 let Some((_, buffer, _)) = self
7867 .buffer
7868 .read(cx)
7869 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7870 else {
7871 return;
7872 };
7873
7874 let extension = buffer
7875 .read(cx)
7876 .file()
7877 .and_then(|file| Some(file.path().extension()?.to_string()));
7878
7879 let event_type = match accepted {
7880 true => "Edit Prediction Accepted",
7881 false => "Edit Prediction Discarded",
7882 };
7883 telemetry::event!(
7884 event_type,
7885 provider = provider.name(),
7886 prediction_id = id,
7887 suggestion_accepted = accepted,
7888 file_extension = extension,
7889 );
7890 }
7891
7892 fn open_editor_at_anchor(
7893 snapshot: &language::BufferSnapshot,
7894 target: language::Anchor,
7895 workspace: &Entity<Workspace>,
7896 window: &mut Window,
7897 cx: &mut App,
7898 ) -> Task<Result<()>> {
7899 workspace.update(cx, |workspace, cx| {
7900 let path = snapshot.file().map(|file| file.full_path(cx));
7901 let Some(path) =
7902 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7903 else {
7904 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7905 };
7906 let target = text::ToPoint::to_point(&target, snapshot);
7907 let item = workspace.open_path(path, None, true, window, cx);
7908 window.spawn(cx, async move |cx| {
7909 let Some(editor) = item.await?.downcast::<Editor>() else {
7910 return Ok(());
7911 };
7912 editor
7913 .update_in(cx, |editor, window, cx| {
7914 editor.go_to_singleton_buffer_point(target, window, cx);
7915 })
7916 .ok();
7917 anyhow::Ok(())
7918 })
7919 })
7920 }
7921
7922 pub fn has_active_edit_prediction(&self) -> bool {
7923 self.active_edit_prediction.is_some()
7924 }
7925
7926 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7927 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7928 return false;
7929 };
7930
7931 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7932 self.clear_highlights::<EditPredictionHighlight>(cx);
7933 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7934 true
7935 }
7936
7937 /// Returns true when we're displaying the edit prediction popover below the cursor
7938 /// like we are not previewing and the LSP autocomplete menu is visible
7939 /// or we are in `when_holding_modifier` mode.
7940 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7941 if self.edit_prediction_preview_is_active()
7942 || !self.show_edit_predictions_in_menu()
7943 || !self.edit_predictions_enabled()
7944 {
7945 return false;
7946 }
7947
7948 if self.has_visible_completions_menu() {
7949 return true;
7950 }
7951
7952 has_completion && self.edit_prediction_requires_modifier()
7953 }
7954
7955 fn handle_modifiers_changed(
7956 &mut self,
7957 modifiers: Modifiers,
7958 position_map: &PositionMap,
7959 window: &mut Window,
7960 cx: &mut Context<Self>,
7961 ) {
7962 // Ensure that the edit prediction preview is updated, even when not
7963 // enabled, if there's an active edit prediction preview.
7964 if self.show_edit_predictions_in_menu()
7965 || matches!(
7966 self.edit_prediction_preview,
7967 EditPredictionPreview::Active { .. }
7968 )
7969 {
7970 self.update_edit_prediction_preview(&modifiers, window, cx);
7971 }
7972
7973 self.update_selection_mode(&modifiers, position_map, window, cx);
7974
7975 let mouse_position = window.mouse_position();
7976 if !position_map.text_hitbox.is_hovered(window) {
7977 return;
7978 }
7979
7980 self.update_hovered_link(
7981 position_map.point_for_position(mouse_position),
7982 &position_map.snapshot,
7983 modifiers,
7984 window,
7985 cx,
7986 )
7987 }
7988
7989 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7990 match EditorSettings::get_global(cx).multi_cursor_modifier {
7991 MultiCursorModifier::Alt => modifiers.secondary(),
7992 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7993 }
7994 }
7995
7996 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7997 match EditorSettings::get_global(cx).multi_cursor_modifier {
7998 MultiCursorModifier::Alt => modifiers.alt,
7999 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
8000 }
8001 }
8002
8003 fn columnar_selection_mode(
8004 modifiers: &Modifiers,
8005 cx: &mut Context<Self>,
8006 ) -> Option<ColumnarMode> {
8007 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
8008 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
8009 Some(ColumnarMode::FromMouse)
8010 } else if Self::is_alt_pressed(modifiers, cx) {
8011 Some(ColumnarMode::FromSelection)
8012 } else {
8013 None
8014 }
8015 } else {
8016 None
8017 }
8018 }
8019
8020 fn update_selection_mode(
8021 &mut self,
8022 modifiers: &Modifiers,
8023 position_map: &PositionMap,
8024 window: &mut Window,
8025 cx: &mut Context<Self>,
8026 ) {
8027 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
8028 return;
8029 };
8030 if self.selections.pending_anchor().is_none() {
8031 return;
8032 }
8033
8034 let mouse_position = window.mouse_position();
8035 let point_for_position = position_map.point_for_position(mouse_position);
8036 let position = point_for_position.previous_valid;
8037
8038 self.select(
8039 SelectPhase::BeginColumnar {
8040 position,
8041 reset: false,
8042 mode,
8043 goal_column: point_for_position.exact_unclipped.column(),
8044 },
8045 window,
8046 cx,
8047 );
8048 }
8049
8050 fn update_edit_prediction_preview(
8051 &mut self,
8052 modifiers: &Modifiers,
8053 window: &mut Window,
8054 cx: &mut Context<Self>,
8055 ) {
8056 let mut modifiers_held = false;
8057
8058 // Check bindings for all granularities.
8059 // If the user holds the key for Word, Line, or Full, we want to show the preview.
8060 let granularities = [
8061 EditPredictionGranularity::Full,
8062 EditPredictionGranularity::Line,
8063 EditPredictionGranularity::Word,
8064 ];
8065
8066 for granularity in granularities {
8067 if let Some(keystroke) = self
8068 .accept_edit_prediction_keybind(granularity, window, cx)
8069 .keystroke()
8070 {
8071 modifiers_held = modifiers_held
8072 || (keystroke.modifiers() == modifiers && keystroke.modifiers().modified());
8073 }
8074 }
8075
8076 if modifiers_held {
8077 if matches!(
8078 self.edit_prediction_preview,
8079 EditPredictionPreview::Inactive { .. }
8080 ) {
8081 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8082 provider.provider.did_show(cx)
8083 }
8084
8085 self.edit_prediction_preview = EditPredictionPreview::Active {
8086 previous_scroll_position: None,
8087 since: Instant::now(),
8088 };
8089
8090 self.update_visible_edit_prediction(window, cx);
8091 cx.notify();
8092 }
8093 } else if let EditPredictionPreview::Active {
8094 previous_scroll_position,
8095 since,
8096 } = self.edit_prediction_preview
8097 {
8098 if let (Some(previous_scroll_position), Some(position_map)) =
8099 (previous_scroll_position, self.last_position_map.as_ref())
8100 {
8101 self.set_scroll_position(
8102 previous_scroll_position
8103 .scroll_position(&position_map.snapshot.display_snapshot),
8104 window,
8105 cx,
8106 );
8107 }
8108
8109 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8110 released_too_fast: since.elapsed() < Duration::from_millis(200),
8111 };
8112 self.clear_row_highlights::<EditPredictionPreview>();
8113 self.update_visible_edit_prediction(window, cx);
8114 cx.notify();
8115 }
8116 }
8117
8118 fn update_visible_edit_prediction(
8119 &mut self,
8120 _window: &mut Window,
8121 cx: &mut Context<Self>,
8122 ) -> Option<()> {
8123 if DisableAiSettings::get_global(cx).disable_ai {
8124 return None;
8125 }
8126
8127 if self.ime_transaction.is_some() {
8128 self.discard_edit_prediction(false, cx);
8129 return None;
8130 }
8131
8132 let selection = self.selections.newest_anchor();
8133 let cursor = selection.head();
8134 let multibuffer = self.buffer.read(cx).snapshot(cx);
8135 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8136 let excerpt_id = cursor.excerpt_id;
8137
8138 let show_in_menu = self.show_edit_predictions_in_menu();
8139 let completions_menu_has_precedence = !show_in_menu
8140 && (self.context_menu.borrow().is_some()
8141 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8142
8143 if completions_menu_has_precedence
8144 || !offset_selection.is_empty()
8145 || self
8146 .active_edit_prediction
8147 .as_ref()
8148 .is_some_and(|completion| {
8149 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8150 return false;
8151 };
8152 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8153 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8154 !invalidation_range.contains(&offset_selection.head())
8155 })
8156 {
8157 self.discard_edit_prediction(false, cx);
8158 return None;
8159 }
8160
8161 self.take_active_edit_prediction(cx);
8162 let Some(provider) = self.edit_prediction_provider() else {
8163 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8164 return None;
8165 };
8166
8167 let (buffer, cursor_buffer_position) =
8168 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8169
8170 self.edit_prediction_settings =
8171 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8172
8173 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8174
8175 if self.edit_prediction_indent_conflict {
8176 let cursor_point = cursor.to_point(&multibuffer);
8177 let mut suggested_indent = None;
8178 multibuffer.suggested_indents_callback(
8179 cursor_point.row..cursor_point.row + 1,
8180 |_, indent| {
8181 suggested_indent = Some(indent);
8182 ControlFlow::Break(())
8183 },
8184 cx,
8185 );
8186
8187 if let Some(indent) = suggested_indent
8188 && indent.len == cursor_point.column
8189 {
8190 self.edit_prediction_indent_conflict = false;
8191 }
8192 }
8193
8194 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8195
8196 let (completion_id, edits, edit_preview) = match edit_prediction {
8197 edit_prediction_types::EditPrediction::Local {
8198 id,
8199 edits,
8200 edit_preview,
8201 } => (id, edits, edit_preview),
8202 edit_prediction_types::EditPrediction::Jump {
8203 id,
8204 snapshot,
8205 target,
8206 } => {
8207 self.stale_edit_prediction_in_menu = None;
8208 self.active_edit_prediction = Some(EditPredictionState {
8209 inlay_ids: vec![],
8210 completion: EditPrediction::MoveOutside { snapshot, target },
8211 completion_id: id,
8212 invalidation_range: None,
8213 });
8214 cx.notify();
8215 return Some(());
8216 }
8217 };
8218
8219 let edits = edits
8220 .into_iter()
8221 .flat_map(|(range, new_text)| {
8222 Some((
8223 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8224 new_text,
8225 ))
8226 })
8227 .collect::<Vec<_>>();
8228 if edits.is_empty() {
8229 return None;
8230 }
8231
8232 let first_edit_start = edits.first().unwrap().0.start;
8233 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8234 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8235
8236 let last_edit_end = edits.last().unwrap().0.end;
8237 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8238 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8239
8240 let cursor_row = cursor.to_point(&multibuffer).row;
8241
8242 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8243
8244 let mut inlay_ids = Vec::new();
8245 let invalidation_row_range;
8246 let move_invalidation_row_range = if cursor_row < edit_start_row {
8247 Some(cursor_row..edit_end_row)
8248 } else if cursor_row > edit_end_row {
8249 Some(edit_start_row..cursor_row)
8250 } else {
8251 None
8252 };
8253 let supports_jump = self
8254 .edit_prediction_provider
8255 .as_ref()
8256 .map(|provider| provider.provider.supports_jump_to_edit())
8257 .unwrap_or(true);
8258
8259 let is_move = supports_jump
8260 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8261 let completion = if is_move {
8262 invalidation_row_range =
8263 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8264 let target = first_edit_start;
8265 EditPrediction::MoveWithin { target, snapshot }
8266 } else {
8267 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8268 && !self.edit_predictions_hidden_for_vim_mode;
8269
8270 if show_completions_in_buffer {
8271 if let Some(provider) = &self.edit_prediction_provider {
8272 provider.provider.did_show(cx);
8273 }
8274 if edits
8275 .iter()
8276 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8277 {
8278 let mut inlays = Vec::new();
8279 for (range, new_text) in &edits {
8280 let inlay = Inlay::edit_prediction(
8281 post_inc(&mut self.next_inlay_id),
8282 range.start,
8283 new_text.as_ref(),
8284 );
8285 inlay_ids.push(inlay.id);
8286 inlays.push(inlay);
8287 }
8288
8289 self.splice_inlays(&[], inlays, cx);
8290 } else {
8291 let background_color = cx.theme().status().deleted_background;
8292 self.highlight_text::<EditPredictionHighlight>(
8293 edits.iter().map(|(range, _)| range.clone()).collect(),
8294 HighlightStyle {
8295 background_color: Some(background_color),
8296 ..Default::default()
8297 },
8298 cx,
8299 );
8300 }
8301 }
8302
8303 invalidation_row_range = edit_start_row..edit_end_row;
8304
8305 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8306 if provider.show_tab_accept_marker() {
8307 EditDisplayMode::TabAccept
8308 } else {
8309 EditDisplayMode::Inline
8310 }
8311 } else {
8312 EditDisplayMode::DiffPopover
8313 };
8314
8315 EditPrediction::Edit {
8316 edits,
8317 edit_preview,
8318 display_mode,
8319 snapshot,
8320 }
8321 };
8322
8323 let invalidation_range = multibuffer
8324 .anchor_before(Point::new(invalidation_row_range.start, 0))
8325 ..multibuffer.anchor_after(Point::new(
8326 invalidation_row_range.end,
8327 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8328 ));
8329
8330 self.stale_edit_prediction_in_menu = None;
8331 self.active_edit_prediction = Some(EditPredictionState {
8332 inlay_ids,
8333 completion,
8334 completion_id,
8335 invalidation_range: Some(invalidation_range),
8336 });
8337
8338 cx.notify();
8339
8340 Some(())
8341 }
8342
8343 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8344 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8345 }
8346
8347 fn clear_tasks(&mut self) {
8348 self.tasks.clear()
8349 }
8350
8351 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8352 if self.tasks.insert(key, value).is_some() {
8353 // This case should hopefully be rare, but just in case...
8354 log::error!(
8355 "multiple different run targets found on a single line, only the last target will be rendered"
8356 )
8357 }
8358 }
8359
8360 /// Get all display points of breakpoints that will be rendered within editor
8361 ///
8362 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8363 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8364 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8365 fn active_breakpoints(
8366 &self,
8367 range: Range<DisplayRow>,
8368 window: &mut Window,
8369 cx: &mut Context<Self>,
8370 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8371 let mut breakpoint_display_points = HashMap::default();
8372
8373 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8374 return breakpoint_display_points;
8375 };
8376
8377 let snapshot = self.snapshot(window, cx);
8378
8379 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8380 let Some(project) = self.project() else {
8381 return breakpoint_display_points;
8382 };
8383
8384 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8385 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8386
8387 for (buffer_snapshot, range, excerpt_id) in
8388 multi_buffer_snapshot.range_to_buffer_ranges(range)
8389 {
8390 let Some(buffer) = project
8391 .read(cx)
8392 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8393 else {
8394 continue;
8395 };
8396 let breakpoints = breakpoint_store.read(cx).breakpoints(
8397 &buffer,
8398 Some(
8399 buffer_snapshot.anchor_before(range.start)
8400 ..buffer_snapshot.anchor_after(range.end),
8401 ),
8402 buffer_snapshot,
8403 cx,
8404 );
8405 for (breakpoint, state) in breakpoints {
8406 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8407 let position = multi_buffer_anchor
8408 .to_point(&multi_buffer_snapshot)
8409 .to_display_point(&snapshot);
8410
8411 breakpoint_display_points.insert(
8412 position.row(),
8413 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8414 );
8415 }
8416 }
8417
8418 breakpoint_display_points
8419 }
8420
8421 fn breakpoint_context_menu(
8422 &self,
8423 anchor: Anchor,
8424 window: &mut Window,
8425 cx: &mut Context<Self>,
8426 ) -> Entity<ui::ContextMenu> {
8427 let weak_editor = cx.weak_entity();
8428 let focus_handle = self.focus_handle(cx);
8429
8430 let row = self
8431 .buffer
8432 .read(cx)
8433 .snapshot(cx)
8434 .summary_for_anchor::<Point>(&anchor)
8435 .row;
8436
8437 let breakpoint = self
8438 .breakpoint_at_row(row, window, cx)
8439 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8440
8441 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8442 "Edit Log Breakpoint"
8443 } else {
8444 "Set Log Breakpoint"
8445 };
8446
8447 let condition_breakpoint_msg = if breakpoint
8448 .as_ref()
8449 .is_some_and(|bp| bp.1.condition.is_some())
8450 {
8451 "Edit Condition Breakpoint"
8452 } else {
8453 "Set Condition Breakpoint"
8454 };
8455
8456 let hit_condition_breakpoint_msg = if breakpoint
8457 .as_ref()
8458 .is_some_and(|bp| bp.1.hit_condition.is_some())
8459 {
8460 "Edit Hit Condition Breakpoint"
8461 } else {
8462 "Set Hit Condition Breakpoint"
8463 };
8464
8465 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8466 "Unset Breakpoint"
8467 } else {
8468 "Set Breakpoint"
8469 };
8470
8471 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8472
8473 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8474 BreakpointState::Enabled => Some("Disable"),
8475 BreakpointState::Disabled => Some("Enable"),
8476 });
8477
8478 let (anchor, breakpoint) =
8479 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8480
8481 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8482 menu.on_blur_subscription(Subscription::new(|| {}))
8483 .context(focus_handle)
8484 .when(run_to_cursor, |this| {
8485 let weak_editor = weak_editor.clone();
8486 this.entry("Run to cursor", None, move |window, cx| {
8487 weak_editor
8488 .update(cx, |editor, cx| {
8489 editor.change_selections(
8490 SelectionEffects::no_scroll(),
8491 window,
8492 cx,
8493 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8494 );
8495 })
8496 .ok();
8497
8498 window.dispatch_action(Box::new(RunToCursor), cx);
8499 })
8500 .separator()
8501 })
8502 .when_some(toggle_state_msg, |this, msg| {
8503 this.entry(msg, None, {
8504 let weak_editor = weak_editor.clone();
8505 let breakpoint = breakpoint.clone();
8506 move |_window, cx| {
8507 weak_editor
8508 .update(cx, |this, cx| {
8509 this.edit_breakpoint_at_anchor(
8510 anchor,
8511 breakpoint.as_ref().clone(),
8512 BreakpointEditAction::InvertState,
8513 cx,
8514 );
8515 })
8516 .log_err();
8517 }
8518 })
8519 })
8520 .entry(set_breakpoint_msg, None, {
8521 let weak_editor = weak_editor.clone();
8522 let breakpoint = breakpoint.clone();
8523 move |_window, cx| {
8524 weak_editor
8525 .update(cx, |this, cx| {
8526 this.edit_breakpoint_at_anchor(
8527 anchor,
8528 breakpoint.as_ref().clone(),
8529 BreakpointEditAction::Toggle,
8530 cx,
8531 );
8532 })
8533 .log_err();
8534 }
8535 })
8536 .entry(log_breakpoint_msg, None, {
8537 let breakpoint = breakpoint.clone();
8538 let weak_editor = weak_editor.clone();
8539 move |window, cx| {
8540 weak_editor
8541 .update(cx, |this, cx| {
8542 this.add_edit_breakpoint_block(
8543 anchor,
8544 breakpoint.as_ref(),
8545 BreakpointPromptEditAction::Log,
8546 window,
8547 cx,
8548 );
8549 })
8550 .log_err();
8551 }
8552 })
8553 .entry(condition_breakpoint_msg, None, {
8554 let breakpoint = breakpoint.clone();
8555 let weak_editor = weak_editor.clone();
8556 move |window, cx| {
8557 weak_editor
8558 .update(cx, |this, cx| {
8559 this.add_edit_breakpoint_block(
8560 anchor,
8561 breakpoint.as_ref(),
8562 BreakpointPromptEditAction::Condition,
8563 window,
8564 cx,
8565 );
8566 })
8567 .log_err();
8568 }
8569 })
8570 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8571 weak_editor
8572 .update(cx, |this, cx| {
8573 this.add_edit_breakpoint_block(
8574 anchor,
8575 breakpoint.as_ref(),
8576 BreakpointPromptEditAction::HitCondition,
8577 window,
8578 cx,
8579 );
8580 })
8581 .log_err();
8582 })
8583 })
8584 }
8585
8586 fn render_breakpoint(
8587 &self,
8588 position: Anchor,
8589 row: DisplayRow,
8590 breakpoint: &Breakpoint,
8591 state: Option<BreakpointSessionState>,
8592 cx: &mut Context<Self>,
8593 ) -> IconButton {
8594 let is_rejected = state.is_some_and(|s| !s.verified);
8595 // Is it a breakpoint that shows up when hovering over gutter?
8596 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8597 (false, false),
8598 |PhantomBreakpointIndicator {
8599 is_active,
8600 display_row,
8601 collides_with_existing_breakpoint,
8602 }| {
8603 (
8604 is_active && display_row == row,
8605 collides_with_existing_breakpoint,
8606 )
8607 },
8608 );
8609
8610 let (color, icon) = {
8611 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8612 (false, false) => ui::IconName::DebugBreakpoint,
8613 (true, false) => ui::IconName::DebugLogBreakpoint,
8614 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8615 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8616 };
8617
8618 let color = cx.theme().colors();
8619
8620 let color = if is_phantom {
8621 if collides_with_existing {
8622 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8623 } else {
8624 Color::Hint
8625 }
8626 } else if is_rejected {
8627 Color::Disabled
8628 } else {
8629 Color::Debugger
8630 };
8631
8632 (color, icon)
8633 };
8634
8635 let breakpoint = Arc::from(breakpoint.clone());
8636
8637 let alt_as_text = gpui::Keystroke {
8638 modifiers: Modifiers::secondary_key(),
8639 ..Default::default()
8640 };
8641 let primary_action_text = if breakpoint.is_disabled() {
8642 "Enable breakpoint"
8643 } else if is_phantom && !collides_with_existing {
8644 "Set breakpoint"
8645 } else {
8646 "Unset breakpoint"
8647 };
8648 let focus_handle = self.focus_handle.clone();
8649
8650 let meta = if is_rejected {
8651 SharedString::from("No executable code is associated with this line.")
8652 } else if collides_with_existing && !breakpoint.is_disabled() {
8653 SharedString::from(format!(
8654 "{alt_as_text}-click to disable,\nright-click for more options."
8655 ))
8656 } else {
8657 SharedString::from("Right-click for more options.")
8658 };
8659 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8660 .icon_size(IconSize::XSmall)
8661 .size(ui::ButtonSize::None)
8662 .when(is_rejected, |this| {
8663 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8664 })
8665 .icon_color(color)
8666 .style(ButtonStyle::Transparent)
8667 .on_click(cx.listener({
8668 move |editor, event: &ClickEvent, window, cx| {
8669 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8670 BreakpointEditAction::InvertState
8671 } else {
8672 BreakpointEditAction::Toggle
8673 };
8674
8675 window.focus(&editor.focus_handle(cx));
8676 editor.edit_breakpoint_at_anchor(
8677 position,
8678 breakpoint.as_ref().clone(),
8679 edit_action,
8680 cx,
8681 );
8682 }
8683 }))
8684 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8685 editor.set_breakpoint_context_menu(
8686 row,
8687 Some(position),
8688 event.position(),
8689 window,
8690 cx,
8691 );
8692 }))
8693 .tooltip(move |_window, cx| {
8694 Tooltip::with_meta_in(
8695 primary_action_text,
8696 Some(&ToggleBreakpoint),
8697 meta.clone(),
8698 &focus_handle,
8699 cx,
8700 )
8701 })
8702 }
8703
8704 fn build_tasks_context(
8705 project: &Entity<Project>,
8706 buffer: &Entity<Buffer>,
8707 buffer_row: u32,
8708 tasks: &Arc<RunnableTasks>,
8709 cx: &mut Context<Self>,
8710 ) -> Task<Option<task::TaskContext>> {
8711 let position = Point::new(buffer_row, tasks.column);
8712 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8713 let location = Location {
8714 buffer: buffer.clone(),
8715 range: range_start..range_start,
8716 };
8717 // Fill in the environmental variables from the tree-sitter captures
8718 let mut captured_task_variables = TaskVariables::default();
8719 for (capture_name, value) in tasks.extra_variables.clone() {
8720 captured_task_variables.insert(
8721 task::VariableName::Custom(capture_name.into()),
8722 value.clone(),
8723 );
8724 }
8725 project.update(cx, |project, cx| {
8726 project.task_store().update(cx, |task_store, cx| {
8727 task_store.task_context_for_location(captured_task_variables, location, cx)
8728 })
8729 })
8730 }
8731
8732 pub fn spawn_nearest_task(
8733 &mut self,
8734 action: &SpawnNearestTask,
8735 window: &mut Window,
8736 cx: &mut Context<Self>,
8737 ) {
8738 let Some((workspace, _)) = self.workspace.clone() else {
8739 return;
8740 };
8741 let Some(project) = self.project.clone() else {
8742 return;
8743 };
8744
8745 // Try to find a closest, enclosing node using tree-sitter that has a task
8746 let Some((buffer, buffer_row, tasks)) = self
8747 .find_enclosing_node_task(cx)
8748 // Or find the task that's closest in row-distance.
8749 .or_else(|| self.find_closest_task(cx))
8750 else {
8751 return;
8752 };
8753
8754 let reveal_strategy = action.reveal;
8755 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8756 cx.spawn_in(window, async move |_, cx| {
8757 let context = task_context.await?;
8758 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8759
8760 let resolved = &mut resolved_task.resolved;
8761 resolved.reveal = reveal_strategy;
8762
8763 workspace
8764 .update_in(cx, |workspace, window, cx| {
8765 workspace.schedule_resolved_task(
8766 task_source_kind,
8767 resolved_task,
8768 false,
8769 window,
8770 cx,
8771 );
8772 })
8773 .ok()
8774 })
8775 .detach();
8776 }
8777
8778 fn find_closest_task(
8779 &mut self,
8780 cx: &mut Context<Self>,
8781 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8782 let cursor_row = self
8783 .selections
8784 .newest_adjusted(&self.display_snapshot(cx))
8785 .head()
8786 .row;
8787
8788 let ((buffer_id, row), tasks) = self
8789 .tasks
8790 .iter()
8791 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8792
8793 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8794 let tasks = Arc::new(tasks.to_owned());
8795 Some((buffer, *row, tasks))
8796 }
8797
8798 fn find_enclosing_node_task(
8799 &mut self,
8800 cx: &mut Context<Self>,
8801 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8802 let snapshot = self.buffer.read(cx).snapshot(cx);
8803 let offset = self
8804 .selections
8805 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8806 .head();
8807 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8808 let offset = excerpt.map_offset_to_buffer(offset);
8809 let buffer_id = excerpt.buffer().remote_id();
8810
8811 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8812 let mut cursor = layer.node().walk();
8813
8814 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8815 if cursor.node().end_byte() == offset.0 {
8816 cursor.goto_next_sibling();
8817 }
8818 }
8819
8820 // Ascend to the smallest ancestor that contains the range and has a task.
8821 loop {
8822 let node = cursor.node();
8823 let node_range = node.byte_range();
8824 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8825
8826 // Check if this node contains our offset
8827 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8828 // If it contains offset, check for task
8829 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8830 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8831 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8832 }
8833 }
8834
8835 if !cursor.goto_parent() {
8836 break;
8837 }
8838 }
8839 None
8840 }
8841
8842 fn render_run_indicator(
8843 &self,
8844 _style: &EditorStyle,
8845 is_active: bool,
8846 row: DisplayRow,
8847 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8848 cx: &mut Context<Self>,
8849 ) -> IconButton {
8850 let color = Color::Muted;
8851 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8852
8853 IconButton::new(
8854 ("run_indicator", row.0 as usize),
8855 ui::IconName::PlayOutlined,
8856 )
8857 .shape(ui::IconButtonShape::Square)
8858 .icon_size(IconSize::XSmall)
8859 .icon_color(color)
8860 .toggle_state(is_active)
8861 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8862 let quick_launch = match e {
8863 ClickEvent::Keyboard(_) => true,
8864 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8865 };
8866
8867 window.focus(&editor.focus_handle(cx));
8868 editor.toggle_code_actions(
8869 &ToggleCodeActions {
8870 deployed_from: Some(CodeActionSource::RunMenu(row)),
8871 quick_launch,
8872 },
8873 window,
8874 cx,
8875 );
8876 }))
8877 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8878 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8879 }))
8880 }
8881
8882 pub fn context_menu_visible(&self) -> bool {
8883 !self.edit_prediction_preview_is_active()
8884 && self
8885 .context_menu
8886 .borrow()
8887 .as_ref()
8888 .is_some_and(|menu| menu.visible())
8889 }
8890
8891 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8892 self.context_menu
8893 .borrow()
8894 .as_ref()
8895 .map(|menu| menu.origin())
8896 }
8897
8898 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8899 self.context_menu_options = Some(options);
8900 }
8901
8902 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8903 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8904
8905 fn render_edit_prediction_popover(
8906 &mut self,
8907 text_bounds: &Bounds<Pixels>,
8908 content_origin: gpui::Point<Pixels>,
8909 right_margin: Pixels,
8910 editor_snapshot: &EditorSnapshot,
8911 visible_row_range: Range<DisplayRow>,
8912 scroll_top: ScrollOffset,
8913 scroll_bottom: ScrollOffset,
8914 line_layouts: &[LineWithInvisibles],
8915 line_height: Pixels,
8916 scroll_position: gpui::Point<ScrollOffset>,
8917 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8918 newest_selection_head: Option<DisplayPoint>,
8919 editor_width: Pixels,
8920 style: &EditorStyle,
8921 window: &mut Window,
8922 cx: &mut App,
8923 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8924 if self.mode().is_minimap() {
8925 return None;
8926 }
8927 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8928
8929 if self.edit_prediction_visible_in_cursor_popover(true) {
8930 return None;
8931 }
8932
8933 match &active_edit_prediction.completion {
8934 EditPrediction::MoveWithin { target, .. } => {
8935 let target_display_point = target.to_display_point(editor_snapshot);
8936
8937 if self.edit_prediction_requires_modifier() {
8938 if !self.edit_prediction_preview_is_active() {
8939 return None;
8940 }
8941
8942 self.render_edit_prediction_modifier_jump_popover(
8943 text_bounds,
8944 content_origin,
8945 visible_row_range,
8946 line_layouts,
8947 line_height,
8948 scroll_pixel_position,
8949 newest_selection_head,
8950 target_display_point,
8951 window,
8952 cx,
8953 )
8954 } else {
8955 self.render_edit_prediction_eager_jump_popover(
8956 text_bounds,
8957 content_origin,
8958 editor_snapshot,
8959 visible_row_range,
8960 scroll_top,
8961 scroll_bottom,
8962 line_height,
8963 scroll_pixel_position,
8964 target_display_point,
8965 editor_width,
8966 window,
8967 cx,
8968 )
8969 }
8970 }
8971 EditPrediction::Edit {
8972 display_mode: EditDisplayMode::Inline,
8973 ..
8974 } => None,
8975 EditPrediction::Edit {
8976 display_mode: EditDisplayMode::TabAccept,
8977 edits,
8978 ..
8979 } => {
8980 let range = &edits.first()?.0;
8981 let target_display_point = range.end.to_display_point(editor_snapshot);
8982
8983 self.render_edit_prediction_end_of_line_popover(
8984 "Accept",
8985 editor_snapshot,
8986 visible_row_range,
8987 target_display_point,
8988 line_height,
8989 scroll_pixel_position,
8990 content_origin,
8991 editor_width,
8992 window,
8993 cx,
8994 )
8995 }
8996 EditPrediction::Edit {
8997 edits,
8998 edit_preview,
8999 display_mode: EditDisplayMode::DiffPopover,
9000 snapshot,
9001 } => self.render_edit_prediction_diff_popover(
9002 text_bounds,
9003 content_origin,
9004 right_margin,
9005 editor_snapshot,
9006 visible_row_range,
9007 line_layouts,
9008 line_height,
9009 scroll_position,
9010 scroll_pixel_position,
9011 newest_selection_head,
9012 editor_width,
9013 style,
9014 edits,
9015 edit_preview,
9016 snapshot,
9017 window,
9018 cx,
9019 ),
9020 EditPrediction::MoveOutside { snapshot, .. } => {
9021 let mut element = self
9022 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
9023 .into_any();
9024
9025 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9026 let origin_x = text_bounds.size.width - size.width - px(30.);
9027 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
9028 element.prepaint_at(origin, window, cx);
9029
9030 Some((element, origin))
9031 }
9032 }
9033 }
9034
9035 fn render_edit_prediction_modifier_jump_popover(
9036 &mut self,
9037 text_bounds: &Bounds<Pixels>,
9038 content_origin: gpui::Point<Pixels>,
9039 visible_row_range: Range<DisplayRow>,
9040 line_layouts: &[LineWithInvisibles],
9041 line_height: Pixels,
9042 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9043 newest_selection_head: Option<DisplayPoint>,
9044 target_display_point: DisplayPoint,
9045 window: &mut Window,
9046 cx: &mut App,
9047 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9048 let scrolled_content_origin =
9049 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9050
9051 const SCROLL_PADDING_Y: Pixels = px(12.);
9052
9053 if target_display_point.row() < visible_row_range.start {
9054 return self.render_edit_prediction_scroll_popover(
9055 |_| SCROLL_PADDING_Y,
9056 IconName::ArrowUp,
9057 visible_row_range,
9058 line_layouts,
9059 newest_selection_head,
9060 scrolled_content_origin,
9061 window,
9062 cx,
9063 );
9064 } else if target_display_point.row() >= visible_row_range.end {
9065 return self.render_edit_prediction_scroll_popover(
9066 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9067 IconName::ArrowDown,
9068 visible_row_range,
9069 line_layouts,
9070 newest_selection_head,
9071 scrolled_content_origin,
9072 window,
9073 cx,
9074 );
9075 }
9076
9077 const POLE_WIDTH: Pixels = px(2.);
9078
9079 let line_layout =
9080 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9081 let target_column = target_display_point.column() as usize;
9082
9083 let target_x = line_layout.x_for_index(target_column);
9084 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9085 - scroll_pixel_position.y;
9086
9087 let flag_on_right = target_x < text_bounds.size.width / 2.;
9088
9089 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9090 border_color.l += 0.001;
9091
9092 let mut element = v_flex()
9093 .items_end()
9094 .when(flag_on_right, |el| el.items_start())
9095 .child(if flag_on_right {
9096 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9097 .rounded_bl(px(0.))
9098 .rounded_tl(px(0.))
9099 .border_l_2()
9100 .border_color(border_color)
9101 } else {
9102 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9103 .rounded_br(px(0.))
9104 .rounded_tr(px(0.))
9105 .border_r_2()
9106 .border_color(border_color)
9107 })
9108 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9109 .into_any();
9110
9111 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9112
9113 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9114 - point(
9115 if flag_on_right {
9116 POLE_WIDTH
9117 } else {
9118 size.width - POLE_WIDTH
9119 },
9120 size.height - line_height,
9121 );
9122
9123 origin.x = origin.x.max(content_origin.x);
9124
9125 element.prepaint_at(origin, window, cx);
9126
9127 Some((element, origin))
9128 }
9129
9130 fn render_edit_prediction_scroll_popover(
9131 &mut self,
9132 to_y: impl Fn(Size<Pixels>) -> Pixels,
9133 scroll_icon: IconName,
9134 visible_row_range: Range<DisplayRow>,
9135 line_layouts: &[LineWithInvisibles],
9136 newest_selection_head: Option<DisplayPoint>,
9137 scrolled_content_origin: gpui::Point<Pixels>,
9138 window: &mut Window,
9139 cx: &mut App,
9140 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9141 let mut element = self
9142 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9143 .into_any();
9144
9145 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9146
9147 let cursor = newest_selection_head?;
9148 let cursor_row_layout =
9149 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9150 let cursor_column = cursor.column() as usize;
9151
9152 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9153
9154 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9155
9156 element.prepaint_at(origin, window, cx);
9157 Some((element, origin))
9158 }
9159
9160 fn render_edit_prediction_eager_jump_popover(
9161 &mut self,
9162 text_bounds: &Bounds<Pixels>,
9163 content_origin: gpui::Point<Pixels>,
9164 editor_snapshot: &EditorSnapshot,
9165 visible_row_range: Range<DisplayRow>,
9166 scroll_top: ScrollOffset,
9167 scroll_bottom: ScrollOffset,
9168 line_height: Pixels,
9169 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9170 target_display_point: DisplayPoint,
9171 editor_width: Pixels,
9172 window: &mut Window,
9173 cx: &mut App,
9174 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9175 if target_display_point.row().as_f64() < scroll_top {
9176 let mut element = self
9177 .render_edit_prediction_line_popover(
9178 "Jump to Edit",
9179 Some(IconName::ArrowUp),
9180 window,
9181 cx,
9182 )
9183 .into_any();
9184
9185 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9186 let offset = point(
9187 (text_bounds.size.width - size.width) / 2.,
9188 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9189 );
9190
9191 let origin = text_bounds.origin + offset;
9192 element.prepaint_at(origin, window, cx);
9193 Some((element, origin))
9194 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9195 let mut element = self
9196 .render_edit_prediction_line_popover(
9197 "Jump to Edit",
9198 Some(IconName::ArrowDown),
9199 window,
9200 cx,
9201 )
9202 .into_any();
9203
9204 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9205 let offset = point(
9206 (text_bounds.size.width - size.width) / 2.,
9207 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9208 );
9209
9210 let origin = text_bounds.origin + offset;
9211 element.prepaint_at(origin, window, cx);
9212 Some((element, origin))
9213 } else {
9214 self.render_edit_prediction_end_of_line_popover(
9215 "Jump to Edit",
9216 editor_snapshot,
9217 visible_row_range,
9218 target_display_point,
9219 line_height,
9220 scroll_pixel_position,
9221 content_origin,
9222 editor_width,
9223 window,
9224 cx,
9225 )
9226 }
9227 }
9228
9229 fn render_edit_prediction_end_of_line_popover(
9230 self: &mut Editor,
9231 label: &'static str,
9232 editor_snapshot: &EditorSnapshot,
9233 visible_row_range: Range<DisplayRow>,
9234 target_display_point: DisplayPoint,
9235 line_height: Pixels,
9236 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9237 content_origin: gpui::Point<Pixels>,
9238 editor_width: Pixels,
9239 window: &mut Window,
9240 cx: &mut App,
9241 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9242 let target_line_end = DisplayPoint::new(
9243 target_display_point.row(),
9244 editor_snapshot.line_len(target_display_point.row()),
9245 );
9246
9247 let mut element = self
9248 .render_edit_prediction_line_popover(label, None, window, cx)
9249 .into_any();
9250
9251 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9252
9253 let line_origin =
9254 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9255
9256 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9257 let mut origin = start_point
9258 + line_origin
9259 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9260 origin.x = origin.x.max(content_origin.x);
9261
9262 let max_x = content_origin.x + editor_width - size.width;
9263
9264 if origin.x > max_x {
9265 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9266
9267 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9268 origin.y += offset;
9269 IconName::ArrowUp
9270 } else {
9271 origin.y -= offset;
9272 IconName::ArrowDown
9273 };
9274
9275 element = self
9276 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9277 .into_any();
9278
9279 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9280
9281 origin.x = content_origin.x + editor_width - size.width - px(2.);
9282 }
9283
9284 element.prepaint_at(origin, window, cx);
9285 Some((element, origin))
9286 }
9287
9288 fn render_edit_prediction_diff_popover(
9289 self: &Editor,
9290 text_bounds: &Bounds<Pixels>,
9291 content_origin: gpui::Point<Pixels>,
9292 right_margin: Pixels,
9293 editor_snapshot: &EditorSnapshot,
9294 visible_row_range: Range<DisplayRow>,
9295 line_layouts: &[LineWithInvisibles],
9296 line_height: Pixels,
9297 scroll_position: gpui::Point<ScrollOffset>,
9298 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9299 newest_selection_head: Option<DisplayPoint>,
9300 editor_width: Pixels,
9301 style: &EditorStyle,
9302 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9303 edit_preview: &Option<language::EditPreview>,
9304 snapshot: &language::BufferSnapshot,
9305 window: &mut Window,
9306 cx: &mut App,
9307 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9308 let edit_start = edits
9309 .first()
9310 .unwrap()
9311 .0
9312 .start
9313 .to_display_point(editor_snapshot);
9314 let edit_end = edits
9315 .last()
9316 .unwrap()
9317 .0
9318 .end
9319 .to_display_point(editor_snapshot);
9320
9321 let is_visible = visible_row_range.contains(&edit_start.row())
9322 || visible_row_range.contains(&edit_end.row());
9323 if !is_visible {
9324 return None;
9325 }
9326
9327 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9328 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9329 } else {
9330 // Fallback for providers without edit_preview
9331 crate::edit_prediction_fallback_text(edits, cx)
9332 };
9333
9334 let styled_text = highlighted_edits.to_styled_text(&style.text);
9335 let line_count = highlighted_edits.text.lines().count();
9336
9337 const BORDER_WIDTH: Pixels = px(1.);
9338
9339 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9340 let has_keybind = keybind.is_some();
9341
9342 let mut element = h_flex()
9343 .items_start()
9344 .child(
9345 h_flex()
9346 .bg(cx.theme().colors().editor_background)
9347 .border(BORDER_WIDTH)
9348 .shadow_xs()
9349 .border_color(cx.theme().colors().border)
9350 .rounded_l_lg()
9351 .when(line_count > 1, |el| el.rounded_br_lg())
9352 .pr_1()
9353 .child(styled_text),
9354 )
9355 .child(
9356 h_flex()
9357 .h(line_height + BORDER_WIDTH * 2.)
9358 .px_1p5()
9359 .gap_1()
9360 // Workaround: For some reason, there's a gap if we don't do this
9361 .ml(-BORDER_WIDTH)
9362 .shadow(vec![gpui::BoxShadow {
9363 color: gpui::black().opacity(0.05),
9364 offset: point(px(1.), px(1.)),
9365 blur_radius: px(2.),
9366 spread_radius: px(0.),
9367 }])
9368 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9369 .border(BORDER_WIDTH)
9370 .border_color(cx.theme().colors().border)
9371 .rounded_r_lg()
9372 .id("edit_prediction_diff_popover_keybind")
9373 .when(!has_keybind, |el| {
9374 let status_colors = cx.theme().status();
9375
9376 el.bg(status_colors.error_background)
9377 .border_color(status_colors.error.opacity(0.6))
9378 .child(Icon::new(IconName::Info).color(Color::Error))
9379 .cursor_default()
9380 .hoverable_tooltip(move |_window, cx| {
9381 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9382 })
9383 })
9384 .children(keybind),
9385 )
9386 .into_any();
9387
9388 let longest_row =
9389 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9390 let longest_line_width = if visible_row_range.contains(&longest_row) {
9391 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9392 } else {
9393 layout_line(
9394 longest_row,
9395 editor_snapshot,
9396 style,
9397 editor_width,
9398 |_| false,
9399 window,
9400 cx,
9401 )
9402 .width
9403 };
9404
9405 let viewport_bounds =
9406 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9407 right: -right_margin,
9408 ..Default::default()
9409 });
9410
9411 let x_after_longest = Pixels::from(
9412 ScrollPixelOffset::from(
9413 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9414 ) - scroll_pixel_position.x,
9415 );
9416
9417 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9418
9419 // Fully visible if it can be displayed within the window (allow overlapping other
9420 // panes). However, this is only allowed if the popover starts within text_bounds.
9421 let can_position_to_the_right = x_after_longest < text_bounds.right()
9422 && x_after_longest + element_bounds.width < viewport_bounds.right();
9423
9424 let mut origin = if can_position_to_the_right {
9425 point(
9426 x_after_longest,
9427 text_bounds.origin.y
9428 + Pixels::from(
9429 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9430 - scroll_pixel_position.y,
9431 ),
9432 )
9433 } else {
9434 let cursor_row = newest_selection_head.map(|head| head.row());
9435 let above_edit = edit_start
9436 .row()
9437 .0
9438 .checked_sub(line_count as u32)
9439 .map(DisplayRow);
9440 let below_edit = Some(edit_end.row() + 1);
9441 let above_cursor =
9442 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9443 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9444
9445 // Place the edit popover adjacent to the edit if there is a location
9446 // available that is onscreen and does not obscure the cursor. Otherwise,
9447 // place it adjacent to the cursor.
9448 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9449 .into_iter()
9450 .flatten()
9451 .find(|&start_row| {
9452 let end_row = start_row + line_count as u32;
9453 visible_row_range.contains(&start_row)
9454 && visible_row_range.contains(&end_row)
9455 && cursor_row
9456 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9457 })?;
9458
9459 content_origin
9460 + point(
9461 Pixels::from(-scroll_pixel_position.x),
9462 Pixels::from(
9463 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9464 ),
9465 )
9466 };
9467
9468 origin.x -= BORDER_WIDTH;
9469
9470 window.defer_draw(element, origin, 1);
9471
9472 // Do not return an element, since it will already be drawn due to defer_draw.
9473 None
9474 }
9475
9476 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9477 px(30.)
9478 }
9479
9480 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9481 if self.read_only(cx) {
9482 cx.theme().players().read_only()
9483 } else {
9484 self.style.as_ref().unwrap().local_player
9485 }
9486 }
9487
9488 fn render_edit_prediction_accept_keybind(
9489 &self,
9490 window: &mut Window,
9491 cx: &mut App,
9492 ) -> Option<AnyElement> {
9493 let accept_binding =
9494 self.accept_edit_prediction_keybind(EditPredictionGranularity::Full, window, cx);
9495 let accept_keystroke = accept_binding.keystroke()?;
9496
9497 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9498
9499 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9500 Color::Accent
9501 } else {
9502 Color::Muted
9503 };
9504
9505 h_flex()
9506 .px_0p5()
9507 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9508 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9509 .text_size(TextSize::XSmall.rems(cx))
9510 .child(h_flex().children(ui::render_modifiers(
9511 accept_keystroke.modifiers(),
9512 PlatformStyle::platform(),
9513 Some(modifiers_color),
9514 Some(IconSize::XSmall.rems().into()),
9515 true,
9516 )))
9517 .when(is_platform_style_mac, |parent| {
9518 parent.child(accept_keystroke.key().to_string())
9519 })
9520 .when(!is_platform_style_mac, |parent| {
9521 parent.child(
9522 Key::new(
9523 util::capitalize(accept_keystroke.key()),
9524 Some(Color::Default),
9525 )
9526 .size(Some(IconSize::XSmall.rems().into())),
9527 )
9528 })
9529 .into_any()
9530 .into()
9531 }
9532
9533 fn render_edit_prediction_line_popover(
9534 &self,
9535 label: impl Into<SharedString>,
9536 icon: Option<IconName>,
9537 window: &mut Window,
9538 cx: &mut App,
9539 ) -> Stateful<Div> {
9540 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9541
9542 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9543 let has_keybind = keybind.is_some();
9544
9545 h_flex()
9546 .id("ep-line-popover")
9547 .py_0p5()
9548 .pl_1()
9549 .pr(padding_right)
9550 .gap_1()
9551 .rounded_md()
9552 .border_1()
9553 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9554 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9555 .shadow_xs()
9556 .when(!has_keybind, |el| {
9557 let status_colors = cx.theme().status();
9558
9559 el.bg(status_colors.error_background)
9560 .border_color(status_colors.error.opacity(0.6))
9561 .pl_2()
9562 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9563 .cursor_default()
9564 .hoverable_tooltip(move |_window, cx| {
9565 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9566 })
9567 })
9568 .children(keybind)
9569 .child(
9570 Label::new(label)
9571 .size(LabelSize::Small)
9572 .when(!has_keybind, |el| {
9573 el.color(cx.theme().status().error.into()).strikethrough()
9574 }),
9575 )
9576 .when(!has_keybind, |el| {
9577 el.child(
9578 h_flex().ml_1().child(
9579 Icon::new(IconName::Info)
9580 .size(IconSize::Small)
9581 .color(cx.theme().status().error.into()),
9582 ),
9583 )
9584 })
9585 .when_some(icon, |element, icon| {
9586 element.child(
9587 div()
9588 .mt(px(1.5))
9589 .child(Icon::new(icon).size(IconSize::Small)),
9590 )
9591 })
9592 }
9593
9594 fn render_edit_prediction_jump_outside_popover(
9595 &self,
9596 snapshot: &BufferSnapshot,
9597 window: &mut Window,
9598 cx: &mut App,
9599 ) -> Stateful<Div> {
9600 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9601 let has_keybind = keybind.is_some();
9602
9603 let file_name = snapshot
9604 .file()
9605 .map(|file| SharedString::new(file.file_name(cx)))
9606 .unwrap_or(SharedString::new_static("untitled"));
9607
9608 h_flex()
9609 .id("ep-jump-outside-popover")
9610 .py_1()
9611 .px_2()
9612 .gap_1()
9613 .rounded_md()
9614 .border_1()
9615 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9616 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9617 .shadow_xs()
9618 .when(!has_keybind, |el| {
9619 let status_colors = cx.theme().status();
9620
9621 el.bg(status_colors.error_background)
9622 .border_color(status_colors.error.opacity(0.6))
9623 .pl_2()
9624 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9625 .cursor_default()
9626 .hoverable_tooltip(move |_window, cx| {
9627 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9628 })
9629 })
9630 .children(keybind)
9631 .child(
9632 Label::new(file_name)
9633 .size(LabelSize::Small)
9634 .buffer_font(cx)
9635 .when(!has_keybind, |el| {
9636 el.color(cx.theme().status().error.into()).strikethrough()
9637 }),
9638 )
9639 .when(!has_keybind, |el| {
9640 el.child(
9641 h_flex().ml_1().child(
9642 Icon::new(IconName::Info)
9643 .size(IconSize::Small)
9644 .color(cx.theme().status().error.into()),
9645 ),
9646 )
9647 })
9648 .child(
9649 div()
9650 .mt(px(1.5))
9651 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9652 )
9653 }
9654
9655 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9656 let accent_color = cx.theme().colors().text_accent;
9657 let editor_bg_color = cx.theme().colors().editor_background;
9658 editor_bg_color.blend(accent_color.opacity(0.1))
9659 }
9660
9661 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9662 let accent_color = cx.theme().colors().text_accent;
9663 let editor_bg_color = cx.theme().colors().editor_background;
9664 editor_bg_color.blend(accent_color.opacity(0.6))
9665 }
9666 fn get_prediction_provider_icon_name(
9667 provider: &Option<RegisteredEditPredictionDelegate>,
9668 ) -> IconName {
9669 match provider {
9670 Some(provider) => match provider.provider.name() {
9671 "copilot" => IconName::Copilot,
9672 "supermaven" => IconName::Supermaven,
9673 _ => IconName::ZedPredict,
9674 },
9675 None => IconName::ZedPredict,
9676 }
9677 }
9678
9679 fn render_edit_prediction_cursor_popover(
9680 &self,
9681 min_width: Pixels,
9682 max_width: Pixels,
9683 cursor_point: Point,
9684 style: &EditorStyle,
9685 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9686 _window: &Window,
9687 cx: &mut Context<Editor>,
9688 ) -> Option<AnyElement> {
9689 let provider = self.edit_prediction_provider.as_ref()?;
9690 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9691
9692 let is_refreshing = provider.provider.is_refreshing(cx);
9693
9694 fn pending_completion_container(icon: IconName) -> Div {
9695 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9696 }
9697
9698 let completion = match &self.active_edit_prediction {
9699 Some(prediction) => {
9700 if !self.has_visible_completions_menu() {
9701 const RADIUS: Pixels = px(6.);
9702 const BORDER_WIDTH: Pixels = px(1.);
9703
9704 return Some(
9705 h_flex()
9706 .elevation_2(cx)
9707 .border(BORDER_WIDTH)
9708 .border_color(cx.theme().colors().border)
9709 .when(accept_keystroke.is_none(), |el| {
9710 el.border_color(cx.theme().status().error)
9711 })
9712 .rounded(RADIUS)
9713 .rounded_tl(px(0.))
9714 .overflow_hidden()
9715 .child(div().px_1p5().child(match &prediction.completion {
9716 EditPrediction::MoveWithin { target, snapshot } => {
9717 use text::ToPoint as _;
9718 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9719 {
9720 Icon::new(IconName::ZedPredictDown)
9721 } else {
9722 Icon::new(IconName::ZedPredictUp)
9723 }
9724 }
9725 EditPrediction::MoveOutside { .. } => {
9726 // TODO [zeta2] custom icon for external jump?
9727 Icon::new(provider_icon)
9728 }
9729 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9730 }))
9731 .child(
9732 h_flex()
9733 .gap_1()
9734 .py_1()
9735 .px_2()
9736 .rounded_r(RADIUS - BORDER_WIDTH)
9737 .border_l_1()
9738 .border_color(cx.theme().colors().border)
9739 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9740 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9741 el.child(
9742 Label::new("Hold")
9743 .size(LabelSize::Small)
9744 .when(accept_keystroke.is_none(), |el| {
9745 el.strikethrough()
9746 })
9747 .line_height_style(LineHeightStyle::UiLabel),
9748 )
9749 })
9750 .id("edit_prediction_cursor_popover_keybind")
9751 .when(accept_keystroke.is_none(), |el| {
9752 let status_colors = cx.theme().status();
9753
9754 el.bg(status_colors.error_background)
9755 .border_color(status_colors.error.opacity(0.6))
9756 .child(Icon::new(IconName::Info).color(Color::Error))
9757 .cursor_default()
9758 .hoverable_tooltip(move |_window, cx| {
9759 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9760 .into()
9761 })
9762 })
9763 .when_some(
9764 accept_keystroke.as_ref(),
9765 |el, accept_keystroke| {
9766 el.child(h_flex().children(ui::render_modifiers(
9767 accept_keystroke.modifiers(),
9768 PlatformStyle::platform(),
9769 Some(Color::Default),
9770 Some(IconSize::XSmall.rems().into()),
9771 false,
9772 )))
9773 },
9774 ),
9775 )
9776 .into_any(),
9777 );
9778 }
9779
9780 self.render_edit_prediction_cursor_popover_preview(
9781 prediction,
9782 cursor_point,
9783 style,
9784 cx,
9785 )?
9786 }
9787
9788 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9789 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9790 stale_completion,
9791 cursor_point,
9792 style,
9793 cx,
9794 )?,
9795
9796 None => pending_completion_container(provider_icon)
9797 .child(Label::new("...").size(LabelSize::Small)),
9798 },
9799
9800 None => pending_completion_container(provider_icon)
9801 .child(Label::new("...").size(LabelSize::Small)),
9802 };
9803
9804 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9805 completion
9806 .with_animation(
9807 "loading-completion",
9808 Animation::new(Duration::from_secs(2))
9809 .repeat()
9810 .with_easing(pulsating_between(0.4, 0.8)),
9811 |label, delta| label.opacity(delta),
9812 )
9813 .into_any_element()
9814 } else {
9815 completion.into_any_element()
9816 };
9817
9818 let has_completion = self.active_edit_prediction.is_some();
9819
9820 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9821 Some(
9822 h_flex()
9823 .min_w(min_width)
9824 .max_w(max_width)
9825 .flex_1()
9826 .elevation_2(cx)
9827 .border_color(cx.theme().colors().border)
9828 .child(
9829 div()
9830 .flex_1()
9831 .py_1()
9832 .px_2()
9833 .overflow_hidden()
9834 .child(completion),
9835 )
9836 .when_some(accept_keystroke, |el, accept_keystroke| {
9837 if !accept_keystroke.modifiers().modified() {
9838 return el;
9839 }
9840
9841 el.child(
9842 h_flex()
9843 .h_full()
9844 .border_l_1()
9845 .rounded_r_lg()
9846 .border_color(cx.theme().colors().border)
9847 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9848 .gap_1()
9849 .py_1()
9850 .px_2()
9851 .child(
9852 h_flex()
9853 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9854 .when(is_platform_style_mac, |parent| parent.gap_1())
9855 .child(h_flex().children(ui::render_modifiers(
9856 accept_keystroke.modifiers(),
9857 PlatformStyle::platform(),
9858 Some(if !has_completion {
9859 Color::Muted
9860 } else {
9861 Color::Default
9862 }),
9863 None,
9864 false,
9865 ))),
9866 )
9867 .child(Label::new("Preview").into_any_element())
9868 .opacity(if has_completion { 1.0 } else { 0.4 }),
9869 )
9870 })
9871 .into_any(),
9872 )
9873 }
9874
9875 fn render_edit_prediction_cursor_popover_preview(
9876 &self,
9877 completion: &EditPredictionState,
9878 cursor_point: Point,
9879 style: &EditorStyle,
9880 cx: &mut Context<Editor>,
9881 ) -> Option<Div> {
9882 use text::ToPoint as _;
9883
9884 fn render_relative_row_jump(
9885 prefix: impl Into<String>,
9886 current_row: u32,
9887 target_row: u32,
9888 ) -> Div {
9889 let (row_diff, arrow) = if target_row < current_row {
9890 (current_row - target_row, IconName::ArrowUp)
9891 } else {
9892 (target_row - current_row, IconName::ArrowDown)
9893 };
9894
9895 h_flex()
9896 .child(
9897 Label::new(format!("{}{}", prefix.into(), row_diff))
9898 .color(Color::Muted)
9899 .size(LabelSize::Small),
9900 )
9901 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9902 }
9903
9904 let supports_jump = self
9905 .edit_prediction_provider
9906 .as_ref()
9907 .map(|provider| provider.provider.supports_jump_to_edit())
9908 .unwrap_or(true);
9909
9910 match &completion.completion {
9911 EditPrediction::MoveWithin {
9912 target, snapshot, ..
9913 } => {
9914 if !supports_jump {
9915 return None;
9916 }
9917
9918 Some(
9919 h_flex()
9920 .px_2()
9921 .gap_2()
9922 .flex_1()
9923 .child(
9924 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9925 Icon::new(IconName::ZedPredictDown)
9926 } else {
9927 Icon::new(IconName::ZedPredictUp)
9928 },
9929 )
9930 .child(Label::new("Jump to Edit")),
9931 )
9932 }
9933 EditPrediction::MoveOutside { snapshot, .. } => {
9934 let file_name = snapshot
9935 .file()
9936 .map(|file| file.file_name(cx))
9937 .unwrap_or("untitled");
9938 Some(
9939 h_flex()
9940 .px_2()
9941 .gap_2()
9942 .flex_1()
9943 .child(Icon::new(IconName::ZedPredict))
9944 .child(Label::new(format!("Jump to {file_name}"))),
9945 )
9946 }
9947 EditPrediction::Edit {
9948 edits,
9949 edit_preview,
9950 snapshot,
9951 display_mode: _,
9952 } => {
9953 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9954
9955 let (highlighted_edits, has_more_lines) =
9956 if let Some(edit_preview) = edit_preview.as_ref() {
9957 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9958 .first_line_preview()
9959 } else {
9960 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9961 };
9962
9963 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9964 .with_default_highlights(&style.text, highlighted_edits.highlights);
9965
9966 let preview = h_flex()
9967 .gap_1()
9968 .min_w_16()
9969 .child(styled_text)
9970 .when(has_more_lines, |parent| parent.child("…"));
9971
9972 let left = if supports_jump && first_edit_row != cursor_point.row {
9973 render_relative_row_jump("", cursor_point.row, first_edit_row)
9974 .into_any_element()
9975 } else {
9976 let icon_name =
9977 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9978 Icon::new(icon_name).into_any_element()
9979 };
9980
9981 Some(
9982 h_flex()
9983 .h_full()
9984 .flex_1()
9985 .gap_2()
9986 .pr_1()
9987 .overflow_x_hidden()
9988 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9989 .child(left)
9990 .child(preview),
9991 )
9992 }
9993 }
9994 }
9995
9996 pub fn render_context_menu(
9997 &mut self,
9998 max_height_in_lines: u32,
9999 window: &mut Window,
10000 cx: &mut Context<Editor>,
10001 ) -> Option<AnyElement> {
10002 let menu = self.context_menu.borrow();
10003 let menu = menu.as_ref()?;
10004 if !menu.visible() {
10005 return None;
10006 };
10007 self.style
10008 .as_ref()
10009 .map(|style| menu.render(style, max_height_in_lines, window, cx))
10010 }
10011
10012 fn render_context_menu_aside(
10013 &mut self,
10014 max_size: Size<Pixels>,
10015 window: &mut Window,
10016 cx: &mut Context<Editor>,
10017 ) -> Option<AnyElement> {
10018 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
10019 if menu.visible() {
10020 menu.render_aside(max_size, window, cx)
10021 } else {
10022 None
10023 }
10024 })
10025 }
10026
10027 fn hide_context_menu(
10028 &mut self,
10029 window: &mut Window,
10030 cx: &mut Context<Self>,
10031 ) -> Option<CodeContextMenu> {
10032 cx.notify();
10033 self.completion_tasks.clear();
10034 let context_menu = self.context_menu.borrow_mut().take();
10035 self.stale_edit_prediction_in_menu.take();
10036 self.update_visible_edit_prediction(window, cx);
10037 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10038 && let Some(completion_provider) = &self.completion_provider
10039 {
10040 completion_provider.selection_changed(None, window, cx);
10041 }
10042 context_menu
10043 }
10044
10045 fn show_snippet_choices(
10046 &mut self,
10047 choices: &Vec<String>,
10048 selection: Range<Anchor>,
10049 cx: &mut Context<Self>,
10050 ) {
10051 let Some((_, buffer, _)) = self
10052 .buffer()
10053 .read(cx)
10054 .excerpt_containing(selection.start, cx)
10055 else {
10056 return;
10057 };
10058 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10059 else {
10060 return;
10061 };
10062 if buffer != end_buffer {
10063 log::error!("expected anchor range to have matching buffer IDs");
10064 return;
10065 }
10066
10067 let id = post_inc(&mut self.next_completion_id);
10068 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10069 let mut context_menu = self.context_menu.borrow_mut();
10070 let old_menu = context_menu.take();
10071 *context_menu = Some(CodeContextMenu::Completions(
10072 CompletionsMenu::new_snippet_choices(
10073 id,
10074 true,
10075 choices,
10076 selection,
10077 buffer,
10078 old_menu.map(|menu| menu.primary_scroll_handle()),
10079 snippet_sort_order,
10080 ),
10081 ));
10082 }
10083
10084 pub fn insert_snippet(
10085 &mut self,
10086 insertion_ranges: &[Range<MultiBufferOffset>],
10087 snippet: Snippet,
10088 window: &mut Window,
10089 cx: &mut Context<Self>,
10090 ) -> Result<()> {
10091 struct Tabstop<T> {
10092 is_end_tabstop: bool,
10093 ranges: Vec<Range<T>>,
10094 choices: Option<Vec<String>>,
10095 }
10096
10097 let tabstops = self.buffer.update(cx, |buffer, cx| {
10098 let snippet_text: Arc<str> = snippet.text.clone().into();
10099 let edits = insertion_ranges
10100 .iter()
10101 .cloned()
10102 .map(|range| (range, snippet_text.clone()));
10103 let autoindent_mode = AutoindentMode::Block {
10104 original_indent_columns: Vec::new(),
10105 };
10106 buffer.edit(edits, Some(autoindent_mode), cx);
10107
10108 let snapshot = &*buffer.read(cx);
10109 let snippet = &snippet;
10110 snippet
10111 .tabstops
10112 .iter()
10113 .map(|tabstop| {
10114 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10115 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10116 });
10117 let mut tabstop_ranges = tabstop
10118 .ranges
10119 .iter()
10120 .flat_map(|tabstop_range| {
10121 let mut delta = 0_isize;
10122 insertion_ranges.iter().map(move |insertion_range| {
10123 let insertion_start = insertion_range.start + delta;
10124 delta += snippet.text.len() as isize
10125 - (insertion_range.end - insertion_range.start) as isize;
10126
10127 let start =
10128 (insertion_start + tabstop_range.start).min(snapshot.len());
10129 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10130 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10131 })
10132 })
10133 .collect::<Vec<_>>();
10134 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10135
10136 Tabstop {
10137 is_end_tabstop,
10138 ranges: tabstop_ranges,
10139 choices: tabstop.choices.clone(),
10140 }
10141 })
10142 .collect::<Vec<_>>()
10143 });
10144 if let Some(tabstop) = tabstops.first() {
10145 self.change_selections(Default::default(), window, cx, |s| {
10146 // Reverse order so that the first range is the newest created selection.
10147 // Completions will use it and autoscroll will prioritize it.
10148 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10149 });
10150
10151 if let Some(choices) = &tabstop.choices
10152 && let Some(selection) = tabstop.ranges.first()
10153 {
10154 self.show_snippet_choices(choices, selection.clone(), cx)
10155 }
10156
10157 // If we're already at the last tabstop and it's at the end of the snippet,
10158 // we're done, we don't need to keep the state around.
10159 if !tabstop.is_end_tabstop {
10160 let choices = tabstops
10161 .iter()
10162 .map(|tabstop| tabstop.choices.clone())
10163 .collect();
10164
10165 let ranges = tabstops
10166 .into_iter()
10167 .map(|tabstop| tabstop.ranges)
10168 .collect::<Vec<_>>();
10169
10170 self.snippet_stack.push(SnippetState {
10171 active_index: 0,
10172 ranges,
10173 choices,
10174 });
10175 }
10176
10177 // Check whether the just-entered snippet ends with an auto-closable bracket.
10178 if self.autoclose_regions.is_empty() {
10179 let snapshot = self.buffer.read(cx).snapshot(cx);
10180 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10181 let selection_head = selection.head();
10182 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10183 continue;
10184 };
10185
10186 let mut bracket_pair = None;
10187 let max_lookup_length = scope
10188 .brackets()
10189 .map(|(pair, _)| {
10190 pair.start
10191 .as_str()
10192 .chars()
10193 .count()
10194 .max(pair.end.as_str().chars().count())
10195 })
10196 .max();
10197 if let Some(max_lookup_length) = max_lookup_length {
10198 let next_text = snapshot
10199 .chars_at(selection_head)
10200 .take(max_lookup_length)
10201 .collect::<String>();
10202 let prev_text = snapshot
10203 .reversed_chars_at(selection_head)
10204 .take(max_lookup_length)
10205 .collect::<String>();
10206
10207 for (pair, enabled) in scope.brackets() {
10208 if enabled
10209 && pair.close
10210 && prev_text.starts_with(pair.start.as_str())
10211 && next_text.starts_with(pair.end.as_str())
10212 {
10213 bracket_pair = Some(pair.clone());
10214 break;
10215 }
10216 }
10217 }
10218
10219 if let Some(pair) = bracket_pair {
10220 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10221 let autoclose_enabled =
10222 self.use_autoclose && snapshot_settings.use_autoclose;
10223 if autoclose_enabled {
10224 let start = snapshot.anchor_after(selection_head);
10225 let end = snapshot.anchor_after(selection_head);
10226 self.autoclose_regions.push(AutocloseRegion {
10227 selection_id: selection.id,
10228 range: start..end,
10229 pair,
10230 });
10231 }
10232 }
10233 }
10234 }
10235 }
10236 Ok(())
10237 }
10238
10239 pub fn move_to_next_snippet_tabstop(
10240 &mut self,
10241 window: &mut Window,
10242 cx: &mut Context<Self>,
10243 ) -> bool {
10244 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10245 }
10246
10247 pub fn move_to_prev_snippet_tabstop(
10248 &mut self,
10249 window: &mut Window,
10250 cx: &mut Context<Self>,
10251 ) -> bool {
10252 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10253 }
10254
10255 pub fn move_to_snippet_tabstop(
10256 &mut self,
10257 bias: Bias,
10258 window: &mut Window,
10259 cx: &mut Context<Self>,
10260 ) -> bool {
10261 if let Some(mut snippet) = self.snippet_stack.pop() {
10262 match bias {
10263 Bias::Left => {
10264 if snippet.active_index > 0 {
10265 snippet.active_index -= 1;
10266 } else {
10267 self.snippet_stack.push(snippet);
10268 return false;
10269 }
10270 }
10271 Bias::Right => {
10272 if snippet.active_index + 1 < snippet.ranges.len() {
10273 snippet.active_index += 1;
10274 } else {
10275 self.snippet_stack.push(snippet);
10276 return false;
10277 }
10278 }
10279 }
10280 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10281 self.change_selections(Default::default(), window, cx, |s| {
10282 // Reverse order so that the first range is the newest created selection.
10283 // Completions will use it and autoscroll will prioritize it.
10284 s.select_ranges(current_ranges.iter().rev().cloned())
10285 });
10286
10287 if let Some(choices) = &snippet.choices[snippet.active_index]
10288 && let Some(selection) = current_ranges.first()
10289 {
10290 self.show_snippet_choices(choices, selection.clone(), cx);
10291 }
10292
10293 // If snippet state is not at the last tabstop, push it back on the stack
10294 if snippet.active_index + 1 < snippet.ranges.len() {
10295 self.snippet_stack.push(snippet);
10296 }
10297 return true;
10298 }
10299 }
10300
10301 false
10302 }
10303
10304 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10305 self.transact(window, cx, |this, window, cx| {
10306 this.select_all(&SelectAll, window, cx);
10307 this.insert("", window, cx);
10308 });
10309 }
10310
10311 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10312 if self.read_only(cx) {
10313 return;
10314 }
10315 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10316 self.transact(window, cx, |this, window, cx| {
10317 this.select_autoclose_pair(window, cx);
10318
10319 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10320
10321 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10322 if !this.linked_edit_ranges.is_empty() {
10323 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10324 let snapshot = this.buffer.read(cx).snapshot(cx);
10325
10326 for selection in selections.iter() {
10327 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10328 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10329 if selection_start.buffer_id != selection_end.buffer_id {
10330 continue;
10331 }
10332 if let Some(ranges) =
10333 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10334 {
10335 for (buffer, entries) in ranges {
10336 linked_ranges.entry(buffer).or_default().extend(entries);
10337 }
10338 }
10339 }
10340 }
10341
10342 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10343 for selection in &mut selections {
10344 if selection.is_empty() {
10345 let old_head = selection.head();
10346 let mut new_head =
10347 movement::left(&display_map, old_head.to_display_point(&display_map))
10348 .to_point(&display_map);
10349 if let Some((buffer, line_buffer_range)) = display_map
10350 .buffer_snapshot()
10351 .buffer_line_for_row(MultiBufferRow(old_head.row))
10352 {
10353 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10354 let indent_len = match indent_size.kind {
10355 IndentKind::Space => {
10356 buffer.settings_at(line_buffer_range.start, cx).tab_size
10357 }
10358 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10359 };
10360 if old_head.column <= indent_size.len && old_head.column > 0 {
10361 let indent_len = indent_len.get();
10362 new_head = cmp::min(
10363 new_head,
10364 MultiBufferPoint::new(
10365 old_head.row,
10366 ((old_head.column - 1) / indent_len) * indent_len,
10367 ),
10368 );
10369 }
10370 }
10371
10372 selection.set_head(new_head, SelectionGoal::None);
10373 }
10374 }
10375
10376 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10377 this.insert("", window, cx);
10378 let empty_str: Arc<str> = Arc::from("");
10379 for (buffer, edits) in linked_ranges {
10380 let snapshot = buffer.read(cx).snapshot();
10381 use text::ToPoint as TP;
10382
10383 let edits = edits
10384 .into_iter()
10385 .map(|range| {
10386 let end_point = TP::to_point(&range.end, &snapshot);
10387 let mut start_point = TP::to_point(&range.start, &snapshot);
10388
10389 if end_point == start_point {
10390 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10391 .saturating_sub(1);
10392 start_point =
10393 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10394 };
10395
10396 (start_point..end_point, empty_str.clone())
10397 })
10398 .sorted_by_key(|(range, _)| range.start)
10399 .collect::<Vec<_>>();
10400 buffer.update(cx, |this, cx| {
10401 this.edit(edits, None, cx);
10402 })
10403 }
10404 this.refresh_edit_prediction(true, false, window, cx);
10405 refresh_linked_ranges(this, window, cx);
10406 });
10407 }
10408
10409 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10410 if self.read_only(cx) {
10411 return;
10412 }
10413 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10414 self.transact(window, cx, |this, window, cx| {
10415 this.change_selections(Default::default(), window, cx, |s| {
10416 s.move_with(|map, selection| {
10417 if selection.is_empty() {
10418 let cursor = movement::right(map, selection.head());
10419 selection.end = cursor;
10420 selection.reversed = true;
10421 selection.goal = SelectionGoal::None;
10422 }
10423 })
10424 });
10425 this.insert("", window, cx);
10426 this.refresh_edit_prediction(true, false, window, cx);
10427 });
10428 }
10429
10430 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10431 if self.mode.is_single_line() {
10432 cx.propagate();
10433 return;
10434 }
10435
10436 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10437 if self.move_to_prev_snippet_tabstop(window, cx) {
10438 return;
10439 }
10440 self.outdent(&Outdent, window, cx);
10441 }
10442
10443 pub fn next_snippet_tabstop(
10444 &mut self,
10445 _: &NextSnippetTabstop,
10446 window: &mut Window,
10447 cx: &mut Context<Self>,
10448 ) {
10449 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10450 cx.propagate();
10451 return;
10452 }
10453
10454 if self.move_to_next_snippet_tabstop(window, cx) {
10455 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10456 return;
10457 }
10458 cx.propagate();
10459 }
10460
10461 pub fn previous_snippet_tabstop(
10462 &mut self,
10463 _: &PreviousSnippetTabstop,
10464 window: &mut Window,
10465 cx: &mut Context<Self>,
10466 ) {
10467 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10468 cx.propagate();
10469 return;
10470 }
10471
10472 if self.move_to_prev_snippet_tabstop(window, cx) {
10473 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10474 return;
10475 }
10476 cx.propagate();
10477 }
10478
10479 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10480 if self.mode.is_single_line() {
10481 cx.propagate();
10482 return;
10483 }
10484
10485 if self.move_to_next_snippet_tabstop(window, cx) {
10486 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10487 return;
10488 }
10489 if self.read_only(cx) {
10490 return;
10491 }
10492 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10493 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10494 let buffer = self.buffer.read(cx);
10495 let snapshot = buffer.snapshot(cx);
10496 let rows_iter = selections.iter().map(|s| s.head().row);
10497 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10498
10499 let has_some_cursor_in_whitespace = selections
10500 .iter()
10501 .filter(|selection| selection.is_empty())
10502 .any(|selection| {
10503 let cursor = selection.head();
10504 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10505 cursor.column < current_indent.len
10506 });
10507
10508 let mut edits = Vec::new();
10509 let mut prev_edited_row = 0;
10510 let mut row_delta = 0;
10511 for selection in &mut selections {
10512 if selection.start.row != prev_edited_row {
10513 row_delta = 0;
10514 }
10515 prev_edited_row = selection.end.row;
10516
10517 // If the selection is non-empty, then increase the indentation of the selected lines.
10518 if !selection.is_empty() {
10519 row_delta =
10520 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10521 continue;
10522 }
10523
10524 let cursor = selection.head();
10525 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10526 if let Some(suggested_indent) =
10527 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10528 {
10529 // Don't do anything if already at suggested indent
10530 // and there is any other cursor which is not
10531 if has_some_cursor_in_whitespace
10532 && cursor.column == current_indent.len
10533 && current_indent.len == suggested_indent.len
10534 {
10535 continue;
10536 }
10537
10538 // Adjust line and move cursor to suggested indent
10539 // if cursor is not at suggested indent
10540 if cursor.column < suggested_indent.len
10541 && cursor.column <= current_indent.len
10542 && current_indent.len <= suggested_indent.len
10543 {
10544 selection.start = Point::new(cursor.row, suggested_indent.len);
10545 selection.end = selection.start;
10546 if row_delta == 0 {
10547 edits.extend(Buffer::edit_for_indent_size_adjustment(
10548 cursor.row,
10549 current_indent,
10550 suggested_indent,
10551 ));
10552 row_delta = suggested_indent.len - current_indent.len;
10553 }
10554 continue;
10555 }
10556
10557 // If current indent is more than suggested indent
10558 // only move cursor to current indent and skip indent
10559 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10560 selection.start = Point::new(cursor.row, current_indent.len);
10561 selection.end = selection.start;
10562 continue;
10563 }
10564 }
10565
10566 // Otherwise, insert a hard or soft tab.
10567 let settings = buffer.language_settings_at(cursor, cx);
10568 let tab_size = if settings.hard_tabs {
10569 IndentSize::tab()
10570 } else {
10571 let tab_size = settings.tab_size.get();
10572 let indent_remainder = snapshot
10573 .text_for_range(Point::new(cursor.row, 0)..cursor)
10574 .flat_map(str::chars)
10575 .fold(row_delta % tab_size, |counter: u32, c| {
10576 if c == '\t' {
10577 0
10578 } else {
10579 (counter + 1) % tab_size
10580 }
10581 });
10582
10583 let chars_to_next_tab_stop = tab_size - indent_remainder;
10584 IndentSize::spaces(chars_to_next_tab_stop)
10585 };
10586 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10587 selection.end = selection.start;
10588 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10589 row_delta += tab_size.len;
10590 }
10591
10592 self.transact(window, cx, |this, window, cx| {
10593 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10594 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10595 this.refresh_edit_prediction(true, false, window, cx);
10596 });
10597 }
10598
10599 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10600 if self.read_only(cx) {
10601 return;
10602 }
10603 if self.mode.is_single_line() {
10604 cx.propagate();
10605 return;
10606 }
10607
10608 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10609 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10610 let mut prev_edited_row = 0;
10611 let mut row_delta = 0;
10612 let mut edits = Vec::new();
10613 let buffer = self.buffer.read(cx);
10614 let snapshot = buffer.snapshot(cx);
10615 for selection in &mut selections {
10616 if selection.start.row != prev_edited_row {
10617 row_delta = 0;
10618 }
10619 prev_edited_row = selection.end.row;
10620
10621 row_delta =
10622 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10623 }
10624
10625 self.transact(window, cx, |this, window, cx| {
10626 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10627 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10628 });
10629 }
10630
10631 fn indent_selection(
10632 buffer: &MultiBuffer,
10633 snapshot: &MultiBufferSnapshot,
10634 selection: &mut Selection<Point>,
10635 edits: &mut Vec<(Range<Point>, String)>,
10636 delta_for_start_row: u32,
10637 cx: &App,
10638 ) -> u32 {
10639 let settings = buffer.language_settings_at(selection.start, cx);
10640 let tab_size = settings.tab_size.get();
10641 let indent_kind = if settings.hard_tabs {
10642 IndentKind::Tab
10643 } else {
10644 IndentKind::Space
10645 };
10646 let mut start_row = selection.start.row;
10647 let mut end_row = selection.end.row + 1;
10648
10649 // If a selection ends at the beginning of a line, don't indent
10650 // that last line.
10651 if selection.end.column == 0 && selection.end.row > selection.start.row {
10652 end_row -= 1;
10653 }
10654
10655 // Avoid re-indenting a row that has already been indented by a
10656 // previous selection, but still update this selection's column
10657 // to reflect that indentation.
10658 if delta_for_start_row > 0 {
10659 start_row += 1;
10660 selection.start.column += delta_for_start_row;
10661 if selection.end.row == selection.start.row {
10662 selection.end.column += delta_for_start_row;
10663 }
10664 }
10665
10666 let mut delta_for_end_row = 0;
10667 let has_multiple_rows = start_row + 1 != end_row;
10668 for row in start_row..end_row {
10669 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10670 let indent_delta = match (current_indent.kind, indent_kind) {
10671 (IndentKind::Space, IndentKind::Space) => {
10672 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10673 IndentSize::spaces(columns_to_next_tab_stop)
10674 }
10675 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10676 (_, IndentKind::Tab) => IndentSize::tab(),
10677 };
10678
10679 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10680 0
10681 } else {
10682 selection.start.column
10683 };
10684 let row_start = Point::new(row, start);
10685 edits.push((
10686 row_start..row_start,
10687 indent_delta.chars().collect::<String>(),
10688 ));
10689
10690 // Update this selection's endpoints to reflect the indentation.
10691 if row == selection.start.row {
10692 selection.start.column += indent_delta.len;
10693 }
10694 if row == selection.end.row {
10695 selection.end.column += indent_delta.len;
10696 delta_for_end_row = indent_delta.len;
10697 }
10698 }
10699
10700 if selection.start.row == selection.end.row {
10701 delta_for_start_row + delta_for_end_row
10702 } else {
10703 delta_for_end_row
10704 }
10705 }
10706
10707 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10708 if self.read_only(cx) {
10709 return;
10710 }
10711 if self.mode.is_single_line() {
10712 cx.propagate();
10713 return;
10714 }
10715
10716 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10717 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10718 let selections = self.selections.all::<Point>(&display_map);
10719 let mut deletion_ranges = Vec::new();
10720 let mut last_outdent = None;
10721 {
10722 let buffer = self.buffer.read(cx);
10723 let snapshot = buffer.snapshot(cx);
10724 for selection in &selections {
10725 let settings = buffer.language_settings_at(selection.start, cx);
10726 let tab_size = settings.tab_size.get();
10727 let mut rows = selection.spanned_rows(false, &display_map);
10728
10729 // Avoid re-outdenting a row that has already been outdented by a
10730 // previous selection.
10731 if let Some(last_row) = last_outdent
10732 && last_row == rows.start
10733 {
10734 rows.start = rows.start.next_row();
10735 }
10736 let has_multiple_rows = rows.len() > 1;
10737 for row in rows.iter_rows() {
10738 let indent_size = snapshot.indent_size_for_line(row);
10739 if indent_size.len > 0 {
10740 let deletion_len = match indent_size.kind {
10741 IndentKind::Space => {
10742 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10743 if columns_to_prev_tab_stop == 0 {
10744 tab_size
10745 } else {
10746 columns_to_prev_tab_stop
10747 }
10748 }
10749 IndentKind::Tab => 1,
10750 };
10751 let start = if has_multiple_rows
10752 || deletion_len > selection.start.column
10753 || indent_size.len < selection.start.column
10754 {
10755 0
10756 } else {
10757 selection.start.column - deletion_len
10758 };
10759 deletion_ranges.push(
10760 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10761 );
10762 last_outdent = Some(row);
10763 }
10764 }
10765 }
10766 }
10767
10768 self.transact(window, cx, |this, window, cx| {
10769 this.buffer.update(cx, |buffer, cx| {
10770 let empty_str: Arc<str> = Arc::default();
10771 buffer.edit(
10772 deletion_ranges
10773 .into_iter()
10774 .map(|range| (range, empty_str.clone())),
10775 None,
10776 cx,
10777 );
10778 });
10779 let selections = this
10780 .selections
10781 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10782 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10783 });
10784 }
10785
10786 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10787 if self.read_only(cx) {
10788 return;
10789 }
10790 if self.mode.is_single_line() {
10791 cx.propagate();
10792 return;
10793 }
10794
10795 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10796 let selections = self
10797 .selections
10798 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10799 .into_iter()
10800 .map(|s| s.range());
10801
10802 self.transact(window, cx, |this, window, cx| {
10803 this.buffer.update(cx, |buffer, cx| {
10804 buffer.autoindent_ranges(selections, cx);
10805 });
10806 let selections = this
10807 .selections
10808 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10809 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10810 });
10811 }
10812
10813 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10814 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10815 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10816 let selections = self.selections.all::<Point>(&display_map);
10817
10818 let mut new_cursors = Vec::new();
10819 let mut edit_ranges = Vec::new();
10820 let mut selections = selections.iter().peekable();
10821 while let Some(selection) = selections.next() {
10822 let mut rows = selection.spanned_rows(false, &display_map);
10823
10824 // Accumulate contiguous regions of rows that we want to delete.
10825 while let Some(next_selection) = selections.peek() {
10826 let next_rows = next_selection.spanned_rows(false, &display_map);
10827 if next_rows.start <= rows.end {
10828 rows.end = next_rows.end;
10829 selections.next().unwrap();
10830 } else {
10831 break;
10832 }
10833 }
10834
10835 let buffer = display_map.buffer_snapshot();
10836 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10837 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10838 // If there's a line after the range, delete the \n from the end of the row range
10839 (
10840 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10841 rows.end,
10842 )
10843 } else {
10844 // If there isn't a line after the range, delete the \n from the line before the
10845 // start of the row range
10846 edit_start = edit_start.saturating_sub_usize(1);
10847 (buffer.len(), rows.start.previous_row())
10848 };
10849
10850 let text_layout_details = self.text_layout_details(window);
10851 let x = display_map.x_for_display_point(
10852 selection.head().to_display_point(&display_map),
10853 &text_layout_details,
10854 );
10855 let row = Point::new(target_row.0, 0)
10856 .to_display_point(&display_map)
10857 .row();
10858 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10859
10860 new_cursors.push((
10861 selection.id,
10862 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10863 SelectionGoal::None,
10864 ));
10865 edit_ranges.push(edit_start..edit_end);
10866 }
10867
10868 self.transact(window, cx, |this, window, cx| {
10869 let buffer = this.buffer.update(cx, |buffer, cx| {
10870 let empty_str: Arc<str> = Arc::default();
10871 buffer.edit(
10872 edit_ranges
10873 .into_iter()
10874 .map(|range| (range, empty_str.clone())),
10875 None,
10876 cx,
10877 );
10878 buffer.snapshot(cx)
10879 });
10880 let new_selections = new_cursors
10881 .into_iter()
10882 .map(|(id, cursor, goal)| {
10883 let cursor = cursor.to_point(&buffer);
10884 Selection {
10885 id,
10886 start: cursor,
10887 end: cursor,
10888 reversed: false,
10889 goal,
10890 }
10891 })
10892 .collect();
10893
10894 this.change_selections(Default::default(), window, cx, |s| {
10895 s.select(new_selections);
10896 });
10897 });
10898 }
10899
10900 pub fn join_lines_impl(
10901 &mut self,
10902 insert_whitespace: bool,
10903 window: &mut Window,
10904 cx: &mut Context<Self>,
10905 ) {
10906 if self.read_only(cx) {
10907 return;
10908 }
10909 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10910 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10911 let start = MultiBufferRow(selection.start.row);
10912 // Treat single line selections as if they include the next line. Otherwise this action
10913 // would do nothing for single line selections individual cursors.
10914 let end = if selection.start.row == selection.end.row {
10915 MultiBufferRow(selection.start.row + 1)
10916 } else {
10917 MultiBufferRow(selection.end.row)
10918 };
10919
10920 if let Some(last_row_range) = row_ranges.last_mut()
10921 && start <= last_row_range.end
10922 {
10923 last_row_range.end = end;
10924 continue;
10925 }
10926 row_ranges.push(start..end);
10927 }
10928
10929 let snapshot = self.buffer.read(cx).snapshot(cx);
10930 let mut cursor_positions = Vec::new();
10931 for row_range in &row_ranges {
10932 let anchor = snapshot.anchor_before(Point::new(
10933 row_range.end.previous_row().0,
10934 snapshot.line_len(row_range.end.previous_row()),
10935 ));
10936 cursor_positions.push(anchor..anchor);
10937 }
10938
10939 self.transact(window, cx, |this, window, cx| {
10940 for row_range in row_ranges.into_iter().rev() {
10941 for row in row_range.iter_rows().rev() {
10942 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10943 let next_line_row = row.next_row();
10944 let indent = snapshot.indent_size_for_line(next_line_row);
10945 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10946
10947 let replace =
10948 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10949 " "
10950 } else {
10951 ""
10952 };
10953
10954 this.buffer.update(cx, |buffer, cx| {
10955 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10956 });
10957 }
10958 }
10959
10960 this.change_selections(Default::default(), window, cx, |s| {
10961 s.select_anchor_ranges(cursor_positions)
10962 });
10963 });
10964 }
10965
10966 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10967 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10968 self.join_lines_impl(true, window, cx);
10969 }
10970
10971 pub fn sort_lines_case_sensitive(
10972 &mut self,
10973 _: &SortLinesCaseSensitive,
10974 window: &mut Window,
10975 cx: &mut Context<Self>,
10976 ) {
10977 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10978 }
10979
10980 pub fn sort_lines_by_length(
10981 &mut self,
10982 _: &SortLinesByLength,
10983 window: &mut Window,
10984 cx: &mut Context<Self>,
10985 ) {
10986 self.manipulate_immutable_lines(window, cx, |lines| {
10987 lines.sort_by_key(|&line| line.chars().count())
10988 })
10989 }
10990
10991 pub fn sort_lines_case_insensitive(
10992 &mut self,
10993 _: &SortLinesCaseInsensitive,
10994 window: &mut Window,
10995 cx: &mut Context<Self>,
10996 ) {
10997 self.manipulate_immutable_lines(window, cx, |lines| {
10998 lines.sort_by_key(|line| line.to_lowercase())
10999 })
11000 }
11001
11002 pub fn unique_lines_case_insensitive(
11003 &mut self,
11004 _: &UniqueLinesCaseInsensitive,
11005 window: &mut Window,
11006 cx: &mut Context<Self>,
11007 ) {
11008 self.manipulate_immutable_lines(window, cx, |lines| {
11009 let mut seen = HashSet::default();
11010 lines.retain(|line| seen.insert(line.to_lowercase()));
11011 })
11012 }
11013
11014 pub fn unique_lines_case_sensitive(
11015 &mut self,
11016 _: &UniqueLinesCaseSensitive,
11017 window: &mut Window,
11018 cx: &mut Context<Self>,
11019 ) {
11020 self.manipulate_immutable_lines(window, cx, |lines| {
11021 let mut seen = HashSet::default();
11022 lines.retain(|line| seen.insert(*line));
11023 })
11024 }
11025
11026 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
11027 let snapshot = self.buffer.read(cx).snapshot(cx);
11028 for selection in self.selections.disjoint_anchors_arc().iter() {
11029 if snapshot
11030 .language_at(selection.start)
11031 .and_then(|lang| lang.config().wrap_characters.as_ref())
11032 .is_some()
11033 {
11034 return true;
11035 }
11036 }
11037 false
11038 }
11039
11040 fn wrap_selections_in_tag(
11041 &mut self,
11042 _: &WrapSelectionsInTag,
11043 window: &mut Window,
11044 cx: &mut Context<Self>,
11045 ) {
11046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11047
11048 let snapshot = self.buffer.read(cx).snapshot(cx);
11049
11050 let mut edits = Vec::new();
11051 let mut boundaries = Vec::new();
11052
11053 for selection in self
11054 .selections
11055 .all_adjusted(&self.display_snapshot(cx))
11056 .iter()
11057 {
11058 let Some(wrap_config) = snapshot
11059 .language_at(selection.start)
11060 .and_then(|lang| lang.config().wrap_characters.clone())
11061 else {
11062 continue;
11063 };
11064
11065 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11066 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11067
11068 let start_before = snapshot.anchor_before(selection.start);
11069 let end_after = snapshot.anchor_after(selection.end);
11070
11071 edits.push((start_before..start_before, open_tag));
11072 edits.push((end_after..end_after, close_tag));
11073
11074 boundaries.push((
11075 start_before,
11076 end_after,
11077 wrap_config.start_prefix.len(),
11078 wrap_config.end_suffix.len(),
11079 ));
11080 }
11081
11082 if edits.is_empty() {
11083 return;
11084 }
11085
11086 self.transact(window, cx, |this, window, cx| {
11087 let buffer = this.buffer.update(cx, |buffer, cx| {
11088 buffer.edit(edits, None, cx);
11089 buffer.snapshot(cx)
11090 });
11091
11092 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11093 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11094 boundaries.into_iter()
11095 {
11096 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11097 let close_offset = end_after
11098 .to_offset(&buffer)
11099 .saturating_sub_usize(end_suffix_len);
11100 new_selections.push(open_offset..open_offset);
11101 new_selections.push(close_offset..close_offset);
11102 }
11103
11104 this.change_selections(Default::default(), window, cx, |s| {
11105 s.select_ranges(new_selections);
11106 });
11107
11108 this.request_autoscroll(Autoscroll::fit(), cx);
11109 });
11110 }
11111
11112 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11113 let Some(project) = self.project.clone() else {
11114 return;
11115 };
11116 self.reload(project, window, cx)
11117 .detach_and_notify_err(window, cx);
11118 }
11119
11120 pub fn restore_file(
11121 &mut self,
11122 _: &::git::RestoreFile,
11123 window: &mut Window,
11124 cx: &mut Context<Self>,
11125 ) {
11126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11127 let mut buffer_ids = HashSet::default();
11128 let snapshot = self.buffer().read(cx).snapshot(cx);
11129 for selection in self
11130 .selections
11131 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11132 {
11133 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11134 }
11135
11136 let buffer = self.buffer().read(cx);
11137 let ranges = buffer_ids
11138 .into_iter()
11139 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11140 .collect::<Vec<_>>();
11141
11142 self.restore_hunks_in_ranges(ranges, window, cx);
11143 }
11144
11145 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11146 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11147 let selections = self
11148 .selections
11149 .all(&self.display_snapshot(cx))
11150 .into_iter()
11151 .map(|s| s.range())
11152 .collect();
11153 self.restore_hunks_in_ranges(selections, window, cx);
11154 }
11155
11156 pub fn restore_hunks_in_ranges(
11157 &mut self,
11158 ranges: Vec<Range<Point>>,
11159 window: &mut Window,
11160 cx: &mut Context<Editor>,
11161 ) {
11162 let mut revert_changes = HashMap::default();
11163 let chunk_by = self
11164 .snapshot(window, cx)
11165 .hunks_for_ranges(ranges)
11166 .into_iter()
11167 .chunk_by(|hunk| hunk.buffer_id);
11168 for (buffer_id, hunks) in &chunk_by {
11169 let hunks = hunks.collect::<Vec<_>>();
11170 for hunk in &hunks {
11171 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11172 }
11173 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11174 }
11175 drop(chunk_by);
11176 if !revert_changes.is_empty() {
11177 self.transact(window, cx, |editor, window, cx| {
11178 editor.restore(revert_changes, window, cx);
11179 });
11180 }
11181 }
11182
11183 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11184 if let Some(status) = self
11185 .addons
11186 .iter()
11187 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11188 {
11189 return Some(status);
11190 }
11191 self.project
11192 .as_ref()?
11193 .read(cx)
11194 .status_for_buffer_id(buffer_id, cx)
11195 }
11196
11197 pub fn open_active_item_in_terminal(
11198 &mut self,
11199 _: &OpenInTerminal,
11200 window: &mut Window,
11201 cx: &mut Context<Self>,
11202 ) {
11203 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11204 let project_path = buffer.read(cx).project_path(cx)?;
11205 let project = self.project()?.read(cx);
11206 let entry = project.entry_for_path(&project_path, cx)?;
11207 let parent = match &entry.canonical_path {
11208 Some(canonical_path) => canonical_path.to_path_buf(),
11209 None => project.absolute_path(&project_path, cx)?,
11210 }
11211 .parent()?
11212 .to_path_buf();
11213 Some(parent)
11214 }) {
11215 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11216 }
11217 }
11218
11219 fn set_breakpoint_context_menu(
11220 &mut self,
11221 display_row: DisplayRow,
11222 position: Option<Anchor>,
11223 clicked_point: gpui::Point<Pixels>,
11224 window: &mut Window,
11225 cx: &mut Context<Self>,
11226 ) {
11227 let source = self
11228 .buffer
11229 .read(cx)
11230 .snapshot(cx)
11231 .anchor_before(Point::new(display_row.0, 0u32));
11232
11233 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11234
11235 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11236 self,
11237 source,
11238 clicked_point,
11239 context_menu,
11240 window,
11241 cx,
11242 );
11243 }
11244
11245 fn add_edit_breakpoint_block(
11246 &mut self,
11247 anchor: Anchor,
11248 breakpoint: &Breakpoint,
11249 edit_action: BreakpointPromptEditAction,
11250 window: &mut Window,
11251 cx: &mut Context<Self>,
11252 ) {
11253 let weak_editor = cx.weak_entity();
11254 let bp_prompt = cx.new(|cx| {
11255 BreakpointPromptEditor::new(
11256 weak_editor,
11257 anchor,
11258 breakpoint.clone(),
11259 edit_action,
11260 window,
11261 cx,
11262 )
11263 });
11264
11265 let height = bp_prompt.update(cx, |this, cx| {
11266 this.prompt
11267 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11268 });
11269 let cloned_prompt = bp_prompt.clone();
11270 let blocks = vec![BlockProperties {
11271 style: BlockStyle::Sticky,
11272 placement: BlockPlacement::Above(anchor),
11273 height: Some(height),
11274 render: Arc::new(move |cx| {
11275 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11276 cloned_prompt.clone().into_any_element()
11277 }),
11278 priority: 0,
11279 }];
11280
11281 let focus_handle = bp_prompt.focus_handle(cx);
11282 window.focus(&focus_handle);
11283
11284 let block_ids = self.insert_blocks(blocks, None, cx);
11285 bp_prompt.update(cx, |prompt, _| {
11286 prompt.add_block_ids(block_ids);
11287 });
11288 }
11289
11290 pub(crate) fn breakpoint_at_row(
11291 &self,
11292 row: u32,
11293 window: &mut Window,
11294 cx: &mut Context<Self>,
11295 ) -> Option<(Anchor, Breakpoint)> {
11296 let snapshot = self.snapshot(window, cx);
11297 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11298
11299 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11300 }
11301
11302 pub(crate) fn breakpoint_at_anchor(
11303 &self,
11304 breakpoint_position: Anchor,
11305 snapshot: &EditorSnapshot,
11306 cx: &mut Context<Self>,
11307 ) -> Option<(Anchor, Breakpoint)> {
11308 let buffer = self
11309 .buffer
11310 .read(cx)
11311 .buffer_for_anchor(breakpoint_position, cx)?;
11312
11313 let enclosing_excerpt = breakpoint_position.excerpt_id;
11314 let buffer_snapshot = buffer.read(cx).snapshot();
11315
11316 let row = buffer_snapshot
11317 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11318 .row;
11319
11320 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11321 let anchor_end = snapshot
11322 .buffer_snapshot()
11323 .anchor_after(Point::new(row, line_len));
11324
11325 self.breakpoint_store
11326 .as_ref()?
11327 .read_with(cx, |breakpoint_store, cx| {
11328 breakpoint_store
11329 .breakpoints(
11330 &buffer,
11331 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11332 &buffer_snapshot,
11333 cx,
11334 )
11335 .next()
11336 .and_then(|(bp, _)| {
11337 let breakpoint_row = buffer_snapshot
11338 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11339 .row;
11340
11341 if breakpoint_row == row {
11342 snapshot
11343 .buffer_snapshot()
11344 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11345 .map(|position| (position, bp.bp.clone()))
11346 } else {
11347 None
11348 }
11349 })
11350 })
11351 }
11352
11353 pub fn edit_log_breakpoint(
11354 &mut self,
11355 _: &EditLogBreakpoint,
11356 window: &mut Window,
11357 cx: &mut Context<Self>,
11358 ) {
11359 if self.breakpoint_store.is_none() {
11360 return;
11361 }
11362
11363 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11364 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11365 message: None,
11366 state: BreakpointState::Enabled,
11367 condition: None,
11368 hit_condition: None,
11369 });
11370
11371 self.add_edit_breakpoint_block(
11372 anchor,
11373 &breakpoint,
11374 BreakpointPromptEditAction::Log,
11375 window,
11376 cx,
11377 );
11378 }
11379 }
11380
11381 fn breakpoints_at_cursors(
11382 &self,
11383 window: &mut Window,
11384 cx: &mut Context<Self>,
11385 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11386 let snapshot = self.snapshot(window, cx);
11387 let cursors = self
11388 .selections
11389 .disjoint_anchors_arc()
11390 .iter()
11391 .map(|selection| {
11392 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11393
11394 let breakpoint_position = self
11395 .breakpoint_at_row(cursor_position.row, window, cx)
11396 .map(|bp| bp.0)
11397 .unwrap_or_else(|| {
11398 snapshot
11399 .display_snapshot
11400 .buffer_snapshot()
11401 .anchor_after(Point::new(cursor_position.row, 0))
11402 });
11403
11404 let breakpoint = self
11405 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11406 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11407
11408 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11409 })
11410 // 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.
11411 .collect::<HashMap<Anchor, _>>();
11412
11413 cursors.into_iter().collect()
11414 }
11415
11416 pub fn enable_breakpoint(
11417 &mut self,
11418 _: &crate::actions::EnableBreakpoint,
11419 window: &mut Window,
11420 cx: &mut Context<Self>,
11421 ) {
11422 if self.breakpoint_store.is_none() {
11423 return;
11424 }
11425
11426 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11427 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11428 continue;
11429 };
11430 self.edit_breakpoint_at_anchor(
11431 anchor,
11432 breakpoint,
11433 BreakpointEditAction::InvertState,
11434 cx,
11435 );
11436 }
11437 }
11438
11439 pub fn disable_breakpoint(
11440 &mut self,
11441 _: &crate::actions::DisableBreakpoint,
11442 window: &mut Window,
11443 cx: &mut Context<Self>,
11444 ) {
11445 if self.breakpoint_store.is_none() {
11446 return;
11447 }
11448
11449 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11450 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11451 continue;
11452 };
11453 self.edit_breakpoint_at_anchor(
11454 anchor,
11455 breakpoint,
11456 BreakpointEditAction::InvertState,
11457 cx,
11458 );
11459 }
11460 }
11461
11462 pub fn toggle_breakpoint(
11463 &mut self,
11464 _: &crate::actions::ToggleBreakpoint,
11465 window: &mut Window,
11466 cx: &mut Context<Self>,
11467 ) {
11468 if self.breakpoint_store.is_none() {
11469 return;
11470 }
11471
11472 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11473 if let Some(breakpoint) = breakpoint {
11474 self.edit_breakpoint_at_anchor(
11475 anchor,
11476 breakpoint,
11477 BreakpointEditAction::Toggle,
11478 cx,
11479 );
11480 } else {
11481 self.edit_breakpoint_at_anchor(
11482 anchor,
11483 Breakpoint::new_standard(),
11484 BreakpointEditAction::Toggle,
11485 cx,
11486 );
11487 }
11488 }
11489 }
11490
11491 pub fn edit_breakpoint_at_anchor(
11492 &mut self,
11493 breakpoint_position: Anchor,
11494 breakpoint: Breakpoint,
11495 edit_action: BreakpointEditAction,
11496 cx: &mut Context<Self>,
11497 ) {
11498 let Some(breakpoint_store) = &self.breakpoint_store else {
11499 return;
11500 };
11501
11502 let Some(buffer) = self
11503 .buffer
11504 .read(cx)
11505 .buffer_for_anchor(breakpoint_position, cx)
11506 else {
11507 return;
11508 };
11509
11510 breakpoint_store.update(cx, |breakpoint_store, cx| {
11511 breakpoint_store.toggle_breakpoint(
11512 buffer,
11513 BreakpointWithPosition {
11514 position: breakpoint_position.text_anchor,
11515 bp: breakpoint,
11516 },
11517 edit_action,
11518 cx,
11519 );
11520 });
11521
11522 cx.notify();
11523 }
11524
11525 #[cfg(any(test, feature = "test-support"))]
11526 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11527 self.breakpoint_store.clone()
11528 }
11529
11530 pub fn prepare_restore_change(
11531 &self,
11532 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11533 hunk: &MultiBufferDiffHunk,
11534 cx: &mut App,
11535 ) -> Option<()> {
11536 if hunk.is_created_file() {
11537 return None;
11538 }
11539 let buffer = self.buffer.read(cx);
11540 let diff = buffer.diff_for(hunk.buffer_id)?;
11541 let buffer = buffer.buffer(hunk.buffer_id)?;
11542 let buffer = buffer.read(cx);
11543 let original_text = diff
11544 .read(cx)
11545 .base_text()
11546 .as_rope()
11547 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11548 let buffer_snapshot = buffer.snapshot();
11549 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11550 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11551 probe
11552 .0
11553 .start
11554 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11555 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11556 }) {
11557 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11558 Some(())
11559 } else {
11560 None
11561 }
11562 }
11563
11564 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11565 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11566 }
11567
11568 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11569 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11570 }
11571
11572 pub fn rotate_selections_forward(
11573 &mut self,
11574 _: &RotateSelectionsForward,
11575 window: &mut Window,
11576 cx: &mut Context<Self>,
11577 ) {
11578 self.rotate_selections(window, cx, false)
11579 }
11580
11581 pub fn rotate_selections_backward(
11582 &mut self,
11583 _: &RotateSelectionsBackward,
11584 window: &mut Window,
11585 cx: &mut Context<Self>,
11586 ) {
11587 self.rotate_selections(window, cx, true)
11588 }
11589
11590 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11592 let display_snapshot = self.display_snapshot(cx);
11593 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11594
11595 if selections.len() < 2 {
11596 return;
11597 }
11598
11599 let (edits, new_selections) = {
11600 let buffer = self.buffer.read(cx).read(cx);
11601 let has_selections = selections.iter().any(|s| !s.is_empty());
11602 if has_selections {
11603 let mut selected_texts: Vec<String> = selections
11604 .iter()
11605 .map(|selection| {
11606 buffer
11607 .text_for_range(selection.start..selection.end)
11608 .collect()
11609 })
11610 .collect();
11611
11612 if reverse {
11613 selected_texts.rotate_left(1);
11614 } else {
11615 selected_texts.rotate_right(1);
11616 }
11617
11618 let mut offset_delta: i64 = 0;
11619 let mut new_selections = Vec::new();
11620 let edits: Vec<_> = selections
11621 .iter()
11622 .zip(selected_texts.iter())
11623 .map(|(selection, new_text)| {
11624 let old_len = (selection.end.0 - selection.start.0) as i64;
11625 let new_len = new_text.len() as i64;
11626 let adjusted_start =
11627 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11628 let adjusted_end =
11629 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11630
11631 new_selections.push(Selection {
11632 id: selection.id,
11633 start: adjusted_start,
11634 end: adjusted_end,
11635 reversed: selection.reversed,
11636 goal: selection.goal,
11637 });
11638
11639 offset_delta += new_len - old_len;
11640 (selection.start..selection.end, new_text.clone())
11641 })
11642 .collect();
11643 (edits, new_selections)
11644 } else {
11645 let mut all_rows: Vec<u32> = selections
11646 .iter()
11647 .map(|selection| buffer.offset_to_point(selection.start).row)
11648 .collect();
11649 all_rows.sort_unstable();
11650 all_rows.dedup();
11651
11652 if all_rows.len() < 2 {
11653 return;
11654 }
11655
11656 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11657 .iter()
11658 .map(|&row| {
11659 let start = Point::new(row, 0);
11660 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11661 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11662 })
11663 .collect();
11664
11665 let mut line_texts: Vec<String> = line_ranges
11666 .iter()
11667 .map(|range| buffer.text_for_range(range.clone()).collect())
11668 .collect();
11669
11670 if reverse {
11671 line_texts.rotate_left(1);
11672 } else {
11673 line_texts.rotate_right(1);
11674 }
11675
11676 let edits = line_ranges
11677 .iter()
11678 .zip(line_texts.iter())
11679 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11680 .collect();
11681
11682 let num_rows = all_rows.len();
11683 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11684 .iter()
11685 .enumerate()
11686 .map(|(i, &row)| (row, i))
11687 .collect();
11688
11689 // Compute new line start offsets after rotation (handles CRLF)
11690 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11691 let first_line_start = line_ranges[0].start.0;
11692 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11693 for text in line_texts.iter().take(num_rows - 1) {
11694 let prev_start = *new_line_starts.last().unwrap();
11695 new_line_starts.push(prev_start + text.len() + newline_len);
11696 }
11697
11698 let new_selections = selections
11699 .iter()
11700 .map(|selection| {
11701 let point = buffer.offset_to_point(selection.start);
11702 let old_index = row_to_index[&point.row];
11703 let new_index = if reverse {
11704 (old_index + num_rows - 1) % num_rows
11705 } else {
11706 (old_index + 1) % num_rows
11707 };
11708 let new_offset =
11709 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11710 Selection {
11711 id: selection.id,
11712 start: new_offset,
11713 end: new_offset,
11714 reversed: selection.reversed,
11715 goal: selection.goal,
11716 }
11717 })
11718 .collect();
11719
11720 (edits, new_selections)
11721 }
11722 };
11723
11724 self.transact(window, cx, |this, window, cx| {
11725 this.buffer.update(cx, |buffer, cx| {
11726 buffer.edit(edits, None, cx);
11727 });
11728 this.change_selections(Default::default(), window, cx, |s| {
11729 s.select(new_selections);
11730 });
11731 });
11732 }
11733
11734 fn manipulate_lines<M>(
11735 &mut self,
11736 window: &mut Window,
11737 cx: &mut Context<Self>,
11738 mut manipulate: M,
11739 ) where
11740 M: FnMut(&str) -> LineManipulationResult,
11741 {
11742 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11743
11744 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11745 let buffer = self.buffer.read(cx).snapshot(cx);
11746
11747 let mut edits = Vec::new();
11748
11749 let selections = self.selections.all::<Point>(&display_map);
11750 let mut selections = selections.iter().peekable();
11751 let mut contiguous_row_selections = Vec::new();
11752 let mut new_selections = Vec::new();
11753 let mut added_lines = 0;
11754 let mut removed_lines = 0;
11755
11756 while let Some(selection) = selections.next() {
11757 let (start_row, end_row) = consume_contiguous_rows(
11758 &mut contiguous_row_selections,
11759 selection,
11760 &display_map,
11761 &mut selections,
11762 );
11763
11764 let start_point = Point::new(start_row.0, 0);
11765 let end_point = Point::new(
11766 end_row.previous_row().0,
11767 buffer.line_len(end_row.previous_row()),
11768 );
11769 let text = buffer
11770 .text_for_range(start_point..end_point)
11771 .collect::<String>();
11772
11773 let LineManipulationResult {
11774 new_text,
11775 line_count_before,
11776 line_count_after,
11777 } = manipulate(&text);
11778
11779 edits.push((start_point..end_point, new_text));
11780
11781 // Selections must change based on added and removed line count
11782 let start_row =
11783 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11784 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11785 new_selections.push(Selection {
11786 id: selection.id,
11787 start: start_row,
11788 end: end_row,
11789 goal: SelectionGoal::None,
11790 reversed: selection.reversed,
11791 });
11792
11793 if line_count_after > line_count_before {
11794 added_lines += line_count_after - line_count_before;
11795 } else if line_count_before > line_count_after {
11796 removed_lines += line_count_before - line_count_after;
11797 }
11798 }
11799
11800 self.transact(window, cx, |this, window, cx| {
11801 let buffer = this.buffer.update(cx, |buffer, cx| {
11802 buffer.edit(edits, None, cx);
11803 buffer.snapshot(cx)
11804 });
11805
11806 // Recalculate offsets on newly edited buffer
11807 let new_selections = new_selections
11808 .iter()
11809 .map(|s| {
11810 let start_point = Point::new(s.start.0, 0);
11811 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11812 Selection {
11813 id: s.id,
11814 start: buffer.point_to_offset(start_point),
11815 end: buffer.point_to_offset(end_point),
11816 goal: s.goal,
11817 reversed: s.reversed,
11818 }
11819 })
11820 .collect();
11821
11822 this.change_selections(Default::default(), window, cx, |s| {
11823 s.select(new_selections);
11824 });
11825
11826 this.request_autoscroll(Autoscroll::fit(), cx);
11827 });
11828 }
11829
11830 fn manipulate_immutable_lines<Fn>(
11831 &mut self,
11832 window: &mut Window,
11833 cx: &mut Context<Self>,
11834 mut callback: Fn,
11835 ) where
11836 Fn: FnMut(&mut Vec<&str>),
11837 {
11838 self.manipulate_lines(window, cx, |text| {
11839 let mut lines: Vec<&str> = text.split('\n').collect();
11840 let line_count_before = lines.len();
11841
11842 callback(&mut lines);
11843
11844 LineManipulationResult {
11845 new_text: lines.join("\n"),
11846 line_count_before,
11847 line_count_after: lines.len(),
11848 }
11849 });
11850 }
11851
11852 fn manipulate_mutable_lines<Fn>(
11853 &mut self,
11854 window: &mut Window,
11855 cx: &mut Context<Self>,
11856 mut callback: Fn,
11857 ) where
11858 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11859 {
11860 self.manipulate_lines(window, cx, |text| {
11861 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11862 let line_count_before = lines.len();
11863
11864 callback(&mut lines);
11865
11866 LineManipulationResult {
11867 new_text: lines.join("\n"),
11868 line_count_before,
11869 line_count_after: lines.len(),
11870 }
11871 });
11872 }
11873
11874 pub fn convert_indentation_to_spaces(
11875 &mut self,
11876 _: &ConvertIndentationToSpaces,
11877 window: &mut Window,
11878 cx: &mut Context<Self>,
11879 ) {
11880 let settings = self.buffer.read(cx).language_settings(cx);
11881 let tab_size = settings.tab_size.get() as usize;
11882
11883 self.manipulate_mutable_lines(window, cx, |lines| {
11884 // Allocates a reasonably sized scratch buffer once for the whole loop
11885 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11886 // Avoids recomputing spaces that could be inserted many times
11887 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11888 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11889 .collect();
11890
11891 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11892 let mut chars = line.as_ref().chars();
11893 let mut col = 0;
11894 let mut changed = false;
11895
11896 for ch in chars.by_ref() {
11897 match ch {
11898 ' ' => {
11899 reindented_line.push(' ');
11900 col += 1;
11901 }
11902 '\t' => {
11903 // \t are converted to spaces depending on the current column
11904 let spaces_len = tab_size - (col % tab_size);
11905 reindented_line.extend(&space_cache[spaces_len - 1]);
11906 col += spaces_len;
11907 changed = true;
11908 }
11909 _ => {
11910 // If we dont append before break, the character is consumed
11911 reindented_line.push(ch);
11912 break;
11913 }
11914 }
11915 }
11916
11917 if !changed {
11918 reindented_line.clear();
11919 continue;
11920 }
11921 // Append the rest of the line and replace old reference with new one
11922 reindented_line.extend(chars);
11923 *line = Cow::Owned(reindented_line.clone());
11924 reindented_line.clear();
11925 }
11926 });
11927 }
11928
11929 pub fn convert_indentation_to_tabs(
11930 &mut self,
11931 _: &ConvertIndentationToTabs,
11932 window: &mut Window,
11933 cx: &mut Context<Self>,
11934 ) {
11935 let settings = self.buffer.read(cx).language_settings(cx);
11936 let tab_size = settings.tab_size.get() as usize;
11937
11938 self.manipulate_mutable_lines(window, cx, |lines| {
11939 // Allocates a reasonably sized buffer once for the whole loop
11940 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11941 // Avoids recomputing spaces that could be inserted many times
11942 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11943 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11944 .collect();
11945
11946 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11947 let mut chars = line.chars();
11948 let mut spaces_count = 0;
11949 let mut first_non_indent_char = None;
11950 let mut changed = false;
11951
11952 for ch in chars.by_ref() {
11953 match ch {
11954 ' ' => {
11955 // Keep track of spaces. Append \t when we reach tab_size
11956 spaces_count += 1;
11957 changed = true;
11958 if spaces_count == tab_size {
11959 reindented_line.push('\t');
11960 spaces_count = 0;
11961 }
11962 }
11963 '\t' => {
11964 reindented_line.push('\t');
11965 spaces_count = 0;
11966 }
11967 _ => {
11968 // Dont append it yet, we might have remaining spaces
11969 first_non_indent_char = Some(ch);
11970 break;
11971 }
11972 }
11973 }
11974
11975 if !changed {
11976 reindented_line.clear();
11977 continue;
11978 }
11979 // Remaining spaces that didn't make a full tab stop
11980 if spaces_count > 0 {
11981 reindented_line.extend(&space_cache[spaces_count - 1]);
11982 }
11983 // If we consume an extra character that was not indentation, add it back
11984 if let Some(extra_char) = first_non_indent_char {
11985 reindented_line.push(extra_char);
11986 }
11987 // Append the rest of the line and replace old reference with new one
11988 reindented_line.extend(chars);
11989 *line = Cow::Owned(reindented_line.clone());
11990 reindented_line.clear();
11991 }
11992 });
11993 }
11994
11995 pub fn convert_to_upper_case(
11996 &mut self,
11997 _: &ConvertToUpperCase,
11998 window: &mut Window,
11999 cx: &mut Context<Self>,
12000 ) {
12001 self.manipulate_text(window, cx, |text| text.to_uppercase())
12002 }
12003
12004 pub fn convert_to_lower_case(
12005 &mut self,
12006 _: &ConvertToLowerCase,
12007 window: &mut Window,
12008 cx: &mut Context<Self>,
12009 ) {
12010 self.manipulate_text(window, cx, |text| text.to_lowercase())
12011 }
12012
12013 pub fn convert_to_title_case(
12014 &mut self,
12015 _: &ConvertToTitleCase,
12016 window: &mut Window,
12017 cx: &mut Context<Self>,
12018 ) {
12019 self.manipulate_text(window, cx, |text| {
12020 text.split('\n')
12021 .map(|line| line.to_case(Case::Title))
12022 .join("\n")
12023 })
12024 }
12025
12026 pub fn convert_to_snake_case(
12027 &mut self,
12028 _: &ConvertToSnakeCase,
12029 window: &mut Window,
12030 cx: &mut Context<Self>,
12031 ) {
12032 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
12033 }
12034
12035 pub fn convert_to_kebab_case(
12036 &mut self,
12037 _: &ConvertToKebabCase,
12038 window: &mut Window,
12039 cx: &mut Context<Self>,
12040 ) {
12041 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12042 }
12043
12044 pub fn convert_to_upper_camel_case(
12045 &mut self,
12046 _: &ConvertToUpperCamelCase,
12047 window: &mut Window,
12048 cx: &mut Context<Self>,
12049 ) {
12050 self.manipulate_text(window, cx, |text| {
12051 text.split('\n')
12052 .map(|line| line.to_case(Case::UpperCamel))
12053 .join("\n")
12054 })
12055 }
12056
12057 pub fn convert_to_lower_camel_case(
12058 &mut self,
12059 _: &ConvertToLowerCamelCase,
12060 window: &mut Window,
12061 cx: &mut Context<Self>,
12062 ) {
12063 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12064 }
12065
12066 pub fn convert_to_opposite_case(
12067 &mut self,
12068 _: &ConvertToOppositeCase,
12069 window: &mut Window,
12070 cx: &mut Context<Self>,
12071 ) {
12072 self.manipulate_text(window, cx, |text| {
12073 text.chars()
12074 .fold(String::with_capacity(text.len()), |mut t, c| {
12075 if c.is_uppercase() {
12076 t.extend(c.to_lowercase());
12077 } else {
12078 t.extend(c.to_uppercase());
12079 }
12080 t
12081 })
12082 })
12083 }
12084
12085 pub fn convert_to_sentence_case(
12086 &mut self,
12087 _: &ConvertToSentenceCase,
12088 window: &mut Window,
12089 cx: &mut Context<Self>,
12090 ) {
12091 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12092 }
12093
12094 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12095 self.manipulate_text(window, cx, |text| {
12096 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12097 if has_upper_case_characters {
12098 text.to_lowercase()
12099 } else {
12100 text.to_uppercase()
12101 }
12102 })
12103 }
12104
12105 pub fn convert_to_rot13(
12106 &mut self,
12107 _: &ConvertToRot13,
12108 window: &mut Window,
12109 cx: &mut Context<Self>,
12110 ) {
12111 self.manipulate_text(window, cx, |text| {
12112 text.chars()
12113 .map(|c| match c {
12114 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12115 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12116 _ => c,
12117 })
12118 .collect()
12119 })
12120 }
12121
12122 pub fn convert_to_rot47(
12123 &mut self,
12124 _: &ConvertToRot47,
12125 window: &mut Window,
12126 cx: &mut Context<Self>,
12127 ) {
12128 self.manipulate_text(window, cx, |text| {
12129 text.chars()
12130 .map(|c| {
12131 let code_point = c as u32;
12132 if code_point >= 33 && code_point <= 126 {
12133 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12134 }
12135 c
12136 })
12137 .collect()
12138 })
12139 }
12140
12141 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12142 where
12143 Fn: FnMut(&str) -> String,
12144 {
12145 let buffer = self.buffer.read(cx).snapshot(cx);
12146
12147 let mut new_selections = Vec::new();
12148 let mut edits = Vec::new();
12149 let mut selection_adjustment = 0isize;
12150
12151 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12152 let selection_is_empty = selection.is_empty();
12153
12154 let (start, end) = if selection_is_empty {
12155 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12156 (word_range.start, word_range.end)
12157 } else {
12158 (
12159 buffer.point_to_offset(selection.start),
12160 buffer.point_to_offset(selection.end),
12161 )
12162 };
12163
12164 let text = buffer.text_for_range(start..end).collect::<String>();
12165 let old_length = text.len() as isize;
12166 let text = callback(&text);
12167
12168 new_selections.push(Selection {
12169 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12170 end: MultiBufferOffset(
12171 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12172 ),
12173 goal: SelectionGoal::None,
12174 id: selection.id,
12175 reversed: selection.reversed,
12176 });
12177
12178 selection_adjustment += old_length - text.len() as isize;
12179
12180 edits.push((start..end, text));
12181 }
12182
12183 self.transact(window, cx, |this, window, cx| {
12184 this.buffer.update(cx, |buffer, cx| {
12185 buffer.edit(edits, None, cx);
12186 });
12187
12188 this.change_selections(Default::default(), window, cx, |s| {
12189 s.select(new_selections);
12190 });
12191
12192 this.request_autoscroll(Autoscroll::fit(), cx);
12193 });
12194 }
12195
12196 pub fn move_selection_on_drop(
12197 &mut self,
12198 selection: &Selection<Anchor>,
12199 target: DisplayPoint,
12200 is_cut: bool,
12201 window: &mut Window,
12202 cx: &mut Context<Self>,
12203 ) {
12204 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12205 let buffer = display_map.buffer_snapshot();
12206 let mut edits = Vec::new();
12207 let insert_point = display_map
12208 .clip_point(target, Bias::Left)
12209 .to_point(&display_map);
12210 let text = buffer
12211 .text_for_range(selection.start..selection.end)
12212 .collect::<String>();
12213 if is_cut {
12214 edits.push(((selection.start..selection.end), String::new()));
12215 }
12216 let insert_anchor = buffer.anchor_before(insert_point);
12217 edits.push(((insert_anchor..insert_anchor), text));
12218 let last_edit_start = insert_anchor.bias_left(buffer);
12219 let last_edit_end = insert_anchor.bias_right(buffer);
12220 self.transact(window, cx, |this, window, cx| {
12221 this.buffer.update(cx, |buffer, cx| {
12222 buffer.edit(edits, None, cx);
12223 });
12224 this.change_selections(Default::default(), window, cx, |s| {
12225 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12226 });
12227 });
12228 }
12229
12230 pub fn clear_selection_drag_state(&mut self) {
12231 self.selection_drag_state = SelectionDragState::None;
12232 }
12233
12234 pub fn duplicate(
12235 &mut self,
12236 upwards: bool,
12237 whole_lines: bool,
12238 window: &mut Window,
12239 cx: &mut Context<Self>,
12240 ) {
12241 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12242
12243 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12244 let buffer = display_map.buffer_snapshot();
12245 let selections = self.selections.all::<Point>(&display_map);
12246
12247 let mut edits = Vec::new();
12248 let mut selections_iter = selections.iter().peekable();
12249 while let Some(selection) = selections_iter.next() {
12250 let mut rows = selection.spanned_rows(false, &display_map);
12251 // duplicate line-wise
12252 if whole_lines || selection.start == selection.end {
12253 // Avoid duplicating the same lines twice.
12254 while let Some(next_selection) = selections_iter.peek() {
12255 let next_rows = next_selection.spanned_rows(false, &display_map);
12256 if next_rows.start < rows.end {
12257 rows.end = next_rows.end;
12258 selections_iter.next().unwrap();
12259 } else {
12260 break;
12261 }
12262 }
12263
12264 // Copy the text from the selected row region and splice it either at the start
12265 // or end of the region.
12266 let start = Point::new(rows.start.0, 0);
12267 let end = Point::new(
12268 rows.end.previous_row().0,
12269 buffer.line_len(rows.end.previous_row()),
12270 );
12271
12272 let mut text = buffer.text_for_range(start..end).collect::<String>();
12273
12274 let insert_location = if upwards {
12275 // When duplicating upward, we need to insert before the current line.
12276 // If we're on the last line and it doesn't end with a newline,
12277 // we need to add a newline before the duplicated content.
12278 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12279 && buffer.max_point().column > 0
12280 && !text.ends_with('\n');
12281
12282 if needs_leading_newline {
12283 text.insert(0, '\n');
12284 end
12285 } else {
12286 text.push('\n');
12287 Point::new(rows.start.0, 0)
12288 }
12289 } else {
12290 text.push('\n');
12291 start
12292 };
12293 edits.push((insert_location..insert_location, text));
12294 } else {
12295 // duplicate character-wise
12296 let start = selection.start;
12297 let end = selection.end;
12298 let text = buffer.text_for_range(start..end).collect::<String>();
12299 edits.push((selection.end..selection.end, text));
12300 }
12301 }
12302
12303 self.transact(window, cx, |this, window, cx| {
12304 this.buffer.update(cx, |buffer, cx| {
12305 buffer.edit(edits, None, cx);
12306 });
12307
12308 // When duplicating upward with whole lines, move the cursor to the duplicated line
12309 if upwards && whole_lines {
12310 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12311
12312 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12313 let mut new_ranges = Vec::new();
12314 let selections = s.all::<Point>(&display_map);
12315 let mut selections_iter = selections.iter().peekable();
12316
12317 while let Some(first_selection) = selections_iter.next() {
12318 // Group contiguous selections together to find the total row span
12319 let mut group_selections = vec![first_selection];
12320 let mut rows = first_selection.spanned_rows(false, &display_map);
12321
12322 while let Some(next_selection) = selections_iter.peek() {
12323 let next_rows = next_selection.spanned_rows(false, &display_map);
12324 if next_rows.start < rows.end {
12325 rows.end = next_rows.end;
12326 group_selections.push(selections_iter.next().unwrap());
12327 } else {
12328 break;
12329 }
12330 }
12331
12332 let row_count = rows.end.0 - rows.start.0;
12333
12334 // Move all selections in this group up by the total number of duplicated rows
12335 for selection in group_selections {
12336 let new_start = Point::new(
12337 selection.start.row.saturating_sub(row_count),
12338 selection.start.column,
12339 );
12340
12341 let new_end = Point::new(
12342 selection.end.row.saturating_sub(row_count),
12343 selection.end.column,
12344 );
12345
12346 new_ranges.push(new_start..new_end);
12347 }
12348 }
12349
12350 s.select_ranges(new_ranges);
12351 });
12352 }
12353
12354 this.request_autoscroll(Autoscroll::fit(), cx);
12355 });
12356 }
12357
12358 pub fn duplicate_line_up(
12359 &mut self,
12360 _: &DuplicateLineUp,
12361 window: &mut Window,
12362 cx: &mut Context<Self>,
12363 ) {
12364 self.duplicate(true, true, window, cx);
12365 }
12366
12367 pub fn duplicate_line_down(
12368 &mut self,
12369 _: &DuplicateLineDown,
12370 window: &mut Window,
12371 cx: &mut Context<Self>,
12372 ) {
12373 self.duplicate(false, true, window, cx);
12374 }
12375
12376 pub fn duplicate_selection(
12377 &mut self,
12378 _: &DuplicateSelection,
12379 window: &mut Window,
12380 cx: &mut Context<Self>,
12381 ) {
12382 self.duplicate(false, false, window, cx);
12383 }
12384
12385 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12386 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12387 if self.mode.is_single_line() {
12388 cx.propagate();
12389 return;
12390 }
12391
12392 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12393 let buffer = self.buffer.read(cx).snapshot(cx);
12394
12395 let mut edits = Vec::new();
12396 let mut unfold_ranges = Vec::new();
12397 let mut refold_creases = Vec::new();
12398
12399 let selections = self.selections.all::<Point>(&display_map);
12400 let mut selections = selections.iter().peekable();
12401 let mut contiguous_row_selections = Vec::new();
12402 let mut new_selections = Vec::new();
12403
12404 while let Some(selection) = selections.next() {
12405 // Find all the selections that span a contiguous row range
12406 let (start_row, end_row) = consume_contiguous_rows(
12407 &mut contiguous_row_selections,
12408 selection,
12409 &display_map,
12410 &mut selections,
12411 );
12412
12413 // Move the text spanned by the row range to be before the line preceding the row range
12414 if start_row.0 > 0 {
12415 let range_to_move = Point::new(
12416 start_row.previous_row().0,
12417 buffer.line_len(start_row.previous_row()),
12418 )
12419 ..Point::new(
12420 end_row.previous_row().0,
12421 buffer.line_len(end_row.previous_row()),
12422 );
12423 let insertion_point = display_map
12424 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12425 .0;
12426
12427 // Don't move lines across excerpts
12428 if buffer
12429 .excerpt_containing(insertion_point..range_to_move.end)
12430 .is_some()
12431 {
12432 let text = buffer
12433 .text_for_range(range_to_move.clone())
12434 .flat_map(|s| s.chars())
12435 .skip(1)
12436 .chain(['\n'])
12437 .collect::<String>();
12438
12439 edits.push((
12440 buffer.anchor_after(range_to_move.start)
12441 ..buffer.anchor_before(range_to_move.end),
12442 String::new(),
12443 ));
12444 let insertion_anchor = buffer.anchor_after(insertion_point);
12445 edits.push((insertion_anchor..insertion_anchor, text));
12446
12447 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12448
12449 // Move selections up
12450 new_selections.extend(contiguous_row_selections.drain(..).map(
12451 |mut selection| {
12452 selection.start.row -= row_delta;
12453 selection.end.row -= row_delta;
12454 selection
12455 },
12456 ));
12457
12458 // Move folds up
12459 unfold_ranges.push(range_to_move.clone());
12460 for fold in display_map.folds_in_range(
12461 buffer.anchor_before(range_to_move.start)
12462 ..buffer.anchor_after(range_to_move.end),
12463 ) {
12464 let mut start = fold.range.start.to_point(&buffer);
12465 let mut end = fold.range.end.to_point(&buffer);
12466 start.row -= row_delta;
12467 end.row -= row_delta;
12468 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12469 }
12470 }
12471 }
12472
12473 // If we didn't move line(s), preserve the existing selections
12474 new_selections.append(&mut contiguous_row_selections);
12475 }
12476
12477 self.transact(window, cx, |this, window, cx| {
12478 this.unfold_ranges(&unfold_ranges, true, true, cx);
12479 this.buffer.update(cx, |buffer, cx| {
12480 for (range, text) in edits {
12481 buffer.edit([(range, text)], None, cx);
12482 }
12483 });
12484 this.fold_creases(refold_creases, true, window, cx);
12485 this.change_selections(Default::default(), window, cx, |s| {
12486 s.select(new_selections);
12487 })
12488 });
12489 }
12490
12491 pub fn move_line_down(
12492 &mut self,
12493 _: &MoveLineDown,
12494 window: &mut Window,
12495 cx: &mut Context<Self>,
12496 ) {
12497 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12498 if self.mode.is_single_line() {
12499 cx.propagate();
12500 return;
12501 }
12502
12503 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12504 let buffer = self.buffer.read(cx).snapshot(cx);
12505
12506 let mut edits = Vec::new();
12507 let mut unfold_ranges = Vec::new();
12508 let mut refold_creases = Vec::new();
12509
12510 let selections = self.selections.all::<Point>(&display_map);
12511 let mut selections = selections.iter().peekable();
12512 let mut contiguous_row_selections = Vec::new();
12513 let mut new_selections = Vec::new();
12514
12515 while let Some(selection) = selections.next() {
12516 // Find all the selections that span a contiguous row range
12517 let (start_row, end_row) = consume_contiguous_rows(
12518 &mut contiguous_row_selections,
12519 selection,
12520 &display_map,
12521 &mut selections,
12522 );
12523
12524 // Move the text spanned by the row range to be after the last line of the row range
12525 if end_row.0 <= buffer.max_point().row {
12526 let range_to_move =
12527 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12528 let insertion_point = display_map
12529 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12530 .0;
12531
12532 // Don't move lines across excerpt boundaries
12533 if buffer
12534 .excerpt_containing(range_to_move.start..insertion_point)
12535 .is_some()
12536 {
12537 let mut text = String::from("\n");
12538 text.extend(buffer.text_for_range(range_to_move.clone()));
12539 text.pop(); // Drop trailing newline
12540 edits.push((
12541 buffer.anchor_after(range_to_move.start)
12542 ..buffer.anchor_before(range_to_move.end),
12543 String::new(),
12544 ));
12545 let insertion_anchor = buffer.anchor_after(insertion_point);
12546 edits.push((insertion_anchor..insertion_anchor, text));
12547
12548 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12549
12550 // Move selections down
12551 new_selections.extend(contiguous_row_selections.drain(..).map(
12552 |mut selection| {
12553 selection.start.row += row_delta;
12554 selection.end.row += row_delta;
12555 selection
12556 },
12557 ));
12558
12559 // Move folds down
12560 unfold_ranges.push(range_to_move.clone());
12561 for fold in display_map.folds_in_range(
12562 buffer.anchor_before(range_to_move.start)
12563 ..buffer.anchor_after(range_to_move.end),
12564 ) {
12565 let mut start = fold.range.start.to_point(&buffer);
12566 let mut end = fold.range.end.to_point(&buffer);
12567 start.row += row_delta;
12568 end.row += row_delta;
12569 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12570 }
12571 }
12572 }
12573
12574 // If we didn't move line(s), preserve the existing selections
12575 new_selections.append(&mut contiguous_row_selections);
12576 }
12577
12578 self.transact(window, cx, |this, window, cx| {
12579 this.unfold_ranges(&unfold_ranges, true, true, cx);
12580 this.buffer.update(cx, |buffer, cx| {
12581 for (range, text) in edits {
12582 buffer.edit([(range, text)], None, cx);
12583 }
12584 });
12585 this.fold_creases(refold_creases, true, window, cx);
12586 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12587 });
12588 }
12589
12590 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12591 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12592 let text_layout_details = &self.text_layout_details(window);
12593 self.transact(window, cx, |this, window, cx| {
12594 let edits = this.change_selections(Default::default(), window, cx, |s| {
12595 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12596 s.move_with(|display_map, selection| {
12597 if !selection.is_empty() {
12598 return;
12599 }
12600
12601 let mut head = selection.head();
12602 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12603 if head.column() == display_map.line_len(head.row()) {
12604 transpose_offset = display_map
12605 .buffer_snapshot()
12606 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12607 }
12608
12609 if transpose_offset == MultiBufferOffset(0) {
12610 return;
12611 }
12612
12613 *head.column_mut() += 1;
12614 head = display_map.clip_point(head, Bias::Right);
12615 let goal = SelectionGoal::HorizontalPosition(
12616 display_map
12617 .x_for_display_point(head, text_layout_details)
12618 .into(),
12619 );
12620 selection.collapse_to(head, goal);
12621
12622 let transpose_start = display_map
12623 .buffer_snapshot()
12624 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12625 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12626 let transpose_end = display_map
12627 .buffer_snapshot()
12628 .clip_offset(transpose_offset + 1usize, Bias::Right);
12629 if let Some(ch) = display_map
12630 .buffer_snapshot()
12631 .chars_at(transpose_start)
12632 .next()
12633 {
12634 edits.push((transpose_start..transpose_offset, String::new()));
12635 edits.push((transpose_end..transpose_end, ch.to_string()));
12636 }
12637 }
12638 });
12639 edits
12640 });
12641 this.buffer
12642 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12643 let selections = this
12644 .selections
12645 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12646 this.change_selections(Default::default(), window, cx, |s| {
12647 s.select(selections);
12648 });
12649 });
12650 }
12651
12652 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12653 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12654 if self.mode.is_single_line() {
12655 cx.propagate();
12656 return;
12657 }
12658
12659 self.rewrap_impl(RewrapOptions::default(), cx)
12660 }
12661
12662 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12663 let buffer = self.buffer.read(cx).snapshot(cx);
12664 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12665
12666 #[derive(Clone, Debug, PartialEq)]
12667 enum CommentFormat {
12668 /// single line comment, with prefix for line
12669 Line(String),
12670 /// single line within a block comment, with prefix for line
12671 BlockLine(String),
12672 /// a single line of a block comment that includes the initial delimiter
12673 BlockCommentWithStart(BlockCommentConfig),
12674 /// a single line of a block comment that includes the ending delimiter
12675 BlockCommentWithEnd(BlockCommentConfig),
12676 }
12677
12678 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12679 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12680 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12681 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12682 .peekable();
12683
12684 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12685 row
12686 } else {
12687 return Vec::new();
12688 };
12689
12690 let language_settings = buffer.language_settings_at(selection.head(), cx);
12691 let language_scope = buffer.language_scope_at(selection.head());
12692
12693 let indent_and_prefix_for_row =
12694 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12695 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12696 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12697 &language_scope
12698 {
12699 let indent_end = Point::new(row, indent.len);
12700 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12701 let line_text_after_indent = buffer
12702 .text_for_range(indent_end..line_end)
12703 .collect::<String>();
12704
12705 let is_within_comment_override = buffer
12706 .language_scope_at(indent_end)
12707 .is_some_and(|scope| scope.override_name() == Some("comment"));
12708 let comment_delimiters = if is_within_comment_override {
12709 // we are within a comment syntax node, but we don't
12710 // yet know what kind of comment: block, doc or line
12711 match (
12712 language_scope.documentation_comment(),
12713 language_scope.block_comment(),
12714 ) {
12715 (Some(config), _) | (_, Some(config))
12716 if buffer.contains_str_at(indent_end, &config.start) =>
12717 {
12718 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12719 }
12720 (Some(config), _) | (_, Some(config))
12721 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12722 {
12723 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12724 }
12725 (Some(config), _) | (_, Some(config))
12726 if buffer.contains_str_at(indent_end, &config.prefix) =>
12727 {
12728 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12729 }
12730 (_, _) => language_scope
12731 .line_comment_prefixes()
12732 .iter()
12733 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12734 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12735 }
12736 } else {
12737 // we not in an overridden comment node, but we may
12738 // be within a non-overridden line comment node
12739 language_scope
12740 .line_comment_prefixes()
12741 .iter()
12742 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12743 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12744 };
12745
12746 let rewrap_prefix = language_scope
12747 .rewrap_prefixes()
12748 .iter()
12749 .find_map(|prefix_regex| {
12750 prefix_regex.find(&line_text_after_indent).map(|mat| {
12751 if mat.start() == 0 {
12752 Some(mat.as_str().to_string())
12753 } else {
12754 None
12755 }
12756 })
12757 })
12758 .flatten();
12759 (comment_delimiters, rewrap_prefix)
12760 } else {
12761 (None, None)
12762 };
12763 (indent, comment_prefix, rewrap_prefix)
12764 };
12765
12766 let mut ranges = Vec::new();
12767 let from_empty_selection = selection.is_empty();
12768
12769 let mut current_range_start = first_row;
12770 let mut prev_row = first_row;
12771 let (
12772 mut current_range_indent,
12773 mut current_range_comment_delimiters,
12774 mut current_range_rewrap_prefix,
12775 ) = indent_and_prefix_for_row(first_row);
12776
12777 for row in non_blank_rows_iter.skip(1) {
12778 let has_paragraph_break = row > prev_row + 1;
12779
12780 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12781 indent_and_prefix_for_row(row);
12782
12783 let has_indent_change = row_indent != current_range_indent;
12784 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12785
12786 let has_boundary_change = has_comment_change
12787 || row_rewrap_prefix.is_some()
12788 || (has_indent_change && current_range_comment_delimiters.is_some());
12789
12790 if has_paragraph_break || has_boundary_change {
12791 ranges.push((
12792 language_settings.clone(),
12793 Point::new(current_range_start, 0)
12794 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12795 current_range_indent,
12796 current_range_comment_delimiters.clone(),
12797 current_range_rewrap_prefix.clone(),
12798 from_empty_selection,
12799 ));
12800 current_range_start = row;
12801 current_range_indent = row_indent;
12802 current_range_comment_delimiters = row_comment_delimiters;
12803 current_range_rewrap_prefix = row_rewrap_prefix;
12804 }
12805 prev_row = row;
12806 }
12807
12808 ranges.push((
12809 language_settings.clone(),
12810 Point::new(current_range_start, 0)
12811 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12812 current_range_indent,
12813 current_range_comment_delimiters,
12814 current_range_rewrap_prefix,
12815 from_empty_selection,
12816 ));
12817
12818 ranges
12819 });
12820
12821 let mut edits = Vec::new();
12822 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12823
12824 for (
12825 language_settings,
12826 wrap_range,
12827 mut indent_size,
12828 comment_prefix,
12829 rewrap_prefix,
12830 from_empty_selection,
12831 ) in wrap_ranges
12832 {
12833 let mut start_row = wrap_range.start.row;
12834 let mut end_row = wrap_range.end.row;
12835
12836 // Skip selections that overlap with a range that has already been rewrapped.
12837 let selection_range = start_row..end_row;
12838 if rewrapped_row_ranges
12839 .iter()
12840 .any(|range| range.overlaps(&selection_range))
12841 {
12842 continue;
12843 }
12844
12845 let tab_size = language_settings.tab_size;
12846
12847 let (line_prefix, inside_comment) = match &comment_prefix {
12848 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12849 (Some(prefix.as_str()), true)
12850 }
12851 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12852 (Some(prefix.as_ref()), true)
12853 }
12854 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12855 start: _,
12856 end: _,
12857 prefix,
12858 tab_size,
12859 })) => {
12860 indent_size.len += tab_size;
12861 (Some(prefix.as_ref()), true)
12862 }
12863 None => (None, false),
12864 };
12865 let indent_prefix = indent_size.chars().collect::<String>();
12866 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12867
12868 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12869 RewrapBehavior::InComments => inside_comment,
12870 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12871 RewrapBehavior::Anywhere => true,
12872 };
12873
12874 let should_rewrap = options.override_language_settings
12875 || allow_rewrap_based_on_language
12876 || self.hard_wrap.is_some();
12877 if !should_rewrap {
12878 continue;
12879 }
12880
12881 if from_empty_selection {
12882 'expand_upwards: while start_row > 0 {
12883 let prev_row = start_row - 1;
12884 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12885 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12886 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12887 {
12888 start_row = prev_row;
12889 } else {
12890 break 'expand_upwards;
12891 }
12892 }
12893
12894 'expand_downwards: while end_row < buffer.max_point().row {
12895 let next_row = end_row + 1;
12896 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12897 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12898 && !buffer.is_line_blank(MultiBufferRow(next_row))
12899 {
12900 end_row = next_row;
12901 } else {
12902 break 'expand_downwards;
12903 }
12904 }
12905 }
12906
12907 let start = Point::new(start_row, 0);
12908 let start_offset = ToOffset::to_offset(&start, &buffer);
12909 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12910 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12911 let mut first_line_delimiter = None;
12912 let mut last_line_delimiter = None;
12913 let Some(lines_without_prefixes) = selection_text
12914 .lines()
12915 .enumerate()
12916 .map(|(ix, line)| {
12917 let line_trimmed = line.trim_start();
12918 if rewrap_prefix.is_some() && ix > 0 {
12919 Ok(line_trimmed)
12920 } else if let Some(
12921 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12922 start,
12923 prefix,
12924 end,
12925 tab_size,
12926 })
12927 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12928 start,
12929 prefix,
12930 end,
12931 tab_size,
12932 }),
12933 ) = &comment_prefix
12934 {
12935 let line_trimmed = line_trimmed
12936 .strip_prefix(start.as_ref())
12937 .map(|s| {
12938 let mut indent_size = indent_size;
12939 indent_size.len -= tab_size;
12940 let indent_prefix: String = indent_size.chars().collect();
12941 first_line_delimiter = Some((indent_prefix, start));
12942 s.trim_start()
12943 })
12944 .unwrap_or(line_trimmed);
12945 let line_trimmed = line_trimmed
12946 .strip_suffix(end.as_ref())
12947 .map(|s| {
12948 last_line_delimiter = Some(end);
12949 s.trim_end()
12950 })
12951 .unwrap_or(line_trimmed);
12952 let line_trimmed = line_trimmed
12953 .strip_prefix(prefix.as_ref())
12954 .unwrap_or(line_trimmed);
12955 Ok(line_trimmed)
12956 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12957 line_trimmed.strip_prefix(prefix).with_context(|| {
12958 format!("line did not start with prefix {prefix:?}: {line:?}")
12959 })
12960 } else {
12961 line_trimmed
12962 .strip_prefix(&line_prefix.trim_start())
12963 .with_context(|| {
12964 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12965 })
12966 }
12967 })
12968 .collect::<Result<Vec<_>, _>>()
12969 .log_err()
12970 else {
12971 continue;
12972 };
12973
12974 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12975 buffer
12976 .language_settings_at(Point::new(start_row, 0), cx)
12977 .preferred_line_length as usize
12978 });
12979
12980 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12981 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12982 } else {
12983 line_prefix.clone()
12984 };
12985
12986 let wrapped_text = {
12987 let mut wrapped_text = wrap_with_prefix(
12988 line_prefix,
12989 subsequent_lines_prefix,
12990 lines_without_prefixes.join("\n"),
12991 wrap_column,
12992 tab_size,
12993 options.preserve_existing_whitespace,
12994 );
12995
12996 if let Some((indent, delimiter)) = first_line_delimiter {
12997 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12998 }
12999 if let Some(last_line) = last_line_delimiter {
13000 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
13001 }
13002
13003 wrapped_text
13004 };
13005
13006 // TODO: should always use char-based diff while still supporting cursor behavior that
13007 // matches vim.
13008 let mut diff_options = DiffOptions::default();
13009 if options.override_language_settings {
13010 diff_options.max_word_diff_len = 0;
13011 diff_options.max_word_diff_line_count = 0;
13012 } else {
13013 diff_options.max_word_diff_len = usize::MAX;
13014 diff_options.max_word_diff_line_count = usize::MAX;
13015 }
13016
13017 for (old_range, new_text) in
13018 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
13019 {
13020 let edit_start = buffer.anchor_after(start_offset + old_range.start);
13021 let edit_end = buffer.anchor_after(start_offset + old_range.end);
13022 edits.push((edit_start..edit_end, new_text));
13023 }
13024
13025 rewrapped_row_ranges.push(start_row..=end_row);
13026 }
13027
13028 self.buffer
13029 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
13030 }
13031
13032 pub fn cut_common(
13033 &mut self,
13034 cut_no_selection_line: bool,
13035 window: &mut Window,
13036 cx: &mut Context<Self>,
13037 ) -> ClipboardItem {
13038 let mut text = String::new();
13039 let buffer = self.buffer.read(cx).snapshot(cx);
13040 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13041 let mut clipboard_selections = Vec::with_capacity(selections.len());
13042 {
13043 let max_point = buffer.max_point();
13044 let mut is_first = true;
13045 let mut prev_selection_was_entire_line = false;
13046 for selection in &mut selections {
13047 let is_entire_line =
13048 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13049 if is_entire_line {
13050 selection.start = Point::new(selection.start.row, 0);
13051 if !selection.is_empty() && selection.end.column == 0 {
13052 selection.end = cmp::min(max_point, selection.end);
13053 } else {
13054 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13055 }
13056 selection.goal = SelectionGoal::None;
13057 }
13058 if is_first {
13059 is_first = false;
13060 } else if !prev_selection_was_entire_line {
13061 text += "\n";
13062 }
13063 prev_selection_was_entire_line = is_entire_line;
13064 let mut len = 0;
13065 for chunk in buffer.text_for_range(selection.start..selection.end) {
13066 text.push_str(chunk);
13067 len += chunk.len();
13068 }
13069
13070 clipboard_selections.push(ClipboardSelection::for_buffer(
13071 len,
13072 is_entire_line,
13073 selection.range(),
13074 &buffer,
13075 self.project.as_ref(),
13076 cx,
13077 ));
13078 }
13079 }
13080
13081 self.transact(window, cx, |this, window, cx| {
13082 this.change_selections(Default::default(), window, cx, |s| {
13083 s.select(selections);
13084 });
13085 this.insert("", window, cx);
13086 });
13087 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13088 }
13089
13090 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13092 let item = self.cut_common(true, window, cx);
13093 cx.write_to_clipboard(item);
13094 }
13095
13096 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13097 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13098 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13099 s.move_with(|snapshot, sel| {
13100 if sel.is_empty() {
13101 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13102 }
13103 if sel.is_empty() {
13104 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13105 }
13106 });
13107 });
13108 let item = self.cut_common(false, window, cx);
13109 cx.set_global(KillRing(item))
13110 }
13111
13112 pub fn kill_ring_yank(
13113 &mut self,
13114 _: &KillRingYank,
13115 window: &mut Window,
13116 cx: &mut Context<Self>,
13117 ) {
13118 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13119 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13120 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13121 (kill_ring.text().to_string(), kill_ring.metadata_json())
13122 } else {
13123 return;
13124 }
13125 } else {
13126 return;
13127 };
13128 self.do_paste(&text, metadata, false, window, cx);
13129 }
13130
13131 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13132 self.do_copy(true, cx);
13133 }
13134
13135 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13136 self.do_copy(false, cx);
13137 }
13138
13139 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13140 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13141 let buffer = self.buffer.read(cx).read(cx);
13142 let mut text = String::new();
13143
13144 let mut clipboard_selections = Vec::with_capacity(selections.len());
13145 {
13146 let max_point = buffer.max_point();
13147 let mut is_first = true;
13148 let mut prev_selection_was_entire_line = false;
13149 for selection in &selections {
13150 let mut start = selection.start;
13151 let mut end = selection.end;
13152 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13153 let mut add_trailing_newline = false;
13154 if is_entire_line {
13155 start = Point::new(start.row, 0);
13156 let next_line_start = Point::new(end.row + 1, 0);
13157 if next_line_start <= max_point {
13158 end = next_line_start;
13159 } else {
13160 // We're on the last line without a trailing newline.
13161 // Copy to the end of the line and add a newline afterwards.
13162 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13163 add_trailing_newline = true;
13164 }
13165 }
13166
13167 let mut trimmed_selections = Vec::new();
13168 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13169 let row = MultiBufferRow(start.row);
13170 let first_indent = buffer.indent_size_for_line(row);
13171 if first_indent.len == 0 || start.column > first_indent.len {
13172 trimmed_selections.push(start..end);
13173 } else {
13174 trimmed_selections.push(
13175 Point::new(row.0, first_indent.len)
13176 ..Point::new(row.0, buffer.line_len(row)),
13177 );
13178 for row in start.row + 1..=end.row {
13179 let mut line_len = buffer.line_len(MultiBufferRow(row));
13180 if row == end.row {
13181 line_len = end.column;
13182 }
13183 if line_len == 0 {
13184 trimmed_selections
13185 .push(Point::new(row, 0)..Point::new(row, line_len));
13186 continue;
13187 }
13188 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13189 if row_indent_size.len >= first_indent.len {
13190 trimmed_selections.push(
13191 Point::new(row, first_indent.len)..Point::new(row, line_len),
13192 );
13193 } else {
13194 trimmed_selections.clear();
13195 trimmed_selections.push(start..end);
13196 break;
13197 }
13198 }
13199 }
13200 } else {
13201 trimmed_selections.push(start..end);
13202 }
13203
13204 for trimmed_range in trimmed_selections {
13205 if is_first {
13206 is_first = false;
13207 } else if !prev_selection_was_entire_line {
13208 text += "\n";
13209 }
13210 prev_selection_was_entire_line = is_entire_line;
13211 let mut len = 0;
13212 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13213 text.push_str(chunk);
13214 len += chunk.len();
13215 }
13216 if add_trailing_newline {
13217 text.push('\n');
13218 len += 1;
13219 }
13220 clipboard_selections.push(ClipboardSelection::for_buffer(
13221 len,
13222 is_entire_line,
13223 trimmed_range,
13224 &buffer,
13225 self.project.as_ref(),
13226 cx,
13227 ));
13228 }
13229 }
13230 }
13231
13232 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13233 text,
13234 clipboard_selections,
13235 ));
13236 }
13237
13238 pub fn do_paste(
13239 &mut self,
13240 text: &String,
13241 clipboard_selections: Option<Vec<ClipboardSelection>>,
13242 handle_entire_lines: bool,
13243 window: &mut Window,
13244 cx: &mut Context<Self>,
13245 ) {
13246 if self.read_only(cx) {
13247 return;
13248 }
13249
13250 let clipboard_text = Cow::Borrowed(text.as_str());
13251
13252 self.transact(window, cx, |this, window, cx| {
13253 let had_active_edit_prediction = this.has_active_edit_prediction();
13254 let display_map = this.display_snapshot(cx);
13255 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13256 let cursor_offset = this
13257 .selections
13258 .last::<MultiBufferOffset>(&display_map)
13259 .head();
13260
13261 if let Some(mut clipboard_selections) = clipboard_selections {
13262 let all_selections_were_entire_line =
13263 clipboard_selections.iter().all(|s| s.is_entire_line);
13264 let first_selection_indent_column =
13265 clipboard_selections.first().map(|s| s.first_line_indent);
13266 if clipboard_selections.len() != old_selections.len() {
13267 clipboard_selections.drain(..);
13268 }
13269 let mut auto_indent_on_paste = true;
13270
13271 this.buffer.update(cx, |buffer, cx| {
13272 let snapshot = buffer.read(cx);
13273 auto_indent_on_paste = snapshot
13274 .language_settings_at(cursor_offset, cx)
13275 .auto_indent_on_paste;
13276
13277 let mut start_offset = 0;
13278 let mut edits = Vec::new();
13279 let mut original_indent_columns = Vec::new();
13280 for (ix, selection) in old_selections.iter().enumerate() {
13281 let to_insert;
13282 let entire_line;
13283 let original_indent_column;
13284 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13285 let end_offset = start_offset + clipboard_selection.len;
13286 to_insert = &clipboard_text[start_offset..end_offset];
13287 entire_line = clipboard_selection.is_entire_line;
13288 start_offset = if entire_line {
13289 end_offset
13290 } else {
13291 end_offset + 1
13292 };
13293 original_indent_column = Some(clipboard_selection.first_line_indent);
13294 } else {
13295 to_insert = &*clipboard_text;
13296 entire_line = all_selections_were_entire_line;
13297 original_indent_column = first_selection_indent_column
13298 }
13299
13300 let (range, to_insert) =
13301 if selection.is_empty() && handle_entire_lines && entire_line {
13302 // If the corresponding selection was empty when this slice of the
13303 // clipboard text was written, then the entire line containing the
13304 // selection was copied. If this selection is also currently empty,
13305 // then paste the line before the current line of the buffer.
13306 let column = selection.start.to_point(&snapshot).column as usize;
13307 let line_start = selection.start - column;
13308 (line_start..line_start, Cow::Borrowed(to_insert))
13309 } else {
13310 let language = snapshot.language_at(selection.head());
13311 let range = selection.range();
13312 if let Some(language) = language
13313 && language.name() == "Markdown".into()
13314 {
13315 edit_for_markdown_paste(
13316 &snapshot,
13317 range,
13318 to_insert,
13319 url::Url::parse(to_insert).ok(),
13320 )
13321 } else {
13322 (range, Cow::Borrowed(to_insert))
13323 }
13324 };
13325
13326 edits.push((range, to_insert));
13327 original_indent_columns.push(original_indent_column);
13328 }
13329 drop(snapshot);
13330
13331 buffer.edit(
13332 edits,
13333 if auto_indent_on_paste {
13334 Some(AutoindentMode::Block {
13335 original_indent_columns,
13336 })
13337 } else {
13338 None
13339 },
13340 cx,
13341 );
13342 });
13343
13344 let selections = this
13345 .selections
13346 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13347 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13348 } else {
13349 let url = url::Url::parse(&clipboard_text).ok();
13350
13351 let auto_indent_mode = if !clipboard_text.is_empty() {
13352 Some(AutoindentMode::Block {
13353 original_indent_columns: Vec::new(),
13354 })
13355 } else {
13356 None
13357 };
13358
13359 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13360 let snapshot = buffer.snapshot(cx);
13361
13362 let anchors = old_selections
13363 .iter()
13364 .map(|s| {
13365 let anchor = snapshot.anchor_after(s.head());
13366 s.map(|_| anchor)
13367 })
13368 .collect::<Vec<_>>();
13369
13370 let mut edits = Vec::new();
13371
13372 for selection in old_selections.iter() {
13373 let language = snapshot.language_at(selection.head());
13374 let range = selection.range();
13375
13376 let (edit_range, edit_text) = if let Some(language) = language
13377 && language.name() == "Markdown".into()
13378 {
13379 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13380 } else {
13381 (range, clipboard_text.clone())
13382 };
13383
13384 edits.push((edit_range, edit_text));
13385 }
13386
13387 drop(snapshot);
13388 buffer.edit(edits, auto_indent_mode, cx);
13389
13390 anchors
13391 });
13392
13393 this.change_selections(Default::default(), window, cx, |s| {
13394 s.select_anchors(selection_anchors);
13395 });
13396 }
13397
13398 // 🤔 | .. | show_in_menu |
13399 // | .. | true true
13400 // | had_edit_prediction | false true
13401
13402 let trigger_in_words =
13403 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13404
13405 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13406 });
13407 }
13408
13409 pub fn diff_clipboard_with_selection(
13410 &mut self,
13411 _: &DiffClipboardWithSelection,
13412 window: &mut Window,
13413 cx: &mut Context<Self>,
13414 ) {
13415 let selections = self
13416 .selections
13417 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13418
13419 if selections.is_empty() {
13420 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13421 return;
13422 };
13423
13424 let clipboard_text = match cx.read_from_clipboard() {
13425 Some(item) => match item.entries().first() {
13426 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13427 _ => None,
13428 },
13429 None => None,
13430 };
13431
13432 let Some(clipboard_text) = clipboard_text else {
13433 log::warn!("Clipboard doesn't contain text.");
13434 return;
13435 };
13436
13437 window.dispatch_action(
13438 Box::new(DiffClipboardWithSelectionData {
13439 clipboard_text,
13440 editor: cx.entity(),
13441 }),
13442 cx,
13443 );
13444 }
13445
13446 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13447 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13448 if let Some(item) = cx.read_from_clipboard() {
13449 let entries = item.entries();
13450
13451 match entries.first() {
13452 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13453 // of all the pasted entries.
13454 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13455 .do_paste(
13456 clipboard_string.text(),
13457 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13458 true,
13459 window,
13460 cx,
13461 ),
13462 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13463 }
13464 }
13465 }
13466
13467 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13468 if self.read_only(cx) {
13469 return;
13470 }
13471
13472 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13473
13474 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13475 if let Some((selections, _)) =
13476 self.selection_history.transaction(transaction_id).cloned()
13477 {
13478 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13479 s.select_anchors(selections.to_vec());
13480 });
13481 } else {
13482 log::error!(
13483 "No entry in selection_history found for undo. \
13484 This may correspond to a bug where undo does not update the selection. \
13485 If this is occurring, please add details to \
13486 https://github.com/zed-industries/zed/issues/22692"
13487 );
13488 }
13489 self.request_autoscroll(Autoscroll::fit(), cx);
13490 self.unmark_text(window, cx);
13491 self.refresh_edit_prediction(true, false, window, cx);
13492 cx.emit(EditorEvent::Edited { transaction_id });
13493 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13494 }
13495 }
13496
13497 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13498 if self.read_only(cx) {
13499 return;
13500 }
13501
13502 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13503
13504 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13505 if let Some((_, Some(selections))) =
13506 self.selection_history.transaction(transaction_id).cloned()
13507 {
13508 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13509 s.select_anchors(selections.to_vec());
13510 });
13511 } else {
13512 log::error!(
13513 "No entry in selection_history found for redo. \
13514 This may correspond to a bug where undo does not update the selection. \
13515 If this is occurring, please add details to \
13516 https://github.com/zed-industries/zed/issues/22692"
13517 );
13518 }
13519 self.request_autoscroll(Autoscroll::fit(), cx);
13520 self.unmark_text(window, cx);
13521 self.refresh_edit_prediction(true, false, window, cx);
13522 cx.emit(EditorEvent::Edited { transaction_id });
13523 }
13524 }
13525
13526 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13527 self.buffer
13528 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13529 }
13530
13531 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13532 self.buffer
13533 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13534 }
13535
13536 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13537 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13538 self.change_selections(Default::default(), window, cx, |s| {
13539 s.move_with(|map, selection| {
13540 let cursor = if selection.is_empty() {
13541 movement::left(map, selection.start)
13542 } else {
13543 selection.start
13544 };
13545 selection.collapse_to(cursor, SelectionGoal::None);
13546 });
13547 })
13548 }
13549
13550 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13551 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13552 self.change_selections(Default::default(), window, cx, |s| {
13553 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13554 })
13555 }
13556
13557 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13558 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13559 self.change_selections(Default::default(), window, cx, |s| {
13560 s.move_with(|map, selection| {
13561 let cursor = if selection.is_empty() {
13562 movement::right(map, selection.end)
13563 } else {
13564 selection.end
13565 };
13566 selection.collapse_to(cursor, SelectionGoal::None)
13567 });
13568 })
13569 }
13570
13571 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13572 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13573 self.change_selections(Default::default(), window, cx, |s| {
13574 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13575 });
13576 }
13577
13578 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13579 if self.take_rename(true, window, cx).is_some() {
13580 return;
13581 }
13582
13583 if self.mode.is_single_line() {
13584 cx.propagate();
13585 return;
13586 }
13587
13588 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13589
13590 let text_layout_details = &self.text_layout_details(window);
13591 let selection_count = self.selections.count();
13592 let first_selection = self.selections.first_anchor();
13593
13594 self.change_selections(Default::default(), window, cx, |s| {
13595 s.move_with(|map, selection| {
13596 if !selection.is_empty() {
13597 selection.goal = SelectionGoal::None;
13598 }
13599 let (cursor, goal) = movement::up(
13600 map,
13601 selection.start,
13602 selection.goal,
13603 false,
13604 text_layout_details,
13605 );
13606 selection.collapse_to(cursor, goal);
13607 });
13608 });
13609
13610 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13611 {
13612 cx.propagate();
13613 }
13614 }
13615
13616 pub fn move_up_by_lines(
13617 &mut self,
13618 action: &MoveUpByLines,
13619 window: &mut Window,
13620 cx: &mut Context<Self>,
13621 ) {
13622 if self.take_rename(true, window, cx).is_some() {
13623 return;
13624 }
13625
13626 if self.mode.is_single_line() {
13627 cx.propagate();
13628 return;
13629 }
13630
13631 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13632
13633 let text_layout_details = &self.text_layout_details(window);
13634
13635 self.change_selections(Default::default(), window, cx, |s| {
13636 s.move_with(|map, selection| {
13637 if !selection.is_empty() {
13638 selection.goal = SelectionGoal::None;
13639 }
13640 let (cursor, goal) = movement::up_by_rows(
13641 map,
13642 selection.start,
13643 action.lines,
13644 selection.goal,
13645 false,
13646 text_layout_details,
13647 );
13648 selection.collapse_to(cursor, goal);
13649 });
13650 })
13651 }
13652
13653 pub fn move_down_by_lines(
13654 &mut self,
13655 action: &MoveDownByLines,
13656 window: &mut Window,
13657 cx: &mut Context<Self>,
13658 ) {
13659 if self.take_rename(true, window, cx).is_some() {
13660 return;
13661 }
13662
13663 if self.mode.is_single_line() {
13664 cx.propagate();
13665 return;
13666 }
13667
13668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13669
13670 let text_layout_details = &self.text_layout_details(window);
13671
13672 self.change_selections(Default::default(), window, cx, |s| {
13673 s.move_with(|map, selection| {
13674 if !selection.is_empty() {
13675 selection.goal = SelectionGoal::None;
13676 }
13677 let (cursor, goal) = movement::down_by_rows(
13678 map,
13679 selection.start,
13680 action.lines,
13681 selection.goal,
13682 false,
13683 text_layout_details,
13684 );
13685 selection.collapse_to(cursor, goal);
13686 });
13687 })
13688 }
13689
13690 pub fn select_down_by_lines(
13691 &mut self,
13692 action: &SelectDownByLines,
13693 window: &mut Window,
13694 cx: &mut Context<Self>,
13695 ) {
13696 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13697 let text_layout_details = &self.text_layout_details(window);
13698 self.change_selections(Default::default(), window, cx, |s| {
13699 s.move_heads_with(|map, head, goal| {
13700 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13701 })
13702 })
13703 }
13704
13705 pub fn select_up_by_lines(
13706 &mut self,
13707 action: &SelectUpByLines,
13708 window: &mut Window,
13709 cx: &mut Context<Self>,
13710 ) {
13711 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13712 let text_layout_details = &self.text_layout_details(window);
13713 self.change_selections(Default::default(), window, cx, |s| {
13714 s.move_heads_with(|map, head, goal| {
13715 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13716 })
13717 })
13718 }
13719
13720 pub fn select_page_up(
13721 &mut self,
13722 _: &SelectPageUp,
13723 window: &mut Window,
13724 cx: &mut Context<Self>,
13725 ) {
13726 let Some(row_count) = self.visible_row_count() else {
13727 return;
13728 };
13729
13730 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13731
13732 let text_layout_details = &self.text_layout_details(window);
13733
13734 self.change_selections(Default::default(), window, cx, |s| {
13735 s.move_heads_with(|map, head, goal| {
13736 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13737 })
13738 })
13739 }
13740
13741 pub fn move_page_up(
13742 &mut self,
13743 action: &MovePageUp,
13744 window: &mut Window,
13745 cx: &mut Context<Self>,
13746 ) {
13747 if self.take_rename(true, window, cx).is_some() {
13748 return;
13749 }
13750
13751 if self
13752 .context_menu
13753 .borrow_mut()
13754 .as_mut()
13755 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13756 .unwrap_or(false)
13757 {
13758 return;
13759 }
13760
13761 if matches!(self.mode, EditorMode::SingleLine) {
13762 cx.propagate();
13763 return;
13764 }
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 effects = if action.center_cursor {
13773 SelectionEffects::scroll(Autoscroll::center())
13774 } else {
13775 SelectionEffects::default()
13776 };
13777
13778 let text_layout_details = &self.text_layout_details(window);
13779
13780 self.change_selections(effects, window, cx, |s| {
13781 s.move_with(|map, selection| {
13782 if !selection.is_empty() {
13783 selection.goal = SelectionGoal::None;
13784 }
13785 let (cursor, goal) = movement::up_by_rows(
13786 map,
13787 selection.end,
13788 row_count,
13789 selection.goal,
13790 false,
13791 text_layout_details,
13792 );
13793 selection.collapse_to(cursor, goal);
13794 });
13795 });
13796 }
13797
13798 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13799 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13800 let text_layout_details = &self.text_layout_details(window);
13801 self.change_selections(Default::default(), window, cx, |s| {
13802 s.move_heads_with(|map, head, goal| {
13803 movement::up(map, head, goal, false, text_layout_details)
13804 })
13805 })
13806 }
13807
13808 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13809 self.take_rename(true, window, cx);
13810
13811 if self.mode.is_single_line() {
13812 cx.propagate();
13813 return;
13814 }
13815
13816 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13817
13818 let text_layout_details = &self.text_layout_details(window);
13819 let selection_count = self.selections.count();
13820 let first_selection = self.selections.first_anchor();
13821
13822 self.change_selections(Default::default(), window, cx, |s| {
13823 s.move_with(|map, selection| {
13824 if !selection.is_empty() {
13825 selection.goal = SelectionGoal::None;
13826 }
13827 let (cursor, goal) = movement::down(
13828 map,
13829 selection.end,
13830 selection.goal,
13831 false,
13832 text_layout_details,
13833 );
13834 selection.collapse_to(cursor, goal);
13835 });
13836 });
13837
13838 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13839 {
13840 cx.propagate();
13841 }
13842 }
13843
13844 pub fn select_page_down(
13845 &mut self,
13846 _: &SelectPageDown,
13847 window: &mut Window,
13848 cx: &mut Context<Self>,
13849 ) {
13850 let Some(row_count) = self.visible_row_count() else {
13851 return;
13852 };
13853
13854 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13855
13856 let text_layout_details = &self.text_layout_details(window);
13857
13858 self.change_selections(Default::default(), window, cx, |s| {
13859 s.move_heads_with(|map, head, goal| {
13860 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13861 })
13862 })
13863 }
13864
13865 pub fn move_page_down(
13866 &mut self,
13867 action: &MovePageDown,
13868 window: &mut Window,
13869 cx: &mut Context<Self>,
13870 ) {
13871 if self.take_rename(true, window, cx).is_some() {
13872 return;
13873 }
13874
13875 if self
13876 .context_menu
13877 .borrow_mut()
13878 .as_mut()
13879 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13880 .unwrap_or(false)
13881 {
13882 return;
13883 }
13884
13885 if matches!(self.mode, EditorMode::SingleLine) {
13886 cx.propagate();
13887 return;
13888 }
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 effects = if action.center_cursor {
13897 SelectionEffects::scroll(Autoscroll::center())
13898 } else {
13899 SelectionEffects::default()
13900 };
13901
13902 let text_layout_details = &self.text_layout_details(window);
13903 self.change_selections(effects, window, cx, |s| {
13904 s.move_with(|map, selection| {
13905 if !selection.is_empty() {
13906 selection.goal = SelectionGoal::None;
13907 }
13908 let (cursor, goal) = movement::down_by_rows(
13909 map,
13910 selection.end,
13911 row_count,
13912 selection.goal,
13913 false,
13914 text_layout_details,
13915 );
13916 selection.collapse_to(cursor, goal);
13917 });
13918 });
13919 }
13920
13921 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13922 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13923 let text_layout_details = &self.text_layout_details(window);
13924 self.change_selections(Default::default(), window, cx, |s| {
13925 s.move_heads_with(|map, head, goal| {
13926 movement::down(map, head, goal, false, text_layout_details)
13927 })
13928 });
13929 }
13930
13931 pub fn context_menu_first(
13932 &mut self,
13933 _: &ContextMenuFirst,
13934 window: &mut Window,
13935 cx: &mut Context<Self>,
13936 ) {
13937 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13938 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13939 }
13940 }
13941
13942 pub fn context_menu_prev(
13943 &mut self,
13944 _: &ContextMenuPrevious,
13945 window: &mut Window,
13946 cx: &mut Context<Self>,
13947 ) {
13948 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13949 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13950 }
13951 }
13952
13953 pub fn context_menu_next(
13954 &mut self,
13955 _: &ContextMenuNext,
13956 window: &mut Window,
13957 cx: &mut Context<Self>,
13958 ) {
13959 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13960 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13961 }
13962 }
13963
13964 pub fn context_menu_last(
13965 &mut self,
13966 _: &ContextMenuLast,
13967 window: &mut Window,
13968 cx: &mut Context<Self>,
13969 ) {
13970 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13971 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13972 }
13973 }
13974
13975 pub fn signature_help_prev(
13976 &mut self,
13977 _: &SignatureHelpPrevious,
13978 _: &mut Window,
13979 cx: &mut Context<Self>,
13980 ) {
13981 if let Some(popover) = self.signature_help_state.popover_mut() {
13982 if popover.current_signature == 0 {
13983 popover.current_signature = popover.signatures.len() - 1;
13984 } else {
13985 popover.current_signature -= 1;
13986 }
13987 cx.notify();
13988 }
13989 }
13990
13991 pub fn signature_help_next(
13992 &mut self,
13993 _: &SignatureHelpNext,
13994 _: &mut Window,
13995 cx: &mut Context<Self>,
13996 ) {
13997 if let Some(popover) = self.signature_help_state.popover_mut() {
13998 if popover.current_signature + 1 == popover.signatures.len() {
13999 popover.current_signature = 0;
14000 } else {
14001 popover.current_signature += 1;
14002 }
14003 cx.notify();
14004 }
14005 }
14006
14007 pub fn move_to_previous_word_start(
14008 &mut self,
14009 _: &MoveToPreviousWordStart,
14010 window: &mut Window,
14011 cx: &mut Context<Self>,
14012 ) {
14013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14014 self.change_selections(Default::default(), window, cx, |s| {
14015 s.move_cursors_with(|map, head, _| {
14016 (
14017 movement::previous_word_start(map, head),
14018 SelectionGoal::None,
14019 )
14020 });
14021 })
14022 }
14023
14024 pub fn move_to_previous_subword_start(
14025 &mut self,
14026 _: &MoveToPreviousSubwordStart,
14027 window: &mut Window,
14028 cx: &mut Context<Self>,
14029 ) {
14030 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14031 self.change_selections(Default::default(), window, cx, |s| {
14032 s.move_cursors_with(|map, head, _| {
14033 (
14034 movement::previous_subword_start(map, head),
14035 SelectionGoal::None,
14036 )
14037 });
14038 })
14039 }
14040
14041 pub fn select_to_previous_word_start(
14042 &mut self,
14043 _: &SelectToPreviousWordStart,
14044 window: &mut Window,
14045 cx: &mut Context<Self>,
14046 ) {
14047 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14048 self.change_selections(Default::default(), window, cx, |s| {
14049 s.move_heads_with(|map, head, _| {
14050 (
14051 movement::previous_word_start(map, head),
14052 SelectionGoal::None,
14053 )
14054 });
14055 })
14056 }
14057
14058 pub fn select_to_previous_subword_start(
14059 &mut self,
14060 _: &SelectToPreviousSubwordStart,
14061 window: &mut Window,
14062 cx: &mut Context<Self>,
14063 ) {
14064 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14065 self.change_selections(Default::default(), window, cx, |s| {
14066 s.move_heads_with(|map, head, _| {
14067 (
14068 movement::previous_subword_start(map, head),
14069 SelectionGoal::None,
14070 )
14071 });
14072 })
14073 }
14074
14075 pub fn delete_to_previous_word_start(
14076 &mut self,
14077 action: &DeleteToPreviousWordStart,
14078 window: &mut Window,
14079 cx: &mut Context<Self>,
14080 ) {
14081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14082 self.transact(window, cx, |this, window, cx| {
14083 this.select_autoclose_pair(window, cx);
14084 this.change_selections(Default::default(), window, cx, |s| {
14085 s.move_with(|map, selection| {
14086 if selection.is_empty() {
14087 let mut cursor = if action.ignore_newlines {
14088 movement::previous_word_start(map, selection.head())
14089 } else {
14090 movement::previous_word_start_or_newline(map, selection.head())
14091 };
14092 cursor = movement::adjust_greedy_deletion(
14093 map,
14094 selection.head(),
14095 cursor,
14096 action.ignore_brackets,
14097 );
14098 selection.set_head(cursor, SelectionGoal::None);
14099 }
14100 });
14101 });
14102 this.insert("", window, cx);
14103 });
14104 }
14105
14106 pub fn delete_to_previous_subword_start(
14107 &mut self,
14108 _: &DeleteToPreviousSubwordStart,
14109 window: &mut Window,
14110 cx: &mut Context<Self>,
14111 ) {
14112 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14113 self.transact(window, cx, |this, window, cx| {
14114 this.select_autoclose_pair(window, cx);
14115 this.change_selections(Default::default(), window, cx, |s| {
14116 s.move_with(|map, selection| {
14117 if selection.is_empty() {
14118 let mut cursor = movement::previous_subword_start(map, selection.head());
14119 cursor =
14120 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14121 selection.set_head(cursor, SelectionGoal::None);
14122 }
14123 });
14124 });
14125 this.insert("", window, cx);
14126 });
14127 }
14128
14129 pub fn move_to_next_word_end(
14130 &mut self,
14131 _: &MoveToNextWordEnd,
14132 window: &mut Window,
14133 cx: &mut Context<Self>,
14134 ) {
14135 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14136 self.change_selections(Default::default(), window, cx, |s| {
14137 s.move_cursors_with(|map, head, _| {
14138 (movement::next_word_end(map, head), SelectionGoal::None)
14139 });
14140 })
14141 }
14142
14143 pub fn move_to_next_subword_end(
14144 &mut self,
14145 _: &MoveToNextSubwordEnd,
14146 window: &mut Window,
14147 cx: &mut Context<Self>,
14148 ) {
14149 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14150 self.change_selections(Default::default(), window, cx, |s| {
14151 s.move_cursors_with(|map, head, _| {
14152 (movement::next_subword_end(map, head), SelectionGoal::None)
14153 });
14154 })
14155 }
14156
14157 pub fn select_to_next_word_end(
14158 &mut self,
14159 _: &SelectToNextWordEnd,
14160 window: &mut Window,
14161 cx: &mut Context<Self>,
14162 ) {
14163 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14164 self.change_selections(Default::default(), window, cx, |s| {
14165 s.move_heads_with(|map, head, _| {
14166 (movement::next_word_end(map, head), SelectionGoal::None)
14167 });
14168 })
14169 }
14170
14171 pub fn select_to_next_subword_end(
14172 &mut self,
14173 _: &SelectToNextSubwordEnd,
14174 window: &mut Window,
14175 cx: &mut Context<Self>,
14176 ) {
14177 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14178 self.change_selections(Default::default(), window, cx, |s| {
14179 s.move_heads_with(|map, head, _| {
14180 (movement::next_subword_end(map, head), SelectionGoal::None)
14181 });
14182 })
14183 }
14184
14185 pub fn delete_to_next_word_end(
14186 &mut self,
14187 action: &DeleteToNextWordEnd,
14188 window: &mut Window,
14189 cx: &mut Context<Self>,
14190 ) {
14191 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14192 self.transact(window, cx, |this, window, cx| {
14193 this.change_selections(Default::default(), window, cx, |s| {
14194 s.move_with(|map, selection| {
14195 if selection.is_empty() {
14196 let mut cursor = if action.ignore_newlines {
14197 movement::next_word_end(map, selection.head())
14198 } else {
14199 movement::next_word_end_or_newline(map, selection.head())
14200 };
14201 cursor = movement::adjust_greedy_deletion(
14202 map,
14203 selection.head(),
14204 cursor,
14205 action.ignore_brackets,
14206 );
14207 selection.set_head(cursor, SelectionGoal::None);
14208 }
14209 });
14210 });
14211 this.insert("", window, cx);
14212 });
14213 }
14214
14215 pub fn delete_to_next_subword_end(
14216 &mut self,
14217 _: &DeleteToNextSubwordEnd,
14218 window: &mut Window,
14219 cx: &mut Context<Self>,
14220 ) {
14221 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14222 self.transact(window, cx, |this, window, cx| {
14223 this.change_selections(Default::default(), window, cx, |s| {
14224 s.move_with(|map, selection| {
14225 if selection.is_empty() {
14226 let mut cursor = movement::next_subword_end(map, selection.head());
14227 cursor =
14228 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14229 selection.set_head(cursor, SelectionGoal::None);
14230 }
14231 });
14232 });
14233 this.insert("", window, cx);
14234 });
14235 }
14236
14237 pub fn move_to_beginning_of_line(
14238 &mut self,
14239 action: &MoveToBeginningOfLine,
14240 window: &mut Window,
14241 cx: &mut Context<Self>,
14242 ) {
14243 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14244 self.change_selections(Default::default(), window, cx, |s| {
14245 s.move_cursors_with(|map, head, _| {
14246 (
14247 movement::indented_line_beginning(
14248 map,
14249 head,
14250 action.stop_at_soft_wraps,
14251 action.stop_at_indent,
14252 ),
14253 SelectionGoal::None,
14254 )
14255 });
14256 })
14257 }
14258
14259 pub fn select_to_beginning_of_line(
14260 &mut self,
14261 action: &SelectToBeginningOfLine,
14262 window: &mut Window,
14263 cx: &mut Context<Self>,
14264 ) {
14265 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14266 self.change_selections(Default::default(), window, cx, |s| {
14267 s.move_heads_with(|map, head, _| {
14268 (
14269 movement::indented_line_beginning(
14270 map,
14271 head,
14272 action.stop_at_soft_wraps,
14273 action.stop_at_indent,
14274 ),
14275 SelectionGoal::None,
14276 )
14277 });
14278 });
14279 }
14280
14281 pub fn delete_to_beginning_of_line(
14282 &mut self,
14283 action: &DeleteToBeginningOfLine,
14284 window: &mut Window,
14285 cx: &mut Context<Self>,
14286 ) {
14287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14288 self.transact(window, cx, |this, window, cx| {
14289 this.change_selections(Default::default(), window, cx, |s| {
14290 s.move_with(|_, selection| {
14291 selection.reversed = true;
14292 });
14293 });
14294
14295 this.select_to_beginning_of_line(
14296 &SelectToBeginningOfLine {
14297 stop_at_soft_wraps: false,
14298 stop_at_indent: action.stop_at_indent,
14299 },
14300 window,
14301 cx,
14302 );
14303 this.backspace(&Backspace, window, cx);
14304 });
14305 }
14306
14307 pub fn move_to_end_of_line(
14308 &mut self,
14309 action: &MoveToEndOfLine,
14310 window: &mut Window,
14311 cx: &mut Context<Self>,
14312 ) {
14313 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14314 self.change_selections(Default::default(), window, cx, |s| {
14315 s.move_cursors_with(|map, head, _| {
14316 (
14317 movement::line_end(map, head, action.stop_at_soft_wraps),
14318 SelectionGoal::None,
14319 )
14320 });
14321 })
14322 }
14323
14324 pub fn select_to_end_of_line(
14325 &mut self,
14326 action: &SelectToEndOfLine,
14327 window: &mut Window,
14328 cx: &mut Context<Self>,
14329 ) {
14330 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14331 self.change_selections(Default::default(), window, cx, |s| {
14332 s.move_heads_with(|map, head, _| {
14333 (
14334 movement::line_end(map, head, action.stop_at_soft_wraps),
14335 SelectionGoal::None,
14336 )
14337 });
14338 })
14339 }
14340
14341 pub fn delete_to_end_of_line(
14342 &mut self,
14343 _: &DeleteToEndOfLine,
14344 window: &mut Window,
14345 cx: &mut Context<Self>,
14346 ) {
14347 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14348 self.transact(window, cx, |this, window, cx| {
14349 this.select_to_end_of_line(
14350 &SelectToEndOfLine {
14351 stop_at_soft_wraps: false,
14352 },
14353 window,
14354 cx,
14355 );
14356 this.delete(&Delete, window, cx);
14357 });
14358 }
14359
14360 pub fn cut_to_end_of_line(
14361 &mut self,
14362 action: &CutToEndOfLine,
14363 window: &mut Window,
14364 cx: &mut Context<Self>,
14365 ) {
14366 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14367 self.transact(window, cx, |this, window, cx| {
14368 this.select_to_end_of_line(
14369 &SelectToEndOfLine {
14370 stop_at_soft_wraps: false,
14371 },
14372 window,
14373 cx,
14374 );
14375 if !action.stop_at_newlines {
14376 this.change_selections(Default::default(), window, cx, |s| {
14377 s.move_with(|_, sel| {
14378 if sel.is_empty() {
14379 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14380 }
14381 });
14382 });
14383 }
14384 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14385 let item = this.cut_common(false, window, cx);
14386 cx.write_to_clipboard(item);
14387 });
14388 }
14389
14390 pub fn move_to_start_of_paragraph(
14391 &mut self,
14392 _: &MoveToStartOfParagraph,
14393 window: &mut Window,
14394 cx: &mut Context<Self>,
14395 ) {
14396 if matches!(self.mode, EditorMode::SingleLine) {
14397 cx.propagate();
14398 return;
14399 }
14400 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14401 self.change_selections(Default::default(), window, cx, |s| {
14402 s.move_with(|map, selection| {
14403 selection.collapse_to(
14404 movement::start_of_paragraph(map, selection.head(), 1),
14405 SelectionGoal::None,
14406 )
14407 });
14408 })
14409 }
14410
14411 pub fn move_to_end_of_paragraph(
14412 &mut self,
14413 _: &MoveToEndOfParagraph,
14414 window: &mut Window,
14415 cx: &mut Context<Self>,
14416 ) {
14417 if matches!(self.mode, EditorMode::SingleLine) {
14418 cx.propagate();
14419 return;
14420 }
14421 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14422 self.change_selections(Default::default(), window, cx, |s| {
14423 s.move_with(|map, selection| {
14424 selection.collapse_to(
14425 movement::end_of_paragraph(map, selection.head(), 1),
14426 SelectionGoal::None,
14427 )
14428 });
14429 })
14430 }
14431
14432 pub fn select_to_start_of_paragraph(
14433 &mut self,
14434 _: &SelectToStartOfParagraph,
14435 window: &mut Window,
14436 cx: &mut Context<Self>,
14437 ) {
14438 if matches!(self.mode, EditorMode::SingleLine) {
14439 cx.propagate();
14440 return;
14441 }
14442 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14443 self.change_selections(Default::default(), window, cx, |s| {
14444 s.move_heads_with(|map, head, _| {
14445 (
14446 movement::start_of_paragraph(map, head, 1),
14447 SelectionGoal::None,
14448 )
14449 });
14450 })
14451 }
14452
14453 pub fn select_to_end_of_paragraph(
14454 &mut self,
14455 _: &SelectToEndOfParagraph,
14456 window: &mut Window,
14457 cx: &mut Context<Self>,
14458 ) {
14459 if matches!(self.mode, EditorMode::SingleLine) {
14460 cx.propagate();
14461 return;
14462 }
14463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14464 self.change_selections(Default::default(), window, cx, |s| {
14465 s.move_heads_with(|map, head, _| {
14466 (
14467 movement::end_of_paragraph(map, head, 1),
14468 SelectionGoal::None,
14469 )
14470 });
14471 })
14472 }
14473
14474 pub fn move_to_start_of_excerpt(
14475 &mut self,
14476 _: &MoveToStartOfExcerpt,
14477 window: &mut Window,
14478 cx: &mut Context<Self>,
14479 ) {
14480 if matches!(self.mode, EditorMode::SingleLine) {
14481 cx.propagate();
14482 return;
14483 }
14484 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14485 self.change_selections(Default::default(), window, cx, |s| {
14486 s.move_with(|map, selection| {
14487 selection.collapse_to(
14488 movement::start_of_excerpt(
14489 map,
14490 selection.head(),
14491 workspace::searchable::Direction::Prev,
14492 ),
14493 SelectionGoal::None,
14494 )
14495 });
14496 })
14497 }
14498
14499 pub fn move_to_start_of_next_excerpt(
14500 &mut self,
14501 _: &MoveToStartOfNextExcerpt,
14502 window: &mut Window,
14503 cx: &mut Context<Self>,
14504 ) {
14505 if matches!(self.mode, EditorMode::SingleLine) {
14506 cx.propagate();
14507 return;
14508 }
14509
14510 self.change_selections(Default::default(), window, cx, |s| {
14511 s.move_with(|map, selection| {
14512 selection.collapse_to(
14513 movement::start_of_excerpt(
14514 map,
14515 selection.head(),
14516 workspace::searchable::Direction::Next,
14517 ),
14518 SelectionGoal::None,
14519 )
14520 });
14521 })
14522 }
14523
14524 pub fn move_to_end_of_excerpt(
14525 &mut self,
14526 _: &MoveToEndOfExcerpt,
14527 window: &mut Window,
14528 cx: &mut Context<Self>,
14529 ) {
14530 if matches!(self.mode, EditorMode::SingleLine) {
14531 cx.propagate();
14532 return;
14533 }
14534 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14535 self.change_selections(Default::default(), window, cx, |s| {
14536 s.move_with(|map, selection| {
14537 selection.collapse_to(
14538 movement::end_of_excerpt(
14539 map,
14540 selection.head(),
14541 workspace::searchable::Direction::Next,
14542 ),
14543 SelectionGoal::None,
14544 )
14545 });
14546 })
14547 }
14548
14549 pub fn move_to_end_of_previous_excerpt(
14550 &mut self,
14551 _: &MoveToEndOfPreviousExcerpt,
14552 window: &mut Window,
14553 cx: &mut Context<Self>,
14554 ) {
14555 if matches!(self.mode, EditorMode::SingleLine) {
14556 cx.propagate();
14557 return;
14558 }
14559 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14560 self.change_selections(Default::default(), window, cx, |s| {
14561 s.move_with(|map, selection| {
14562 selection.collapse_to(
14563 movement::end_of_excerpt(
14564 map,
14565 selection.head(),
14566 workspace::searchable::Direction::Prev,
14567 ),
14568 SelectionGoal::None,
14569 )
14570 });
14571 })
14572 }
14573
14574 pub fn select_to_start_of_excerpt(
14575 &mut self,
14576 _: &SelectToStartOfExcerpt,
14577 window: &mut Window,
14578 cx: &mut Context<Self>,
14579 ) {
14580 if matches!(self.mode, EditorMode::SingleLine) {
14581 cx.propagate();
14582 return;
14583 }
14584 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14585 self.change_selections(Default::default(), window, cx, |s| {
14586 s.move_heads_with(|map, head, _| {
14587 (
14588 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14589 SelectionGoal::None,
14590 )
14591 });
14592 })
14593 }
14594
14595 pub fn select_to_start_of_next_excerpt(
14596 &mut self,
14597 _: &SelectToStartOfNextExcerpt,
14598 window: &mut Window,
14599 cx: &mut Context<Self>,
14600 ) {
14601 if matches!(self.mode, EditorMode::SingleLine) {
14602 cx.propagate();
14603 return;
14604 }
14605 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14606 self.change_selections(Default::default(), window, cx, |s| {
14607 s.move_heads_with(|map, head, _| {
14608 (
14609 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14610 SelectionGoal::None,
14611 )
14612 });
14613 })
14614 }
14615
14616 pub fn select_to_end_of_excerpt(
14617 &mut self,
14618 _: &SelectToEndOfExcerpt,
14619 window: &mut Window,
14620 cx: &mut Context<Self>,
14621 ) {
14622 if matches!(self.mode, EditorMode::SingleLine) {
14623 cx.propagate();
14624 return;
14625 }
14626 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14627 self.change_selections(Default::default(), window, cx, |s| {
14628 s.move_heads_with(|map, head, _| {
14629 (
14630 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14631 SelectionGoal::None,
14632 )
14633 });
14634 })
14635 }
14636
14637 pub fn select_to_end_of_previous_excerpt(
14638 &mut self,
14639 _: &SelectToEndOfPreviousExcerpt,
14640 window: &mut Window,
14641 cx: &mut Context<Self>,
14642 ) {
14643 if matches!(self.mode, EditorMode::SingleLine) {
14644 cx.propagate();
14645 return;
14646 }
14647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14648 self.change_selections(Default::default(), window, cx, |s| {
14649 s.move_heads_with(|map, head, _| {
14650 (
14651 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14652 SelectionGoal::None,
14653 )
14654 });
14655 })
14656 }
14657
14658 pub fn move_to_beginning(
14659 &mut self,
14660 _: &MoveToBeginning,
14661 window: &mut Window,
14662 cx: &mut Context<Self>,
14663 ) {
14664 if matches!(self.mode, EditorMode::SingleLine) {
14665 cx.propagate();
14666 return;
14667 }
14668 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14669 self.change_selections(Default::default(), window, cx, |s| {
14670 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14671 });
14672 }
14673
14674 pub fn select_to_beginning(
14675 &mut self,
14676 _: &SelectToBeginning,
14677 window: &mut Window,
14678 cx: &mut Context<Self>,
14679 ) {
14680 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14681 selection.set_head(Point::zero(), SelectionGoal::None);
14682 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14683 self.change_selections(Default::default(), window, cx, |s| {
14684 s.select(vec![selection]);
14685 });
14686 }
14687
14688 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14689 if matches!(self.mode, EditorMode::SingleLine) {
14690 cx.propagate();
14691 return;
14692 }
14693 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14694 let cursor = self.buffer.read(cx).read(cx).len();
14695 self.change_selections(Default::default(), window, cx, |s| {
14696 s.select_ranges(vec![cursor..cursor])
14697 });
14698 }
14699
14700 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14701 self.nav_history = nav_history;
14702 }
14703
14704 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14705 self.nav_history.as_ref()
14706 }
14707
14708 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14709 self.push_to_nav_history(
14710 self.selections.newest_anchor().head(),
14711 None,
14712 false,
14713 true,
14714 cx,
14715 );
14716 }
14717
14718 fn push_to_nav_history(
14719 &mut self,
14720 cursor_anchor: Anchor,
14721 new_position: Option<Point>,
14722 is_deactivate: bool,
14723 always: bool,
14724 cx: &mut Context<Self>,
14725 ) {
14726 if let Some(nav_history) = self.nav_history.as_mut() {
14727 let buffer = self.buffer.read(cx).read(cx);
14728 let cursor_position = cursor_anchor.to_point(&buffer);
14729 let scroll_state = self.scroll_manager.anchor();
14730 let scroll_top_row = scroll_state.top_row(&buffer);
14731 drop(buffer);
14732
14733 if let Some(new_position) = new_position {
14734 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14735 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14736 return;
14737 }
14738 }
14739
14740 nav_history.push(
14741 Some(NavigationData {
14742 cursor_anchor,
14743 cursor_position,
14744 scroll_anchor: scroll_state,
14745 scroll_top_row,
14746 }),
14747 cx,
14748 );
14749 cx.emit(EditorEvent::PushedToNavHistory {
14750 anchor: cursor_anchor,
14751 is_deactivate,
14752 })
14753 }
14754 }
14755
14756 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14757 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14758 let buffer = self.buffer.read(cx).snapshot(cx);
14759 let mut selection = self
14760 .selections
14761 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14762 selection.set_head(buffer.len(), SelectionGoal::None);
14763 self.change_selections(Default::default(), window, cx, |s| {
14764 s.select(vec![selection]);
14765 });
14766 }
14767
14768 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14769 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14770 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14771 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14772 });
14773 }
14774
14775 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14776 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14777 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14778 let mut selections = self.selections.all::<Point>(&display_map);
14779 let max_point = display_map.buffer_snapshot().max_point();
14780 for selection in &mut selections {
14781 let rows = selection.spanned_rows(true, &display_map);
14782 selection.start = Point::new(rows.start.0, 0);
14783 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14784 selection.reversed = false;
14785 }
14786 self.change_selections(Default::default(), window, cx, |s| {
14787 s.select(selections);
14788 });
14789 }
14790
14791 pub fn split_selection_into_lines(
14792 &mut self,
14793 action: &SplitSelectionIntoLines,
14794 window: &mut Window,
14795 cx: &mut Context<Self>,
14796 ) {
14797 let selections = self
14798 .selections
14799 .all::<Point>(&self.display_snapshot(cx))
14800 .into_iter()
14801 .map(|selection| selection.start..selection.end)
14802 .collect::<Vec<_>>();
14803 self.unfold_ranges(&selections, true, true, cx);
14804
14805 let mut new_selection_ranges = Vec::new();
14806 {
14807 let buffer = self.buffer.read(cx).read(cx);
14808 for selection in selections {
14809 for row in selection.start.row..selection.end.row {
14810 let line_start = Point::new(row, 0);
14811 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14812
14813 if action.keep_selections {
14814 // Keep the selection range for each line
14815 let selection_start = if row == selection.start.row {
14816 selection.start
14817 } else {
14818 line_start
14819 };
14820 new_selection_ranges.push(selection_start..line_end);
14821 } else {
14822 // Collapse to cursor at end of line
14823 new_selection_ranges.push(line_end..line_end);
14824 }
14825 }
14826
14827 let is_multiline_selection = selection.start.row != selection.end.row;
14828 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14829 // so this action feels more ergonomic when paired with other selection operations
14830 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14831 if !should_skip_last {
14832 if action.keep_selections {
14833 if is_multiline_selection {
14834 let line_start = Point::new(selection.end.row, 0);
14835 new_selection_ranges.push(line_start..selection.end);
14836 } else {
14837 new_selection_ranges.push(selection.start..selection.end);
14838 }
14839 } else {
14840 new_selection_ranges.push(selection.end..selection.end);
14841 }
14842 }
14843 }
14844 }
14845 self.change_selections(Default::default(), window, cx, |s| {
14846 s.select_ranges(new_selection_ranges);
14847 });
14848 }
14849
14850 pub fn add_selection_above(
14851 &mut self,
14852 action: &AddSelectionAbove,
14853 window: &mut Window,
14854 cx: &mut Context<Self>,
14855 ) {
14856 self.add_selection(true, action.skip_soft_wrap, window, cx);
14857 }
14858
14859 pub fn add_selection_below(
14860 &mut self,
14861 action: &AddSelectionBelow,
14862 window: &mut Window,
14863 cx: &mut Context<Self>,
14864 ) {
14865 self.add_selection(false, action.skip_soft_wrap, window, cx);
14866 }
14867
14868 fn add_selection(
14869 &mut self,
14870 above: bool,
14871 skip_soft_wrap: bool,
14872 window: &mut Window,
14873 cx: &mut Context<Self>,
14874 ) {
14875 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14876
14877 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14878 let all_selections = self.selections.all::<Point>(&display_map);
14879 let text_layout_details = self.text_layout_details(window);
14880
14881 let (mut columnar_selections, new_selections_to_columnarize) = {
14882 if let Some(state) = self.add_selections_state.as_ref() {
14883 let columnar_selection_ids: HashSet<_> = state
14884 .groups
14885 .iter()
14886 .flat_map(|group| group.stack.iter())
14887 .copied()
14888 .collect();
14889
14890 all_selections
14891 .into_iter()
14892 .partition(|s| columnar_selection_ids.contains(&s.id))
14893 } else {
14894 (Vec::new(), all_selections)
14895 }
14896 };
14897
14898 let mut state = self
14899 .add_selections_state
14900 .take()
14901 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14902
14903 for selection in new_selections_to_columnarize {
14904 let range = selection.display_range(&display_map).sorted();
14905 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14906 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14907 let positions = start_x.min(end_x)..start_x.max(end_x);
14908 let mut stack = Vec::new();
14909 for row in range.start.row().0..=range.end.row().0 {
14910 if let Some(selection) = self.selections.build_columnar_selection(
14911 &display_map,
14912 DisplayRow(row),
14913 &positions,
14914 selection.reversed,
14915 &text_layout_details,
14916 ) {
14917 stack.push(selection.id);
14918 columnar_selections.push(selection);
14919 }
14920 }
14921 if !stack.is_empty() {
14922 if above {
14923 stack.reverse();
14924 }
14925 state.groups.push(AddSelectionsGroup { above, stack });
14926 }
14927 }
14928
14929 let mut final_selections = Vec::new();
14930 let end_row = if above {
14931 DisplayRow(0)
14932 } else {
14933 display_map.max_point().row()
14934 };
14935
14936 let mut last_added_item_per_group = HashMap::default();
14937 for group in state.groups.iter_mut() {
14938 if let Some(last_id) = group.stack.last() {
14939 last_added_item_per_group.insert(*last_id, group);
14940 }
14941 }
14942
14943 for selection in columnar_selections {
14944 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14945 if above == group.above {
14946 let range = selection.display_range(&display_map).sorted();
14947 debug_assert_eq!(range.start.row(), range.end.row());
14948 let mut row = range.start.row();
14949 let positions =
14950 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14951 Pixels::from(start)..Pixels::from(end)
14952 } else {
14953 let start_x =
14954 display_map.x_for_display_point(range.start, &text_layout_details);
14955 let end_x =
14956 display_map.x_for_display_point(range.end, &text_layout_details);
14957 start_x.min(end_x)..start_x.max(end_x)
14958 };
14959
14960 let mut maybe_new_selection = None;
14961 let direction = if above { -1 } else { 1 };
14962
14963 while row != end_row {
14964 if skip_soft_wrap {
14965 row = display_map
14966 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14967 .row();
14968 } else if above {
14969 row.0 -= 1;
14970 } else {
14971 row.0 += 1;
14972 }
14973
14974 if let Some(new_selection) = self.selections.build_columnar_selection(
14975 &display_map,
14976 row,
14977 &positions,
14978 selection.reversed,
14979 &text_layout_details,
14980 ) {
14981 maybe_new_selection = Some(new_selection);
14982 break;
14983 }
14984 }
14985
14986 if let Some(new_selection) = maybe_new_selection {
14987 group.stack.push(new_selection.id);
14988 if above {
14989 final_selections.push(new_selection);
14990 final_selections.push(selection);
14991 } else {
14992 final_selections.push(selection);
14993 final_selections.push(new_selection);
14994 }
14995 } else {
14996 final_selections.push(selection);
14997 }
14998 } else {
14999 group.stack.pop();
15000 }
15001 } else {
15002 final_selections.push(selection);
15003 }
15004 }
15005
15006 self.change_selections(Default::default(), window, cx, |s| {
15007 s.select(final_selections);
15008 });
15009
15010 let final_selection_ids: HashSet<_> = self
15011 .selections
15012 .all::<Point>(&display_map)
15013 .iter()
15014 .map(|s| s.id)
15015 .collect();
15016 state.groups.retain_mut(|group| {
15017 // selections might get merged above so we remove invalid items from stacks
15018 group.stack.retain(|id| final_selection_ids.contains(id));
15019
15020 // single selection in stack can be treated as initial state
15021 group.stack.len() > 1
15022 });
15023
15024 if !state.groups.is_empty() {
15025 self.add_selections_state = Some(state);
15026 }
15027 }
15028
15029 pub fn insert_snippet_at_selections(
15030 &mut self,
15031 action: &InsertSnippet,
15032 window: &mut Window,
15033 cx: &mut Context<Self>,
15034 ) {
15035 self.try_insert_snippet_at_selections(action, window, cx)
15036 .log_err();
15037 }
15038
15039 fn try_insert_snippet_at_selections(
15040 &mut self,
15041 action: &InsertSnippet,
15042 window: &mut Window,
15043 cx: &mut Context<Self>,
15044 ) -> Result<()> {
15045 let insertion_ranges = self
15046 .selections
15047 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15048 .into_iter()
15049 .map(|selection| selection.range())
15050 .collect_vec();
15051
15052 let snippet = if let Some(snippet_body) = &action.snippet {
15053 if action.language.is_none() && action.name.is_none() {
15054 Snippet::parse(snippet_body)?
15055 } else {
15056 bail!("`snippet` is mutually exclusive with `language` and `name`")
15057 }
15058 } else if let Some(name) = &action.name {
15059 let project = self.project().context("no project")?;
15060 let snippet_store = project.read(cx).snippets().read(cx);
15061 let snippet = snippet_store
15062 .snippets_for(action.language.clone(), cx)
15063 .into_iter()
15064 .find(|snippet| snippet.name == *name)
15065 .context("snippet not found")?;
15066 Snippet::parse(&snippet.body)?
15067 } else {
15068 // todo(andrew): open modal to select snippet
15069 bail!("`name` or `snippet` is required")
15070 };
15071
15072 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15073 }
15074
15075 fn select_match_ranges(
15076 &mut self,
15077 range: Range<MultiBufferOffset>,
15078 reversed: bool,
15079 replace_newest: bool,
15080 auto_scroll: Option<Autoscroll>,
15081 window: &mut Window,
15082 cx: &mut Context<Editor>,
15083 ) {
15084 self.unfold_ranges(
15085 std::slice::from_ref(&range),
15086 false,
15087 auto_scroll.is_some(),
15088 cx,
15089 );
15090 let effects = if let Some(scroll) = auto_scroll {
15091 SelectionEffects::scroll(scroll)
15092 } else {
15093 SelectionEffects::no_scroll()
15094 };
15095 self.change_selections(effects, window, cx, |s| {
15096 if replace_newest {
15097 s.delete(s.newest_anchor().id);
15098 }
15099 if reversed {
15100 s.insert_range(range.end..range.start);
15101 } else {
15102 s.insert_range(range);
15103 }
15104 });
15105 }
15106
15107 pub fn select_next_match_internal(
15108 &mut self,
15109 display_map: &DisplaySnapshot,
15110 replace_newest: bool,
15111 autoscroll: Option<Autoscroll>,
15112 window: &mut Window,
15113 cx: &mut Context<Self>,
15114 ) -> Result<()> {
15115 let buffer = display_map.buffer_snapshot();
15116 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15117 if let Some(mut select_next_state) = self.select_next_state.take() {
15118 let query = &select_next_state.query;
15119 if !select_next_state.done {
15120 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15121 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15122 let mut next_selected_range = None;
15123
15124 let bytes_after_last_selection =
15125 buffer.bytes_in_range(last_selection.end..buffer.len());
15126 let bytes_before_first_selection =
15127 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15128 let query_matches = query
15129 .stream_find_iter(bytes_after_last_selection)
15130 .map(|result| (last_selection.end, result))
15131 .chain(
15132 query
15133 .stream_find_iter(bytes_before_first_selection)
15134 .map(|result| (MultiBufferOffset(0), result)),
15135 );
15136
15137 for (start_offset, query_match) in query_matches {
15138 let query_match = query_match.unwrap(); // can only fail due to I/O
15139 let offset_range =
15140 start_offset + query_match.start()..start_offset + query_match.end();
15141
15142 if !select_next_state.wordwise
15143 || (!buffer.is_inside_word(offset_range.start, None)
15144 && !buffer.is_inside_word(offset_range.end, None))
15145 {
15146 let idx = selections
15147 .partition_point(|selection| selection.end <= offset_range.start);
15148 let overlaps = selections
15149 .get(idx)
15150 .map_or(false, |selection| selection.start < offset_range.end);
15151
15152 if !overlaps {
15153 next_selected_range = Some(offset_range);
15154 break;
15155 }
15156 }
15157 }
15158
15159 if let Some(next_selected_range) = next_selected_range {
15160 self.select_match_ranges(
15161 next_selected_range,
15162 last_selection.reversed,
15163 replace_newest,
15164 autoscroll,
15165 window,
15166 cx,
15167 );
15168 } else {
15169 select_next_state.done = true;
15170 }
15171 }
15172
15173 self.select_next_state = Some(select_next_state);
15174 } else {
15175 let mut only_carets = true;
15176 let mut same_text_selected = true;
15177 let mut selected_text = None;
15178
15179 let mut selections_iter = selections.iter().peekable();
15180 while let Some(selection) = selections_iter.next() {
15181 if selection.start != selection.end {
15182 only_carets = false;
15183 }
15184
15185 if same_text_selected {
15186 if selected_text.is_none() {
15187 selected_text =
15188 Some(buffer.text_for_range(selection.range()).collect::<String>());
15189 }
15190
15191 if let Some(next_selection) = selections_iter.peek() {
15192 if next_selection.len() == selection.len() {
15193 let next_selected_text = buffer
15194 .text_for_range(next_selection.range())
15195 .collect::<String>();
15196 if Some(next_selected_text) != selected_text {
15197 same_text_selected = false;
15198 selected_text = None;
15199 }
15200 } else {
15201 same_text_selected = false;
15202 selected_text = None;
15203 }
15204 }
15205 }
15206 }
15207
15208 if only_carets {
15209 for selection in &mut selections {
15210 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15211 selection.start = word_range.start;
15212 selection.end = word_range.end;
15213 selection.goal = SelectionGoal::None;
15214 selection.reversed = false;
15215 self.select_match_ranges(
15216 selection.start..selection.end,
15217 selection.reversed,
15218 replace_newest,
15219 autoscroll,
15220 window,
15221 cx,
15222 );
15223 }
15224
15225 if selections.len() == 1 {
15226 let selection = selections
15227 .last()
15228 .expect("ensured that there's only one selection");
15229 let query = buffer
15230 .text_for_range(selection.start..selection.end)
15231 .collect::<String>();
15232 let is_empty = query.is_empty();
15233 let select_state = SelectNextState {
15234 query: self.build_query(&[query], cx)?,
15235 wordwise: true,
15236 done: is_empty,
15237 };
15238 self.select_next_state = Some(select_state);
15239 } else {
15240 self.select_next_state = None;
15241 }
15242 } else if let Some(selected_text) = selected_text {
15243 self.select_next_state = Some(SelectNextState {
15244 query: self.build_query(&[selected_text], cx)?,
15245 wordwise: false,
15246 done: false,
15247 });
15248 self.select_next_match_internal(
15249 display_map,
15250 replace_newest,
15251 autoscroll,
15252 window,
15253 cx,
15254 )?;
15255 }
15256 }
15257 Ok(())
15258 }
15259
15260 pub fn select_all_matches(
15261 &mut self,
15262 _action: &SelectAllMatches,
15263 window: &mut Window,
15264 cx: &mut Context<Self>,
15265 ) -> Result<()> {
15266 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15267
15268 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15269
15270 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15271 let Some(select_next_state) = self.select_next_state.as_mut() else {
15272 return Ok(());
15273 };
15274 if select_next_state.done {
15275 return Ok(());
15276 }
15277
15278 let mut new_selections = Vec::new();
15279
15280 let reversed = self
15281 .selections
15282 .oldest::<MultiBufferOffset>(&display_map)
15283 .reversed;
15284 let buffer = display_map.buffer_snapshot();
15285 let query_matches = select_next_state
15286 .query
15287 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15288
15289 for query_match in query_matches.into_iter() {
15290 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15291 let offset_range = if reversed {
15292 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15293 } else {
15294 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15295 };
15296
15297 if !select_next_state.wordwise
15298 || (!buffer.is_inside_word(offset_range.start, None)
15299 && !buffer.is_inside_word(offset_range.end, None))
15300 {
15301 new_selections.push(offset_range.start..offset_range.end);
15302 }
15303 }
15304
15305 select_next_state.done = true;
15306
15307 if new_selections.is_empty() {
15308 log::error!("bug: new_selections is empty in select_all_matches");
15309 return Ok(());
15310 }
15311
15312 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15313 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15314 selections.select_ranges(new_selections)
15315 });
15316
15317 Ok(())
15318 }
15319
15320 pub fn select_next(
15321 &mut self,
15322 action: &SelectNext,
15323 window: &mut Window,
15324 cx: &mut Context<Self>,
15325 ) -> Result<()> {
15326 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15327 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15328 self.select_next_match_internal(
15329 &display_map,
15330 action.replace_newest,
15331 Some(Autoscroll::newest()),
15332 window,
15333 cx,
15334 )?;
15335 Ok(())
15336 }
15337
15338 pub fn select_previous(
15339 &mut self,
15340 action: &SelectPrevious,
15341 window: &mut Window,
15342 cx: &mut Context<Self>,
15343 ) -> Result<()> {
15344 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15345 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15346 let buffer = display_map.buffer_snapshot();
15347 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15348 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15349 let query = &select_prev_state.query;
15350 if !select_prev_state.done {
15351 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15352 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15353 let mut next_selected_range = None;
15354 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15355 let bytes_before_last_selection =
15356 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15357 let bytes_after_first_selection =
15358 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15359 let query_matches = query
15360 .stream_find_iter(bytes_before_last_selection)
15361 .map(|result| (last_selection.start, result))
15362 .chain(
15363 query
15364 .stream_find_iter(bytes_after_first_selection)
15365 .map(|result| (buffer.len(), result)),
15366 );
15367 for (end_offset, query_match) in query_matches {
15368 let query_match = query_match.unwrap(); // can only fail due to I/O
15369 let offset_range =
15370 end_offset - query_match.end()..end_offset - query_match.start();
15371
15372 if !select_prev_state.wordwise
15373 || (!buffer.is_inside_word(offset_range.start, None)
15374 && !buffer.is_inside_word(offset_range.end, None))
15375 {
15376 next_selected_range = Some(offset_range);
15377 break;
15378 }
15379 }
15380
15381 if let Some(next_selected_range) = next_selected_range {
15382 self.select_match_ranges(
15383 next_selected_range,
15384 last_selection.reversed,
15385 action.replace_newest,
15386 Some(Autoscroll::newest()),
15387 window,
15388 cx,
15389 );
15390 } else {
15391 select_prev_state.done = true;
15392 }
15393 }
15394
15395 self.select_prev_state = Some(select_prev_state);
15396 } else {
15397 let mut only_carets = true;
15398 let mut same_text_selected = true;
15399 let mut selected_text = None;
15400
15401 let mut selections_iter = selections.iter().peekable();
15402 while let Some(selection) = selections_iter.next() {
15403 if selection.start != selection.end {
15404 only_carets = false;
15405 }
15406
15407 if same_text_selected {
15408 if selected_text.is_none() {
15409 selected_text =
15410 Some(buffer.text_for_range(selection.range()).collect::<String>());
15411 }
15412
15413 if let Some(next_selection) = selections_iter.peek() {
15414 if next_selection.len() == selection.len() {
15415 let next_selected_text = buffer
15416 .text_for_range(next_selection.range())
15417 .collect::<String>();
15418 if Some(next_selected_text) != selected_text {
15419 same_text_selected = false;
15420 selected_text = None;
15421 }
15422 } else {
15423 same_text_selected = false;
15424 selected_text = None;
15425 }
15426 }
15427 }
15428 }
15429
15430 if only_carets {
15431 for selection in &mut selections {
15432 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15433 selection.start = word_range.start;
15434 selection.end = word_range.end;
15435 selection.goal = SelectionGoal::None;
15436 selection.reversed = false;
15437 self.select_match_ranges(
15438 selection.start..selection.end,
15439 selection.reversed,
15440 action.replace_newest,
15441 Some(Autoscroll::newest()),
15442 window,
15443 cx,
15444 );
15445 }
15446 if selections.len() == 1 {
15447 let selection = selections
15448 .last()
15449 .expect("ensured that there's only one selection");
15450 let query = buffer
15451 .text_for_range(selection.start..selection.end)
15452 .collect::<String>();
15453 let is_empty = query.is_empty();
15454 let select_state = SelectNextState {
15455 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15456 wordwise: true,
15457 done: is_empty,
15458 };
15459 self.select_prev_state = Some(select_state);
15460 } else {
15461 self.select_prev_state = None;
15462 }
15463 } else if let Some(selected_text) = selected_text {
15464 self.select_prev_state = Some(SelectNextState {
15465 query: self
15466 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15467 wordwise: false,
15468 done: false,
15469 });
15470 self.select_previous(action, window, cx)?;
15471 }
15472 }
15473 Ok(())
15474 }
15475
15476 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15477 /// setting the case sensitivity based on the global
15478 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15479 /// editor's settings.
15480 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15481 where
15482 I: IntoIterator<Item = P>,
15483 P: AsRef<[u8]>,
15484 {
15485 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15486 || EditorSettings::get_global(cx).search.case_sensitive,
15487 |value| value,
15488 );
15489
15490 let mut builder = AhoCorasickBuilder::new();
15491 builder.ascii_case_insensitive(!case_sensitive);
15492 builder.build(patterns)
15493 }
15494
15495 pub fn find_next_match(
15496 &mut self,
15497 _: &FindNextMatch,
15498 window: &mut Window,
15499 cx: &mut Context<Self>,
15500 ) -> Result<()> {
15501 let selections = self.selections.disjoint_anchors_arc();
15502 match selections.first() {
15503 Some(first) if selections.len() >= 2 => {
15504 self.change_selections(Default::default(), window, cx, |s| {
15505 s.select_ranges([first.range()]);
15506 });
15507 }
15508 _ => self.select_next(
15509 &SelectNext {
15510 replace_newest: true,
15511 },
15512 window,
15513 cx,
15514 )?,
15515 }
15516 Ok(())
15517 }
15518
15519 pub fn find_previous_match(
15520 &mut self,
15521 _: &FindPreviousMatch,
15522 window: &mut Window,
15523 cx: &mut Context<Self>,
15524 ) -> Result<()> {
15525 let selections = self.selections.disjoint_anchors_arc();
15526 match selections.last() {
15527 Some(last) if selections.len() >= 2 => {
15528 self.change_selections(Default::default(), window, cx, |s| {
15529 s.select_ranges([last.range()]);
15530 });
15531 }
15532 _ => self.select_previous(
15533 &SelectPrevious {
15534 replace_newest: true,
15535 },
15536 window,
15537 cx,
15538 )?,
15539 }
15540 Ok(())
15541 }
15542
15543 pub fn toggle_comments(
15544 &mut self,
15545 action: &ToggleComments,
15546 window: &mut Window,
15547 cx: &mut Context<Self>,
15548 ) {
15549 if self.read_only(cx) {
15550 return;
15551 }
15552 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15553 let text_layout_details = &self.text_layout_details(window);
15554 self.transact(window, cx, |this, window, cx| {
15555 let mut selections = this
15556 .selections
15557 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15558 let mut edits = Vec::new();
15559 let mut selection_edit_ranges = Vec::new();
15560 let mut last_toggled_row = None;
15561 let snapshot = this.buffer.read(cx).read(cx);
15562 let empty_str: Arc<str> = Arc::default();
15563 let mut suffixes_inserted = Vec::new();
15564 let ignore_indent = action.ignore_indent;
15565
15566 fn comment_prefix_range(
15567 snapshot: &MultiBufferSnapshot,
15568 row: MultiBufferRow,
15569 comment_prefix: &str,
15570 comment_prefix_whitespace: &str,
15571 ignore_indent: bool,
15572 ) -> Range<Point> {
15573 let indent_size = if ignore_indent {
15574 0
15575 } else {
15576 snapshot.indent_size_for_line(row).len
15577 };
15578
15579 let start = Point::new(row.0, indent_size);
15580
15581 let mut line_bytes = snapshot
15582 .bytes_in_range(start..snapshot.max_point())
15583 .flatten()
15584 .copied();
15585
15586 // If this line currently begins with the line comment prefix, then record
15587 // the range containing the prefix.
15588 if line_bytes
15589 .by_ref()
15590 .take(comment_prefix.len())
15591 .eq(comment_prefix.bytes())
15592 {
15593 // Include any whitespace that matches the comment prefix.
15594 let matching_whitespace_len = line_bytes
15595 .zip(comment_prefix_whitespace.bytes())
15596 .take_while(|(a, b)| a == b)
15597 .count() as u32;
15598 let end = Point::new(
15599 start.row,
15600 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15601 );
15602 start..end
15603 } else {
15604 start..start
15605 }
15606 }
15607
15608 fn comment_suffix_range(
15609 snapshot: &MultiBufferSnapshot,
15610 row: MultiBufferRow,
15611 comment_suffix: &str,
15612 comment_suffix_has_leading_space: bool,
15613 ) -> Range<Point> {
15614 let end = Point::new(row.0, snapshot.line_len(row));
15615 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15616
15617 let mut line_end_bytes = snapshot
15618 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15619 .flatten()
15620 .copied();
15621
15622 let leading_space_len = if suffix_start_column > 0
15623 && line_end_bytes.next() == Some(b' ')
15624 && comment_suffix_has_leading_space
15625 {
15626 1
15627 } else {
15628 0
15629 };
15630
15631 // If this line currently begins with the line comment prefix, then record
15632 // the range containing the prefix.
15633 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15634 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15635 start..end
15636 } else {
15637 end..end
15638 }
15639 }
15640
15641 // TODO: Handle selections that cross excerpts
15642 for selection in &mut selections {
15643 let start_column = snapshot
15644 .indent_size_for_line(MultiBufferRow(selection.start.row))
15645 .len;
15646 let language = if let Some(language) =
15647 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15648 {
15649 language
15650 } else {
15651 continue;
15652 };
15653
15654 selection_edit_ranges.clear();
15655
15656 // If multiple selections contain a given row, avoid processing that
15657 // row more than once.
15658 let mut start_row = MultiBufferRow(selection.start.row);
15659 if last_toggled_row == Some(start_row) {
15660 start_row = start_row.next_row();
15661 }
15662 let end_row =
15663 if selection.end.row > selection.start.row && selection.end.column == 0 {
15664 MultiBufferRow(selection.end.row - 1)
15665 } else {
15666 MultiBufferRow(selection.end.row)
15667 };
15668 last_toggled_row = Some(end_row);
15669
15670 if start_row > end_row {
15671 continue;
15672 }
15673
15674 // If the language has line comments, toggle those.
15675 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15676
15677 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15678 if ignore_indent {
15679 full_comment_prefixes = full_comment_prefixes
15680 .into_iter()
15681 .map(|s| Arc::from(s.trim_end()))
15682 .collect();
15683 }
15684
15685 if !full_comment_prefixes.is_empty() {
15686 let first_prefix = full_comment_prefixes
15687 .first()
15688 .expect("prefixes is non-empty");
15689 let prefix_trimmed_lengths = full_comment_prefixes
15690 .iter()
15691 .map(|p| p.trim_end_matches(' ').len())
15692 .collect::<SmallVec<[usize; 4]>>();
15693
15694 let mut all_selection_lines_are_comments = true;
15695
15696 for row in start_row.0..=end_row.0 {
15697 let row = MultiBufferRow(row);
15698 if start_row < end_row && snapshot.is_line_blank(row) {
15699 continue;
15700 }
15701
15702 let prefix_range = full_comment_prefixes
15703 .iter()
15704 .zip(prefix_trimmed_lengths.iter().copied())
15705 .map(|(prefix, trimmed_prefix_len)| {
15706 comment_prefix_range(
15707 snapshot.deref(),
15708 row,
15709 &prefix[..trimmed_prefix_len],
15710 &prefix[trimmed_prefix_len..],
15711 ignore_indent,
15712 )
15713 })
15714 .max_by_key(|range| range.end.column - range.start.column)
15715 .expect("prefixes is non-empty");
15716
15717 if prefix_range.is_empty() {
15718 all_selection_lines_are_comments = false;
15719 }
15720
15721 selection_edit_ranges.push(prefix_range);
15722 }
15723
15724 if all_selection_lines_are_comments {
15725 edits.extend(
15726 selection_edit_ranges
15727 .iter()
15728 .cloned()
15729 .map(|range| (range, empty_str.clone())),
15730 );
15731 } else {
15732 let min_column = selection_edit_ranges
15733 .iter()
15734 .map(|range| range.start.column)
15735 .min()
15736 .unwrap_or(0);
15737 edits.extend(selection_edit_ranges.iter().map(|range| {
15738 let position = Point::new(range.start.row, min_column);
15739 (position..position, first_prefix.clone())
15740 }));
15741 }
15742 } else if let Some(BlockCommentConfig {
15743 start: full_comment_prefix,
15744 end: comment_suffix,
15745 ..
15746 }) = language.block_comment()
15747 {
15748 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15749 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15750 let prefix_range = comment_prefix_range(
15751 snapshot.deref(),
15752 start_row,
15753 comment_prefix,
15754 comment_prefix_whitespace,
15755 ignore_indent,
15756 );
15757 let suffix_range = comment_suffix_range(
15758 snapshot.deref(),
15759 end_row,
15760 comment_suffix.trim_start_matches(' '),
15761 comment_suffix.starts_with(' '),
15762 );
15763
15764 if prefix_range.is_empty() || suffix_range.is_empty() {
15765 edits.push((
15766 prefix_range.start..prefix_range.start,
15767 full_comment_prefix.clone(),
15768 ));
15769 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15770 suffixes_inserted.push((end_row, comment_suffix.len()));
15771 } else {
15772 edits.push((prefix_range, empty_str.clone()));
15773 edits.push((suffix_range, empty_str.clone()));
15774 }
15775 } else {
15776 continue;
15777 }
15778 }
15779
15780 drop(snapshot);
15781 this.buffer.update(cx, |buffer, cx| {
15782 buffer.edit(edits, None, cx);
15783 });
15784
15785 // Adjust selections so that they end before any comment suffixes that
15786 // were inserted.
15787 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15788 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15789 let snapshot = this.buffer.read(cx).read(cx);
15790 for selection in &mut selections {
15791 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15792 match row.cmp(&MultiBufferRow(selection.end.row)) {
15793 Ordering::Less => {
15794 suffixes_inserted.next();
15795 continue;
15796 }
15797 Ordering::Greater => break,
15798 Ordering::Equal => {
15799 if selection.end.column == snapshot.line_len(row) {
15800 if selection.is_empty() {
15801 selection.start.column -= suffix_len as u32;
15802 }
15803 selection.end.column -= suffix_len as u32;
15804 }
15805 break;
15806 }
15807 }
15808 }
15809 }
15810
15811 drop(snapshot);
15812 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15813
15814 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15815 let selections_on_single_row = selections.windows(2).all(|selections| {
15816 selections[0].start.row == selections[1].start.row
15817 && selections[0].end.row == selections[1].end.row
15818 && selections[0].start.row == selections[0].end.row
15819 });
15820 let selections_selecting = selections
15821 .iter()
15822 .any(|selection| selection.start != selection.end);
15823 let advance_downwards = action.advance_downwards
15824 && selections_on_single_row
15825 && !selections_selecting
15826 && !matches!(this.mode, EditorMode::SingleLine);
15827
15828 if advance_downwards {
15829 let snapshot = this.buffer.read(cx).snapshot(cx);
15830
15831 this.change_selections(Default::default(), window, cx, |s| {
15832 s.move_cursors_with(|display_snapshot, display_point, _| {
15833 let mut point = display_point.to_point(display_snapshot);
15834 point.row += 1;
15835 point = snapshot.clip_point(point, Bias::Left);
15836 let display_point = point.to_display_point(display_snapshot);
15837 let goal = SelectionGoal::HorizontalPosition(
15838 display_snapshot
15839 .x_for_display_point(display_point, text_layout_details)
15840 .into(),
15841 );
15842 (display_point, goal)
15843 })
15844 });
15845 }
15846 });
15847 }
15848
15849 pub fn select_enclosing_symbol(
15850 &mut self,
15851 _: &SelectEnclosingSymbol,
15852 window: &mut Window,
15853 cx: &mut Context<Self>,
15854 ) {
15855 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15856
15857 let buffer = self.buffer.read(cx).snapshot(cx);
15858 let old_selections = self
15859 .selections
15860 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15861 .into_boxed_slice();
15862
15863 fn update_selection(
15864 selection: &Selection<MultiBufferOffset>,
15865 buffer_snap: &MultiBufferSnapshot,
15866 ) -> Option<Selection<MultiBufferOffset>> {
15867 let cursor = selection.head();
15868 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15869 for symbol in symbols.iter().rev() {
15870 let start = symbol.range.start.to_offset(buffer_snap);
15871 let end = symbol.range.end.to_offset(buffer_snap);
15872 let new_range = start..end;
15873 if start < selection.start || end > selection.end {
15874 return Some(Selection {
15875 id: selection.id,
15876 start: new_range.start,
15877 end: new_range.end,
15878 goal: SelectionGoal::None,
15879 reversed: selection.reversed,
15880 });
15881 }
15882 }
15883 None
15884 }
15885
15886 let mut selected_larger_symbol = false;
15887 let new_selections = old_selections
15888 .iter()
15889 .map(|selection| match update_selection(selection, &buffer) {
15890 Some(new_selection) => {
15891 if new_selection.range() != selection.range() {
15892 selected_larger_symbol = true;
15893 }
15894 new_selection
15895 }
15896 None => selection.clone(),
15897 })
15898 .collect::<Vec<_>>();
15899
15900 if selected_larger_symbol {
15901 self.change_selections(Default::default(), window, cx, |s| {
15902 s.select(new_selections);
15903 });
15904 }
15905 }
15906
15907 pub fn select_larger_syntax_node(
15908 &mut self,
15909 _: &SelectLargerSyntaxNode,
15910 window: &mut Window,
15911 cx: &mut Context<Self>,
15912 ) {
15913 let Some(visible_row_count) = self.visible_row_count() else {
15914 return;
15915 };
15916 let old_selections: Box<[_]> = self
15917 .selections
15918 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15919 .into();
15920 if old_selections.is_empty() {
15921 return;
15922 }
15923
15924 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15925
15926 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15927 let buffer = self.buffer.read(cx).snapshot(cx);
15928
15929 let mut selected_larger_node = false;
15930 let mut new_selections = old_selections
15931 .iter()
15932 .map(|selection| {
15933 let old_range = selection.start..selection.end;
15934
15935 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15936 // manually select word at selection
15937 if ["string_content", "inline"].contains(&node.kind()) {
15938 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15939 // ignore if word is already selected
15940 if !word_range.is_empty() && old_range != word_range {
15941 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15942 // only select word if start and end point belongs to same word
15943 if word_range == last_word_range {
15944 selected_larger_node = true;
15945 return Selection {
15946 id: selection.id,
15947 start: word_range.start,
15948 end: word_range.end,
15949 goal: SelectionGoal::None,
15950 reversed: selection.reversed,
15951 };
15952 }
15953 }
15954 }
15955 }
15956
15957 let mut new_range = old_range.clone();
15958 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15959 new_range = range;
15960 if !node.is_named() {
15961 continue;
15962 }
15963 if !display_map.intersects_fold(new_range.start)
15964 && !display_map.intersects_fold(new_range.end)
15965 {
15966 break;
15967 }
15968 }
15969
15970 selected_larger_node |= new_range != old_range;
15971 Selection {
15972 id: selection.id,
15973 start: new_range.start,
15974 end: new_range.end,
15975 goal: SelectionGoal::None,
15976 reversed: selection.reversed,
15977 }
15978 })
15979 .collect::<Vec<_>>();
15980
15981 if !selected_larger_node {
15982 return; // don't put this call in the history
15983 }
15984
15985 // scroll based on transformation done to the last selection created by the user
15986 let (last_old, last_new) = old_selections
15987 .last()
15988 .zip(new_selections.last().cloned())
15989 .expect("old_selections isn't empty");
15990
15991 // revert selection
15992 let is_selection_reversed = {
15993 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15994 new_selections.last_mut().expect("checked above").reversed =
15995 should_newest_selection_be_reversed;
15996 should_newest_selection_be_reversed
15997 };
15998
15999 if selected_larger_node {
16000 self.select_syntax_node_history.disable_clearing = true;
16001 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16002 s.select(new_selections.clone());
16003 });
16004 self.select_syntax_node_history.disable_clearing = false;
16005 }
16006
16007 let start_row = last_new.start.to_display_point(&display_map).row().0;
16008 let end_row = last_new.end.to_display_point(&display_map).row().0;
16009 let selection_height = end_row - start_row + 1;
16010 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
16011
16012 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
16013 let scroll_behavior = if fits_on_the_screen {
16014 self.request_autoscroll(Autoscroll::fit(), cx);
16015 SelectSyntaxNodeScrollBehavior::FitSelection
16016 } else if is_selection_reversed {
16017 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16018 SelectSyntaxNodeScrollBehavior::CursorTop
16019 } else {
16020 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16021 SelectSyntaxNodeScrollBehavior::CursorBottom
16022 };
16023
16024 self.select_syntax_node_history.push((
16025 old_selections,
16026 scroll_behavior,
16027 is_selection_reversed,
16028 ));
16029 }
16030
16031 pub fn select_smaller_syntax_node(
16032 &mut self,
16033 _: &SelectSmallerSyntaxNode,
16034 window: &mut Window,
16035 cx: &mut Context<Self>,
16036 ) {
16037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16038
16039 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16040 self.select_syntax_node_history.pop()
16041 {
16042 if let Some(selection) = selections.last_mut() {
16043 selection.reversed = is_selection_reversed;
16044 }
16045
16046 self.select_syntax_node_history.disable_clearing = true;
16047 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16048 s.select(selections.to_vec());
16049 });
16050 self.select_syntax_node_history.disable_clearing = false;
16051
16052 match scroll_behavior {
16053 SelectSyntaxNodeScrollBehavior::CursorTop => {
16054 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16055 }
16056 SelectSyntaxNodeScrollBehavior::FitSelection => {
16057 self.request_autoscroll(Autoscroll::fit(), cx);
16058 }
16059 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16060 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16061 }
16062 }
16063 }
16064 }
16065
16066 pub fn unwrap_syntax_node(
16067 &mut self,
16068 _: &UnwrapSyntaxNode,
16069 window: &mut Window,
16070 cx: &mut Context<Self>,
16071 ) {
16072 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16073
16074 let buffer = self.buffer.read(cx).snapshot(cx);
16075 let selections = self
16076 .selections
16077 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16078 .into_iter()
16079 // subtracting the offset requires sorting
16080 .sorted_by_key(|i| i.start);
16081
16082 let full_edits = selections
16083 .into_iter()
16084 .filter_map(|selection| {
16085 let child = if selection.is_empty()
16086 && let Some((_, ancestor_range)) =
16087 buffer.syntax_ancestor(selection.start..selection.end)
16088 {
16089 ancestor_range
16090 } else {
16091 selection.range()
16092 };
16093
16094 let mut parent = child.clone();
16095 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16096 parent = ancestor_range;
16097 if parent.start < child.start || parent.end > child.end {
16098 break;
16099 }
16100 }
16101
16102 if parent == child {
16103 return None;
16104 }
16105 let text = buffer.text_for_range(child).collect::<String>();
16106 Some((selection.id, parent, text))
16107 })
16108 .collect::<Vec<_>>();
16109 if full_edits.is_empty() {
16110 return;
16111 }
16112
16113 self.transact(window, cx, |this, window, cx| {
16114 this.buffer.update(cx, |buffer, cx| {
16115 buffer.edit(
16116 full_edits
16117 .iter()
16118 .map(|(_, p, t)| (p.clone(), t.clone()))
16119 .collect::<Vec<_>>(),
16120 None,
16121 cx,
16122 );
16123 });
16124 this.change_selections(Default::default(), window, cx, |s| {
16125 let mut offset = 0;
16126 let mut selections = vec![];
16127 for (id, parent, text) in full_edits {
16128 let start = parent.start - offset;
16129 offset += (parent.end - parent.start) - text.len();
16130 selections.push(Selection {
16131 id,
16132 start,
16133 end: start + text.len(),
16134 reversed: false,
16135 goal: Default::default(),
16136 });
16137 }
16138 s.select(selections);
16139 });
16140 });
16141 }
16142
16143 pub fn select_next_syntax_node(
16144 &mut self,
16145 _: &SelectNextSyntaxNode,
16146 window: &mut Window,
16147 cx: &mut Context<Self>,
16148 ) {
16149 let old_selections: Box<[_]> = self
16150 .selections
16151 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16152 .into();
16153 if old_selections.is_empty() {
16154 return;
16155 }
16156
16157 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16158
16159 let buffer = self.buffer.read(cx).snapshot(cx);
16160 let mut selected_sibling = false;
16161
16162 let new_selections = old_selections
16163 .iter()
16164 .map(|selection| {
16165 let old_range = selection.start..selection.end;
16166
16167 let old_range =
16168 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16169 let excerpt = buffer.excerpt_containing(old_range.clone());
16170
16171 if let Some(mut excerpt) = excerpt
16172 && let Some(node) = excerpt
16173 .buffer()
16174 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16175 {
16176 let new_range = excerpt.map_range_from_buffer(
16177 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16178 );
16179 selected_sibling = true;
16180 Selection {
16181 id: selection.id,
16182 start: new_range.start,
16183 end: new_range.end,
16184 goal: SelectionGoal::None,
16185 reversed: selection.reversed,
16186 }
16187 } else {
16188 selection.clone()
16189 }
16190 })
16191 .collect::<Vec<_>>();
16192
16193 if selected_sibling {
16194 self.change_selections(
16195 SelectionEffects::scroll(Autoscroll::fit()),
16196 window,
16197 cx,
16198 |s| {
16199 s.select(new_selections);
16200 },
16201 );
16202 }
16203 }
16204
16205 pub fn select_prev_syntax_node(
16206 &mut self,
16207 _: &SelectPreviousSyntaxNode,
16208 window: &mut Window,
16209 cx: &mut Context<Self>,
16210 ) {
16211 let old_selections: Box<[_]> = self
16212 .selections
16213 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16214 .into();
16215 if old_selections.is_empty() {
16216 return;
16217 }
16218
16219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16220
16221 let buffer = self.buffer.read(cx).snapshot(cx);
16222 let mut selected_sibling = false;
16223
16224 let new_selections = old_selections
16225 .iter()
16226 .map(|selection| {
16227 let old_range = selection.start..selection.end;
16228 let old_range =
16229 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16230 let excerpt = buffer.excerpt_containing(old_range.clone());
16231
16232 if let Some(mut excerpt) = excerpt
16233 && let Some(node) = excerpt
16234 .buffer()
16235 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16236 {
16237 let new_range = excerpt.map_range_from_buffer(
16238 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16239 );
16240 selected_sibling = true;
16241 Selection {
16242 id: selection.id,
16243 start: new_range.start,
16244 end: new_range.end,
16245 goal: SelectionGoal::None,
16246 reversed: selection.reversed,
16247 }
16248 } else {
16249 selection.clone()
16250 }
16251 })
16252 .collect::<Vec<_>>();
16253
16254 if selected_sibling {
16255 self.change_selections(
16256 SelectionEffects::scroll(Autoscroll::fit()),
16257 window,
16258 cx,
16259 |s| {
16260 s.select(new_selections);
16261 },
16262 );
16263 }
16264 }
16265
16266 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16267 if !EditorSettings::get_global(cx).gutter.runnables {
16268 self.clear_tasks();
16269 return Task::ready(());
16270 }
16271 let project = self.project().map(Entity::downgrade);
16272 let task_sources = self.lsp_task_sources(cx);
16273 let multi_buffer = self.buffer.downgrade();
16274 cx.spawn_in(window, async move |editor, cx| {
16275 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16276 let Some(project) = project.and_then(|p| p.upgrade()) else {
16277 return;
16278 };
16279 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16280 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16281 }) else {
16282 return;
16283 };
16284
16285 let hide_runnables = project
16286 .update(cx, |project, _| project.is_via_collab())
16287 .unwrap_or(true);
16288 if hide_runnables {
16289 return;
16290 }
16291 let new_rows =
16292 cx.background_spawn({
16293 let snapshot = display_snapshot.clone();
16294 async move {
16295 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16296 }
16297 })
16298 .await;
16299 let Ok(lsp_tasks) =
16300 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16301 else {
16302 return;
16303 };
16304 let lsp_tasks = lsp_tasks.await;
16305
16306 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16307 lsp_tasks
16308 .into_iter()
16309 .flat_map(|(kind, tasks)| {
16310 tasks.into_iter().filter_map(move |(location, task)| {
16311 Some((kind.clone(), location?, task))
16312 })
16313 })
16314 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16315 let buffer = location.target.buffer;
16316 let buffer_snapshot = buffer.read(cx).snapshot();
16317 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16318 |(excerpt_id, snapshot, _)| {
16319 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16320 display_snapshot
16321 .buffer_snapshot()
16322 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16323 } else {
16324 None
16325 }
16326 },
16327 );
16328 if let Some(offset) = offset {
16329 let task_buffer_range =
16330 location.target.range.to_point(&buffer_snapshot);
16331 let context_buffer_range =
16332 task_buffer_range.to_offset(&buffer_snapshot);
16333 let context_range = BufferOffset(context_buffer_range.start)
16334 ..BufferOffset(context_buffer_range.end);
16335
16336 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16337 .or_insert_with(|| RunnableTasks {
16338 templates: Vec::new(),
16339 offset,
16340 column: task_buffer_range.start.column,
16341 extra_variables: HashMap::default(),
16342 context_range,
16343 })
16344 .templates
16345 .push((kind, task.original_task().clone()));
16346 }
16347
16348 acc
16349 })
16350 }) else {
16351 return;
16352 };
16353
16354 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16355 buffer.language_settings(cx).tasks.prefer_lsp
16356 }) else {
16357 return;
16358 };
16359
16360 let rows = Self::runnable_rows(
16361 project,
16362 display_snapshot,
16363 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16364 new_rows,
16365 cx.clone(),
16366 )
16367 .await;
16368 editor
16369 .update(cx, |editor, _| {
16370 editor.clear_tasks();
16371 for (key, mut value) in rows {
16372 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16373 value.templates.extend(lsp_tasks.templates);
16374 }
16375
16376 editor.insert_tasks(key, value);
16377 }
16378 for (key, value) in lsp_tasks_by_rows {
16379 editor.insert_tasks(key, value);
16380 }
16381 })
16382 .ok();
16383 })
16384 }
16385 fn fetch_runnable_ranges(
16386 snapshot: &DisplaySnapshot,
16387 range: Range<Anchor>,
16388 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16389 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16390 }
16391
16392 fn runnable_rows(
16393 project: Entity<Project>,
16394 snapshot: DisplaySnapshot,
16395 prefer_lsp: bool,
16396 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16397 cx: AsyncWindowContext,
16398 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16399 cx.spawn(async move |cx| {
16400 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16401 for (run_range, mut runnable) in runnable_ranges {
16402 let Some(tasks) = cx
16403 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16404 .ok()
16405 else {
16406 continue;
16407 };
16408 let mut tasks = tasks.await;
16409
16410 if prefer_lsp {
16411 tasks.retain(|(task_kind, _)| {
16412 !matches!(task_kind, TaskSourceKind::Language { .. })
16413 });
16414 }
16415 if tasks.is_empty() {
16416 continue;
16417 }
16418
16419 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16420 let Some(row) = snapshot
16421 .buffer_snapshot()
16422 .buffer_line_for_row(MultiBufferRow(point.row))
16423 .map(|(_, range)| range.start.row)
16424 else {
16425 continue;
16426 };
16427
16428 let context_range =
16429 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16430 runnable_rows.push((
16431 (runnable.buffer_id, row),
16432 RunnableTasks {
16433 templates: tasks,
16434 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16435 context_range,
16436 column: point.column,
16437 extra_variables: runnable.extra_captures,
16438 },
16439 ));
16440 }
16441 runnable_rows
16442 })
16443 }
16444
16445 fn templates_with_tags(
16446 project: &Entity<Project>,
16447 runnable: &mut Runnable,
16448 cx: &mut App,
16449 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16450 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16451 let (worktree_id, file) = project
16452 .buffer_for_id(runnable.buffer, cx)
16453 .and_then(|buffer| buffer.read(cx).file())
16454 .map(|file| (file.worktree_id(cx), file.clone()))
16455 .unzip();
16456
16457 (
16458 project.task_store().read(cx).task_inventory().cloned(),
16459 worktree_id,
16460 file,
16461 )
16462 });
16463
16464 let tags = mem::take(&mut runnable.tags);
16465 let language = runnable.language.clone();
16466 cx.spawn(async move |cx| {
16467 let mut templates_with_tags = Vec::new();
16468 if let Some(inventory) = inventory {
16469 for RunnableTag(tag) in tags {
16470 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16471 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16472 }) else {
16473 return templates_with_tags;
16474 };
16475 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16476 move |(_, template)| {
16477 template.tags.iter().any(|source_tag| source_tag == &tag)
16478 },
16479 ));
16480 }
16481 }
16482 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16483
16484 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16485 // Strongest source wins; if we have worktree tag binding, prefer that to
16486 // global and language bindings;
16487 // if we have a global binding, prefer that to language binding.
16488 let first_mismatch = templates_with_tags
16489 .iter()
16490 .position(|(tag_source, _)| tag_source != leading_tag_source);
16491 if let Some(index) = first_mismatch {
16492 templates_with_tags.truncate(index);
16493 }
16494 }
16495
16496 templates_with_tags
16497 })
16498 }
16499
16500 pub fn move_to_enclosing_bracket(
16501 &mut self,
16502 _: &MoveToEnclosingBracket,
16503 window: &mut Window,
16504 cx: &mut Context<Self>,
16505 ) {
16506 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16507 self.change_selections(Default::default(), window, cx, |s| {
16508 s.move_offsets_with(|snapshot, selection| {
16509 let Some(enclosing_bracket_ranges) =
16510 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16511 else {
16512 return;
16513 };
16514
16515 let mut best_length = usize::MAX;
16516 let mut best_inside = false;
16517 let mut best_in_bracket_range = false;
16518 let mut best_destination = None;
16519 for (open, close) in enclosing_bracket_ranges {
16520 let close = close.to_inclusive();
16521 let length = *close.end() - open.start;
16522 let inside = selection.start >= open.end && selection.end <= *close.start();
16523 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16524 || close.contains(&selection.head());
16525
16526 // If best is next to a bracket and current isn't, skip
16527 if !in_bracket_range && best_in_bracket_range {
16528 continue;
16529 }
16530
16531 // Prefer smaller lengths unless best is inside and current isn't
16532 if length > best_length && (best_inside || !inside) {
16533 continue;
16534 }
16535
16536 best_length = length;
16537 best_inside = inside;
16538 best_in_bracket_range = in_bracket_range;
16539 best_destination = Some(
16540 if close.contains(&selection.start) && close.contains(&selection.end) {
16541 if inside { open.end } else { open.start }
16542 } else if inside {
16543 *close.start()
16544 } else {
16545 *close.end()
16546 },
16547 );
16548 }
16549
16550 if let Some(destination) = best_destination {
16551 selection.collapse_to(destination, SelectionGoal::None);
16552 }
16553 })
16554 });
16555 }
16556
16557 pub fn undo_selection(
16558 &mut self,
16559 _: &UndoSelection,
16560 window: &mut Window,
16561 cx: &mut Context<Self>,
16562 ) {
16563 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16564 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16565 self.selection_history.mode = SelectionHistoryMode::Undoing;
16566 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16567 this.end_selection(window, cx);
16568 this.change_selections(
16569 SelectionEffects::scroll(Autoscroll::newest()),
16570 window,
16571 cx,
16572 |s| s.select_anchors(entry.selections.to_vec()),
16573 );
16574 });
16575 self.selection_history.mode = SelectionHistoryMode::Normal;
16576
16577 self.select_next_state = entry.select_next_state;
16578 self.select_prev_state = entry.select_prev_state;
16579 self.add_selections_state = entry.add_selections_state;
16580 }
16581 }
16582
16583 pub fn redo_selection(
16584 &mut self,
16585 _: &RedoSelection,
16586 window: &mut Window,
16587 cx: &mut Context<Self>,
16588 ) {
16589 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16590 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16591 self.selection_history.mode = SelectionHistoryMode::Redoing;
16592 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16593 this.end_selection(window, cx);
16594 this.change_selections(
16595 SelectionEffects::scroll(Autoscroll::newest()),
16596 window,
16597 cx,
16598 |s| s.select_anchors(entry.selections.to_vec()),
16599 );
16600 });
16601 self.selection_history.mode = SelectionHistoryMode::Normal;
16602
16603 self.select_next_state = entry.select_next_state;
16604 self.select_prev_state = entry.select_prev_state;
16605 self.add_selections_state = entry.add_selections_state;
16606 }
16607 }
16608
16609 pub fn expand_excerpts(
16610 &mut self,
16611 action: &ExpandExcerpts,
16612 _: &mut Window,
16613 cx: &mut Context<Self>,
16614 ) {
16615 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16616 }
16617
16618 pub fn expand_excerpts_down(
16619 &mut self,
16620 action: &ExpandExcerptsDown,
16621 _: &mut Window,
16622 cx: &mut Context<Self>,
16623 ) {
16624 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16625 }
16626
16627 pub fn expand_excerpts_up(
16628 &mut self,
16629 action: &ExpandExcerptsUp,
16630 _: &mut Window,
16631 cx: &mut Context<Self>,
16632 ) {
16633 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16634 }
16635
16636 pub fn expand_excerpts_for_direction(
16637 &mut self,
16638 lines: u32,
16639 direction: ExpandExcerptDirection,
16640
16641 cx: &mut Context<Self>,
16642 ) {
16643 let selections = self.selections.disjoint_anchors_arc();
16644
16645 let lines = if lines == 0 {
16646 EditorSettings::get_global(cx).expand_excerpt_lines
16647 } else {
16648 lines
16649 };
16650
16651 self.buffer.update(cx, |buffer, cx| {
16652 let snapshot = buffer.snapshot(cx);
16653 let mut excerpt_ids = selections
16654 .iter()
16655 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16656 .collect::<Vec<_>>();
16657 excerpt_ids.sort();
16658 excerpt_ids.dedup();
16659 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16660 })
16661 }
16662
16663 pub fn expand_excerpt(
16664 &mut self,
16665 excerpt: ExcerptId,
16666 direction: ExpandExcerptDirection,
16667 window: &mut Window,
16668 cx: &mut Context<Self>,
16669 ) {
16670 let current_scroll_position = self.scroll_position(cx);
16671 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16672 let mut scroll = None;
16673
16674 if direction == ExpandExcerptDirection::Down {
16675 let multi_buffer = self.buffer.read(cx);
16676 let snapshot = multi_buffer.snapshot(cx);
16677 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16678 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16679 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16680 {
16681 let buffer_snapshot = buffer.read(cx).snapshot();
16682 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16683 let last_row = buffer_snapshot.max_point().row;
16684 let lines_below = last_row.saturating_sub(excerpt_end_row);
16685 if lines_below >= lines_to_expand {
16686 scroll = Some(
16687 current_scroll_position
16688 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16689 );
16690 }
16691 }
16692 }
16693 if direction == ExpandExcerptDirection::Up
16694 && self
16695 .buffer
16696 .read(cx)
16697 .snapshot(cx)
16698 .excerpt_before(excerpt)
16699 .is_none()
16700 {
16701 scroll = Some(current_scroll_position);
16702 }
16703
16704 self.buffer.update(cx, |buffer, cx| {
16705 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16706 });
16707
16708 if let Some(new_scroll_position) = scroll {
16709 self.set_scroll_position(new_scroll_position, window, cx);
16710 }
16711 }
16712
16713 pub fn go_to_singleton_buffer_point(
16714 &mut self,
16715 point: Point,
16716 window: &mut Window,
16717 cx: &mut Context<Self>,
16718 ) {
16719 self.go_to_singleton_buffer_range(point..point, window, cx);
16720 }
16721
16722 pub fn go_to_singleton_buffer_range(
16723 &mut self,
16724 range: Range<Point>,
16725 window: &mut Window,
16726 cx: &mut Context<Self>,
16727 ) {
16728 let multibuffer = self.buffer().read(cx);
16729 let Some(buffer) = multibuffer.as_singleton() else {
16730 return;
16731 };
16732 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16733 return;
16734 };
16735 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16736 return;
16737 };
16738 self.change_selections(
16739 SelectionEffects::default().nav_history(true),
16740 window,
16741 cx,
16742 |s| s.select_anchor_ranges([start..end]),
16743 );
16744 }
16745
16746 pub fn go_to_diagnostic(
16747 &mut self,
16748 action: &GoToDiagnostic,
16749 window: &mut Window,
16750 cx: &mut Context<Self>,
16751 ) {
16752 if !self.diagnostics_enabled() {
16753 return;
16754 }
16755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16756 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16757 }
16758
16759 pub fn go_to_prev_diagnostic(
16760 &mut self,
16761 action: &GoToPreviousDiagnostic,
16762 window: &mut Window,
16763 cx: &mut Context<Self>,
16764 ) {
16765 if !self.diagnostics_enabled() {
16766 return;
16767 }
16768 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16769 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16770 }
16771
16772 pub fn go_to_diagnostic_impl(
16773 &mut self,
16774 direction: Direction,
16775 severity: GoToDiagnosticSeverityFilter,
16776 window: &mut Window,
16777 cx: &mut Context<Self>,
16778 ) {
16779 let buffer = self.buffer.read(cx).snapshot(cx);
16780 let selection = self
16781 .selections
16782 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16783
16784 let mut active_group_id = None;
16785 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16786 && active_group.active_range.start.to_offset(&buffer) == selection.start
16787 {
16788 active_group_id = Some(active_group.group_id);
16789 }
16790
16791 fn filtered<'a>(
16792 severity: GoToDiagnosticSeverityFilter,
16793 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16794 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16795 diagnostics
16796 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16797 .filter(|entry| entry.range.start != entry.range.end)
16798 .filter(|entry| !entry.diagnostic.is_unnecessary)
16799 }
16800
16801 let before = filtered(
16802 severity,
16803 buffer
16804 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16805 .filter(|entry| entry.range.start <= selection.start),
16806 );
16807 let after = filtered(
16808 severity,
16809 buffer
16810 .diagnostics_in_range(selection.start..buffer.len())
16811 .filter(|entry| entry.range.start >= selection.start),
16812 );
16813
16814 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16815 if direction == Direction::Prev {
16816 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16817 {
16818 for diagnostic in prev_diagnostics.into_iter().rev() {
16819 if diagnostic.range.start != selection.start
16820 || active_group_id
16821 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16822 {
16823 found = Some(diagnostic);
16824 break 'outer;
16825 }
16826 }
16827 }
16828 } else {
16829 for diagnostic in after.chain(before) {
16830 if diagnostic.range.start != selection.start
16831 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16832 {
16833 found = Some(diagnostic);
16834 break;
16835 }
16836 }
16837 }
16838 let Some(next_diagnostic) = found else {
16839 return;
16840 };
16841
16842 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16843 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16844 return;
16845 };
16846 let snapshot = self.snapshot(window, cx);
16847 if snapshot.intersects_fold(next_diagnostic.range.start) {
16848 self.unfold_ranges(
16849 std::slice::from_ref(&next_diagnostic.range),
16850 true,
16851 false,
16852 cx,
16853 );
16854 }
16855 self.change_selections(Default::default(), window, cx, |s| {
16856 s.select_ranges(vec![
16857 next_diagnostic.range.start..next_diagnostic.range.start,
16858 ])
16859 });
16860 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16861 self.refresh_edit_prediction(false, true, window, cx);
16862 }
16863
16864 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16865 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16866 let snapshot = self.snapshot(window, cx);
16867 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16868 self.go_to_hunk_before_or_after_position(
16869 &snapshot,
16870 selection.head(),
16871 Direction::Next,
16872 window,
16873 cx,
16874 );
16875 }
16876
16877 pub fn go_to_hunk_before_or_after_position(
16878 &mut self,
16879 snapshot: &EditorSnapshot,
16880 position: Point,
16881 direction: Direction,
16882 window: &mut Window,
16883 cx: &mut Context<Editor>,
16884 ) {
16885 let row = if direction == Direction::Next {
16886 self.hunk_after_position(snapshot, position)
16887 .map(|hunk| hunk.row_range.start)
16888 } else {
16889 self.hunk_before_position(snapshot, position)
16890 };
16891
16892 if let Some(row) = row {
16893 let destination = Point::new(row.0, 0);
16894 let autoscroll = Autoscroll::center();
16895
16896 self.unfold_ranges(&[destination..destination], false, false, cx);
16897 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16898 s.select_ranges([destination..destination]);
16899 });
16900 }
16901 }
16902
16903 fn hunk_after_position(
16904 &mut self,
16905 snapshot: &EditorSnapshot,
16906 position: Point,
16907 ) -> Option<MultiBufferDiffHunk> {
16908 snapshot
16909 .buffer_snapshot()
16910 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16911 .find(|hunk| hunk.row_range.start.0 > position.row)
16912 .or_else(|| {
16913 snapshot
16914 .buffer_snapshot()
16915 .diff_hunks_in_range(Point::zero()..position)
16916 .find(|hunk| hunk.row_range.end.0 < position.row)
16917 })
16918 }
16919
16920 fn go_to_prev_hunk(
16921 &mut self,
16922 _: &GoToPreviousHunk,
16923 window: &mut Window,
16924 cx: &mut Context<Self>,
16925 ) {
16926 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16927 let snapshot = self.snapshot(window, cx);
16928 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16929 self.go_to_hunk_before_or_after_position(
16930 &snapshot,
16931 selection.head(),
16932 Direction::Prev,
16933 window,
16934 cx,
16935 );
16936 }
16937
16938 fn hunk_before_position(
16939 &mut self,
16940 snapshot: &EditorSnapshot,
16941 position: Point,
16942 ) -> Option<MultiBufferRow> {
16943 snapshot
16944 .buffer_snapshot()
16945 .diff_hunk_before(position)
16946 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16947 }
16948
16949 fn go_to_next_change(
16950 &mut self,
16951 _: &GoToNextChange,
16952 window: &mut Window,
16953 cx: &mut Context<Self>,
16954 ) {
16955 if let Some(selections) = self
16956 .change_list
16957 .next_change(1, Direction::Next)
16958 .map(|s| s.to_vec())
16959 {
16960 self.change_selections(Default::default(), window, cx, |s| {
16961 let map = s.display_snapshot();
16962 s.select_display_ranges(selections.iter().map(|a| {
16963 let point = a.to_display_point(&map);
16964 point..point
16965 }))
16966 })
16967 }
16968 }
16969
16970 fn go_to_previous_change(
16971 &mut self,
16972 _: &GoToPreviousChange,
16973 window: &mut Window,
16974 cx: &mut Context<Self>,
16975 ) {
16976 if let Some(selections) = self
16977 .change_list
16978 .next_change(1, Direction::Prev)
16979 .map(|s| s.to_vec())
16980 {
16981 self.change_selections(Default::default(), window, cx, |s| {
16982 let map = s.display_snapshot();
16983 s.select_display_ranges(selections.iter().map(|a| {
16984 let point = a.to_display_point(&map);
16985 point..point
16986 }))
16987 })
16988 }
16989 }
16990
16991 pub fn go_to_next_document_highlight(
16992 &mut self,
16993 _: &GoToNextDocumentHighlight,
16994 window: &mut Window,
16995 cx: &mut Context<Self>,
16996 ) {
16997 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16998 }
16999
17000 pub fn go_to_prev_document_highlight(
17001 &mut self,
17002 _: &GoToPreviousDocumentHighlight,
17003 window: &mut Window,
17004 cx: &mut Context<Self>,
17005 ) {
17006 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
17007 }
17008
17009 pub fn go_to_document_highlight_before_or_after_position(
17010 &mut self,
17011 direction: Direction,
17012 window: &mut Window,
17013 cx: &mut Context<Editor>,
17014 ) {
17015 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
17016 let snapshot = self.snapshot(window, cx);
17017 let buffer = &snapshot.buffer_snapshot();
17018 let position = self
17019 .selections
17020 .newest::<Point>(&snapshot.display_snapshot)
17021 .head();
17022 let anchor_position = buffer.anchor_after(position);
17023
17024 // Get all document highlights (both read and write)
17025 let mut all_highlights = Vec::new();
17026
17027 if let Some((_, read_highlights)) = self
17028 .background_highlights
17029 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
17030 {
17031 all_highlights.extend(read_highlights.iter());
17032 }
17033
17034 if let Some((_, write_highlights)) = self
17035 .background_highlights
17036 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17037 {
17038 all_highlights.extend(write_highlights.iter());
17039 }
17040
17041 if all_highlights.is_empty() {
17042 return;
17043 }
17044
17045 // Sort highlights by position
17046 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17047
17048 let target_highlight = match direction {
17049 Direction::Next => {
17050 // Find the first highlight after the current position
17051 all_highlights
17052 .iter()
17053 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17054 }
17055 Direction::Prev => {
17056 // Find the last highlight before the current position
17057 all_highlights
17058 .iter()
17059 .rev()
17060 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17061 }
17062 };
17063
17064 if let Some(highlight) = target_highlight {
17065 let destination = highlight.start.to_point(buffer);
17066 let autoscroll = Autoscroll::center();
17067
17068 self.unfold_ranges(&[destination..destination], false, false, cx);
17069 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17070 s.select_ranges([destination..destination]);
17071 });
17072 }
17073 }
17074
17075 fn go_to_line<T: 'static>(
17076 &mut self,
17077 position: Anchor,
17078 highlight_color: Option<Hsla>,
17079 window: &mut Window,
17080 cx: &mut Context<Self>,
17081 ) {
17082 let snapshot = self.snapshot(window, cx).display_snapshot;
17083 let position = position.to_point(&snapshot.buffer_snapshot());
17084 let start = snapshot
17085 .buffer_snapshot()
17086 .clip_point(Point::new(position.row, 0), Bias::Left);
17087 let end = start + Point::new(1, 0);
17088 let start = snapshot.buffer_snapshot().anchor_before(start);
17089 let end = snapshot.buffer_snapshot().anchor_before(end);
17090
17091 self.highlight_rows::<T>(
17092 start..end,
17093 highlight_color
17094 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17095 Default::default(),
17096 cx,
17097 );
17098
17099 if self.buffer.read(cx).is_singleton() {
17100 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17101 }
17102 }
17103
17104 pub fn go_to_definition(
17105 &mut self,
17106 _: &GoToDefinition,
17107 window: &mut Window,
17108 cx: &mut Context<Self>,
17109 ) -> Task<Result<Navigated>> {
17110 let definition =
17111 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17112 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17113 cx.spawn_in(window, async move |editor, cx| {
17114 if definition.await? == Navigated::Yes {
17115 return Ok(Navigated::Yes);
17116 }
17117 match fallback_strategy {
17118 GoToDefinitionFallback::None => Ok(Navigated::No),
17119 GoToDefinitionFallback::FindAllReferences => {
17120 match editor.update_in(cx, |editor, window, cx| {
17121 editor.find_all_references(&FindAllReferences::default(), window, cx)
17122 })? {
17123 Some(references) => references.await,
17124 None => Ok(Navigated::No),
17125 }
17126 }
17127 }
17128 })
17129 }
17130
17131 pub fn go_to_declaration(
17132 &mut self,
17133 _: &GoToDeclaration,
17134 window: &mut Window,
17135 cx: &mut Context<Self>,
17136 ) -> Task<Result<Navigated>> {
17137 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17138 }
17139
17140 pub fn go_to_declaration_split(
17141 &mut self,
17142 _: &GoToDeclaration,
17143 window: &mut Window,
17144 cx: &mut Context<Self>,
17145 ) -> Task<Result<Navigated>> {
17146 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17147 }
17148
17149 pub fn go_to_implementation(
17150 &mut self,
17151 _: &GoToImplementation,
17152 window: &mut Window,
17153 cx: &mut Context<Self>,
17154 ) -> Task<Result<Navigated>> {
17155 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17156 }
17157
17158 pub fn go_to_implementation_split(
17159 &mut self,
17160 _: &GoToImplementationSplit,
17161 window: &mut Window,
17162 cx: &mut Context<Self>,
17163 ) -> Task<Result<Navigated>> {
17164 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17165 }
17166
17167 pub fn go_to_type_definition(
17168 &mut self,
17169 _: &GoToTypeDefinition,
17170 window: &mut Window,
17171 cx: &mut Context<Self>,
17172 ) -> Task<Result<Navigated>> {
17173 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17174 }
17175
17176 pub fn go_to_definition_split(
17177 &mut self,
17178 _: &GoToDefinitionSplit,
17179 window: &mut Window,
17180 cx: &mut Context<Self>,
17181 ) -> Task<Result<Navigated>> {
17182 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17183 }
17184
17185 pub fn go_to_type_definition_split(
17186 &mut self,
17187 _: &GoToTypeDefinitionSplit,
17188 window: &mut Window,
17189 cx: &mut Context<Self>,
17190 ) -> Task<Result<Navigated>> {
17191 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17192 }
17193
17194 fn go_to_definition_of_kind(
17195 &mut self,
17196 kind: GotoDefinitionKind,
17197 split: bool,
17198 window: &mut Window,
17199 cx: &mut Context<Self>,
17200 ) -> Task<Result<Navigated>> {
17201 let Some(provider) = self.semantics_provider.clone() else {
17202 return Task::ready(Ok(Navigated::No));
17203 };
17204 let head = self
17205 .selections
17206 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17207 .head();
17208 let buffer = self.buffer.read(cx);
17209 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17210 return Task::ready(Ok(Navigated::No));
17211 };
17212 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17213 return Task::ready(Ok(Navigated::No));
17214 };
17215
17216 cx.spawn_in(window, async move |editor, cx| {
17217 let Some(definitions) = definitions.await? else {
17218 return Ok(Navigated::No);
17219 };
17220 let navigated = editor
17221 .update_in(cx, |editor, window, cx| {
17222 editor.navigate_to_hover_links(
17223 Some(kind),
17224 definitions
17225 .into_iter()
17226 .filter(|location| {
17227 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17228 })
17229 .map(HoverLink::Text)
17230 .collect::<Vec<_>>(),
17231 split,
17232 window,
17233 cx,
17234 )
17235 })?
17236 .await?;
17237 anyhow::Ok(navigated)
17238 })
17239 }
17240
17241 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17242 let selection = self.selections.newest_anchor();
17243 let head = selection.head();
17244 let tail = selection.tail();
17245
17246 let Some((buffer, start_position)) =
17247 self.buffer.read(cx).text_anchor_for_position(head, cx)
17248 else {
17249 return;
17250 };
17251
17252 let end_position = if head != tail {
17253 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17254 return;
17255 };
17256 Some(pos)
17257 } else {
17258 None
17259 };
17260
17261 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17262 let url = if let Some(end_pos) = end_position {
17263 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17264 } else {
17265 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17266 };
17267
17268 if let Some(url) = url {
17269 cx.update(|window, cx| {
17270 if parse_zed_link(&url, cx).is_some() {
17271 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17272 } else {
17273 cx.open_url(&url);
17274 }
17275 })?;
17276 }
17277
17278 anyhow::Ok(())
17279 });
17280
17281 url_finder.detach();
17282 }
17283
17284 pub fn open_selected_filename(
17285 &mut self,
17286 _: &OpenSelectedFilename,
17287 window: &mut Window,
17288 cx: &mut Context<Self>,
17289 ) {
17290 let Some(workspace) = self.workspace() else {
17291 return;
17292 };
17293
17294 let position = self.selections.newest_anchor().head();
17295
17296 let Some((buffer, buffer_position)) =
17297 self.buffer.read(cx).text_anchor_for_position(position, cx)
17298 else {
17299 return;
17300 };
17301
17302 let project = self.project.clone();
17303
17304 cx.spawn_in(window, async move |_, cx| {
17305 let result = find_file(&buffer, project, buffer_position, cx).await;
17306
17307 if let Some((_, path)) = result {
17308 workspace
17309 .update_in(cx, |workspace, window, cx| {
17310 workspace.open_resolved_path(path, window, cx)
17311 })?
17312 .await?;
17313 }
17314 anyhow::Ok(())
17315 })
17316 .detach();
17317 }
17318
17319 pub(crate) fn navigate_to_hover_links(
17320 &mut self,
17321 kind: Option<GotoDefinitionKind>,
17322 definitions: Vec<HoverLink>,
17323 split: bool,
17324 window: &mut Window,
17325 cx: &mut Context<Editor>,
17326 ) -> Task<Result<Navigated>> {
17327 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17328 let mut first_url_or_file = None;
17329 let definitions: Vec<_> = definitions
17330 .into_iter()
17331 .filter_map(|def| match def {
17332 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17333 HoverLink::InlayHint(lsp_location, server_id) => {
17334 let computation =
17335 self.compute_target_location(lsp_location, server_id, window, cx);
17336 Some(cx.background_spawn(computation))
17337 }
17338 HoverLink::Url(url) => {
17339 first_url_or_file = Some(Either::Left(url));
17340 None
17341 }
17342 HoverLink::File(path) => {
17343 first_url_or_file = Some(Either::Right(path));
17344 None
17345 }
17346 })
17347 .collect();
17348
17349 let workspace = self.workspace();
17350
17351 cx.spawn_in(window, async move |editor, cx| {
17352 let locations: Vec<Location> = future::join_all(definitions)
17353 .await
17354 .into_iter()
17355 .filter_map(|location| location.transpose())
17356 .collect::<Result<_>>()
17357 .context("location tasks")?;
17358 let mut locations = cx.update(|_, cx| {
17359 locations
17360 .into_iter()
17361 .map(|location| {
17362 let buffer = location.buffer.read(cx);
17363 (location.buffer, location.range.to_point(buffer))
17364 })
17365 .into_group_map()
17366 })?;
17367 let mut num_locations = 0;
17368 for ranges in locations.values_mut() {
17369 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17370 ranges.dedup();
17371 num_locations += ranges.len();
17372 }
17373
17374 if num_locations > 1 {
17375 let tab_kind = match kind {
17376 Some(GotoDefinitionKind::Implementation) => "Implementations",
17377 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17378 Some(GotoDefinitionKind::Declaration) => "Declarations",
17379 Some(GotoDefinitionKind::Type) => "Types",
17380 };
17381 let title = editor
17382 .update_in(cx, |_, _, cx| {
17383 let target = locations
17384 .iter()
17385 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17386 .map(|(buffer, location)| {
17387 buffer
17388 .read(cx)
17389 .text_for_range(location.clone())
17390 .collect::<String>()
17391 })
17392 .filter(|text| !text.contains('\n'))
17393 .unique()
17394 .take(3)
17395 .join(", ");
17396 if target.is_empty() {
17397 tab_kind.to_owned()
17398 } else {
17399 format!("{tab_kind} for {target}")
17400 }
17401 })
17402 .context("buffer title")?;
17403
17404 let Some(workspace) = workspace else {
17405 return Ok(Navigated::No);
17406 };
17407
17408 let opened = workspace
17409 .update_in(cx, |workspace, window, cx| {
17410 let allow_preview = PreviewTabsSettings::get_global(cx)
17411 .enable_preview_multibuffer_from_code_navigation;
17412 Self::open_locations_in_multibuffer(
17413 workspace,
17414 locations,
17415 title,
17416 split,
17417 allow_preview,
17418 MultibufferSelectionMode::First,
17419 window,
17420 cx,
17421 )
17422 })
17423 .is_ok();
17424
17425 anyhow::Ok(Navigated::from_bool(opened))
17426 } else if num_locations == 0 {
17427 // If there is one url or file, open it directly
17428 match first_url_or_file {
17429 Some(Either::Left(url)) => {
17430 cx.update(|_, cx| cx.open_url(&url))?;
17431 Ok(Navigated::Yes)
17432 }
17433 Some(Either::Right(path)) => {
17434 // TODO(andrew): respect preview tab settings
17435 // `enable_keep_preview_on_code_navigation` and
17436 // `enable_preview_file_from_code_navigation`
17437 let Some(workspace) = workspace else {
17438 return Ok(Navigated::No);
17439 };
17440 workspace
17441 .update_in(cx, |workspace, window, cx| {
17442 workspace.open_resolved_path(path, window, cx)
17443 })?
17444 .await?;
17445 Ok(Navigated::Yes)
17446 }
17447 None => Ok(Navigated::No),
17448 }
17449 } else {
17450 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17451 let target_range = target_ranges.first().unwrap().clone();
17452
17453 editor.update_in(cx, |editor, window, cx| {
17454 let range = target_range.to_point(target_buffer.read(cx));
17455 let range = editor.range_for_match(&range);
17456 let range = collapse_multiline_range(range);
17457
17458 if !split
17459 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17460 {
17461 editor.go_to_singleton_buffer_range(range, window, cx);
17462 } else {
17463 let Some(workspace) = workspace else {
17464 return Navigated::No;
17465 };
17466 let pane = workspace.read(cx).active_pane().clone();
17467 window.defer(cx, move |window, cx| {
17468 let target_editor: Entity<Self> =
17469 workspace.update(cx, |workspace, cx| {
17470 let pane = if split {
17471 workspace.adjacent_pane(window, cx)
17472 } else {
17473 workspace.active_pane().clone()
17474 };
17475
17476 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17477 let keep_old_preview = preview_tabs_settings
17478 .enable_keep_preview_on_code_navigation;
17479 let allow_new_preview = preview_tabs_settings
17480 .enable_preview_file_from_code_navigation;
17481
17482 workspace.open_project_item(
17483 pane,
17484 target_buffer.clone(),
17485 true,
17486 true,
17487 keep_old_preview,
17488 allow_new_preview,
17489 window,
17490 cx,
17491 )
17492 });
17493 target_editor.update(cx, |target_editor, cx| {
17494 // When selecting a definition in a different buffer, disable the nav history
17495 // to avoid creating a history entry at the previous cursor location.
17496 pane.update(cx, |pane, _| pane.disable_history());
17497 target_editor.go_to_singleton_buffer_range(range, window, cx);
17498 pane.update(cx, |pane, _| pane.enable_history());
17499 });
17500 });
17501 }
17502 Navigated::Yes
17503 })
17504 }
17505 })
17506 }
17507
17508 fn compute_target_location(
17509 &self,
17510 lsp_location: lsp::Location,
17511 server_id: LanguageServerId,
17512 window: &mut Window,
17513 cx: &mut Context<Self>,
17514 ) -> Task<anyhow::Result<Option<Location>>> {
17515 let Some(project) = self.project.clone() else {
17516 return Task::ready(Ok(None));
17517 };
17518
17519 cx.spawn_in(window, async move |editor, cx| {
17520 let location_task = editor.update(cx, |_, cx| {
17521 project.update(cx, |project, cx| {
17522 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17523 })
17524 })?;
17525 let location = Some({
17526 let target_buffer_handle = location_task.await.context("open local buffer")?;
17527 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17528 let target_start = target_buffer
17529 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17530 let target_end = target_buffer
17531 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17532 target_buffer.anchor_after(target_start)
17533 ..target_buffer.anchor_before(target_end)
17534 })?;
17535 Location {
17536 buffer: target_buffer_handle,
17537 range,
17538 }
17539 });
17540 Ok(location)
17541 })
17542 }
17543
17544 fn go_to_next_reference(
17545 &mut self,
17546 _: &GoToNextReference,
17547 window: &mut Window,
17548 cx: &mut Context<Self>,
17549 ) {
17550 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17551 if let Some(task) = task {
17552 task.detach();
17553 };
17554 }
17555
17556 fn go_to_prev_reference(
17557 &mut self,
17558 _: &GoToPreviousReference,
17559 window: &mut Window,
17560 cx: &mut Context<Self>,
17561 ) {
17562 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17563 if let Some(task) = task {
17564 task.detach();
17565 };
17566 }
17567
17568 pub fn go_to_reference_before_or_after_position(
17569 &mut self,
17570 direction: Direction,
17571 count: usize,
17572 window: &mut Window,
17573 cx: &mut Context<Self>,
17574 ) -> Option<Task<Result<()>>> {
17575 let selection = self.selections.newest_anchor();
17576 let head = selection.head();
17577
17578 let multi_buffer = self.buffer.read(cx);
17579
17580 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17581 let workspace = self.workspace()?;
17582 let project = workspace.read(cx).project().clone();
17583 let references =
17584 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17585 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17586 let Some(locations) = references.await? else {
17587 return Ok(());
17588 };
17589
17590 if locations.is_empty() {
17591 // totally normal - the cursor may be on something which is not
17592 // a symbol (e.g. a keyword)
17593 log::info!("no references found under cursor");
17594 return Ok(());
17595 }
17596
17597 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17598
17599 let (locations, current_location_index) =
17600 multi_buffer.update(cx, |multi_buffer, cx| {
17601 let mut locations = locations
17602 .into_iter()
17603 .filter_map(|loc| {
17604 let start = multi_buffer.buffer_anchor_to_anchor(
17605 &loc.buffer,
17606 loc.range.start,
17607 cx,
17608 )?;
17609 let end = multi_buffer.buffer_anchor_to_anchor(
17610 &loc.buffer,
17611 loc.range.end,
17612 cx,
17613 )?;
17614 Some(start..end)
17615 })
17616 .collect::<Vec<_>>();
17617
17618 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17619 // There is an O(n) implementation, but given this list will be
17620 // small (usually <100 items), the extra O(log(n)) factor isn't
17621 // worth the (surprisingly large amount of) extra complexity.
17622 locations
17623 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17624
17625 let head_offset = head.to_offset(&multi_buffer_snapshot);
17626
17627 let current_location_index = locations.iter().position(|loc| {
17628 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17629 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17630 });
17631
17632 (locations, current_location_index)
17633 })?;
17634
17635 let Some(current_location_index) = current_location_index else {
17636 // This indicates something has gone wrong, because we already
17637 // handle the "no references" case above
17638 log::error!(
17639 "failed to find current reference under cursor. Total references: {}",
17640 locations.len()
17641 );
17642 return Ok(());
17643 };
17644
17645 let destination_location_index = match direction {
17646 Direction::Next => (current_location_index + count) % locations.len(),
17647 Direction::Prev => {
17648 (current_location_index + locations.len() - count % locations.len())
17649 % locations.len()
17650 }
17651 };
17652
17653 // TODO(cameron): is this needed?
17654 // the thinking is to avoid "jumping to the current location" (avoid
17655 // polluting "jumplist" in vim terms)
17656 if current_location_index == destination_location_index {
17657 return Ok(());
17658 }
17659
17660 let Range { start, end } = locations[destination_location_index];
17661
17662 editor.update_in(cx, |editor, window, cx| {
17663 let effects = SelectionEffects::default();
17664
17665 editor.unfold_ranges(&[start..end], false, false, cx);
17666 editor.change_selections(effects, window, cx, |s| {
17667 s.select_ranges([start..start]);
17668 });
17669 })?;
17670
17671 Ok(())
17672 }))
17673 }
17674
17675 pub fn find_all_references(
17676 &mut self,
17677 action: &FindAllReferences,
17678 window: &mut Window,
17679 cx: &mut Context<Self>,
17680 ) -> Option<Task<Result<Navigated>>> {
17681 let always_open_multibuffer = action.always_open_multibuffer;
17682 let selection = self.selections.newest_anchor();
17683 let multi_buffer = self.buffer.read(cx);
17684 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17685 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17686 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17687 let head = selection_offset.head();
17688
17689 let head_anchor = multi_buffer_snapshot.anchor_at(
17690 head,
17691 if head < selection_offset.tail() {
17692 Bias::Right
17693 } else {
17694 Bias::Left
17695 },
17696 );
17697
17698 match self
17699 .find_all_references_task_sources
17700 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17701 {
17702 Ok(_) => {
17703 log::info!(
17704 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17705 );
17706 return None;
17707 }
17708 Err(i) => {
17709 self.find_all_references_task_sources.insert(i, head_anchor);
17710 }
17711 }
17712
17713 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17714 let workspace = self.workspace()?;
17715 let project = workspace.read(cx).project().clone();
17716 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17717 Some(cx.spawn_in(window, async move |editor, cx| {
17718 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17719 if let Ok(i) = editor
17720 .find_all_references_task_sources
17721 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17722 {
17723 editor.find_all_references_task_sources.remove(i);
17724 }
17725 });
17726
17727 let Some(locations) = references.await? else {
17728 return anyhow::Ok(Navigated::No);
17729 };
17730 let mut locations = cx.update(|_, cx| {
17731 locations
17732 .into_iter()
17733 .map(|location| {
17734 let buffer = location.buffer.read(cx);
17735 (location.buffer, location.range.to_point(buffer))
17736 })
17737 // if special-casing the single-match case, remove ranges
17738 // that intersect current selection
17739 .filter(|(location_buffer, location)| {
17740 if always_open_multibuffer || &buffer != location_buffer {
17741 return true;
17742 }
17743
17744 !location.contains_inclusive(&selection_point.range())
17745 })
17746 .into_group_map()
17747 })?;
17748 if locations.is_empty() {
17749 return anyhow::Ok(Navigated::No);
17750 }
17751 for ranges in locations.values_mut() {
17752 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17753 ranges.dedup();
17754 }
17755 let mut num_locations = 0;
17756 for ranges in locations.values_mut() {
17757 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17758 ranges.dedup();
17759 num_locations += ranges.len();
17760 }
17761
17762 if num_locations == 1 && !always_open_multibuffer {
17763 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17764 let target_range = target_ranges.first().unwrap().clone();
17765
17766 return editor.update_in(cx, |editor, window, cx| {
17767 let range = target_range.to_point(target_buffer.read(cx));
17768 let range = editor.range_for_match(&range);
17769 let range = range.start..range.start;
17770
17771 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17772 editor.go_to_singleton_buffer_range(range, window, cx);
17773 } else {
17774 let pane = workspace.read(cx).active_pane().clone();
17775 window.defer(cx, move |window, cx| {
17776 let target_editor: Entity<Self> =
17777 workspace.update(cx, |workspace, cx| {
17778 let pane = workspace.active_pane().clone();
17779
17780 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17781 let keep_old_preview = preview_tabs_settings
17782 .enable_keep_preview_on_code_navigation;
17783 let allow_new_preview = preview_tabs_settings
17784 .enable_preview_file_from_code_navigation;
17785
17786 workspace.open_project_item(
17787 pane,
17788 target_buffer.clone(),
17789 true,
17790 true,
17791 keep_old_preview,
17792 allow_new_preview,
17793 window,
17794 cx,
17795 )
17796 });
17797 target_editor.update(cx, |target_editor, cx| {
17798 // When selecting a definition in a different buffer, disable the nav history
17799 // to avoid creating a history entry at the previous cursor location.
17800 pane.update(cx, |pane, _| pane.disable_history());
17801 target_editor.go_to_singleton_buffer_range(range, window, cx);
17802 pane.update(cx, |pane, _| pane.enable_history());
17803 });
17804 });
17805 }
17806 Navigated::No
17807 });
17808 }
17809
17810 workspace.update_in(cx, |workspace, window, cx| {
17811 let target = locations
17812 .iter()
17813 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17814 .map(|(buffer, location)| {
17815 buffer
17816 .read(cx)
17817 .text_for_range(location.clone())
17818 .collect::<String>()
17819 })
17820 .filter(|text| !text.contains('\n'))
17821 .unique()
17822 .take(3)
17823 .join(", ");
17824 let title = if target.is_empty() {
17825 "References".to_owned()
17826 } else {
17827 format!("References to {target}")
17828 };
17829 let allow_preview = PreviewTabsSettings::get_global(cx)
17830 .enable_preview_multibuffer_from_code_navigation;
17831 Self::open_locations_in_multibuffer(
17832 workspace,
17833 locations,
17834 title,
17835 false,
17836 allow_preview,
17837 MultibufferSelectionMode::First,
17838 window,
17839 cx,
17840 );
17841 Navigated::Yes
17842 })
17843 }))
17844 }
17845
17846 /// Opens a multibuffer with the given project locations in it.
17847 pub fn open_locations_in_multibuffer(
17848 workspace: &mut Workspace,
17849 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17850 title: String,
17851 split: bool,
17852 allow_preview: bool,
17853 multibuffer_selection_mode: MultibufferSelectionMode,
17854 window: &mut Window,
17855 cx: &mut Context<Workspace>,
17856 ) {
17857 if locations.is_empty() {
17858 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17859 return;
17860 }
17861
17862 let capability = workspace.project().read(cx).capability();
17863 let mut ranges = <Vec<Range<Anchor>>>::new();
17864
17865 // a key to find existing multibuffer editors with the same set of locations
17866 // to prevent us from opening more and more multibuffer tabs for searches and the like
17867 let mut key = (title.clone(), vec![]);
17868 let excerpt_buffer = cx.new(|cx| {
17869 let key = &mut key.1;
17870 let mut multibuffer = MultiBuffer::new(capability);
17871 for (buffer, mut ranges_for_buffer) in locations {
17872 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17873 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17874 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17875 PathKey::for_buffer(&buffer, cx),
17876 buffer.clone(),
17877 ranges_for_buffer,
17878 multibuffer_context_lines(cx),
17879 cx,
17880 );
17881 ranges.extend(new_ranges)
17882 }
17883
17884 multibuffer.with_title(title)
17885 });
17886 let existing = workspace.active_pane().update(cx, |pane, cx| {
17887 pane.items()
17888 .filter_map(|item| item.downcast::<Editor>())
17889 .find(|editor| {
17890 editor
17891 .read(cx)
17892 .lookup_key
17893 .as_ref()
17894 .and_then(|it| {
17895 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17896 })
17897 .is_some_and(|it| *it == key)
17898 })
17899 });
17900 let was_existing = existing.is_some();
17901 let editor = existing.unwrap_or_else(|| {
17902 cx.new(|cx| {
17903 let mut editor = Editor::for_multibuffer(
17904 excerpt_buffer,
17905 Some(workspace.project().clone()),
17906 window,
17907 cx,
17908 );
17909 editor.lookup_key = Some(Box::new(key));
17910 editor
17911 })
17912 });
17913 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17914 MultibufferSelectionMode::First => {
17915 if let Some(first_range) = ranges.first() {
17916 editor.change_selections(
17917 SelectionEffects::no_scroll(),
17918 window,
17919 cx,
17920 |selections| {
17921 selections.clear_disjoint();
17922 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17923 },
17924 );
17925 }
17926 editor.highlight_background::<Self>(
17927 &ranges,
17928 |_, theme| theme.colors().editor_highlighted_line_background,
17929 cx,
17930 );
17931 }
17932 MultibufferSelectionMode::All => {
17933 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17934 selections.clear_disjoint();
17935 selections.select_anchor_ranges(ranges);
17936 });
17937 }
17938 });
17939
17940 let item = Box::new(editor);
17941
17942 let pane = if split {
17943 workspace.adjacent_pane(window, cx)
17944 } else {
17945 workspace.active_pane().clone()
17946 };
17947 let activate_pane = split;
17948
17949 let mut destination_index = None;
17950 pane.update(cx, |pane, cx| {
17951 if allow_preview && !was_existing {
17952 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17953 }
17954 if was_existing && !allow_preview {
17955 pane.unpreview_item_if_preview(item.item_id());
17956 }
17957 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17958 });
17959 }
17960
17961 pub fn rename(
17962 &mut self,
17963 _: &Rename,
17964 window: &mut Window,
17965 cx: &mut Context<Self>,
17966 ) -> Option<Task<Result<()>>> {
17967 use language::ToOffset as _;
17968
17969 let provider = self.semantics_provider.clone()?;
17970 let selection = self.selections.newest_anchor().clone();
17971 let (cursor_buffer, cursor_buffer_position) = self
17972 .buffer
17973 .read(cx)
17974 .text_anchor_for_position(selection.head(), cx)?;
17975 let (tail_buffer, cursor_buffer_position_end) = self
17976 .buffer
17977 .read(cx)
17978 .text_anchor_for_position(selection.tail(), cx)?;
17979 if tail_buffer != cursor_buffer {
17980 return None;
17981 }
17982
17983 let snapshot = cursor_buffer.read(cx).snapshot();
17984 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17985 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17986 let prepare_rename = provider
17987 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17988 .unwrap_or_else(|| Task::ready(Ok(None)));
17989 drop(snapshot);
17990
17991 Some(cx.spawn_in(window, async move |this, cx| {
17992 let rename_range = if let Some(range) = prepare_rename.await? {
17993 Some(range)
17994 } else {
17995 this.update(cx, |this, cx| {
17996 let buffer = this.buffer.read(cx).snapshot(cx);
17997 let mut buffer_highlights = this
17998 .document_highlights_for_position(selection.head(), &buffer)
17999 .filter(|highlight| {
18000 highlight.start.excerpt_id == selection.head().excerpt_id
18001 && highlight.end.excerpt_id == selection.head().excerpt_id
18002 });
18003 buffer_highlights
18004 .next()
18005 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
18006 })?
18007 };
18008 if let Some(rename_range) = rename_range {
18009 this.update_in(cx, |this, window, cx| {
18010 let snapshot = cursor_buffer.read(cx).snapshot();
18011 let rename_buffer_range = rename_range.to_offset(&snapshot);
18012 let cursor_offset_in_rename_range =
18013 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
18014 let cursor_offset_in_rename_range_end =
18015 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
18016
18017 this.take_rename(false, window, cx);
18018 let buffer = this.buffer.read(cx).read(cx);
18019 let cursor_offset = selection.head().to_offset(&buffer);
18020 let rename_start =
18021 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
18022 let rename_end = rename_start + rename_buffer_range.len();
18023 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
18024 let mut old_highlight_id = None;
18025 let old_name: Arc<str> = buffer
18026 .chunks(rename_start..rename_end, true)
18027 .map(|chunk| {
18028 if old_highlight_id.is_none() {
18029 old_highlight_id = chunk.syntax_highlight_id;
18030 }
18031 chunk.text
18032 })
18033 .collect::<String>()
18034 .into();
18035
18036 drop(buffer);
18037
18038 // Position the selection in the rename editor so that it matches the current selection.
18039 this.show_local_selections = false;
18040 let rename_editor = cx.new(|cx| {
18041 let mut editor = Editor::single_line(window, cx);
18042 editor.buffer.update(cx, |buffer, cx| {
18043 buffer.edit(
18044 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18045 None,
18046 cx,
18047 )
18048 });
18049 let cursor_offset_in_rename_range =
18050 MultiBufferOffset(cursor_offset_in_rename_range);
18051 let cursor_offset_in_rename_range_end =
18052 MultiBufferOffset(cursor_offset_in_rename_range_end);
18053 let rename_selection_range = match cursor_offset_in_rename_range
18054 .cmp(&cursor_offset_in_rename_range_end)
18055 {
18056 Ordering::Equal => {
18057 editor.select_all(&SelectAll, window, cx);
18058 return editor;
18059 }
18060 Ordering::Less => {
18061 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18062 }
18063 Ordering::Greater => {
18064 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18065 }
18066 };
18067 if rename_selection_range.end.0 > old_name.len() {
18068 editor.select_all(&SelectAll, window, cx);
18069 } else {
18070 editor.change_selections(Default::default(), window, cx, |s| {
18071 s.select_ranges([rename_selection_range]);
18072 });
18073 }
18074 editor
18075 });
18076 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18077 if e == &EditorEvent::Focused {
18078 cx.emit(EditorEvent::FocusedIn)
18079 }
18080 })
18081 .detach();
18082
18083 let write_highlights =
18084 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18085 let read_highlights =
18086 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18087 let ranges = write_highlights
18088 .iter()
18089 .flat_map(|(_, ranges)| ranges.iter())
18090 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18091 .cloned()
18092 .collect();
18093
18094 this.highlight_text::<Rename>(
18095 ranges,
18096 HighlightStyle {
18097 fade_out: Some(0.6),
18098 ..Default::default()
18099 },
18100 cx,
18101 );
18102 let rename_focus_handle = rename_editor.focus_handle(cx);
18103 window.focus(&rename_focus_handle);
18104 let block_id = this.insert_blocks(
18105 [BlockProperties {
18106 style: BlockStyle::Flex,
18107 placement: BlockPlacement::Below(range.start),
18108 height: Some(1),
18109 render: Arc::new({
18110 let rename_editor = rename_editor.clone();
18111 move |cx: &mut BlockContext| {
18112 let mut text_style = cx.editor_style.text.clone();
18113 if let Some(highlight_style) = old_highlight_id
18114 .and_then(|h| h.style(&cx.editor_style.syntax))
18115 {
18116 text_style = text_style.highlight(highlight_style);
18117 }
18118 div()
18119 .block_mouse_except_scroll()
18120 .pl(cx.anchor_x)
18121 .child(EditorElement::new(
18122 &rename_editor,
18123 EditorStyle {
18124 background: cx.theme().system().transparent,
18125 local_player: cx.editor_style.local_player,
18126 text: text_style,
18127 scrollbar_width: cx.editor_style.scrollbar_width,
18128 syntax: cx.editor_style.syntax.clone(),
18129 status: cx.editor_style.status.clone(),
18130 inlay_hints_style: HighlightStyle {
18131 font_weight: Some(FontWeight::BOLD),
18132 ..make_inlay_hints_style(cx.app)
18133 },
18134 edit_prediction_styles: make_suggestion_styles(
18135 cx.app,
18136 ),
18137 ..EditorStyle::default()
18138 },
18139 ))
18140 .into_any_element()
18141 }
18142 }),
18143 priority: 0,
18144 }],
18145 Some(Autoscroll::fit()),
18146 cx,
18147 )[0];
18148 this.pending_rename = Some(RenameState {
18149 range,
18150 old_name,
18151 editor: rename_editor,
18152 block_id,
18153 });
18154 })?;
18155 }
18156
18157 Ok(())
18158 }))
18159 }
18160
18161 pub fn confirm_rename(
18162 &mut self,
18163 _: &ConfirmRename,
18164 window: &mut Window,
18165 cx: &mut Context<Self>,
18166 ) -> Option<Task<Result<()>>> {
18167 let rename = self.take_rename(false, window, cx)?;
18168 let workspace = self.workspace()?.downgrade();
18169 let (buffer, start) = self
18170 .buffer
18171 .read(cx)
18172 .text_anchor_for_position(rename.range.start, cx)?;
18173 let (end_buffer, _) = self
18174 .buffer
18175 .read(cx)
18176 .text_anchor_for_position(rename.range.end, cx)?;
18177 if buffer != end_buffer {
18178 return None;
18179 }
18180
18181 let old_name = rename.old_name;
18182 let new_name = rename.editor.read(cx).text(cx);
18183
18184 let rename = self.semantics_provider.as_ref()?.perform_rename(
18185 &buffer,
18186 start,
18187 new_name.clone(),
18188 cx,
18189 )?;
18190
18191 Some(cx.spawn_in(window, async move |editor, cx| {
18192 let project_transaction = rename.await?;
18193 Self::open_project_transaction(
18194 &editor,
18195 workspace,
18196 project_transaction,
18197 format!("Rename: {} → {}", old_name, new_name),
18198 cx,
18199 )
18200 .await?;
18201
18202 editor.update(cx, |editor, cx| {
18203 editor.refresh_document_highlights(cx);
18204 })?;
18205 Ok(())
18206 }))
18207 }
18208
18209 fn take_rename(
18210 &mut self,
18211 moving_cursor: bool,
18212 window: &mut Window,
18213 cx: &mut Context<Self>,
18214 ) -> Option<RenameState> {
18215 let rename = self.pending_rename.take()?;
18216 if rename.editor.focus_handle(cx).is_focused(window) {
18217 window.focus(&self.focus_handle);
18218 }
18219
18220 self.remove_blocks(
18221 [rename.block_id].into_iter().collect(),
18222 Some(Autoscroll::fit()),
18223 cx,
18224 );
18225 self.clear_highlights::<Rename>(cx);
18226 self.show_local_selections = true;
18227
18228 if moving_cursor {
18229 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18230 editor
18231 .selections
18232 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18233 .head()
18234 });
18235
18236 // Update the selection to match the position of the selection inside
18237 // the rename editor.
18238 let snapshot = self.buffer.read(cx).read(cx);
18239 let rename_range = rename.range.to_offset(&snapshot);
18240 let cursor_in_editor = snapshot
18241 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18242 .min(rename_range.end);
18243 drop(snapshot);
18244
18245 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18246 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18247 });
18248 } else {
18249 self.refresh_document_highlights(cx);
18250 }
18251
18252 Some(rename)
18253 }
18254
18255 pub fn pending_rename(&self) -> Option<&RenameState> {
18256 self.pending_rename.as_ref()
18257 }
18258
18259 fn format(
18260 &mut self,
18261 _: &Format,
18262 window: &mut Window,
18263 cx: &mut Context<Self>,
18264 ) -> Option<Task<Result<()>>> {
18265 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18266
18267 let project = match &self.project {
18268 Some(project) => project.clone(),
18269 None => return None,
18270 };
18271
18272 Some(self.perform_format(
18273 project,
18274 FormatTrigger::Manual,
18275 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18276 window,
18277 cx,
18278 ))
18279 }
18280
18281 fn format_selections(
18282 &mut self,
18283 _: &FormatSelections,
18284 window: &mut Window,
18285 cx: &mut Context<Self>,
18286 ) -> Option<Task<Result<()>>> {
18287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18288
18289 let project = match &self.project {
18290 Some(project) => project.clone(),
18291 None => return None,
18292 };
18293
18294 let ranges = self
18295 .selections
18296 .all_adjusted(&self.display_snapshot(cx))
18297 .into_iter()
18298 .map(|selection| selection.range())
18299 .collect_vec();
18300
18301 Some(self.perform_format(
18302 project,
18303 FormatTrigger::Manual,
18304 FormatTarget::Ranges(ranges),
18305 window,
18306 cx,
18307 ))
18308 }
18309
18310 fn perform_format(
18311 &mut self,
18312 project: Entity<Project>,
18313 trigger: FormatTrigger,
18314 target: FormatTarget,
18315 window: &mut Window,
18316 cx: &mut Context<Self>,
18317 ) -> Task<Result<()>> {
18318 let buffer = self.buffer.clone();
18319 let (buffers, target) = match target {
18320 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18321 FormatTarget::Ranges(selection_ranges) => {
18322 let multi_buffer = buffer.read(cx);
18323 let snapshot = multi_buffer.read(cx);
18324 let mut buffers = HashSet::default();
18325 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18326 BTreeMap::new();
18327 for selection_range in selection_ranges {
18328 for (buffer, buffer_range, _) in
18329 snapshot.range_to_buffer_ranges(selection_range)
18330 {
18331 let buffer_id = buffer.remote_id();
18332 let start = buffer.anchor_before(buffer_range.start);
18333 let end = buffer.anchor_after(buffer_range.end);
18334 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18335 buffer_id_to_ranges
18336 .entry(buffer_id)
18337 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18338 .or_insert_with(|| vec![start..end]);
18339 }
18340 }
18341 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18342 }
18343 };
18344
18345 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18346 let selections_prev = transaction_id_prev
18347 .and_then(|transaction_id_prev| {
18348 // default to selections as they were after the last edit, if we have them,
18349 // instead of how they are now.
18350 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18351 // will take you back to where you made the last edit, instead of staying where you scrolled
18352 self.selection_history
18353 .transaction(transaction_id_prev)
18354 .map(|t| t.0.clone())
18355 })
18356 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18357
18358 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18359 let format = project.update(cx, |project, cx| {
18360 project.format(buffers, target, true, trigger, cx)
18361 });
18362
18363 cx.spawn_in(window, async move |editor, cx| {
18364 let transaction = futures::select_biased! {
18365 transaction = format.log_err().fuse() => transaction,
18366 () = timeout => {
18367 log::warn!("timed out waiting for formatting");
18368 None
18369 }
18370 };
18371
18372 buffer
18373 .update(cx, |buffer, cx| {
18374 if let Some(transaction) = transaction
18375 && !buffer.is_singleton()
18376 {
18377 buffer.push_transaction(&transaction.0, cx);
18378 }
18379 cx.notify();
18380 })
18381 .ok();
18382
18383 if let Some(transaction_id_now) =
18384 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18385 {
18386 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18387 if has_new_transaction {
18388 _ = editor.update(cx, |editor, _| {
18389 editor
18390 .selection_history
18391 .insert_transaction(transaction_id_now, selections_prev);
18392 });
18393 }
18394 }
18395
18396 Ok(())
18397 })
18398 }
18399
18400 fn organize_imports(
18401 &mut self,
18402 _: &OrganizeImports,
18403 window: &mut Window,
18404 cx: &mut Context<Self>,
18405 ) -> Option<Task<Result<()>>> {
18406 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18407 let project = match &self.project {
18408 Some(project) => project.clone(),
18409 None => return None,
18410 };
18411 Some(self.perform_code_action_kind(
18412 project,
18413 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18414 window,
18415 cx,
18416 ))
18417 }
18418
18419 fn perform_code_action_kind(
18420 &mut self,
18421 project: Entity<Project>,
18422 kind: CodeActionKind,
18423 window: &mut Window,
18424 cx: &mut Context<Self>,
18425 ) -> Task<Result<()>> {
18426 let buffer = self.buffer.clone();
18427 let buffers = buffer.read(cx).all_buffers();
18428 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18429 let apply_action = project.update(cx, |project, cx| {
18430 project.apply_code_action_kind(buffers, kind, true, cx)
18431 });
18432 cx.spawn_in(window, async move |_, cx| {
18433 let transaction = futures::select_biased! {
18434 () = timeout => {
18435 log::warn!("timed out waiting for executing code action");
18436 None
18437 }
18438 transaction = apply_action.log_err().fuse() => transaction,
18439 };
18440 buffer
18441 .update(cx, |buffer, cx| {
18442 // check if we need this
18443 if let Some(transaction) = transaction
18444 && !buffer.is_singleton()
18445 {
18446 buffer.push_transaction(&transaction.0, cx);
18447 }
18448 cx.notify();
18449 })
18450 .ok();
18451 Ok(())
18452 })
18453 }
18454
18455 pub fn restart_language_server(
18456 &mut self,
18457 _: &RestartLanguageServer,
18458 _: &mut Window,
18459 cx: &mut Context<Self>,
18460 ) {
18461 if let Some(project) = self.project.clone() {
18462 self.buffer.update(cx, |multi_buffer, cx| {
18463 project.update(cx, |project, cx| {
18464 project.restart_language_servers_for_buffers(
18465 multi_buffer.all_buffers().into_iter().collect(),
18466 HashSet::default(),
18467 cx,
18468 );
18469 });
18470 })
18471 }
18472 }
18473
18474 pub fn stop_language_server(
18475 &mut self,
18476 _: &StopLanguageServer,
18477 _: &mut Window,
18478 cx: &mut Context<Self>,
18479 ) {
18480 if let Some(project) = self.project.clone() {
18481 self.buffer.update(cx, |multi_buffer, cx| {
18482 project.update(cx, |project, cx| {
18483 project.stop_language_servers_for_buffers(
18484 multi_buffer.all_buffers().into_iter().collect(),
18485 HashSet::default(),
18486 cx,
18487 );
18488 });
18489 });
18490 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18491 }
18492 }
18493
18494 fn cancel_language_server_work(
18495 workspace: &mut Workspace,
18496 _: &actions::CancelLanguageServerWork,
18497 _: &mut Window,
18498 cx: &mut Context<Workspace>,
18499 ) {
18500 let project = workspace.project();
18501 let buffers = workspace
18502 .active_item(cx)
18503 .and_then(|item| item.act_as::<Editor>(cx))
18504 .map_or(HashSet::default(), |editor| {
18505 editor.read(cx).buffer.read(cx).all_buffers()
18506 });
18507 project.update(cx, |project, cx| {
18508 project.cancel_language_server_work_for_buffers(buffers, cx);
18509 });
18510 }
18511
18512 fn show_character_palette(
18513 &mut self,
18514 _: &ShowCharacterPalette,
18515 window: &mut Window,
18516 _: &mut Context<Self>,
18517 ) {
18518 window.show_character_palette();
18519 }
18520
18521 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18522 if !self.diagnostics_enabled() {
18523 return;
18524 }
18525
18526 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18527 let buffer = self.buffer.read(cx).snapshot(cx);
18528 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18529 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18530 let is_valid = buffer
18531 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18532 .any(|entry| {
18533 entry.diagnostic.is_primary
18534 && !entry.range.is_empty()
18535 && entry.range.start == primary_range_start
18536 && entry.diagnostic.message == active_diagnostics.active_message
18537 });
18538
18539 if !is_valid {
18540 self.dismiss_diagnostics(cx);
18541 }
18542 }
18543 }
18544
18545 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18546 match &self.active_diagnostics {
18547 ActiveDiagnostic::Group(group) => Some(group),
18548 _ => None,
18549 }
18550 }
18551
18552 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18553 if !self.diagnostics_enabled() {
18554 return;
18555 }
18556 self.dismiss_diagnostics(cx);
18557 self.active_diagnostics = ActiveDiagnostic::All;
18558 }
18559
18560 fn activate_diagnostics(
18561 &mut self,
18562 buffer_id: BufferId,
18563 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18564 window: &mut Window,
18565 cx: &mut Context<Self>,
18566 ) {
18567 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18568 return;
18569 }
18570 self.dismiss_diagnostics(cx);
18571 let snapshot = self.snapshot(window, cx);
18572 let buffer = self.buffer.read(cx).snapshot(cx);
18573 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18574 return;
18575 };
18576
18577 let diagnostic_group = buffer
18578 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18579 .collect::<Vec<_>>();
18580
18581 let language_registry = self
18582 .project()
18583 .map(|project| project.read(cx).languages().clone());
18584
18585 let blocks = renderer.render_group(
18586 diagnostic_group,
18587 buffer_id,
18588 snapshot,
18589 cx.weak_entity(),
18590 language_registry,
18591 cx,
18592 );
18593
18594 let blocks = self.display_map.update(cx, |display_map, cx| {
18595 display_map.insert_blocks(blocks, cx).into_iter().collect()
18596 });
18597 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18598 active_range: buffer.anchor_before(diagnostic.range.start)
18599 ..buffer.anchor_after(diagnostic.range.end),
18600 active_message: diagnostic.diagnostic.message.clone(),
18601 group_id: diagnostic.diagnostic.group_id,
18602 blocks,
18603 });
18604 cx.notify();
18605 }
18606
18607 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18608 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18609 return;
18610 };
18611
18612 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18613 if let ActiveDiagnostic::Group(group) = prev {
18614 self.display_map.update(cx, |display_map, cx| {
18615 display_map.remove_blocks(group.blocks, cx);
18616 });
18617 cx.notify();
18618 }
18619 }
18620
18621 /// Disable inline diagnostics rendering for this editor.
18622 pub fn disable_inline_diagnostics(&mut self) {
18623 self.inline_diagnostics_enabled = false;
18624 self.inline_diagnostics_update = Task::ready(());
18625 self.inline_diagnostics.clear();
18626 }
18627
18628 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18629 self.diagnostics_enabled = false;
18630 self.dismiss_diagnostics(cx);
18631 self.inline_diagnostics_update = Task::ready(());
18632 self.inline_diagnostics.clear();
18633 }
18634
18635 pub fn disable_word_completions(&mut self) {
18636 self.word_completions_enabled = false;
18637 }
18638
18639 pub fn diagnostics_enabled(&self) -> bool {
18640 self.diagnostics_enabled && self.mode.is_full()
18641 }
18642
18643 pub fn inline_diagnostics_enabled(&self) -> bool {
18644 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18645 }
18646
18647 pub fn show_inline_diagnostics(&self) -> bool {
18648 self.show_inline_diagnostics
18649 }
18650
18651 pub fn toggle_inline_diagnostics(
18652 &mut self,
18653 _: &ToggleInlineDiagnostics,
18654 window: &mut Window,
18655 cx: &mut Context<Editor>,
18656 ) {
18657 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18658 self.refresh_inline_diagnostics(false, window, cx);
18659 }
18660
18661 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18662 self.diagnostics_max_severity = severity;
18663 self.display_map.update(cx, |display_map, _| {
18664 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18665 });
18666 }
18667
18668 pub fn toggle_diagnostics(
18669 &mut self,
18670 _: &ToggleDiagnostics,
18671 window: &mut Window,
18672 cx: &mut Context<Editor>,
18673 ) {
18674 if !self.diagnostics_enabled() {
18675 return;
18676 }
18677
18678 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18679 EditorSettings::get_global(cx)
18680 .diagnostics_max_severity
18681 .filter(|severity| severity != &DiagnosticSeverity::Off)
18682 .unwrap_or(DiagnosticSeverity::Hint)
18683 } else {
18684 DiagnosticSeverity::Off
18685 };
18686 self.set_max_diagnostics_severity(new_severity, cx);
18687 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18688 self.active_diagnostics = ActiveDiagnostic::None;
18689 self.inline_diagnostics_update = Task::ready(());
18690 self.inline_diagnostics.clear();
18691 } else {
18692 self.refresh_inline_diagnostics(false, window, cx);
18693 }
18694
18695 cx.notify();
18696 }
18697
18698 pub fn toggle_minimap(
18699 &mut self,
18700 _: &ToggleMinimap,
18701 window: &mut Window,
18702 cx: &mut Context<Editor>,
18703 ) {
18704 if self.supports_minimap(cx) {
18705 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18706 }
18707 }
18708
18709 fn refresh_inline_diagnostics(
18710 &mut self,
18711 debounce: bool,
18712 window: &mut Window,
18713 cx: &mut Context<Self>,
18714 ) {
18715 let max_severity = ProjectSettings::get_global(cx)
18716 .diagnostics
18717 .inline
18718 .max_severity
18719 .unwrap_or(self.diagnostics_max_severity);
18720
18721 if !self.inline_diagnostics_enabled()
18722 || !self.diagnostics_enabled()
18723 || !self.show_inline_diagnostics
18724 || max_severity == DiagnosticSeverity::Off
18725 {
18726 self.inline_diagnostics_update = Task::ready(());
18727 self.inline_diagnostics.clear();
18728 return;
18729 }
18730
18731 let debounce_ms = ProjectSettings::get_global(cx)
18732 .diagnostics
18733 .inline
18734 .update_debounce_ms;
18735 let debounce = if debounce && debounce_ms > 0 {
18736 Some(Duration::from_millis(debounce_ms))
18737 } else {
18738 None
18739 };
18740 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18741 if let Some(debounce) = debounce {
18742 cx.background_executor().timer(debounce).await;
18743 }
18744 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18745 editor
18746 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18747 .ok()
18748 }) else {
18749 return;
18750 };
18751
18752 let new_inline_diagnostics = cx
18753 .background_spawn(async move {
18754 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18755 for diagnostic_entry in
18756 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18757 {
18758 let message = diagnostic_entry
18759 .diagnostic
18760 .message
18761 .split_once('\n')
18762 .map(|(line, _)| line)
18763 .map(SharedString::new)
18764 .unwrap_or_else(|| {
18765 SharedString::new(&*diagnostic_entry.diagnostic.message)
18766 });
18767 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18768 let (Ok(i) | Err(i)) = inline_diagnostics
18769 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18770 inline_diagnostics.insert(
18771 i,
18772 (
18773 start_anchor,
18774 InlineDiagnostic {
18775 message,
18776 group_id: diagnostic_entry.diagnostic.group_id,
18777 start: diagnostic_entry.range.start.to_point(&snapshot),
18778 is_primary: diagnostic_entry.diagnostic.is_primary,
18779 severity: diagnostic_entry.diagnostic.severity,
18780 },
18781 ),
18782 );
18783 }
18784 inline_diagnostics
18785 })
18786 .await;
18787
18788 editor
18789 .update(cx, |editor, cx| {
18790 editor.inline_diagnostics = new_inline_diagnostics;
18791 cx.notify();
18792 })
18793 .ok();
18794 });
18795 }
18796
18797 fn pull_diagnostics(
18798 &mut self,
18799 buffer_id: Option<BufferId>,
18800 window: &Window,
18801 cx: &mut Context<Self>,
18802 ) -> Option<()> {
18803 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18804 return None;
18805 }
18806 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18807 .diagnostics
18808 .lsp_pull_diagnostics;
18809 if !pull_diagnostics_settings.enabled {
18810 return None;
18811 }
18812 let project = self.project()?.downgrade();
18813
18814 let mut edited_buffer_ids = HashSet::default();
18815 let mut edited_worktree_ids = HashSet::default();
18816 let edited_buffers = match buffer_id {
18817 Some(buffer_id) => {
18818 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18819 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18820 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18821 edited_worktree_ids.insert(worktree_id);
18822 vec![buffer]
18823 }
18824 None => self
18825 .buffer()
18826 .read(cx)
18827 .all_buffers()
18828 .into_iter()
18829 .filter(|buffer| {
18830 let buffer = buffer.read(cx);
18831 match buffer.file().map(|f| f.worktree_id(cx)) {
18832 Some(worktree_id) => {
18833 edited_buffer_ids.insert(buffer.remote_id());
18834 edited_worktree_ids.insert(worktree_id);
18835 true
18836 }
18837 None => false,
18838 }
18839 })
18840 .collect::<Vec<_>>(),
18841 };
18842
18843 if edited_buffers.is_empty() {
18844 self.pull_diagnostics_task = Task::ready(());
18845 self.pull_diagnostics_background_task = Task::ready(());
18846 return None;
18847 }
18848
18849 let mut already_used_buffers = HashSet::default();
18850 let related_open_buffers = self
18851 .workspace
18852 .as_ref()
18853 .and_then(|(workspace, _)| workspace.upgrade())
18854 .into_iter()
18855 .flat_map(|workspace| workspace.read(cx).panes())
18856 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18857 .filter(|editor| editor != &cx.entity())
18858 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18859 .filter(|buffer| {
18860 let buffer = buffer.read(cx);
18861 let buffer_id = buffer.remote_id();
18862 if already_used_buffers.insert(buffer_id) {
18863 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18864 return !edited_buffer_ids.contains(&buffer_id)
18865 && !edited_worktree_ids.contains(&worktree_id);
18866 }
18867 }
18868 false
18869 })
18870 .collect::<Vec<_>>();
18871
18872 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18873 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18874 if buffers.is_empty() {
18875 return Task::ready(());
18876 }
18877 let project_weak = project.clone();
18878 cx.spawn_in(window, async move |_, cx| {
18879 cx.background_executor().timer(delay).await;
18880
18881 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18882 buffers
18883 .into_iter()
18884 .filter_map(|buffer| {
18885 project_weak
18886 .update(cx, |project, cx| {
18887 project.lsp_store().update(cx, |lsp_store, cx| {
18888 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18889 })
18890 })
18891 .ok()
18892 })
18893 .collect::<FuturesUnordered<_>>()
18894 }) else {
18895 return;
18896 };
18897
18898 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18899 if let Err(e) = pull_task {
18900 log::error!("Failed to update project diagnostics: {e:#}");
18901 }
18902 }
18903 })
18904 };
18905
18906 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18907 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18908
18909 Some(())
18910 }
18911
18912 pub fn set_selections_from_remote(
18913 &mut self,
18914 selections: Vec<Selection<Anchor>>,
18915 pending_selection: Option<Selection<Anchor>>,
18916 window: &mut Window,
18917 cx: &mut Context<Self>,
18918 ) {
18919 let old_cursor_position = self.selections.newest_anchor().head();
18920 self.selections
18921 .change_with(&self.display_snapshot(cx), |s| {
18922 s.select_anchors(selections);
18923 if let Some(pending_selection) = pending_selection {
18924 s.set_pending(pending_selection, SelectMode::Character);
18925 } else {
18926 s.clear_pending();
18927 }
18928 });
18929 self.selections_did_change(
18930 false,
18931 &old_cursor_position,
18932 SelectionEffects::default(),
18933 window,
18934 cx,
18935 );
18936 }
18937
18938 pub fn transact(
18939 &mut self,
18940 window: &mut Window,
18941 cx: &mut Context<Self>,
18942 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18943 ) -> Option<TransactionId> {
18944 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18945 this.start_transaction_at(Instant::now(), window, cx);
18946 update(this, window, cx);
18947 this.end_transaction_at(Instant::now(), cx)
18948 })
18949 }
18950
18951 pub fn start_transaction_at(
18952 &mut self,
18953 now: Instant,
18954 window: &mut Window,
18955 cx: &mut Context<Self>,
18956 ) -> Option<TransactionId> {
18957 self.end_selection(window, cx);
18958 if let Some(tx_id) = self
18959 .buffer
18960 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18961 {
18962 self.selection_history
18963 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18964 cx.emit(EditorEvent::TransactionBegun {
18965 transaction_id: tx_id,
18966 });
18967 Some(tx_id)
18968 } else {
18969 None
18970 }
18971 }
18972
18973 pub fn end_transaction_at(
18974 &mut self,
18975 now: Instant,
18976 cx: &mut Context<Self>,
18977 ) -> Option<TransactionId> {
18978 if let Some(transaction_id) = self
18979 .buffer
18980 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18981 {
18982 if let Some((_, end_selections)) =
18983 self.selection_history.transaction_mut(transaction_id)
18984 {
18985 *end_selections = Some(self.selections.disjoint_anchors_arc());
18986 } else {
18987 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18988 }
18989
18990 cx.emit(EditorEvent::Edited { transaction_id });
18991 Some(transaction_id)
18992 } else {
18993 None
18994 }
18995 }
18996
18997 pub fn modify_transaction_selection_history(
18998 &mut self,
18999 transaction_id: TransactionId,
19000 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
19001 ) -> bool {
19002 self.selection_history
19003 .transaction_mut(transaction_id)
19004 .map(modify)
19005 .is_some()
19006 }
19007
19008 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
19009 if self.selection_mark_mode {
19010 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19011 s.move_with(|_, sel| {
19012 sel.collapse_to(sel.head(), SelectionGoal::None);
19013 });
19014 })
19015 }
19016 self.selection_mark_mode = true;
19017 cx.notify();
19018 }
19019
19020 pub fn swap_selection_ends(
19021 &mut self,
19022 _: &actions::SwapSelectionEnds,
19023 window: &mut Window,
19024 cx: &mut Context<Self>,
19025 ) {
19026 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
19027 s.move_with(|_, sel| {
19028 if sel.start != sel.end {
19029 sel.reversed = !sel.reversed
19030 }
19031 });
19032 });
19033 self.request_autoscroll(Autoscroll::newest(), cx);
19034 cx.notify();
19035 }
19036
19037 pub fn toggle_focus(
19038 workspace: &mut Workspace,
19039 _: &actions::ToggleFocus,
19040 window: &mut Window,
19041 cx: &mut Context<Workspace>,
19042 ) {
19043 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19044 return;
19045 };
19046 workspace.activate_item(&item, true, true, window, cx);
19047 }
19048
19049 pub fn toggle_fold(
19050 &mut self,
19051 _: &actions::ToggleFold,
19052 window: &mut Window,
19053 cx: &mut Context<Self>,
19054 ) {
19055 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19056 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19057 let selection = self.selections.newest::<Point>(&display_map);
19058
19059 let range = if selection.is_empty() {
19060 let point = selection.head().to_display_point(&display_map);
19061 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19062 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19063 .to_point(&display_map);
19064 start..end
19065 } else {
19066 selection.range()
19067 };
19068 if display_map.folds_in_range(range).next().is_some() {
19069 self.unfold_lines(&Default::default(), window, cx)
19070 } else {
19071 self.fold(&Default::default(), window, cx)
19072 }
19073 } else {
19074 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19075 let buffer_ids: HashSet<_> = self
19076 .selections
19077 .disjoint_anchor_ranges()
19078 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19079 .collect();
19080
19081 let should_unfold = buffer_ids
19082 .iter()
19083 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19084
19085 for buffer_id in buffer_ids {
19086 if should_unfold {
19087 self.unfold_buffer(buffer_id, cx);
19088 } else {
19089 self.fold_buffer(buffer_id, cx);
19090 }
19091 }
19092 }
19093 }
19094
19095 pub fn toggle_fold_recursive(
19096 &mut self,
19097 _: &actions::ToggleFoldRecursive,
19098 window: &mut Window,
19099 cx: &mut Context<Self>,
19100 ) {
19101 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19102
19103 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19104 let range = if selection.is_empty() {
19105 let point = selection.head().to_display_point(&display_map);
19106 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19107 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19108 .to_point(&display_map);
19109 start..end
19110 } else {
19111 selection.range()
19112 };
19113 if display_map.folds_in_range(range).next().is_some() {
19114 self.unfold_recursive(&Default::default(), window, cx)
19115 } else {
19116 self.fold_recursive(&Default::default(), window, cx)
19117 }
19118 }
19119
19120 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19121 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19122 let mut to_fold = Vec::new();
19123 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19124 let selections = self.selections.all_adjusted(&display_map);
19125
19126 for selection in selections {
19127 let range = selection.range().sorted();
19128 let buffer_start_row = range.start.row;
19129
19130 if range.start.row != range.end.row {
19131 let mut found = false;
19132 let mut row = range.start.row;
19133 while row <= range.end.row {
19134 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19135 {
19136 found = true;
19137 row = crease.range().end.row + 1;
19138 to_fold.push(crease);
19139 } else {
19140 row += 1
19141 }
19142 }
19143 if found {
19144 continue;
19145 }
19146 }
19147
19148 for row in (0..=range.start.row).rev() {
19149 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19150 && crease.range().end.row >= buffer_start_row
19151 {
19152 to_fold.push(crease);
19153 if row <= range.start.row {
19154 break;
19155 }
19156 }
19157 }
19158 }
19159
19160 self.fold_creases(to_fold, true, window, cx);
19161 } else {
19162 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19163 let buffer_ids = self
19164 .selections
19165 .disjoint_anchor_ranges()
19166 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19167 .collect::<HashSet<_>>();
19168 for buffer_id in buffer_ids {
19169 self.fold_buffer(buffer_id, cx);
19170 }
19171 }
19172 }
19173
19174 pub fn toggle_fold_all(
19175 &mut self,
19176 _: &actions::ToggleFoldAll,
19177 window: &mut Window,
19178 cx: &mut Context<Self>,
19179 ) {
19180 if self.buffer.read(cx).is_singleton() {
19181 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19182 let has_folds = display_map
19183 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19184 .next()
19185 .is_some();
19186
19187 if has_folds {
19188 self.unfold_all(&actions::UnfoldAll, window, cx);
19189 } else {
19190 self.fold_all(&actions::FoldAll, window, cx);
19191 }
19192 } else {
19193 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19194 let should_unfold = buffer_ids
19195 .iter()
19196 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19197
19198 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19199 editor
19200 .update_in(cx, |editor, _, cx| {
19201 for buffer_id in buffer_ids {
19202 if should_unfold {
19203 editor.unfold_buffer(buffer_id, cx);
19204 } else {
19205 editor.fold_buffer(buffer_id, cx);
19206 }
19207 }
19208 })
19209 .ok();
19210 });
19211 }
19212 }
19213
19214 fn fold_at_level(
19215 &mut self,
19216 fold_at: &FoldAtLevel,
19217 window: &mut Window,
19218 cx: &mut Context<Self>,
19219 ) {
19220 if !self.buffer.read(cx).is_singleton() {
19221 return;
19222 }
19223
19224 let fold_at_level = fold_at.0;
19225 let snapshot = self.buffer.read(cx).snapshot(cx);
19226 let mut to_fold = Vec::new();
19227 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19228
19229 let row_ranges_to_keep: Vec<Range<u32>> = self
19230 .selections
19231 .all::<Point>(&self.display_snapshot(cx))
19232 .into_iter()
19233 .map(|sel| sel.start.row..sel.end.row)
19234 .collect();
19235
19236 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19237 while start_row < end_row {
19238 match self
19239 .snapshot(window, cx)
19240 .crease_for_buffer_row(MultiBufferRow(start_row))
19241 {
19242 Some(crease) => {
19243 let nested_start_row = crease.range().start.row + 1;
19244 let nested_end_row = crease.range().end.row;
19245
19246 if current_level < fold_at_level {
19247 stack.push((nested_start_row, nested_end_row, current_level + 1));
19248 } else if current_level == fold_at_level {
19249 // Fold iff there is no selection completely contained within the fold region
19250 if !row_ranges_to_keep.iter().any(|selection| {
19251 selection.end >= nested_start_row
19252 && selection.start <= nested_end_row
19253 }) {
19254 to_fold.push(crease);
19255 }
19256 }
19257
19258 start_row = nested_end_row + 1;
19259 }
19260 None => start_row += 1,
19261 }
19262 }
19263 }
19264
19265 self.fold_creases(to_fold, true, window, cx);
19266 }
19267
19268 pub fn fold_at_level_1(
19269 &mut self,
19270 _: &actions::FoldAtLevel1,
19271 window: &mut Window,
19272 cx: &mut Context<Self>,
19273 ) {
19274 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19275 }
19276
19277 pub fn fold_at_level_2(
19278 &mut self,
19279 _: &actions::FoldAtLevel2,
19280 window: &mut Window,
19281 cx: &mut Context<Self>,
19282 ) {
19283 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19284 }
19285
19286 pub fn fold_at_level_3(
19287 &mut self,
19288 _: &actions::FoldAtLevel3,
19289 window: &mut Window,
19290 cx: &mut Context<Self>,
19291 ) {
19292 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19293 }
19294
19295 pub fn fold_at_level_4(
19296 &mut self,
19297 _: &actions::FoldAtLevel4,
19298 window: &mut Window,
19299 cx: &mut Context<Self>,
19300 ) {
19301 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19302 }
19303
19304 pub fn fold_at_level_5(
19305 &mut self,
19306 _: &actions::FoldAtLevel5,
19307 window: &mut Window,
19308 cx: &mut Context<Self>,
19309 ) {
19310 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19311 }
19312
19313 pub fn fold_at_level_6(
19314 &mut self,
19315 _: &actions::FoldAtLevel6,
19316 window: &mut Window,
19317 cx: &mut Context<Self>,
19318 ) {
19319 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19320 }
19321
19322 pub fn fold_at_level_7(
19323 &mut self,
19324 _: &actions::FoldAtLevel7,
19325 window: &mut Window,
19326 cx: &mut Context<Self>,
19327 ) {
19328 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19329 }
19330
19331 pub fn fold_at_level_8(
19332 &mut self,
19333 _: &actions::FoldAtLevel8,
19334 window: &mut Window,
19335 cx: &mut Context<Self>,
19336 ) {
19337 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19338 }
19339
19340 pub fn fold_at_level_9(
19341 &mut self,
19342 _: &actions::FoldAtLevel9,
19343 window: &mut Window,
19344 cx: &mut Context<Self>,
19345 ) {
19346 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19347 }
19348
19349 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19350 if self.buffer.read(cx).is_singleton() {
19351 let mut fold_ranges = Vec::new();
19352 let snapshot = self.buffer.read(cx).snapshot(cx);
19353
19354 for row in 0..snapshot.max_row().0 {
19355 if let Some(foldable_range) = self
19356 .snapshot(window, cx)
19357 .crease_for_buffer_row(MultiBufferRow(row))
19358 {
19359 fold_ranges.push(foldable_range);
19360 }
19361 }
19362
19363 self.fold_creases(fold_ranges, true, window, cx);
19364 } else {
19365 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19366 editor
19367 .update_in(cx, |editor, _, cx| {
19368 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19369 editor.fold_buffer(buffer_id, cx);
19370 }
19371 })
19372 .ok();
19373 });
19374 }
19375 }
19376
19377 pub fn fold_function_bodies(
19378 &mut self,
19379 _: &actions::FoldFunctionBodies,
19380 window: &mut Window,
19381 cx: &mut Context<Self>,
19382 ) {
19383 let snapshot = self.buffer.read(cx).snapshot(cx);
19384
19385 let ranges = snapshot
19386 .text_object_ranges(
19387 MultiBufferOffset(0)..snapshot.len(),
19388 TreeSitterOptions::default(),
19389 )
19390 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19391 .collect::<Vec<_>>();
19392
19393 let creases = ranges
19394 .into_iter()
19395 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19396 .collect();
19397
19398 self.fold_creases(creases, true, window, cx);
19399 }
19400
19401 pub fn fold_recursive(
19402 &mut self,
19403 _: &actions::FoldRecursive,
19404 window: &mut Window,
19405 cx: &mut Context<Self>,
19406 ) {
19407 let mut to_fold = Vec::new();
19408 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19409 let selections = self.selections.all_adjusted(&display_map);
19410
19411 for selection in selections {
19412 let range = selection.range().sorted();
19413 let buffer_start_row = range.start.row;
19414
19415 if range.start.row != range.end.row {
19416 let mut found = false;
19417 for row in range.start.row..=range.end.row {
19418 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19419 found = true;
19420 to_fold.push(crease);
19421 }
19422 }
19423 if found {
19424 continue;
19425 }
19426 }
19427
19428 for row in (0..=range.start.row).rev() {
19429 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19430 if crease.range().end.row >= buffer_start_row {
19431 to_fold.push(crease);
19432 } else {
19433 break;
19434 }
19435 }
19436 }
19437 }
19438
19439 self.fold_creases(to_fold, true, window, cx);
19440 }
19441
19442 pub fn fold_at(
19443 &mut self,
19444 buffer_row: MultiBufferRow,
19445 window: &mut Window,
19446 cx: &mut Context<Self>,
19447 ) {
19448 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19449
19450 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19451 let autoscroll = self
19452 .selections
19453 .all::<Point>(&display_map)
19454 .iter()
19455 .any(|selection| crease.range().overlaps(&selection.range()));
19456
19457 self.fold_creases(vec![crease], autoscroll, window, cx);
19458 }
19459 }
19460
19461 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19462 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19463 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19464 let buffer = display_map.buffer_snapshot();
19465 let selections = self.selections.all::<Point>(&display_map);
19466 let ranges = selections
19467 .iter()
19468 .map(|s| {
19469 let range = s.display_range(&display_map).sorted();
19470 let mut start = range.start.to_point(&display_map);
19471 let mut end = range.end.to_point(&display_map);
19472 start.column = 0;
19473 end.column = buffer.line_len(MultiBufferRow(end.row));
19474 start..end
19475 })
19476 .collect::<Vec<_>>();
19477
19478 self.unfold_ranges(&ranges, true, true, cx);
19479 } else {
19480 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19481 let buffer_ids = self
19482 .selections
19483 .disjoint_anchor_ranges()
19484 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19485 .collect::<HashSet<_>>();
19486 for buffer_id in buffer_ids {
19487 self.unfold_buffer(buffer_id, cx);
19488 }
19489 }
19490 }
19491
19492 pub fn unfold_recursive(
19493 &mut self,
19494 _: &UnfoldRecursive,
19495 _window: &mut Window,
19496 cx: &mut Context<Self>,
19497 ) {
19498 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19499 let selections = self.selections.all::<Point>(&display_map);
19500 let ranges = selections
19501 .iter()
19502 .map(|s| {
19503 let mut range = s.display_range(&display_map).sorted();
19504 *range.start.column_mut() = 0;
19505 *range.end.column_mut() = display_map.line_len(range.end.row());
19506 let start = range.start.to_point(&display_map);
19507 let end = range.end.to_point(&display_map);
19508 start..end
19509 })
19510 .collect::<Vec<_>>();
19511
19512 self.unfold_ranges(&ranges, true, true, cx);
19513 }
19514
19515 pub fn unfold_at(
19516 &mut self,
19517 buffer_row: MultiBufferRow,
19518 _window: &mut Window,
19519 cx: &mut Context<Self>,
19520 ) {
19521 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19522
19523 let intersection_range = Point::new(buffer_row.0, 0)
19524 ..Point::new(
19525 buffer_row.0,
19526 display_map.buffer_snapshot().line_len(buffer_row),
19527 );
19528
19529 let autoscroll = self
19530 .selections
19531 .all::<Point>(&display_map)
19532 .iter()
19533 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19534
19535 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19536 }
19537
19538 pub fn unfold_all(
19539 &mut self,
19540 _: &actions::UnfoldAll,
19541 _window: &mut Window,
19542 cx: &mut Context<Self>,
19543 ) {
19544 if self.buffer.read(cx).is_singleton() {
19545 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19546 self.unfold_ranges(
19547 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19548 true,
19549 true,
19550 cx,
19551 );
19552 } else {
19553 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19554 editor
19555 .update(cx, |editor, cx| {
19556 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19557 editor.unfold_buffer(buffer_id, cx);
19558 }
19559 })
19560 .ok();
19561 });
19562 }
19563 }
19564
19565 pub fn fold_selected_ranges(
19566 &mut self,
19567 _: &FoldSelectedRanges,
19568 window: &mut Window,
19569 cx: &mut Context<Self>,
19570 ) {
19571 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19572 let selections = self.selections.all_adjusted(&display_map);
19573 let ranges = selections
19574 .into_iter()
19575 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19576 .collect::<Vec<_>>();
19577 self.fold_creases(ranges, true, window, cx);
19578 }
19579
19580 pub fn fold_ranges<T: ToOffset + Clone>(
19581 &mut self,
19582 ranges: Vec<Range<T>>,
19583 auto_scroll: bool,
19584 window: &mut Window,
19585 cx: &mut Context<Self>,
19586 ) {
19587 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19588 let ranges = ranges
19589 .into_iter()
19590 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19591 .collect::<Vec<_>>();
19592 self.fold_creases(ranges, auto_scroll, window, cx);
19593 }
19594
19595 pub fn fold_creases<T: ToOffset + Clone>(
19596 &mut self,
19597 creases: Vec<Crease<T>>,
19598 auto_scroll: bool,
19599 _window: &mut Window,
19600 cx: &mut Context<Self>,
19601 ) {
19602 if creases.is_empty() {
19603 return;
19604 }
19605
19606 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19607
19608 if auto_scroll {
19609 self.request_autoscroll(Autoscroll::fit(), cx);
19610 }
19611
19612 cx.notify();
19613
19614 self.scrollbar_marker_state.dirty = true;
19615 self.folds_did_change(cx);
19616 }
19617
19618 /// Removes any folds whose ranges intersect any of the given ranges.
19619 pub fn unfold_ranges<T: ToOffset + Clone>(
19620 &mut self,
19621 ranges: &[Range<T>],
19622 inclusive: bool,
19623 auto_scroll: bool,
19624 cx: &mut Context<Self>,
19625 ) {
19626 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19627 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19628 });
19629 self.folds_did_change(cx);
19630 }
19631
19632 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19633 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19634 return;
19635 }
19636
19637 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19638 self.display_map.update(cx, |display_map, cx| {
19639 display_map.fold_buffers([buffer_id], cx)
19640 });
19641
19642 let snapshot = self.display_snapshot(cx);
19643 self.selections.change_with(&snapshot, |selections| {
19644 selections.remove_selections_from_buffer(buffer_id);
19645 });
19646
19647 cx.emit(EditorEvent::BufferFoldToggled {
19648 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19649 folded: true,
19650 });
19651 cx.notify();
19652 }
19653
19654 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19655 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19656 return;
19657 }
19658 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19659 self.display_map.update(cx, |display_map, cx| {
19660 display_map.unfold_buffers([buffer_id], cx);
19661 });
19662 cx.emit(EditorEvent::BufferFoldToggled {
19663 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19664 folded: false,
19665 });
19666 cx.notify();
19667 }
19668
19669 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19670 self.display_map.read(cx).is_buffer_folded(buffer)
19671 }
19672
19673 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19674 self.display_map.read(cx).folded_buffers()
19675 }
19676
19677 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19678 self.display_map.update(cx, |display_map, cx| {
19679 display_map.disable_header_for_buffer(buffer_id, cx);
19680 });
19681 cx.notify();
19682 }
19683
19684 /// Removes any folds with the given ranges.
19685 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19686 &mut self,
19687 ranges: &[Range<T>],
19688 type_id: TypeId,
19689 auto_scroll: bool,
19690 cx: &mut Context<Self>,
19691 ) {
19692 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19693 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19694 });
19695 self.folds_did_change(cx);
19696 }
19697
19698 fn remove_folds_with<T: ToOffset + Clone>(
19699 &mut self,
19700 ranges: &[Range<T>],
19701 auto_scroll: bool,
19702 cx: &mut Context<Self>,
19703 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19704 ) {
19705 if ranges.is_empty() {
19706 return;
19707 }
19708
19709 let mut buffers_affected = HashSet::default();
19710 let multi_buffer = self.buffer().read(cx);
19711 for range in ranges {
19712 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19713 buffers_affected.insert(buffer.read(cx).remote_id());
19714 };
19715 }
19716
19717 self.display_map.update(cx, update);
19718
19719 if auto_scroll {
19720 self.request_autoscroll(Autoscroll::fit(), cx);
19721 }
19722
19723 cx.notify();
19724 self.scrollbar_marker_state.dirty = true;
19725 self.active_indent_guides_state.dirty = true;
19726 }
19727
19728 pub fn update_renderer_widths(
19729 &mut self,
19730 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19731 cx: &mut Context<Self>,
19732 ) -> bool {
19733 self.display_map
19734 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19735 }
19736
19737 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19738 self.display_map.read(cx).fold_placeholder.clone()
19739 }
19740
19741 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19742 self.use_base_text_line_numbers = show;
19743 }
19744
19745 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19746 self.buffer.update(cx, |buffer, cx| {
19747 buffer.set_all_diff_hunks_expanded(cx);
19748 });
19749 }
19750
19751 pub fn expand_all_diff_hunks(
19752 &mut self,
19753 _: &ExpandAllDiffHunks,
19754 _window: &mut Window,
19755 cx: &mut Context<Self>,
19756 ) {
19757 self.buffer.update(cx, |buffer, cx| {
19758 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19759 });
19760 }
19761
19762 pub fn collapse_all_diff_hunks(
19763 &mut self,
19764 _: &CollapseAllDiffHunks,
19765 _window: &mut Window,
19766 cx: &mut Context<Self>,
19767 ) {
19768 self.buffer.update(cx, |buffer, cx| {
19769 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19770 });
19771 }
19772
19773 pub fn toggle_selected_diff_hunks(
19774 &mut self,
19775 _: &ToggleSelectedDiffHunks,
19776 _window: &mut Window,
19777 cx: &mut Context<Self>,
19778 ) {
19779 let ranges: Vec<_> = self
19780 .selections
19781 .disjoint_anchors()
19782 .iter()
19783 .map(|s| s.range())
19784 .collect();
19785 self.toggle_diff_hunks_in_ranges(ranges, cx);
19786 }
19787
19788 pub fn diff_hunks_in_ranges<'a>(
19789 &'a self,
19790 ranges: &'a [Range<Anchor>],
19791 buffer: &'a MultiBufferSnapshot,
19792 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19793 ranges.iter().flat_map(move |range| {
19794 let end_excerpt_id = range.end.excerpt_id;
19795 let range = range.to_point(buffer);
19796 let mut peek_end = range.end;
19797 if range.end.row < buffer.max_row().0 {
19798 peek_end = Point::new(range.end.row + 1, 0);
19799 }
19800 buffer
19801 .diff_hunks_in_range(range.start..peek_end)
19802 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19803 })
19804 }
19805
19806 pub fn has_stageable_diff_hunks_in_ranges(
19807 &self,
19808 ranges: &[Range<Anchor>],
19809 snapshot: &MultiBufferSnapshot,
19810 ) -> bool {
19811 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19812 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19813 }
19814
19815 pub fn toggle_staged_selected_diff_hunks(
19816 &mut self,
19817 _: &::git::ToggleStaged,
19818 _: &mut Window,
19819 cx: &mut Context<Self>,
19820 ) {
19821 let snapshot = self.buffer.read(cx).snapshot(cx);
19822 let ranges: Vec<_> = self
19823 .selections
19824 .disjoint_anchors()
19825 .iter()
19826 .map(|s| s.range())
19827 .collect();
19828 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19829 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19830 }
19831
19832 pub fn set_render_diff_hunk_controls(
19833 &mut self,
19834 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19835 cx: &mut Context<Self>,
19836 ) {
19837 self.render_diff_hunk_controls = render_diff_hunk_controls;
19838 cx.notify();
19839 }
19840
19841 pub fn stage_and_next(
19842 &mut self,
19843 _: &::git::StageAndNext,
19844 window: &mut Window,
19845 cx: &mut Context<Self>,
19846 ) {
19847 self.do_stage_or_unstage_and_next(true, window, cx);
19848 }
19849
19850 pub fn unstage_and_next(
19851 &mut self,
19852 _: &::git::UnstageAndNext,
19853 window: &mut Window,
19854 cx: &mut Context<Self>,
19855 ) {
19856 self.do_stage_or_unstage_and_next(false, window, cx);
19857 }
19858
19859 pub fn stage_or_unstage_diff_hunks(
19860 &mut self,
19861 stage: bool,
19862 ranges: Vec<Range<Anchor>>,
19863 cx: &mut Context<Self>,
19864 ) {
19865 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19866 cx.spawn(async move |this, cx| {
19867 task.await?;
19868 this.update(cx, |this, cx| {
19869 let snapshot = this.buffer.read(cx).snapshot(cx);
19870 let chunk_by = this
19871 .diff_hunks_in_ranges(&ranges, &snapshot)
19872 .chunk_by(|hunk| hunk.buffer_id);
19873 for (buffer_id, hunks) in &chunk_by {
19874 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19875 }
19876 })
19877 })
19878 .detach_and_log_err(cx);
19879 }
19880
19881 fn save_buffers_for_ranges_if_needed(
19882 &mut self,
19883 ranges: &[Range<Anchor>],
19884 cx: &mut Context<Editor>,
19885 ) -> Task<Result<()>> {
19886 let multibuffer = self.buffer.read(cx);
19887 let snapshot = multibuffer.read(cx);
19888 let buffer_ids: HashSet<_> = ranges
19889 .iter()
19890 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19891 .collect();
19892 drop(snapshot);
19893
19894 let mut buffers = HashSet::default();
19895 for buffer_id in buffer_ids {
19896 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19897 let buffer = buffer_entity.read(cx);
19898 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19899 {
19900 buffers.insert(buffer_entity);
19901 }
19902 }
19903 }
19904
19905 if let Some(project) = &self.project {
19906 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19907 } else {
19908 Task::ready(Ok(()))
19909 }
19910 }
19911
19912 fn do_stage_or_unstage_and_next(
19913 &mut self,
19914 stage: bool,
19915 window: &mut Window,
19916 cx: &mut Context<Self>,
19917 ) {
19918 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19919
19920 if ranges.iter().any(|range| range.start != range.end) {
19921 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19922 return;
19923 }
19924
19925 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19926 let snapshot = self.snapshot(window, cx);
19927 let position = self
19928 .selections
19929 .newest::<Point>(&snapshot.display_snapshot)
19930 .head();
19931 let mut row = snapshot
19932 .buffer_snapshot()
19933 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19934 .find(|hunk| hunk.row_range.start.0 > position.row)
19935 .map(|hunk| hunk.row_range.start);
19936
19937 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19938 // Outside of the project diff editor, wrap around to the beginning.
19939 if !all_diff_hunks_expanded {
19940 row = row.or_else(|| {
19941 snapshot
19942 .buffer_snapshot()
19943 .diff_hunks_in_range(Point::zero()..position)
19944 .find(|hunk| hunk.row_range.end.0 < position.row)
19945 .map(|hunk| hunk.row_range.start)
19946 });
19947 }
19948
19949 if let Some(row) = row {
19950 let destination = Point::new(row.0, 0);
19951 let autoscroll = Autoscroll::center();
19952
19953 self.unfold_ranges(&[destination..destination], false, false, cx);
19954 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19955 s.select_ranges([destination..destination]);
19956 });
19957 }
19958 }
19959
19960 fn do_stage_or_unstage(
19961 &self,
19962 stage: bool,
19963 buffer_id: BufferId,
19964 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19965 cx: &mut App,
19966 ) -> Option<()> {
19967 let project = self.project()?;
19968 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19969 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19970 let buffer_snapshot = buffer.read(cx).snapshot();
19971 let file_exists = buffer_snapshot
19972 .file()
19973 .is_some_and(|file| file.disk_state().exists());
19974 diff.update(cx, |diff, cx| {
19975 diff.stage_or_unstage_hunks(
19976 stage,
19977 &hunks
19978 .map(|hunk| buffer_diff::DiffHunk {
19979 buffer_range: hunk.buffer_range,
19980 // We don't need to pass in word diffs here because they're only used for rendering and
19981 // this function changes internal state
19982 base_word_diffs: Vec::default(),
19983 buffer_word_diffs: Vec::default(),
19984 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19985 ..hunk.diff_base_byte_range.end.0,
19986 secondary_status: hunk.secondary_status,
19987 range: Point::zero()..Point::zero(), // unused
19988 })
19989 .collect::<Vec<_>>(),
19990 &buffer_snapshot,
19991 file_exists,
19992 cx,
19993 )
19994 });
19995 None
19996 }
19997
19998 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19999 let ranges: Vec<_> = self
20000 .selections
20001 .disjoint_anchors()
20002 .iter()
20003 .map(|s| s.range())
20004 .collect();
20005 self.buffer
20006 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
20007 }
20008
20009 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
20010 self.buffer.update(cx, |buffer, cx| {
20011 let ranges = vec![Anchor::min()..Anchor::max()];
20012 if !buffer.all_diff_hunks_expanded()
20013 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
20014 {
20015 buffer.collapse_diff_hunks(ranges, cx);
20016 true
20017 } else {
20018 false
20019 }
20020 })
20021 }
20022
20023 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
20024 if self.buffer.read(cx).all_diff_hunks_expanded() {
20025 return true;
20026 }
20027 let ranges = vec![Anchor::min()..Anchor::max()];
20028 self.buffer
20029 .read(cx)
20030 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
20031 }
20032
20033 fn toggle_diff_hunks_in_ranges(
20034 &mut self,
20035 ranges: Vec<Range<Anchor>>,
20036 cx: &mut Context<Editor>,
20037 ) {
20038 self.buffer.update(cx, |buffer, cx| {
20039 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20040 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20041 })
20042 }
20043
20044 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20045 self.buffer.update(cx, |buffer, cx| {
20046 let snapshot = buffer.snapshot(cx);
20047 let excerpt_id = range.end.excerpt_id;
20048 let point_range = range.to_point(&snapshot);
20049 let expand = !buffer.single_hunk_is_expanded(range, cx);
20050 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20051 })
20052 }
20053
20054 pub(crate) fn apply_all_diff_hunks(
20055 &mut self,
20056 _: &ApplyAllDiffHunks,
20057 window: &mut Window,
20058 cx: &mut Context<Self>,
20059 ) {
20060 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20061
20062 let buffers = self.buffer.read(cx).all_buffers();
20063 for branch_buffer in buffers {
20064 branch_buffer.update(cx, |branch_buffer, cx| {
20065 branch_buffer.merge_into_base(Vec::new(), cx);
20066 });
20067 }
20068
20069 if let Some(project) = self.project.clone() {
20070 self.save(
20071 SaveOptions {
20072 format: true,
20073 autosave: false,
20074 },
20075 project,
20076 window,
20077 cx,
20078 )
20079 .detach_and_log_err(cx);
20080 }
20081 }
20082
20083 pub(crate) fn apply_selected_diff_hunks(
20084 &mut self,
20085 _: &ApplyDiffHunk,
20086 window: &mut Window,
20087 cx: &mut Context<Self>,
20088 ) {
20089 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20090 let snapshot = self.snapshot(window, cx);
20091 let hunks = snapshot.hunks_for_ranges(
20092 self.selections
20093 .all(&snapshot.display_snapshot)
20094 .into_iter()
20095 .map(|selection| selection.range()),
20096 );
20097 let mut ranges_by_buffer = HashMap::default();
20098 self.transact(window, cx, |editor, _window, cx| {
20099 for hunk in hunks {
20100 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20101 ranges_by_buffer
20102 .entry(buffer.clone())
20103 .or_insert_with(Vec::new)
20104 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20105 }
20106 }
20107
20108 for (buffer, ranges) in ranges_by_buffer {
20109 buffer.update(cx, |buffer, cx| {
20110 buffer.merge_into_base(ranges, cx);
20111 });
20112 }
20113 });
20114
20115 if let Some(project) = self.project.clone() {
20116 self.save(
20117 SaveOptions {
20118 format: true,
20119 autosave: false,
20120 },
20121 project,
20122 window,
20123 cx,
20124 )
20125 .detach_and_log_err(cx);
20126 }
20127 }
20128
20129 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20130 if hovered != self.gutter_hovered {
20131 self.gutter_hovered = hovered;
20132 cx.notify();
20133 }
20134 }
20135
20136 pub fn insert_blocks(
20137 &mut self,
20138 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20139 autoscroll: Option<Autoscroll>,
20140 cx: &mut Context<Self>,
20141 ) -> Vec<CustomBlockId> {
20142 let blocks = self
20143 .display_map
20144 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20145 if let Some(autoscroll) = autoscroll {
20146 self.request_autoscroll(autoscroll, cx);
20147 }
20148 cx.notify();
20149 blocks
20150 }
20151
20152 pub fn resize_blocks(
20153 &mut self,
20154 heights: HashMap<CustomBlockId, u32>,
20155 autoscroll: Option<Autoscroll>,
20156 cx: &mut Context<Self>,
20157 ) {
20158 self.display_map
20159 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20160 if let Some(autoscroll) = autoscroll {
20161 self.request_autoscroll(autoscroll, cx);
20162 }
20163 cx.notify();
20164 }
20165
20166 pub fn replace_blocks(
20167 &mut self,
20168 renderers: HashMap<CustomBlockId, RenderBlock>,
20169 autoscroll: Option<Autoscroll>,
20170 cx: &mut Context<Self>,
20171 ) {
20172 self.display_map
20173 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20174 if let Some(autoscroll) = autoscroll {
20175 self.request_autoscroll(autoscroll, cx);
20176 }
20177 cx.notify();
20178 }
20179
20180 pub fn remove_blocks(
20181 &mut self,
20182 block_ids: HashSet<CustomBlockId>,
20183 autoscroll: Option<Autoscroll>,
20184 cx: &mut Context<Self>,
20185 ) {
20186 self.display_map.update(cx, |display_map, cx| {
20187 display_map.remove_blocks(block_ids, cx)
20188 });
20189 if let Some(autoscroll) = autoscroll {
20190 self.request_autoscroll(autoscroll, cx);
20191 }
20192 cx.notify();
20193 }
20194
20195 pub fn row_for_block(
20196 &self,
20197 block_id: CustomBlockId,
20198 cx: &mut Context<Self>,
20199 ) -> Option<DisplayRow> {
20200 self.display_map
20201 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20202 }
20203
20204 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20205 self.focused_block = Some(focused_block);
20206 }
20207
20208 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20209 self.focused_block.take()
20210 }
20211
20212 pub fn insert_creases(
20213 &mut self,
20214 creases: impl IntoIterator<Item = Crease<Anchor>>,
20215 cx: &mut Context<Self>,
20216 ) -> Vec<CreaseId> {
20217 self.display_map
20218 .update(cx, |map, cx| map.insert_creases(creases, cx))
20219 }
20220
20221 pub fn remove_creases(
20222 &mut self,
20223 ids: impl IntoIterator<Item = CreaseId>,
20224 cx: &mut Context<Self>,
20225 ) -> Vec<(CreaseId, Range<Anchor>)> {
20226 self.display_map
20227 .update(cx, |map, cx| map.remove_creases(ids, cx))
20228 }
20229
20230 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20231 self.display_map
20232 .update(cx, |map, cx| map.snapshot(cx))
20233 .longest_row()
20234 }
20235
20236 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20237 self.display_map
20238 .update(cx, |map, cx| map.snapshot(cx))
20239 .max_point()
20240 }
20241
20242 pub fn text(&self, cx: &App) -> String {
20243 self.buffer.read(cx).read(cx).text()
20244 }
20245
20246 pub fn is_empty(&self, cx: &App) -> bool {
20247 self.buffer.read(cx).read(cx).is_empty()
20248 }
20249
20250 pub fn text_option(&self, cx: &App) -> Option<String> {
20251 let text = self.text(cx);
20252 let text = text.trim();
20253
20254 if text.is_empty() {
20255 return None;
20256 }
20257
20258 Some(text.to_string())
20259 }
20260
20261 pub fn set_text(
20262 &mut self,
20263 text: impl Into<Arc<str>>,
20264 window: &mut Window,
20265 cx: &mut Context<Self>,
20266 ) {
20267 self.transact(window, cx, |this, _, cx| {
20268 this.buffer
20269 .read(cx)
20270 .as_singleton()
20271 .expect("you can only call set_text on editors for singleton buffers")
20272 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20273 });
20274 }
20275
20276 pub fn display_text(&self, cx: &mut App) -> String {
20277 self.display_map
20278 .update(cx, |map, cx| map.snapshot(cx))
20279 .text()
20280 }
20281
20282 fn create_minimap(
20283 &self,
20284 minimap_settings: MinimapSettings,
20285 window: &mut Window,
20286 cx: &mut Context<Self>,
20287 ) -> Option<Entity<Self>> {
20288 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20289 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20290 }
20291
20292 fn initialize_new_minimap(
20293 &self,
20294 minimap_settings: MinimapSettings,
20295 window: &mut Window,
20296 cx: &mut Context<Self>,
20297 ) -> Entity<Self> {
20298 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20299
20300 let mut minimap = Editor::new_internal(
20301 EditorMode::Minimap {
20302 parent: cx.weak_entity(),
20303 },
20304 self.buffer.clone(),
20305 None,
20306 Some(self.display_map.clone()),
20307 window,
20308 cx,
20309 );
20310 minimap.scroll_manager.clone_state(&self.scroll_manager);
20311 minimap.set_text_style_refinement(TextStyleRefinement {
20312 font_size: Some(MINIMAP_FONT_SIZE),
20313 font_weight: Some(MINIMAP_FONT_WEIGHT),
20314 ..Default::default()
20315 });
20316 minimap.update_minimap_configuration(minimap_settings, cx);
20317 cx.new(|_| minimap)
20318 }
20319
20320 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20321 let current_line_highlight = minimap_settings
20322 .current_line_highlight
20323 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20324 self.set_current_line_highlight(Some(current_line_highlight));
20325 }
20326
20327 pub fn minimap(&self) -> Option<&Entity<Self>> {
20328 self.minimap
20329 .as_ref()
20330 .filter(|_| self.minimap_visibility.visible())
20331 }
20332
20333 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20334 let mut wrap_guides = smallvec![];
20335
20336 if self.show_wrap_guides == Some(false) {
20337 return wrap_guides;
20338 }
20339
20340 let settings = self.buffer.read(cx).language_settings(cx);
20341 if settings.show_wrap_guides {
20342 match self.soft_wrap_mode(cx) {
20343 SoftWrap::Column(soft_wrap) => {
20344 wrap_guides.push((soft_wrap as usize, true));
20345 }
20346 SoftWrap::Bounded(soft_wrap) => {
20347 wrap_guides.push((soft_wrap as usize, true));
20348 }
20349 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20350 }
20351 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20352 }
20353
20354 wrap_guides
20355 }
20356
20357 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20358 let settings = self.buffer.read(cx).language_settings(cx);
20359 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20360 match mode {
20361 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20362 SoftWrap::None
20363 }
20364 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20365 language_settings::SoftWrap::PreferredLineLength => {
20366 SoftWrap::Column(settings.preferred_line_length)
20367 }
20368 language_settings::SoftWrap::Bounded => {
20369 SoftWrap::Bounded(settings.preferred_line_length)
20370 }
20371 }
20372 }
20373
20374 pub fn set_soft_wrap_mode(
20375 &mut self,
20376 mode: language_settings::SoftWrap,
20377
20378 cx: &mut Context<Self>,
20379 ) {
20380 self.soft_wrap_mode_override = Some(mode);
20381 cx.notify();
20382 }
20383
20384 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20385 self.hard_wrap = hard_wrap;
20386 cx.notify();
20387 }
20388
20389 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20390 self.text_style_refinement = Some(style);
20391 }
20392
20393 /// called by the Element so we know what style we were most recently rendered with.
20394 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20395 // We intentionally do not inform the display map about the minimap style
20396 // so that wrapping is not recalculated and stays consistent for the editor
20397 // and its linked minimap.
20398 if !self.mode.is_minimap() {
20399 let font = style.text.font();
20400 let font_size = style.text.font_size.to_pixels(window.rem_size());
20401 let display_map = self
20402 .placeholder_display_map
20403 .as_ref()
20404 .filter(|_| self.is_empty(cx))
20405 .unwrap_or(&self.display_map);
20406
20407 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20408 }
20409 self.style = Some(style);
20410 }
20411
20412 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20413 if self.style.is_none() {
20414 self.style = Some(self.create_style(cx));
20415 }
20416 self.style.as_ref().unwrap()
20417 }
20418
20419 // Called by the element. This method is not designed to be called outside of the editor
20420 // element's layout code because it does not notify when rewrapping is computed synchronously.
20421 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20422 if self.is_empty(cx) {
20423 self.placeholder_display_map
20424 .as_ref()
20425 .map_or(false, |display_map| {
20426 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20427 })
20428 } else {
20429 self.display_map
20430 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20431 }
20432 }
20433
20434 pub fn set_soft_wrap(&mut self) {
20435 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20436 }
20437
20438 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20439 if self.soft_wrap_mode_override.is_some() {
20440 self.soft_wrap_mode_override.take();
20441 } else {
20442 let soft_wrap = match self.soft_wrap_mode(cx) {
20443 SoftWrap::GitDiff => return,
20444 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20445 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20446 language_settings::SoftWrap::None
20447 }
20448 };
20449 self.soft_wrap_mode_override = Some(soft_wrap);
20450 }
20451 cx.notify();
20452 }
20453
20454 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20455 let Some(workspace) = self.workspace() else {
20456 return;
20457 };
20458 let fs = workspace.read(cx).app_state().fs.clone();
20459 let current_show = TabBarSettings::get_global(cx).show;
20460 update_settings_file(fs, cx, move |setting, _| {
20461 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20462 });
20463 }
20464
20465 pub fn toggle_indent_guides(
20466 &mut self,
20467 _: &ToggleIndentGuides,
20468 _: &mut Window,
20469 cx: &mut Context<Self>,
20470 ) {
20471 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20472 self.buffer
20473 .read(cx)
20474 .language_settings(cx)
20475 .indent_guides
20476 .enabled
20477 });
20478 self.show_indent_guides = Some(!currently_enabled);
20479 cx.notify();
20480 }
20481
20482 fn should_show_indent_guides(&self) -> Option<bool> {
20483 self.show_indent_guides
20484 }
20485
20486 pub fn disable_indent_guides_for_buffer(
20487 &mut self,
20488 buffer_id: BufferId,
20489 cx: &mut Context<Self>,
20490 ) {
20491 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20492 cx.notify();
20493 }
20494
20495 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20496 self.buffers_with_disabled_indent_guides
20497 .contains(&buffer_id)
20498 }
20499
20500 pub fn toggle_line_numbers(
20501 &mut self,
20502 _: &ToggleLineNumbers,
20503 _: &mut Window,
20504 cx: &mut Context<Self>,
20505 ) {
20506 let mut editor_settings = EditorSettings::get_global(cx).clone();
20507 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20508 EditorSettings::override_global(editor_settings, cx);
20509 }
20510
20511 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20512 if let Some(show_line_numbers) = self.show_line_numbers {
20513 return show_line_numbers;
20514 }
20515 EditorSettings::get_global(cx).gutter.line_numbers
20516 }
20517
20518 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20519 match (
20520 self.use_relative_line_numbers,
20521 EditorSettings::get_global(cx).relative_line_numbers,
20522 ) {
20523 (None, setting) => setting,
20524 (Some(false), _) => RelativeLineNumbers::Disabled,
20525 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20526 (Some(true), _) => RelativeLineNumbers::Enabled,
20527 }
20528 }
20529
20530 pub fn toggle_relative_line_numbers(
20531 &mut self,
20532 _: &ToggleRelativeLineNumbers,
20533 _: &mut Window,
20534 cx: &mut Context<Self>,
20535 ) {
20536 let is_relative = self.relative_line_numbers(cx);
20537 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20538 }
20539
20540 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20541 self.use_relative_line_numbers = is_relative;
20542 cx.notify();
20543 }
20544
20545 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20546 self.show_gutter = show_gutter;
20547 cx.notify();
20548 }
20549
20550 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20551 self.show_scrollbars = ScrollbarAxes {
20552 horizontal: show,
20553 vertical: show,
20554 };
20555 cx.notify();
20556 }
20557
20558 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20559 self.show_scrollbars.vertical = show;
20560 cx.notify();
20561 }
20562
20563 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20564 self.show_scrollbars.horizontal = show;
20565 cx.notify();
20566 }
20567
20568 pub fn set_minimap_visibility(
20569 &mut self,
20570 minimap_visibility: MinimapVisibility,
20571 window: &mut Window,
20572 cx: &mut Context<Self>,
20573 ) {
20574 if self.minimap_visibility != minimap_visibility {
20575 if minimap_visibility.visible() && self.minimap.is_none() {
20576 let minimap_settings = EditorSettings::get_global(cx).minimap;
20577 self.minimap =
20578 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20579 }
20580 self.minimap_visibility = minimap_visibility;
20581 cx.notify();
20582 }
20583 }
20584
20585 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20586 self.set_show_scrollbars(false, cx);
20587 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20588 }
20589
20590 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20591 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20592 }
20593
20594 /// Normally the text in full mode and auto height editors is padded on the
20595 /// left side by roughly half a character width for improved hit testing.
20596 ///
20597 /// Use this method to disable this for cases where this is not wanted (e.g.
20598 /// if you want to align the editor text with some other text above or below)
20599 /// or if you want to add this padding to single-line editors.
20600 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20601 self.offset_content = offset_content;
20602 cx.notify();
20603 }
20604
20605 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20606 self.show_line_numbers = Some(show_line_numbers);
20607 cx.notify();
20608 }
20609
20610 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20611 self.disable_expand_excerpt_buttons = true;
20612 cx.notify();
20613 }
20614
20615 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20616 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20617 cx.notify();
20618 }
20619
20620 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20621 self.show_code_actions = Some(show_code_actions);
20622 cx.notify();
20623 }
20624
20625 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20626 self.show_runnables = Some(show_runnables);
20627 cx.notify();
20628 }
20629
20630 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20631 self.show_breakpoints = Some(show_breakpoints);
20632 cx.notify();
20633 }
20634
20635 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20636 if self.display_map.read(cx).masked != masked {
20637 self.display_map.update(cx, |map, _| map.masked = masked);
20638 }
20639 cx.notify()
20640 }
20641
20642 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20643 self.show_wrap_guides = Some(show_wrap_guides);
20644 cx.notify();
20645 }
20646
20647 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20648 self.show_indent_guides = Some(show_indent_guides);
20649 cx.notify();
20650 }
20651
20652 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20653 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20654 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20655 && let Some(dir) = file.abs_path(cx).parent()
20656 {
20657 return Some(dir.to_owned());
20658 }
20659 }
20660
20661 None
20662 }
20663
20664 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20665 self.active_excerpt(cx)?
20666 .1
20667 .read(cx)
20668 .file()
20669 .and_then(|f| f.as_local())
20670 }
20671
20672 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20673 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20674 let buffer = buffer.read(cx);
20675 if let Some(project_path) = buffer.project_path(cx) {
20676 let project = self.project()?.read(cx);
20677 project.absolute_path(&project_path, cx)
20678 } else {
20679 buffer
20680 .file()
20681 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20682 }
20683 })
20684 }
20685
20686 pub fn reveal_in_finder(
20687 &mut self,
20688 _: &RevealInFileManager,
20689 _window: &mut Window,
20690 cx: &mut Context<Self>,
20691 ) {
20692 if let Some(target) = self.target_file(cx) {
20693 cx.reveal_path(&target.abs_path(cx));
20694 }
20695 }
20696
20697 pub fn copy_path(
20698 &mut self,
20699 _: &zed_actions::workspace::CopyPath,
20700 _window: &mut Window,
20701 cx: &mut Context<Self>,
20702 ) {
20703 if let Some(path) = self.target_file_abs_path(cx)
20704 && let Some(path) = path.to_str()
20705 {
20706 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20707 } else {
20708 cx.propagate();
20709 }
20710 }
20711
20712 pub fn copy_relative_path(
20713 &mut self,
20714 _: &zed_actions::workspace::CopyRelativePath,
20715 _window: &mut Window,
20716 cx: &mut Context<Self>,
20717 ) {
20718 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20719 let project = self.project()?.read(cx);
20720 let path = buffer.read(cx).file()?.path();
20721 let path = path.display(project.path_style(cx));
20722 Some(path)
20723 }) {
20724 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20725 } else {
20726 cx.propagate();
20727 }
20728 }
20729
20730 /// Returns the project path for the editor's buffer, if any buffer is
20731 /// opened in the editor.
20732 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20733 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20734 buffer.read(cx).project_path(cx)
20735 } else {
20736 None
20737 }
20738 }
20739
20740 // Returns true if the editor handled a go-to-line request
20741 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20742 maybe!({
20743 let breakpoint_store = self.breakpoint_store.as_ref()?;
20744
20745 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20746 else {
20747 self.clear_row_highlights::<ActiveDebugLine>();
20748 return None;
20749 };
20750
20751 let position = active_stack_frame.position;
20752 let buffer_id = position.buffer_id?;
20753 let snapshot = self
20754 .project
20755 .as_ref()?
20756 .read(cx)
20757 .buffer_for_id(buffer_id, cx)?
20758 .read(cx)
20759 .snapshot();
20760
20761 let mut handled = false;
20762 for (id, ExcerptRange { context, .. }) in
20763 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20764 {
20765 if context.start.cmp(&position, &snapshot).is_ge()
20766 || context.end.cmp(&position, &snapshot).is_lt()
20767 {
20768 continue;
20769 }
20770 let snapshot = self.buffer.read(cx).snapshot(cx);
20771 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20772
20773 handled = true;
20774 self.clear_row_highlights::<ActiveDebugLine>();
20775
20776 self.go_to_line::<ActiveDebugLine>(
20777 multibuffer_anchor,
20778 Some(cx.theme().colors().editor_debugger_active_line_background),
20779 window,
20780 cx,
20781 );
20782
20783 cx.notify();
20784 }
20785
20786 handled.then_some(())
20787 })
20788 .is_some()
20789 }
20790
20791 pub fn copy_file_name_without_extension(
20792 &mut self,
20793 _: &CopyFileNameWithoutExtension,
20794 _: &mut Window,
20795 cx: &mut Context<Self>,
20796 ) {
20797 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20798 let file = buffer.read(cx).file()?;
20799 file.path().file_stem()
20800 }) {
20801 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20802 }
20803 }
20804
20805 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20806 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20807 let file = buffer.read(cx).file()?;
20808 Some(file.file_name(cx))
20809 }) {
20810 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20811 }
20812 }
20813
20814 pub fn toggle_git_blame(
20815 &mut self,
20816 _: &::git::Blame,
20817 window: &mut Window,
20818 cx: &mut Context<Self>,
20819 ) {
20820 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20821
20822 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20823 self.start_git_blame(true, window, cx);
20824 }
20825
20826 cx.notify();
20827 }
20828
20829 pub fn toggle_git_blame_inline(
20830 &mut self,
20831 _: &ToggleGitBlameInline,
20832 window: &mut Window,
20833 cx: &mut Context<Self>,
20834 ) {
20835 self.toggle_git_blame_inline_internal(true, window, cx);
20836 cx.notify();
20837 }
20838
20839 pub fn open_git_blame_commit(
20840 &mut self,
20841 _: &OpenGitBlameCommit,
20842 window: &mut Window,
20843 cx: &mut Context<Self>,
20844 ) {
20845 self.open_git_blame_commit_internal(window, cx);
20846 }
20847
20848 fn open_git_blame_commit_internal(
20849 &mut self,
20850 window: &mut Window,
20851 cx: &mut Context<Self>,
20852 ) -> Option<()> {
20853 let blame = self.blame.as_ref()?;
20854 let snapshot = self.snapshot(window, cx);
20855 let cursor = self
20856 .selections
20857 .newest::<Point>(&snapshot.display_snapshot)
20858 .head();
20859 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20860 let (_, blame_entry) = blame
20861 .update(cx, |blame, cx| {
20862 blame
20863 .blame_for_rows(
20864 &[RowInfo {
20865 buffer_id: Some(buffer.remote_id()),
20866 buffer_row: Some(point.row),
20867 ..Default::default()
20868 }],
20869 cx,
20870 )
20871 .next()
20872 })
20873 .flatten()?;
20874 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20875 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20876 let workspace = self.workspace()?.downgrade();
20877 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20878 None
20879 }
20880
20881 pub fn git_blame_inline_enabled(&self) -> bool {
20882 self.git_blame_inline_enabled
20883 }
20884
20885 pub fn toggle_selection_menu(
20886 &mut self,
20887 _: &ToggleSelectionMenu,
20888 _: &mut Window,
20889 cx: &mut Context<Self>,
20890 ) {
20891 self.show_selection_menu = self
20892 .show_selection_menu
20893 .map(|show_selections_menu| !show_selections_menu)
20894 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20895
20896 cx.notify();
20897 }
20898
20899 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20900 self.show_selection_menu
20901 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20902 }
20903
20904 fn start_git_blame(
20905 &mut self,
20906 user_triggered: bool,
20907 window: &mut Window,
20908 cx: &mut Context<Self>,
20909 ) {
20910 if let Some(project) = self.project() {
20911 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20912 && buffer.read(cx).file().is_none()
20913 {
20914 return;
20915 }
20916
20917 let focused = self.focus_handle(cx).contains_focused(window, cx);
20918
20919 let project = project.clone();
20920 let blame = cx
20921 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20922 self.blame_subscription =
20923 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20924 self.blame = Some(blame);
20925 }
20926 }
20927
20928 fn toggle_git_blame_inline_internal(
20929 &mut self,
20930 user_triggered: bool,
20931 window: &mut Window,
20932 cx: &mut Context<Self>,
20933 ) {
20934 if self.git_blame_inline_enabled {
20935 self.git_blame_inline_enabled = false;
20936 self.show_git_blame_inline = false;
20937 self.show_git_blame_inline_delay_task.take();
20938 } else {
20939 self.git_blame_inline_enabled = true;
20940 self.start_git_blame_inline(user_triggered, window, cx);
20941 }
20942
20943 cx.notify();
20944 }
20945
20946 fn start_git_blame_inline(
20947 &mut self,
20948 user_triggered: bool,
20949 window: &mut Window,
20950 cx: &mut Context<Self>,
20951 ) {
20952 self.start_git_blame(user_triggered, window, cx);
20953
20954 if ProjectSettings::get_global(cx)
20955 .git
20956 .inline_blame_delay()
20957 .is_some()
20958 {
20959 self.start_inline_blame_timer(window, cx);
20960 } else {
20961 self.show_git_blame_inline = true
20962 }
20963 }
20964
20965 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20966 self.blame.as_ref()
20967 }
20968
20969 pub fn show_git_blame_gutter(&self) -> bool {
20970 self.show_git_blame_gutter
20971 }
20972
20973 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20974 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20975 }
20976
20977 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20978 self.show_git_blame_inline
20979 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20980 && !self.newest_selection_head_on_empty_line(cx)
20981 && self.has_blame_entries(cx)
20982 }
20983
20984 fn has_blame_entries(&self, cx: &App) -> bool {
20985 self.blame()
20986 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20987 }
20988
20989 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20990 let cursor_anchor = self.selections.newest_anchor().head();
20991
20992 let snapshot = self.buffer.read(cx).snapshot(cx);
20993 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20994
20995 snapshot.line_len(buffer_row) == 0
20996 }
20997
20998 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20999 let buffer_and_selection = maybe!({
21000 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
21001 let selection_range = selection.range();
21002
21003 let multi_buffer = self.buffer().read(cx);
21004 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
21005 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
21006
21007 let (buffer, range, _) = if selection.reversed {
21008 buffer_ranges.first()
21009 } else {
21010 buffer_ranges.last()
21011 }?;
21012
21013 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
21014 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
21015
21016 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
21017 let selection = start_row_in_buffer..end_row_in_buffer;
21018
21019 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
21020 };
21021
21022 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
21023
21024 Some((
21025 multi_buffer.buffer(buffer.remote_id()).unwrap(),
21026 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
21027 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
21028 ))
21029 });
21030
21031 let Some((buffer, selection)) = buffer_and_selection else {
21032 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
21033 };
21034
21035 let Some(project) = self.project() else {
21036 return Task::ready(Err(anyhow!("editor does not have project")));
21037 };
21038
21039 project.update(cx, |project, cx| {
21040 project.get_permalink_to_line(&buffer, selection, cx)
21041 })
21042 }
21043
21044 pub fn copy_permalink_to_line(
21045 &mut self,
21046 _: &CopyPermalinkToLine,
21047 window: &mut Window,
21048 cx: &mut Context<Self>,
21049 ) {
21050 let permalink_task = self.get_permalink_to_line(cx);
21051 let workspace = self.workspace();
21052
21053 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21054 Ok(permalink) => {
21055 cx.update(|_, cx| {
21056 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21057 })
21058 .ok();
21059 }
21060 Err(err) => {
21061 let message = format!("Failed to copy permalink: {err}");
21062
21063 anyhow::Result::<()>::Err(err).log_err();
21064
21065 if let Some(workspace) = workspace {
21066 workspace
21067 .update_in(cx, |workspace, _, cx| {
21068 struct CopyPermalinkToLine;
21069
21070 workspace.show_toast(
21071 Toast::new(
21072 NotificationId::unique::<CopyPermalinkToLine>(),
21073 message,
21074 ),
21075 cx,
21076 )
21077 })
21078 .ok();
21079 }
21080 }
21081 })
21082 .detach();
21083 }
21084
21085 pub fn copy_file_location(
21086 &mut self,
21087 _: &CopyFileLocation,
21088 _: &mut Window,
21089 cx: &mut Context<Self>,
21090 ) {
21091 let selection = self
21092 .selections
21093 .newest::<Point>(&self.display_snapshot(cx))
21094 .start
21095 .row
21096 + 1;
21097 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21098 let project = self.project()?.read(cx);
21099 let file = buffer.read(cx).file()?;
21100 let path = file.path().display(project.path_style(cx));
21101
21102 Some(format!("{path}:{selection}"))
21103 }) {
21104 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21105 }
21106 }
21107
21108 pub fn open_permalink_to_line(
21109 &mut self,
21110 _: &OpenPermalinkToLine,
21111 window: &mut Window,
21112 cx: &mut Context<Self>,
21113 ) {
21114 let permalink_task = self.get_permalink_to_line(cx);
21115 let workspace = self.workspace();
21116
21117 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21118 Ok(permalink) => {
21119 cx.update(|_, cx| {
21120 cx.open_url(permalink.as_ref());
21121 })
21122 .ok();
21123 }
21124 Err(err) => {
21125 let message = format!("Failed to open permalink: {err}");
21126
21127 anyhow::Result::<()>::Err(err).log_err();
21128
21129 if let Some(workspace) = workspace {
21130 workspace
21131 .update(cx, |workspace, cx| {
21132 struct OpenPermalinkToLine;
21133
21134 workspace.show_toast(
21135 Toast::new(
21136 NotificationId::unique::<OpenPermalinkToLine>(),
21137 message,
21138 ),
21139 cx,
21140 )
21141 })
21142 .ok();
21143 }
21144 }
21145 })
21146 .detach();
21147 }
21148
21149 pub fn insert_uuid_v4(
21150 &mut self,
21151 _: &InsertUuidV4,
21152 window: &mut Window,
21153 cx: &mut Context<Self>,
21154 ) {
21155 self.insert_uuid(UuidVersion::V4, window, cx);
21156 }
21157
21158 pub fn insert_uuid_v7(
21159 &mut self,
21160 _: &InsertUuidV7,
21161 window: &mut Window,
21162 cx: &mut Context<Self>,
21163 ) {
21164 self.insert_uuid(UuidVersion::V7, window, cx);
21165 }
21166
21167 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21168 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21169 self.transact(window, cx, |this, window, cx| {
21170 let edits = this
21171 .selections
21172 .all::<Point>(&this.display_snapshot(cx))
21173 .into_iter()
21174 .map(|selection| {
21175 let uuid = match version {
21176 UuidVersion::V4 => uuid::Uuid::new_v4(),
21177 UuidVersion::V7 => uuid::Uuid::now_v7(),
21178 };
21179
21180 (selection.range(), uuid.to_string())
21181 });
21182 this.edit(edits, cx);
21183 this.refresh_edit_prediction(true, false, window, cx);
21184 });
21185 }
21186
21187 pub fn open_selections_in_multibuffer(
21188 &mut self,
21189 _: &OpenSelectionsInMultibuffer,
21190 window: &mut Window,
21191 cx: &mut Context<Self>,
21192 ) {
21193 let multibuffer = self.buffer.read(cx);
21194
21195 let Some(buffer) = multibuffer.as_singleton() else {
21196 return;
21197 };
21198
21199 let Some(workspace) = self.workspace() else {
21200 return;
21201 };
21202
21203 let title = multibuffer.title(cx).to_string();
21204
21205 let locations = self
21206 .selections
21207 .all_anchors(&self.display_snapshot(cx))
21208 .iter()
21209 .map(|selection| {
21210 (
21211 buffer.clone(),
21212 (selection.start.text_anchor..selection.end.text_anchor)
21213 .to_point(buffer.read(cx)),
21214 )
21215 })
21216 .into_group_map();
21217
21218 cx.spawn_in(window, async move |_, cx| {
21219 workspace.update_in(cx, |workspace, window, cx| {
21220 Self::open_locations_in_multibuffer(
21221 workspace,
21222 locations,
21223 format!("Selections for '{title}'"),
21224 false,
21225 false,
21226 MultibufferSelectionMode::All,
21227 window,
21228 cx,
21229 );
21230 })
21231 })
21232 .detach();
21233 }
21234
21235 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21236 /// last highlight added will be used.
21237 ///
21238 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21239 pub fn highlight_rows<T: 'static>(
21240 &mut self,
21241 range: Range<Anchor>,
21242 color: Hsla,
21243 options: RowHighlightOptions,
21244 cx: &mut Context<Self>,
21245 ) {
21246 let snapshot = self.buffer().read(cx).snapshot(cx);
21247 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21248 let ix = row_highlights.binary_search_by(|highlight| {
21249 Ordering::Equal
21250 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21251 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21252 });
21253
21254 if let Err(mut ix) = ix {
21255 let index = post_inc(&mut self.highlight_order);
21256
21257 // If this range intersects with the preceding highlight, then merge it with
21258 // the preceding highlight. Otherwise insert a new highlight.
21259 let mut merged = false;
21260 if ix > 0 {
21261 let prev_highlight = &mut row_highlights[ix - 1];
21262 if prev_highlight
21263 .range
21264 .end
21265 .cmp(&range.start, &snapshot)
21266 .is_ge()
21267 {
21268 ix -= 1;
21269 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21270 prev_highlight.range.end = range.end;
21271 }
21272 merged = true;
21273 prev_highlight.index = index;
21274 prev_highlight.color = color;
21275 prev_highlight.options = options;
21276 }
21277 }
21278
21279 if !merged {
21280 row_highlights.insert(
21281 ix,
21282 RowHighlight {
21283 range,
21284 index,
21285 color,
21286 options,
21287 type_id: TypeId::of::<T>(),
21288 },
21289 );
21290 }
21291
21292 // If any of the following highlights intersect with this one, merge them.
21293 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21294 let highlight = &row_highlights[ix];
21295 if next_highlight
21296 .range
21297 .start
21298 .cmp(&highlight.range.end, &snapshot)
21299 .is_le()
21300 {
21301 if next_highlight
21302 .range
21303 .end
21304 .cmp(&highlight.range.end, &snapshot)
21305 .is_gt()
21306 {
21307 row_highlights[ix].range.end = next_highlight.range.end;
21308 }
21309 row_highlights.remove(ix + 1);
21310 } else {
21311 break;
21312 }
21313 }
21314 }
21315 }
21316
21317 /// Remove any highlighted row ranges of the given type that intersect the
21318 /// given ranges.
21319 pub fn remove_highlighted_rows<T: 'static>(
21320 &mut self,
21321 ranges_to_remove: Vec<Range<Anchor>>,
21322 cx: &mut Context<Self>,
21323 ) {
21324 let snapshot = self.buffer().read(cx).snapshot(cx);
21325 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21326 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21327 row_highlights.retain(|highlight| {
21328 while let Some(range_to_remove) = ranges_to_remove.peek() {
21329 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21330 Ordering::Less | Ordering::Equal => {
21331 ranges_to_remove.next();
21332 }
21333 Ordering::Greater => {
21334 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21335 Ordering::Less | Ordering::Equal => {
21336 return false;
21337 }
21338 Ordering::Greater => break,
21339 }
21340 }
21341 }
21342 }
21343
21344 true
21345 })
21346 }
21347
21348 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21349 pub fn clear_row_highlights<T: 'static>(&mut self) {
21350 self.highlighted_rows.remove(&TypeId::of::<T>());
21351 }
21352
21353 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21354 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21355 self.highlighted_rows
21356 .get(&TypeId::of::<T>())
21357 .map_or(&[] as &[_], |vec| vec.as_slice())
21358 .iter()
21359 .map(|highlight| (highlight.range.clone(), highlight.color))
21360 }
21361
21362 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21363 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21364 /// Allows to ignore certain kinds of highlights.
21365 pub fn highlighted_display_rows(
21366 &self,
21367 window: &mut Window,
21368 cx: &mut App,
21369 ) -> BTreeMap<DisplayRow, LineHighlight> {
21370 let snapshot = self.snapshot(window, cx);
21371 let mut used_highlight_orders = HashMap::default();
21372 self.highlighted_rows
21373 .iter()
21374 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21375 .fold(
21376 BTreeMap::<DisplayRow, LineHighlight>::new(),
21377 |mut unique_rows, highlight| {
21378 let start = highlight.range.start.to_display_point(&snapshot);
21379 let end = highlight.range.end.to_display_point(&snapshot);
21380 let start_row = start.row().0;
21381 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21382 {
21383 end.row().0.saturating_sub(1)
21384 } else {
21385 end.row().0
21386 };
21387 for row in start_row..=end_row {
21388 let used_index =
21389 used_highlight_orders.entry(row).or_insert(highlight.index);
21390 if highlight.index >= *used_index {
21391 *used_index = highlight.index;
21392 unique_rows.insert(
21393 DisplayRow(row),
21394 LineHighlight {
21395 include_gutter: highlight.options.include_gutter,
21396 border: None,
21397 background: highlight.color.into(),
21398 type_id: Some(highlight.type_id),
21399 },
21400 );
21401 }
21402 }
21403 unique_rows
21404 },
21405 )
21406 }
21407
21408 pub fn highlighted_display_row_for_autoscroll(
21409 &self,
21410 snapshot: &DisplaySnapshot,
21411 ) -> Option<DisplayRow> {
21412 self.highlighted_rows
21413 .values()
21414 .flat_map(|highlighted_rows| highlighted_rows.iter())
21415 .filter_map(|highlight| {
21416 if highlight.options.autoscroll {
21417 Some(highlight.range.start.to_display_point(snapshot).row())
21418 } else {
21419 None
21420 }
21421 })
21422 .min()
21423 }
21424
21425 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21426 self.highlight_background::<SearchWithinRange>(
21427 ranges,
21428 |_, colors| colors.colors().editor_document_highlight_read_background,
21429 cx,
21430 )
21431 }
21432
21433 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21434 self.breadcrumb_header = Some(new_header);
21435 }
21436
21437 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21438 self.clear_background_highlights::<SearchWithinRange>(cx);
21439 }
21440
21441 pub fn highlight_background<T: 'static>(
21442 &mut self,
21443 ranges: &[Range<Anchor>],
21444 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21445 cx: &mut Context<Self>,
21446 ) {
21447 self.background_highlights.insert(
21448 HighlightKey::Type(TypeId::of::<T>()),
21449 (Arc::new(color_fetcher), Arc::from(ranges)),
21450 );
21451 self.scrollbar_marker_state.dirty = true;
21452 cx.notify();
21453 }
21454
21455 pub fn highlight_background_key<T: 'static>(
21456 &mut self,
21457 key: usize,
21458 ranges: &[Range<Anchor>],
21459 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21460 cx: &mut Context<Self>,
21461 ) {
21462 self.background_highlights.insert(
21463 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21464 (Arc::new(color_fetcher), Arc::from(ranges)),
21465 );
21466 self.scrollbar_marker_state.dirty = true;
21467 cx.notify();
21468 }
21469
21470 pub fn clear_background_highlights<T: 'static>(
21471 &mut self,
21472 cx: &mut Context<Self>,
21473 ) -> Option<BackgroundHighlight> {
21474 let text_highlights = self
21475 .background_highlights
21476 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21477 if !text_highlights.1.is_empty() {
21478 self.scrollbar_marker_state.dirty = true;
21479 cx.notify();
21480 }
21481 Some(text_highlights)
21482 }
21483
21484 pub fn highlight_gutter<T: 'static>(
21485 &mut self,
21486 ranges: impl Into<Vec<Range<Anchor>>>,
21487 color_fetcher: fn(&App) -> Hsla,
21488 cx: &mut Context<Self>,
21489 ) {
21490 self.gutter_highlights
21491 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21492 cx.notify();
21493 }
21494
21495 pub fn clear_gutter_highlights<T: 'static>(
21496 &mut self,
21497 cx: &mut Context<Self>,
21498 ) -> Option<GutterHighlight> {
21499 cx.notify();
21500 self.gutter_highlights.remove(&TypeId::of::<T>())
21501 }
21502
21503 pub fn insert_gutter_highlight<T: 'static>(
21504 &mut self,
21505 range: Range<Anchor>,
21506 color_fetcher: fn(&App) -> Hsla,
21507 cx: &mut Context<Self>,
21508 ) {
21509 let snapshot = self.buffer().read(cx).snapshot(cx);
21510 let mut highlights = self
21511 .gutter_highlights
21512 .remove(&TypeId::of::<T>())
21513 .map(|(_, highlights)| highlights)
21514 .unwrap_or_default();
21515 let ix = highlights.binary_search_by(|highlight| {
21516 Ordering::Equal
21517 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21518 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21519 });
21520 if let Err(ix) = ix {
21521 highlights.insert(ix, range);
21522 }
21523 self.gutter_highlights
21524 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21525 }
21526
21527 pub fn remove_gutter_highlights<T: 'static>(
21528 &mut self,
21529 ranges_to_remove: Vec<Range<Anchor>>,
21530 cx: &mut Context<Self>,
21531 ) {
21532 let snapshot = self.buffer().read(cx).snapshot(cx);
21533 let Some((color_fetcher, mut gutter_highlights)) =
21534 self.gutter_highlights.remove(&TypeId::of::<T>())
21535 else {
21536 return;
21537 };
21538 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21539 gutter_highlights.retain(|highlight| {
21540 while let Some(range_to_remove) = ranges_to_remove.peek() {
21541 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21542 Ordering::Less | Ordering::Equal => {
21543 ranges_to_remove.next();
21544 }
21545 Ordering::Greater => {
21546 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21547 Ordering::Less | Ordering::Equal => {
21548 return false;
21549 }
21550 Ordering::Greater => break,
21551 }
21552 }
21553 }
21554 }
21555
21556 true
21557 });
21558 self.gutter_highlights
21559 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21560 }
21561
21562 #[cfg(feature = "test-support")]
21563 pub fn all_text_highlights(
21564 &self,
21565 window: &mut Window,
21566 cx: &mut Context<Self>,
21567 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21568 let snapshot = self.snapshot(window, cx);
21569 self.display_map.update(cx, |display_map, _| {
21570 display_map
21571 .all_text_highlights()
21572 .map(|highlight| {
21573 let (style, ranges) = highlight.as_ref();
21574 (
21575 *style,
21576 ranges
21577 .iter()
21578 .map(|range| range.clone().to_display_points(&snapshot))
21579 .collect(),
21580 )
21581 })
21582 .collect()
21583 })
21584 }
21585
21586 #[cfg(feature = "test-support")]
21587 pub fn all_text_background_highlights(
21588 &self,
21589 window: &mut Window,
21590 cx: &mut Context<Self>,
21591 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21592 let snapshot = self.snapshot(window, cx);
21593 let buffer = &snapshot.buffer_snapshot();
21594 let start = buffer.anchor_before(MultiBufferOffset(0));
21595 let end = buffer.anchor_after(buffer.len());
21596 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21597 }
21598
21599 #[cfg(any(test, feature = "test-support"))]
21600 pub fn sorted_background_highlights_in_range(
21601 &self,
21602 search_range: Range<Anchor>,
21603 display_snapshot: &DisplaySnapshot,
21604 theme: &Theme,
21605 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21606 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21607 res.sort_by(|a, b| {
21608 a.0.start
21609 .cmp(&b.0.start)
21610 .then_with(|| a.0.end.cmp(&b.0.end))
21611 .then_with(|| a.1.cmp(&b.1))
21612 });
21613 res
21614 }
21615
21616 #[cfg(feature = "test-support")]
21617 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21618 let snapshot = self.buffer().read(cx).snapshot(cx);
21619
21620 let highlights = self
21621 .background_highlights
21622 .get(&HighlightKey::Type(TypeId::of::<
21623 items::BufferSearchHighlights,
21624 >()));
21625
21626 if let Some((_color, ranges)) = highlights {
21627 ranges
21628 .iter()
21629 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21630 .collect_vec()
21631 } else {
21632 vec![]
21633 }
21634 }
21635
21636 fn document_highlights_for_position<'a>(
21637 &'a self,
21638 position: Anchor,
21639 buffer: &'a MultiBufferSnapshot,
21640 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21641 let read_highlights = self
21642 .background_highlights
21643 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21644 .map(|h| &h.1);
21645 let write_highlights = self
21646 .background_highlights
21647 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21648 .map(|h| &h.1);
21649 let left_position = position.bias_left(buffer);
21650 let right_position = position.bias_right(buffer);
21651 read_highlights
21652 .into_iter()
21653 .chain(write_highlights)
21654 .flat_map(move |ranges| {
21655 let start_ix = match ranges.binary_search_by(|probe| {
21656 let cmp = probe.end.cmp(&left_position, buffer);
21657 if cmp.is_ge() {
21658 Ordering::Greater
21659 } else {
21660 Ordering::Less
21661 }
21662 }) {
21663 Ok(i) | Err(i) => i,
21664 };
21665
21666 ranges[start_ix..]
21667 .iter()
21668 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21669 })
21670 }
21671
21672 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21673 self.background_highlights
21674 .get(&HighlightKey::Type(TypeId::of::<T>()))
21675 .is_some_and(|(_, highlights)| !highlights.is_empty())
21676 }
21677
21678 /// Returns all background highlights for a given range.
21679 ///
21680 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21681 pub fn background_highlights_in_range(
21682 &self,
21683 search_range: Range<Anchor>,
21684 display_snapshot: &DisplaySnapshot,
21685 theme: &Theme,
21686 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21687 let mut results = Vec::new();
21688 for (color_fetcher, ranges) in self.background_highlights.values() {
21689 let start_ix = match ranges.binary_search_by(|probe| {
21690 let cmp = probe
21691 .end
21692 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21693 if cmp.is_gt() {
21694 Ordering::Greater
21695 } else {
21696 Ordering::Less
21697 }
21698 }) {
21699 Ok(i) | Err(i) => i,
21700 };
21701 for (index, range) in ranges[start_ix..].iter().enumerate() {
21702 if range
21703 .start
21704 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21705 .is_ge()
21706 {
21707 break;
21708 }
21709
21710 let color = color_fetcher(&(start_ix + index), theme);
21711 let start = range.start.to_display_point(display_snapshot);
21712 let end = range.end.to_display_point(display_snapshot);
21713 results.push((start..end, color))
21714 }
21715 }
21716 results
21717 }
21718
21719 pub fn gutter_highlights_in_range(
21720 &self,
21721 search_range: Range<Anchor>,
21722 display_snapshot: &DisplaySnapshot,
21723 cx: &App,
21724 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21725 let mut results = Vec::new();
21726 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21727 let color = color_fetcher(cx);
21728 let start_ix = match ranges.binary_search_by(|probe| {
21729 let cmp = probe
21730 .end
21731 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21732 if cmp.is_gt() {
21733 Ordering::Greater
21734 } else {
21735 Ordering::Less
21736 }
21737 }) {
21738 Ok(i) | Err(i) => i,
21739 };
21740 for range in &ranges[start_ix..] {
21741 if range
21742 .start
21743 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21744 .is_ge()
21745 {
21746 break;
21747 }
21748
21749 let start = range.start.to_display_point(display_snapshot);
21750 let end = range.end.to_display_point(display_snapshot);
21751 results.push((start..end, color))
21752 }
21753 }
21754 results
21755 }
21756
21757 /// Get the text ranges corresponding to the redaction query
21758 pub fn redacted_ranges(
21759 &self,
21760 search_range: Range<Anchor>,
21761 display_snapshot: &DisplaySnapshot,
21762 cx: &App,
21763 ) -> Vec<Range<DisplayPoint>> {
21764 display_snapshot
21765 .buffer_snapshot()
21766 .redacted_ranges(search_range, |file| {
21767 if let Some(file) = file {
21768 file.is_private()
21769 && EditorSettings::get(
21770 Some(SettingsLocation {
21771 worktree_id: file.worktree_id(cx),
21772 path: file.path().as_ref(),
21773 }),
21774 cx,
21775 )
21776 .redact_private_values
21777 } else {
21778 false
21779 }
21780 })
21781 .map(|range| {
21782 range.start.to_display_point(display_snapshot)
21783 ..range.end.to_display_point(display_snapshot)
21784 })
21785 .collect()
21786 }
21787
21788 pub fn highlight_text_key<T: 'static>(
21789 &mut self,
21790 key: usize,
21791 ranges: Vec<Range<Anchor>>,
21792 style: HighlightStyle,
21793 merge: bool,
21794 cx: &mut Context<Self>,
21795 ) {
21796 self.display_map.update(cx, |map, cx| {
21797 map.highlight_text(
21798 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21799 ranges,
21800 style,
21801 merge,
21802 cx,
21803 );
21804 });
21805 cx.notify();
21806 }
21807
21808 pub fn highlight_text<T: 'static>(
21809 &mut self,
21810 ranges: Vec<Range<Anchor>>,
21811 style: HighlightStyle,
21812 cx: &mut Context<Self>,
21813 ) {
21814 self.display_map.update(cx, |map, cx| {
21815 map.highlight_text(
21816 HighlightKey::Type(TypeId::of::<T>()),
21817 ranges,
21818 style,
21819 false,
21820 cx,
21821 )
21822 });
21823 cx.notify();
21824 }
21825
21826 pub fn text_highlights<'a, T: 'static>(
21827 &'a self,
21828 cx: &'a App,
21829 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21830 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21831 }
21832
21833 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21834 let cleared = self
21835 .display_map
21836 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21837 if cleared {
21838 cx.notify();
21839 }
21840 }
21841
21842 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21843 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21844 && self.focus_handle.is_focused(window)
21845 }
21846
21847 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21848 self.show_cursor_when_unfocused = is_enabled;
21849 cx.notify();
21850 }
21851
21852 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21853 cx.notify();
21854 }
21855
21856 fn on_debug_session_event(
21857 &mut self,
21858 _session: Entity<Session>,
21859 event: &SessionEvent,
21860 cx: &mut Context<Self>,
21861 ) {
21862 if let SessionEvent::InvalidateInlineValue = event {
21863 self.refresh_inline_values(cx);
21864 }
21865 }
21866
21867 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21868 let Some(project) = self.project.clone() else {
21869 return;
21870 };
21871
21872 if !self.inline_value_cache.enabled {
21873 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21874 self.splice_inlays(&inlays, Vec::new(), cx);
21875 return;
21876 }
21877
21878 let current_execution_position = self
21879 .highlighted_rows
21880 .get(&TypeId::of::<ActiveDebugLine>())
21881 .and_then(|lines| lines.last().map(|line| line.range.end));
21882
21883 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21884 let inline_values = editor
21885 .update(cx, |editor, cx| {
21886 let Some(current_execution_position) = current_execution_position else {
21887 return Some(Task::ready(Ok(Vec::new())));
21888 };
21889
21890 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21891 let snapshot = buffer.snapshot(cx);
21892
21893 let excerpt = snapshot.excerpt_containing(
21894 current_execution_position..current_execution_position,
21895 )?;
21896
21897 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21898 })?;
21899
21900 let range =
21901 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21902
21903 project.inline_values(buffer, range, cx)
21904 })
21905 .ok()
21906 .flatten()?
21907 .await
21908 .context("refreshing debugger inlays")
21909 .log_err()?;
21910
21911 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21912
21913 for (buffer_id, inline_value) in inline_values
21914 .into_iter()
21915 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21916 {
21917 buffer_inline_values
21918 .entry(buffer_id)
21919 .or_default()
21920 .push(inline_value);
21921 }
21922
21923 editor
21924 .update(cx, |editor, cx| {
21925 let snapshot = editor.buffer.read(cx).snapshot(cx);
21926 let mut new_inlays = Vec::default();
21927
21928 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21929 let buffer_id = buffer_snapshot.remote_id();
21930 buffer_inline_values
21931 .get(&buffer_id)
21932 .into_iter()
21933 .flatten()
21934 .for_each(|hint| {
21935 let inlay = Inlay::debugger(
21936 post_inc(&mut editor.next_inlay_id),
21937 Anchor::in_buffer(excerpt_id, hint.position),
21938 hint.text(),
21939 );
21940 if !inlay.text().chars().contains(&'\n') {
21941 new_inlays.push(inlay);
21942 }
21943 });
21944 }
21945
21946 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21947 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21948
21949 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21950 })
21951 .ok()?;
21952 Some(())
21953 });
21954 }
21955
21956 fn on_buffer_event(
21957 &mut self,
21958 multibuffer: &Entity<MultiBuffer>,
21959 event: &multi_buffer::Event,
21960 window: &mut Window,
21961 cx: &mut Context<Self>,
21962 ) {
21963 match event {
21964 multi_buffer::Event::Edited { edited_buffer } => {
21965 self.scrollbar_marker_state.dirty = true;
21966 self.active_indent_guides_state.dirty = true;
21967 self.refresh_active_diagnostics(cx);
21968 self.refresh_code_actions(window, cx);
21969 self.refresh_single_line_folds(window, cx);
21970 self.refresh_matching_bracket_highlights(window, cx);
21971 if self.has_active_edit_prediction() {
21972 self.update_visible_edit_prediction(window, cx);
21973 }
21974
21975 if let Some(buffer) = edited_buffer {
21976 if buffer.read(cx).file().is_none() {
21977 cx.emit(EditorEvent::TitleChanged);
21978 }
21979
21980 if self.project.is_some() {
21981 let buffer_id = buffer.read(cx).remote_id();
21982 self.register_buffer(buffer_id, cx);
21983 self.update_lsp_data(Some(buffer_id), window, cx);
21984 self.refresh_inlay_hints(
21985 InlayHintRefreshReason::BufferEdited(buffer_id),
21986 cx,
21987 );
21988 }
21989 }
21990
21991 cx.emit(EditorEvent::BufferEdited);
21992 cx.emit(SearchEvent::MatchesInvalidated);
21993
21994 let Some(project) = &self.project else { return };
21995 let (telemetry, is_via_ssh) = {
21996 let project = project.read(cx);
21997 let telemetry = project.client().telemetry().clone();
21998 let is_via_ssh = project.is_via_remote_server();
21999 (telemetry, is_via_ssh)
22000 };
22001 telemetry.log_edit_event("editor", is_via_ssh);
22002 }
22003 multi_buffer::Event::ExcerptsAdded {
22004 buffer,
22005 predecessor,
22006 excerpts,
22007 } => {
22008 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22009 let buffer_id = buffer.read(cx).remote_id();
22010 if self.buffer.read(cx).diff_for(buffer_id).is_none()
22011 && let Some(project) = &self.project
22012 {
22013 update_uncommitted_diff_for_buffer(
22014 cx.entity(),
22015 project,
22016 [buffer.clone()],
22017 self.buffer.clone(),
22018 cx,
22019 )
22020 .detach();
22021 }
22022 self.update_lsp_data(Some(buffer_id), window, cx);
22023 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22024 self.colorize_brackets(false, cx);
22025 cx.emit(EditorEvent::ExcerptsAdded {
22026 buffer: buffer.clone(),
22027 predecessor: *predecessor,
22028 excerpts: excerpts.clone(),
22029 });
22030 }
22031 multi_buffer::Event::ExcerptsRemoved {
22032 ids,
22033 removed_buffer_ids,
22034 } => {
22035 if let Some(inlay_hints) = &mut self.inlay_hints {
22036 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22037 }
22038 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22039 for buffer_id in removed_buffer_ids {
22040 self.registered_buffers.remove(buffer_id);
22041 }
22042 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22043 cx.emit(EditorEvent::ExcerptsRemoved {
22044 ids: ids.clone(),
22045 removed_buffer_ids: removed_buffer_ids.clone(),
22046 });
22047 }
22048 multi_buffer::Event::ExcerptsEdited {
22049 excerpt_ids,
22050 buffer_ids,
22051 } => {
22052 self.display_map.update(cx, |map, cx| {
22053 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22054 });
22055 cx.emit(EditorEvent::ExcerptsEdited {
22056 ids: excerpt_ids.clone(),
22057 });
22058 }
22059 multi_buffer::Event::ExcerptsExpanded { ids } => {
22060 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22061 self.refresh_document_highlights(cx);
22062 for id in ids {
22063 self.fetched_tree_sitter_chunks.remove(id);
22064 }
22065 self.colorize_brackets(false, cx);
22066 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22067 }
22068 multi_buffer::Event::Reparsed(buffer_id) => {
22069 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22070 self.refresh_selected_text_highlights(true, window, cx);
22071 self.colorize_brackets(true, cx);
22072 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22073
22074 cx.emit(EditorEvent::Reparsed(*buffer_id));
22075 }
22076 multi_buffer::Event::DiffHunksToggled => {
22077 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22078 }
22079 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22080 if !is_fresh_language {
22081 self.registered_buffers.remove(&buffer_id);
22082 }
22083 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22084 cx.emit(EditorEvent::Reparsed(*buffer_id));
22085 cx.notify();
22086 }
22087 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22088 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22089 multi_buffer::Event::FileHandleChanged
22090 | multi_buffer::Event::Reloaded
22091 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22092 multi_buffer::Event::DiagnosticsUpdated => {
22093 self.update_diagnostics_state(window, cx);
22094 }
22095 _ => {}
22096 };
22097 }
22098
22099 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22100 if !self.diagnostics_enabled() {
22101 return;
22102 }
22103 self.refresh_active_diagnostics(cx);
22104 self.refresh_inline_diagnostics(true, window, cx);
22105 self.scrollbar_marker_state.dirty = true;
22106 cx.notify();
22107 }
22108
22109 pub fn start_temporary_diff_override(&mut self) {
22110 self.load_diff_task.take();
22111 self.temporary_diff_override = true;
22112 }
22113
22114 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22115 self.temporary_diff_override = false;
22116 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22117 self.buffer.update(cx, |buffer, cx| {
22118 buffer.set_all_diff_hunks_collapsed(cx);
22119 });
22120
22121 if let Some(project) = self.project.clone() {
22122 self.load_diff_task = Some(
22123 update_uncommitted_diff_for_buffer(
22124 cx.entity(),
22125 &project,
22126 self.buffer.read(cx).all_buffers(),
22127 self.buffer.clone(),
22128 cx,
22129 )
22130 .shared(),
22131 );
22132 }
22133 }
22134
22135 fn on_display_map_changed(
22136 &mut self,
22137 _: Entity<DisplayMap>,
22138 _: &mut Window,
22139 cx: &mut Context<Self>,
22140 ) {
22141 cx.notify();
22142 }
22143
22144 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22145 if !self.mode.is_full() {
22146 return None;
22147 }
22148
22149 let theme_settings = theme::ThemeSettings::get_global(cx);
22150 let theme = cx.theme();
22151 let accent_colors = theme.accents().clone();
22152
22153 let accent_overrides = theme_settings
22154 .theme_overrides
22155 .get(theme.name.as_ref())
22156 .map(|theme_style| &theme_style.accents)
22157 .into_iter()
22158 .flatten()
22159 .chain(
22160 theme_settings
22161 .experimental_theme_overrides
22162 .as_ref()
22163 .map(|overrides| &overrides.accents)
22164 .into_iter()
22165 .flatten(),
22166 )
22167 .flat_map(|accent| accent.0.clone())
22168 .collect();
22169
22170 Some(AccentData {
22171 colors: accent_colors,
22172 overrides: accent_overrides,
22173 })
22174 }
22175
22176 fn fetch_applicable_language_settings(
22177 &self,
22178 cx: &App,
22179 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22180 if !self.mode.is_full() {
22181 return HashMap::default();
22182 }
22183
22184 self.buffer().read(cx).all_buffers().into_iter().fold(
22185 HashMap::default(),
22186 |mut acc, buffer| {
22187 let buffer = buffer.read(cx);
22188 let language = buffer.language().map(|language| language.name());
22189 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22190 let file = buffer.file();
22191 v.insert(language_settings(language, file, cx).into_owned());
22192 }
22193 acc
22194 },
22195 )
22196 }
22197
22198 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22199 let new_language_settings = self.fetch_applicable_language_settings(cx);
22200 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22201 self.applicable_language_settings = new_language_settings;
22202
22203 let new_accents = self.fetch_accent_data(cx);
22204 let accents_changed = new_accents != self.accent_data;
22205 self.accent_data = new_accents;
22206
22207 if self.diagnostics_enabled() {
22208 let new_severity = EditorSettings::get_global(cx)
22209 .diagnostics_max_severity
22210 .unwrap_or(DiagnosticSeverity::Hint);
22211 self.set_max_diagnostics_severity(new_severity, cx);
22212 }
22213 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22214 self.update_edit_prediction_settings(cx);
22215 self.refresh_edit_prediction(true, false, window, cx);
22216 self.refresh_inline_values(cx);
22217 self.refresh_inlay_hints(
22218 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22219 self.selections.newest_anchor().head(),
22220 &self.buffer.read(cx).snapshot(cx),
22221 cx,
22222 )),
22223 cx,
22224 );
22225
22226 let old_cursor_shape = self.cursor_shape;
22227 let old_show_breadcrumbs = self.show_breadcrumbs;
22228
22229 {
22230 let editor_settings = EditorSettings::get_global(cx);
22231 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22232 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22233 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22234 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22235 }
22236
22237 if old_cursor_shape != self.cursor_shape {
22238 cx.emit(EditorEvent::CursorShapeChanged);
22239 }
22240
22241 if old_show_breadcrumbs != self.show_breadcrumbs {
22242 cx.emit(EditorEvent::BreadcrumbsChanged);
22243 }
22244
22245 let project_settings = ProjectSettings::get_global(cx);
22246 self.buffer_serialization = self
22247 .should_serialize_buffer()
22248 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22249
22250 if self.mode.is_full() {
22251 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22252 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22253 if self.show_inline_diagnostics != show_inline_diagnostics {
22254 self.show_inline_diagnostics = show_inline_diagnostics;
22255 self.refresh_inline_diagnostics(false, window, cx);
22256 }
22257
22258 if self.git_blame_inline_enabled != inline_blame_enabled {
22259 self.toggle_git_blame_inline_internal(false, window, cx);
22260 }
22261
22262 let minimap_settings = EditorSettings::get_global(cx).minimap;
22263 if self.minimap_visibility != MinimapVisibility::Disabled {
22264 if self.minimap_visibility.settings_visibility()
22265 != minimap_settings.minimap_enabled()
22266 {
22267 self.set_minimap_visibility(
22268 MinimapVisibility::for_mode(self.mode(), cx),
22269 window,
22270 cx,
22271 );
22272 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22273 minimap_entity.update(cx, |minimap_editor, cx| {
22274 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22275 })
22276 }
22277 }
22278
22279 if language_settings_changed || accents_changed {
22280 self.colorize_brackets(true, cx);
22281 }
22282
22283 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22284 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22285 }) {
22286 if !inlay_splice.is_empty() {
22287 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22288 }
22289 self.refresh_colors_for_visible_range(None, window, cx);
22290 }
22291 }
22292
22293 cx.notify();
22294 }
22295
22296 pub fn set_searchable(&mut self, searchable: bool) {
22297 self.searchable = searchable;
22298 }
22299
22300 pub fn searchable(&self) -> bool {
22301 self.searchable
22302 }
22303
22304 pub fn open_excerpts_in_split(
22305 &mut self,
22306 _: &OpenExcerptsSplit,
22307 window: &mut Window,
22308 cx: &mut Context<Self>,
22309 ) {
22310 self.open_excerpts_common(None, true, window, cx)
22311 }
22312
22313 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22314 self.open_excerpts_common(None, false, window, cx)
22315 }
22316
22317 fn open_excerpts_common(
22318 &mut self,
22319 jump_data: Option<JumpData>,
22320 split: bool,
22321 window: &mut Window,
22322 cx: &mut Context<Self>,
22323 ) {
22324 let Some(workspace) = self.workspace() else {
22325 cx.propagate();
22326 return;
22327 };
22328
22329 if self.buffer.read(cx).is_singleton() {
22330 cx.propagate();
22331 return;
22332 }
22333
22334 let mut new_selections_by_buffer = HashMap::default();
22335 match &jump_data {
22336 Some(JumpData::MultiBufferPoint {
22337 excerpt_id,
22338 position,
22339 anchor,
22340 line_offset_from_top,
22341 }) => {
22342 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22343 if let Some(buffer) = multi_buffer_snapshot
22344 .buffer_id_for_excerpt(*excerpt_id)
22345 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22346 {
22347 let buffer_snapshot = buffer.read(cx).snapshot();
22348 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22349 language::ToPoint::to_point(anchor, &buffer_snapshot)
22350 } else {
22351 buffer_snapshot.clip_point(*position, Bias::Left)
22352 };
22353 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22354 new_selections_by_buffer.insert(
22355 buffer,
22356 (
22357 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22358 Some(*line_offset_from_top),
22359 ),
22360 );
22361 }
22362 }
22363 Some(JumpData::MultiBufferRow {
22364 row,
22365 line_offset_from_top,
22366 }) => {
22367 let point = MultiBufferPoint::new(row.0, 0);
22368 if let Some((buffer, buffer_point, _)) =
22369 self.buffer.read(cx).point_to_buffer_point(point, cx)
22370 {
22371 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22372 new_selections_by_buffer
22373 .entry(buffer)
22374 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22375 .0
22376 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22377 }
22378 }
22379 None => {
22380 let selections = self
22381 .selections
22382 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22383 let multi_buffer = self.buffer.read(cx);
22384 for selection in selections {
22385 for (snapshot, range, _, anchor) in multi_buffer
22386 .snapshot(cx)
22387 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22388 {
22389 if let Some(anchor) = anchor {
22390 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22391 else {
22392 continue;
22393 };
22394 let offset = text::ToOffset::to_offset(
22395 &anchor.text_anchor,
22396 &buffer_handle.read(cx).snapshot(),
22397 );
22398 let range = BufferOffset(offset)..BufferOffset(offset);
22399 new_selections_by_buffer
22400 .entry(buffer_handle)
22401 .or_insert((Vec::new(), None))
22402 .0
22403 .push(range)
22404 } else {
22405 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22406 else {
22407 continue;
22408 };
22409 new_selections_by_buffer
22410 .entry(buffer_handle)
22411 .or_insert((Vec::new(), None))
22412 .0
22413 .push(range)
22414 }
22415 }
22416 }
22417 }
22418 }
22419
22420 new_selections_by_buffer
22421 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22422
22423 if new_selections_by_buffer.is_empty() {
22424 return;
22425 }
22426
22427 // We defer the pane interaction because we ourselves are a workspace item
22428 // and activating a new item causes the pane to call a method on us reentrantly,
22429 // which panics if we're on the stack.
22430 window.defer(cx, move |window, cx| {
22431 workspace.update(cx, |workspace, cx| {
22432 let pane = if split {
22433 workspace.adjacent_pane(window, cx)
22434 } else {
22435 workspace.active_pane().clone()
22436 };
22437
22438 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22439 let buffer_read = buffer.read(cx);
22440 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22441 (true, project::File::from_dyn(Some(file)).is_some())
22442 } else {
22443 (false, false)
22444 };
22445
22446 // If project file is none workspace.open_project_item will fail to open the excerpt
22447 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22448 // so we check if there's a tab match in that case first
22449 let editor = (!has_file || !is_project_file)
22450 .then(|| {
22451 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22452 // so `workspace.open_project_item` will never find them, always opening a new editor.
22453 // Instead, we try to activate the existing editor in the pane first.
22454 let (editor, pane_item_index, pane_item_id) =
22455 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22456 let editor = item.downcast::<Editor>()?;
22457 let singleton_buffer =
22458 editor.read(cx).buffer().read(cx).as_singleton()?;
22459 if singleton_buffer == buffer {
22460 Some((editor, i, item.item_id()))
22461 } else {
22462 None
22463 }
22464 })?;
22465 pane.update(cx, |pane, cx| {
22466 pane.activate_item(pane_item_index, true, true, window, cx);
22467 if !PreviewTabsSettings::get_global(cx)
22468 .enable_preview_from_multibuffer
22469 {
22470 pane.unpreview_item_if_preview(pane_item_id);
22471 }
22472 });
22473 Some(editor)
22474 })
22475 .flatten()
22476 .unwrap_or_else(|| {
22477 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22478 .enable_keep_preview_on_code_navigation;
22479 let allow_new_preview =
22480 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22481 workspace.open_project_item::<Self>(
22482 pane.clone(),
22483 buffer,
22484 true,
22485 true,
22486 keep_old_preview,
22487 allow_new_preview,
22488 window,
22489 cx,
22490 )
22491 });
22492
22493 editor.update(cx, |editor, cx| {
22494 if has_file && !is_project_file {
22495 editor.set_read_only(true);
22496 }
22497 let autoscroll = match scroll_offset {
22498 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22499 None => Autoscroll::newest(),
22500 };
22501 let nav_history = editor.nav_history.take();
22502 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22503 let Some((&excerpt_id, _, buffer_snapshot)) =
22504 multibuffer_snapshot.as_singleton()
22505 else {
22506 return;
22507 };
22508 editor.change_selections(
22509 SelectionEffects::scroll(autoscroll),
22510 window,
22511 cx,
22512 |s| {
22513 s.select_ranges(ranges.into_iter().map(|range| {
22514 let range = buffer_snapshot.anchor_before(range.start)
22515 ..buffer_snapshot.anchor_after(range.end);
22516 multibuffer_snapshot
22517 .anchor_range_in_excerpt(excerpt_id, range)
22518 .unwrap()
22519 }));
22520 },
22521 );
22522 editor.nav_history = nav_history;
22523 });
22524 }
22525 })
22526 });
22527 }
22528
22529 // Allow opening excerpts for buffers that either belong to the current project
22530 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22531 // are also supported so tests and other in-memory views keep working.
22532 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22533 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22534 }
22535
22536 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22537 let snapshot = self.buffer.read(cx).read(cx);
22538 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22539 Some(
22540 ranges
22541 .iter()
22542 .map(move |range| {
22543 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22544 })
22545 .collect(),
22546 )
22547 }
22548
22549 fn selection_replacement_ranges(
22550 &self,
22551 range: Range<MultiBufferOffsetUtf16>,
22552 cx: &mut App,
22553 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22554 let selections = self
22555 .selections
22556 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22557 let newest_selection = selections
22558 .iter()
22559 .max_by_key(|selection| selection.id)
22560 .unwrap();
22561 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22562 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22563 let snapshot = self.buffer.read(cx).read(cx);
22564 selections
22565 .into_iter()
22566 .map(|mut selection| {
22567 selection.start.0.0 =
22568 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22569 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22570 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22571 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22572 })
22573 .collect()
22574 }
22575
22576 fn report_editor_event(
22577 &self,
22578 reported_event: ReportEditorEvent,
22579 file_extension: Option<String>,
22580 cx: &App,
22581 ) {
22582 if cfg!(any(test, feature = "test-support")) {
22583 return;
22584 }
22585
22586 let Some(project) = &self.project else { return };
22587
22588 // If None, we are in a file without an extension
22589 let file = self
22590 .buffer
22591 .read(cx)
22592 .as_singleton()
22593 .and_then(|b| b.read(cx).file());
22594 let file_extension = file_extension.or(file
22595 .as_ref()
22596 .and_then(|file| Path::new(file.file_name(cx)).extension())
22597 .and_then(|e| e.to_str())
22598 .map(|a| a.to_string()));
22599
22600 let vim_mode_enabled = self.is_vim_mode_enabled(cx);
22601 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22602 let copilot_enabled = edit_predictions_provider
22603 == language::language_settings::EditPredictionProvider::Copilot;
22604 let copilot_enabled_for_language = self
22605 .buffer
22606 .read(cx)
22607 .language_settings(cx)
22608 .show_edit_predictions;
22609
22610 let project = project.read(cx);
22611 let event_type = reported_event.event_type();
22612
22613 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22614 telemetry::event!(
22615 event_type,
22616 type = if auto_saved {"autosave"} else {"manual"},
22617 file_extension,
22618 vim_mode_enabled,
22619 copilot_enabled,
22620 copilot_enabled_for_language,
22621 edit_predictions_provider,
22622 is_via_ssh = project.is_via_remote_server(),
22623 );
22624 } else {
22625 telemetry::event!(
22626 event_type,
22627 file_extension,
22628 vim_mode_enabled,
22629 copilot_enabled,
22630 copilot_enabled_for_language,
22631 edit_predictions_provider,
22632 is_via_ssh = project.is_via_remote_server(),
22633 );
22634 };
22635 }
22636
22637 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22638 /// with each line being an array of {text, highlight} objects.
22639 fn copy_highlight_json(
22640 &mut self,
22641 _: &CopyHighlightJson,
22642 window: &mut Window,
22643 cx: &mut Context<Self>,
22644 ) {
22645 #[derive(Serialize)]
22646 struct Chunk<'a> {
22647 text: String,
22648 highlight: Option<&'a str>,
22649 }
22650
22651 let snapshot = self.buffer.read(cx).snapshot(cx);
22652 let range = self
22653 .selected_text_range(false, window, cx)
22654 .and_then(|selection| {
22655 if selection.range.is_empty() {
22656 None
22657 } else {
22658 Some(
22659 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22660 selection.range.start,
22661 )))
22662 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22663 selection.range.end,
22664 ))),
22665 )
22666 }
22667 })
22668 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22669
22670 let chunks = snapshot.chunks(range, true);
22671 let mut lines = Vec::new();
22672 let mut line: VecDeque<Chunk> = VecDeque::new();
22673
22674 let Some(style) = self.style.as_ref() else {
22675 return;
22676 };
22677
22678 for chunk in chunks {
22679 let highlight = chunk
22680 .syntax_highlight_id
22681 .and_then(|id| id.name(&style.syntax));
22682 let mut chunk_lines = chunk.text.split('\n').peekable();
22683 while let Some(text) = chunk_lines.next() {
22684 let mut merged_with_last_token = false;
22685 if let Some(last_token) = line.back_mut()
22686 && last_token.highlight == highlight
22687 {
22688 last_token.text.push_str(text);
22689 merged_with_last_token = true;
22690 }
22691
22692 if !merged_with_last_token {
22693 line.push_back(Chunk {
22694 text: text.into(),
22695 highlight,
22696 });
22697 }
22698
22699 if chunk_lines.peek().is_some() {
22700 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22701 line.pop_front();
22702 }
22703 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22704 line.pop_back();
22705 }
22706
22707 lines.push(mem::take(&mut line));
22708 }
22709 }
22710 }
22711
22712 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22713 return;
22714 };
22715 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22716 }
22717
22718 pub fn open_context_menu(
22719 &mut self,
22720 _: &OpenContextMenu,
22721 window: &mut Window,
22722 cx: &mut Context<Self>,
22723 ) {
22724 self.request_autoscroll(Autoscroll::newest(), cx);
22725 let position = self
22726 .selections
22727 .newest_display(&self.display_snapshot(cx))
22728 .start;
22729 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22730 }
22731
22732 pub fn replay_insert_event(
22733 &mut self,
22734 text: &str,
22735 relative_utf16_range: Option<Range<isize>>,
22736 window: &mut Window,
22737 cx: &mut Context<Self>,
22738 ) {
22739 if !self.input_enabled {
22740 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22741 return;
22742 }
22743 if let Some(relative_utf16_range) = relative_utf16_range {
22744 let selections = self
22745 .selections
22746 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22747 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22748 let new_ranges = selections.into_iter().map(|range| {
22749 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22750 range
22751 .head()
22752 .0
22753 .0
22754 .saturating_add_signed(relative_utf16_range.start),
22755 ));
22756 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22757 range
22758 .head()
22759 .0
22760 .0
22761 .saturating_add_signed(relative_utf16_range.end),
22762 ));
22763 start..end
22764 });
22765 s.select_ranges(new_ranges);
22766 });
22767 }
22768
22769 self.handle_input(text, window, cx);
22770 }
22771
22772 pub fn is_focused(&self, window: &Window) -> bool {
22773 self.focus_handle.is_focused(window)
22774 }
22775
22776 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22777 cx.emit(EditorEvent::Focused);
22778
22779 if let Some(descendant) = self
22780 .last_focused_descendant
22781 .take()
22782 .and_then(|descendant| descendant.upgrade())
22783 {
22784 window.focus(&descendant);
22785 } else {
22786 if let Some(blame) = self.blame.as_ref() {
22787 blame.update(cx, GitBlame::focus)
22788 }
22789
22790 self.blink_manager.update(cx, BlinkManager::enable);
22791 self.show_cursor_names(window, cx);
22792 self.buffer.update(cx, |buffer, cx| {
22793 buffer.finalize_last_transaction(cx);
22794 if self.leader_id.is_none() {
22795 buffer.set_active_selections(
22796 &self.selections.disjoint_anchors_arc(),
22797 self.selections.line_mode(),
22798 self.cursor_shape,
22799 cx,
22800 );
22801 }
22802 });
22803
22804 if let Some(position_map) = self.last_position_map.clone() {
22805 EditorElement::mouse_moved(
22806 self,
22807 &MouseMoveEvent {
22808 position: window.mouse_position(),
22809 pressed_button: None,
22810 modifiers: window.modifiers(),
22811 },
22812 &position_map,
22813 window,
22814 cx,
22815 );
22816 }
22817 }
22818 }
22819
22820 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22821 cx.emit(EditorEvent::FocusedIn)
22822 }
22823
22824 fn handle_focus_out(
22825 &mut self,
22826 event: FocusOutEvent,
22827 _window: &mut Window,
22828 cx: &mut Context<Self>,
22829 ) {
22830 if event.blurred != self.focus_handle {
22831 self.last_focused_descendant = Some(event.blurred);
22832 }
22833 self.selection_drag_state = SelectionDragState::None;
22834 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22835 }
22836
22837 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22838 self.blink_manager.update(cx, BlinkManager::disable);
22839 self.buffer
22840 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22841
22842 if let Some(blame) = self.blame.as_ref() {
22843 blame.update(cx, GitBlame::blur)
22844 }
22845 if !self.hover_state.focused(window, cx) {
22846 hide_hover(self, cx);
22847 }
22848 if !self
22849 .context_menu
22850 .borrow()
22851 .as_ref()
22852 .is_some_and(|context_menu| context_menu.focused(window, cx))
22853 {
22854 self.hide_context_menu(window, cx);
22855 }
22856 self.take_active_edit_prediction(cx);
22857 cx.emit(EditorEvent::Blurred);
22858 cx.notify();
22859 }
22860
22861 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22862 let mut pending: String = window
22863 .pending_input_keystrokes()
22864 .into_iter()
22865 .flatten()
22866 .filter_map(|keystroke| keystroke.key_char.clone())
22867 .collect();
22868
22869 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22870 pending = "".to_string();
22871 }
22872
22873 let existing_pending = self
22874 .text_highlights::<PendingInput>(cx)
22875 .map(|(_, ranges)| ranges.to_vec());
22876 if existing_pending.is_none() && pending.is_empty() {
22877 return;
22878 }
22879 let transaction =
22880 self.transact(window, cx, |this, window, cx| {
22881 let selections = this
22882 .selections
22883 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22884 let edits = selections
22885 .iter()
22886 .map(|selection| (selection.end..selection.end, pending.clone()));
22887 this.edit(edits, cx);
22888 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22889 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22890 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22891 }));
22892 });
22893 if let Some(existing_ranges) = existing_pending {
22894 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22895 this.edit(edits, cx);
22896 }
22897 });
22898
22899 let snapshot = self.snapshot(window, cx);
22900 let ranges = self
22901 .selections
22902 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22903 .into_iter()
22904 .map(|selection| {
22905 snapshot.buffer_snapshot().anchor_after(selection.end)
22906 ..snapshot
22907 .buffer_snapshot()
22908 .anchor_before(selection.end + pending.len())
22909 })
22910 .collect();
22911
22912 if pending.is_empty() {
22913 self.clear_highlights::<PendingInput>(cx);
22914 } else {
22915 self.highlight_text::<PendingInput>(
22916 ranges,
22917 HighlightStyle {
22918 underline: Some(UnderlineStyle {
22919 thickness: px(1.),
22920 color: None,
22921 wavy: false,
22922 }),
22923 ..Default::default()
22924 },
22925 cx,
22926 );
22927 }
22928
22929 self.ime_transaction = self.ime_transaction.or(transaction);
22930 if let Some(transaction) = self.ime_transaction {
22931 self.buffer.update(cx, |buffer, cx| {
22932 buffer.group_until_transaction(transaction, cx);
22933 });
22934 }
22935
22936 if self.text_highlights::<PendingInput>(cx).is_none() {
22937 self.ime_transaction.take();
22938 }
22939 }
22940
22941 pub fn register_action_renderer(
22942 &mut self,
22943 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22944 ) -> Subscription {
22945 let id = self.next_editor_action_id.post_inc();
22946 self.editor_actions
22947 .borrow_mut()
22948 .insert(id, Box::new(listener));
22949
22950 let editor_actions = self.editor_actions.clone();
22951 Subscription::new(move || {
22952 editor_actions.borrow_mut().remove(&id);
22953 })
22954 }
22955
22956 pub fn register_action<A: Action>(
22957 &mut self,
22958 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22959 ) -> Subscription {
22960 let id = self.next_editor_action_id.post_inc();
22961 let listener = Arc::new(listener);
22962 self.editor_actions.borrow_mut().insert(
22963 id,
22964 Box::new(move |_, window, _| {
22965 let listener = listener.clone();
22966 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22967 let action = action.downcast_ref().unwrap();
22968 if phase == DispatchPhase::Bubble {
22969 listener(action, window, cx)
22970 }
22971 })
22972 }),
22973 );
22974
22975 let editor_actions = self.editor_actions.clone();
22976 Subscription::new(move || {
22977 editor_actions.borrow_mut().remove(&id);
22978 })
22979 }
22980
22981 pub fn file_header_size(&self) -> u32 {
22982 FILE_HEADER_HEIGHT
22983 }
22984
22985 pub fn restore(
22986 &mut self,
22987 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22988 window: &mut Window,
22989 cx: &mut Context<Self>,
22990 ) {
22991 self.buffer().update(cx, |multi_buffer, cx| {
22992 for (buffer_id, changes) in revert_changes {
22993 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22994 buffer.update(cx, |buffer, cx| {
22995 buffer.edit(
22996 changes
22997 .into_iter()
22998 .map(|(range, text)| (range, text.to_string())),
22999 None,
23000 cx,
23001 );
23002 });
23003 }
23004 }
23005 });
23006 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23007 selections.refresh()
23008 });
23009 }
23010
23011 pub fn to_pixel_point(
23012 &mut self,
23013 source: multi_buffer::Anchor,
23014 editor_snapshot: &EditorSnapshot,
23015 window: &mut Window,
23016 cx: &App,
23017 ) -> Option<gpui::Point<Pixels>> {
23018 let source_point = source.to_display_point(editor_snapshot);
23019 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23020 }
23021
23022 pub fn display_to_pixel_point(
23023 &mut self,
23024 source: DisplayPoint,
23025 editor_snapshot: &EditorSnapshot,
23026 window: &mut Window,
23027 cx: &App,
23028 ) -> Option<gpui::Point<Pixels>> {
23029 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23030 let text_layout_details = self.text_layout_details(window);
23031 let scroll_top = text_layout_details
23032 .scroll_anchor
23033 .scroll_position(editor_snapshot)
23034 .y;
23035
23036 if source.row().as_f64() < scroll_top.floor() {
23037 return None;
23038 }
23039 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23040 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23041 Some(gpui::Point::new(source_x, source_y))
23042 }
23043
23044 pub fn has_visible_completions_menu(&self) -> bool {
23045 !self.edit_prediction_preview_is_active()
23046 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23047 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23048 })
23049 }
23050
23051 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23052 if self.mode.is_minimap() {
23053 return;
23054 }
23055 self.addons
23056 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23057 }
23058
23059 pub fn unregister_addon<T: Addon>(&mut self) {
23060 self.addons.remove(&std::any::TypeId::of::<T>());
23061 }
23062
23063 pub fn addon<T: Addon>(&self) -> Option<&T> {
23064 let type_id = std::any::TypeId::of::<T>();
23065 self.addons
23066 .get(&type_id)
23067 .and_then(|item| item.to_any().downcast_ref::<T>())
23068 }
23069
23070 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23071 let type_id = std::any::TypeId::of::<T>();
23072 self.addons
23073 .get_mut(&type_id)
23074 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23075 }
23076
23077 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23078 let text_layout_details = self.text_layout_details(window);
23079 let style = &text_layout_details.editor_style;
23080 let font_id = window.text_system().resolve_font(&style.text.font());
23081 let font_size = style.text.font_size.to_pixels(window.rem_size());
23082 let line_height = style.text.line_height_in_pixels(window.rem_size());
23083 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23084 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23085
23086 CharacterDimensions {
23087 em_width,
23088 em_advance,
23089 line_height,
23090 }
23091 }
23092
23093 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23094 self.load_diff_task.clone()
23095 }
23096
23097 fn read_metadata_from_db(
23098 &mut self,
23099 item_id: u64,
23100 workspace_id: WorkspaceId,
23101 window: &mut Window,
23102 cx: &mut Context<Editor>,
23103 ) {
23104 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23105 && !self.mode.is_minimap()
23106 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
23107 {
23108 let buffer_snapshot = OnceCell::new();
23109
23110 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23111 && !folds.is_empty()
23112 {
23113 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23114 self.fold_ranges(
23115 folds
23116 .into_iter()
23117 .map(|(start, end)| {
23118 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23119 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23120 })
23121 .collect(),
23122 false,
23123 window,
23124 cx,
23125 );
23126 }
23127
23128 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23129 && !selections.is_empty()
23130 {
23131 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23132 // skip adding the initial selection to selection history
23133 self.selection_history.mode = SelectionHistoryMode::Skipping;
23134 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23135 s.select_ranges(selections.into_iter().map(|(start, end)| {
23136 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23137 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23138 }));
23139 });
23140 self.selection_history.mode = SelectionHistoryMode::Normal;
23141 };
23142 }
23143
23144 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23145 }
23146
23147 fn update_lsp_data(
23148 &mut self,
23149 for_buffer: Option<BufferId>,
23150 window: &mut Window,
23151 cx: &mut Context<'_, Self>,
23152 ) {
23153 self.pull_diagnostics(for_buffer, window, cx);
23154 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23155 }
23156
23157 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23158 if self.ignore_lsp_data() {
23159 return;
23160 }
23161 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23162 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23163 }
23164 }
23165
23166 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23167 if self.ignore_lsp_data() {
23168 return;
23169 }
23170
23171 if !self.registered_buffers.contains_key(&buffer_id)
23172 && let Some(project) = self.project.as_ref()
23173 {
23174 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23175 project.update(cx, |project, cx| {
23176 self.registered_buffers.insert(
23177 buffer_id,
23178 project.register_buffer_with_language_servers(&buffer, cx),
23179 );
23180 });
23181 } else {
23182 self.registered_buffers.remove(&buffer_id);
23183 }
23184 }
23185 }
23186
23187 fn ignore_lsp_data(&self) -> bool {
23188 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23189 // skip any LSP updates for it.
23190 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23191 }
23192
23193 fn create_style(&self, cx: &App) -> EditorStyle {
23194 let settings = ThemeSettings::get_global(cx);
23195
23196 let mut text_style = match self.mode {
23197 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23198 color: cx.theme().colors().editor_foreground,
23199 font_family: settings.ui_font.family.clone(),
23200 font_features: settings.ui_font.features.clone(),
23201 font_fallbacks: settings.ui_font.fallbacks.clone(),
23202 font_size: rems(0.875).into(),
23203 font_weight: settings.ui_font.weight,
23204 line_height: relative(settings.buffer_line_height.value()),
23205 ..Default::default()
23206 },
23207 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23208 color: cx.theme().colors().editor_foreground,
23209 font_family: settings.buffer_font.family.clone(),
23210 font_features: settings.buffer_font.features.clone(),
23211 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23212 font_size: settings.buffer_font_size(cx).into(),
23213 font_weight: settings.buffer_font.weight,
23214 line_height: relative(settings.buffer_line_height.value()),
23215 ..Default::default()
23216 },
23217 };
23218 if let Some(text_style_refinement) = &self.text_style_refinement {
23219 text_style.refine(text_style_refinement)
23220 }
23221
23222 let background = match self.mode {
23223 EditorMode::SingleLine => cx.theme().system().transparent,
23224 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23225 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23226 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23227 };
23228
23229 EditorStyle {
23230 background,
23231 border: cx.theme().colors().border,
23232 local_player: cx.theme().players().local(),
23233 text: text_style,
23234 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23235 syntax: cx.theme().syntax().clone(),
23236 status: cx.theme().status().clone(),
23237 inlay_hints_style: make_inlay_hints_style(cx),
23238 edit_prediction_styles: make_suggestion_styles(cx),
23239 unnecessary_code_fade: settings.unnecessary_code_fade,
23240 show_underlines: self.diagnostics_enabled(),
23241 }
23242 }
23243
23244 /// Returns the value of the `vim_mode` setting, defaulting `false` if the
23245 /// setting is not set.
23246 pub(crate) fn is_vim_mode_enabled(&self, cx: &App) -> bool {
23247 VimModeSetting::try_get(cx)
23248 .map(|vim_mode| vim_mode.0)
23249 .unwrap_or(false)
23250 }
23251}
23252
23253fn edit_for_markdown_paste<'a>(
23254 buffer: &MultiBufferSnapshot,
23255 range: Range<MultiBufferOffset>,
23256 to_insert: &'a str,
23257 url: Option<url::Url>,
23258) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23259 if url.is_none() {
23260 return (range, Cow::Borrowed(to_insert));
23261 };
23262
23263 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23264
23265 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23266 Cow::Borrowed(to_insert)
23267 } else {
23268 Cow::Owned(format!("[{old_text}]({to_insert})"))
23269 };
23270 (range, new_text)
23271}
23272
23273fn process_completion_for_edit(
23274 completion: &Completion,
23275 intent: CompletionIntent,
23276 buffer: &Entity<Buffer>,
23277 cursor_position: &text::Anchor,
23278 cx: &mut Context<Editor>,
23279) -> CompletionEdit {
23280 let buffer = buffer.read(cx);
23281 let buffer_snapshot = buffer.snapshot();
23282 let (snippet, new_text) = if completion.is_snippet() {
23283 let mut snippet_source = completion.new_text.clone();
23284 // Workaround for typescript language server issues so that methods don't expand within
23285 // strings and functions with type expressions. The previous point is used because the query
23286 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23287 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23288 let previous_point = if previous_point.column > 0 {
23289 cursor_position.to_previous_offset(&buffer_snapshot)
23290 } else {
23291 cursor_position.to_offset(&buffer_snapshot)
23292 };
23293 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23294 && scope.prefers_label_for_snippet_in_completion()
23295 && let Some(label) = completion.label()
23296 && matches!(
23297 completion.kind(),
23298 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23299 )
23300 {
23301 snippet_source = label;
23302 }
23303 match Snippet::parse(&snippet_source).log_err() {
23304 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23305 None => (None, completion.new_text.clone()),
23306 }
23307 } else {
23308 (None, completion.new_text.clone())
23309 };
23310
23311 let mut range_to_replace = {
23312 let replace_range = &completion.replace_range;
23313 if let CompletionSource::Lsp {
23314 insert_range: Some(insert_range),
23315 ..
23316 } = &completion.source
23317 {
23318 debug_assert_eq!(
23319 insert_range.start, replace_range.start,
23320 "insert_range and replace_range should start at the same position"
23321 );
23322 debug_assert!(
23323 insert_range
23324 .start
23325 .cmp(cursor_position, &buffer_snapshot)
23326 .is_le(),
23327 "insert_range should start before or at cursor position"
23328 );
23329 debug_assert!(
23330 replace_range
23331 .start
23332 .cmp(cursor_position, &buffer_snapshot)
23333 .is_le(),
23334 "replace_range should start before or at cursor position"
23335 );
23336
23337 let should_replace = match intent {
23338 CompletionIntent::CompleteWithInsert => false,
23339 CompletionIntent::CompleteWithReplace => true,
23340 CompletionIntent::Complete | CompletionIntent::Compose => {
23341 let insert_mode =
23342 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23343 .completions
23344 .lsp_insert_mode;
23345 match insert_mode {
23346 LspInsertMode::Insert => false,
23347 LspInsertMode::Replace => true,
23348 LspInsertMode::ReplaceSubsequence => {
23349 let mut text_to_replace = buffer.chars_for_range(
23350 buffer.anchor_before(replace_range.start)
23351 ..buffer.anchor_after(replace_range.end),
23352 );
23353 let mut current_needle = text_to_replace.next();
23354 for haystack_ch in completion.label.text.chars() {
23355 if let Some(needle_ch) = current_needle
23356 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23357 {
23358 current_needle = text_to_replace.next();
23359 }
23360 }
23361 current_needle.is_none()
23362 }
23363 LspInsertMode::ReplaceSuffix => {
23364 if replace_range
23365 .end
23366 .cmp(cursor_position, &buffer_snapshot)
23367 .is_gt()
23368 {
23369 let range_after_cursor = *cursor_position..replace_range.end;
23370 let text_after_cursor = buffer
23371 .text_for_range(
23372 buffer.anchor_before(range_after_cursor.start)
23373 ..buffer.anchor_after(range_after_cursor.end),
23374 )
23375 .collect::<String>()
23376 .to_ascii_lowercase();
23377 completion
23378 .label
23379 .text
23380 .to_ascii_lowercase()
23381 .ends_with(&text_after_cursor)
23382 } else {
23383 true
23384 }
23385 }
23386 }
23387 }
23388 };
23389
23390 if should_replace {
23391 replace_range.clone()
23392 } else {
23393 insert_range.clone()
23394 }
23395 } else {
23396 replace_range.clone()
23397 }
23398 };
23399
23400 if range_to_replace
23401 .end
23402 .cmp(cursor_position, &buffer_snapshot)
23403 .is_lt()
23404 {
23405 range_to_replace.end = *cursor_position;
23406 }
23407
23408 let replace_range = range_to_replace.to_offset(buffer);
23409 CompletionEdit {
23410 new_text,
23411 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23412 snippet,
23413 }
23414}
23415
23416struct CompletionEdit {
23417 new_text: String,
23418 replace_range: Range<BufferOffset>,
23419 snippet: Option<Snippet>,
23420}
23421
23422fn insert_extra_newline_brackets(
23423 buffer: &MultiBufferSnapshot,
23424 range: Range<MultiBufferOffset>,
23425 language: &language::LanguageScope,
23426) -> bool {
23427 let leading_whitespace_len = buffer
23428 .reversed_chars_at(range.start)
23429 .take_while(|c| c.is_whitespace() && *c != '\n')
23430 .map(|c| c.len_utf8())
23431 .sum::<usize>();
23432 let trailing_whitespace_len = buffer
23433 .chars_at(range.end)
23434 .take_while(|c| c.is_whitespace() && *c != '\n')
23435 .map(|c| c.len_utf8())
23436 .sum::<usize>();
23437 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23438
23439 language.brackets().any(|(pair, enabled)| {
23440 let pair_start = pair.start.trim_end();
23441 let pair_end = pair.end.trim_start();
23442
23443 enabled
23444 && pair.newline
23445 && buffer.contains_str_at(range.end, pair_end)
23446 && buffer.contains_str_at(
23447 range.start.saturating_sub_usize(pair_start.len()),
23448 pair_start,
23449 )
23450 })
23451}
23452
23453fn insert_extra_newline_tree_sitter(
23454 buffer: &MultiBufferSnapshot,
23455 range: Range<MultiBufferOffset>,
23456) -> bool {
23457 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23458 [(buffer, range, _)] => (*buffer, range.clone()),
23459 _ => return false,
23460 };
23461 let pair = {
23462 let mut result: Option<BracketMatch<usize>> = None;
23463
23464 for pair in buffer
23465 .all_bracket_ranges(range.start.0..range.end.0)
23466 .filter(move |pair| {
23467 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23468 })
23469 {
23470 let len = pair.close_range.end - pair.open_range.start;
23471
23472 if let Some(existing) = &result {
23473 let existing_len = existing.close_range.end - existing.open_range.start;
23474 if len > existing_len {
23475 continue;
23476 }
23477 }
23478
23479 result = Some(pair);
23480 }
23481
23482 result
23483 };
23484 let Some(pair) = pair else {
23485 return false;
23486 };
23487 pair.newline_only
23488 && buffer
23489 .chars_for_range(pair.open_range.end..range.start.0)
23490 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23491 .all(|c| c.is_whitespace() && c != '\n')
23492}
23493
23494fn update_uncommitted_diff_for_buffer(
23495 editor: Entity<Editor>,
23496 project: &Entity<Project>,
23497 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23498 buffer: Entity<MultiBuffer>,
23499 cx: &mut App,
23500) -> Task<()> {
23501 let mut tasks = Vec::new();
23502 project.update(cx, |project, cx| {
23503 for buffer in buffers {
23504 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23505 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23506 }
23507 }
23508 });
23509 cx.spawn(async move |cx| {
23510 let diffs = future::join_all(tasks).await;
23511 if editor
23512 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23513 .unwrap_or(false)
23514 {
23515 return;
23516 }
23517
23518 buffer
23519 .update(cx, |buffer, cx| {
23520 for diff in diffs.into_iter().flatten() {
23521 buffer.add_diff(diff, cx);
23522 }
23523 })
23524 .ok();
23525 })
23526}
23527
23528fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23529 let tab_size = tab_size.get() as usize;
23530 let mut width = offset;
23531
23532 for ch in text.chars() {
23533 width += if ch == '\t' {
23534 tab_size - (width % tab_size)
23535 } else {
23536 1
23537 };
23538 }
23539
23540 width - offset
23541}
23542
23543#[cfg(test)]
23544mod tests {
23545 use super::*;
23546
23547 #[test]
23548 fn test_string_size_with_expanded_tabs() {
23549 let nz = |val| NonZeroU32::new(val).unwrap();
23550 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23551 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23552 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23553 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23554 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23555 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23556 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23557 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23558 }
23559}
23560
23561/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23562struct WordBreakingTokenizer<'a> {
23563 input: &'a str,
23564}
23565
23566impl<'a> WordBreakingTokenizer<'a> {
23567 fn new(input: &'a str) -> Self {
23568 Self { input }
23569 }
23570}
23571
23572fn is_char_ideographic(ch: char) -> bool {
23573 use unicode_script::Script::*;
23574 use unicode_script::UnicodeScript;
23575 matches!(ch.script(), Han | Tangut | Yi)
23576}
23577
23578fn is_grapheme_ideographic(text: &str) -> bool {
23579 text.chars().any(is_char_ideographic)
23580}
23581
23582fn is_grapheme_whitespace(text: &str) -> bool {
23583 text.chars().any(|x| x.is_whitespace())
23584}
23585
23586fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23587 text.chars()
23588 .next()
23589 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23590}
23591
23592#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23593enum WordBreakToken<'a> {
23594 Word { token: &'a str, grapheme_len: usize },
23595 InlineWhitespace { token: &'a str, grapheme_len: usize },
23596 Newline,
23597}
23598
23599impl<'a> Iterator for WordBreakingTokenizer<'a> {
23600 /// Yields a span, the count of graphemes in the token, and whether it was
23601 /// whitespace. Note that it also breaks at word boundaries.
23602 type Item = WordBreakToken<'a>;
23603
23604 fn next(&mut self) -> Option<Self::Item> {
23605 use unicode_segmentation::UnicodeSegmentation;
23606 if self.input.is_empty() {
23607 return None;
23608 }
23609
23610 let mut iter = self.input.graphemes(true).peekable();
23611 let mut offset = 0;
23612 let mut grapheme_len = 0;
23613 if let Some(first_grapheme) = iter.next() {
23614 let is_newline = first_grapheme == "\n";
23615 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23616 offset += first_grapheme.len();
23617 grapheme_len += 1;
23618 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23619 if let Some(grapheme) = iter.peek().copied()
23620 && should_stay_with_preceding_ideograph(grapheme)
23621 {
23622 offset += grapheme.len();
23623 grapheme_len += 1;
23624 }
23625 } else {
23626 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23627 let mut next_word_bound = words.peek().copied();
23628 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23629 next_word_bound = words.next();
23630 }
23631 while let Some(grapheme) = iter.peek().copied() {
23632 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23633 break;
23634 };
23635 if is_grapheme_whitespace(grapheme) != is_whitespace
23636 || (grapheme == "\n") != is_newline
23637 {
23638 break;
23639 };
23640 offset += grapheme.len();
23641 grapheme_len += 1;
23642 iter.next();
23643 }
23644 }
23645 let token = &self.input[..offset];
23646 self.input = &self.input[offset..];
23647 if token == "\n" {
23648 Some(WordBreakToken::Newline)
23649 } else if is_whitespace {
23650 Some(WordBreakToken::InlineWhitespace {
23651 token,
23652 grapheme_len,
23653 })
23654 } else {
23655 Some(WordBreakToken::Word {
23656 token,
23657 grapheme_len,
23658 })
23659 }
23660 } else {
23661 None
23662 }
23663 }
23664}
23665
23666#[test]
23667fn test_word_breaking_tokenizer() {
23668 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23669 ("", &[]),
23670 (" ", &[whitespace(" ", 2)]),
23671 ("Ʒ", &[word("Ʒ", 1)]),
23672 ("Ǽ", &[word("Ǽ", 1)]),
23673 ("⋑", &[word("⋑", 1)]),
23674 ("⋑⋑", &[word("⋑⋑", 2)]),
23675 (
23676 "原理,进而",
23677 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23678 ),
23679 (
23680 "hello world",
23681 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23682 ),
23683 (
23684 "hello, world",
23685 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23686 ),
23687 (
23688 " hello world",
23689 &[
23690 whitespace(" ", 2),
23691 word("hello", 5),
23692 whitespace(" ", 1),
23693 word("world", 5),
23694 ],
23695 ),
23696 (
23697 "这是什么 \n 钢笔",
23698 &[
23699 word("这", 1),
23700 word("是", 1),
23701 word("什", 1),
23702 word("么", 1),
23703 whitespace(" ", 1),
23704 newline(),
23705 whitespace(" ", 1),
23706 word("钢", 1),
23707 word("笔", 1),
23708 ],
23709 ),
23710 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23711 ];
23712
23713 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23714 WordBreakToken::Word {
23715 token,
23716 grapheme_len,
23717 }
23718 }
23719
23720 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23721 WordBreakToken::InlineWhitespace {
23722 token,
23723 grapheme_len,
23724 }
23725 }
23726
23727 fn newline() -> WordBreakToken<'static> {
23728 WordBreakToken::Newline
23729 }
23730
23731 for (input, result) in tests {
23732 assert_eq!(
23733 WordBreakingTokenizer::new(input)
23734 .collect::<Vec<_>>()
23735 .as_slice(),
23736 *result,
23737 );
23738 }
23739}
23740
23741fn wrap_with_prefix(
23742 first_line_prefix: String,
23743 subsequent_lines_prefix: String,
23744 unwrapped_text: String,
23745 wrap_column: usize,
23746 tab_size: NonZeroU32,
23747 preserve_existing_whitespace: bool,
23748) -> String {
23749 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23750 let subsequent_lines_prefix_len =
23751 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23752 let mut wrapped_text = String::new();
23753 let mut current_line = first_line_prefix;
23754 let mut is_first_line = true;
23755
23756 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23757 let mut current_line_len = first_line_prefix_len;
23758 let mut in_whitespace = false;
23759 for token in tokenizer {
23760 let have_preceding_whitespace = in_whitespace;
23761 match token {
23762 WordBreakToken::Word {
23763 token,
23764 grapheme_len,
23765 } => {
23766 in_whitespace = false;
23767 let current_prefix_len = if is_first_line {
23768 first_line_prefix_len
23769 } else {
23770 subsequent_lines_prefix_len
23771 };
23772 if current_line_len + grapheme_len > wrap_column
23773 && current_line_len != current_prefix_len
23774 {
23775 wrapped_text.push_str(current_line.trim_end());
23776 wrapped_text.push('\n');
23777 is_first_line = false;
23778 current_line = subsequent_lines_prefix.clone();
23779 current_line_len = subsequent_lines_prefix_len;
23780 }
23781 current_line.push_str(token);
23782 current_line_len += grapheme_len;
23783 }
23784 WordBreakToken::InlineWhitespace {
23785 mut token,
23786 mut grapheme_len,
23787 } => {
23788 in_whitespace = true;
23789 if have_preceding_whitespace && !preserve_existing_whitespace {
23790 continue;
23791 }
23792 if !preserve_existing_whitespace {
23793 // Keep a single whitespace grapheme as-is
23794 if let Some(first) =
23795 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23796 {
23797 token = first;
23798 } else {
23799 token = " ";
23800 }
23801 grapheme_len = 1;
23802 }
23803 let current_prefix_len = if is_first_line {
23804 first_line_prefix_len
23805 } else {
23806 subsequent_lines_prefix_len
23807 };
23808 if current_line_len + grapheme_len > wrap_column {
23809 wrapped_text.push_str(current_line.trim_end());
23810 wrapped_text.push('\n');
23811 is_first_line = false;
23812 current_line = subsequent_lines_prefix.clone();
23813 current_line_len = subsequent_lines_prefix_len;
23814 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23815 current_line.push_str(token);
23816 current_line_len += grapheme_len;
23817 }
23818 }
23819 WordBreakToken::Newline => {
23820 in_whitespace = true;
23821 let current_prefix_len = if is_first_line {
23822 first_line_prefix_len
23823 } else {
23824 subsequent_lines_prefix_len
23825 };
23826 if preserve_existing_whitespace {
23827 wrapped_text.push_str(current_line.trim_end());
23828 wrapped_text.push('\n');
23829 is_first_line = false;
23830 current_line = subsequent_lines_prefix.clone();
23831 current_line_len = subsequent_lines_prefix_len;
23832 } else if have_preceding_whitespace {
23833 continue;
23834 } else if current_line_len + 1 > wrap_column
23835 && current_line_len != current_prefix_len
23836 {
23837 wrapped_text.push_str(current_line.trim_end());
23838 wrapped_text.push('\n');
23839 is_first_line = false;
23840 current_line = subsequent_lines_prefix.clone();
23841 current_line_len = subsequent_lines_prefix_len;
23842 } else if current_line_len != current_prefix_len {
23843 current_line.push(' ');
23844 current_line_len += 1;
23845 }
23846 }
23847 }
23848 }
23849
23850 if !current_line.is_empty() {
23851 wrapped_text.push_str(¤t_line);
23852 }
23853 wrapped_text
23854}
23855
23856#[test]
23857fn test_wrap_with_prefix() {
23858 assert_eq!(
23859 wrap_with_prefix(
23860 "# ".to_string(),
23861 "# ".to_string(),
23862 "abcdefg".to_string(),
23863 4,
23864 NonZeroU32::new(4).unwrap(),
23865 false,
23866 ),
23867 "# abcdefg"
23868 );
23869 assert_eq!(
23870 wrap_with_prefix(
23871 "".to_string(),
23872 "".to_string(),
23873 "\thello world".to_string(),
23874 8,
23875 NonZeroU32::new(4).unwrap(),
23876 false,
23877 ),
23878 "hello\nworld"
23879 );
23880 assert_eq!(
23881 wrap_with_prefix(
23882 "// ".to_string(),
23883 "// ".to_string(),
23884 "xx \nyy zz aa bb cc".to_string(),
23885 12,
23886 NonZeroU32::new(4).unwrap(),
23887 false,
23888 ),
23889 "// xx yy zz\n// aa bb cc"
23890 );
23891 assert_eq!(
23892 wrap_with_prefix(
23893 String::new(),
23894 String::new(),
23895 "这是什么 \n 钢笔".to_string(),
23896 3,
23897 NonZeroU32::new(4).unwrap(),
23898 false,
23899 ),
23900 "这是什\n么 钢\n笔"
23901 );
23902 assert_eq!(
23903 wrap_with_prefix(
23904 String::new(),
23905 String::new(),
23906 format!("foo{}bar", '\u{2009}'), // thin space
23907 80,
23908 NonZeroU32::new(4).unwrap(),
23909 false,
23910 ),
23911 format!("foo{}bar", '\u{2009}')
23912 );
23913}
23914
23915pub trait CollaborationHub {
23916 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23917 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23918 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23919}
23920
23921impl CollaborationHub for Entity<Project> {
23922 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23923 self.read(cx).collaborators()
23924 }
23925
23926 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23927 self.read(cx).user_store().read(cx).participant_indices()
23928 }
23929
23930 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23931 let this = self.read(cx);
23932 let user_ids = this.collaborators().values().map(|c| c.user_id);
23933 this.user_store().read(cx).participant_names(user_ids, cx)
23934 }
23935}
23936
23937pub trait SemanticsProvider {
23938 fn hover(
23939 &self,
23940 buffer: &Entity<Buffer>,
23941 position: text::Anchor,
23942 cx: &mut App,
23943 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23944
23945 fn inline_values(
23946 &self,
23947 buffer_handle: Entity<Buffer>,
23948 range: Range<text::Anchor>,
23949 cx: &mut App,
23950 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23951
23952 fn applicable_inlay_chunks(
23953 &self,
23954 buffer: &Entity<Buffer>,
23955 ranges: &[Range<text::Anchor>],
23956 cx: &mut App,
23957 ) -> Vec<Range<BufferRow>>;
23958
23959 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23960
23961 fn inlay_hints(
23962 &self,
23963 invalidate: InvalidationStrategy,
23964 buffer: Entity<Buffer>,
23965 ranges: Vec<Range<text::Anchor>>,
23966 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23967 cx: &mut App,
23968 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23969
23970 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23971
23972 fn document_highlights(
23973 &self,
23974 buffer: &Entity<Buffer>,
23975 position: text::Anchor,
23976 cx: &mut App,
23977 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23978
23979 fn definitions(
23980 &self,
23981 buffer: &Entity<Buffer>,
23982 position: text::Anchor,
23983 kind: GotoDefinitionKind,
23984 cx: &mut App,
23985 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23986
23987 fn range_for_rename(
23988 &self,
23989 buffer: &Entity<Buffer>,
23990 position: text::Anchor,
23991 cx: &mut App,
23992 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23993
23994 fn perform_rename(
23995 &self,
23996 buffer: &Entity<Buffer>,
23997 position: text::Anchor,
23998 new_name: String,
23999 cx: &mut App,
24000 ) -> Option<Task<Result<ProjectTransaction>>>;
24001}
24002
24003pub trait CompletionProvider {
24004 fn completions(
24005 &self,
24006 excerpt_id: ExcerptId,
24007 buffer: &Entity<Buffer>,
24008 buffer_position: text::Anchor,
24009 trigger: CompletionContext,
24010 window: &mut Window,
24011 cx: &mut Context<Editor>,
24012 ) -> Task<Result<Vec<CompletionResponse>>>;
24013
24014 fn resolve_completions(
24015 &self,
24016 _buffer: Entity<Buffer>,
24017 _completion_indices: Vec<usize>,
24018 _completions: Rc<RefCell<Box<[Completion]>>>,
24019 _cx: &mut Context<Editor>,
24020 ) -> Task<Result<bool>> {
24021 Task::ready(Ok(false))
24022 }
24023
24024 fn apply_additional_edits_for_completion(
24025 &self,
24026 _buffer: Entity<Buffer>,
24027 _completions: Rc<RefCell<Box<[Completion]>>>,
24028 _completion_index: usize,
24029 _push_to_history: bool,
24030 _cx: &mut Context<Editor>,
24031 ) -> Task<Result<Option<language::Transaction>>> {
24032 Task::ready(Ok(None))
24033 }
24034
24035 fn is_completion_trigger(
24036 &self,
24037 buffer: &Entity<Buffer>,
24038 position: language::Anchor,
24039 text: &str,
24040 trigger_in_words: bool,
24041 cx: &mut Context<Editor>,
24042 ) -> bool;
24043
24044 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24045
24046 fn sort_completions(&self) -> bool {
24047 true
24048 }
24049
24050 fn filter_completions(&self) -> bool {
24051 true
24052 }
24053
24054 fn show_snippets(&self) -> bool {
24055 false
24056 }
24057}
24058
24059pub trait CodeActionProvider {
24060 fn id(&self) -> Arc<str>;
24061
24062 fn code_actions(
24063 &self,
24064 buffer: &Entity<Buffer>,
24065 range: Range<text::Anchor>,
24066 window: &mut Window,
24067 cx: &mut App,
24068 ) -> Task<Result<Vec<CodeAction>>>;
24069
24070 fn apply_code_action(
24071 &self,
24072 buffer_handle: Entity<Buffer>,
24073 action: CodeAction,
24074 excerpt_id: ExcerptId,
24075 push_to_history: bool,
24076 window: &mut Window,
24077 cx: &mut App,
24078 ) -> Task<Result<ProjectTransaction>>;
24079}
24080
24081impl CodeActionProvider for Entity<Project> {
24082 fn id(&self) -> Arc<str> {
24083 "project".into()
24084 }
24085
24086 fn code_actions(
24087 &self,
24088 buffer: &Entity<Buffer>,
24089 range: Range<text::Anchor>,
24090 _window: &mut Window,
24091 cx: &mut App,
24092 ) -> Task<Result<Vec<CodeAction>>> {
24093 self.update(cx, |project, cx| {
24094 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24095 let code_actions = project.code_actions(buffer, range, None, cx);
24096 cx.background_spawn(async move {
24097 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24098 Ok(code_lens_actions
24099 .context("code lens fetch")?
24100 .into_iter()
24101 .flatten()
24102 .chain(
24103 code_actions
24104 .context("code action fetch")?
24105 .into_iter()
24106 .flatten(),
24107 )
24108 .collect())
24109 })
24110 })
24111 }
24112
24113 fn apply_code_action(
24114 &self,
24115 buffer_handle: Entity<Buffer>,
24116 action: CodeAction,
24117 _excerpt_id: ExcerptId,
24118 push_to_history: bool,
24119 _window: &mut Window,
24120 cx: &mut App,
24121 ) -> Task<Result<ProjectTransaction>> {
24122 self.update(cx, |project, cx| {
24123 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24124 })
24125 }
24126}
24127
24128fn snippet_completions(
24129 project: &Project,
24130 buffer: &Entity<Buffer>,
24131 buffer_anchor: text::Anchor,
24132 classifier: CharClassifier,
24133 cx: &mut App,
24134) -> Task<Result<CompletionResponse>> {
24135 let languages = buffer.read(cx).languages_at(buffer_anchor);
24136 let snippet_store = project.snippets().read(cx);
24137
24138 let scopes: Vec<_> = languages
24139 .iter()
24140 .filter_map(|language| {
24141 let language_name = language.lsp_id();
24142 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24143
24144 if snippets.is_empty() {
24145 None
24146 } else {
24147 Some((language.default_scope(), snippets))
24148 }
24149 })
24150 .collect();
24151
24152 if scopes.is_empty() {
24153 return Task::ready(Ok(CompletionResponse {
24154 completions: vec![],
24155 display_options: CompletionDisplayOptions::default(),
24156 is_incomplete: false,
24157 }));
24158 }
24159
24160 let snapshot = buffer.read(cx).text_snapshot();
24161 let executor = cx.background_executor().clone();
24162
24163 cx.background_spawn(async move {
24164 let is_word_char = |c| classifier.is_word(c);
24165
24166 let mut is_incomplete = false;
24167 let mut completions: Vec<Completion> = Vec::new();
24168
24169 const MAX_PREFIX_LEN: usize = 128;
24170 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24171 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24172 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24173
24174 let max_buffer_window: String = snapshot
24175 .text_for_range(window_start..buffer_offset)
24176 .collect();
24177
24178 if max_buffer_window.is_empty() {
24179 return Ok(CompletionResponse {
24180 completions: vec![],
24181 display_options: CompletionDisplayOptions::default(),
24182 is_incomplete: true,
24183 });
24184 }
24185
24186 for (_scope, snippets) in scopes.into_iter() {
24187 // Sort snippets by word count to match longer snippet prefixes first.
24188 let mut sorted_snippet_candidates = snippets
24189 .iter()
24190 .enumerate()
24191 .flat_map(|(snippet_ix, snippet)| {
24192 snippet
24193 .prefix
24194 .iter()
24195 .enumerate()
24196 .map(move |(prefix_ix, prefix)| {
24197 let word_count =
24198 snippet_candidate_suffixes(prefix, is_word_char).count();
24199 ((snippet_ix, prefix_ix), prefix, word_count)
24200 })
24201 })
24202 .collect_vec();
24203 sorted_snippet_candidates
24204 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24205
24206 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24207
24208 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24209 .take(
24210 sorted_snippet_candidates
24211 .first()
24212 .map(|(_, _, word_count)| *word_count)
24213 .unwrap_or_default(),
24214 )
24215 .collect_vec();
24216
24217 const MAX_RESULTS: usize = 100;
24218 // Each match also remembers how many characters from the buffer it consumed
24219 let mut matches: Vec<(StringMatch, usize)> = vec![];
24220
24221 let mut snippet_list_cutoff_index = 0;
24222 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24223 let word_count = buffer_index + 1;
24224 // Increase `snippet_list_cutoff_index` until we have all of the
24225 // snippets with sufficiently many words.
24226 while sorted_snippet_candidates
24227 .get(snippet_list_cutoff_index)
24228 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24229 *snippet_word_count >= word_count
24230 })
24231 {
24232 snippet_list_cutoff_index += 1;
24233 }
24234
24235 // Take only the candidates with at least `word_count` many words
24236 let snippet_candidates_at_word_len =
24237 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24238
24239 let candidates = snippet_candidates_at_word_len
24240 .iter()
24241 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24242 .enumerate() // index in `sorted_snippet_candidates`
24243 // First char must match
24244 .filter(|(_ix, prefix)| {
24245 itertools::equal(
24246 prefix
24247 .chars()
24248 .next()
24249 .into_iter()
24250 .flat_map(|c| c.to_lowercase()),
24251 buffer_window
24252 .chars()
24253 .next()
24254 .into_iter()
24255 .flat_map(|c| c.to_lowercase()),
24256 )
24257 })
24258 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24259 .collect::<Vec<StringMatchCandidate>>();
24260
24261 matches.extend(
24262 fuzzy::match_strings(
24263 &candidates,
24264 &buffer_window,
24265 buffer_window.chars().any(|c| c.is_uppercase()),
24266 true,
24267 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24268 &Default::default(),
24269 executor.clone(),
24270 )
24271 .await
24272 .into_iter()
24273 .map(|string_match| (string_match, buffer_window.len())),
24274 );
24275
24276 if matches.len() >= MAX_RESULTS {
24277 break;
24278 }
24279 }
24280
24281 let to_lsp = |point: &text::Anchor| {
24282 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24283 point_to_lsp(end)
24284 };
24285 let lsp_end = to_lsp(&buffer_anchor);
24286
24287 if matches.len() >= MAX_RESULTS {
24288 is_incomplete = true;
24289 }
24290
24291 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24292 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24293 sorted_snippet_candidates[string_match.candidate_id];
24294 let snippet = &snippets[snippet_index];
24295 let start = buffer_offset - buffer_window_len;
24296 let start = snapshot.anchor_before(start);
24297 let range = start..buffer_anchor;
24298 let lsp_start = to_lsp(&start);
24299 let lsp_range = lsp::Range {
24300 start: lsp_start,
24301 end: lsp_end,
24302 };
24303 Completion {
24304 replace_range: range,
24305 new_text: snippet.body.clone(),
24306 source: CompletionSource::Lsp {
24307 insert_range: None,
24308 server_id: LanguageServerId(usize::MAX),
24309 resolved: true,
24310 lsp_completion: Box::new(lsp::CompletionItem {
24311 label: snippet.prefix.first().unwrap().clone(),
24312 kind: Some(CompletionItemKind::SNIPPET),
24313 label_details: snippet.description.as_ref().map(|description| {
24314 lsp::CompletionItemLabelDetails {
24315 detail: Some(description.clone()),
24316 description: None,
24317 }
24318 }),
24319 insert_text_format: Some(InsertTextFormat::SNIPPET),
24320 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24321 lsp::InsertReplaceEdit {
24322 new_text: snippet.body.clone(),
24323 insert: lsp_range,
24324 replace: lsp_range,
24325 },
24326 )),
24327 filter_text: Some(snippet.body.clone()),
24328 sort_text: Some(char::MAX.to_string()),
24329 ..lsp::CompletionItem::default()
24330 }),
24331 lsp_defaults: None,
24332 },
24333 label: CodeLabel {
24334 text: matching_prefix.clone(),
24335 runs: Vec::new(),
24336 filter_range: 0..matching_prefix.len(),
24337 },
24338 icon_path: None,
24339 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24340 single_line: snippet.name.clone().into(),
24341 plain_text: snippet
24342 .description
24343 .clone()
24344 .map(|description| description.into()),
24345 }),
24346 insert_text_mode: None,
24347 confirm: None,
24348 match_start: Some(start),
24349 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24350 }
24351 }));
24352 }
24353
24354 Ok(CompletionResponse {
24355 completions,
24356 display_options: CompletionDisplayOptions::default(),
24357 is_incomplete,
24358 })
24359 })
24360}
24361
24362impl CompletionProvider for Entity<Project> {
24363 fn completions(
24364 &self,
24365 _excerpt_id: ExcerptId,
24366 buffer: &Entity<Buffer>,
24367 buffer_position: text::Anchor,
24368 options: CompletionContext,
24369 _window: &mut Window,
24370 cx: &mut Context<Editor>,
24371 ) -> Task<Result<Vec<CompletionResponse>>> {
24372 self.update(cx, |project, cx| {
24373 let task = project.completions(buffer, buffer_position, options, cx);
24374 cx.background_spawn(task)
24375 })
24376 }
24377
24378 fn resolve_completions(
24379 &self,
24380 buffer: Entity<Buffer>,
24381 completion_indices: Vec<usize>,
24382 completions: Rc<RefCell<Box<[Completion]>>>,
24383 cx: &mut Context<Editor>,
24384 ) -> Task<Result<bool>> {
24385 self.update(cx, |project, cx| {
24386 project.lsp_store().update(cx, |lsp_store, cx| {
24387 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24388 })
24389 })
24390 }
24391
24392 fn apply_additional_edits_for_completion(
24393 &self,
24394 buffer: Entity<Buffer>,
24395 completions: Rc<RefCell<Box<[Completion]>>>,
24396 completion_index: usize,
24397 push_to_history: bool,
24398 cx: &mut Context<Editor>,
24399 ) -> Task<Result<Option<language::Transaction>>> {
24400 self.update(cx, |project, cx| {
24401 project.lsp_store().update(cx, |lsp_store, cx| {
24402 lsp_store.apply_additional_edits_for_completion(
24403 buffer,
24404 completions,
24405 completion_index,
24406 push_to_history,
24407 cx,
24408 )
24409 })
24410 })
24411 }
24412
24413 fn is_completion_trigger(
24414 &self,
24415 buffer: &Entity<Buffer>,
24416 position: language::Anchor,
24417 text: &str,
24418 trigger_in_words: bool,
24419 cx: &mut Context<Editor>,
24420 ) -> bool {
24421 let mut chars = text.chars();
24422 let char = if let Some(char) = chars.next() {
24423 char
24424 } else {
24425 return false;
24426 };
24427 if chars.next().is_some() {
24428 return false;
24429 }
24430
24431 let buffer = buffer.read(cx);
24432 let snapshot = buffer.snapshot();
24433 let classifier = snapshot
24434 .char_classifier_at(position)
24435 .scope_context(Some(CharScopeContext::Completion));
24436 if trigger_in_words && classifier.is_word(char) {
24437 return true;
24438 }
24439
24440 buffer.completion_triggers().contains(text)
24441 }
24442
24443 fn show_snippets(&self) -> bool {
24444 true
24445 }
24446}
24447
24448impl SemanticsProvider for Entity<Project> {
24449 fn hover(
24450 &self,
24451 buffer: &Entity<Buffer>,
24452 position: text::Anchor,
24453 cx: &mut App,
24454 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24455 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24456 }
24457
24458 fn document_highlights(
24459 &self,
24460 buffer: &Entity<Buffer>,
24461 position: text::Anchor,
24462 cx: &mut App,
24463 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24464 Some(self.update(cx, |project, cx| {
24465 project.document_highlights(buffer, position, cx)
24466 }))
24467 }
24468
24469 fn definitions(
24470 &self,
24471 buffer: &Entity<Buffer>,
24472 position: text::Anchor,
24473 kind: GotoDefinitionKind,
24474 cx: &mut App,
24475 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24476 Some(self.update(cx, |project, cx| match kind {
24477 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24478 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24479 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24480 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24481 }))
24482 }
24483
24484 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24485 self.update(cx, |project, cx| {
24486 if project
24487 .active_debug_session(cx)
24488 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24489 {
24490 return true;
24491 }
24492
24493 buffer.update(cx, |buffer, cx| {
24494 project.any_language_server_supports_inlay_hints(buffer, cx)
24495 })
24496 })
24497 }
24498
24499 fn inline_values(
24500 &self,
24501 buffer_handle: Entity<Buffer>,
24502 range: Range<text::Anchor>,
24503 cx: &mut App,
24504 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24505 self.update(cx, |project, cx| {
24506 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24507
24508 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24509 })
24510 }
24511
24512 fn applicable_inlay_chunks(
24513 &self,
24514 buffer: &Entity<Buffer>,
24515 ranges: &[Range<text::Anchor>],
24516 cx: &mut App,
24517 ) -> Vec<Range<BufferRow>> {
24518 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24519 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24520 })
24521 }
24522
24523 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24524 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24525 lsp_store.invalidate_inlay_hints(for_buffers)
24526 });
24527 }
24528
24529 fn inlay_hints(
24530 &self,
24531 invalidate: InvalidationStrategy,
24532 buffer: Entity<Buffer>,
24533 ranges: Vec<Range<text::Anchor>>,
24534 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24535 cx: &mut App,
24536 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24537 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24538 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24539 }))
24540 }
24541
24542 fn range_for_rename(
24543 &self,
24544 buffer: &Entity<Buffer>,
24545 position: text::Anchor,
24546 cx: &mut App,
24547 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24548 Some(self.update(cx, |project, cx| {
24549 let buffer = buffer.clone();
24550 let task = project.prepare_rename(buffer.clone(), position, cx);
24551 cx.spawn(async move |_, cx| {
24552 Ok(match task.await? {
24553 PrepareRenameResponse::Success(range) => Some(range),
24554 PrepareRenameResponse::InvalidPosition => None,
24555 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24556 // Fallback on using TreeSitter info to determine identifier range
24557 buffer.read_with(cx, |buffer, _| {
24558 let snapshot = buffer.snapshot();
24559 let (range, kind) = snapshot.surrounding_word(position, None);
24560 if kind != Some(CharKind::Word) {
24561 return None;
24562 }
24563 Some(
24564 snapshot.anchor_before(range.start)
24565 ..snapshot.anchor_after(range.end),
24566 )
24567 })?
24568 }
24569 })
24570 })
24571 }))
24572 }
24573
24574 fn perform_rename(
24575 &self,
24576 buffer: &Entity<Buffer>,
24577 position: text::Anchor,
24578 new_name: String,
24579 cx: &mut App,
24580 ) -> Option<Task<Result<ProjectTransaction>>> {
24581 Some(self.update(cx, |project, cx| {
24582 project.perform_rename(buffer.clone(), position, new_name, cx)
24583 }))
24584 }
24585}
24586
24587fn consume_contiguous_rows(
24588 contiguous_row_selections: &mut Vec<Selection<Point>>,
24589 selection: &Selection<Point>,
24590 display_map: &DisplaySnapshot,
24591 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24592) -> (MultiBufferRow, MultiBufferRow) {
24593 contiguous_row_selections.push(selection.clone());
24594 let start_row = starting_row(selection, display_map);
24595 let mut end_row = ending_row(selection, display_map);
24596
24597 while let Some(next_selection) = selections.peek() {
24598 if next_selection.start.row <= end_row.0 {
24599 end_row = ending_row(next_selection, display_map);
24600 contiguous_row_selections.push(selections.next().unwrap().clone());
24601 } else {
24602 break;
24603 }
24604 }
24605 (start_row, end_row)
24606}
24607
24608fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24609 if selection.start.column > 0 {
24610 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24611 } else {
24612 MultiBufferRow(selection.start.row)
24613 }
24614}
24615
24616fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24617 if next_selection.end.column > 0 || next_selection.is_empty() {
24618 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24619 } else {
24620 MultiBufferRow(next_selection.end.row)
24621 }
24622}
24623
24624impl EditorSnapshot {
24625 pub fn remote_selections_in_range<'a>(
24626 &'a self,
24627 range: &'a Range<Anchor>,
24628 collaboration_hub: &dyn CollaborationHub,
24629 cx: &'a App,
24630 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24631 let participant_names = collaboration_hub.user_names(cx);
24632 let participant_indices = collaboration_hub.user_participant_indices(cx);
24633 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24634 let collaborators_by_replica_id = collaborators_by_peer_id
24635 .values()
24636 .map(|collaborator| (collaborator.replica_id, collaborator))
24637 .collect::<HashMap<_, _>>();
24638 self.buffer_snapshot()
24639 .selections_in_range(range, false)
24640 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24641 if replica_id == ReplicaId::AGENT {
24642 Some(RemoteSelection {
24643 replica_id,
24644 selection,
24645 cursor_shape,
24646 line_mode,
24647 collaborator_id: CollaboratorId::Agent,
24648 user_name: Some("Agent".into()),
24649 color: cx.theme().players().agent(),
24650 })
24651 } else {
24652 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24653 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24654 let user_name = participant_names.get(&collaborator.user_id).cloned();
24655 Some(RemoteSelection {
24656 replica_id,
24657 selection,
24658 cursor_shape,
24659 line_mode,
24660 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24661 user_name,
24662 color: if let Some(index) = participant_index {
24663 cx.theme().players().color_for_participant(index.0)
24664 } else {
24665 cx.theme().players().absent()
24666 },
24667 })
24668 }
24669 })
24670 }
24671
24672 pub fn hunks_for_ranges(
24673 &self,
24674 ranges: impl IntoIterator<Item = Range<Point>>,
24675 ) -> Vec<MultiBufferDiffHunk> {
24676 let mut hunks = Vec::new();
24677 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24678 HashMap::default();
24679 for query_range in ranges {
24680 let query_rows =
24681 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24682 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24683 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24684 ) {
24685 // Include deleted hunks that are adjacent to the query range, because
24686 // otherwise they would be missed.
24687 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24688 if hunk.status().is_deleted() {
24689 intersects_range |= hunk.row_range.start == query_rows.end;
24690 intersects_range |= hunk.row_range.end == query_rows.start;
24691 }
24692 if intersects_range {
24693 if !processed_buffer_rows
24694 .entry(hunk.buffer_id)
24695 .or_default()
24696 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24697 {
24698 continue;
24699 }
24700 hunks.push(hunk);
24701 }
24702 }
24703 }
24704
24705 hunks
24706 }
24707
24708 fn display_diff_hunks_for_rows<'a>(
24709 &'a self,
24710 display_rows: Range<DisplayRow>,
24711 folded_buffers: &'a HashSet<BufferId>,
24712 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24713 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24714 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24715
24716 self.buffer_snapshot()
24717 .diff_hunks_in_range(buffer_start..buffer_end)
24718 .filter_map(|hunk| {
24719 if folded_buffers.contains(&hunk.buffer_id) {
24720 return None;
24721 }
24722
24723 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24724 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24725
24726 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24727 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24728
24729 let display_hunk = if hunk_display_start.column() != 0 {
24730 DisplayDiffHunk::Folded {
24731 display_row: hunk_display_start.row(),
24732 }
24733 } else {
24734 let mut end_row = hunk_display_end.row();
24735 if hunk_display_end.column() > 0 {
24736 end_row.0 += 1;
24737 }
24738 let is_created_file = hunk.is_created_file();
24739
24740 DisplayDiffHunk::Unfolded {
24741 status: hunk.status(),
24742 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24743 ..hunk.diff_base_byte_range.end.0,
24744 word_diffs: hunk.word_diffs,
24745 display_row_range: hunk_display_start.row()..end_row,
24746 multi_buffer_range: Anchor::range_in_buffer(
24747 hunk.excerpt_id,
24748 hunk.buffer_range,
24749 ),
24750 is_created_file,
24751 }
24752 };
24753
24754 Some(display_hunk)
24755 })
24756 }
24757
24758 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24759 self.display_snapshot
24760 .buffer_snapshot()
24761 .language_at(position)
24762 }
24763
24764 pub fn is_focused(&self) -> bool {
24765 self.is_focused
24766 }
24767
24768 pub fn placeholder_text(&self) -> Option<String> {
24769 self.placeholder_display_snapshot
24770 .as_ref()
24771 .map(|display_map| display_map.text())
24772 }
24773
24774 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24775 self.scroll_anchor.scroll_position(&self.display_snapshot)
24776 }
24777
24778 pub fn gutter_dimensions(
24779 &self,
24780 font_id: FontId,
24781 font_size: Pixels,
24782 style: &EditorStyle,
24783 window: &mut Window,
24784 cx: &App,
24785 ) -> GutterDimensions {
24786 if self.show_gutter
24787 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24788 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24789 {
24790 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24791 matches!(
24792 ProjectSettings::get_global(cx).git.git_gutter,
24793 GitGutterSetting::TrackedFiles
24794 )
24795 });
24796 let gutter_settings = EditorSettings::get_global(cx).gutter;
24797 let show_line_numbers = self
24798 .show_line_numbers
24799 .unwrap_or(gutter_settings.line_numbers);
24800 let line_gutter_width = if show_line_numbers {
24801 // Avoid flicker-like gutter resizes when the line number gains another digit by
24802 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24803 let min_width_for_number_on_gutter =
24804 ch_advance * gutter_settings.min_line_number_digits as f32;
24805 self.max_line_number_width(style, window)
24806 .max(min_width_for_number_on_gutter)
24807 } else {
24808 0.0.into()
24809 };
24810
24811 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24812 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24813
24814 let git_blame_entries_width =
24815 self.git_blame_gutter_max_author_length
24816 .map(|max_author_length| {
24817 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24818 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24819
24820 /// The number of characters to dedicate to gaps and margins.
24821 const SPACING_WIDTH: usize = 4;
24822
24823 let max_char_count = max_author_length.min(renderer.max_author_length())
24824 + ::git::SHORT_SHA_LENGTH
24825 + MAX_RELATIVE_TIMESTAMP.len()
24826 + SPACING_WIDTH;
24827
24828 ch_advance * max_char_count
24829 });
24830
24831 let is_singleton = self.buffer_snapshot().is_singleton();
24832
24833 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24834 left_padding += if !is_singleton {
24835 ch_width * 4.0
24836 } else if show_runnables || show_breakpoints {
24837 ch_width * 3.0
24838 } else if show_git_gutter && show_line_numbers {
24839 ch_width * 2.0
24840 } else if show_git_gutter || show_line_numbers {
24841 ch_width
24842 } else {
24843 px(0.)
24844 };
24845
24846 let shows_folds = is_singleton && gutter_settings.folds;
24847
24848 let right_padding = if shows_folds && show_line_numbers {
24849 ch_width * 4.0
24850 } else if shows_folds || (!is_singleton && show_line_numbers) {
24851 ch_width * 3.0
24852 } else if show_line_numbers {
24853 ch_width
24854 } else {
24855 px(0.)
24856 };
24857
24858 GutterDimensions {
24859 left_padding,
24860 right_padding,
24861 width: line_gutter_width + left_padding + right_padding,
24862 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24863 git_blame_entries_width,
24864 }
24865 } else if self.offset_content {
24866 GutterDimensions::default_with_margin(font_id, font_size, cx)
24867 } else {
24868 GutterDimensions::default()
24869 }
24870 }
24871
24872 pub fn render_crease_toggle(
24873 &self,
24874 buffer_row: MultiBufferRow,
24875 row_contains_cursor: bool,
24876 editor: Entity<Editor>,
24877 window: &mut Window,
24878 cx: &mut App,
24879 ) -> Option<AnyElement> {
24880 let folded = self.is_line_folded(buffer_row);
24881 let mut is_foldable = false;
24882
24883 if let Some(crease) = self
24884 .crease_snapshot
24885 .query_row(buffer_row, self.buffer_snapshot())
24886 {
24887 is_foldable = true;
24888 match crease {
24889 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24890 if let Some(render_toggle) = render_toggle {
24891 let toggle_callback =
24892 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24893 if folded {
24894 editor.update(cx, |editor, cx| {
24895 editor.fold_at(buffer_row, window, cx)
24896 });
24897 } else {
24898 editor.update(cx, |editor, cx| {
24899 editor.unfold_at(buffer_row, window, cx)
24900 });
24901 }
24902 });
24903 return Some((render_toggle)(
24904 buffer_row,
24905 folded,
24906 toggle_callback,
24907 window,
24908 cx,
24909 ));
24910 }
24911 }
24912 }
24913 }
24914
24915 is_foldable |= self.starts_indent(buffer_row);
24916
24917 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24918 Some(
24919 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24920 .toggle_state(folded)
24921 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24922 if folded {
24923 this.unfold_at(buffer_row, window, cx);
24924 } else {
24925 this.fold_at(buffer_row, window, cx);
24926 }
24927 }))
24928 .into_any_element(),
24929 )
24930 } else {
24931 None
24932 }
24933 }
24934
24935 pub fn render_crease_trailer(
24936 &self,
24937 buffer_row: MultiBufferRow,
24938 window: &mut Window,
24939 cx: &mut App,
24940 ) -> Option<AnyElement> {
24941 let folded = self.is_line_folded(buffer_row);
24942 if let Crease::Inline { render_trailer, .. } = self
24943 .crease_snapshot
24944 .query_row(buffer_row, self.buffer_snapshot())?
24945 {
24946 let render_trailer = render_trailer.as_ref()?;
24947 Some(render_trailer(buffer_row, folded, window, cx))
24948 } else {
24949 None
24950 }
24951 }
24952
24953 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
24954 let digit_count = self.widest_line_number().ilog10() + 1;
24955 column_pixels(style, digit_count as usize, window)
24956 }
24957}
24958
24959pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
24960 let font_size = style.text.font_size.to_pixels(window.rem_size());
24961 let layout = window.text_system().shape_line(
24962 SharedString::from(" ".repeat(column)),
24963 font_size,
24964 &[TextRun {
24965 len: column,
24966 font: style.text.font(),
24967 color: Hsla::default(),
24968 ..Default::default()
24969 }],
24970 None,
24971 );
24972
24973 layout.width
24974}
24975
24976impl Deref for EditorSnapshot {
24977 type Target = DisplaySnapshot;
24978
24979 fn deref(&self) -> &Self::Target {
24980 &self.display_snapshot
24981 }
24982}
24983
24984#[derive(Clone, Debug, PartialEq, Eq)]
24985pub enum EditorEvent {
24986 InputIgnored {
24987 text: Arc<str>,
24988 },
24989 InputHandled {
24990 utf16_range_to_replace: Option<Range<isize>>,
24991 text: Arc<str>,
24992 },
24993 ExcerptsAdded {
24994 buffer: Entity<Buffer>,
24995 predecessor: ExcerptId,
24996 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24997 },
24998 ExcerptsRemoved {
24999 ids: Vec<ExcerptId>,
25000 removed_buffer_ids: Vec<BufferId>,
25001 },
25002 BufferFoldToggled {
25003 ids: Vec<ExcerptId>,
25004 folded: bool,
25005 },
25006 ExcerptsEdited {
25007 ids: Vec<ExcerptId>,
25008 },
25009 ExcerptsExpanded {
25010 ids: Vec<ExcerptId>,
25011 },
25012 BufferEdited,
25013 Edited {
25014 transaction_id: clock::Lamport,
25015 },
25016 Reparsed(BufferId),
25017 Focused,
25018 FocusedIn,
25019 Blurred,
25020 DirtyChanged,
25021 Saved,
25022 TitleChanged,
25023 SelectionsChanged {
25024 local: bool,
25025 },
25026 ScrollPositionChanged {
25027 local: bool,
25028 autoscroll: bool,
25029 },
25030 TransactionUndone {
25031 transaction_id: clock::Lamport,
25032 },
25033 TransactionBegun {
25034 transaction_id: clock::Lamport,
25035 },
25036 CursorShapeChanged,
25037 BreadcrumbsChanged,
25038 PushedToNavHistory {
25039 anchor: Anchor,
25040 is_deactivate: bool,
25041 },
25042}
25043
25044impl EventEmitter<EditorEvent> for Editor {}
25045
25046impl Focusable for Editor {
25047 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25048 self.focus_handle.clone()
25049 }
25050}
25051
25052impl Render for Editor {
25053 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25054 EditorElement::new(&cx.entity(), self.create_style(cx))
25055 }
25056}
25057
25058impl EntityInputHandler for Editor {
25059 fn text_for_range(
25060 &mut self,
25061 range_utf16: Range<usize>,
25062 adjusted_range: &mut Option<Range<usize>>,
25063 _: &mut Window,
25064 cx: &mut Context<Self>,
25065 ) -> Option<String> {
25066 let snapshot = self.buffer.read(cx).read(cx);
25067 let start = snapshot.clip_offset_utf16(
25068 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25069 Bias::Left,
25070 );
25071 let end = snapshot.clip_offset_utf16(
25072 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25073 Bias::Right,
25074 );
25075 if (start.0.0..end.0.0) != range_utf16 {
25076 adjusted_range.replace(start.0.0..end.0.0);
25077 }
25078 Some(snapshot.text_for_range(start..end).collect())
25079 }
25080
25081 fn selected_text_range(
25082 &mut self,
25083 ignore_disabled_input: bool,
25084 _: &mut Window,
25085 cx: &mut Context<Self>,
25086 ) -> Option<UTF16Selection> {
25087 // Prevent the IME menu from appearing when holding down an alphabetic key
25088 // while input is disabled.
25089 if !ignore_disabled_input && !self.input_enabled {
25090 return None;
25091 }
25092
25093 let selection = self
25094 .selections
25095 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25096 let range = selection.range();
25097
25098 Some(UTF16Selection {
25099 range: range.start.0.0..range.end.0.0,
25100 reversed: selection.reversed,
25101 })
25102 }
25103
25104 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25105 let snapshot = self.buffer.read(cx).read(cx);
25106 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25107 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25108 }
25109
25110 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25111 self.clear_highlights::<InputComposition>(cx);
25112 self.ime_transaction.take();
25113 }
25114
25115 fn replace_text_in_range(
25116 &mut self,
25117 range_utf16: Option<Range<usize>>,
25118 text: &str,
25119 window: &mut Window,
25120 cx: &mut Context<Self>,
25121 ) {
25122 if !self.input_enabled {
25123 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25124 return;
25125 }
25126
25127 self.transact(window, cx, |this, window, cx| {
25128 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25129 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25130 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25131 Some(this.selection_replacement_ranges(range_utf16, cx))
25132 } else {
25133 this.marked_text_ranges(cx)
25134 };
25135
25136 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25137 let newest_selection_id = this.selections.newest_anchor().id;
25138 this.selections
25139 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25140 .iter()
25141 .zip(ranges_to_replace.iter())
25142 .find_map(|(selection, range)| {
25143 if selection.id == newest_selection_id {
25144 Some(
25145 (range.start.0.0 as isize - selection.head().0.0 as isize)
25146 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25147 )
25148 } else {
25149 None
25150 }
25151 })
25152 });
25153
25154 cx.emit(EditorEvent::InputHandled {
25155 utf16_range_to_replace: range_to_replace,
25156 text: text.into(),
25157 });
25158
25159 if let Some(new_selected_ranges) = new_selected_ranges {
25160 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25161 selections.select_ranges(new_selected_ranges)
25162 });
25163 this.backspace(&Default::default(), window, cx);
25164 }
25165
25166 this.handle_input(text, window, cx);
25167 });
25168
25169 if let Some(transaction) = self.ime_transaction {
25170 self.buffer.update(cx, |buffer, cx| {
25171 buffer.group_until_transaction(transaction, cx);
25172 });
25173 }
25174
25175 self.unmark_text(window, cx);
25176 }
25177
25178 fn replace_and_mark_text_in_range(
25179 &mut self,
25180 range_utf16: Option<Range<usize>>,
25181 text: &str,
25182 new_selected_range_utf16: Option<Range<usize>>,
25183 window: &mut Window,
25184 cx: &mut Context<Self>,
25185 ) {
25186 if !self.input_enabled {
25187 return;
25188 }
25189
25190 let transaction = self.transact(window, cx, |this, window, cx| {
25191 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25192 let snapshot = this.buffer.read(cx).read(cx);
25193 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25194 for marked_range in &mut marked_ranges {
25195 marked_range.end = marked_range.start + relative_range_utf16.end;
25196 marked_range.start += relative_range_utf16.start;
25197 marked_range.start =
25198 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25199 marked_range.end =
25200 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25201 }
25202 }
25203 Some(marked_ranges)
25204 } else if let Some(range_utf16) = range_utf16 {
25205 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25206 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25207 Some(this.selection_replacement_ranges(range_utf16, cx))
25208 } else {
25209 None
25210 };
25211
25212 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25213 let newest_selection_id = this.selections.newest_anchor().id;
25214 this.selections
25215 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25216 .iter()
25217 .zip(ranges_to_replace.iter())
25218 .find_map(|(selection, range)| {
25219 if selection.id == newest_selection_id {
25220 Some(
25221 (range.start.0.0 as isize - selection.head().0.0 as isize)
25222 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25223 )
25224 } else {
25225 None
25226 }
25227 })
25228 });
25229
25230 cx.emit(EditorEvent::InputHandled {
25231 utf16_range_to_replace: range_to_replace,
25232 text: text.into(),
25233 });
25234
25235 if let Some(ranges) = ranges_to_replace {
25236 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25237 s.select_ranges(ranges)
25238 });
25239 }
25240
25241 let marked_ranges = {
25242 let snapshot = this.buffer.read(cx).read(cx);
25243 this.selections
25244 .disjoint_anchors_arc()
25245 .iter()
25246 .map(|selection| {
25247 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25248 })
25249 .collect::<Vec<_>>()
25250 };
25251
25252 if text.is_empty() {
25253 this.unmark_text(window, cx);
25254 } else {
25255 this.highlight_text::<InputComposition>(
25256 marked_ranges.clone(),
25257 HighlightStyle {
25258 underline: Some(UnderlineStyle {
25259 thickness: px(1.),
25260 color: None,
25261 wavy: false,
25262 }),
25263 ..Default::default()
25264 },
25265 cx,
25266 );
25267 }
25268
25269 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25270 let use_autoclose = this.use_autoclose;
25271 let use_auto_surround = this.use_auto_surround;
25272 this.set_use_autoclose(false);
25273 this.set_use_auto_surround(false);
25274 this.handle_input(text, window, cx);
25275 this.set_use_autoclose(use_autoclose);
25276 this.set_use_auto_surround(use_auto_surround);
25277
25278 if let Some(new_selected_range) = new_selected_range_utf16 {
25279 let snapshot = this.buffer.read(cx).read(cx);
25280 let new_selected_ranges = marked_ranges
25281 .into_iter()
25282 .map(|marked_range| {
25283 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25284 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25285 insertion_start.0 + new_selected_range.start,
25286 ));
25287 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25288 insertion_start.0 + new_selected_range.end,
25289 ));
25290 snapshot.clip_offset_utf16(new_start, Bias::Left)
25291 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25292 })
25293 .collect::<Vec<_>>();
25294
25295 drop(snapshot);
25296 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25297 selections.select_ranges(new_selected_ranges)
25298 });
25299 }
25300 });
25301
25302 self.ime_transaction = self.ime_transaction.or(transaction);
25303 if let Some(transaction) = self.ime_transaction {
25304 self.buffer.update(cx, |buffer, cx| {
25305 buffer.group_until_transaction(transaction, cx);
25306 });
25307 }
25308
25309 if self.text_highlights::<InputComposition>(cx).is_none() {
25310 self.ime_transaction.take();
25311 }
25312 }
25313
25314 fn bounds_for_range(
25315 &mut self,
25316 range_utf16: Range<usize>,
25317 element_bounds: gpui::Bounds<Pixels>,
25318 window: &mut Window,
25319 cx: &mut Context<Self>,
25320 ) -> Option<gpui::Bounds<Pixels>> {
25321 let text_layout_details = self.text_layout_details(window);
25322 let CharacterDimensions {
25323 em_width,
25324 em_advance,
25325 line_height,
25326 } = self.character_dimensions(window);
25327
25328 let snapshot = self.snapshot(window, cx);
25329 let scroll_position = snapshot.scroll_position();
25330 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25331
25332 let start =
25333 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25334 let x = Pixels::from(
25335 ScrollOffset::from(
25336 snapshot.x_for_display_point(start, &text_layout_details)
25337 + self.gutter_dimensions.full_width(),
25338 ) - scroll_left,
25339 );
25340 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25341
25342 Some(Bounds {
25343 origin: element_bounds.origin + point(x, y),
25344 size: size(em_width, line_height),
25345 })
25346 }
25347
25348 fn character_index_for_point(
25349 &mut self,
25350 point: gpui::Point<Pixels>,
25351 _window: &mut Window,
25352 _cx: &mut Context<Self>,
25353 ) -> Option<usize> {
25354 let position_map = self.last_position_map.as_ref()?;
25355 if !position_map.text_hitbox.contains(&point) {
25356 return None;
25357 }
25358 let display_point = position_map.point_for_position(point).previous_valid;
25359 let anchor = position_map
25360 .snapshot
25361 .display_point_to_anchor(display_point, Bias::Left);
25362 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25363 Some(utf16_offset.0.0)
25364 }
25365
25366 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25367 self.input_enabled
25368 }
25369}
25370
25371trait SelectionExt {
25372 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25373 fn spanned_rows(
25374 &self,
25375 include_end_if_at_line_start: bool,
25376 map: &DisplaySnapshot,
25377 ) -> Range<MultiBufferRow>;
25378}
25379
25380impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25381 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25382 let start = self
25383 .start
25384 .to_point(map.buffer_snapshot())
25385 .to_display_point(map);
25386 let end = self
25387 .end
25388 .to_point(map.buffer_snapshot())
25389 .to_display_point(map);
25390 if self.reversed {
25391 end..start
25392 } else {
25393 start..end
25394 }
25395 }
25396
25397 fn spanned_rows(
25398 &self,
25399 include_end_if_at_line_start: bool,
25400 map: &DisplaySnapshot,
25401 ) -> Range<MultiBufferRow> {
25402 let start = self.start.to_point(map.buffer_snapshot());
25403 let mut end = self.end.to_point(map.buffer_snapshot());
25404 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25405 end.row -= 1;
25406 }
25407
25408 let buffer_start = map.prev_line_boundary(start).0;
25409 let buffer_end = map.next_line_boundary(end).0;
25410 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25411 }
25412}
25413
25414impl<T: InvalidationRegion> InvalidationStack<T> {
25415 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25416 where
25417 S: Clone + ToOffset,
25418 {
25419 while let Some(region) = self.last() {
25420 let all_selections_inside_invalidation_ranges =
25421 if selections.len() == region.ranges().len() {
25422 selections
25423 .iter()
25424 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25425 .all(|(selection, invalidation_range)| {
25426 let head = selection.head().to_offset(buffer);
25427 invalidation_range.start <= head && invalidation_range.end >= head
25428 })
25429 } else {
25430 false
25431 };
25432
25433 if all_selections_inside_invalidation_ranges {
25434 break;
25435 } else {
25436 self.pop();
25437 }
25438 }
25439 }
25440}
25441
25442impl<T> Default for InvalidationStack<T> {
25443 fn default() -> Self {
25444 Self(Default::default())
25445 }
25446}
25447
25448impl<T> Deref for InvalidationStack<T> {
25449 type Target = Vec<T>;
25450
25451 fn deref(&self) -> &Self::Target {
25452 &self.0
25453 }
25454}
25455
25456impl<T> DerefMut for InvalidationStack<T> {
25457 fn deref_mut(&mut self) -> &mut Self::Target {
25458 &mut self.0
25459 }
25460}
25461
25462impl InvalidationRegion for SnippetState {
25463 fn ranges(&self) -> &[Range<Anchor>] {
25464 &self.ranges[self.active_index]
25465 }
25466}
25467
25468fn edit_prediction_edit_text(
25469 current_snapshot: &BufferSnapshot,
25470 edits: &[(Range<Anchor>, impl AsRef<str>)],
25471 edit_preview: &EditPreview,
25472 include_deletions: bool,
25473 cx: &App,
25474) -> HighlightedText {
25475 let edits = edits
25476 .iter()
25477 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25478 .collect::<Vec<_>>();
25479
25480 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25481}
25482
25483fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25484 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25485 // Just show the raw edit text with basic styling
25486 let mut text = String::new();
25487 let mut highlights = Vec::new();
25488
25489 let insertion_highlight_style = HighlightStyle {
25490 color: Some(cx.theme().colors().text),
25491 ..Default::default()
25492 };
25493
25494 for (_, edit_text) in edits {
25495 let start_offset = text.len();
25496 text.push_str(edit_text);
25497 let end_offset = text.len();
25498
25499 if start_offset < end_offset {
25500 highlights.push((start_offset..end_offset, insertion_highlight_style));
25501 }
25502 }
25503
25504 HighlightedText {
25505 text: text.into(),
25506 highlights,
25507 }
25508}
25509
25510pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25511 match severity {
25512 lsp::DiagnosticSeverity::ERROR => colors.error,
25513 lsp::DiagnosticSeverity::WARNING => colors.warning,
25514 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25515 lsp::DiagnosticSeverity::HINT => colors.info,
25516 _ => colors.ignored,
25517 }
25518}
25519
25520pub fn styled_runs_for_code_label<'a>(
25521 label: &'a CodeLabel,
25522 syntax_theme: &'a theme::SyntaxTheme,
25523 local_player: &'a theme::PlayerColor,
25524) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25525 let fade_out = HighlightStyle {
25526 fade_out: Some(0.35),
25527 ..Default::default()
25528 };
25529
25530 let mut prev_end = label.filter_range.end;
25531 label
25532 .runs
25533 .iter()
25534 .enumerate()
25535 .flat_map(move |(ix, (range, highlight_id))| {
25536 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25537 HighlightStyle {
25538 color: Some(local_player.cursor),
25539 ..Default::default()
25540 }
25541 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25542 HighlightStyle {
25543 background_color: Some(local_player.selection),
25544 ..Default::default()
25545 }
25546 } else if let Some(style) = highlight_id.style(syntax_theme) {
25547 style
25548 } else {
25549 return Default::default();
25550 };
25551 let muted_style = style.highlight(fade_out);
25552
25553 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25554 if range.start >= label.filter_range.end {
25555 if range.start > prev_end {
25556 runs.push((prev_end..range.start, fade_out));
25557 }
25558 runs.push((range.clone(), muted_style));
25559 } else if range.end <= label.filter_range.end {
25560 runs.push((range.clone(), style));
25561 } else {
25562 runs.push((range.start..label.filter_range.end, style));
25563 runs.push((label.filter_range.end..range.end, muted_style));
25564 }
25565 prev_end = cmp::max(prev_end, range.end);
25566
25567 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25568 runs.push((prev_end..label.text.len(), fade_out));
25569 }
25570
25571 runs
25572 })
25573}
25574
25575pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25576 let mut prev_index = 0;
25577 let mut prev_codepoint: Option<char> = None;
25578 text.char_indices()
25579 .chain([(text.len(), '\0')])
25580 .filter_map(move |(index, codepoint)| {
25581 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25582 let is_boundary = index == text.len()
25583 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25584 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25585 if is_boundary {
25586 let chunk = &text[prev_index..index];
25587 prev_index = index;
25588 Some(chunk)
25589 } else {
25590 None
25591 }
25592 })
25593}
25594
25595/// Given a string of text immediately before the cursor, iterates over possible
25596/// strings a snippet could match to. More precisely: returns an iterator over
25597/// suffixes of `text` created by splitting at word boundaries (before & after
25598/// every non-word character).
25599///
25600/// Shorter suffixes are returned first.
25601pub(crate) fn snippet_candidate_suffixes(
25602 text: &str,
25603 is_word_char: impl Fn(char) -> bool,
25604) -> impl std::iter::Iterator<Item = &str> {
25605 let mut prev_index = text.len();
25606 let mut prev_codepoint = None;
25607 text.char_indices()
25608 .rev()
25609 .chain([(0, '\0')])
25610 .filter_map(move |(index, codepoint)| {
25611 let prev_index = std::mem::replace(&mut prev_index, index);
25612 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25613 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25614 None
25615 } else {
25616 let chunk = &text[prev_index..]; // go to end of string
25617 Some(chunk)
25618 }
25619 })
25620}
25621
25622pub trait RangeToAnchorExt: Sized {
25623 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25624
25625 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25626 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25627 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25628 }
25629}
25630
25631impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25632 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25633 let start_offset = self.start.to_offset(snapshot);
25634 let end_offset = self.end.to_offset(snapshot);
25635 if start_offset == end_offset {
25636 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25637 } else {
25638 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25639 }
25640 }
25641}
25642
25643pub trait RowExt {
25644 fn as_f64(&self) -> f64;
25645
25646 fn next_row(&self) -> Self;
25647
25648 fn previous_row(&self) -> Self;
25649
25650 fn minus(&self, other: Self) -> u32;
25651}
25652
25653impl RowExt for DisplayRow {
25654 fn as_f64(&self) -> f64 {
25655 self.0 as _
25656 }
25657
25658 fn next_row(&self) -> Self {
25659 Self(self.0 + 1)
25660 }
25661
25662 fn previous_row(&self) -> Self {
25663 Self(self.0.saturating_sub(1))
25664 }
25665
25666 fn minus(&self, other: Self) -> u32 {
25667 self.0 - other.0
25668 }
25669}
25670
25671impl RowExt for MultiBufferRow {
25672 fn as_f64(&self) -> f64 {
25673 self.0 as _
25674 }
25675
25676 fn next_row(&self) -> Self {
25677 Self(self.0 + 1)
25678 }
25679
25680 fn previous_row(&self) -> Self {
25681 Self(self.0.saturating_sub(1))
25682 }
25683
25684 fn minus(&self, other: Self) -> u32 {
25685 self.0 - other.0
25686 }
25687}
25688
25689trait RowRangeExt {
25690 type Row;
25691
25692 fn len(&self) -> usize;
25693
25694 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25695}
25696
25697impl RowRangeExt for Range<MultiBufferRow> {
25698 type Row = MultiBufferRow;
25699
25700 fn len(&self) -> usize {
25701 (self.end.0 - self.start.0) as usize
25702 }
25703
25704 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25705 (self.start.0..self.end.0).map(MultiBufferRow)
25706 }
25707}
25708
25709impl RowRangeExt for Range<DisplayRow> {
25710 type Row = DisplayRow;
25711
25712 fn len(&self) -> usize {
25713 (self.end.0 - self.start.0) as usize
25714 }
25715
25716 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25717 (self.start.0..self.end.0).map(DisplayRow)
25718 }
25719}
25720
25721/// If select range has more than one line, we
25722/// just point the cursor to range.start.
25723fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25724 if range.start.row == range.end.row {
25725 range
25726 } else {
25727 range.start..range.start
25728 }
25729}
25730pub struct KillRing(ClipboardItem);
25731impl Global for KillRing {}
25732
25733const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25734
25735enum BreakpointPromptEditAction {
25736 Log,
25737 Condition,
25738 HitCondition,
25739}
25740
25741struct BreakpointPromptEditor {
25742 pub(crate) prompt: Entity<Editor>,
25743 editor: WeakEntity<Editor>,
25744 breakpoint_anchor: Anchor,
25745 breakpoint: Breakpoint,
25746 edit_action: BreakpointPromptEditAction,
25747 block_ids: HashSet<CustomBlockId>,
25748 editor_margins: Arc<Mutex<EditorMargins>>,
25749 _subscriptions: Vec<Subscription>,
25750}
25751
25752impl BreakpointPromptEditor {
25753 const MAX_LINES: u8 = 4;
25754
25755 fn new(
25756 editor: WeakEntity<Editor>,
25757 breakpoint_anchor: Anchor,
25758 breakpoint: Breakpoint,
25759 edit_action: BreakpointPromptEditAction,
25760 window: &mut Window,
25761 cx: &mut Context<Self>,
25762 ) -> Self {
25763 let base_text = match edit_action {
25764 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25765 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25766 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25767 }
25768 .map(|msg| msg.to_string())
25769 .unwrap_or_default();
25770
25771 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25772 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25773
25774 let prompt = cx.new(|cx| {
25775 let mut prompt = Editor::new(
25776 EditorMode::AutoHeight {
25777 min_lines: 1,
25778 max_lines: Some(Self::MAX_LINES as usize),
25779 },
25780 buffer,
25781 None,
25782 window,
25783 cx,
25784 );
25785 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25786 prompt.set_show_cursor_when_unfocused(false, cx);
25787 prompt.set_placeholder_text(
25788 match edit_action {
25789 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25790 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25791 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25792 },
25793 window,
25794 cx,
25795 );
25796
25797 prompt
25798 });
25799
25800 Self {
25801 prompt,
25802 editor,
25803 breakpoint_anchor,
25804 breakpoint,
25805 edit_action,
25806 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25807 block_ids: Default::default(),
25808 _subscriptions: vec![],
25809 }
25810 }
25811
25812 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25813 self.block_ids.extend(block_ids)
25814 }
25815
25816 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25817 if let Some(editor) = self.editor.upgrade() {
25818 let message = self
25819 .prompt
25820 .read(cx)
25821 .buffer
25822 .read(cx)
25823 .as_singleton()
25824 .expect("A multi buffer in breakpoint prompt isn't possible")
25825 .read(cx)
25826 .as_rope()
25827 .to_string();
25828
25829 editor.update(cx, |editor, cx| {
25830 editor.edit_breakpoint_at_anchor(
25831 self.breakpoint_anchor,
25832 self.breakpoint.clone(),
25833 match self.edit_action {
25834 BreakpointPromptEditAction::Log => {
25835 BreakpointEditAction::EditLogMessage(message.into())
25836 }
25837 BreakpointPromptEditAction::Condition => {
25838 BreakpointEditAction::EditCondition(message.into())
25839 }
25840 BreakpointPromptEditAction::HitCondition => {
25841 BreakpointEditAction::EditHitCondition(message.into())
25842 }
25843 },
25844 cx,
25845 );
25846
25847 editor.remove_blocks(self.block_ids.clone(), None, cx);
25848 cx.focus_self(window);
25849 });
25850 }
25851 }
25852
25853 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25854 self.editor
25855 .update(cx, |editor, cx| {
25856 editor.remove_blocks(self.block_ids.clone(), None, cx);
25857 window.focus(&editor.focus_handle);
25858 })
25859 .log_err();
25860 }
25861
25862 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25863 let settings = ThemeSettings::get_global(cx);
25864 let text_style = TextStyle {
25865 color: if self.prompt.read(cx).read_only(cx) {
25866 cx.theme().colors().text_disabled
25867 } else {
25868 cx.theme().colors().text
25869 },
25870 font_family: settings.buffer_font.family.clone(),
25871 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25872 font_size: settings.buffer_font_size(cx).into(),
25873 font_weight: settings.buffer_font.weight,
25874 line_height: relative(settings.buffer_line_height.value()),
25875 ..Default::default()
25876 };
25877 EditorElement::new(
25878 &self.prompt,
25879 EditorStyle {
25880 background: cx.theme().colors().editor_background,
25881 local_player: cx.theme().players().local(),
25882 text: text_style,
25883 ..Default::default()
25884 },
25885 )
25886 }
25887}
25888
25889impl Render for BreakpointPromptEditor {
25890 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25891 let editor_margins = *self.editor_margins.lock();
25892 let gutter_dimensions = editor_margins.gutter;
25893 h_flex()
25894 .key_context("Editor")
25895 .bg(cx.theme().colors().editor_background)
25896 .border_y_1()
25897 .border_color(cx.theme().status().info_border)
25898 .size_full()
25899 .py(window.line_height() / 2.5)
25900 .on_action(cx.listener(Self::confirm))
25901 .on_action(cx.listener(Self::cancel))
25902 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25903 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25904 }
25905}
25906
25907impl Focusable for BreakpointPromptEditor {
25908 fn focus_handle(&self, cx: &App) -> FocusHandle {
25909 self.prompt.focus_handle(cx)
25910 }
25911}
25912
25913fn all_edits_insertions_or_deletions(
25914 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25915 snapshot: &MultiBufferSnapshot,
25916) -> bool {
25917 let mut all_insertions = true;
25918 let mut all_deletions = true;
25919
25920 for (range, new_text) in edits.iter() {
25921 let range_is_empty = range.to_offset(snapshot).is_empty();
25922 let text_is_empty = new_text.is_empty();
25923
25924 if range_is_empty != text_is_empty {
25925 if range_is_empty {
25926 all_deletions = false;
25927 } else {
25928 all_insertions = false;
25929 }
25930 } else {
25931 return false;
25932 }
25933
25934 if !all_insertions && !all_deletions {
25935 return false;
25936 }
25937 }
25938 all_insertions || all_deletions
25939}
25940
25941struct MissingEditPredictionKeybindingTooltip;
25942
25943impl Render for MissingEditPredictionKeybindingTooltip {
25944 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25945 ui::tooltip_container(cx, |container, cx| {
25946 container
25947 .flex_shrink_0()
25948 .max_w_80()
25949 .min_h(rems_from_px(124.))
25950 .justify_between()
25951 .child(
25952 v_flex()
25953 .flex_1()
25954 .text_ui_sm(cx)
25955 .child(Label::new("Conflict with Accept Keybinding"))
25956 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25957 )
25958 .child(
25959 h_flex()
25960 .pb_1()
25961 .gap_1()
25962 .items_end()
25963 .w_full()
25964 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25965 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25966 }))
25967 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25968 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25969 })),
25970 )
25971 })
25972 }
25973}
25974
25975#[derive(Debug, Clone, Copy, PartialEq)]
25976pub struct LineHighlight {
25977 pub background: Background,
25978 pub border: Option<gpui::Hsla>,
25979 pub include_gutter: bool,
25980 pub type_id: Option<TypeId>,
25981}
25982
25983struct LineManipulationResult {
25984 pub new_text: String,
25985 pub line_count_before: usize,
25986 pub line_count_after: usize,
25987}
25988
25989fn render_diff_hunk_controls(
25990 row: u32,
25991 status: &DiffHunkStatus,
25992 hunk_range: Range<Anchor>,
25993 is_created_file: bool,
25994 line_height: Pixels,
25995 editor: &Entity<Editor>,
25996 _window: &mut Window,
25997 cx: &mut App,
25998) -> AnyElement {
25999 h_flex()
26000 .h(line_height)
26001 .mr_1()
26002 .gap_1()
26003 .px_0p5()
26004 .pb_1()
26005 .border_x_1()
26006 .border_b_1()
26007 .border_color(cx.theme().colors().border_variant)
26008 .rounded_b_lg()
26009 .bg(cx.theme().colors().editor_background)
26010 .gap_1()
26011 .block_mouse_except_scroll()
26012 .shadow_md()
26013 .child(if status.has_secondary_hunk() {
26014 Button::new(("stage", row as u64), "Stage")
26015 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26016 .tooltip({
26017 let focus_handle = editor.focus_handle(cx);
26018 move |_window, cx| {
26019 Tooltip::for_action_in(
26020 "Stage Hunk",
26021 &::git::ToggleStaged,
26022 &focus_handle,
26023 cx,
26024 )
26025 }
26026 })
26027 .on_click({
26028 let editor = editor.clone();
26029 move |_event, _window, cx| {
26030 editor.update(cx, |editor, cx| {
26031 editor.stage_or_unstage_diff_hunks(
26032 true,
26033 vec![hunk_range.start..hunk_range.start],
26034 cx,
26035 );
26036 });
26037 }
26038 })
26039 } else {
26040 Button::new(("unstage", row as u64), "Unstage")
26041 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26042 .tooltip({
26043 let focus_handle = editor.focus_handle(cx);
26044 move |_window, cx| {
26045 Tooltip::for_action_in(
26046 "Unstage Hunk",
26047 &::git::ToggleStaged,
26048 &focus_handle,
26049 cx,
26050 )
26051 }
26052 })
26053 .on_click({
26054 let editor = editor.clone();
26055 move |_event, _window, cx| {
26056 editor.update(cx, |editor, cx| {
26057 editor.stage_or_unstage_diff_hunks(
26058 false,
26059 vec![hunk_range.start..hunk_range.start],
26060 cx,
26061 );
26062 });
26063 }
26064 })
26065 })
26066 .child(
26067 Button::new(("restore", row as u64), "Restore")
26068 .tooltip({
26069 let focus_handle = editor.focus_handle(cx);
26070 move |_window, cx| {
26071 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26072 }
26073 })
26074 .on_click({
26075 let editor = editor.clone();
26076 move |_event, window, cx| {
26077 editor.update(cx, |editor, cx| {
26078 let snapshot = editor.snapshot(window, cx);
26079 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26080 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26081 });
26082 }
26083 })
26084 .disabled(is_created_file),
26085 )
26086 .when(
26087 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26088 |el| {
26089 el.child(
26090 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26091 .shape(IconButtonShape::Square)
26092 .icon_size(IconSize::Small)
26093 // .disabled(!has_multiple_hunks)
26094 .tooltip({
26095 let focus_handle = editor.focus_handle(cx);
26096 move |_window, cx| {
26097 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26098 }
26099 })
26100 .on_click({
26101 let editor = editor.clone();
26102 move |_event, window, cx| {
26103 editor.update(cx, |editor, cx| {
26104 let snapshot = editor.snapshot(window, cx);
26105 let position =
26106 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26107 editor.go_to_hunk_before_or_after_position(
26108 &snapshot,
26109 position,
26110 Direction::Next,
26111 window,
26112 cx,
26113 );
26114 editor.expand_selected_diff_hunks(cx);
26115 });
26116 }
26117 }),
26118 )
26119 .child(
26120 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26121 .shape(IconButtonShape::Square)
26122 .icon_size(IconSize::Small)
26123 // .disabled(!has_multiple_hunks)
26124 .tooltip({
26125 let focus_handle = editor.focus_handle(cx);
26126 move |_window, cx| {
26127 Tooltip::for_action_in(
26128 "Previous Hunk",
26129 &GoToPreviousHunk,
26130 &focus_handle,
26131 cx,
26132 )
26133 }
26134 })
26135 .on_click({
26136 let editor = editor.clone();
26137 move |_event, window, cx| {
26138 editor.update(cx, |editor, cx| {
26139 let snapshot = editor.snapshot(window, cx);
26140 let point =
26141 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26142 editor.go_to_hunk_before_or_after_position(
26143 &snapshot,
26144 point,
26145 Direction::Prev,
26146 window,
26147 cx,
26148 );
26149 editor.expand_selected_diff_hunks(cx);
26150 });
26151 }
26152 }),
26153 )
26154 },
26155 )
26156 .into_any_element()
26157}
26158
26159pub fn multibuffer_context_lines(cx: &App) -> u32 {
26160 EditorSettings::try_get(cx)
26161 .map(|settings| settings.excerpt_context_lines)
26162 .unwrap_or(2)
26163 .min(32)
26164}