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::{EditPredictionDelegate, EditPredictionDelegateHandle};
96use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
97use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
98use futures::{
99 FutureExt, StreamExt as _,
100 future::{self, Shared, join},
101 stream::FuturesUnordered,
102};
103use fuzzy::{StringMatch, StringMatchCandidate};
104use git::blame::{GitBlame, GlobalBlameRenderer};
105use gpui::{
106 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
107 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
108 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
109 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
110 MouseButton, MouseDownEvent, MouseMoveEvent, PaintQuad, ParentElement, Pixels, Render,
111 ScrollHandle, SharedString, Size, Stateful, Styled, Subscription, Task, TextRun, TextStyle,
112 TextStyleRefinement, UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity,
113 WeakFocusHandle, Window, div, point, prelude::*, pulsating_between, px, relative, size,
114};
115use hover_links::{HoverLink, HoveredLinkState, find_file};
116use hover_popover::{HoverState, hide_hover};
117use indent_guides::ActiveIndentGuidesState;
118use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
119use itertools::{Either, Itertools};
120use language::{
121 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
122 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
123 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
124 IndentSize, Language, LanguageName, LanguageRegistry, OffsetRangeExt, OutlineItem, Point,
125 Runnable, Selection, SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
126 language_settings::{
127 self, LanguageSettings, LspInsertMode, RewrapBehavior, WordsCompletionMode,
128 all_language_settings, language_settings,
129 },
130 point_from_lsp, point_to_lsp, text_diff_with_options,
131};
132use linked_editing_ranges::refresh_linked_ranges;
133use lsp::{
134 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
135 LanguageServerId,
136};
137use lsp_colors::LspColorData;
138use markdown::Markdown;
139use mouse_context_menu::MouseContextMenu;
140use movement::TextLayoutDetails;
141use multi_buffer::{
142 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
143};
144use parking_lot::Mutex;
145use persistence::DB;
146use project::{
147 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
148 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
149 InvalidationStrategy, Location, LocationLink, LspAction, PrepareRenameResponse, Project,
150 ProjectItem, ProjectPath, ProjectTransaction, TaskSourceKind,
151 debugger::{
152 breakpoint_store::{
153 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
154 BreakpointStore, BreakpointStoreEvent,
155 },
156 session::{Session, SessionEvent},
157 },
158 git_store::GitStoreEvent,
159 lsp_store::{
160 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
161 OpenLspBufferHandle,
162 },
163 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
164};
165use rand::seq::SliceRandom;
166use rpc::{ErrorCode, ErrorExt, proto::PeerId};
167use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
168use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
169use serde::{Deserialize, Serialize};
170use settings::{
171 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
172 update_settings_file,
173};
174use smallvec::{SmallVec, smallvec};
175use snippet::Snippet;
176use std::{
177 any::{Any, TypeId},
178 borrow::Cow,
179 cell::{OnceCell, RefCell},
180 cmp::{self, Ordering, Reverse},
181 collections::hash_map,
182 iter::{self, Peekable},
183 mem,
184 num::NonZeroU32,
185 ops::{ControlFlow, Deref, DerefMut, Not, Range, RangeInclusive},
186 path::{Path, PathBuf},
187 rc::Rc,
188 sync::Arc,
189 time::{Duration, Instant},
190};
191use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
192use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
193use theme::{
194 AccentColors, ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
195 observe_buffer_font_size_adjustment,
196};
197use ui::{
198 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
199 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
200};
201use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
202use workspace::{
203 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
204 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
205 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
206 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
207 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
208 searchable::SearchEvent,
209};
210
211use crate::{
212 code_context_menus::CompletionsMenuSource,
213 editor_settings::MultiCursorModifier,
214 hover_links::{find_url, find_url_from_range},
215 inlays::{
216 InlineValueCache,
217 inlay_hints::{LspInlayHintData, inlay_hint_settings},
218 },
219 scroll::{ScrollOffset, ScrollPixelOffset},
220 selections_collection::resolve_selections_wrapping_blocks,
221 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
222};
223
224pub const FILE_HEADER_HEIGHT: u32 = 2;
225pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
226const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
227const MAX_LINE_LEN: usize = 1024;
228const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
229const MAX_SELECTION_HISTORY_LEN: usize = 1024;
230pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
231#[doc(hidden)]
232pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
233pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
234
235pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
236pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
237pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
238pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
239
240pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
241pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
242pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
243
244pub type RenderDiffHunkControlsFn = Arc<
245 dyn Fn(
246 u32,
247 &DiffHunkStatus,
248 Range<Anchor>,
249 bool,
250 Pixels,
251 &Entity<Editor>,
252 &mut Window,
253 &mut App,
254 ) -> AnyElement,
255>;
256
257enum ReportEditorEvent {
258 Saved { auto_saved: bool },
259 EditorOpened,
260 Closed,
261}
262
263impl ReportEditorEvent {
264 pub fn event_type(&self) -> &'static str {
265 match self {
266 Self::Saved { .. } => "Editor Saved",
267 Self::EditorOpened => "Editor Opened",
268 Self::Closed => "Editor Closed",
269 }
270 }
271}
272
273pub enum ActiveDebugLine {}
274pub enum DebugStackFrameLine {}
275enum DocumentHighlightRead {}
276enum DocumentHighlightWrite {}
277enum InputComposition {}
278pub enum PendingInput {}
279enum SelectedTextHighlight {}
280
281pub enum ConflictsOuter {}
282pub enum ConflictsOurs {}
283pub enum ConflictsTheirs {}
284pub enum ConflictsOursMarker {}
285pub enum ConflictsTheirsMarker {}
286
287pub struct HunkAddedColor;
288pub struct HunkRemovedColor;
289
290#[derive(Debug, Copy, Clone, PartialEq, Eq)]
291pub enum Navigated {
292 Yes,
293 No,
294}
295
296impl Navigated {
297 pub fn from_bool(yes: bool) -> Navigated {
298 if yes { Navigated::Yes } else { Navigated::No }
299 }
300}
301
302#[derive(Debug, Clone, PartialEq, Eq)]
303enum DisplayDiffHunk {
304 Folded {
305 display_row: DisplayRow,
306 },
307 Unfolded {
308 is_created_file: bool,
309 diff_base_byte_range: Range<usize>,
310 display_row_range: Range<DisplayRow>,
311 multi_buffer_range: Range<Anchor>,
312 status: DiffHunkStatus,
313 word_diffs: Vec<Range<MultiBufferOffset>>,
314 },
315}
316
317pub enum HideMouseCursorOrigin {
318 TypingAction,
319 MovementAction,
320}
321
322pub fn init(cx: &mut App) {
323 cx.set_global(GlobalBlameRenderer(Arc::new(())));
324
325 workspace::register_project_item::<Editor>(cx);
326 workspace::FollowableViewRegistry::register::<Editor>(cx);
327 workspace::register_serializable_item::<Editor>(cx);
328
329 cx.observe_new(
330 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
331 workspace.register_action(Editor::new_file);
332 workspace.register_action(Editor::new_file_split);
333 workspace.register_action(Editor::new_file_vertical);
334 workspace.register_action(Editor::new_file_horizontal);
335 workspace.register_action(Editor::cancel_language_server_work);
336 workspace.register_action(Editor::toggle_focus);
337 },
338 )
339 .detach();
340
341 cx.on_action(move |_: &workspace::NewFile, cx| {
342 let app_state = workspace::AppState::global(cx);
343 if let Some(app_state) = app_state.upgrade() {
344 workspace::open_new(
345 Default::default(),
346 app_state,
347 cx,
348 |workspace, window, cx| {
349 Editor::new_file(workspace, &Default::default(), window, cx)
350 },
351 )
352 .detach();
353 }
354 });
355 cx.on_action(move |_: &workspace::NewWindow, cx| {
356 let app_state = workspace::AppState::global(cx);
357 if let Some(app_state) = app_state.upgrade() {
358 workspace::open_new(
359 Default::default(),
360 app_state,
361 cx,
362 |workspace, window, cx| {
363 cx.activate(true);
364 Editor::new_file(workspace, &Default::default(), window, cx)
365 },
366 )
367 .detach();
368 }
369 });
370}
371
372pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
373 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
374}
375
376pub trait DiagnosticRenderer {
377 fn render_group(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 buffer_id: BufferId,
381 snapshot: EditorSnapshot,
382 editor: WeakEntity<Editor>,
383 language_registry: Option<Arc<LanguageRegistry>>,
384 cx: &mut App,
385 ) -> Vec<BlockProperties<Anchor>>;
386
387 fn render_hover(
388 &self,
389 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
390 range: Range<Point>,
391 buffer_id: BufferId,
392 language_registry: Option<Arc<LanguageRegistry>>,
393 cx: &mut App,
394 ) -> Option<Entity<markdown::Markdown>>;
395
396 fn open_link(
397 &self,
398 editor: &mut Editor,
399 link: SharedString,
400 window: &mut Window,
401 cx: &mut Context<Editor>,
402 );
403}
404
405pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
406
407impl GlobalDiagnosticRenderer {
408 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
409 cx.try_global::<Self>().map(|g| g.0.clone())
410 }
411}
412
413impl gpui::Global for GlobalDiagnosticRenderer {}
414pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
415 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
416}
417
418pub struct SearchWithinRange;
419
420trait InvalidationRegion {
421 fn ranges(&self) -> &[Range<Anchor>];
422}
423
424#[derive(Clone, Debug, PartialEq)]
425pub enum SelectPhase {
426 Begin {
427 position: DisplayPoint,
428 add: bool,
429 click_count: usize,
430 },
431 BeginColumnar {
432 position: DisplayPoint,
433 reset: bool,
434 mode: ColumnarMode,
435 goal_column: u32,
436 },
437 Extend {
438 position: DisplayPoint,
439 click_count: usize,
440 },
441 Update {
442 position: DisplayPoint,
443 goal_column: u32,
444 scroll_delta: gpui::Point<f32>,
445 },
446 End,
447}
448
449#[derive(Clone, Debug, PartialEq)]
450pub enum ColumnarMode {
451 FromMouse,
452 FromSelection,
453}
454
455#[derive(Clone, Debug)]
456pub enum SelectMode {
457 Character,
458 Word(Range<Anchor>),
459 Line(Range<Anchor>),
460 All,
461}
462
463#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
464pub enum SizingBehavior {
465 /// The editor will layout itself using `size_full` and will include the vertical
466 /// scroll margin as requested by user settings.
467 #[default]
468 Default,
469 /// The editor will layout itself using `size_full`, but will not have any
470 /// vertical overscroll.
471 ExcludeOverscrollMargin,
472 /// The editor will request a vertical size according to its content and will be
473 /// layouted without a vertical scroll margin.
474 SizeByContent,
475}
476
477#[derive(Clone, PartialEq, Eq, Debug)]
478pub enum EditorMode {
479 SingleLine,
480 AutoHeight {
481 min_lines: usize,
482 max_lines: Option<usize>,
483 },
484 Full {
485 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
486 scale_ui_elements_with_buffer_font_size: bool,
487 /// When set to `true`, the editor will render a background for the active line.
488 show_active_line_background: bool,
489 /// Determines the sizing behavior for this editor
490 sizing_behavior: SizingBehavior,
491 },
492 Minimap {
493 parent: WeakEntity<Editor>,
494 },
495}
496
497impl EditorMode {
498 pub fn full() -> Self {
499 Self::Full {
500 scale_ui_elements_with_buffer_font_size: true,
501 show_active_line_background: true,
502 sizing_behavior: SizingBehavior::Default,
503 }
504 }
505
506 #[inline]
507 pub fn is_full(&self) -> bool {
508 matches!(self, Self::Full { .. })
509 }
510
511 #[inline]
512 pub fn is_single_line(&self) -> bool {
513 matches!(self, Self::SingleLine { .. })
514 }
515
516 #[inline]
517 fn is_minimap(&self) -> bool {
518 matches!(self, Self::Minimap { .. })
519 }
520}
521
522#[derive(Copy, Clone, Debug)]
523pub enum SoftWrap {
524 /// Prefer not to wrap at all.
525 ///
526 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
527 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
528 GitDiff,
529 /// Prefer a single line generally, unless an overly long line is encountered.
530 None,
531 /// Soft wrap lines that exceed the editor width.
532 EditorWidth,
533 /// Soft wrap lines at the preferred line length.
534 Column(u32),
535 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
536 Bounded(u32),
537}
538
539#[derive(Clone)]
540pub struct EditorStyle {
541 pub background: Hsla,
542 pub border: Hsla,
543 pub local_player: PlayerColor,
544 pub text: TextStyle,
545 pub scrollbar_width: Pixels,
546 pub syntax: Arc<SyntaxTheme>,
547 pub status: StatusColors,
548 pub inlay_hints_style: HighlightStyle,
549 pub edit_prediction_styles: EditPredictionStyles,
550 pub unnecessary_code_fade: f32,
551 pub show_underlines: bool,
552}
553
554impl Default for EditorStyle {
555 fn default() -> Self {
556 Self {
557 background: Hsla::default(),
558 border: Hsla::default(),
559 local_player: PlayerColor::default(),
560 text: TextStyle::default(),
561 scrollbar_width: Pixels::default(),
562 syntax: Default::default(),
563 // HACK: Status colors don't have a real default.
564 // We should look into removing the status colors from the editor
565 // style and retrieve them directly from the theme.
566 status: StatusColors::dark(),
567 inlay_hints_style: HighlightStyle::default(),
568 edit_prediction_styles: EditPredictionStyles {
569 insertion: HighlightStyle::default(),
570 whitespace: HighlightStyle::default(),
571 },
572 unnecessary_code_fade: Default::default(),
573 show_underlines: true,
574 }
575 }
576}
577
578pub fn make_inlay_hints_style(cx: &App) -> HighlightStyle {
579 let show_background = language_settings::language_settings(None, None, cx)
580 .inlay_hints
581 .show_background;
582
583 let mut style = cx.theme().syntax().get("hint");
584
585 if style.color.is_none() {
586 style.color = Some(cx.theme().status().hint);
587 }
588
589 if !show_background {
590 style.background_color = None;
591 return style;
592 }
593
594 if style.background_color.is_none() {
595 style.background_color = Some(cx.theme().status().hint_background);
596 }
597
598 style
599}
600
601pub fn make_suggestion_styles(cx: &App) -> EditPredictionStyles {
602 EditPredictionStyles {
603 insertion: HighlightStyle {
604 color: Some(cx.theme().status().predictive),
605 ..HighlightStyle::default()
606 },
607 whitespace: HighlightStyle {
608 background_color: Some(cx.theme().status().created_background),
609 ..HighlightStyle::default()
610 },
611 }
612}
613
614type CompletionId = usize;
615
616pub(crate) enum EditDisplayMode {
617 TabAccept,
618 DiffPopover,
619 Inline,
620}
621
622enum EditPrediction {
623 Edit {
624 edits: Vec<(Range<Anchor>, Arc<str>)>,
625 edit_preview: Option<EditPreview>,
626 display_mode: EditDisplayMode,
627 snapshot: BufferSnapshot,
628 },
629 /// Move to a specific location in the active editor
630 MoveWithin {
631 target: Anchor,
632 snapshot: BufferSnapshot,
633 },
634 /// Move to a specific location in a different editor (not the active one)
635 MoveOutside {
636 target: language::Anchor,
637 snapshot: BufferSnapshot,
638 },
639}
640
641struct EditPredictionState {
642 inlay_ids: Vec<InlayId>,
643 completion: EditPrediction,
644 completion_id: Option<SharedString>,
645 invalidation_range: Option<Range<Anchor>>,
646}
647
648enum EditPredictionSettings {
649 Disabled,
650 Enabled {
651 show_in_menu: bool,
652 preview_requires_modifier: bool,
653 },
654}
655
656enum EditPredictionHighlight {}
657
658#[derive(Debug, Clone)]
659struct InlineDiagnostic {
660 message: SharedString,
661 group_id: usize,
662 is_primary: bool,
663 start: Point,
664 severity: lsp::DiagnosticSeverity,
665}
666
667pub enum MenuEditPredictionsPolicy {
668 Never,
669 ByProvider,
670}
671
672pub enum EditPredictionPreview {
673 /// Modifier is not pressed
674 Inactive { released_too_fast: bool },
675 /// Modifier pressed
676 Active {
677 since: Instant,
678 previous_scroll_position: Option<ScrollAnchor>,
679 },
680}
681
682impl EditPredictionPreview {
683 pub fn released_too_fast(&self) -> bool {
684 match self {
685 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
686 EditPredictionPreview::Active { .. } => false,
687 }
688 }
689
690 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
691 if let EditPredictionPreview::Active {
692 previous_scroll_position,
693 ..
694 } = self
695 {
696 *previous_scroll_position = scroll_position;
697 }
698 }
699}
700
701pub struct ContextMenuOptions {
702 pub min_entries_visible: usize,
703 pub max_entries_visible: usize,
704 pub placement: Option<ContextMenuPlacement>,
705}
706
707#[derive(Debug, Clone, PartialEq, Eq)]
708pub enum ContextMenuPlacement {
709 Above,
710 Below,
711}
712
713#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
714struct EditorActionId(usize);
715
716impl EditorActionId {
717 pub fn post_inc(&mut self) -> Self {
718 let answer = self.0;
719
720 *self = Self(answer + 1);
721
722 Self(answer)
723 }
724}
725
726// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
727// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
728
729type BackgroundHighlight = (
730 Arc<dyn Fn(&usize, &Theme) -> Hsla + Send + Sync>,
731 Arc<[Range<Anchor>]>,
732);
733type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
734
735#[derive(Default)]
736struct ScrollbarMarkerState {
737 scrollbar_size: Size<Pixels>,
738 dirty: bool,
739 markers: Arc<[PaintQuad]>,
740 pending_refresh: Option<Task<Result<()>>>,
741}
742
743impl ScrollbarMarkerState {
744 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
745 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
746 }
747}
748
749#[derive(Clone, Copy, PartialEq, Eq)]
750pub enum MinimapVisibility {
751 Disabled,
752 Enabled {
753 /// The configuration currently present in the users settings.
754 setting_configuration: bool,
755 /// Whether to override the currently set visibility from the users setting.
756 toggle_override: bool,
757 },
758}
759
760impl MinimapVisibility {
761 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
762 if mode.is_full() {
763 Self::Enabled {
764 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
765 toggle_override: false,
766 }
767 } else {
768 Self::Disabled
769 }
770 }
771
772 fn hidden(&self) -> Self {
773 match *self {
774 Self::Enabled {
775 setting_configuration,
776 ..
777 } => Self::Enabled {
778 setting_configuration,
779 toggle_override: setting_configuration,
780 },
781 Self::Disabled => Self::Disabled,
782 }
783 }
784
785 fn disabled(&self) -> bool {
786 matches!(*self, Self::Disabled)
787 }
788
789 fn settings_visibility(&self) -> bool {
790 match *self {
791 Self::Enabled {
792 setting_configuration,
793 ..
794 } => setting_configuration,
795 _ => false,
796 }
797 }
798
799 fn visible(&self) -> bool {
800 match *self {
801 Self::Enabled {
802 setting_configuration,
803 toggle_override,
804 } => setting_configuration ^ toggle_override,
805 _ => false,
806 }
807 }
808
809 fn toggle_visibility(&self) -> Self {
810 match *self {
811 Self::Enabled {
812 toggle_override,
813 setting_configuration,
814 } => Self::Enabled {
815 setting_configuration,
816 toggle_override: !toggle_override,
817 },
818 Self::Disabled => Self::Disabled,
819 }
820 }
821}
822
823#[derive(Debug, Clone, Copy, PartialEq, Eq)]
824pub enum BufferSerialization {
825 All,
826 NonDirtyBuffers,
827}
828
829impl BufferSerialization {
830 fn new(restore_unsaved_buffers: bool) -> Self {
831 if restore_unsaved_buffers {
832 Self::All
833 } else {
834 Self::NonDirtyBuffers
835 }
836 }
837}
838
839#[derive(Clone, Debug)]
840struct RunnableTasks {
841 templates: Vec<(TaskSourceKind, TaskTemplate)>,
842 offset: multi_buffer::Anchor,
843 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
844 column: u32,
845 // Values of all named captures, including those starting with '_'
846 extra_variables: HashMap<String, String>,
847 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
848 context_range: Range<BufferOffset>,
849}
850
851impl RunnableTasks {
852 fn resolve<'a>(
853 &'a self,
854 cx: &'a task::TaskContext,
855 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
856 self.templates.iter().filter_map(|(kind, template)| {
857 template
858 .resolve_task(&kind.to_id_base(), cx)
859 .map(|task| (kind.clone(), task))
860 })
861 }
862}
863
864#[derive(Clone)]
865pub struct ResolvedTasks {
866 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
867 position: Anchor,
868}
869
870/// Addons allow storing per-editor state in other crates (e.g. Vim)
871pub trait Addon: 'static {
872 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
873
874 fn render_buffer_header_controls(
875 &self,
876 _: &ExcerptInfo,
877 _: &Window,
878 _: &App,
879 ) -> Option<AnyElement> {
880 None
881 }
882
883 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
884 None
885 }
886
887 fn to_any(&self) -> &dyn std::any::Any;
888
889 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
890 None
891 }
892}
893
894struct ChangeLocation {
895 current: Option<Vec<Anchor>>,
896 original: Vec<Anchor>,
897}
898impl ChangeLocation {
899 fn locations(&self) -> &[Anchor] {
900 self.current.as_ref().unwrap_or(&self.original)
901 }
902}
903
904/// A set of caret positions, registered when the editor was edited.
905pub struct ChangeList {
906 changes: Vec<ChangeLocation>,
907 /// Currently "selected" change.
908 position: Option<usize>,
909}
910
911impl ChangeList {
912 pub fn new() -> Self {
913 Self {
914 changes: Vec::new(),
915 position: None,
916 }
917 }
918
919 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
920 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
921 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
922 if self.changes.is_empty() {
923 return None;
924 }
925
926 let prev = self.position.unwrap_or(self.changes.len());
927 let next = if direction == Direction::Prev {
928 prev.saturating_sub(count)
929 } else {
930 (prev + count).min(self.changes.len() - 1)
931 };
932 self.position = Some(next);
933 self.changes.get(next).map(|change| change.locations())
934 }
935
936 /// Adds a new change to the list, resetting the change list position.
937 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
938 self.position.take();
939 if let Some(last) = self.changes.last_mut()
940 && group
941 {
942 last.current = Some(new_positions)
943 } else {
944 self.changes.push(ChangeLocation {
945 original: new_positions,
946 current: None,
947 });
948 }
949 }
950
951 pub fn last(&self) -> Option<&[Anchor]> {
952 self.changes.last().map(|change| change.locations())
953 }
954
955 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
956 self.changes.last().map(|change| change.original.as_slice())
957 }
958
959 pub fn invert_last_group(&mut self) {
960 if let Some(last) = self.changes.last_mut()
961 && let Some(current) = last.current.as_mut()
962 {
963 mem::swap(&mut last.original, current);
964 }
965 }
966}
967
968#[derive(Clone)]
969struct InlineBlamePopoverState {
970 scroll_handle: ScrollHandle,
971 commit_message: Option<ParsedCommitMessage>,
972 markdown: Entity<Markdown>,
973}
974
975struct InlineBlamePopover {
976 position: gpui::Point<Pixels>,
977 hide_task: Option<Task<()>>,
978 popover_bounds: Option<Bounds<Pixels>>,
979 popover_state: InlineBlamePopoverState,
980 keyboard_grace: bool,
981}
982
983enum SelectionDragState {
984 /// State when no drag related activity is detected.
985 None,
986 /// State when the mouse is down on a selection that is about to be dragged.
987 ReadyToDrag {
988 selection: Selection<Anchor>,
989 click_position: gpui::Point<Pixels>,
990 mouse_down_time: Instant,
991 },
992 /// State when the mouse is dragging the selection in the editor.
993 Dragging {
994 selection: Selection<Anchor>,
995 drop_cursor: Selection<Anchor>,
996 hide_drop_cursor: bool,
997 },
998}
999
1000enum ColumnarSelectionState {
1001 FromMouse {
1002 selection_tail: Anchor,
1003 display_point: Option<DisplayPoint>,
1004 },
1005 FromSelection {
1006 selection_tail: Anchor,
1007 },
1008}
1009
1010/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1011/// a breakpoint on them.
1012#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1013struct PhantomBreakpointIndicator {
1014 display_row: DisplayRow,
1015 /// There's a small debounce between hovering over the line and showing the indicator.
1016 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1017 is_active: bool,
1018 collides_with_existing_breakpoint: bool,
1019}
1020
1021/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1022///
1023/// See the [module level documentation](self) for more information.
1024pub struct Editor {
1025 focus_handle: FocusHandle,
1026 last_focused_descendant: Option<WeakFocusHandle>,
1027 /// The text buffer being edited
1028 buffer: Entity<MultiBuffer>,
1029 /// Map of how text in the buffer should be displayed.
1030 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1031 pub display_map: Entity<DisplayMap>,
1032 placeholder_display_map: Option<Entity<DisplayMap>>,
1033 pub selections: SelectionsCollection,
1034 pub scroll_manager: ScrollManager,
1035 /// When inline assist editors are linked, they all render cursors because
1036 /// typing enters text into each of them, even the ones that aren't focused.
1037 pub(crate) show_cursor_when_unfocused: bool,
1038 columnar_selection_state: Option<ColumnarSelectionState>,
1039 add_selections_state: Option<AddSelectionsState>,
1040 select_next_state: Option<SelectNextState>,
1041 select_prev_state: Option<SelectNextState>,
1042 selection_history: SelectionHistory,
1043 defer_selection_effects: bool,
1044 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1045 autoclose_regions: Vec<AutocloseRegion>,
1046 snippet_stack: InvalidationStack<SnippetState>,
1047 select_syntax_node_history: SelectSyntaxNodeHistory,
1048 ime_transaction: Option<TransactionId>,
1049 pub diagnostics_max_severity: DiagnosticSeverity,
1050 active_diagnostics: ActiveDiagnostic,
1051 show_inline_diagnostics: bool,
1052 inline_diagnostics_update: Task<()>,
1053 inline_diagnostics_enabled: bool,
1054 diagnostics_enabled: bool,
1055 word_completions_enabled: bool,
1056 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1057 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1058 hard_wrap: Option<usize>,
1059 project: Option<Entity<Project>>,
1060 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1061 completion_provider: Option<Rc<dyn CompletionProvider>>,
1062 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1063 blink_manager: Entity<BlinkManager>,
1064 show_cursor_names: bool,
1065 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1066 pub show_local_selections: bool,
1067 mode: EditorMode,
1068 show_breadcrumbs: bool,
1069 show_gutter: bool,
1070 show_scrollbars: ScrollbarAxes,
1071 minimap_visibility: MinimapVisibility,
1072 offset_content: bool,
1073 disable_expand_excerpt_buttons: bool,
1074 show_line_numbers: Option<bool>,
1075 use_relative_line_numbers: Option<bool>,
1076 show_git_diff_gutter: Option<bool>,
1077 show_code_actions: Option<bool>,
1078 show_runnables: Option<bool>,
1079 show_breakpoints: Option<bool>,
1080 show_wrap_guides: Option<bool>,
1081 show_indent_guides: Option<bool>,
1082 buffers_with_disabled_indent_guides: HashSet<BufferId>,
1083 highlight_order: usize,
1084 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1085 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1086 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1087 scrollbar_marker_state: ScrollbarMarkerState,
1088 active_indent_guides_state: ActiveIndentGuidesState,
1089 nav_history: Option<ItemNavHistory>,
1090 context_menu: RefCell<Option<CodeContextMenu>>,
1091 context_menu_options: Option<ContextMenuOptions>,
1092 mouse_context_menu: Option<MouseContextMenu>,
1093 completion_tasks: Vec<(CompletionId, Task<()>)>,
1094 inline_blame_popover: Option<InlineBlamePopover>,
1095 inline_blame_popover_show_task: Option<Task<()>>,
1096 signature_help_state: SignatureHelpState,
1097 auto_signature_help: Option<bool>,
1098 find_all_references_task_sources: Vec<Anchor>,
1099 next_completion_id: CompletionId,
1100 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1101 code_actions_task: Option<Task<Result<()>>>,
1102 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1103 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1104 document_highlights_task: Option<Task<()>>,
1105 linked_editing_range_task: Option<Task<Option<()>>>,
1106 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1107 pending_rename: Option<RenameState>,
1108 searchable: bool,
1109 cursor_shape: CursorShape,
1110 current_line_highlight: Option<CurrentLineHighlight>,
1111 pub collapse_matches: bool,
1112 autoindent_mode: Option<AutoindentMode>,
1113 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1114 input_enabled: bool,
1115 use_modal_editing: bool,
1116 read_only: bool,
1117 leader_id: Option<CollaboratorId>,
1118 remote_id: Option<ViewId>,
1119 pub hover_state: HoverState,
1120 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1121 gutter_hovered: bool,
1122 hovered_link_state: Option<HoveredLinkState>,
1123 edit_prediction_provider: Option<RegisteredEditPredictionDelegate>,
1124 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1125 active_edit_prediction: Option<EditPredictionState>,
1126 /// Used to prevent flickering as the user types while the menu is open
1127 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1128 edit_prediction_settings: EditPredictionSettings,
1129 edit_predictions_hidden_for_vim_mode: bool,
1130 show_edit_predictions_override: Option<bool>,
1131 show_completions_on_input_override: Option<bool>,
1132 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1133 edit_prediction_preview: EditPredictionPreview,
1134 edit_prediction_indent_conflict: bool,
1135 edit_prediction_requires_modifier_in_indent_conflict: bool,
1136 next_inlay_id: usize,
1137 next_color_inlay_id: usize,
1138 _subscriptions: Vec<Subscription>,
1139 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1140 gutter_dimensions: GutterDimensions,
1141 style: Option<EditorStyle>,
1142 text_style_refinement: Option<TextStyleRefinement>,
1143 next_editor_action_id: EditorActionId,
1144 editor_actions: Rc<
1145 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1146 >,
1147 use_autoclose: bool,
1148 use_auto_surround: bool,
1149 auto_replace_emoji_shortcode: bool,
1150 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1151 show_git_blame_gutter: bool,
1152 show_git_blame_inline: bool,
1153 show_git_blame_inline_delay_task: Option<Task<()>>,
1154 git_blame_inline_enabled: bool,
1155 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1156 buffer_serialization: Option<BufferSerialization>,
1157 show_selection_menu: Option<bool>,
1158 blame: Option<Entity<GitBlame>>,
1159 blame_subscription: Option<Subscription>,
1160 custom_context_menu: Option<
1161 Box<
1162 dyn 'static
1163 + Fn(
1164 &mut Self,
1165 DisplayPoint,
1166 &mut Window,
1167 &mut Context<Self>,
1168 ) -> Option<Entity<ui::ContextMenu>>,
1169 >,
1170 >,
1171 last_bounds: Option<Bounds<Pixels>>,
1172 last_position_map: Option<Rc<PositionMap>>,
1173 expect_bounds_change: Option<Bounds<Pixels>>,
1174 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1175 tasks_update_task: Option<Task<()>>,
1176 breakpoint_store: Option<Entity<BreakpointStore>>,
1177 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1178 hovered_diff_hunk_row: Option<DisplayRow>,
1179 pull_diagnostics_task: Task<()>,
1180 pull_diagnostics_background_task: Task<()>,
1181 in_project_search: bool,
1182 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1183 breadcrumb_header: Option<String>,
1184 focused_block: Option<FocusedBlock>,
1185 next_scroll_position: NextScrollCursorCenterTopBottom,
1186 addons: HashMap<TypeId, Box<dyn Addon>>,
1187 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1188 load_diff_task: Option<Shared<Task<()>>>,
1189 /// Whether we are temporarily displaying a diff other than git's
1190 temporary_diff_override: bool,
1191 selection_mark_mode: bool,
1192 toggle_fold_multiple_buffers: Task<()>,
1193 _scroll_cursor_center_top_bottom_task: Task<()>,
1194 serialize_selections: Task<()>,
1195 serialize_folds: Task<()>,
1196 mouse_cursor_hidden: bool,
1197 minimap: Option<Entity<Self>>,
1198 hide_mouse_mode: HideMouseMode,
1199 pub change_list: ChangeList,
1200 inline_value_cache: InlineValueCache,
1201
1202 selection_drag_state: SelectionDragState,
1203 colors: Option<LspColorData>,
1204 post_scroll_update: Task<()>,
1205 refresh_colors_task: Task<()>,
1206 inlay_hints: Option<LspInlayHintData>,
1207 folding_newlines: Task<()>,
1208 select_next_is_case_sensitive: Option<bool>,
1209 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1210 applicable_language_settings: HashMap<Option<LanguageName>, LanguageSettings>,
1211 accent_data: Option<AccentData>,
1212 fetched_tree_sitter_chunks: HashMap<ExcerptId, HashSet<Range<BufferRow>>>,
1213 use_base_text_line_numbers: bool,
1214}
1215
1216#[derive(Debug, PartialEq)]
1217struct AccentData {
1218 colors: AccentColors,
1219 overrides: Vec<SharedString>,
1220}
1221
1222fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1223 if debounce_ms > 0 {
1224 Some(Duration::from_millis(debounce_ms))
1225 } else {
1226 None
1227 }
1228}
1229
1230#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1231enum NextScrollCursorCenterTopBottom {
1232 #[default]
1233 Center,
1234 Top,
1235 Bottom,
1236}
1237
1238impl NextScrollCursorCenterTopBottom {
1239 fn next(&self) -> Self {
1240 match self {
1241 Self::Center => Self::Top,
1242 Self::Top => Self::Bottom,
1243 Self::Bottom => Self::Center,
1244 }
1245 }
1246}
1247
1248#[derive(Clone)]
1249pub struct EditorSnapshot {
1250 pub mode: EditorMode,
1251 show_gutter: bool,
1252 offset_content: bool,
1253 show_line_numbers: Option<bool>,
1254 show_git_diff_gutter: Option<bool>,
1255 show_code_actions: Option<bool>,
1256 show_runnables: Option<bool>,
1257 show_breakpoints: Option<bool>,
1258 git_blame_gutter_max_author_length: Option<usize>,
1259 pub display_snapshot: DisplaySnapshot,
1260 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1261 is_focused: bool,
1262 scroll_anchor: ScrollAnchor,
1263 ongoing_scroll: OngoingScroll,
1264 current_line_highlight: CurrentLineHighlight,
1265 gutter_hovered: bool,
1266}
1267
1268#[derive(Default, Debug, Clone, Copy)]
1269pub struct GutterDimensions {
1270 pub left_padding: Pixels,
1271 pub right_padding: Pixels,
1272 pub width: Pixels,
1273 pub margin: Pixels,
1274 pub git_blame_entries_width: Option<Pixels>,
1275}
1276
1277impl GutterDimensions {
1278 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1279 Self {
1280 margin: Self::default_gutter_margin(font_id, font_size, cx),
1281 ..Default::default()
1282 }
1283 }
1284
1285 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1286 -cx.text_system().descent(font_id, font_size)
1287 }
1288 /// The full width of the space taken up by the gutter.
1289 pub fn full_width(&self) -> Pixels {
1290 self.margin + self.width
1291 }
1292
1293 /// The width of the space reserved for the fold indicators,
1294 /// use alongside 'justify_end' and `gutter_width` to
1295 /// right align content with the line numbers
1296 pub fn fold_area_width(&self) -> Pixels {
1297 self.margin + self.right_padding
1298 }
1299}
1300
1301struct CharacterDimensions {
1302 em_width: Pixels,
1303 em_advance: Pixels,
1304 line_height: Pixels,
1305}
1306
1307#[derive(Debug)]
1308pub struct RemoteSelection {
1309 pub replica_id: ReplicaId,
1310 pub selection: Selection<Anchor>,
1311 pub cursor_shape: CursorShape,
1312 pub collaborator_id: CollaboratorId,
1313 pub line_mode: bool,
1314 pub user_name: Option<SharedString>,
1315 pub color: PlayerColor,
1316}
1317
1318#[derive(Clone, Debug)]
1319struct SelectionHistoryEntry {
1320 selections: Arc<[Selection<Anchor>]>,
1321 select_next_state: Option<SelectNextState>,
1322 select_prev_state: Option<SelectNextState>,
1323 add_selections_state: Option<AddSelectionsState>,
1324}
1325
1326#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
1327enum SelectionHistoryMode {
1328 #[default]
1329 Normal,
1330 Undoing,
1331 Redoing,
1332 Skipping,
1333}
1334
1335#[derive(Clone, PartialEq, Eq, Hash)]
1336struct HoveredCursor {
1337 replica_id: ReplicaId,
1338 selection_id: usize,
1339}
1340
1341#[derive(Debug)]
1342/// SelectionEffects controls the side-effects of updating the selection.
1343///
1344/// The default behaviour does "what you mostly want":
1345/// - it pushes to the nav history if the cursor moved by >10 lines
1346/// - it re-triggers completion requests
1347/// - it scrolls to fit
1348///
1349/// You might want to modify these behaviours. For example when doing a "jump"
1350/// like go to definition, we always want to add to nav history; but when scrolling
1351/// in vim mode we never do.
1352///
1353/// Similarly, you might want to disable scrolling if you don't want the viewport to
1354/// move.
1355#[derive(Clone)]
1356pub struct SelectionEffects {
1357 nav_history: Option<bool>,
1358 completions: bool,
1359 scroll: Option<Autoscroll>,
1360}
1361
1362impl Default for SelectionEffects {
1363 fn default() -> Self {
1364 Self {
1365 nav_history: None,
1366 completions: true,
1367 scroll: Some(Autoscroll::fit()),
1368 }
1369 }
1370}
1371impl SelectionEffects {
1372 pub fn scroll(scroll: Autoscroll) -> Self {
1373 Self {
1374 scroll: Some(scroll),
1375 ..Default::default()
1376 }
1377 }
1378
1379 pub fn no_scroll() -> Self {
1380 Self {
1381 scroll: None,
1382 ..Default::default()
1383 }
1384 }
1385
1386 pub fn completions(self, completions: bool) -> Self {
1387 Self {
1388 completions,
1389 ..self
1390 }
1391 }
1392
1393 pub fn nav_history(self, nav_history: bool) -> Self {
1394 Self {
1395 nav_history: Some(nav_history),
1396 ..self
1397 }
1398 }
1399}
1400
1401struct DeferredSelectionEffectsState {
1402 changed: bool,
1403 effects: SelectionEffects,
1404 old_cursor_position: Anchor,
1405 history_entry: SelectionHistoryEntry,
1406}
1407
1408#[derive(Default)]
1409struct SelectionHistory {
1410 #[allow(clippy::type_complexity)]
1411 selections_by_transaction:
1412 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1413 mode: SelectionHistoryMode,
1414 undo_stack: VecDeque<SelectionHistoryEntry>,
1415 redo_stack: VecDeque<SelectionHistoryEntry>,
1416}
1417
1418impl SelectionHistory {
1419 #[track_caller]
1420 fn insert_transaction(
1421 &mut self,
1422 transaction_id: TransactionId,
1423 selections: Arc<[Selection<Anchor>]>,
1424 ) {
1425 if selections.is_empty() {
1426 log::error!(
1427 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1428 std::panic::Location::caller()
1429 );
1430 return;
1431 }
1432 self.selections_by_transaction
1433 .insert(transaction_id, (selections, None));
1434 }
1435
1436 #[allow(clippy::type_complexity)]
1437 fn transaction(
1438 &self,
1439 transaction_id: TransactionId,
1440 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1441 self.selections_by_transaction.get(&transaction_id)
1442 }
1443
1444 #[allow(clippy::type_complexity)]
1445 fn transaction_mut(
1446 &mut self,
1447 transaction_id: TransactionId,
1448 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1449 self.selections_by_transaction.get_mut(&transaction_id)
1450 }
1451
1452 fn push(&mut self, entry: SelectionHistoryEntry) {
1453 if !entry.selections.is_empty() {
1454 match self.mode {
1455 SelectionHistoryMode::Normal => {
1456 self.push_undo(entry);
1457 self.redo_stack.clear();
1458 }
1459 SelectionHistoryMode::Undoing => self.push_redo(entry),
1460 SelectionHistoryMode::Redoing => self.push_undo(entry),
1461 SelectionHistoryMode::Skipping => {}
1462 }
1463 }
1464 }
1465
1466 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1467 if self
1468 .undo_stack
1469 .back()
1470 .is_none_or(|e| e.selections != entry.selections)
1471 {
1472 self.undo_stack.push_back(entry);
1473 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1474 self.undo_stack.pop_front();
1475 }
1476 }
1477 }
1478
1479 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1480 if self
1481 .redo_stack
1482 .back()
1483 .is_none_or(|e| e.selections != entry.selections)
1484 {
1485 self.redo_stack.push_back(entry);
1486 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1487 self.redo_stack.pop_front();
1488 }
1489 }
1490 }
1491}
1492
1493#[derive(Clone, Copy)]
1494pub struct RowHighlightOptions {
1495 pub autoscroll: bool,
1496 pub include_gutter: bool,
1497}
1498
1499impl Default for RowHighlightOptions {
1500 fn default() -> Self {
1501 Self {
1502 autoscroll: Default::default(),
1503 include_gutter: true,
1504 }
1505 }
1506}
1507
1508struct RowHighlight {
1509 index: usize,
1510 range: Range<Anchor>,
1511 color: Hsla,
1512 options: RowHighlightOptions,
1513 type_id: TypeId,
1514}
1515
1516#[derive(Clone, Debug)]
1517struct AddSelectionsState {
1518 groups: Vec<AddSelectionsGroup>,
1519}
1520
1521#[derive(Clone, Debug)]
1522struct AddSelectionsGroup {
1523 above: bool,
1524 stack: Vec<usize>,
1525}
1526
1527#[derive(Clone)]
1528struct SelectNextState {
1529 query: AhoCorasick,
1530 wordwise: bool,
1531 done: bool,
1532}
1533
1534impl std::fmt::Debug for SelectNextState {
1535 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1536 f.debug_struct(std::any::type_name::<Self>())
1537 .field("wordwise", &self.wordwise)
1538 .field("done", &self.done)
1539 .finish()
1540 }
1541}
1542
1543#[derive(Debug)]
1544struct AutocloseRegion {
1545 selection_id: usize,
1546 range: Range<Anchor>,
1547 pair: BracketPair,
1548}
1549
1550#[derive(Debug)]
1551struct SnippetState {
1552 ranges: Vec<Vec<Range<Anchor>>>,
1553 active_index: usize,
1554 choices: Vec<Option<Vec<String>>>,
1555}
1556
1557#[doc(hidden)]
1558pub struct RenameState {
1559 pub range: Range<Anchor>,
1560 pub old_name: Arc<str>,
1561 pub editor: Entity<Editor>,
1562 block_id: CustomBlockId,
1563}
1564
1565struct InvalidationStack<T>(Vec<T>);
1566
1567struct RegisteredEditPredictionDelegate {
1568 provider: Arc<dyn EditPredictionDelegateHandle>,
1569 _subscription: Subscription,
1570}
1571
1572#[derive(Debug, PartialEq, Eq)]
1573pub struct ActiveDiagnosticGroup {
1574 pub active_range: Range<Anchor>,
1575 pub active_message: String,
1576 pub group_id: usize,
1577 pub blocks: HashSet<CustomBlockId>,
1578}
1579
1580#[derive(Debug, PartialEq, Eq)]
1581
1582pub(crate) enum ActiveDiagnostic {
1583 None,
1584 All,
1585 Group(ActiveDiagnosticGroup),
1586}
1587
1588#[derive(Serialize, Deserialize, Clone, Debug)]
1589pub struct ClipboardSelection {
1590 /// The number of bytes in this selection.
1591 pub len: usize,
1592 /// Whether this was a full-line selection.
1593 pub is_entire_line: bool,
1594 /// The indentation of the first line when this content was originally copied.
1595 pub first_line_indent: u32,
1596 #[serde(default)]
1597 pub file_path: Option<PathBuf>,
1598 #[serde(default)]
1599 pub line_range: Option<RangeInclusive<u32>>,
1600}
1601
1602impl ClipboardSelection {
1603 pub fn for_buffer(
1604 len: usize,
1605 is_entire_line: bool,
1606 range: Range<Point>,
1607 buffer: &MultiBufferSnapshot,
1608 project: Option<&Entity<Project>>,
1609 cx: &App,
1610 ) -> Self {
1611 let first_line_indent = buffer
1612 .indent_size_for_line(MultiBufferRow(range.start.row))
1613 .len;
1614
1615 let file_path = util::maybe!({
1616 let project = project?.read(cx);
1617 let file = buffer.file_at(range.start)?;
1618 let project_path = ProjectPath {
1619 worktree_id: file.worktree_id(cx),
1620 path: file.path().clone(),
1621 };
1622 project.absolute_path(&project_path, cx)
1623 });
1624
1625 let line_range = file_path.as_ref().map(|_| range.start.row..=range.end.row);
1626
1627 Self {
1628 len,
1629 is_entire_line,
1630 first_line_indent,
1631 file_path,
1632 line_range,
1633 }
1634 }
1635}
1636
1637// selections, scroll behavior, was newest selection reversed
1638type SelectSyntaxNodeHistoryState = (
1639 Box<[Selection<MultiBufferOffset>]>,
1640 SelectSyntaxNodeScrollBehavior,
1641 bool,
1642);
1643
1644#[derive(Default)]
1645struct SelectSyntaxNodeHistory {
1646 stack: Vec<SelectSyntaxNodeHistoryState>,
1647 // disable temporarily to allow changing selections without losing the stack
1648 pub disable_clearing: bool,
1649}
1650
1651impl SelectSyntaxNodeHistory {
1652 pub fn try_clear(&mut self) {
1653 if !self.disable_clearing {
1654 self.stack.clear();
1655 }
1656 }
1657
1658 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1659 self.stack.push(selection);
1660 }
1661
1662 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1663 self.stack.pop()
1664 }
1665}
1666
1667enum SelectSyntaxNodeScrollBehavior {
1668 CursorTop,
1669 FitSelection,
1670 CursorBottom,
1671}
1672
1673#[derive(Debug)]
1674pub(crate) struct NavigationData {
1675 cursor_anchor: Anchor,
1676 cursor_position: Point,
1677 scroll_anchor: ScrollAnchor,
1678 scroll_top_row: u32,
1679}
1680
1681#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1682pub enum GotoDefinitionKind {
1683 Symbol,
1684 Declaration,
1685 Type,
1686 Implementation,
1687}
1688
1689pub enum FormatTarget {
1690 Buffers(HashSet<Entity<Buffer>>),
1691 Ranges(Vec<Range<MultiBufferPoint>>),
1692}
1693
1694pub(crate) struct FocusedBlock {
1695 id: BlockId,
1696 focus_handle: WeakFocusHandle,
1697}
1698
1699#[derive(Clone, Debug)]
1700enum JumpData {
1701 MultiBufferRow {
1702 row: MultiBufferRow,
1703 line_offset_from_top: u32,
1704 },
1705 MultiBufferPoint {
1706 excerpt_id: ExcerptId,
1707 position: Point,
1708 anchor: text::Anchor,
1709 line_offset_from_top: u32,
1710 },
1711}
1712
1713pub enum MultibufferSelectionMode {
1714 First,
1715 All,
1716}
1717
1718#[derive(Clone, Copy, Debug, Default)]
1719pub struct RewrapOptions {
1720 pub override_language_settings: bool,
1721 pub preserve_existing_whitespace: bool,
1722}
1723
1724impl Editor {
1725 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1726 let buffer = cx.new(|cx| Buffer::local("", cx));
1727 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1728 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1729 }
1730
1731 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1732 let buffer = cx.new(|cx| Buffer::local("", cx));
1733 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1734 Self::new(EditorMode::full(), buffer, None, window, cx)
1735 }
1736
1737 pub fn auto_height(
1738 min_lines: usize,
1739 max_lines: usize,
1740 window: &mut Window,
1741 cx: &mut Context<Self>,
1742 ) -> Self {
1743 let buffer = cx.new(|cx| Buffer::local("", cx));
1744 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1745 Self::new(
1746 EditorMode::AutoHeight {
1747 min_lines,
1748 max_lines: Some(max_lines),
1749 },
1750 buffer,
1751 None,
1752 window,
1753 cx,
1754 )
1755 }
1756
1757 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1758 /// The editor grows as tall as needed to fit its content.
1759 pub fn auto_height_unbounded(
1760 min_lines: usize,
1761 window: &mut Window,
1762 cx: &mut Context<Self>,
1763 ) -> Self {
1764 let buffer = cx.new(|cx| Buffer::local("", cx));
1765 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1766 Self::new(
1767 EditorMode::AutoHeight {
1768 min_lines,
1769 max_lines: None,
1770 },
1771 buffer,
1772 None,
1773 window,
1774 cx,
1775 )
1776 }
1777
1778 pub fn for_buffer(
1779 buffer: Entity<Buffer>,
1780 project: Option<Entity<Project>>,
1781 window: &mut Window,
1782 cx: &mut Context<Self>,
1783 ) -> Self {
1784 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1785 Self::new(EditorMode::full(), buffer, project, window, cx)
1786 }
1787
1788 pub fn for_multibuffer(
1789 buffer: Entity<MultiBuffer>,
1790 project: Option<Entity<Project>>,
1791 window: &mut Window,
1792 cx: &mut Context<Self>,
1793 ) -> Self {
1794 Self::new(EditorMode::full(), buffer, project, window, cx)
1795 }
1796
1797 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1798 let mut clone = Self::new(
1799 self.mode.clone(),
1800 self.buffer.clone(),
1801 self.project.clone(),
1802 window,
1803 cx,
1804 );
1805 self.display_map.update(cx, |display_map, cx| {
1806 let snapshot = display_map.snapshot(cx);
1807 clone.display_map.update(cx, |display_map, cx| {
1808 display_map.set_state(&snapshot, cx);
1809 });
1810 });
1811 clone.folds_did_change(cx);
1812 clone.selections.clone_state(&self.selections);
1813 clone.scroll_manager.clone_state(&self.scroll_manager);
1814 clone.searchable = self.searchable;
1815 clone.read_only = self.read_only;
1816 clone
1817 }
1818
1819 pub fn new(
1820 mode: EditorMode,
1821 buffer: Entity<MultiBuffer>,
1822 project: Option<Entity<Project>>,
1823 window: &mut Window,
1824 cx: &mut Context<Self>,
1825 ) -> Self {
1826 Editor::new_internal(mode, buffer, project, None, window, cx)
1827 }
1828
1829 pub fn sticky_headers(
1830 &self,
1831 style: &EditorStyle,
1832 cx: &App,
1833 ) -> Option<Vec<OutlineItem<Anchor>>> {
1834 let multi_buffer = self.buffer().read(cx);
1835 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1836 let multi_buffer_visible_start = self
1837 .scroll_manager
1838 .anchor()
1839 .anchor
1840 .to_point(&multi_buffer_snapshot);
1841 let max_row = multi_buffer_snapshot.max_point().row;
1842
1843 let start_row = (multi_buffer_visible_start.row).min(max_row);
1844 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1845
1846 if let Some((excerpt_id, _, buffer)) = multi_buffer.read(cx).as_singleton() {
1847 let outline_items = buffer
1848 .outline_items_containing(
1849 Point::new(start_row, 0)..Point::new(end_row, 0),
1850 true,
1851 Some(style.syntax.as_ref()),
1852 )
1853 .into_iter()
1854 .map(|outline_item| OutlineItem {
1855 depth: outline_item.depth,
1856 range: Anchor::range_in_buffer(*excerpt_id, outline_item.range),
1857 source_range_for_text: Anchor::range_in_buffer(
1858 *excerpt_id,
1859 outline_item.source_range_for_text,
1860 ),
1861 text: outline_item.text,
1862 highlight_ranges: outline_item.highlight_ranges,
1863 name_ranges: outline_item.name_ranges,
1864 body_range: outline_item
1865 .body_range
1866 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1867 annotation_range: outline_item
1868 .annotation_range
1869 .map(|range| Anchor::range_in_buffer(*excerpt_id, range)),
1870 });
1871 return Some(outline_items.collect());
1872 }
1873
1874 None
1875 }
1876
1877 fn new_internal(
1878 mode: EditorMode,
1879 multi_buffer: Entity<MultiBuffer>,
1880 project: Option<Entity<Project>>,
1881 display_map: Option<Entity<DisplayMap>>,
1882 window: &mut Window,
1883 cx: &mut Context<Self>,
1884 ) -> Self {
1885 debug_assert!(
1886 display_map.is_none() || mode.is_minimap(),
1887 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1888 );
1889
1890 let full_mode = mode.is_full();
1891 let is_minimap = mode.is_minimap();
1892 let diagnostics_max_severity = if full_mode {
1893 EditorSettings::get_global(cx)
1894 .diagnostics_max_severity
1895 .unwrap_or(DiagnosticSeverity::Hint)
1896 } else {
1897 DiagnosticSeverity::Off
1898 };
1899 let style = window.text_style();
1900 let font_size = style.font_size.to_pixels(window.rem_size());
1901 let editor = cx.entity().downgrade();
1902 let fold_placeholder = FoldPlaceholder {
1903 constrain_width: false,
1904 render: Arc::new(move |fold_id, fold_range, cx| {
1905 let editor = editor.clone();
1906 div()
1907 .id(fold_id)
1908 .bg(cx.theme().colors().ghost_element_background)
1909 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1910 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1911 .rounded_xs()
1912 .size_full()
1913 .cursor_pointer()
1914 .child("⋯")
1915 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1916 .on_click(move |_, _window, cx| {
1917 editor
1918 .update(cx, |editor, cx| {
1919 editor.unfold_ranges(
1920 &[fold_range.start..fold_range.end],
1921 true,
1922 false,
1923 cx,
1924 );
1925 cx.stop_propagation();
1926 })
1927 .ok();
1928 })
1929 .into_any()
1930 }),
1931 merge_adjacent: true,
1932 ..FoldPlaceholder::default()
1933 };
1934 let display_map = display_map.unwrap_or_else(|| {
1935 cx.new(|cx| {
1936 DisplayMap::new(
1937 multi_buffer.clone(),
1938 style.font(),
1939 font_size,
1940 None,
1941 FILE_HEADER_HEIGHT,
1942 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1943 fold_placeholder,
1944 diagnostics_max_severity,
1945 cx,
1946 )
1947 })
1948 });
1949
1950 let selections = SelectionsCollection::new();
1951
1952 let blink_manager = cx.new(|cx| {
1953 let mut blink_manager = BlinkManager::new(
1954 CURSOR_BLINK_INTERVAL,
1955 |cx| EditorSettings::get_global(cx).cursor_blink,
1956 cx,
1957 );
1958 if is_minimap {
1959 blink_manager.disable(cx);
1960 }
1961 blink_manager
1962 });
1963
1964 let soft_wrap_mode_override =
1965 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1966
1967 let mut project_subscriptions = Vec::new();
1968 if full_mode && let Some(project) = project.as_ref() {
1969 project_subscriptions.push(cx.subscribe_in(
1970 project,
1971 window,
1972 |editor, _, event, window, cx| match event {
1973 project::Event::RefreshCodeLens => {
1974 // we always query lens with actions, without storing them, always refreshing them
1975 }
1976 project::Event::RefreshInlayHints {
1977 server_id,
1978 request_id,
1979 } => {
1980 editor.refresh_inlay_hints(
1981 InlayHintRefreshReason::RefreshRequested {
1982 server_id: *server_id,
1983 request_id: *request_id,
1984 },
1985 cx,
1986 );
1987 }
1988 project::Event::LanguageServerRemoved(..) => {
1989 if editor.tasks_update_task.is_none() {
1990 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1991 }
1992 editor.registered_buffers.clear();
1993 editor.register_visible_buffers(cx);
1994 }
1995 project::Event::LanguageServerAdded(..) => {
1996 if editor.tasks_update_task.is_none() {
1997 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1998 }
1999 }
2000 project::Event::SnippetEdit(id, snippet_edits) => {
2001 // todo(lw): Non singletons
2002 if let Some(buffer) = editor.buffer.read(cx).as_singleton() {
2003 let snapshot = buffer.read(cx).snapshot();
2004 let focus_handle = editor.focus_handle(cx);
2005 if snapshot.remote_id() == *id && focus_handle.is_focused(window) {
2006 for (range, snippet) in snippet_edits {
2007 let buffer_range =
2008 language::range_from_lsp(*range).to_offset(&snapshot);
2009 editor
2010 .insert_snippet(
2011 &[MultiBufferOffset(buffer_range.start)
2012 ..MultiBufferOffset(buffer_range.end)],
2013 snippet.clone(),
2014 window,
2015 cx,
2016 )
2017 .ok();
2018 }
2019 }
2020 }
2021 }
2022 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
2023 let buffer_id = *buffer_id;
2024 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
2025 editor.register_buffer(buffer_id, cx);
2026 editor.update_lsp_data(Some(buffer_id), window, cx);
2027 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
2028 refresh_linked_ranges(editor, window, cx);
2029 editor.refresh_code_actions(window, cx);
2030 editor.refresh_document_highlights(cx);
2031 }
2032 }
2033
2034 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
2035 let Some(workspace) = editor.workspace() else {
2036 return;
2037 };
2038 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
2039 else {
2040 return;
2041 };
2042
2043 if active_editor.entity_id() == cx.entity_id() {
2044 let entity_id = cx.entity_id();
2045 workspace.update(cx, |this, cx| {
2046 this.panes_mut()
2047 .iter_mut()
2048 .filter(|pane| pane.entity_id() != entity_id)
2049 .for_each(|p| {
2050 p.update(cx, |pane, _| {
2051 pane.nav_history_mut().rename_item(
2052 entity_id,
2053 project_path.clone(),
2054 abs_path.clone().into(),
2055 );
2056 })
2057 });
2058 });
2059 let edited_buffers_already_open = {
2060 let other_editors: Vec<Entity<Editor>> = workspace
2061 .read(cx)
2062 .panes()
2063 .iter()
2064 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
2065 .filter(|editor| editor.entity_id() != cx.entity_id())
2066 .collect();
2067
2068 transaction.0.keys().all(|buffer| {
2069 other_editors.iter().any(|editor| {
2070 let multi_buffer = editor.read(cx).buffer();
2071 multi_buffer.read(cx).is_singleton()
2072 && multi_buffer.read(cx).as_singleton().map_or(
2073 false,
2074 |singleton| {
2075 singleton.entity_id() == buffer.entity_id()
2076 },
2077 )
2078 })
2079 })
2080 };
2081 if !edited_buffers_already_open {
2082 let workspace = workspace.downgrade();
2083 let transaction = transaction.clone();
2084 cx.defer_in(window, move |_, window, cx| {
2085 cx.spawn_in(window, async move |editor, cx| {
2086 Self::open_project_transaction(
2087 &editor,
2088 workspace,
2089 transaction,
2090 "Rename".to_string(),
2091 cx,
2092 )
2093 .await
2094 .ok()
2095 })
2096 .detach();
2097 });
2098 }
2099 }
2100 }
2101
2102 _ => {}
2103 },
2104 ));
2105 if let Some(task_inventory) = project
2106 .read(cx)
2107 .task_store()
2108 .read(cx)
2109 .task_inventory()
2110 .cloned()
2111 {
2112 project_subscriptions.push(cx.observe_in(
2113 &task_inventory,
2114 window,
2115 |editor, _, window, cx| {
2116 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2117 },
2118 ));
2119 };
2120
2121 project_subscriptions.push(cx.subscribe_in(
2122 &project.read(cx).breakpoint_store(),
2123 window,
2124 |editor, _, event, window, cx| match event {
2125 BreakpointStoreEvent::ClearDebugLines => {
2126 editor.clear_row_highlights::<ActiveDebugLine>();
2127 editor.refresh_inline_values(cx);
2128 }
2129 BreakpointStoreEvent::SetDebugLine => {
2130 if editor.go_to_active_debug_line(window, cx) {
2131 cx.stop_propagation();
2132 }
2133
2134 editor.refresh_inline_values(cx);
2135 }
2136 _ => {}
2137 },
2138 ));
2139 let git_store = project.read(cx).git_store().clone();
2140 let project = project.clone();
2141 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2142 if let GitStoreEvent::RepositoryAdded = event {
2143 this.load_diff_task = Some(
2144 update_uncommitted_diff_for_buffer(
2145 cx.entity(),
2146 &project,
2147 this.buffer.read(cx).all_buffers(),
2148 this.buffer.clone(),
2149 cx,
2150 )
2151 .shared(),
2152 );
2153 }
2154 }));
2155 }
2156
2157 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2158
2159 let inlay_hint_settings =
2160 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2161 let focus_handle = cx.focus_handle();
2162 if !is_minimap {
2163 cx.on_focus(&focus_handle, window, Self::handle_focus)
2164 .detach();
2165 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2166 .detach();
2167 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2168 .detach();
2169 cx.on_blur(&focus_handle, window, Self::handle_blur)
2170 .detach();
2171 cx.observe_pending_input(window, Self::observe_pending_input)
2172 .detach();
2173 }
2174
2175 let show_indent_guides =
2176 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2177 Some(false)
2178 } else {
2179 None
2180 };
2181
2182 let breakpoint_store = match (&mode, project.as_ref()) {
2183 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2184 _ => None,
2185 };
2186
2187 let mut code_action_providers = Vec::new();
2188 let mut load_uncommitted_diff = None;
2189 if let Some(project) = project.clone() {
2190 load_uncommitted_diff = Some(
2191 update_uncommitted_diff_for_buffer(
2192 cx.entity(),
2193 &project,
2194 multi_buffer.read(cx).all_buffers(),
2195 multi_buffer.clone(),
2196 cx,
2197 )
2198 .shared(),
2199 );
2200 code_action_providers.push(Rc::new(project) as Rc<_>);
2201 }
2202
2203 let mut editor = Self {
2204 focus_handle,
2205 show_cursor_when_unfocused: false,
2206 last_focused_descendant: None,
2207 buffer: multi_buffer.clone(),
2208 display_map: display_map.clone(),
2209 placeholder_display_map: None,
2210 selections,
2211 scroll_manager: ScrollManager::new(cx),
2212 columnar_selection_state: None,
2213 add_selections_state: None,
2214 select_next_state: None,
2215 select_prev_state: None,
2216 selection_history: SelectionHistory::default(),
2217 defer_selection_effects: false,
2218 deferred_selection_effects_state: None,
2219 autoclose_regions: Vec::new(),
2220 snippet_stack: InvalidationStack::default(),
2221 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2222 ime_transaction: None,
2223 active_diagnostics: ActiveDiagnostic::None,
2224 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2225 inline_diagnostics_update: Task::ready(()),
2226 inline_diagnostics: Vec::new(),
2227 soft_wrap_mode_override,
2228 diagnostics_max_severity,
2229 hard_wrap: None,
2230 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2231 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2232 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2233 project,
2234 blink_manager: blink_manager.clone(),
2235 show_local_selections: true,
2236 show_scrollbars: ScrollbarAxes {
2237 horizontal: full_mode,
2238 vertical: full_mode,
2239 },
2240 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2241 offset_content: !matches!(mode, EditorMode::SingleLine),
2242 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2243 show_gutter: full_mode,
2244 show_line_numbers: (!full_mode).then_some(false),
2245 use_relative_line_numbers: None,
2246 disable_expand_excerpt_buttons: !full_mode,
2247 show_git_diff_gutter: None,
2248 show_code_actions: None,
2249 show_runnables: None,
2250 show_breakpoints: None,
2251 show_wrap_guides: None,
2252 show_indent_guides,
2253 buffers_with_disabled_indent_guides: HashSet::default(),
2254 highlight_order: 0,
2255 highlighted_rows: HashMap::default(),
2256 background_highlights: HashMap::default(),
2257 gutter_highlights: HashMap::default(),
2258 scrollbar_marker_state: ScrollbarMarkerState::default(),
2259 active_indent_guides_state: ActiveIndentGuidesState::default(),
2260 nav_history: None,
2261 context_menu: RefCell::new(None),
2262 context_menu_options: None,
2263 mouse_context_menu: None,
2264 completion_tasks: Vec::new(),
2265 inline_blame_popover: None,
2266 inline_blame_popover_show_task: None,
2267 signature_help_state: SignatureHelpState::default(),
2268 auto_signature_help: None,
2269 find_all_references_task_sources: Vec::new(),
2270 next_completion_id: 0,
2271 next_inlay_id: 0,
2272 code_action_providers,
2273 available_code_actions: None,
2274 code_actions_task: None,
2275 quick_selection_highlight_task: None,
2276 debounced_selection_highlight_task: None,
2277 document_highlights_task: None,
2278 linked_editing_range_task: None,
2279 pending_rename: None,
2280 searchable: !is_minimap,
2281 cursor_shape: EditorSettings::get_global(cx)
2282 .cursor_shape
2283 .unwrap_or_default(),
2284 current_line_highlight: None,
2285 autoindent_mode: Some(AutoindentMode::EachLine),
2286 collapse_matches: false,
2287 workspace: None,
2288 input_enabled: !is_minimap,
2289 use_modal_editing: full_mode,
2290 read_only: is_minimap,
2291 use_autoclose: true,
2292 use_auto_surround: true,
2293 auto_replace_emoji_shortcode: false,
2294 jsx_tag_auto_close_enabled_in_any_buffer: false,
2295 leader_id: None,
2296 remote_id: None,
2297 hover_state: HoverState::default(),
2298 pending_mouse_down: None,
2299 hovered_link_state: None,
2300 edit_prediction_provider: None,
2301 active_edit_prediction: None,
2302 stale_edit_prediction_in_menu: None,
2303 edit_prediction_preview: EditPredictionPreview::Inactive {
2304 released_too_fast: false,
2305 },
2306 inline_diagnostics_enabled: full_mode,
2307 diagnostics_enabled: full_mode,
2308 word_completions_enabled: full_mode,
2309 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2310 gutter_hovered: false,
2311 pixel_position_of_newest_cursor: None,
2312 last_bounds: None,
2313 last_position_map: None,
2314 expect_bounds_change: None,
2315 gutter_dimensions: GutterDimensions::default(),
2316 style: None,
2317 show_cursor_names: false,
2318 hovered_cursors: HashMap::default(),
2319 next_editor_action_id: EditorActionId::default(),
2320 editor_actions: Rc::default(),
2321 edit_predictions_hidden_for_vim_mode: false,
2322 show_edit_predictions_override: None,
2323 show_completions_on_input_override: None,
2324 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2325 edit_prediction_settings: EditPredictionSettings::Disabled,
2326 edit_prediction_indent_conflict: false,
2327 edit_prediction_requires_modifier_in_indent_conflict: true,
2328 custom_context_menu: None,
2329 show_git_blame_gutter: false,
2330 show_git_blame_inline: false,
2331 show_selection_menu: None,
2332 show_git_blame_inline_delay_task: None,
2333 git_blame_inline_enabled: full_mode
2334 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2335 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2336 buffer_serialization: is_minimap.not().then(|| {
2337 BufferSerialization::new(
2338 ProjectSettings::get_global(cx)
2339 .session
2340 .restore_unsaved_buffers,
2341 )
2342 }),
2343 blame: None,
2344 blame_subscription: None,
2345 tasks: BTreeMap::default(),
2346
2347 breakpoint_store,
2348 gutter_breakpoint_indicator: (None, None),
2349 hovered_diff_hunk_row: None,
2350 _subscriptions: (!is_minimap)
2351 .then(|| {
2352 vec![
2353 cx.observe(&multi_buffer, Self::on_buffer_changed),
2354 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2355 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2356 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2357 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2358 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2359 cx.observe_window_activation(window, |editor, window, cx| {
2360 let active = window.is_window_active();
2361 editor.blink_manager.update(cx, |blink_manager, cx| {
2362 if active {
2363 blink_manager.enable(cx);
2364 } else {
2365 blink_manager.disable(cx);
2366 }
2367 });
2368 if active {
2369 editor.show_mouse_cursor(cx);
2370 }
2371 }),
2372 ]
2373 })
2374 .unwrap_or_default(),
2375 tasks_update_task: None,
2376 pull_diagnostics_task: Task::ready(()),
2377 pull_diagnostics_background_task: Task::ready(()),
2378 colors: None,
2379 refresh_colors_task: Task::ready(()),
2380 inlay_hints: None,
2381 next_color_inlay_id: 0,
2382 post_scroll_update: Task::ready(()),
2383 linked_edit_ranges: Default::default(),
2384 in_project_search: false,
2385 previous_search_ranges: None,
2386 breadcrumb_header: None,
2387 focused_block: None,
2388 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2389 addons: HashMap::default(),
2390 registered_buffers: HashMap::default(),
2391 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2392 selection_mark_mode: false,
2393 toggle_fold_multiple_buffers: Task::ready(()),
2394 serialize_selections: Task::ready(()),
2395 serialize_folds: Task::ready(()),
2396 text_style_refinement: None,
2397 load_diff_task: load_uncommitted_diff,
2398 temporary_diff_override: false,
2399 mouse_cursor_hidden: false,
2400 minimap: None,
2401 hide_mouse_mode: EditorSettings::get_global(cx)
2402 .hide_mouse
2403 .unwrap_or_default(),
2404 change_list: ChangeList::new(),
2405 mode,
2406 selection_drag_state: SelectionDragState::None,
2407 folding_newlines: Task::ready(()),
2408 lookup_key: None,
2409 select_next_is_case_sensitive: None,
2410 applicable_language_settings: HashMap::default(),
2411 accent_data: None,
2412 fetched_tree_sitter_chunks: HashMap::default(),
2413 use_base_text_line_numbers: false,
2414 };
2415
2416 if is_minimap {
2417 return editor;
2418 }
2419
2420 editor.applicable_language_settings = editor.fetch_applicable_language_settings(cx);
2421 editor.accent_data = editor.fetch_accent_data(cx);
2422
2423 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2424 editor
2425 ._subscriptions
2426 .push(cx.observe(breakpoints, |_, _, cx| {
2427 cx.notify();
2428 }));
2429 }
2430 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2431 editor._subscriptions.extend(project_subscriptions);
2432
2433 editor._subscriptions.push(cx.subscribe_in(
2434 &cx.entity(),
2435 window,
2436 |editor, _, e: &EditorEvent, window, cx| match e {
2437 EditorEvent::ScrollPositionChanged { local, .. } => {
2438 if *local {
2439 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2440 editor.inline_blame_popover.take();
2441 let new_anchor = editor.scroll_manager.anchor();
2442 let snapshot = editor.snapshot(window, cx);
2443 editor.update_restoration_data(cx, move |data| {
2444 data.scroll_position = (
2445 new_anchor.top_row(snapshot.buffer_snapshot()),
2446 new_anchor.offset,
2447 );
2448 });
2449
2450 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2451 cx.background_executor()
2452 .timer(Duration::from_millis(50))
2453 .await;
2454 editor
2455 .update_in(cx, |editor, window, cx| {
2456 editor.register_visible_buffers(cx);
2457 editor.refresh_colors_for_visible_range(None, window, cx);
2458 editor.refresh_inlay_hints(
2459 InlayHintRefreshReason::NewLinesShown,
2460 cx,
2461 );
2462 editor.colorize_brackets(false, cx);
2463 })
2464 .ok();
2465 });
2466 }
2467 }
2468 EditorEvent::Edited { .. } => {
2469 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
2470 .map(|vim_mode| vim_mode.0)
2471 .unwrap_or(false);
2472 if !vim_mode {
2473 let display_map = editor.display_snapshot(cx);
2474 let selections = editor.selections.all_adjusted_display(&display_map);
2475 let pop_state = editor
2476 .change_list
2477 .last()
2478 .map(|previous| {
2479 previous.len() == selections.len()
2480 && previous.iter().enumerate().all(|(ix, p)| {
2481 p.to_display_point(&display_map).row()
2482 == selections[ix].head().row()
2483 })
2484 })
2485 .unwrap_or(false);
2486 let new_positions = selections
2487 .into_iter()
2488 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2489 .collect();
2490 editor
2491 .change_list
2492 .push_to_change_list(pop_state, new_positions);
2493 }
2494 }
2495 _ => (),
2496 },
2497 ));
2498
2499 if let Some(dap_store) = editor
2500 .project
2501 .as_ref()
2502 .map(|project| project.read(cx).dap_store())
2503 {
2504 let weak_editor = cx.weak_entity();
2505
2506 editor
2507 ._subscriptions
2508 .push(
2509 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2510 let session_entity = cx.entity();
2511 weak_editor
2512 .update(cx, |editor, cx| {
2513 editor._subscriptions.push(
2514 cx.subscribe(&session_entity, Self::on_debug_session_event),
2515 );
2516 })
2517 .ok();
2518 }),
2519 );
2520
2521 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2522 editor
2523 ._subscriptions
2524 .push(cx.subscribe(&session, Self::on_debug_session_event));
2525 }
2526 }
2527
2528 // skip adding the initial selection to selection history
2529 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2530 editor.end_selection(window, cx);
2531 editor.selection_history.mode = SelectionHistoryMode::Normal;
2532
2533 editor.scroll_manager.show_scrollbars(window, cx);
2534 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2535
2536 if full_mode {
2537 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2538 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2539
2540 if editor.git_blame_inline_enabled {
2541 editor.start_git_blame_inline(false, window, cx);
2542 }
2543
2544 editor.go_to_active_debug_line(window, cx);
2545
2546 editor.minimap =
2547 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2548 editor.colors = Some(LspColorData::new(cx));
2549 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2550
2551 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2552 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2553 }
2554 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2555 }
2556
2557 editor
2558 }
2559
2560 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2561 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2562 }
2563
2564 pub fn deploy_mouse_context_menu(
2565 &mut self,
2566 position: gpui::Point<Pixels>,
2567 context_menu: Entity<ContextMenu>,
2568 window: &mut Window,
2569 cx: &mut Context<Self>,
2570 ) {
2571 self.mouse_context_menu = Some(MouseContextMenu::new(
2572 self,
2573 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2574 context_menu,
2575 window,
2576 cx,
2577 ));
2578 }
2579
2580 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2581 self.mouse_context_menu
2582 .as_ref()
2583 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2584 }
2585
2586 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2587 if self
2588 .selections
2589 .pending_anchor()
2590 .is_some_and(|pending_selection| {
2591 let snapshot = self.buffer().read(cx).snapshot(cx);
2592 pending_selection.range().includes(range, &snapshot)
2593 })
2594 {
2595 return true;
2596 }
2597
2598 self.selections
2599 .disjoint_in_range::<MultiBufferOffset>(range.clone(), &self.display_snapshot(cx))
2600 .into_iter()
2601 .any(|selection| {
2602 // This is needed to cover a corner case, if we just check for an existing
2603 // selection in the fold range, having a cursor at the start of the fold
2604 // marks it as selected. Non-empty selections don't cause this.
2605 let length = selection.end - selection.start;
2606 length > 0
2607 })
2608 }
2609
2610 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2611 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2612 }
2613
2614 fn key_context_internal(
2615 &self,
2616 has_active_edit_prediction: bool,
2617 window: &mut Window,
2618 cx: &mut App,
2619 ) -> KeyContext {
2620 let mut key_context = KeyContext::new_with_defaults();
2621 key_context.add("Editor");
2622 let mode = match self.mode {
2623 EditorMode::SingleLine => "single_line",
2624 EditorMode::AutoHeight { .. } => "auto_height",
2625 EditorMode::Minimap { .. } => "minimap",
2626 EditorMode::Full { .. } => "full",
2627 };
2628
2629 if EditorSettings::jupyter_enabled(cx) {
2630 key_context.add("jupyter");
2631 }
2632
2633 key_context.set("mode", mode);
2634 if self.pending_rename.is_some() {
2635 key_context.add("renaming");
2636 }
2637
2638 if let Some(snippet_stack) = self.snippet_stack.last() {
2639 key_context.add("in_snippet");
2640
2641 if snippet_stack.active_index > 0 {
2642 key_context.add("has_previous_tabstop");
2643 }
2644
2645 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2646 key_context.add("has_next_tabstop");
2647 }
2648 }
2649
2650 match self.context_menu.borrow().as_ref() {
2651 Some(CodeContextMenu::Completions(menu)) => {
2652 if menu.visible() {
2653 key_context.add("menu");
2654 key_context.add("showing_completions");
2655 }
2656 }
2657 Some(CodeContextMenu::CodeActions(menu)) => {
2658 if menu.visible() {
2659 key_context.add("menu");
2660 key_context.add("showing_code_actions")
2661 }
2662 }
2663 None => {}
2664 }
2665
2666 if self.signature_help_state.has_multiple_signatures() {
2667 key_context.add("showing_signature_help");
2668 }
2669
2670 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2671 if !self.focus_handle(cx).contains_focused(window, cx)
2672 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2673 {
2674 for addon in self.addons.values() {
2675 addon.extend_key_context(&mut key_context, cx)
2676 }
2677 }
2678
2679 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2680 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2681 Some(
2682 file.full_path(cx)
2683 .extension()?
2684 .to_string_lossy()
2685 .into_owned(),
2686 )
2687 }) {
2688 key_context.set("extension", extension);
2689 }
2690 } else {
2691 key_context.add("multibuffer");
2692 }
2693
2694 if has_active_edit_prediction {
2695 if self.edit_prediction_in_conflict() {
2696 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2697 } else {
2698 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2699 key_context.add("copilot_suggestion");
2700 }
2701 }
2702
2703 if self.selection_mark_mode {
2704 key_context.add("selection_mode");
2705 }
2706
2707 let disjoint = self.selections.disjoint_anchors();
2708 let snapshot = self.snapshot(window, cx);
2709 let snapshot = snapshot.buffer_snapshot();
2710 if self.mode == EditorMode::SingleLine
2711 && let [selection] = disjoint
2712 && selection.start == selection.end
2713 && selection.end.to_offset(snapshot) == snapshot.len()
2714 {
2715 key_context.add("end_of_input");
2716 }
2717
2718 if self.has_any_expanded_diff_hunks(cx) {
2719 key_context.add("diffs_expanded");
2720 }
2721
2722 key_context
2723 }
2724
2725 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2726 self.last_bounds.as_ref()
2727 }
2728
2729 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2730 if self.mouse_cursor_hidden {
2731 self.mouse_cursor_hidden = false;
2732 cx.notify();
2733 }
2734 }
2735
2736 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2737 let hide_mouse_cursor = match origin {
2738 HideMouseCursorOrigin::TypingAction => {
2739 matches!(
2740 self.hide_mouse_mode,
2741 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2742 )
2743 }
2744 HideMouseCursorOrigin::MovementAction => {
2745 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2746 }
2747 };
2748 if self.mouse_cursor_hidden != hide_mouse_cursor {
2749 self.mouse_cursor_hidden = hide_mouse_cursor;
2750 cx.notify();
2751 }
2752 }
2753
2754 pub fn edit_prediction_in_conflict(&self) -> bool {
2755 if !self.show_edit_predictions_in_menu() {
2756 return false;
2757 }
2758
2759 let showing_completions = self
2760 .context_menu
2761 .borrow()
2762 .as_ref()
2763 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2764
2765 showing_completions
2766 || self.edit_prediction_requires_modifier()
2767 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2768 // bindings to insert tab characters.
2769 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2770 }
2771
2772 pub fn accept_edit_prediction_keybind(
2773 &self,
2774 accept_partial: bool,
2775 window: &mut Window,
2776 cx: &mut App,
2777 ) -> AcceptEditPredictionBinding {
2778 let key_context = self.key_context_internal(true, window, cx);
2779 let in_conflict = self.edit_prediction_in_conflict();
2780
2781 let bindings = if accept_partial {
2782 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2783 } else {
2784 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2785 };
2786
2787 // TODO: if the binding contains multiple keystrokes, display all of them, not
2788 // just the first one.
2789 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2790 !in_conflict
2791 || binding
2792 .keystrokes()
2793 .first()
2794 .is_some_and(|keystroke| keystroke.modifiers().modified())
2795 }))
2796 }
2797
2798 pub fn new_file(
2799 workspace: &mut Workspace,
2800 _: &workspace::NewFile,
2801 window: &mut Window,
2802 cx: &mut Context<Workspace>,
2803 ) {
2804 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2805 "Failed to create buffer",
2806 window,
2807 cx,
2808 |e, _, _| match e.error_code() {
2809 ErrorCode::RemoteUpgradeRequired => Some(format!(
2810 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2811 e.error_tag("required").unwrap_or("the latest version")
2812 )),
2813 _ => None,
2814 },
2815 );
2816 }
2817
2818 pub fn new_in_workspace(
2819 workspace: &mut Workspace,
2820 window: &mut Window,
2821 cx: &mut Context<Workspace>,
2822 ) -> Task<Result<Entity<Editor>>> {
2823 let project = workspace.project().clone();
2824 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2825
2826 cx.spawn_in(window, async move |workspace, cx| {
2827 let buffer = create.await?;
2828 workspace.update_in(cx, |workspace, window, cx| {
2829 let editor =
2830 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2831 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2832 editor
2833 })
2834 })
2835 }
2836
2837 fn new_file_vertical(
2838 workspace: &mut Workspace,
2839 _: &workspace::NewFileSplitVertical,
2840 window: &mut Window,
2841 cx: &mut Context<Workspace>,
2842 ) {
2843 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2844 }
2845
2846 fn new_file_horizontal(
2847 workspace: &mut Workspace,
2848 _: &workspace::NewFileSplitHorizontal,
2849 window: &mut Window,
2850 cx: &mut Context<Workspace>,
2851 ) {
2852 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2853 }
2854
2855 fn new_file_split(
2856 workspace: &mut Workspace,
2857 action: &workspace::NewFileSplit,
2858 window: &mut Window,
2859 cx: &mut Context<Workspace>,
2860 ) {
2861 Self::new_file_in_direction(workspace, action.0, window, cx)
2862 }
2863
2864 fn new_file_in_direction(
2865 workspace: &mut Workspace,
2866 direction: SplitDirection,
2867 window: &mut Window,
2868 cx: &mut Context<Workspace>,
2869 ) {
2870 let project = workspace.project().clone();
2871 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2872
2873 cx.spawn_in(window, async move |workspace, cx| {
2874 let buffer = create.await?;
2875 workspace.update_in(cx, move |workspace, window, cx| {
2876 workspace.split_item(
2877 direction,
2878 Box::new(
2879 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2880 ),
2881 window,
2882 cx,
2883 )
2884 })?;
2885 anyhow::Ok(())
2886 })
2887 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2888 match e.error_code() {
2889 ErrorCode::RemoteUpgradeRequired => Some(format!(
2890 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2891 e.error_tag("required").unwrap_or("the latest version")
2892 )),
2893 _ => None,
2894 }
2895 });
2896 }
2897
2898 pub fn leader_id(&self) -> Option<CollaboratorId> {
2899 self.leader_id
2900 }
2901
2902 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2903 &self.buffer
2904 }
2905
2906 pub fn project(&self) -> Option<&Entity<Project>> {
2907 self.project.as_ref()
2908 }
2909
2910 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2911 self.workspace.as_ref()?.0.upgrade()
2912 }
2913
2914 /// Returns the workspace serialization ID if this editor should be serialized.
2915 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2916 self.workspace
2917 .as_ref()
2918 .filter(|_| self.should_serialize_buffer())
2919 .and_then(|workspace| workspace.1)
2920 }
2921
2922 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2923 self.buffer().read(cx).title(cx)
2924 }
2925
2926 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2927 let git_blame_gutter_max_author_length = self
2928 .render_git_blame_gutter(cx)
2929 .then(|| {
2930 if let Some(blame) = self.blame.as_ref() {
2931 let max_author_length =
2932 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2933 Some(max_author_length)
2934 } else {
2935 None
2936 }
2937 })
2938 .flatten();
2939
2940 EditorSnapshot {
2941 mode: self.mode.clone(),
2942 show_gutter: self.show_gutter,
2943 offset_content: self.offset_content,
2944 show_line_numbers: self.show_line_numbers,
2945 show_git_diff_gutter: self.show_git_diff_gutter,
2946 show_code_actions: self.show_code_actions,
2947 show_runnables: self.show_runnables,
2948 show_breakpoints: self.show_breakpoints,
2949 git_blame_gutter_max_author_length,
2950 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2951 placeholder_display_snapshot: self
2952 .placeholder_display_map
2953 .as_ref()
2954 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2955 scroll_anchor: self.scroll_manager.anchor(),
2956 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2957 is_focused: self.focus_handle.is_focused(window),
2958 current_line_highlight: self
2959 .current_line_highlight
2960 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2961 gutter_hovered: self.gutter_hovered,
2962 }
2963 }
2964
2965 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2966 self.buffer.read(cx).language_at(point, cx)
2967 }
2968
2969 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2970 self.buffer.read(cx).read(cx).file_at(point).cloned()
2971 }
2972
2973 pub fn active_excerpt(
2974 &self,
2975 cx: &App,
2976 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2977 self.buffer
2978 .read(cx)
2979 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2980 }
2981
2982 pub fn mode(&self) -> &EditorMode {
2983 &self.mode
2984 }
2985
2986 pub fn set_mode(&mut self, mode: EditorMode) {
2987 self.mode = mode;
2988 }
2989
2990 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2991 self.collaboration_hub.as_deref()
2992 }
2993
2994 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2995 self.collaboration_hub = Some(hub);
2996 }
2997
2998 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2999 self.in_project_search = in_project_search;
3000 }
3001
3002 pub fn set_custom_context_menu(
3003 &mut self,
3004 f: impl 'static
3005 + Fn(
3006 &mut Self,
3007 DisplayPoint,
3008 &mut Window,
3009 &mut Context<Self>,
3010 ) -> Option<Entity<ui::ContextMenu>>,
3011 ) {
3012 self.custom_context_menu = Some(Box::new(f))
3013 }
3014
3015 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
3016 self.completion_provider = provider;
3017 }
3018
3019 #[cfg(any(test, feature = "test-support"))]
3020 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
3021 self.completion_provider.clone()
3022 }
3023
3024 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
3025 self.semantics_provider.clone()
3026 }
3027
3028 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
3029 self.semantics_provider = provider;
3030 }
3031
3032 pub fn set_edit_prediction_provider<T>(
3033 &mut self,
3034 provider: Option<Entity<T>>,
3035 window: &mut Window,
3036 cx: &mut Context<Self>,
3037 ) where
3038 T: EditPredictionDelegate,
3039 {
3040 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionDelegate {
3041 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
3042 if this.focus_handle.is_focused(window) {
3043 this.update_visible_edit_prediction(window, cx);
3044 }
3045 }),
3046 provider: Arc::new(provider),
3047 });
3048 self.update_edit_prediction_settings(cx);
3049 self.refresh_edit_prediction(false, false, window, cx);
3050 }
3051
3052 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
3053 self.placeholder_display_map
3054 .as_ref()
3055 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
3056 }
3057
3058 pub fn set_placeholder_text(
3059 &mut self,
3060 placeholder_text: &str,
3061 window: &mut Window,
3062 cx: &mut Context<Self>,
3063 ) {
3064 let multibuffer = cx
3065 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
3066
3067 let style = window.text_style();
3068
3069 self.placeholder_display_map = Some(cx.new(|cx| {
3070 DisplayMap::new(
3071 multibuffer,
3072 style.font(),
3073 style.font_size.to_pixels(window.rem_size()),
3074 None,
3075 FILE_HEADER_HEIGHT,
3076 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
3077 Default::default(),
3078 DiagnosticSeverity::Off,
3079 cx,
3080 )
3081 }));
3082 cx.notify();
3083 }
3084
3085 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
3086 self.cursor_shape = cursor_shape;
3087
3088 // Disrupt blink for immediate user feedback that the cursor shape has changed
3089 self.blink_manager.update(cx, BlinkManager::show_cursor);
3090
3091 cx.notify();
3092 }
3093
3094 pub fn cursor_shape(&self) -> CursorShape {
3095 self.cursor_shape
3096 }
3097
3098 pub fn set_current_line_highlight(
3099 &mut self,
3100 current_line_highlight: Option<CurrentLineHighlight>,
3101 ) {
3102 self.current_line_highlight = current_line_highlight;
3103 }
3104
3105 pub fn set_collapse_matches(&mut self, collapse_matches: bool) {
3106 self.collapse_matches = collapse_matches;
3107 }
3108
3109 pub fn range_for_match<T: std::marker::Copy>(&self, range: &Range<T>) -> Range<T> {
3110 if self.collapse_matches {
3111 return range.start..range.start;
3112 }
3113 range.clone()
3114 }
3115
3116 pub fn clip_at_line_ends(&mut self, cx: &mut Context<Self>) -> bool {
3117 self.display_map.read(cx).clip_at_line_ends
3118 }
3119
3120 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3121 if self.display_map.read(cx).clip_at_line_ends != clip {
3122 self.display_map
3123 .update(cx, |map, _| map.clip_at_line_ends = clip);
3124 }
3125 }
3126
3127 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3128 self.input_enabled = input_enabled;
3129 }
3130
3131 pub fn set_edit_predictions_hidden_for_vim_mode(
3132 &mut self,
3133 hidden: bool,
3134 window: &mut Window,
3135 cx: &mut Context<Self>,
3136 ) {
3137 if hidden != self.edit_predictions_hidden_for_vim_mode {
3138 self.edit_predictions_hidden_for_vim_mode = hidden;
3139 if hidden {
3140 self.update_visible_edit_prediction(window, cx);
3141 } else {
3142 self.refresh_edit_prediction(true, false, window, cx);
3143 }
3144 }
3145 }
3146
3147 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3148 self.menu_edit_predictions_policy = value;
3149 }
3150
3151 pub fn set_autoindent(&mut self, autoindent: bool) {
3152 if autoindent {
3153 self.autoindent_mode = Some(AutoindentMode::EachLine);
3154 } else {
3155 self.autoindent_mode = None;
3156 }
3157 }
3158
3159 pub fn read_only(&self, cx: &App) -> bool {
3160 self.read_only || self.buffer.read(cx).read_only()
3161 }
3162
3163 pub fn set_read_only(&mut self, read_only: bool) {
3164 self.read_only = read_only;
3165 }
3166
3167 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3168 self.use_autoclose = autoclose;
3169 }
3170
3171 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3172 self.use_auto_surround = auto_surround;
3173 }
3174
3175 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3176 self.auto_replace_emoji_shortcode = auto_replace;
3177 }
3178
3179 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3180 self.buffer_serialization = should_serialize.then(|| {
3181 BufferSerialization::new(
3182 ProjectSettings::get_global(cx)
3183 .session
3184 .restore_unsaved_buffers,
3185 )
3186 })
3187 }
3188
3189 fn should_serialize_buffer(&self) -> bool {
3190 self.buffer_serialization.is_some()
3191 }
3192
3193 pub fn toggle_edit_predictions(
3194 &mut self,
3195 _: &ToggleEditPrediction,
3196 window: &mut Window,
3197 cx: &mut Context<Self>,
3198 ) {
3199 if self.show_edit_predictions_override.is_some() {
3200 self.set_show_edit_predictions(None, window, cx);
3201 } else {
3202 let show_edit_predictions = !self.edit_predictions_enabled();
3203 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3204 }
3205 }
3206
3207 pub fn set_show_completions_on_input(&mut self, show_completions_on_input: Option<bool>) {
3208 self.show_completions_on_input_override = show_completions_on_input;
3209 }
3210
3211 pub fn set_show_edit_predictions(
3212 &mut self,
3213 show_edit_predictions: Option<bool>,
3214 window: &mut Window,
3215 cx: &mut Context<Self>,
3216 ) {
3217 self.show_edit_predictions_override = show_edit_predictions;
3218 self.update_edit_prediction_settings(cx);
3219
3220 if let Some(false) = show_edit_predictions {
3221 self.discard_edit_prediction(false, cx);
3222 } else {
3223 self.refresh_edit_prediction(false, true, window, cx);
3224 }
3225 }
3226
3227 fn edit_predictions_disabled_in_scope(
3228 &self,
3229 buffer: &Entity<Buffer>,
3230 buffer_position: language::Anchor,
3231 cx: &App,
3232 ) -> bool {
3233 let snapshot = buffer.read(cx).snapshot();
3234 let settings = snapshot.settings_at(buffer_position, cx);
3235
3236 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3237 return false;
3238 };
3239
3240 scope.override_name().is_some_and(|scope_name| {
3241 settings
3242 .edit_predictions_disabled_in
3243 .iter()
3244 .any(|s| s == scope_name)
3245 })
3246 }
3247
3248 pub fn set_use_modal_editing(&mut self, to: bool) {
3249 self.use_modal_editing = to;
3250 }
3251
3252 pub fn use_modal_editing(&self) -> bool {
3253 self.use_modal_editing
3254 }
3255
3256 fn selections_did_change(
3257 &mut self,
3258 local: bool,
3259 old_cursor_position: &Anchor,
3260 effects: SelectionEffects,
3261 window: &mut Window,
3262 cx: &mut Context<Self>,
3263 ) {
3264 window.invalidate_character_coordinates();
3265
3266 // Copy selections to primary selection buffer
3267 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3268 if local {
3269 let selections = self
3270 .selections
3271 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
3272 let buffer_handle = self.buffer.read(cx).read(cx);
3273
3274 let mut text = String::new();
3275 for (index, selection) in selections.iter().enumerate() {
3276 let text_for_selection = buffer_handle
3277 .text_for_range(selection.start..selection.end)
3278 .collect::<String>();
3279
3280 text.push_str(&text_for_selection);
3281 if index != selections.len() - 1 {
3282 text.push('\n');
3283 }
3284 }
3285
3286 if !text.is_empty() {
3287 cx.write_to_primary(ClipboardItem::new_string(text));
3288 }
3289 }
3290
3291 let selection_anchors = self.selections.disjoint_anchors_arc();
3292
3293 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3294 self.buffer.update(cx, |buffer, cx| {
3295 buffer.set_active_selections(
3296 &selection_anchors,
3297 self.selections.line_mode(),
3298 self.cursor_shape,
3299 cx,
3300 )
3301 });
3302 }
3303 let display_map = self
3304 .display_map
3305 .update(cx, |display_map, cx| display_map.snapshot(cx));
3306 let buffer = display_map.buffer_snapshot();
3307 if self.selections.count() == 1 {
3308 self.add_selections_state = None;
3309 }
3310 self.select_next_state = None;
3311 self.select_prev_state = None;
3312 self.select_syntax_node_history.try_clear();
3313 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3314 self.snippet_stack.invalidate(&selection_anchors, buffer);
3315 self.take_rename(false, window, cx);
3316
3317 let newest_selection = self.selections.newest_anchor();
3318 let new_cursor_position = newest_selection.head();
3319 let selection_start = newest_selection.start;
3320
3321 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3322 self.push_to_nav_history(
3323 *old_cursor_position,
3324 Some(new_cursor_position.to_point(buffer)),
3325 false,
3326 effects.nav_history == Some(true),
3327 cx,
3328 );
3329 }
3330
3331 if local {
3332 if let Some(buffer_id) = new_cursor_position.text_anchor.buffer_id {
3333 self.register_buffer(buffer_id, cx);
3334 }
3335
3336 let mut context_menu = self.context_menu.borrow_mut();
3337 let completion_menu = match context_menu.as_ref() {
3338 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3339 Some(CodeContextMenu::CodeActions(_)) => {
3340 *context_menu = None;
3341 None
3342 }
3343 None => None,
3344 };
3345 let completion_position = completion_menu.map(|menu| menu.initial_position);
3346 drop(context_menu);
3347
3348 if effects.completions
3349 && let Some(completion_position) = completion_position
3350 {
3351 let start_offset = selection_start.to_offset(buffer);
3352 let position_matches = start_offset == completion_position.to_offset(buffer);
3353 let continue_showing = if position_matches {
3354 if self.snippet_stack.is_empty() {
3355 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3356 == Some(CharKind::Word)
3357 } else {
3358 // Snippet choices can be shown even when the cursor is in whitespace.
3359 // Dismissing the menu with actions like backspace is handled by
3360 // invalidation regions.
3361 true
3362 }
3363 } else {
3364 false
3365 };
3366
3367 if continue_showing {
3368 self.open_or_update_completions_menu(None, None, false, window, cx);
3369 } else {
3370 self.hide_context_menu(window, cx);
3371 }
3372 }
3373
3374 hide_hover(self, cx);
3375
3376 if old_cursor_position.to_display_point(&display_map).row()
3377 != new_cursor_position.to_display_point(&display_map).row()
3378 {
3379 self.available_code_actions.take();
3380 }
3381 self.refresh_code_actions(window, cx);
3382 self.refresh_document_highlights(cx);
3383 refresh_linked_ranges(self, window, cx);
3384
3385 self.refresh_selected_text_highlights(false, window, cx);
3386 self.refresh_matching_bracket_highlights(window, cx);
3387 self.update_visible_edit_prediction(window, cx);
3388 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3389 self.inline_blame_popover.take();
3390 if self.git_blame_inline_enabled {
3391 self.start_inline_blame_timer(window, cx);
3392 }
3393 }
3394
3395 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3396 cx.emit(EditorEvent::SelectionsChanged { local });
3397
3398 let selections = &self.selections.disjoint_anchors_arc();
3399 if selections.len() == 1 {
3400 cx.emit(SearchEvent::ActiveMatchChanged)
3401 }
3402 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3403 let inmemory_selections = selections
3404 .iter()
3405 .map(|s| {
3406 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3407 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3408 })
3409 .collect();
3410 self.update_restoration_data(cx, |data| {
3411 data.selections = inmemory_selections;
3412 });
3413
3414 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3415 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3416 {
3417 let snapshot = self.buffer().read(cx).snapshot(cx);
3418 let selections = selections.clone();
3419 let background_executor = cx.background_executor().clone();
3420 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3421 self.serialize_selections = cx.background_spawn(async move {
3422 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3423 let db_selections = selections
3424 .iter()
3425 .map(|selection| {
3426 (
3427 selection.start.to_offset(&snapshot).0,
3428 selection.end.to_offset(&snapshot).0,
3429 )
3430 })
3431 .collect();
3432
3433 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3434 .await
3435 .with_context(|| {
3436 format!(
3437 "persisting editor selections for editor {editor_id}, \
3438 workspace {workspace_id:?}"
3439 )
3440 })
3441 .log_err();
3442 });
3443 }
3444 }
3445
3446 cx.notify();
3447 }
3448
3449 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3450 use text::ToOffset as _;
3451 use text::ToPoint as _;
3452
3453 if self.mode.is_minimap()
3454 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3455 {
3456 return;
3457 }
3458
3459 if !self.buffer().read(cx).is_singleton() {
3460 return;
3461 }
3462
3463 let display_snapshot = self
3464 .display_map
3465 .update(cx, |display_map, cx| display_map.snapshot(cx));
3466 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3467 return;
3468 };
3469 let inmemory_folds = display_snapshot
3470 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3471 .map(|fold| {
3472 fold.range.start.text_anchor.to_point(&snapshot)
3473 ..fold.range.end.text_anchor.to_point(&snapshot)
3474 })
3475 .collect();
3476 self.update_restoration_data(cx, |data| {
3477 data.folds = inmemory_folds;
3478 });
3479
3480 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3481 return;
3482 };
3483 let background_executor = cx.background_executor().clone();
3484 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3485 let db_folds = display_snapshot
3486 .folds_in_range(MultiBufferOffset(0)..display_snapshot.buffer_snapshot().len())
3487 .map(|fold| {
3488 (
3489 fold.range.start.text_anchor.to_offset(&snapshot),
3490 fold.range.end.text_anchor.to_offset(&snapshot),
3491 )
3492 })
3493 .collect();
3494 self.serialize_folds = cx.background_spawn(async move {
3495 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3496 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3497 .await
3498 .with_context(|| {
3499 format!(
3500 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3501 )
3502 })
3503 .log_err();
3504 });
3505 }
3506
3507 pub fn sync_selections(
3508 &mut self,
3509 other: Entity<Editor>,
3510 cx: &mut Context<Self>,
3511 ) -> gpui::Subscription {
3512 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3513 if !other_selections.is_empty() {
3514 self.selections
3515 .change_with(&self.display_snapshot(cx), |selections| {
3516 selections.select_anchors(other_selections);
3517 });
3518 }
3519
3520 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3521 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3522 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3523 if other_selections.is_empty() {
3524 return;
3525 }
3526 let snapshot = this.display_snapshot(cx);
3527 this.selections.change_with(&snapshot, |selections| {
3528 selections.select_anchors(other_selections);
3529 });
3530 }
3531 });
3532
3533 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3534 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3535 let these_selections = this.selections.disjoint_anchors().to_vec();
3536 if these_selections.is_empty() {
3537 return;
3538 }
3539 other.update(cx, |other_editor, cx| {
3540 let snapshot = other_editor.display_snapshot(cx);
3541 other_editor
3542 .selections
3543 .change_with(&snapshot, |selections| {
3544 selections.select_anchors(these_selections);
3545 })
3546 });
3547 }
3548 });
3549
3550 Subscription::join(other_subscription, this_subscription)
3551 }
3552
3553 fn unfold_buffers_with_selections(&mut self, cx: &mut Context<Self>) {
3554 if self.buffer().read(cx).is_singleton() {
3555 return;
3556 }
3557 let snapshot = self.buffer.read(cx).snapshot(cx);
3558 let buffer_ids: HashSet<BufferId> = self
3559 .selections
3560 .disjoint_anchor_ranges()
3561 .flat_map(|range| snapshot.buffer_ids_for_range(range))
3562 .collect();
3563 for buffer_id in buffer_ids {
3564 self.unfold_buffer(buffer_id, cx);
3565 }
3566 }
3567
3568 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3569 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3570 /// effects of selection change occur at the end of the transaction.
3571 pub fn change_selections<R>(
3572 &mut self,
3573 effects: SelectionEffects,
3574 window: &mut Window,
3575 cx: &mut Context<Self>,
3576 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3577 ) -> R {
3578 let snapshot = self.display_snapshot(cx);
3579 if let Some(state) = &mut self.deferred_selection_effects_state {
3580 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3581 state.effects.completions = effects.completions;
3582 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3583 let (changed, result) = self.selections.change_with(&snapshot, change);
3584 state.changed |= changed;
3585 return result;
3586 }
3587 let mut state = DeferredSelectionEffectsState {
3588 changed: false,
3589 effects,
3590 old_cursor_position: self.selections.newest_anchor().head(),
3591 history_entry: SelectionHistoryEntry {
3592 selections: self.selections.disjoint_anchors_arc(),
3593 select_next_state: self.select_next_state.clone(),
3594 select_prev_state: self.select_prev_state.clone(),
3595 add_selections_state: self.add_selections_state.clone(),
3596 },
3597 };
3598 let (changed, result) = self.selections.change_with(&snapshot, change);
3599 state.changed = state.changed || changed;
3600 if self.defer_selection_effects {
3601 self.deferred_selection_effects_state = Some(state);
3602 } else {
3603 self.apply_selection_effects(state, window, cx);
3604 }
3605 result
3606 }
3607
3608 /// Defers the effects of selection change, so that the effects of multiple calls to
3609 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3610 /// to selection history and the state of popovers based on selection position aren't
3611 /// erroneously updated.
3612 pub fn with_selection_effects_deferred<R>(
3613 &mut self,
3614 window: &mut Window,
3615 cx: &mut Context<Self>,
3616 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3617 ) -> R {
3618 let already_deferred = self.defer_selection_effects;
3619 self.defer_selection_effects = true;
3620 let result = update(self, window, cx);
3621 if !already_deferred {
3622 self.defer_selection_effects = false;
3623 if let Some(state) = self.deferred_selection_effects_state.take() {
3624 self.apply_selection_effects(state, window, cx);
3625 }
3626 }
3627 result
3628 }
3629
3630 fn apply_selection_effects(
3631 &mut self,
3632 state: DeferredSelectionEffectsState,
3633 window: &mut Window,
3634 cx: &mut Context<Self>,
3635 ) {
3636 if state.changed {
3637 self.selection_history.push(state.history_entry);
3638
3639 if let Some(autoscroll) = state.effects.scroll {
3640 self.request_autoscroll(autoscroll, cx);
3641 }
3642
3643 let old_cursor_position = &state.old_cursor_position;
3644
3645 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3646
3647 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3648 self.show_signature_help(&ShowSignatureHelp, window, cx);
3649 }
3650 }
3651 }
3652
3653 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3654 where
3655 I: IntoIterator<Item = (Range<S>, T)>,
3656 S: ToOffset,
3657 T: Into<Arc<str>>,
3658 {
3659 if self.read_only(cx) {
3660 return;
3661 }
3662
3663 self.buffer
3664 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3665 }
3666
3667 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3668 where
3669 I: IntoIterator<Item = (Range<S>, T)>,
3670 S: ToOffset,
3671 T: Into<Arc<str>>,
3672 {
3673 if self.read_only(cx) {
3674 return;
3675 }
3676
3677 self.buffer.update(cx, |buffer, cx| {
3678 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3679 });
3680 }
3681
3682 pub fn edit_with_block_indent<I, S, T>(
3683 &mut self,
3684 edits: I,
3685 original_indent_columns: Vec<Option<u32>>,
3686 cx: &mut Context<Self>,
3687 ) where
3688 I: IntoIterator<Item = (Range<S>, T)>,
3689 S: ToOffset,
3690 T: Into<Arc<str>>,
3691 {
3692 if self.read_only(cx) {
3693 return;
3694 }
3695
3696 self.buffer.update(cx, |buffer, cx| {
3697 buffer.edit(
3698 edits,
3699 Some(AutoindentMode::Block {
3700 original_indent_columns,
3701 }),
3702 cx,
3703 )
3704 });
3705 }
3706
3707 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3708 self.hide_context_menu(window, cx);
3709
3710 match phase {
3711 SelectPhase::Begin {
3712 position,
3713 add,
3714 click_count,
3715 } => self.begin_selection(position, add, click_count, window, cx),
3716 SelectPhase::BeginColumnar {
3717 position,
3718 goal_column,
3719 reset,
3720 mode,
3721 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3722 SelectPhase::Extend {
3723 position,
3724 click_count,
3725 } => self.extend_selection(position, click_count, window, cx),
3726 SelectPhase::Update {
3727 position,
3728 goal_column,
3729 scroll_delta,
3730 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3731 SelectPhase::End => self.end_selection(window, cx),
3732 }
3733 }
3734
3735 fn extend_selection(
3736 &mut self,
3737 position: DisplayPoint,
3738 click_count: usize,
3739 window: &mut Window,
3740 cx: &mut Context<Self>,
3741 ) {
3742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3743 let tail = self
3744 .selections
3745 .newest::<MultiBufferOffset>(&display_map)
3746 .tail();
3747 let click_count = click_count.max(match self.selections.select_mode() {
3748 SelectMode::Character => 1,
3749 SelectMode::Word(_) => 2,
3750 SelectMode::Line(_) => 3,
3751 SelectMode::All => 4,
3752 });
3753 self.begin_selection(position, false, click_count, window, cx);
3754
3755 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3756
3757 let current_selection = match self.selections.select_mode() {
3758 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3759 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3760 };
3761
3762 let mut pending_selection = self
3763 .selections
3764 .pending_anchor()
3765 .cloned()
3766 .expect("extend_selection not called with pending selection");
3767
3768 if pending_selection
3769 .start
3770 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3771 == Ordering::Greater
3772 {
3773 pending_selection.start = current_selection.start;
3774 }
3775 if pending_selection
3776 .end
3777 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3778 == Ordering::Less
3779 {
3780 pending_selection.end = current_selection.end;
3781 pending_selection.reversed = true;
3782 }
3783
3784 let mut pending_mode = self.selections.pending_mode().unwrap();
3785 match &mut pending_mode {
3786 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3787 _ => {}
3788 }
3789
3790 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3791 SelectionEffects::scroll(Autoscroll::fit())
3792 } else {
3793 SelectionEffects::no_scroll()
3794 };
3795
3796 self.change_selections(effects, window, cx, |s| {
3797 s.set_pending(pending_selection.clone(), pending_mode);
3798 s.set_is_extending(true);
3799 });
3800 }
3801
3802 fn begin_selection(
3803 &mut self,
3804 position: DisplayPoint,
3805 add: bool,
3806 click_count: usize,
3807 window: &mut Window,
3808 cx: &mut Context<Self>,
3809 ) {
3810 if !self.focus_handle.is_focused(window) {
3811 self.last_focused_descendant = None;
3812 window.focus(&self.focus_handle);
3813 }
3814
3815 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3816 let buffer = display_map.buffer_snapshot();
3817 let position = display_map.clip_point(position, Bias::Left);
3818
3819 let start;
3820 let end;
3821 let mode;
3822 let mut auto_scroll;
3823 match click_count {
3824 1 => {
3825 start = buffer.anchor_before(position.to_point(&display_map));
3826 end = start;
3827 mode = SelectMode::Character;
3828 auto_scroll = true;
3829 }
3830 2 => {
3831 let position = display_map
3832 .clip_point(position, Bias::Left)
3833 .to_offset(&display_map, Bias::Left);
3834 let (range, _) = buffer.surrounding_word(position, None);
3835 start = buffer.anchor_before(range.start);
3836 end = buffer.anchor_before(range.end);
3837 mode = SelectMode::Word(start..end);
3838 auto_scroll = true;
3839 }
3840 3 => {
3841 let position = display_map
3842 .clip_point(position, Bias::Left)
3843 .to_point(&display_map);
3844 let line_start = display_map.prev_line_boundary(position).0;
3845 let next_line_start = buffer.clip_point(
3846 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3847 Bias::Left,
3848 );
3849 start = buffer.anchor_before(line_start);
3850 end = buffer.anchor_before(next_line_start);
3851 mode = SelectMode::Line(start..end);
3852 auto_scroll = true;
3853 }
3854 _ => {
3855 start = buffer.anchor_before(MultiBufferOffset(0));
3856 end = buffer.anchor_before(buffer.len());
3857 mode = SelectMode::All;
3858 auto_scroll = false;
3859 }
3860 }
3861 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3862
3863 let point_to_delete: Option<usize> = {
3864 let selected_points: Vec<Selection<Point>> =
3865 self.selections.disjoint_in_range(start..end, &display_map);
3866
3867 if !add || click_count > 1 {
3868 None
3869 } else if !selected_points.is_empty() {
3870 Some(selected_points[0].id)
3871 } else {
3872 let clicked_point_already_selected =
3873 self.selections.disjoint_anchors().iter().find(|selection| {
3874 selection.start.to_point(buffer) == start.to_point(buffer)
3875 || selection.end.to_point(buffer) == end.to_point(buffer)
3876 });
3877
3878 clicked_point_already_selected.map(|selection| selection.id)
3879 }
3880 };
3881
3882 let selections_count = self.selections.count();
3883 let effects = if auto_scroll {
3884 SelectionEffects::default()
3885 } else {
3886 SelectionEffects::no_scroll()
3887 };
3888
3889 self.change_selections(effects, window, cx, |s| {
3890 if let Some(point_to_delete) = point_to_delete {
3891 s.delete(point_to_delete);
3892
3893 if selections_count == 1 {
3894 s.set_pending_anchor_range(start..end, mode);
3895 }
3896 } else {
3897 if !add {
3898 s.clear_disjoint();
3899 }
3900
3901 s.set_pending_anchor_range(start..end, mode);
3902 }
3903 });
3904 }
3905
3906 fn begin_columnar_selection(
3907 &mut self,
3908 position: DisplayPoint,
3909 goal_column: u32,
3910 reset: bool,
3911 mode: ColumnarMode,
3912 window: &mut Window,
3913 cx: &mut Context<Self>,
3914 ) {
3915 if !self.focus_handle.is_focused(window) {
3916 self.last_focused_descendant = None;
3917 window.focus(&self.focus_handle);
3918 }
3919
3920 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3921
3922 if reset {
3923 let pointer_position = display_map
3924 .buffer_snapshot()
3925 .anchor_before(position.to_point(&display_map));
3926
3927 self.change_selections(
3928 SelectionEffects::scroll(Autoscroll::newest()),
3929 window,
3930 cx,
3931 |s| {
3932 s.clear_disjoint();
3933 s.set_pending_anchor_range(
3934 pointer_position..pointer_position,
3935 SelectMode::Character,
3936 );
3937 },
3938 );
3939 };
3940
3941 let tail = self.selections.newest::<Point>(&display_map).tail();
3942 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3943 self.columnar_selection_state = match mode {
3944 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3945 selection_tail: selection_anchor,
3946 display_point: if reset {
3947 if position.column() != goal_column {
3948 Some(DisplayPoint::new(position.row(), goal_column))
3949 } else {
3950 None
3951 }
3952 } else {
3953 None
3954 },
3955 }),
3956 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3957 selection_tail: selection_anchor,
3958 }),
3959 };
3960
3961 if !reset {
3962 self.select_columns(position, goal_column, &display_map, window, cx);
3963 }
3964 }
3965
3966 fn update_selection(
3967 &mut self,
3968 position: DisplayPoint,
3969 goal_column: u32,
3970 scroll_delta: gpui::Point<f32>,
3971 window: &mut Window,
3972 cx: &mut Context<Self>,
3973 ) {
3974 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3975
3976 if self.columnar_selection_state.is_some() {
3977 self.select_columns(position, goal_column, &display_map, window, cx);
3978 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3979 let buffer = display_map.buffer_snapshot();
3980 let head;
3981 let tail;
3982 let mode = self.selections.pending_mode().unwrap();
3983 match &mode {
3984 SelectMode::Character => {
3985 head = position.to_point(&display_map);
3986 tail = pending.tail().to_point(buffer);
3987 }
3988 SelectMode::Word(original_range) => {
3989 let offset = display_map
3990 .clip_point(position, Bias::Left)
3991 .to_offset(&display_map, Bias::Left);
3992 let original_range = original_range.to_offset(buffer);
3993
3994 let head_offset = if buffer.is_inside_word(offset, None)
3995 || original_range.contains(&offset)
3996 {
3997 let (word_range, _) = buffer.surrounding_word(offset, None);
3998 if word_range.start < original_range.start {
3999 word_range.start
4000 } else {
4001 word_range.end
4002 }
4003 } else {
4004 offset
4005 };
4006
4007 head = head_offset.to_point(buffer);
4008 if head_offset <= original_range.start {
4009 tail = original_range.end.to_point(buffer);
4010 } else {
4011 tail = original_range.start.to_point(buffer);
4012 }
4013 }
4014 SelectMode::Line(original_range) => {
4015 let original_range = original_range.to_point(display_map.buffer_snapshot());
4016
4017 let position = display_map
4018 .clip_point(position, Bias::Left)
4019 .to_point(&display_map);
4020 let line_start = display_map.prev_line_boundary(position).0;
4021 let next_line_start = buffer.clip_point(
4022 display_map.next_line_boundary(position).0 + Point::new(1, 0),
4023 Bias::Left,
4024 );
4025
4026 if line_start < original_range.start {
4027 head = line_start
4028 } else {
4029 head = next_line_start
4030 }
4031
4032 if head <= original_range.start {
4033 tail = original_range.end;
4034 } else {
4035 tail = original_range.start;
4036 }
4037 }
4038 SelectMode::All => {
4039 return;
4040 }
4041 };
4042
4043 if head < tail {
4044 pending.start = buffer.anchor_before(head);
4045 pending.end = buffer.anchor_before(tail);
4046 pending.reversed = true;
4047 } else {
4048 pending.start = buffer.anchor_before(tail);
4049 pending.end = buffer.anchor_before(head);
4050 pending.reversed = false;
4051 }
4052
4053 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4054 s.set_pending(pending.clone(), mode);
4055 });
4056 } else {
4057 log::error!("update_selection dispatched with no pending selection");
4058 return;
4059 }
4060
4061 self.apply_scroll_delta(scroll_delta, window, cx);
4062 cx.notify();
4063 }
4064
4065 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
4066 self.columnar_selection_state.take();
4067 if let Some(pending_mode) = self.selections.pending_mode() {
4068 let selections = self
4069 .selections
4070 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
4071 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4072 s.select(selections);
4073 s.clear_pending();
4074 if s.is_extending() {
4075 s.set_is_extending(false);
4076 } else {
4077 s.set_select_mode(pending_mode);
4078 }
4079 });
4080 }
4081 }
4082
4083 fn select_columns(
4084 &mut self,
4085 head: DisplayPoint,
4086 goal_column: u32,
4087 display_map: &DisplaySnapshot,
4088 window: &mut Window,
4089 cx: &mut Context<Self>,
4090 ) {
4091 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
4092 return;
4093 };
4094
4095 let tail = match columnar_state {
4096 ColumnarSelectionState::FromMouse {
4097 selection_tail,
4098 display_point,
4099 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
4100 ColumnarSelectionState::FromSelection { selection_tail } => {
4101 selection_tail.to_display_point(display_map)
4102 }
4103 };
4104
4105 let start_row = cmp::min(tail.row(), head.row());
4106 let end_row = cmp::max(tail.row(), head.row());
4107 let start_column = cmp::min(tail.column(), goal_column);
4108 let end_column = cmp::max(tail.column(), goal_column);
4109 let reversed = start_column < tail.column();
4110
4111 let selection_ranges = (start_row.0..=end_row.0)
4112 .map(DisplayRow)
4113 .filter_map(|row| {
4114 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
4115 || start_column <= display_map.line_len(row))
4116 && !display_map.is_block_line(row)
4117 {
4118 let start = display_map
4119 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
4120 .to_point(display_map);
4121 let end = display_map
4122 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4123 .to_point(display_map);
4124 if reversed {
4125 Some(end..start)
4126 } else {
4127 Some(start..end)
4128 }
4129 } else {
4130 None
4131 }
4132 })
4133 .collect::<Vec<_>>();
4134 if selection_ranges.is_empty() {
4135 return;
4136 }
4137
4138 let ranges = match columnar_state {
4139 ColumnarSelectionState::FromMouse { .. } => {
4140 let mut non_empty_ranges = selection_ranges
4141 .iter()
4142 .filter(|selection_range| selection_range.start != selection_range.end)
4143 .peekable();
4144 if non_empty_ranges.peek().is_some() {
4145 non_empty_ranges.cloned().collect()
4146 } else {
4147 selection_ranges
4148 }
4149 }
4150 _ => selection_ranges,
4151 };
4152
4153 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4154 s.select_ranges(ranges);
4155 });
4156 cx.notify();
4157 }
4158
4159 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4160 self.selections
4161 .all_adjusted(snapshot)
4162 .iter()
4163 .any(|selection| !selection.is_empty())
4164 }
4165
4166 pub fn has_pending_nonempty_selection(&self) -> bool {
4167 let pending_nonempty_selection = match self.selections.pending_anchor() {
4168 Some(Selection { start, end, .. }) => start != end,
4169 None => false,
4170 };
4171
4172 pending_nonempty_selection
4173 || (self.columnar_selection_state.is_some()
4174 && self.selections.disjoint_anchors().len() > 1)
4175 }
4176
4177 pub fn has_pending_selection(&self) -> bool {
4178 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4179 }
4180
4181 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4182 self.selection_mark_mode = false;
4183 self.selection_drag_state = SelectionDragState::None;
4184
4185 if self.dismiss_menus_and_popups(true, window, cx) {
4186 cx.notify();
4187 return;
4188 }
4189 if self.clear_expanded_diff_hunks(cx) {
4190 cx.notify();
4191 return;
4192 }
4193 if self.show_git_blame_gutter {
4194 self.show_git_blame_gutter = false;
4195 cx.notify();
4196 return;
4197 }
4198
4199 if self.mode.is_full()
4200 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4201 {
4202 cx.notify();
4203 return;
4204 }
4205
4206 cx.propagate();
4207 }
4208
4209 pub fn dismiss_menus_and_popups(
4210 &mut self,
4211 is_user_requested: bool,
4212 window: &mut Window,
4213 cx: &mut Context<Self>,
4214 ) -> bool {
4215 let mut dismissed = false;
4216
4217 dismissed |= self.take_rename(false, window, cx).is_some();
4218 dismissed |= self.hide_blame_popover(true, cx);
4219 dismissed |= hide_hover(self, cx);
4220 dismissed |= self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
4221 dismissed |= self.hide_context_menu(window, cx).is_some();
4222 dismissed |= self.mouse_context_menu.take().is_some();
4223 dismissed |= is_user_requested && self.discard_edit_prediction(true, cx);
4224 dismissed |= self.snippet_stack.pop().is_some();
4225
4226 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4227 self.dismiss_diagnostics(cx);
4228 dismissed = true;
4229 }
4230
4231 dismissed
4232 }
4233
4234 fn linked_editing_ranges_for(
4235 &self,
4236 selection: Range<text::Anchor>,
4237 cx: &App,
4238 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4239 if self.linked_edit_ranges.is_empty() {
4240 return None;
4241 }
4242 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4243 selection.end.buffer_id.and_then(|end_buffer_id| {
4244 if selection.start.buffer_id != Some(end_buffer_id) {
4245 return None;
4246 }
4247 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4248 let snapshot = buffer.read(cx).snapshot();
4249 self.linked_edit_ranges
4250 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4251 .map(|ranges| (ranges, snapshot, buffer))
4252 })?;
4253 use text::ToOffset as TO;
4254 // find offset from the start of current range to current cursor position
4255 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4256
4257 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4258 let start_difference = start_offset - start_byte_offset;
4259 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4260 let end_difference = end_offset - start_byte_offset;
4261 // Current range has associated linked ranges.
4262 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4263 for range in linked_ranges.iter() {
4264 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4265 let end_offset = start_offset + end_difference;
4266 let start_offset = start_offset + start_difference;
4267 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4268 continue;
4269 }
4270 if self.selections.disjoint_anchor_ranges().any(|s| {
4271 if s.start.text_anchor.buffer_id != selection.start.buffer_id
4272 || s.end.text_anchor.buffer_id != selection.end.buffer_id
4273 {
4274 return false;
4275 }
4276 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4277 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4278 }) {
4279 continue;
4280 }
4281 let start = buffer_snapshot.anchor_after(start_offset);
4282 let end = buffer_snapshot.anchor_after(end_offset);
4283 linked_edits
4284 .entry(buffer.clone())
4285 .or_default()
4286 .push(start..end);
4287 }
4288 Some(linked_edits)
4289 }
4290
4291 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4292 let text: Arc<str> = text.into();
4293
4294 if self.read_only(cx) {
4295 return;
4296 }
4297
4298 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4299
4300 self.unfold_buffers_with_selections(cx);
4301
4302 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4303 let mut bracket_inserted = false;
4304 let mut edits = Vec::new();
4305 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4306 let mut new_selections = Vec::with_capacity(selections.len());
4307 let mut new_autoclose_regions = Vec::new();
4308 let snapshot = self.buffer.read(cx).read(cx);
4309 let mut clear_linked_edit_ranges = false;
4310
4311 for (selection, autoclose_region) in
4312 self.selections_with_autoclose_regions(selections, &snapshot)
4313 {
4314 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4315 // Determine if the inserted text matches the opening or closing
4316 // bracket of any of this language's bracket pairs.
4317 let mut bracket_pair = None;
4318 let mut is_bracket_pair_start = false;
4319 let mut is_bracket_pair_end = false;
4320 if !text.is_empty() {
4321 let mut bracket_pair_matching_end = None;
4322 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4323 // and they are removing the character that triggered IME popup.
4324 for (pair, enabled) in scope.brackets() {
4325 if !pair.close && !pair.surround {
4326 continue;
4327 }
4328
4329 if enabled && pair.start.ends_with(text.as_ref()) {
4330 let prefix_len = pair.start.len() - text.len();
4331 let preceding_text_matches_prefix = prefix_len == 0
4332 || (selection.start.column >= (prefix_len as u32)
4333 && snapshot.contains_str_at(
4334 Point::new(
4335 selection.start.row,
4336 selection.start.column - (prefix_len as u32),
4337 ),
4338 &pair.start[..prefix_len],
4339 ));
4340 if preceding_text_matches_prefix {
4341 bracket_pair = Some(pair.clone());
4342 is_bracket_pair_start = true;
4343 break;
4344 }
4345 }
4346 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4347 {
4348 // take first bracket pair matching end, but don't break in case a later bracket
4349 // pair matches start
4350 bracket_pair_matching_end = Some(pair.clone());
4351 }
4352 }
4353 if let Some(end) = bracket_pair_matching_end
4354 && bracket_pair.is_none()
4355 {
4356 bracket_pair = Some(end);
4357 is_bracket_pair_end = true;
4358 }
4359 }
4360
4361 if let Some(bracket_pair) = bracket_pair {
4362 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4363 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4364 let auto_surround =
4365 self.use_auto_surround && snapshot_settings.use_auto_surround;
4366 if selection.is_empty() {
4367 if is_bracket_pair_start {
4368 // If the inserted text is a suffix of an opening bracket and the
4369 // selection is preceded by the rest of the opening bracket, then
4370 // insert the closing bracket.
4371 let following_text_allows_autoclose = snapshot
4372 .chars_at(selection.start)
4373 .next()
4374 .is_none_or(|c| scope.should_autoclose_before(c));
4375
4376 let preceding_text_allows_autoclose = selection.start.column == 0
4377 || snapshot
4378 .reversed_chars_at(selection.start)
4379 .next()
4380 .is_none_or(|c| {
4381 bracket_pair.start != bracket_pair.end
4382 || !snapshot
4383 .char_classifier_at(selection.start)
4384 .is_word(c)
4385 });
4386
4387 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4388 && bracket_pair.start.len() == 1
4389 {
4390 let target = bracket_pair.start.chars().next().unwrap();
4391 let current_line_count = snapshot
4392 .reversed_chars_at(selection.start)
4393 .take_while(|&c| c != '\n')
4394 .filter(|&c| c == target)
4395 .count();
4396 current_line_count % 2 == 1
4397 } else {
4398 false
4399 };
4400
4401 if autoclose
4402 && bracket_pair.close
4403 && following_text_allows_autoclose
4404 && preceding_text_allows_autoclose
4405 && !is_closing_quote
4406 {
4407 let anchor = snapshot.anchor_before(selection.end);
4408 new_selections.push((selection.map(|_| anchor), text.len()));
4409 new_autoclose_regions.push((
4410 anchor,
4411 text.len(),
4412 selection.id,
4413 bracket_pair.clone(),
4414 ));
4415 edits.push((
4416 selection.range(),
4417 format!("{}{}", text, bracket_pair.end).into(),
4418 ));
4419 bracket_inserted = true;
4420 continue;
4421 }
4422 }
4423
4424 if let Some(region) = autoclose_region {
4425 // If the selection is followed by an auto-inserted closing bracket,
4426 // then don't insert that closing bracket again; just move the selection
4427 // past the closing bracket.
4428 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4429 && text.as_ref() == region.pair.end.as_str()
4430 && snapshot.contains_str_at(region.range.end, text.as_ref());
4431 if should_skip {
4432 let anchor = snapshot.anchor_after(selection.end);
4433 new_selections
4434 .push((selection.map(|_| anchor), region.pair.end.len()));
4435 continue;
4436 }
4437 }
4438
4439 let always_treat_brackets_as_autoclosed = snapshot
4440 .language_settings_at(selection.start, cx)
4441 .always_treat_brackets_as_autoclosed;
4442 if always_treat_brackets_as_autoclosed
4443 && is_bracket_pair_end
4444 && snapshot.contains_str_at(selection.end, text.as_ref())
4445 {
4446 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4447 // and the inserted text is a closing bracket and the selection is followed
4448 // by the closing bracket then move the selection past the closing bracket.
4449 let anchor = snapshot.anchor_after(selection.end);
4450 new_selections.push((selection.map(|_| anchor), text.len()));
4451 continue;
4452 }
4453 }
4454 // If an opening bracket is 1 character long and is typed while
4455 // text is selected, then surround that text with the bracket pair.
4456 else if auto_surround
4457 && bracket_pair.surround
4458 && is_bracket_pair_start
4459 && bracket_pair.start.chars().count() == 1
4460 {
4461 edits.push((selection.start..selection.start, text.clone()));
4462 edits.push((
4463 selection.end..selection.end,
4464 bracket_pair.end.as_str().into(),
4465 ));
4466 bracket_inserted = true;
4467 new_selections.push((
4468 Selection {
4469 id: selection.id,
4470 start: snapshot.anchor_after(selection.start),
4471 end: snapshot.anchor_before(selection.end),
4472 reversed: selection.reversed,
4473 goal: selection.goal,
4474 },
4475 0,
4476 ));
4477 continue;
4478 }
4479 }
4480 }
4481
4482 if self.auto_replace_emoji_shortcode
4483 && selection.is_empty()
4484 && text.as_ref().ends_with(':')
4485 && let Some(possible_emoji_short_code) =
4486 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4487 && !possible_emoji_short_code.is_empty()
4488 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4489 {
4490 let emoji_shortcode_start = Point::new(
4491 selection.start.row,
4492 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4493 );
4494
4495 // Remove shortcode from buffer
4496 edits.push((
4497 emoji_shortcode_start..selection.start,
4498 "".to_string().into(),
4499 ));
4500 new_selections.push((
4501 Selection {
4502 id: selection.id,
4503 start: snapshot.anchor_after(emoji_shortcode_start),
4504 end: snapshot.anchor_before(selection.start),
4505 reversed: selection.reversed,
4506 goal: selection.goal,
4507 },
4508 0,
4509 ));
4510
4511 // Insert emoji
4512 let selection_start_anchor = snapshot.anchor_after(selection.start);
4513 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4514 edits.push((selection.start..selection.end, emoji.to_string().into()));
4515
4516 continue;
4517 }
4518
4519 // If not handling any auto-close operation, then just replace the selected
4520 // text with the given input and move the selection to the end of the
4521 // newly inserted text.
4522 let anchor = snapshot.anchor_after(selection.end);
4523 if !self.linked_edit_ranges.is_empty() {
4524 let start_anchor = snapshot.anchor_before(selection.start);
4525
4526 let is_word_char = text.chars().next().is_none_or(|char| {
4527 let classifier = snapshot
4528 .char_classifier_at(start_anchor.to_offset(&snapshot))
4529 .scope_context(Some(CharScopeContext::LinkedEdit));
4530 classifier.is_word(char)
4531 });
4532
4533 if is_word_char {
4534 if let Some(ranges) = self
4535 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4536 {
4537 for (buffer, edits) in ranges {
4538 linked_edits
4539 .entry(buffer.clone())
4540 .or_default()
4541 .extend(edits.into_iter().map(|range| (range, text.clone())));
4542 }
4543 }
4544 } else {
4545 clear_linked_edit_ranges = true;
4546 }
4547 }
4548
4549 new_selections.push((selection.map(|_| anchor), 0));
4550 edits.push((selection.start..selection.end, text.clone()));
4551 }
4552
4553 drop(snapshot);
4554
4555 self.transact(window, cx, |this, window, cx| {
4556 if clear_linked_edit_ranges {
4557 this.linked_edit_ranges.clear();
4558 }
4559 let initial_buffer_versions =
4560 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4561
4562 this.buffer.update(cx, |buffer, cx| {
4563 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4564 });
4565 for (buffer, edits) in linked_edits {
4566 buffer.update(cx, |buffer, cx| {
4567 let snapshot = buffer.snapshot();
4568 let edits = edits
4569 .into_iter()
4570 .map(|(range, text)| {
4571 use text::ToPoint as TP;
4572 let end_point = TP::to_point(&range.end, &snapshot);
4573 let start_point = TP::to_point(&range.start, &snapshot);
4574 (start_point..end_point, text)
4575 })
4576 .sorted_by_key(|(range, _)| range.start);
4577 buffer.edit(edits, None, cx);
4578 })
4579 }
4580 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4581 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4582 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4583 let new_selections = resolve_selections_wrapping_blocks::<MultiBufferOffset, _>(
4584 new_anchor_selections,
4585 &map,
4586 )
4587 .zip(new_selection_deltas)
4588 .map(|(selection, delta)| Selection {
4589 id: selection.id,
4590 start: selection.start + delta,
4591 end: selection.end + delta,
4592 reversed: selection.reversed,
4593 goal: SelectionGoal::None,
4594 })
4595 .collect::<Vec<_>>();
4596
4597 let mut i = 0;
4598 for (position, delta, selection_id, pair) in new_autoclose_regions {
4599 let position = position.to_offset(map.buffer_snapshot()) + delta;
4600 let start = map.buffer_snapshot().anchor_before(position);
4601 let end = map.buffer_snapshot().anchor_after(position);
4602 while let Some(existing_state) = this.autoclose_regions.get(i) {
4603 match existing_state
4604 .range
4605 .start
4606 .cmp(&start, map.buffer_snapshot())
4607 {
4608 Ordering::Less => i += 1,
4609 Ordering::Greater => break,
4610 Ordering::Equal => {
4611 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4612 Ordering::Less => i += 1,
4613 Ordering::Equal => break,
4614 Ordering::Greater => break,
4615 }
4616 }
4617 }
4618 }
4619 this.autoclose_regions.insert(
4620 i,
4621 AutocloseRegion {
4622 selection_id,
4623 range: start..end,
4624 pair,
4625 },
4626 );
4627 }
4628
4629 let had_active_edit_prediction = this.has_active_edit_prediction();
4630 this.change_selections(
4631 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4632 window,
4633 cx,
4634 |s| s.select(new_selections),
4635 );
4636
4637 if !bracket_inserted
4638 && let Some(on_type_format_task) =
4639 this.trigger_on_type_formatting(text.to_string(), window, cx)
4640 {
4641 on_type_format_task.detach_and_log_err(cx);
4642 }
4643
4644 let editor_settings = EditorSettings::get_global(cx);
4645 if bracket_inserted
4646 && (editor_settings.auto_signature_help
4647 || editor_settings.show_signature_help_after_edits)
4648 {
4649 this.show_signature_help(&ShowSignatureHelp, window, cx);
4650 }
4651
4652 let trigger_in_words =
4653 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4654 if this.hard_wrap.is_some() {
4655 let latest: Range<Point> = this.selections.newest(&map).range();
4656 if latest.is_empty()
4657 && this
4658 .buffer()
4659 .read(cx)
4660 .snapshot(cx)
4661 .line_len(MultiBufferRow(latest.start.row))
4662 == latest.start.column
4663 {
4664 this.rewrap_impl(
4665 RewrapOptions {
4666 override_language_settings: true,
4667 preserve_existing_whitespace: true,
4668 },
4669 cx,
4670 )
4671 }
4672 }
4673 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4674 refresh_linked_ranges(this, window, cx);
4675 this.refresh_edit_prediction(true, false, window, cx);
4676 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4677 });
4678 }
4679
4680 fn find_possible_emoji_shortcode_at_position(
4681 snapshot: &MultiBufferSnapshot,
4682 position: Point,
4683 ) -> Option<String> {
4684 let mut chars = Vec::new();
4685 let mut found_colon = false;
4686 for char in snapshot.reversed_chars_at(position).take(100) {
4687 // Found a possible emoji shortcode in the middle of the buffer
4688 if found_colon {
4689 if char.is_whitespace() {
4690 chars.reverse();
4691 return Some(chars.iter().collect());
4692 }
4693 // If the previous character is not a whitespace, we are in the middle of a word
4694 // and we only want to complete the shortcode if the word is made up of other emojis
4695 let mut containing_word = String::new();
4696 for ch in snapshot
4697 .reversed_chars_at(position)
4698 .skip(chars.len() + 1)
4699 .take(100)
4700 {
4701 if ch.is_whitespace() {
4702 break;
4703 }
4704 containing_word.push(ch);
4705 }
4706 let containing_word = containing_word.chars().rev().collect::<String>();
4707 if util::word_consists_of_emojis(containing_word.as_str()) {
4708 chars.reverse();
4709 return Some(chars.iter().collect());
4710 }
4711 }
4712
4713 if char.is_whitespace() || !char.is_ascii() {
4714 return None;
4715 }
4716 if char == ':' {
4717 found_colon = true;
4718 } else {
4719 chars.push(char);
4720 }
4721 }
4722 // Found a possible emoji shortcode at the beginning of the buffer
4723 chars.reverse();
4724 Some(chars.iter().collect())
4725 }
4726
4727 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4728 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4729 self.transact(window, cx, |this, window, cx| {
4730 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4731 let selections = this
4732 .selections
4733 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
4734 let multi_buffer = this.buffer.read(cx);
4735 let buffer = multi_buffer.snapshot(cx);
4736 selections
4737 .iter()
4738 .map(|selection| {
4739 let start_point = selection.start.to_point(&buffer);
4740 let mut existing_indent =
4741 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4742 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4743 let start = selection.start;
4744 let end = selection.end;
4745 let selection_is_empty = start == end;
4746 let language_scope = buffer.language_scope_at(start);
4747 let (
4748 comment_delimiter,
4749 doc_delimiter,
4750 insert_extra_newline,
4751 indent_on_newline,
4752 indent_on_extra_newline,
4753 ) = if let Some(language) = &language_scope {
4754 let mut insert_extra_newline =
4755 insert_extra_newline_brackets(&buffer, start..end, language)
4756 || insert_extra_newline_tree_sitter(&buffer, start..end);
4757
4758 // Comment extension on newline is allowed only for cursor selections
4759 let comment_delimiter = maybe!({
4760 if !selection_is_empty {
4761 return None;
4762 }
4763
4764 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4765 return None;
4766 }
4767
4768 let delimiters = language.line_comment_prefixes();
4769 let max_len_of_delimiter =
4770 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4771 let (snapshot, range) =
4772 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4773
4774 let num_of_whitespaces = snapshot
4775 .chars_for_range(range.clone())
4776 .take_while(|c| c.is_whitespace())
4777 .count();
4778 let comment_candidate = snapshot
4779 .chars_for_range(range.clone())
4780 .skip(num_of_whitespaces)
4781 .take(max_len_of_delimiter)
4782 .collect::<String>();
4783 let (delimiter, trimmed_len) = delimiters
4784 .iter()
4785 .filter_map(|delimiter| {
4786 let prefix = delimiter.trim_end();
4787 if comment_candidate.starts_with(prefix) {
4788 Some((delimiter, prefix.len()))
4789 } else {
4790 None
4791 }
4792 })
4793 .max_by_key(|(_, len)| *len)?;
4794
4795 if let Some(BlockCommentConfig {
4796 start: block_start, ..
4797 }) = language.block_comment()
4798 {
4799 let block_start_trimmed = block_start.trim_end();
4800 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4801 let line_content = snapshot
4802 .chars_for_range(range)
4803 .skip(num_of_whitespaces)
4804 .take(block_start_trimmed.len())
4805 .collect::<String>();
4806
4807 if line_content.starts_with(block_start_trimmed) {
4808 return None;
4809 }
4810 }
4811 }
4812
4813 let cursor_is_placed_after_comment_marker =
4814 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4815 if cursor_is_placed_after_comment_marker {
4816 Some(delimiter.clone())
4817 } else {
4818 None
4819 }
4820 });
4821
4822 let mut indent_on_newline = IndentSize::spaces(0);
4823 let mut indent_on_extra_newline = IndentSize::spaces(0);
4824
4825 let doc_delimiter = maybe!({
4826 if !selection_is_empty {
4827 return None;
4828 }
4829
4830 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4831 return None;
4832 }
4833
4834 let BlockCommentConfig {
4835 start: start_tag,
4836 end: end_tag,
4837 prefix: delimiter,
4838 tab_size: len,
4839 } = language.documentation_comment()?;
4840 let is_within_block_comment = buffer
4841 .language_scope_at(start_point)
4842 .is_some_and(|scope| scope.override_name() == Some("comment"));
4843 if !is_within_block_comment {
4844 return None;
4845 }
4846
4847 let (snapshot, range) =
4848 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4849
4850 let num_of_whitespaces = snapshot
4851 .chars_for_range(range.clone())
4852 .take_while(|c| c.is_whitespace())
4853 .count();
4854
4855 // 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.
4856 let column = start_point.column;
4857 let cursor_is_after_start_tag = {
4858 let start_tag_len = start_tag.len();
4859 let start_tag_line = snapshot
4860 .chars_for_range(range.clone())
4861 .skip(num_of_whitespaces)
4862 .take(start_tag_len)
4863 .collect::<String>();
4864 if start_tag_line.starts_with(start_tag.as_ref()) {
4865 num_of_whitespaces + start_tag_len <= column as usize
4866 } else {
4867 false
4868 }
4869 };
4870
4871 let cursor_is_after_delimiter = {
4872 let delimiter_trim = delimiter.trim_end();
4873 let delimiter_line = snapshot
4874 .chars_for_range(range.clone())
4875 .skip(num_of_whitespaces)
4876 .take(delimiter_trim.len())
4877 .collect::<String>();
4878 if delimiter_line.starts_with(delimiter_trim) {
4879 num_of_whitespaces + delimiter_trim.len() <= column as usize
4880 } else {
4881 false
4882 }
4883 };
4884
4885 let cursor_is_before_end_tag_if_exists = {
4886 let mut char_position = 0u32;
4887 let mut end_tag_offset = None;
4888
4889 'outer: for chunk in snapshot.text_for_range(range) {
4890 if let Some(byte_pos) = chunk.find(&**end_tag) {
4891 let chars_before_match =
4892 chunk[..byte_pos].chars().count() as u32;
4893 end_tag_offset =
4894 Some(char_position + chars_before_match);
4895 break 'outer;
4896 }
4897 char_position += chunk.chars().count() as u32;
4898 }
4899
4900 if let Some(end_tag_offset) = end_tag_offset {
4901 let cursor_is_before_end_tag = column <= end_tag_offset;
4902 if cursor_is_after_start_tag {
4903 if cursor_is_before_end_tag {
4904 insert_extra_newline = true;
4905 }
4906 let cursor_is_at_start_of_end_tag =
4907 column == end_tag_offset;
4908 if cursor_is_at_start_of_end_tag {
4909 indent_on_extra_newline.len = *len;
4910 }
4911 }
4912 cursor_is_before_end_tag
4913 } else {
4914 true
4915 }
4916 };
4917
4918 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4919 && cursor_is_before_end_tag_if_exists
4920 {
4921 if cursor_is_after_start_tag {
4922 indent_on_newline.len = *len;
4923 }
4924 Some(delimiter.clone())
4925 } else {
4926 None
4927 }
4928 });
4929
4930 (
4931 comment_delimiter,
4932 doc_delimiter,
4933 insert_extra_newline,
4934 indent_on_newline,
4935 indent_on_extra_newline,
4936 )
4937 } else {
4938 (
4939 None,
4940 None,
4941 false,
4942 IndentSize::default(),
4943 IndentSize::default(),
4944 )
4945 };
4946
4947 let prevent_auto_indent = doc_delimiter.is_some();
4948 let delimiter = comment_delimiter.or(doc_delimiter);
4949
4950 let capacity_for_delimiter =
4951 delimiter.as_deref().map(str::len).unwrap_or_default();
4952 let mut new_text = String::with_capacity(
4953 1 + capacity_for_delimiter
4954 + existing_indent.len as usize
4955 + indent_on_newline.len as usize
4956 + indent_on_extra_newline.len as usize,
4957 );
4958 new_text.push('\n');
4959 new_text.extend(existing_indent.chars());
4960 new_text.extend(indent_on_newline.chars());
4961
4962 if let Some(delimiter) = &delimiter {
4963 new_text.push_str(delimiter);
4964 }
4965
4966 if insert_extra_newline {
4967 new_text.push('\n');
4968 new_text.extend(existing_indent.chars());
4969 new_text.extend(indent_on_extra_newline.chars());
4970 }
4971
4972 let anchor = buffer.anchor_after(end);
4973 let new_selection = selection.map(|_| anchor);
4974 (
4975 ((start..end, new_text), prevent_auto_indent),
4976 (insert_extra_newline, new_selection),
4977 )
4978 })
4979 .unzip()
4980 };
4981
4982 let mut auto_indent_edits = Vec::new();
4983 let mut edits = Vec::new();
4984 for (edit, prevent_auto_indent) in edits_with_flags {
4985 if prevent_auto_indent {
4986 edits.push(edit);
4987 } else {
4988 auto_indent_edits.push(edit);
4989 }
4990 }
4991 if !edits.is_empty() {
4992 this.edit(edits, cx);
4993 }
4994 if !auto_indent_edits.is_empty() {
4995 this.edit_with_autoindent(auto_indent_edits, cx);
4996 }
4997
4998 let buffer = this.buffer.read(cx).snapshot(cx);
4999 let new_selections = selection_info
5000 .into_iter()
5001 .map(|(extra_newline_inserted, new_selection)| {
5002 let mut cursor = new_selection.end.to_point(&buffer);
5003 if extra_newline_inserted {
5004 cursor.row -= 1;
5005 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
5006 }
5007 new_selection.map(|_| cursor)
5008 })
5009 .collect();
5010
5011 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
5012 this.refresh_edit_prediction(true, false, window, cx);
5013 });
5014 }
5015
5016 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
5017 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5018
5019 let buffer = self.buffer.read(cx);
5020 let snapshot = buffer.snapshot(cx);
5021
5022 let mut edits = Vec::new();
5023 let mut rows = Vec::new();
5024
5025 for (rows_inserted, selection) in self
5026 .selections
5027 .all_adjusted(&self.display_snapshot(cx))
5028 .into_iter()
5029 .enumerate()
5030 {
5031 let cursor = selection.head();
5032 let row = cursor.row;
5033
5034 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
5035
5036 let newline = "\n".to_string();
5037 edits.push((start_of_line..start_of_line, newline));
5038
5039 rows.push(row + rows_inserted as u32);
5040 }
5041
5042 self.transact(window, cx, |editor, window, cx| {
5043 editor.edit(edits, cx);
5044
5045 editor.change_selections(Default::default(), window, cx, |s| {
5046 let mut index = 0;
5047 s.move_cursors_with(|map, _, _| {
5048 let row = rows[index];
5049 index += 1;
5050
5051 let point = Point::new(row, 0);
5052 let boundary = map.next_line_boundary(point).1;
5053 let clipped = map.clip_point(boundary, Bias::Left);
5054
5055 (clipped, SelectionGoal::None)
5056 });
5057 });
5058
5059 let mut indent_edits = Vec::new();
5060 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5061 for row in rows {
5062 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5063 for (row, indent) in indents {
5064 if indent.len == 0 {
5065 continue;
5066 }
5067
5068 let text = match indent.kind {
5069 IndentKind::Space => " ".repeat(indent.len as usize),
5070 IndentKind::Tab => "\t".repeat(indent.len as usize),
5071 };
5072 let point = Point::new(row.0, 0);
5073 indent_edits.push((point..point, text));
5074 }
5075 }
5076 editor.edit(indent_edits, cx);
5077 });
5078 }
5079
5080 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
5081 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5082
5083 let buffer = self.buffer.read(cx);
5084 let snapshot = buffer.snapshot(cx);
5085
5086 let mut edits = Vec::new();
5087 let mut rows = Vec::new();
5088 let mut rows_inserted = 0;
5089
5090 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
5091 let cursor = selection.head();
5092 let row = cursor.row;
5093
5094 let point = Point::new(row + 1, 0);
5095 let start_of_line = snapshot.clip_point(point, Bias::Left);
5096
5097 let newline = "\n".to_string();
5098 edits.push((start_of_line..start_of_line, newline));
5099
5100 rows_inserted += 1;
5101 rows.push(row + rows_inserted);
5102 }
5103
5104 self.transact(window, cx, |editor, window, cx| {
5105 editor.edit(edits, cx);
5106
5107 editor.change_selections(Default::default(), window, cx, |s| {
5108 let mut index = 0;
5109 s.move_cursors_with(|map, _, _| {
5110 let row = rows[index];
5111 index += 1;
5112
5113 let point = Point::new(row, 0);
5114 let boundary = map.next_line_boundary(point).1;
5115 let clipped = map.clip_point(boundary, Bias::Left);
5116
5117 (clipped, SelectionGoal::None)
5118 });
5119 });
5120
5121 let mut indent_edits = Vec::new();
5122 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5123 for row in rows {
5124 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5125 for (row, indent) in indents {
5126 if indent.len == 0 {
5127 continue;
5128 }
5129
5130 let text = match indent.kind {
5131 IndentKind::Space => " ".repeat(indent.len as usize),
5132 IndentKind::Tab => "\t".repeat(indent.len as usize),
5133 };
5134 let point = Point::new(row.0, 0);
5135 indent_edits.push((point..point, text));
5136 }
5137 }
5138 editor.edit(indent_edits, cx);
5139 });
5140 }
5141
5142 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5143 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5144 original_indent_columns: Vec::new(),
5145 });
5146 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5147 }
5148
5149 fn insert_with_autoindent_mode(
5150 &mut self,
5151 text: &str,
5152 autoindent_mode: Option<AutoindentMode>,
5153 window: &mut Window,
5154 cx: &mut Context<Self>,
5155 ) {
5156 if self.read_only(cx) {
5157 return;
5158 }
5159
5160 let text: Arc<str> = text.into();
5161 self.transact(window, cx, |this, window, cx| {
5162 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5163 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5164 let anchors = {
5165 let snapshot = buffer.read(cx);
5166 old_selections
5167 .iter()
5168 .map(|s| {
5169 let anchor = snapshot.anchor_after(s.head());
5170 s.map(|_| anchor)
5171 })
5172 .collect::<Vec<_>>()
5173 };
5174 buffer.edit(
5175 old_selections
5176 .iter()
5177 .map(|s| (s.start..s.end, text.clone())),
5178 autoindent_mode,
5179 cx,
5180 );
5181 anchors
5182 });
5183
5184 this.change_selections(Default::default(), window, cx, |s| {
5185 s.select_anchors(selection_anchors);
5186 });
5187
5188 cx.notify();
5189 });
5190 }
5191
5192 fn trigger_completion_on_input(
5193 &mut self,
5194 text: &str,
5195 trigger_in_words: bool,
5196 window: &mut Window,
5197 cx: &mut Context<Self>,
5198 ) {
5199 let completions_source = self
5200 .context_menu
5201 .borrow()
5202 .as_ref()
5203 .and_then(|menu| match menu {
5204 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5205 CodeContextMenu::CodeActions(_) => None,
5206 });
5207
5208 match completions_source {
5209 Some(CompletionsMenuSource::Words { .. }) => {
5210 self.open_or_update_completions_menu(
5211 Some(CompletionsMenuSource::Words {
5212 ignore_threshold: false,
5213 }),
5214 None,
5215 trigger_in_words,
5216 window,
5217 cx,
5218 );
5219 }
5220 _ => self.open_or_update_completions_menu(
5221 None,
5222 Some(text.to_owned()).filter(|x| !x.is_empty()),
5223 true,
5224 window,
5225 cx,
5226 ),
5227 }
5228 }
5229
5230 /// If any empty selections is touching the start of its innermost containing autoclose
5231 /// region, expand it to select the brackets.
5232 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5233 let selections = self
5234 .selections
5235 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
5236 let buffer = self.buffer.read(cx).read(cx);
5237 let new_selections = self
5238 .selections_with_autoclose_regions(selections, &buffer)
5239 .map(|(mut selection, region)| {
5240 if !selection.is_empty() {
5241 return selection;
5242 }
5243
5244 if let Some(region) = region {
5245 let mut range = region.range.to_offset(&buffer);
5246 if selection.start == range.start && range.start.0 >= region.pair.start.len() {
5247 range.start -= region.pair.start.len();
5248 if buffer.contains_str_at(range.start, ®ion.pair.start)
5249 && buffer.contains_str_at(range.end, ®ion.pair.end)
5250 {
5251 range.end += region.pair.end.len();
5252 selection.start = range.start;
5253 selection.end = range.end;
5254
5255 return selection;
5256 }
5257 }
5258 }
5259
5260 let always_treat_brackets_as_autoclosed = buffer
5261 .language_settings_at(selection.start, cx)
5262 .always_treat_brackets_as_autoclosed;
5263
5264 if !always_treat_brackets_as_autoclosed {
5265 return selection;
5266 }
5267
5268 if let Some(scope) = buffer.language_scope_at(selection.start) {
5269 for (pair, enabled) in scope.brackets() {
5270 if !enabled || !pair.close {
5271 continue;
5272 }
5273
5274 if buffer.contains_str_at(selection.start, &pair.end) {
5275 let pair_start_len = pair.start.len();
5276 if buffer.contains_str_at(
5277 selection.start.saturating_sub_usize(pair_start_len),
5278 &pair.start,
5279 ) {
5280 selection.start -= pair_start_len;
5281 selection.end += pair.end.len();
5282
5283 return selection;
5284 }
5285 }
5286 }
5287 }
5288
5289 selection
5290 })
5291 .collect();
5292
5293 drop(buffer);
5294 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5295 selections.select(new_selections)
5296 });
5297 }
5298
5299 /// Iterate the given selections, and for each one, find the smallest surrounding
5300 /// autoclose region. This uses the ordering of the selections and the autoclose
5301 /// regions to avoid repeated comparisons.
5302 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5303 &'a self,
5304 selections: impl IntoIterator<Item = Selection<D>>,
5305 buffer: &'a MultiBufferSnapshot,
5306 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5307 let mut i = 0;
5308 let mut regions = self.autoclose_regions.as_slice();
5309 selections.into_iter().map(move |selection| {
5310 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5311
5312 let mut enclosing = None;
5313 while let Some(pair_state) = regions.get(i) {
5314 if pair_state.range.end.to_offset(buffer) < range.start {
5315 regions = ®ions[i + 1..];
5316 i = 0;
5317 } else if pair_state.range.start.to_offset(buffer) > range.end {
5318 break;
5319 } else {
5320 if pair_state.selection_id == selection.id {
5321 enclosing = Some(pair_state);
5322 }
5323 i += 1;
5324 }
5325 }
5326
5327 (selection, enclosing)
5328 })
5329 }
5330
5331 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5332 fn invalidate_autoclose_regions(
5333 &mut self,
5334 mut selections: &[Selection<Anchor>],
5335 buffer: &MultiBufferSnapshot,
5336 ) {
5337 self.autoclose_regions.retain(|state| {
5338 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5339 return false;
5340 }
5341
5342 let mut i = 0;
5343 while let Some(selection) = selections.get(i) {
5344 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5345 selections = &selections[1..];
5346 continue;
5347 }
5348 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5349 break;
5350 }
5351 if selection.id == state.selection_id {
5352 return true;
5353 } else {
5354 i += 1;
5355 }
5356 }
5357 false
5358 });
5359 }
5360
5361 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5362 let offset = position.to_offset(buffer);
5363 let (word_range, kind) =
5364 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5365 if offset > word_range.start && kind == Some(CharKind::Word) {
5366 Some(
5367 buffer
5368 .text_for_range(word_range.start..offset)
5369 .collect::<String>(),
5370 )
5371 } else {
5372 None
5373 }
5374 }
5375
5376 pub fn visible_excerpts(
5377 &self,
5378 lsp_related_only: bool,
5379 cx: &mut Context<Editor>,
5380 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5381 let project = self.project().cloned();
5382 let multi_buffer = self.buffer().read(cx);
5383 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5384 let multi_buffer_visible_start = self
5385 .scroll_manager
5386 .anchor()
5387 .anchor
5388 .to_point(&multi_buffer_snapshot);
5389 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5390 multi_buffer_visible_start
5391 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5392 Bias::Left,
5393 );
5394 multi_buffer_snapshot
5395 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5396 .into_iter()
5397 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5398 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5399 if !lsp_related_only {
5400 return Some((
5401 excerpt_id,
5402 (
5403 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5404 buffer.version().clone(),
5405 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5406 ),
5407 ));
5408 }
5409
5410 let project = project.as_ref()?.read(cx);
5411 let buffer_file = project::File::from_dyn(buffer.file())?;
5412 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5413 let worktree_entry = buffer_worktree
5414 .read(cx)
5415 .entry_for_id(buffer_file.project_entry_id()?)?;
5416 if worktree_entry.is_ignored {
5417 None
5418 } else {
5419 Some((
5420 excerpt_id,
5421 (
5422 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5423 buffer.version().clone(),
5424 excerpt_visible_range.start.0..excerpt_visible_range.end.0,
5425 ),
5426 ))
5427 }
5428 })
5429 .collect()
5430 }
5431
5432 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5433 TextLayoutDetails {
5434 text_system: window.text_system().clone(),
5435 editor_style: self.style.clone().unwrap(),
5436 rem_size: window.rem_size(),
5437 scroll_anchor: self.scroll_manager.anchor(),
5438 visible_rows: self.visible_line_count(),
5439 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5440 }
5441 }
5442
5443 fn trigger_on_type_formatting(
5444 &self,
5445 input: String,
5446 window: &mut Window,
5447 cx: &mut Context<Self>,
5448 ) -> Option<Task<Result<()>>> {
5449 if input.len() != 1 {
5450 return None;
5451 }
5452
5453 let project = self.project()?;
5454 let position = self.selections.newest_anchor().head();
5455 let (buffer, buffer_position) = self
5456 .buffer
5457 .read(cx)
5458 .text_anchor_for_position(position, cx)?;
5459
5460 let settings = language_settings::language_settings(
5461 buffer
5462 .read(cx)
5463 .language_at(buffer_position)
5464 .map(|l| l.name()),
5465 buffer.read(cx).file(),
5466 cx,
5467 );
5468 if !settings.use_on_type_format {
5469 return None;
5470 }
5471
5472 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5473 // hence we do LSP request & edit on host side only — add formats to host's history.
5474 let push_to_lsp_host_history = true;
5475 // If this is not the host, append its history with new edits.
5476 let push_to_client_history = project.read(cx).is_via_collab();
5477
5478 let on_type_formatting = project.update(cx, |project, cx| {
5479 project.on_type_format(
5480 buffer.clone(),
5481 buffer_position,
5482 input,
5483 push_to_lsp_host_history,
5484 cx,
5485 )
5486 });
5487 Some(cx.spawn_in(window, async move |editor, cx| {
5488 if let Some(transaction) = on_type_formatting.await? {
5489 if push_to_client_history {
5490 buffer
5491 .update(cx, |buffer, _| {
5492 buffer.push_transaction(transaction, Instant::now());
5493 buffer.finalize_last_transaction();
5494 })
5495 .ok();
5496 }
5497 editor.update(cx, |editor, cx| {
5498 editor.refresh_document_highlights(cx);
5499 })?;
5500 }
5501 Ok(())
5502 }))
5503 }
5504
5505 pub fn show_word_completions(
5506 &mut self,
5507 _: &ShowWordCompletions,
5508 window: &mut Window,
5509 cx: &mut Context<Self>,
5510 ) {
5511 self.open_or_update_completions_menu(
5512 Some(CompletionsMenuSource::Words {
5513 ignore_threshold: true,
5514 }),
5515 None,
5516 false,
5517 window,
5518 cx,
5519 );
5520 }
5521
5522 pub fn show_completions(
5523 &mut self,
5524 _: &ShowCompletions,
5525 window: &mut Window,
5526 cx: &mut Context<Self>,
5527 ) {
5528 self.open_or_update_completions_menu(None, None, false, window, cx);
5529 }
5530
5531 fn open_or_update_completions_menu(
5532 &mut self,
5533 requested_source: Option<CompletionsMenuSource>,
5534 trigger: Option<String>,
5535 trigger_in_words: bool,
5536 window: &mut Window,
5537 cx: &mut Context<Self>,
5538 ) {
5539 if self.pending_rename.is_some() {
5540 return;
5541 }
5542
5543 let completions_source = self
5544 .context_menu
5545 .borrow()
5546 .as_ref()
5547 .and_then(|menu| match menu {
5548 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5549 CodeContextMenu::CodeActions(_) => None,
5550 });
5551
5552 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5553
5554 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5555 // inserted and selected. To handle that case, the start of the selection is used so that
5556 // the menu starts with all choices.
5557 let position = self
5558 .selections
5559 .newest_anchor()
5560 .start
5561 .bias_right(&multibuffer_snapshot);
5562 if position.diff_base_anchor.is_some() {
5563 return;
5564 }
5565 let buffer_position = multibuffer_snapshot.anchor_before(position);
5566 let Some(buffer) = buffer_position
5567 .text_anchor
5568 .buffer_id
5569 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5570 else {
5571 return;
5572 };
5573 let buffer_snapshot = buffer.read(cx).snapshot();
5574
5575 let menu_is_open = matches!(
5576 self.context_menu.borrow().as_ref(),
5577 Some(CodeContextMenu::Completions(_))
5578 );
5579
5580 let language = buffer_snapshot
5581 .language_at(buffer_position.text_anchor)
5582 .map(|language| language.name());
5583
5584 let language_settings = language_settings(language.clone(), buffer_snapshot.file(), cx);
5585 let completion_settings = language_settings.completions.clone();
5586
5587 let show_completions_on_input = self
5588 .show_completions_on_input_override
5589 .unwrap_or(language_settings.show_completions_on_input);
5590 if !menu_is_open && trigger.is_some() && !show_completions_on_input {
5591 return;
5592 }
5593
5594 let query: Option<Arc<String>> =
5595 Self::completion_query(&multibuffer_snapshot, buffer_position)
5596 .map(|query| query.into());
5597
5598 drop(multibuffer_snapshot);
5599
5600 // Hide the current completions menu when query is empty. Without this, cached
5601 // completions from before the trigger char may be reused (#32774).
5602 if query.is_none() && menu_is_open {
5603 self.hide_context_menu(window, cx);
5604 }
5605
5606 let mut ignore_word_threshold = false;
5607 let provider = match requested_source {
5608 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5609 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5610 ignore_word_threshold = ignore_threshold;
5611 None
5612 }
5613 Some(CompletionsMenuSource::SnippetChoices)
5614 | Some(CompletionsMenuSource::SnippetsOnly) => {
5615 log::error!("bug: SnippetChoices requested_source is not handled");
5616 None
5617 }
5618 };
5619
5620 let sort_completions = provider
5621 .as_ref()
5622 .is_some_and(|provider| provider.sort_completions());
5623
5624 let filter_completions = provider
5625 .as_ref()
5626 .is_none_or(|provider| provider.filter_completions());
5627
5628 let was_snippets_only = matches!(
5629 completions_source,
5630 Some(CompletionsMenuSource::SnippetsOnly)
5631 );
5632
5633 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5634 if filter_completions {
5635 menu.filter(
5636 query.clone().unwrap_or_default(),
5637 buffer_position.text_anchor,
5638 &buffer,
5639 provider.clone(),
5640 window,
5641 cx,
5642 );
5643 }
5644 // When `is_incomplete` is false, no need to re-query completions when the current query
5645 // is a suffix of the initial query.
5646 let was_complete = !menu.is_incomplete;
5647 if was_complete && !was_snippets_only {
5648 // If the new query is a suffix of the old query (typing more characters) and
5649 // the previous result was complete, the existing completions can be filtered.
5650 //
5651 // Note that snippet completions are always complete.
5652 let query_matches = match (&menu.initial_query, &query) {
5653 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5654 (None, _) => true,
5655 _ => false,
5656 };
5657 if query_matches {
5658 let position_matches = if menu.initial_position == position {
5659 true
5660 } else {
5661 let snapshot = self.buffer.read(cx).read(cx);
5662 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5663 };
5664 if position_matches {
5665 return;
5666 }
5667 }
5668 }
5669 };
5670
5671 let Anchor {
5672 excerpt_id: buffer_excerpt_id,
5673 text_anchor: buffer_position,
5674 ..
5675 } = buffer_position;
5676
5677 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5678 buffer_snapshot.surrounding_word(buffer_position, None)
5679 {
5680 let word_to_exclude = buffer_snapshot
5681 .text_for_range(word_range.clone())
5682 .collect::<String>();
5683 (
5684 buffer_snapshot.anchor_before(word_range.start)
5685 ..buffer_snapshot.anchor_after(buffer_position),
5686 Some(word_to_exclude),
5687 )
5688 } else {
5689 (buffer_position..buffer_position, None)
5690 };
5691
5692 let show_completion_documentation = buffer_snapshot
5693 .settings_at(buffer_position, cx)
5694 .show_completion_documentation;
5695
5696 // The document can be large, so stay in reasonable bounds when searching for words,
5697 // otherwise completion pop-up might be slow to appear.
5698 const WORD_LOOKUP_ROWS: u32 = 5_000;
5699 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5700 let min_word_search = buffer_snapshot.clip_point(
5701 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5702 Bias::Left,
5703 );
5704 let max_word_search = buffer_snapshot.clip_point(
5705 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5706 Bias::Right,
5707 );
5708 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5709 ..buffer_snapshot.point_to_offset(max_word_search);
5710
5711 let skip_digits = query
5712 .as_ref()
5713 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5714
5715 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5716 trigger.as_ref().is_none_or(|trigger| {
5717 provider.is_completion_trigger(
5718 &buffer,
5719 position.text_anchor,
5720 trigger,
5721 trigger_in_words,
5722 cx,
5723 )
5724 })
5725 });
5726
5727 let provider_responses = if let Some(provider) = &provider
5728 && load_provider_completions
5729 {
5730 let trigger_character =
5731 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5732 let completion_context = CompletionContext {
5733 trigger_kind: match &trigger_character {
5734 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5735 None => CompletionTriggerKind::INVOKED,
5736 },
5737 trigger_character,
5738 };
5739
5740 provider.completions(
5741 buffer_excerpt_id,
5742 &buffer,
5743 buffer_position,
5744 completion_context,
5745 window,
5746 cx,
5747 )
5748 } else {
5749 Task::ready(Ok(Vec::new()))
5750 };
5751
5752 let load_word_completions = if !self.word_completions_enabled {
5753 false
5754 } else if requested_source
5755 == Some(CompletionsMenuSource::Words {
5756 ignore_threshold: true,
5757 })
5758 {
5759 true
5760 } else {
5761 load_provider_completions
5762 && completion_settings.words != WordsCompletionMode::Disabled
5763 && (ignore_word_threshold || {
5764 let words_min_length = completion_settings.words_min_length;
5765 // check whether word has at least `words_min_length` characters
5766 let query_chars = query.iter().flat_map(|q| q.chars());
5767 query_chars.take(words_min_length).count() == words_min_length
5768 })
5769 };
5770
5771 let mut words = if load_word_completions {
5772 cx.background_spawn({
5773 let buffer_snapshot = buffer_snapshot.clone();
5774 async move {
5775 buffer_snapshot.words_in_range(WordsQuery {
5776 fuzzy_contents: None,
5777 range: word_search_range,
5778 skip_digits,
5779 })
5780 }
5781 })
5782 } else {
5783 Task::ready(BTreeMap::default())
5784 };
5785
5786 let snippets = if let Some(provider) = &provider
5787 && provider.show_snippets()
5788 && let Some(project) = self.project()
5789 {
5790 let char_classifier = buffer_snapshot
5791 .char_classifier_at(buffer_position)
5792 .scope_context(Some(CharScopeContext::Completion));
5793 project.update(cx, |project, cx| {
5794 snippet_completions(project, &buffer, buffer_position, char_classifier, cx)
5795 })
5796 } else {
5797 Task::ready(Ok(CompletionResponse {
5798 completions: Vec::new(),
5799 display_options: Default::default(),
5800 is_incomplete: false,
5801 }))
5802 };
5803
5804 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5805
5806 let id = post_inc(&mut self.next_completion_id);
5807 let task = cx.spawn_in(window, async move |editor, cx| {
5808 let Ok(()) = editor.update(cx, |this, _| {
5809 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5810 }) else {
5811 return;
5812 };
5813
5814 // TODO: Ideally completions from different sources would be selectively re-queried, so
5815 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5816 let mut completions = Vec::new();
5817 let mut is_incomplete = false;
5818 let mut display_options: Option<CompletionDisplayOptions> = None;
5819 if let Some(provider_responses) = provider_responses.await.log_err()
5820 && !provider_responses.is_empty()
5821 {
5822 for response in provider_responses {
5823 completions.extend(response.completions);
5824 is_incomplete = is_incomplete || response.is_incomplete;
5825 match display_options.as_mut() {
5826 None => {
5827 display_options = Some(response.display_options);
5828 }
5829 Some(options) => options.merge(&response.display_options),
5830 }
5831 }
5832 if completion_settings.words == WordsCompletionMode::Fallback {
5833 words = Task::ready(BTreeMap::default());
5834 }
5835 }
5836 let display_options = display_options.unwrap_or_default();
5837
5838 let mut words = words.await;
5839 if let Some(word_to_exclude) = &word_to_exclude {
5840 words.remove(word_to_exclude);
5841 }
5842 for lsp_completion in &completions {
5843 words.remove(&lsp_completion.new_text);
5844 }
5845 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5846 replace_range: word_replace_range.clone(),
5847 new_text: word.clone(),
5848 label: CodeLabel::plain(word, None),
5849 match_start: None,
5850 snippet_deduplication_key: None,
5851 icon_path: None,
5852 documentation: None,
5853 source: CompletionSource::BufferWord {
5854 word_range,
5855 resolved: false,
5856 },
5857 insert_text_mode: Some(InsertTextMode::AS_IS),
5858 confirm: None,
5859 }));
5860
5861 completions.extend(
5862 snippets
5863 .await
5864 .into_iter()
5865 .flat_map(|response| response.completions),
5866 );
5867
5868 let menu = if completions.is_empty() {
5869 None
5870 } else {
5871 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5872 let languages = editor
5873 .workspace
5874 .as_ref()
5875 .and_then(|(workspace, _)| workspace.upgrade())
5876 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5877 let menu = CompletionsMenu::new(
5878 id,
5879 requested_source.unwrap_or(if load_provider_completions {
5880 CompletionsMenuSource::Normal
5881 } else {
5882 CompletionsMenuSource::SnippetsOnly
5883 }),
5884 sort_completions,
5885 show_completion_documentation,
5886 position,
5887 query.clone(),
5888 is_incomplete,
5889 buffer.clone(),
5890 completions.into(),
5891 editor
5892 .context_menu()
5893 .borrow_mut()
5894 .as_ref()
5895 .map(|menu| menu.primary_scroll_handle()),
5896 display_options,
5897 snippet_sort_order,
5898 languages,
5899 language,
5900 cx,
5901 );
5902
5903 let query = if filter_completions { query } else { None };
5904 let matches_task = menu.do_async_filtering(
5905 query.unwrap_or_default(),
5906 buffer_position,
5907 &buffer,
5908 cx,
5909 );
5910 (menu, matches_task)
5911 }) else {
5912 return;
5913 };
5914
5915 let matches = matches_task.await;
5916
5917 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5918 // Newer menu already set, so exit.
5919 if let Some(CodeContextMenu::Completions(prev_menu)) =
5920 editor.context_menu.borrow().as_ref()
5921 && prev_menu.id > id
5922 {
5923 return;
5924 };
5925
5926 // Only valid to take prev_menu because either the new menu is immediately set
5927 // below, or the menu is hidden.
5928 if let Some(CodeContextMenu::Completions(prev_menu)) =
5929 editor.context_menu.borrow_mut().take()
5930 {
5931 let position_matches =
5932 if prev_menu.initial_position == menu.initial_position {
5933 true
5934 } else {
5935 let snapshot = editor.buffer.read(cx).read(cx);
5936 prev_menu.initial_position.to_offset(&snapshot)
5937 == menu.initial_position.to_offset(&snapshot)
5938 };
5939 if position_matches {
5940 // Preserve markdown cache before `set_filter_results` because it will
5941 // try to populate the documentation cache.
5942 menu.preserve_markdown_cache(prev_menu);
5943 }
5944 };
5945
5946 menu.set_filter_results(matches, provider, window, cx);
5947 }) else {
5948 return;
5949 };
5950
5951 menu.visible().then_some(menu)
5952 };
5953
5954 editor
5955 .update_in(cx, |editor, window, cx| {
5956 if editor.focus_handle.is_focused(window)
5957 && let Some(menu) = menu
5958 {
5959 *editor.context_menu.borrow_mut() =
5960 Some(CodeContextMenu::Completions(menu));
5961
5962 crate::hover_popover::hide_hover(editor, cx);
5963 if editor.show_edit_predictions_in_menu() {
5964 editor.update_visible_edit_prediction(window, cx);
5965 } else {
5966 editor.discard_edit_prediction(false, cx);
5967 }
5968
5969 cx.notify();
5970 return;
5971 }
5972
5973 if editor.completion_tasks.len() <= 1 {
5974 // If there are no more completion tasks and the last menu was empty, we should hide it.
5975 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5976 // If it was already hidden and we don't show edit predictions in the menu,
5977 // we should also show the edit prediction when available.
5978 if was_hidden && editor.show_edit_predictions_in_menu() {
5979 editor.update_visible_edit_prediction(window, cx);
5980 }
5981 }
5982 })
5983 .ok();
5984 });
5985
5986 self.completion_tasks.push((id, task));
5987 }
5988
5989 #[cfg(feature = "test-support")]
5990 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5991 let menu = self.context_menu.borrow();
5992 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5993 let completions = menu.completions.borrow();
5994 Some(completions.to_vec())
5995 } else {
5996 None
5997 }
5998 }
5999
6000 pub fn with_completions_menu_matching_id<R>(
6001 &self,
6002 id: CompletionId,
6003 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
6004 ) -> R {
6005 let mut context_menu = self.context_menu.borrow_mut();
6006 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
6007 return f(None);
6008 };
6009 if completions_menu.id != id {
6010 return f(None);
6011 }
6012 f(Some(completions_menu))
6013 }
6014
6015 pub fn confirm_completion(
6016 &mut self,
6017 action: &ConfirmCompletion,
6018 window: &mut Window,
6019 cx: &mut Context<Self>,
6020 ) -> Option<Task<Result<()>>> {
6021 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6022 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
6023 }
6024
6025 pub fn confirm_completion_insert(
6026 &mut self,
6027 _: &ConfirmCompletionInsert,
6028 window: &mut Window,
6029 cx: &mut Context<Self>,
6030 ) -> Option<Task<Result<()>>> {
6031 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6032 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
6033 }
6034
6035 pub fn confirm_completion_replace(
6036 &mut self,
6037 _: &ConfirmCompletionReplace,
6038 window: &mut Window,
6039 cx: &mut Context<Self>,
6040 ) -> Option<Task<Result<()>>> {
6041 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6042 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
6043 }
6044
6045 pub fn compose_completion(
6046 &mut self,
6047 action: &ComposeCompletion,
6048 window: &mut Window,
6049 cx: &mut Context<Self>,
6050 ) -> Option<Task<Result<()>>> {
6051 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6052 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
6053 }
6054
6055 fn do_completion(
6056 &mut self,
6057 item_ix: Option<usize>,
6058 intent: CompletionIntent,
6059 window: &mut Window,
6060 cx: &mut Context<Editor>,
6061 ) -> Option<Task<Result<()>>> {
6062 use language::ToOffset as _;
6063
6064 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
6065 else {
6066 return None;
6067 };
6068
6069 let candidate_id = {
6070 let entries = completions_menu.entries.borrow();
6071 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
6072 if self.show_edit_predictions_in_menu() {
6073 self.discard_edit_prediction(true, cx);
6074 }
6075 mat.candidate_id
6076 };
6077
6078 let completion = completions_menu
6079 .completions
6080 .borrow()
6081 .get(candidate_id)?
6082 .clone();
6083 cx.stop_propagation();
6084
6085 let buffer_handle = completions_menu.buffer.clone();
6086
6087 let CompletionEdit {
6088 new_text,
6089 snippet,
6090 replace_range,
6091 } = process_completion_for_edit(
6092 &completion,
6093 intent,
6094 &buffer_handle,
6095 &completions_menu.initial_position.text_anchor,
6096 cx,
6097 );
6098
6099 let buffer = buffer_handle.read(cx);
6100 let snapshot = self.buffer.read(cx).snapshot(cx);
6101 let newest_anchor = self.selections.newest_anchor();
6102 let replace_range_multibuffer = {
6103 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
6104 excerpt.map_range_from_buffer(replace_range.clone())
6105 };
6106 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
6107 return None;
6108 }
6109
6110 let old_text = buffer
6111 .text_for_range(replace_range.clone())
6112 .collect::<String>();
6113 let lookbehind = newest_anchor
6114 .start
6115 .text_anchor
6116 .to_offset(buffer)
6117 .saturating_sub(replace_range.start.0);
6118 let lookahead = replace_range
6119 .end
6120 .0
6121 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
6122 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
6123 let suffix = &old_text[lookbehind.min(old_text.len())..];
6124
6125 let selections = self
6126 .selections
6127 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
6128 let mut ranges = Vec::new();
6129 let mut linked_edits = HashMap::<_, Vec<_>>::default();
6130
6131 for selection in &selections {
6132 let range = if selection.id == newest_anchor.id {
6133 replace_range_multibuffer.clone()
6134 } else {
6135 let mut range = selection.range();
6136
6137 // if prefix is present, don't duplicate it
6138 if snapshot.contains_str_at(range.start.saturating_sub_usize(lookbehind), prefix) {
6139 range.start = range.start.saturating_sub_usize(lookbehind);
6140
6141 // if suffix is also present, mimic the newest cursor and replace it
6142 if selection.id != newest_anchor.id
6143 && snapshot.contains_str_at(range.end, suffix)
6144 {
6145 range.end += lookahead;
6146 }
6147 }
6148 range
6149 };
6150
6151 ranges.push(range.clone());
6152
6153 if !self.linked_edit_ranges.is_empty() {
6154 let start_anchor = snapshot.anchor_before(range.start);
6155 let end_anchor = snapshot.anchor_after(range.end);
6156 if let Some(ranges) = self
6157 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6158 {
6159 for (buffer, edits) in ranges {
6160 linked_edits
6161 .entry(buffer.clone())
6162 .or_default()
6163 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6164 }
6165 }
6166 }
6167 }
6168
6169 let common_prefix_len = old_text
6170 .chars()
6171 .zip(new_text.chars())
6172 .take_while(|(a, b)| a == b)
6173 .map(|(a, _)| a.len_utf8())
6174 .sum::<usize>();
6175
6176 cx.emit(EditorEvent::InputHandled {
6177 utf16_range_to_replace: None,
6178 text: new_text[common_prefix_len..].into(),
6179 });
6180
6181 self.transact(window, cx, |editor, window, cx| {
6182 if let Some(mut snippet) = snippet {
6183 snippet.text = new_text.to_string();
6184 editor
6185 .insert_snippet(&ranges, snippet, window, cx)
6186 .log_err();
6187 } else {
6188 editor.buffer.update(cx, |multi_buffer, cx| {
6189 let auto_indent = match completion.insert_text_mode {
6190 Some(InsertTextMode::AS_IS) => None,
6191 _ => editor.autoindent_mode.clone(),
6192 };
6193 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6194 multi_buffer.edit(edits, auto_indent, cx);
6195 });
6196 }
6197 for (buffer, edits) in linked_edits {
6198 buffer.update(cx, |buffer, cx| {
6199 let snapshot = buffer.snapshot();
6200 let edits = edits
6201 .into_iter()
6202 .map(|(range, text)| {
6203 use text::ToPoint as TP;
6204 let end_point = TP::to_point(&range.end, &snapshot);
6205 let start_point = TP::to_point(&range.start, &snapshot);
6206 (start_point..end_point, text)
6207 })
6208 .sorted_by_key(|(range, _)| range.start);
6209 buffer.edit(edits, None, cx);
6210 })
6211 }
6212
6213 editor.refresh_edit_prediction(true, false, window, cx);
6214 });
6215 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6216
6217 let show_new_completions_on_confirm = completion
6218 .confirm
6219 .as_ref()
6220 .is_some_and(|confirm| confirm(intent, window, cx));
6221 if show_new_completions_on_confirm {
6222 self.open_or_update_completions_menu(None, None, false, window, cx);
6223 }
6224
6225 let provider = self.completion_provider.as_ref()?;
6226
6227 let lsp_store = self.project().map(|project| project.read(cx).lsp_store());
6228 let command = lsp_store.as_ref().and_then(|lsp_store| {
6229 let CompletionSource::Lsp {
6230 lsp_completion,
6231 server_id,
6232 ..
6233 } = &completion.source
6234 else {
6235 return None;
6236 };
6237 let lsp_command = lsp_completion.command.as_ref()?;
6238 let available_commands = lsp_store
6239 .read(cx)
6240 .lsp_server_capabilities
6241 .get(server_id)
6242 .and_then(|server_capabilities| {
6243 server_capabilities
6244 .execute_command_provider
6245 .as_ref()
6246 .map(|options| options.commands.as_slice())
6247 })?;
6248 if available_commands.contains(&lsp_command.command) {
6249 Some(CodeAction {
6250 server_id: *server_id,
6251 range: language::Anchor::MIN..language::Anchor::MIN,
6252 lsp_action: LspAction::Command(lsp_command.clone()),
6253 resolved: false,
6254 })
6255 } else {
6256 None
6257 }
6258 });
6259
6260 drop(completion);
6261 let apply_edits = provider.apply_additional_edits_for_completion(
6262 buffer_handle.clone(),
6263 completions_menu.completions.clone(),
6264 candidate_id,
6265 true,
6266 cx,
6267 );
6268
6269 let editor_settings = EditorSettings::get_global(cx);
6270 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6271 // After the code completion is finished, users often want to know what signatures are needed.
6272 // so we should automatically call signature_help
6273 self.show_signature_help(&ShowSignatureHelp, window, cx);
6274 }
6275
6276 Some(cx.spawn_in(window, async move |editor, cx| {
6277 apply_edits.await?;
6278
6279 if let Some((lsp_store, command)) = lsp_store.zip(command) {
6280 let title = command.lsp_action.title().to_owned();
6281 let project_transaction = lsp_store
6282 .update(cx, |lsp_store, cx| {
6283 lsp_store.apply_code_action(buffer_handle, command, false, cx)
6284 })?
6285 .await
6286 .context("applying post-completion command")?;
6287 if let Some(workspace) = editor.read_with(cx, |editor, _| editor.workspace())? {
6288 Self::open_project_transaction(
6289 &editor,
6290 workspace.downgrade(),
6291 project_transaction,
6292 title,
6293 cx,
6294 )
6295 .await?;
6296 }
6297 }
6298
6299 Ok(())
6300 }))
6301 }
6302
6303 pub fn toggle_code_actions(
6304 &mut self,
6305 action: &ToggleCodeActions,
6306 window: &mut Window,
6307 cx: &mut Context<Self>,
6308 ) {
6309 let quick_launch = action.quick_launch;
6310 let mut context_menu = self.context_menu.borrow_mut();
6311 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6312 if code_actions.deployed_from == action.deployed_from {
6313 // Toggle if we're selecting the same one
6314 *context_menu = None;
6315 cx.notify();
6316 return;
6317 } else {
6318 // Otherwise, clear it and start a new one
6319 *context_menu = None;
6320 cx.notify();
6321 }
6322 }
6323 drop(context_menu);
6324 let snapshot = self.snapshot(window, cx);
6325 let deployed_from = action.deployed_from.clone();
6326 let action = action.clone();
6327 self.completion_tasks.clear();
6328 self.discard_edit_prediction(false, cx);
6329
6330 let multibuffer_point = match &action.deployed_from {
6331 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6332 DisplayPoint::new(*row, 0).to_point(&snapshot)
6333 }
6334 _ => self
6335 .selections
6336 .newest::<Point>(&snapshot.display_snapshot)
6337 .head(),
6338 };
6339 let Some((buffer, buffer_row)) = snapshot
6340 .buffer_snapshot()
6341 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6342 .and_then(|(buffer_snapshot, range)| {
6343 self.buffer()
6344 .read(cx)
6345 .buffer(buffer_snapshot.remote_id())
6346 .map(|buffer| (buffer, range.start.row))
6347 })
6348 else {
6349 return;
6350 };
6351 let buffer_id = buffer.read(cx).remote_id();
6352 let tasks = self
6353 .tasks
6354 .get(&(buffer_id, buffer_row))
6355 .map(|t| Arc::new(t.to_owned()));
6356
6357 if !self.focus_handle.is_focused(window) {
6358 return;
6359 }
6360 let project = self.project.clone();
6361
6362 let code_actions_task = match deployed_from {
6363 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6364 _ => self.code_actions(buffer_row, window, cx),
6365 };
6366
6367 let runnable_task = match deployed_from {
6368 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6369 _ => {
6370 let mut task_context_task = Task::ready(None);
6371 if let Some(tasks) = &tasks
6372 && let Some(project) = project
6373 {
6374 task_context_task =
6375 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6376 }
6377
6378 cx.spawn_in(window, {
6379 let buffer = buffer.clone();
6380 async move |editor, cx| {
6381 let task_context = task_context_task.await;
6382
6383 let resolved_tasks =
6384 tasks
6385 .zip(task_context.clone())
6386 .map(|(tasks, task_context)| ResolvedTasks {
6387 templates: tasks.resolve(&task_context).collect(),
6388 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6389 multibuffer_point.row,
6390 tasks.column,
6391 )),
6392 });
6393 let debug_scenarios = editor
6394 .update(cx, |editor, cx| {
6395 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6396 })?
6397 .await;
6398 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6399 }
6400 })
6401 }
6402 };
6403
6404 cx.spawn_in(window, async move |editor, cx| {
6405 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6406 let code_actions = code_actions_task.await;
6407 let spawn_straight_away = quick_launch
6408 && resolved_tasks
6409 .as_ref()
6410 .is_some_and(|tasks| tasks.templates.len() == 1)
6411 && code_actions
6412 .as_ref()
6413 .is_none_or(|actions| actions.is_empty())
6414 && debug_scenarios.is_empty();
6415
6416 editor.update_in(cx, |editor, window, cx| {
6417 crate::hover_popover::hide_hover(editor, cx);
6418 let actions = CodeActionContents::new(
6419 resolved_tasks,
6420 code_actions,
6421 debug_scenarios,
6422 task_context.unwrap_or_default(),
6423 );
6424
6425 // Don't show the menu if there are no actions available
6426 if actions.is_empty() {
6427 cx.notify();
6428 return Task::ready(Ok(()));
6429 }
6430
6431 *editor.context_menu.borrow_mut() =
6432 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6433 buffer,
6434 actions,
6435 selected_item: Default::default(),
6436 scroll_handle: UniformListScrollHandle::default(),
6437 deployed_from,
6438 }));
6439 cx.notify();
6440 if spawn_straight_away
6441 && let Some(task) = editor.confirm_code_action(
6442 &ConfirmCodeAction { item_ix: Some(0) },
6443 window,
6444 cx,
6445 )
6446 {
6447 return task;
6448 }
6449
6450 Task::ready(Ok(()))
6451 })
6452 })
6453 .detach_and_log_err(cx);
6454 }
6455
6456 fn debug_scenarios(
6457 &mut self,
6458 resolved_tasks: &Option<ResolvedTasks>,
6459 buffer: &Entity<Buffer>,
6460 cx: &mut App,
6461 ) -> Task<Vec<task::DebugScenario>> {
6462 maybe!({
6463 let project = self.project()?;
6464 let dap_store = project.read(cx).dap_store();
6465 let mut scenarios = vec![];
6466 let resolved_tasks = resolved_tasks.as_ref()?;
6467 let buffer = buffer.read(cx);
6468 let language = buffer.language()?;
6469 let file = buffer.file();
6470 let debug_adapter = language_settings(language.name().into(), file, cx)
6471 .debuggers
6472 .first()
6473 .map(SharedString::from)
6474 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6475
6476 dap_store.update(cx, |dap_store, cx| {
6477 for (_, task) in &resolved_tasks.templates {
6478 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6479 task.original_task().clone(),
6480 debug_adapter.clone().into(),
6481 task.display_label().to_owned().into(),
6482 cx,
6483 );
6484 scenarios.push(maybe_scenario);
6485 }
6486 });
6487 Some(cx.background_spawn(async move {
6488 futures::future::join_all(scenarios)
6489 .await
6490 .into_iter()
6491 .flatten()
6492 .collect::<Vec<_>>()
6493 }))
6494 })
6495 .unwrap_or_else(|| Task::ready(vec![]))
6496 }
6497
6498 fn code_actions(
6499 &mut self,
6500 buffer_row: u32,
6501 window: &mut Window,
6502 cx: &mut Context<Self>,
6503 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6504 let mut task = self.code_actions_task.take();
6505 cx.spawn_in(window, async move |editor, cx| {
6506 while let Some(prev_task) = task {
6507 prev_task.await.log_err();
6508 task = editor
6509 .update(cx, |this, _| this.code_actions_task.take())
6510 .ok()?;
6511 }
6512
6513 editor
6514 .update(cx, |editor, cx| {
6515 editor
6516 .available_code_actions
6517 .clone()
6518 .and_then(|(location, code_actions)| {
6519 let snapshot = location.buffer.read(cx).snapshot();
6520 let point_range = location.range.to_point(&snapshot);
6521 let point_range = point_range.start.row..=point_range.end.row;
6522 if point_range.contains(&buffer_row) {
6523 Some(code_actions)
6524 } else {
6525 None
6526 }
6527 })
6528 })
6529 .ok()
6530 .flatten()
6531 })
6532 }
6533
6534 pub fn confirm_code_action(
6535 &mut self,
6536 action: &ConfirmCodeAction,
6537 window: &mut Window,
6538 cx: &mut Context<Self>,
6539 ) -> Option<Task<Result<()>>> {
6540 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6541
6542 let actions_menu =
6543 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6544 menu
6545 } else {
6546 return None;
6547 };
6548
6549 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6550 let action = actions_menu.actions.get(action_ix)?;
6551 let title = action.label();
6552 let buffer = actions_menu.buffer;
6553 let workspace = self.workspace()?;
6554
6555 match action {
6556 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6557 workspace.update(cx, |workspace, cx| {
6558 workspace.schedule_resolved_task(
6559 task_source_kind,
6560 resolved_task,
6561 false,
6562 window,
6563 cx,
6564 );
6565
6566 Some(Task::ready(Ok(())))
6567 })
6568 }
6569 CodeActionsItem::CodeAction {
6570 excerpt_id,
6571 action,
6572 provider,
6573 } => {
6574 let apply_code_action =
6575 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6576 let workspace = workspace.downgrade();
6577 Some(cx.spawn_in(window, async move |editor, cx| {
6578 let project_transaction = apply_code_action.await?;
6579 Self::open_project_transaction(
6580 &editor,
6581 workspace,
6582 project_transaction,
6583 title,
6584 cx,
6585 )
6586 .await
6587 }))
6588 }
6589 CodeActionsItem::DebugScenario(scenario) => {
6590 let context = actions_menu.actions.context;
6591
6592 workspace.update(cx, |workspace, cx| {
6593 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6594 workspace.start_debug_session(
6595 scenario,
6596 context,
6597 Some(buffer),
6598 None,
6599 window,
6600 cx,
6601 );
6602 });
6603 Some(Task::ready(Ok(())))
6604 }
6605 }
6606 }
6607
6608 pub async fn open_project_transaction(
6609 editor: &WeakEntity<Editor>,
6610 workspace: WeakEntity<Workspace>,
6611 transaction: ProjectTransaction,
6612 title: String,
6613 cx: &mut AsyncWindowContext,
6614 ) -> Result<()> {
6615 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6616 cx.update(|_, cx| {
6617 entries.sort_unstable_by_key(|(buffer, _)| {
6618 buffer.read(cx).file().map(|f| f.path().clone())
6619 });
6620 })?;
6621 if entries.is_empty() {
6622 return Ok(());
6623 }
6624
6625 // If the project transaction's edits are all contained within this editor, then
6626 // avoid opening a new editor to display them.
6627
6628 if let [(buffer, transaction)] = &*entries {
6629 let excerpt = editor.update(cx, |editor, cx| {
6630 editor
6631 .buffer()
6632 .read(cx)
6633 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6634 })?;
6635 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6636 && excerpted_buffer == *buffer
6637 {
6638 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6639 let excerpt_range = excerpt_range.to_offset(buffer);
6640 buffer
6641 .edited_ranges_for_transaction::<usize>(transaction)
6642 .all(|range| {
6643 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6644 })
6645 })?;
6646
6647 if all_edits_within_excerpt {
6648 return Ok(());
6649 }
6650 }
6651 }
6652
6653 let mut ranges_to_highlight = Vec::new();
6654 let excerpt_buffer = cx.new(|cx| {
6655 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6656 for (buffer_handle, transaction) in &entries {
6657 let edited_ranges = buffer_handle
6658 .read(cx)
6659 .edited_ranges_for_transaction::<Point>(transaction)
6660 .collect::<Vec<_>>();
6661 let (ranges, _) = multibuffer.set_excerpts_for_path(
6662 PathKey::for_buffer(buffer_handle, cx),
6663 buffer_handle.clone(),
6664 edited_ranges,
6665 multibuffer_context_lines(cx),
6666 cx,
6667 );
6668
6669 ranges_to_highlight.extend(ranges);
6670 }
6671 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6672 multibuffer
6673 })?;
6674
6675 workspace.update_in(cx, |workspace, window, cx| {
6676 let project = workspace.project().clone();
6677 let editor =
6678 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6679 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6680 editor.update(cx, |editor, cx| {
6681 editor.highlight_background::<Self>(
6682 &ranges_to_highlight,
6683 |_, theme| theme.colors().editor_highlighted_line_background,
6684 cx,
6685 );
6686 });
6687 })?;
6688
6689 Ok(())
6690 }
6691
6692 pub fn clear_code_action_providers(&mut self) {
6693 self.code_action_providers.clear();
6694 self.available_code_actions.take();
6695 }
6696
6697 pub fn add_code_action_provider(
6698 &mut self,
6699 provider: Rc<dyn CodeActionProvider>,
6700 window: &mut Window,
6701 cx: &mut Context<Self>,
6702 ) {
6703 if self
6704 .code_action_providers
6705 .iter()
6706 .any(|existing_provider| existing_provider.id() == provider.id())
6707 {
6708 return;
6709 }
6710
6711 self.code_action_providers.push(provider);
6712 self.refresh_code_actions(window, cx);
6713 }
6714
6715 pub fn remove_code_action_provider(
6716 &mut self,
6717 id: Arc<str>,
6718 window: &mut Window,
6719 cx: &mut Context<Self>,
6720 ) {
6721 self.code_action_providers
6722 .retain(|provider| provider.id() != id);
6723 self.refresh_code_actions(window, cx);
6724 }
6725
6726 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6727 !self.code_action_providers.is_empty()
6728 && EditorSettings::get_global(cx).toolbar.code_actions
6729 }
6730
6731 pub fn has_available_code_actions(&self) -> bool {
6732 self.available_code_actions
6733 .as_ref()
6734 .is_some_and(|(_, actions)| !actions.is_empty())
6735 }
6736
6737 fn render_inline_code_actions(
6738 &self,
6739 icon_size: ui::IconSize,
6740 display_row: DisplayRow,
6741 is_active: bool,
6742 cx: &mut Context<Self>,
6743 ) -> AnyElement {
6744 let show_tooltip = !self.context_menu_visible();
6745 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6746 .icon_size(icon_size)
6747 .shape(ui::IconButtonShape::Square)
6748 .icon_color(ui::Color::Hidden)
6749 .toggle_state(is_active)
6750 .when(show_tooltip, |this| {
6751 this.tooltip({
6752 let focus_handle = self.focus_handle.clone();
6753 move |_window, cx| {
6754 Tooltip::for_action_in(
6755 "Toggle Code Actions",
6756 &ToggleCodeActions {
6757 deployed_from: None,
6758 quick_launch: false,
6759 },
6760 &focus_handle,
6761 cx,
6762 )
6763 }
6764 })
6765 })
6766 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6767 window.focus(&editor.focus_handle(cx));
6768 editor.toggle_code_actions(
6769 &crate::actions::ToggleCodeActions {
6770 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6771 display_row,
6772 )),
6773 quick_launch: false,
6774 },
6775 window,
6776 cx,
6777 );
6778 }))
6779 .into_any_element()
6780 }
6781
6782 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6783 &self.context_menu
6784 }
6785
6786 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6787 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6788 cx.background_executor()
6789 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6790 .await;
6791
6792 let (start_buffer, start, _, end, newest_selection) = this
6793 .update(cx, |this, cx| {
6794 let newest_selection = this.selections.newest_anchor().clone();
6795 if newest_selection.head().diff_base_anchor.is_some() {
6796 return None;
6797 }
6798 let display_snapshot = this.display_snapshot(cx);
6799 let newest_selection_adjusted =
6800 this.selections.newest_adjusted(&display_snapshot);
6801 let buffer = this.buffer.read(cx);
6802
6803 let (start_buffer, start) =
6804 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6805 let (end_buffer, end) =
6806 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6807
6808 Some((start_buffer, start, end_buffer, end, newest_selection))
6809 })?
6810 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6811 .context(
6812 "Expected selection to lie in a single buffer when refreshing code actions",
6813 )?;
6814 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6815 let providers = this.code_action_providers.clone();
6816 let tasks = this
6817 .code_action_providers
6818 .iter()
6819 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6820 .collect::<Vec<_>>();
6821 (providers, tasks)
6822 })?;
6823
6824 let mut actions = Vec::new();
6825 for (provider, provider_actions) in
6826 providers.into_iter().zip(future::join_all(tasks).await)
6827 {
6828 if let Some(provider_actions) = provider_actions.log_err() {
6829 actions.extend(provider_actions.into_iter().map(|action| {
6830 AvailableCodeAction {
6831 excerpt_id: newest_selection.start.excerpt_id,
6832 action,
6833 provider: provider.clone(),
6834 }
6835 }));
6836 }
6837 }
6838
6839 this.update(cx, |this, cx| {
6840 this.available_code_actions = if actions.is_empty() {
6841 None
6842 } else {
6843 Some((
6844 Location {
6845 buffer: start_buffer,
6846 range: start..end,
6847 },
6848 actions.into(),
6849 ))
6850 };
6851 cx.notify();
6852 })
6853 }));
6854 }
6855
6856 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6857 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6858 self.show_git_blame_inline = false;
6859
6860 self.show_git_blame_inline_delay_task =
6861 Some(cx.spawn_in(window, async move |this, cx| {
6862 cx.background_executor().timer(delay).await;
6863
6864 this.update(cx, |this, cx| {
6865 this.show_git_blame_inline = true;
6866 cx.notify();
6867 })
6868 .log_err();
6869 }));
6870 }
6871 }
6872
6873 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6874 let snapshot = self.snapshot(window, cx);
6875 let cursor = self
6876 .selections
6877 .newest::<Point>(&snapshot.display_snapshot)
6878 .head();
6879 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6880 else {
6881 return;
6882 };
6883
6884 if self.blame.is_none() {
6885 self.start_git_blame(true, window, cx);
6886 }
6887 let Some(blame) = self.blame.as_ref() else {
6888 return;
6889 };
6890
6891 let row_info = RowInfo {
6892 buffer_id: Some(buffer.remote_id()),
6893 buffer_row: Some(point.row),
6894 ..Default::default()
6895 };
6896 let Some((buffer, blame_entry)) = blame
6897 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6898 .flatten()
6899 else {
6900 return;
6901 };
6902
6903 let anchor = self.selections.newest_anchor().head();
6904 let position = self.to_pixel_point(anchor, &snapshot, window, cx);
6905 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6906 self.show_blame_popover(
6907 buffer,
6908 &blame_entry,
6909 position + last_bounds.origin,
6910 true,
6911 cx,
6912 );
6913 };
6914 }
6915
6916 fn show_blame_popover(
6917 &mut self,
6918 buffer: BufferId,
6919 blame_entry: &BlameEntry,
6920 position: gpui::Point<Pixels>,
6921 ignore_timeout: bool,
6922 cx: &mut Context<Self>,
6923 ) {
6924 if let Some(state) = &mut self.inline_blame_popover {
6925 state.hide_task.take();
6926 } else {
6927 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6928 let blame_entry = blame_entry.clone();
6929 let show_task = cx.spawn(async move |editor, cx| {
6930 if !ignore_timeout {
6931 cx.background_executor()
6932 .timer(std::time::Duration::from_millis(blame_popover_delay))
6933 .await;
6934 }
6935 editor
6936 .update(cx, |editor, cx| {
6937 editor.inline_blame_popover_show_task.take();
6938 let Some(blame) = editor.blame.as_ref() else {
6939 return;
6940 };
6941 let blame = blame.read(cx);
6942 let details = blame.details_for_entry(buffer, &blame_entry);
6943 let markdown = cx.new(|cx| {
6944 Markdown::new(
6945 details
6946 .as_ref()
6947 .map(|message| message.message.clone())
6948 .unwrap_or_default(),
6949 None,
6950 None,
6951 cx,
6952 )
6953 });
6954 editor.inline_blame_popover = Some(InlineBlamePopover {
6955 position,
6956 hide_task: None,
6957 popover_bounds: None,
6958 popover_state: InlineBlamePopoverState {
6959 scroll_handle: ScrollHandle::new(),
6960 commit_message: details,
6961 markdown,
6962 },
6963 keyboard_grace: ignore_timeout,
6964 });
6965 cx.notify();
6966 })
6967 .ok();
6968 });
6969 self.inline_blame_popover_show_task = Some(show_task);
6970 }
6971 }
6972
6973 pub fn has_mouse_context_menu(&self) -> bool {
6974 self.mouse_context_menu.is_some()
6975 }
6976
6977 pub fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6978 self.inline_blame_popover_show_task.take();
6979 if let Some(state) = &mut self.inline_blame_popover {
6980 let hide_task = cx.spawn(async move |editor, cx| {
6981 if !ignore_timeout {
6982 cx.background_executor()
6983 .timer(std::time::Duration::from_millis(100))
6984 .await;
6985 }
6986 editor
6987 .update(cx, |editor, cx| {
6988 editor.inline_blame_popover.take();
6989 cx.notify();
6990 })
6991 .ok();
6992 });
6993 state.hide_task = Some(hide_task);
6994 true
6995 } else {
6996 false
6997 }
6998 }
6999
7000 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
7001 if self.pending_rename.is_some() {
7002 return None;
7003 }
7004
7005 let provider = self.semantics_provider.clone()?;
7006 let buffer = self.buffer.read(cx);
7007 let newest_selection = self.selections.newest_anchor().clone();
7008 let cursor_position = newest_selection.head();
7009 let (cursor_buffer, cursor_buffer_position) =
7010 buffer.text_anchor_for_position(cursor_position, cx)?;
7011 let (tail_buffer, tail_buffer_position) =
7012 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
7013 if cursor_buffer != tail_buffer {
7014 return None;
7015 }
7016
7017 let snapshot = cursor_buffer.read(cx).snapshot();
7018 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
7019 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
7020 if start_word_range != end_word_range {
7021 self.document_highlights_task.take();
7022 self.clear_background_highlights::<DocumentHighlightRead>(cx);
7023 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
7024 return None;
7025 }
7026
7027 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
7028 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
7029 cx.background_executor()
7030 .timer(Duration::from_millis(debounce))
7031 .await;
7032
7033 let highlights = if let Some(highlights) = cx
7034 .update(|cx| {
7035 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
7036 })
7037 .ok()
7038 .flatten()
7039 {
7040 highlights.await.log_err()
7041 } else {
7042 None
7043 };
7044
7045 if let Some(highlights) = highlights {
7046 this.update(cx, |this, cx| {
7047 if this.pending_rename.is_some() {
7048 return;
7049 }
7050
7051 let buffer = this.buffer.read(cx);
7052 if buffer
7053 .text_anchor_for_position(cursor_position, cx)
7054 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
7055 {
7056 return;
7057 }
7058
7059 let cursor_buffer_snapshot = cursor_buffer.read(cx);
7060 let mut write_ranges = Vec::new();
7061 let mut read_ranges = Vec::new();
7062 for highlight in highlights {
7063 let buffer_id = cursor_buffer.read(cx).remote_id();
7064 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
7065 {
7066 let start = highlight
7067 .range
7068 .start
7069 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
7070 let end = highlight
7071 .range
7072 .end
7073 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
7074 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
7075 continue;
7076 }
7077
7078 let range = Anchor::range_in_buffer(excerpt_id, *start..*end);
7079 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
7080 write_ranges.push(range);
7081 } else {
7082 read_ranges.push(range);
7083 }
7084 }
7085 }
7086
7087 this.highlight_background::<DocumentHighlightRead>(
7088 &read_ranges,
7089 |_, theme| theme.colors().editor_document_highlight_read_background,
7090 cx,
7091 );
7092 this.highlight_background::<DocumentHighlightWrite>(
7093 &write_ranges,
7094 |_, theme| theme.colors().editor_document_highlight_write_background,
7095 cx,
7096 );
7097 cx.notify();
7098 })
7099 .log_err();
7100 }
7101 }));
7102 None
7103 }
7104
7105 fn prepare_highlight_query_from_selection(
7106 &mut self,
7107 window: &Window,
7108 cx: &mut Context<Editor>,
7109 ) -> Option<(String, Range<Anchor>)> {
7110 if matches!(self.mode, EditorMode::SingleLine) {
7111 return None;
7112 }
7113 if !EditorSettings::get_global(cx).selection_highlight {
7114 return None;
7115 }
7116 if self.selections.count() != 1 || self.selections.line_mode() {
7117 return None;
7118 }
7119 let snapshot = self.snapshot(window, cx);
7120 let selection = self.selections.newest::<Point>(&snapshot);
7121 // If the selection spans multiple rows OR it is empty
7122 if selection.start.row != selection.end.row
7123 || selection.start.column == selection.end.column
7124 {
7125 return None;
7126 }
7127 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
7128 let query = snapshot
7129 .buffer_snapshot()
7130 .text_for_range(selection_anchor_range.clone())
7131 .collect::<String>();
7132 if query.trim().is_empty() {
7133 return None;
7134 }
7135 Some((query, selection_anchor_range))
7136 }
7137
7138 #[ztracing::instrument(skip_all)]
7139 fn update_selection_occurrence_highlights(
7140 &mut self,
7141 query_text: String,
7142 query_range: Range<Anchor>,
7143 multi_buffer_range_to_query: Range<Point>,
7144 use_debounce: bool,
7145 window: &mut Window,
7146 cx: &mut Context<Editor>,
7147 ) -> Task<()> {
7148 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7149 cx.spawn_in(window, async move |editor, cx| {
7150 if use_debounce {
7151 cx.background_executor()
7152 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
7153 .await;
7154 }
7155 let match_task = cx.background_spawn(async move {
7156 let buffer_ranges = multi_buffer_snapshot
7157 .range_to_buffer_ranges(multi_buffer_range_to_query)
7158 .into_iter()
7159 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
7160 let mut match_ranges = Vec::new();
7161 let Ok(regex) = project::search::SearchQuery::text(
7162 query_text.clone(),
7163 false,
7164 false,
7165 false,
7166 Default::default(),
7167 Default::default(),
7168 false,
7169 None,
7170 ) else {
7171 return Vec::default();
7172 };
7173 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
7174 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
7175 match_ranges.extend(
7176 regex
7177 .search(
7178 buffer_snapshot,
7179 Some(search_range.start.0..search_range.end.0),
7180 )
7181 .await
7182 .into_iter()
7183 .filter_map(|match_range| {
7184 let match_start = buffer_snapshot
7185 .anchor_after(search_range.start + match_range.start);
7186 let match_end = buffer_snapshot
7187 .anchor_before(search_range.start + match_range.end);
7188 let match_anchor_range =
7189 Anchor::range_in_buffer(excerpt_id, match_start..match_end);
7190 (match_anchor_range != query_range).then_some(match_anchor_range)
7191 }),
7192 );
7193 }
7194 match_ranges
7195 });
7196 let match_ranges = match_task.await;
7197 editor
7198 .update_in(cx, |editor, _, cx| {
7199 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
7200 if !match_ranges.is_empty() {
7201 editor.highlight_background::<SelectedTextHighlight>(
7202 &match_ranges,
7203 |_, theme| theme.colors().editor_document_highlight_bracket_background,
7204 cx,
7205 )
7206 }
7207 })
7208 .log_err();
7209 })
7210 }
7211
7212 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
7213 struct NewlineFold;
7214 let type_id = std::any::TypeId::of::<NewlineFold>();
7215 if !self.mode.is_single_line() {
7216 return;
7217 }
7218 let snapshot = self.snapshot(window, cx);
7219 if snapshot.buffer_snapshot().max_point().row == 0 {
7220 return;
7221 }
7222 let task = cx.background_spawn(async move {
7223 let new_newlines = snapshot
7224 .buffer_chars_at(MultiBufferOffset(0))
7225 .filter_map(|(c, i)| {
7226 if c == '\n' {
7227 Some(
7228 snapshot.buffer_snapshot().anchor_after(i)
7229 ..snapshot.buffer_snapshot().anchor_before(i + 1usize),
7230 )
7231 } else {
7232 None
7233 }
7234 })
7235 .collect::<Vec<_>>();
7236 let existing_newlines = snapshot
7237 .folds_in_range(MultiBufferOffset(0)..snapshot.buffer_snapshot().len())
7238 .filter_map(|fold| {
7239 if fold.placeholder.type_tag == Some(type_id) {
7240 Some(fold.range.start..fold.range.end)
7241 } else {
7242 None
7243 }
7244 })
7245 .collect::<Vec<_>>();
7246
7247 (new_newlines, existing_newlines)
7248 });
7249 self.folding_newlines = cx.spawn(async move |this, cx| {
7250 let (new_newlines, existing_newlines) = task.await;
7251 if new_newlines == existing_newlines {
7252 return;
7253 }
7254 let placeholder = FoldPlaceholder {
7255 render: Arc::new(move |_, _, cx| {
7256 div()
7257 .bg(cx.theme().status().hint_background)
7258 .border_b_1()
7259 .size_full()
7260 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7261 .border_color(cx.theme().status().hint)
7262 .child("\\n")
7263 .into_any()
7264 }),
7265 constrain_width: false,
7266 merge_adjacent: false,
7267 type_tag: Some(type_id),
7268 };
7269 let creases = new_newlines
7270 .into_iter()
7271 .map(|range| Crease::simple(range, placeholder.clone()))
7272 .collect();
7273 this.update(cx, |this, cx| {
7274 this.display_map.update(cx, |display_map, cx| {
7275 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7276 display_map.fold(creases, cx);
7277 });
7278 })
7279 .ok();
7280 });
7281 }
7282
7283 #[ztracing::instrument(skip_all)]
7284 fn refresh_selected_text_highlights(
7285 &mut self,
7286 on_buffer_edit: bool,
7287 window: &mut Window,
7288 cx: &mut Context<Editor>,
7289 ) {
7290 let Some((query_text, query_range)) =
7291 self.prepare_highlight_query_from_selection(window, cx)
7292 else {
7293 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7294 self.quick_selection_highlight_task.take();
7295 self.debounced_selection_highlight_task.take();
7296 return;
7297 };
7298 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7299 if on_buffer_edit
7300 || self
7301 .quick_selection_highlight_task
7302 .as_ref()
7303 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7304 {
7305 let multi_buffer_visible_start = self
7306 .scroll_manager
7307 .anchor()
7308 .anchor
7309 .to_point(&multi_buffer_snapshot);
7310 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7311 multi_buffer_visible_start
7312 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7313 Bias::Left,
7314 );
7315 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7316 self.quick_selection_highlight_task = Some((
7317 query_range.clone(),
7318 self.update_selection_occurrence_highlights(
7319 query_text.clone(),
7320 query_range.clone(),
7321 multi_buffer_visible_range,
7322 false,
7323 window,
7324 cx,
7325 ),
7326 ));
7327 }
7328 if on_buffer_edit
7329 || self
7330 .debounced_selection_highlight_task
7331 .as_ref()
7332 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7333 {
7334 let multi_buffer_start = multi_buffer_snapshot
7335 .anchor_before(MultiBufferOffset(0))
7336 .to_point(&multi_buffer_snapshot);
7337 let multi_buffer_end = multi_buffer_snapshot
7338 .anchor_after(multi_buffer_snapshot.len())
7339 .to_point(&multi_buffer_snapshot);
7340 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7341 self.debounced_selection_highlight_task = Some((
7342 query_range.clone(),
7343 self.update_selection_occurrence_highlights(
7344 query_text,
7345 query_range,
7346 multi_buffer_full_range,
7347 true,
7348 window,
7349 cx,
7350 ),
7351 ));
7352 }
7353 }
7354
7355 pub fn refresh_edit_prediction(
7356 &mut self,
7357 debounce: bool,
7358 user_requested: bool,
7359 window: &mut Window,
7360 cx: &mut Context<Self>,
7361 ) -> Option<()> {
7362 if DisableAiSettings::get_global(cx).disable_ai {
7363 return None;
7364 }
7365
7366 let provider = self.edit_prediction_provider()?;
7367 let cursor = self.selections.newest_anchor().head();
7368 let (buffer, cursor_buffer_position) =
7369 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7370
7371 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7372 self.discard_edit_prediction(false, cx);
7373 return None;
7374 }
7375
7376 self.update_visible_edit_prediction(window, cx);
7377
7378 if !user_requested
7379 && (!self.should_show_edit_predictions()
7380 || !self.is_focused(window)
7381 || buffer.read(cx).is_empty())
7382 {
7383 self.discard_edit_prediction(false, cx);
7384 return None;
7385 }
7386
7387 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7388 Some(())
7389 }
7390
7391 fn show_edit_predictions_in_menu(&self) -> bool {
7392 match self.edit_prediction_settings {
7393 EditPredictionSettings::Disabled => false,
7394 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7395 }
7396 }
7397
7398 pub fn edit_predictions_enabled(&self) -> bool {
7399 match self.edit_prediction_settings {
7400 EditPredictionSettings::Disabled => false,
7401 EditPredictionSettings::Enabled { .. } => true,
7402 }
7403 }
7404
7405 fn edit_prediction_requires_modifier(&self) -> bool {
7406 match self.edit_prediction_settings {
7407 EditPredictionSettings::Disabled => false,
7408 EditPredictionSettings::Enabled {
7409 preview_requires_modifier,
7410 ..
7411 } => preview_requires_modifier,
7412 }
7413 }
7414
7415 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7416 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7417 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7418 self.discard_edit_prediction(false, cx);
7419 } else {
7420 let selection = self.selections.newest_anchor();
7421 let cursor = selection.head();
7422
7423 if let Some((buffer, cursor_buffer_position)) =
7424 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7425 {
7426 self.edit_prediction_settings =
7427 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7428 }
7429 }
7430 }
7431
7432 fn edit_prediction_settings_at_position(
7433 &self,
7434 buffer: &Entity<Buffer>,
7435 buffer_position: language::Anchor,
7436 cx: &App,
7437 ) -> EditPredictionSettings {
7438 if !self.mode.is_full()
7439 || !self.show_edit_predictions_override.unwrap_or(true)
7440 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7441 {
7442 return EditPredictionSettings::Disabled;
7443 }
7444
7445 let buffer = buffer.read(cx);
7446
7447 let file = buffer.file();
7448
7449 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7450 return EditPredictionSettings::Disabled;
7451 };
7452
7453 let by_provider = matches!(
7454 self.menu_edit_predictions_policy,
7455 MenuEditPredictionsPolicy::ByProvider
7456 );
7457
7458 let show_in_menu = by_provider
7459 && self
7460 .edit_prediction_provider
7461 .as_ref()
7462 .is_some_and(|provider| provider.provider.show_predictions_in_menu());
7463
7464 let preview_requires_modifier =
7465 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7466
7467 EditPredictionSettings::Enabled {
7468 show_in_menu,
7469 preview_requires_modifier,
7470 }
7471 }
7472
7473 fn should_show_edit_predictions(&self) -> bool {
7474 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7475 }
7476
7477 pub fn edit_prediction_preview_is_active(&self) -> bool {
7478 matches!(
7479 self.edit_prediction_preview,
7480 EditPredictionPreview::Active { .. }
7481 )
7482 }
7483
7484 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7485 let cursor = self.selections.newest_anchor().head();
7486 if let Some((buffer, cursor_position)) =
7487 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7488 {
7489 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7490 } else {
7491 false
7492 }
7493 }
7494
7495 pub fn supports_minimap(&self, cx: &App) -> bool {
7496 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7497 }
7498
7499 fn edit_predictions_enabled_in_buffer(
7500 &self,
7501 buffer: &Entity<Buffer>,
7502 buffer_position: language::Anchor,
7503 cx: &App,
7504 ) -> bool {
7505 maybe!({
7506 if self.read_only(cx) {
7507 return Some(false);
7508 }
7509 let provider = self.edit_prediction_provider()?;
7510 if !provider.is_enabled(buffer, buffer_position, cx) {
7511 return Some(false);
7512 }
7513 let buffer = buffer.read(cx);
7514 let Some(file) = buffer.file() else {
7515 return Some(true);
7516 };
7517 let settings = all_language_settings(Some(file), cx);
7518 Some(settings.edit_predictions_enabled_for_file(file, cx))
7519 })
7520 .unwrap_or(false)
7521 }
7522
7523 fn cycle_edit_prediction(
7524 &mut self,
7525 direction: Direction,
7526 window: &mut Window,
7527 cx: &mut Context<Self>,
7528 ) -> Option<()> {
7529 let provider = self.edit_prediction_provider()?;
7530 let cursor = self.selections.newest_anchor().head();
7531 let (buffer, cursor_buffer_position) =
7532 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7533 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7534 return None;
7535 }
7536
7537 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7538 self.update_visible_edit_prediction(window, cx);
7539
7540 Some(())
7541 }
7542
7543 pub fn show_edit_prediction(
7544 &mut self,
7545 _: &ShowEditPrediction,
7546 window: &mut Window,
7547 cx: &mut Context<Self>,
7548 ) {
7549 if !self.has_active_edit_prediction() {
7550 self.refresh_edit_prediction(false, true, window, cx);
7551 return;
7552 }
7553
7554 self.update_visible_edit_prediction(window, cx);
7555 }
7556
7557 pub fn display_cursor_names(
7558 &mut self,
7559 _: &DisplayCursorNames,
7560 window: &mut Window,
7561 cx: &mut Context<Self>,
7562 ) {
7563 self.show_cursor_names(window, cx);
7564 }
7565
7566 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7567 self.show_cursor_names = true;
7568 cx.notify();
7569 cx.spawn_in(window, async move |this, cx| {
7570 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7571 this.update(cx, |this, cx| {
7572 this.show_cursor_names = false;
7573 cx.notify()
7574 })
7575 .ok()
7576 })
7577 .detach();
7578 }
7579
7580 pub fn next_edit_prediction(
7581 &mut self,
7582 _: &NextEditPrediction,
7583 window: &mut Window,
7584 cx: &mut Context<Self>,
7585 ) {
7586 if self.has_active_edit_prediction() {
7587 self.cycle_edit_prediction(Direction::Next, window, cx);
7588 } else {
7589 let is_copilot_disabled = self
7590 .refresh_edit_prediction(false, true, window, cx)
7591 .is_none();
7592 if is_copilot_disabled {
7593 cx.propagate();
7594 }
7595 }
7596 }
7597
7598 pub fn previous_edit_prediction(
7599 &mut self,
7600 _: &PreviousEditPrediction,
7601 window: &mut Window,
7602 cx: &mut Context<Self>,
7603 ) {
7604 if self.has_active_edit_prediction() {
7605 self.cycle_edit_prediction(Direction::Prev, window, cx);
7606 } else {
7607 let is_copilot_disabled = self
7608 .refresh_edit_prediction(false, true, window, cx)
7609 .is_none();
7610 if is_copilot_disabled {
7611 cx.propagate();
7612 }
7613 }
7614 }
7615
7616 pub fn accept_edit_prediction(
7617 &mut self,
7618 _: &AcceptEditPrediction,
7619 window: &mut Window,
7620 cx: &mut Context<Self>,
7621 ) {
7622 if self.show_edit_predictions_in_menu() {
7623 self.hide_context_menu(window, cx);
7624 }
7625
7626 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7627 return;
7628 };
7629
7630 match &active_edit_prediction.completion {
7631 EditPrediction::MoveWithin { target, .. } => {
7632 let target = *target;
7633
7634 if let Some(position_map) = &self.last_position_map {
7635 if position_map
7636 .visible_row_range
7637 .contains(&target.to_display_point(&position_map.snapshot).row())
7638 || !self.edit_prediction_requires_modifier()
7639 {
7640 self.unfold_ranges(&[target..target], true, false, cx);
7641 // Note that this is also done in vim's handler of the Tab action.
7642 self.change_selections(
7643 SelectionEffects::scroll(Autoscroll::newest()),
7644 window,
7645 cx,
7646 |selections| {
7647 selections.select_anchor_ranges([target..target]);
7648 },
7649 );
7650 self.clear_row_highlights::<EditPredictionPreview>();
7651
7652 self.edit_prediction_preview
7653 .set_previous_scroll_position(None);
7654 } else {
7655 self.edit_prediction_preview
7656 .set_previous_scroll_position(Some(
7657 position_map.snapshot.scroll_anchor,
7658 ));
7659
7660 self.highlight_rows::<EditPredictionPreview>(
7661 target..target,
7662 cx.theme().colors().editor_highlighted_line_background,
7663 RowHighlightOptions {
7664 autoscroll: true,
7665 ..Default::default()
7666 },
7667 cx,
7668 );
7669 self.request_autoscroll(Autoscroll::fit(), cx);
7670 }
7671 }
7672 }
7673 EditPrediction::MoveOutside { snapshot, target } => {
7674 if let Some(workspace) = self.workspace() {
7675 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7676 .detach_and_log_err(cx);
7677 }
7678 }
7679 EditPrediction::Edit { edits, .. } => {
7680 self.report_edit_prediction_event(
7681 active_edit_prediction.completion_id.clone(),
7682 true,
7683 cx,
7684 );
7685
7686 if let Some(provider) = self.edit_prediction_provider() {
7687 provider.accept(cx);
7688 }
7689
7690 // Store the transaction ID and selections before applying the edit
7691 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7692
7693 let snapshot = self.buffer.read(cx).snapshot(cx);
7694 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7695
7696 self.buffer.update(cx, |buffer, cx| {
7697 buffer.edit(edits.iter().cloned(), None, cx)
7698 });
7699
7700 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7701 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7702 });
7703
7704 let selections = self.selections.disjoint_anchors_arc();
7705 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7706 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7707 if has_new_transaction {
7708 self.selection_history
7709 .insert_transaction(transaction_id_now, selections);
7710 }
7711 }
7712
7713 self.update_visible_edit_prediction(window, cx);
7714 if self.active_edit_prediction.is_none() {
7715 self.refresh_edit_prediction(true, true, window, cx);
7716 }
7717
7718 cx.notify();
7719 }
7720 }
7721
7722 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7723 }
7724
7725 pub fn accept_partial_edit_prediction(
7726 &mut self,
7727 _: &AcceptPartialEditPrediction,
7728 window: &mut Window,
7729 cx: &mut Context<Self>,
7730 ) {
7731 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7732 return;
7733 };
7734 if self.selections.count() != 1 {
7735 return;
7736 }
7737
7738 match &active_edit_prediction.completion {
7739 EditPrediction::MoveWithin { target, .. } => {
7740 let target = *target;
7741 self.change_selections(
7742 SelectionEffects::scroll(Autoscroll::newest()),
7743 window,
7744 cx,
7745 |selections| {
7746 selections.select_anchor_ranges([target..target]);
7747 },
7748 );
7749 }
7750 EditPrediction::MoveOutside { snapshot, target } => {
7751 if let Some(workspace) = self.workspace() {
7752 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7753 .detach_and_log_err(cx);
7754 }
7755 }
7756 EditPrediction::Edit { edits, .. } => {
7757 self.report_edit_prediction_event(
7758 active_edit_prediction.completion_id.clone(),
7759 true,
7760 cx,
7761 );
7762
7763 // Find an insertion that starts at the cursor position.
7764 let snapshot = self.buffer.read(cx).snapshot(cx);
7765 let cursor_offset = self
7766 .selections
7767 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
7768 .head();
7769 let insertion = edits.iter().find_map(|(range, text)| {
7770 let range = range.to_offset(&snapshot);
7771 if range.is_empty() && range.start == cursor_offset {
7772 Some(text)
7773 } else {
7774 None
7775 }
7776 });
7777
7778 if let Some(text) = insertion {
7779 let mut partial_completion = text
7780 .chars()
7781 .by_ref()
7782 .take_while(|c| c.is_alphabetic())
7783 .collect::<String>();
7784 if partial_completion.is_empty() {
7785 partial_completion = text
7786 .chars()
7787 .by_ref()
7788 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7789 .collect::<String>();
7790 }
7791
7792 cx.emit(EditorEvent::InputHandled {
7793 utf16_range_to_replace: None,
7794 text: partial_completion.clone().into(),
7795 });
7796
7797 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7798
7799 self.refresh_edit_prediction(true, true, window, cx);
7800 cx.notify();
7801 } else {
7802 self.accept_edit_prediction(&Default::default(), window, cx);
7803 }
7804 }
7805 }
7806 }
7807
7808 fn discard_edit_prediction(
7809 &mut self,
7810 should_report_edit_prediction_event: bool,
7811 cx: &mut Context<Self>,
7812 ) -> bool {
7813 if should_report_edit_prediction_event {
7814 let completion_id = self
7815 .active_edit_prediction
7816 .as_ref()
7817 .and_then(|active_completion| active_completion.completion_id.clone());
7818
7819 self.report_edit_prediction_event(completion_id, false, cx);
7820 }
7821
7822 if let Some(provider) = self.edit_prediction_provider() {
7823 provider.discard(cx);
7824 }
7825
7826 self.take_active_edit_prediction(cx)
7827 }
7828
7829 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7830 let Some(provider) = self.edit_prediction_provider() else {
7831 return;
7832 };
7833
7834 let Some((_, buffer, _)) = self
7835 .buffer
7836 .read(cx)
7837 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7838 else {
7839 return;
7840 };
7841
7842 let extension = buffer
7843 .read(cx)
7844 .file()
7845 .and_then(|file| Some(file.path().extension()?.to_string()));
7846
7847 let event_type = match accepted {
7848 true => "Edit Prediction Accepted",
7849 false => "Edit Prediction Discarded",
7850 };
7851 telemetry::event!(
7852 event_type,
7853 provider = provider.name(),
7854 prediction_id = id,
7855 suggestion_accepted = accepted,
7856 file_extension = extension,
7857 );
7858 }
7859
7860 fn open_editor_at_anchor(
7861 snapshot: &language::BufferSnapshot,
7862 target: language::Anchor,
7863 workspace: &Entity<Workspace>,
7864 window: &mut Window,
7865 cx: &mut App,
7866 ) -> Task<Result<()>> {
7867 workspace.update(cx, |workspace, cx| {
7868 let path = snapshot.file().map(|file| file.full_path(cx));
7869 let Some(path) =
7870 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7871 else {
7872 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7873 };
7874 let target = text::ToPoint::to_point(&target, snapshot);
7875 let item = workspace.open_path(path, None, true, window, cx);
7876 window.spawn(cx, async move |cx| {
7877 let Some(editor) = item.await?.downcast::<Editor>() else {
7878 return Ok(());
7879 };
7880 editor
7881 .update_in(cx, |editor, window, cx| {
7882 editor.go_to_singleton_buffer_point(target, window, cx);
7883 })
7884 .ok();
7885 anyhow::Ok(())
7886 })
7887 })
7888 }
7889
7890 pub fn has_active_edit_prediction(&self) -> bool {
7891 self.active_edit_prediction.is_some()
7892 }
7893
7894 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7895 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7896 return false;
7897 };
7898
7899 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7900 self.clear_highlights::<EditPredictionHighlight>(cx);
7901 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7902 true
7903 }
7904
7905 /// Returns true when we're displaying the edit prediction popover below the cursor
7906 /// like we are not previewing and the LSP autocomplete menu is visible
7907 /// or we are in `when_holding_modifier` mode.
7908 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7909 if self.edit_prediction_preview_is_active()
7910 || !self.show_edit_predictions_in_menu()
7911 || !self.edit_predictions_enabled()
7912 {
7913 return false;
7914 }
7915
7916 if self.has_visible_completions_menu() {
7917 return true;
7918 }
7919
7920 has_completion && self.edit_prediction_requires_modifier()
7921 }
7922
7923 fn handle_modifiers_changed(
7924 &mut self,
7925 modifiers: Modifiers,
7926 position_map: &PositionMap,
7927 window: &mut Window,
7928 cx: &mut Context<Self>,
7929 ) {
7930 // Ensure that the edit prediction preview is updated, even when not
7931 // enabled, if there's an active edit prediction preview.
7932 if self.show_edit_predictions_in_menu()
7933 || matches!(
7934 self.edit_prediction_preview,
7935 EditPredictionPreview::Active { .. }
7936 )
7937 {
7938 self.update_edit_prediction_preview(&modifiers, window, cx);
7939 }
7940
7941 self.update_selection_mode(&modifiers, position_map, window, cx);
7942
7943 let mouse_position = window.mouse_position();
7944 if !position_map.text_hitbox.is_hovered(window) {
7945 return;
7946 }
7947
7948 self.update_hovered_link(
7949 position_map.point_for_position(mouse_position),
7950 &position_map.snapshot,
7951 modifiers,
7952 window,
7953 cx,
7954 )
7955 }
7956
7957 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7958 match EditorSettings::get_global(cx).multi_cursor_modifier {
7959 MultiCursorModifier::Alt => modifiers.secondary(),
7960 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7961 }
7962 }
7963
7964 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7965 match EditorSettings::get_global(cx).multi_cursor_modifier {
7966 MultiCursorModifier::Alt => modifiers.alt,
7967 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7968 }
7969 }
7970
7971 fn columnar_selection_mode(
7972 modifiers: &Modifiers,
7973 cx: &mut Context<Self>,
7974 ) -> Option<ColumnarMode> {
7975 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7976 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7977 Some(ColumnarMode::FromMouse)
7978 } else if Self::is_alt_pressed(modifiers, cx) {
7979 Some(ColumnarMode::FromSelection)
7980 } else {
7981 None
7982 }
7983 } else {
7984 None
7985 }
7986 }
7987
7988 fn update_selection_mode(
7989 &mut self,
7990 modifiers: &Modifiers,
7991 position_map: &PositionMap,
7992 window: &mut Window,
7993 cx: &mut Context<Self>,
7994 ) {
7995 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7996 return;
7997 };
7998 if self.selections.pending_anchor().is_none() {
7999 return;
8000 }
8001
8002 let mouse_position = window.mouse_position();
8003 let point_for_position = position_map.point_for_position(mouse_position);
8004 let position = point_for_position.previous_valid;
8005
8006 self.select(
8007 SelectPhase::BeginColumnar {
8008 position,
8009 reset: false,
8010 mode,
8011 goal_column: point_for_position.exact_unclipped.column(),
8012 },
8013 window,
8014 cx,
8015 );
8016 }
8017
8018 fn update_edit_prediction_preview(
8019 &mut self,
8020 modifiers: &Modifiers,
8021 window: &mut Window,
8022 cx: &mut Context<Self>,
8023 ) {
8024 let mut modifiers_held = false;
8025 if let Some(accept_keystroke) = self
8026 .accept_edit_prediction_keybind(false, window, cx)
8027 .keystroke()
8028 {
8029 modifiers_held = modifiers_held
8030 || (accept_keystroke.modifiers() == modifiers
8031 && accept_keystroke.modifiers().modified());
8032 };
8033 if let Some(accept_partial_keystroke) = self
8034 .accept_edit_prediction_keybind(true, window, cx)
8035 .keystroke()
8036 {
8037 modifiers_held = modifiers_held
8038 || (accept_partial_keystroke.modifiers() == modifiers
8039 && accept_partial_keystroke.modifiers().modified());
8040 }
8041
8042 if modifiers_held {
8043 if matches!(
8044 self.edit_prediction_preview,
8045 EditPredictionPreview::Inactive { .. }
8046 ) {
8047 if let Some(provider) = self.edit_prediction_provider.as_ref() {
8048 provider.provider.did_show(cx)
8049 }
8050
8051 self.edit_prediction_preview = EditPredictionPreview::Active {
8052 previous_scroll_position: None,
8053 since: Instant::now(),
8054 };
8055
8056 self.update_visible_edit_prediction(window, cx);
8057 cx.notify();
8058 }
8059 } else if let EditPredictionPreview::Active {
8060 previous_scroll_position,
8061 since,
8062 } = self.edit_prediction_preview
8063 {
8064 if let (Some(previous_scroll_position), Some(position_map)) =
8065 (previous_scroll_position, self.last_position_map.as_ref())
8066 {
8067 self.set_scroll_position(
8068 previous_scroll_position
8069 .scroll_position(&position_map.snapshot.display_snapshot),
8070 window,
8071 cx,
8072 );
8073 }
8074
8075 self.edit_prediction_preview = EditPredictionPreview::Inactive {
8076 released_too_fast: since.elapsed() < Duration::from_millis(200),
8077 };
8078 self.clear_row_highlights::<EditPredictionPreview>();
8079 self.update_visible_edit_prediction(window, cx);
8080 cx.notify();
8081 }
8082 }
8083
8084 fn update_visible_edit_prediction(
8085 &mut self,
8086 _window: &mut Window,
8087 cx: &mut Context<Self>,
8088 ) -> Option<()> {
8089 if DisableAiSettings::get_global(cx).disable_ai {
8090 return None;
8091 }
8092
8093 if self.ime_transaction.is_some() {
8094 self.discard_edit_prediction(false, cx);
8095 return None;
8096 }
8097
8098 let selection = self.selections.newest_anchor();
8099 let cursor = selection.head();
8100 let multibuffer = self.buffer.read(cx).snapshot(cx);
8101 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
8102 let excerpt_id = cursor.excerpt_id;
8103
8104 let show_in_menu = self.show_edit_predictions_in_menu();
8105 let completions_menu_has_precedence = !show_in_menu
8106 && (self.context_menu.borrow().is_some()
8107 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
8108
8109 if completions_menu_has_precedence
8110 || !offset_selection.is_empty()
8111 || self
8112 .active_edit_prediction
8113 .as_ref()
8114 .is_some_and(|completion| {
8115 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
8116 return false;
8117 };
8118 let invalidation_range = invalidation_range.to_offset(&multibuffer);
8119 let invalidation_range = invalidation_range.start..=invalidation_range.end;
8120 !invalidation_range.contains(&offset_selection.head())
8121 })
8122 {
8123 self.discard_edit_prediction(false, cx);
8124 return None;
8125 }
8126
8127 self.take_active_edit_prediction(cx);
8128 let Some(provider) = self.edit_prediction_provider() else {
8129 self.edit_prediction_settings = EditPredictionSettings::Disabled;
8130 return None;
8131 };
8132
8133 let (buffer, cursor_buffer_position) =
8134 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
8135
8136 self.edit_prediction_settings =
8137 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
8138
8139 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
8140
8141 if self.edit_prediction_indent_conflict {
8142 let cursor_point = cursor.to_point(&multibuffer);
8143 let mut suggested_indent = None;
8144 multibuffer.suggested_indents_callback(
8145 cursor_point.row..cursor_point.row + 1,
8146 |_, indent| {
8147 suggested_indent = Some(indent);
8148 ControlFlow::Break(())
8149 },
8150 cx,
8151 );
8152
8153 if let Some(indent) = suggested_indent
8154 && indent.len == cursor_point.column
8155 {
8156 self.edit_prediction_indent_conflict = false;
8157 }
8158 }
8159
8160 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
8161
8162 let (completion_id, edits, edit_preview) = match edit_prediction {
8163 edit_prediction_types::EditPrediction::Local {
8164 id,
8165 edits,
8166 edit_preview,
8167 } => (id, edits, edit_preview),
8168 edit_prediction_types::EditPrediction::Jump {
8169 id,
8170 snapshot,
8171 target,
8172 } => {
8173 self.stale_edit_prediction_in_menu = None;
8174 self.active_edit_prediction = Some(EditPredictionState {
8175 inlay_ids: vec![],
8176 completion: EditPrediction::MoveOutside { snapshot, target },
8177 completion_id: id,
8178 invalidation_range: None,
8179 });
8180 cx.notify();
8181 return Some(());
8182 }
8183 };
8184
8185 let edits = edits
8186 .into_iter()
8187 .flat_map(|(range, new_text)| {
8188 Some((
8189 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
8190 new_text,
8191 ))
8192 })
8193 .collect::<Vec<_>>();
8194 if edits.is_empty() {
8195 return None;
8196 }
8197
8198 let first_edit_start = edits.first().unwrap().0.start;
8199 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
8200 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
8201
8202 let last_edit_end = edits.last().unwrap().0.end;
8203 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
8204 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
8205
8206 let cursor_row = cursor.to_point(&multibuffer).row;
8207
8208 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
8209
8210 let mut inlay_ids = Vec::new();
8211 let invalidation_row_range;
8212 let move_invalidation_row_range = if cursor_row < edit_start_row {
8213 Some(cursor_row..edit_end_row)
8214 } else if cursor_row > edit_end_row {
8215 Some(edit_start_row..cursor_row)
8216 } else {
8217 None
8218 };
8219 let supports_jump = self
8220 .edit_prediction_provider
8221 .as_ref()
8222 .map(|provider| provider.provider.supports_jump_to_edit())
8223 .unwrap_or(true);
8224
8225 let is_move = supports_jump
8226 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
8227 let completion = if is_move {
8228 invalidation_row_range =
8229 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8230 let target = first_edit_start;
8231 EditPrediction::MoveWithin { target, snapshot }
8232 } else {
8233 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8234 && !self.edit_predictions_hidden_for_vim_mode;
8235
8236 if show_completions_in_buffer {
8237 if let Some(provider) = &self.edit_prediction_provider {
8238 provider.provider.did_show(cx);
8239 }
8240 if edits
8241 .iter()
8242 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8243 {
8244 let mut inlays = Vec::new();
8245 for (range, new_text) in &edits {
8246 let inlay = Inlay::edit_prediction(
8247 post_inc(&mut self.next_inlay_id),
8248 range.start,
8249 new_text.as_ref(),
8250 );
8251 inlay_ids.push(inlay.id);
8252 inlays.push(inlay);
8253 }
8254
8255 self.splice_inlays(&[], inlays, cx);
8256 } else {
8257 let background_color = cx.theme().status().deleted_background;
8258 self.highlight_text::<EditPredictionHighlight>(
8259 edits.iter().map(|(range, _)| range.clone()).collect(),
8260 HighlightStyle {
8261 background_color: Some(background_color),
8262 ..Default::default()
8263 },
8264 cx,
8265 );
8266 }
8267 }
8268
8269 invalidation_row_range = edit_start_row..edit_end_row;
8270
8271 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8272 if provider.show_tab_accept_marker() {
8273 EditDisplayMode::TabAccept
8274 } else {
8275 EditDisplayMode::Inline
8276 }
8277 } else {
8278 EditDisplayMode::DiffPopover
8279 };
8280
8281 EditPrediction::Edit {
8282 edits,
8283 edit_preview,
8284 display_mode,
8285 snapshot,
8286 }
8287 };
8288
8289 let invalidation_range = multibuffer
8290 .anchor_before(Point::new(invalidation_row_range.start, 0))
8291 ..multibuffer.anchor_after(Point::new(
8292 invalidation_row_range.end,
8293 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8294 ));
8295
8296 self.stale_edit_prediction_in_menu = None;
8297 self.active_edit_prediction = Some(EditPredictionState {
8298 inlay_ids,
8299 completion,
8300 completion_id,
8301 invalidation_range: Some(invalidation_range),
8302 });
8303
8304 cx.notify();
8305
8306 Some(())
8307 }
8308
8309 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionDelegateHandle>> {
8310 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8311 }
8312
8313 fn clear_tasks(&mut self) {
8314 self.tasks.clear()
8315 }
8316
8317 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8318 if self.tasks.insert(key, value).is_some() {
8319 // This case should hopefully be rare, but just in case...
8320 log::error!(
8321 "multiple different run targets found on a single line, only the last target will be rendered"
8322 )
8323 }
8324 }
8325
8326 /// Get all display points of breakpoints that will be rendered within editor
8327 ///
8328 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8329 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8330 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8331 fn active_breakpoints(
8332 &self,
8333 range: Range<DisplayRow>,
8334 window: &mut Window,
8335 cx: &mut Context<Self>,
8336 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8337 let mut breakpoint_display_points = HashMap::default();
8338
8339 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8340 return breakpoint_display_points;
8341 };
8342
8343 let snapshot = self.snapshot(window, cx);
8344
8345 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8346 let Some(project) = self.project() else {
8347 return breakpoint_display_points;
8348 };
8349
8350 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8351 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8352
8353 for (buffer_snapshot, range, excerpt_id) in
8354 multi_buffer_snapshot.range_to_buffer_ranges(range)
8355 {
8356 let Some(buffer) = project
8357 .read(cx)
8358 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8359 else {
8360 continue;
8361 };
8362 let breakpoints = breakpoint_store.read(cx).breakpoints(
8363 &buffer,
8364 Some(
8365 buffer_snapshot.anchor_before(range.start)
8366 ..buffer_snapshot.anchor_after(range.end),
8367 ),
8368 buffer_snapshot,
8369 cx,
8370 );
8371 for (breakpoint, state) in breakpoints {
8372 let multi_buffer_anchor = Anchor::in_buffer(excerpt_id, breakpoint.position);
8373 let position = multi_buffer_anchor
8374 .to_point(&multi_buffer_snapshot)
8375 .to_display_point(&snapshot);
8376
8377 breakpoint_display_points.insert(
8378 position.row(),
8379 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8380 );
8381 }
8382 }
8383
8384 breakpoint_display_points
8385 }
8386
8387 fn breakpoint_context_menu(
8388 &self,
8389 anchor: Anchor,
8390 window: &mut Window,
8391 cx: &mut Context<Self>,
8392 ) -> Entity<ui::ContextMenu> {
8393 let weak_editor = cx.weak_entity();
8394 let focus_handle = self.focus_handle(cx);
8395
8396 let row = self
8397 .buffer
8398 .read(cx)
8399 .snapshot(cx)
8400 .summary_for_anchor::<Point>(&anchor)
8401 .row;
8402
8403 let breakpoint = self
8404 .breakpoint_at_row(row, window, cx)
8405 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8406
8407 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8408 "Edit Log Breakpoint"
8409 } else {
8410 "Set Log Breakpoint"
8411 };
8412
8413 let condition_breakpoint_msg = if breakpoint
8414 .as_ref()
8415 .is_some_and(|bp| bp.1.condition.is_some())
8416 {
8417 "Edit Condition Breakpoint"
8418 } else {
8419 "Set Condition Breakpoint"
8420 };
8421
8422 let hit_condition_breakpoint_msg = if breakpoint
8423 .as_ref()
8424 .is_some_and(|bp| bp.1.hit_condition.is_some())
8425 {
8426 "Edit Hit Condition Breakpoint"
8427 } else {
8428 "Set Hit Condition Breakpoint"
8429 };
8430
8431 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8432 "Unset Breakpoint"
8433 } else {
8434 "Set Breakpoint"
8435 };
8436
8437 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8438
8439 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8440 BreakpointState::Enabled => Some("Disable"),
8441 BreakpointState::Disabled => Some("Enable"),
8442 });
8443
8444 let (anchor, breakpoint) =
8445 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8446
8447 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8448 menu.on_blur_subscription(Subscription::new(|| {}))
8449 .context(focus_handle)
8450 .when(run_to_cursor, |this| {
8451 let weak_editor = weak_editor.clone();
8452 this.entry("Run to cursor", None, move |window, cx| {
8453 weak_editor
8454 .update(cx, |editor, cx| {
8455 editor.change_selections(
8456 SelectionEffects::no_scroll(),
8457 window,
8458 cx,
8459 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8460 );
8461 })
8462 .ok();
8463
8464 window.dispatch_action(Box::new(RunToCursor), cx);
8465 })
8466 .separator()
8467 })
8468 .when_some(toggle_state_msg, |this, msg| {
8469 this.entry(msg, None, {
8470 let weak_editor = weak_editor.clone();
8471 let breakpoint = breakpoint.clone();
8472 move |_window, cx| {
8473 weak_editor
8474 .update(cx, |this, cx| {
8475 this.edit_breakpoint_at_anchor(
8476 anchor,
8477 breakpoint.as_ref().clone(),
8478 BreakpointEditAction::InvertState,
8479 cx,
8480 );
8481 })
8482 .log_err();
8483 }
8484 })
8485 })
8486 .entry(set_breakpoint_msg, None, {
8487 let weak_editor = weak_editor.clone();
8488 let breakpoint = breakpoint.clone();
8489 move |_window, cx| {
8490 weak_editor
8491 .update(cx, |this, cx| {
8492 this.edit_breakpoint_at_anchor(
8493 anchor,
8494 breakpoint.as_ref().clone(),
8495 BreakpointEditAction::Toggle,
8496 cx,
8497 );
8498 })
8499 .log_err();
8500 }
8501 })
8502 .entry(log_breakpoint_msg, None, {
8503 let breakpoint = breakpoint.clone();
8504 let weak_editor = weak_editor.clone();
8505 move |window, cx| {
8506 weak_editor
8507 .update(cx, |this, cx| {
8508 this.add_edit_breakpoint_block(
8509 anchor,
8510 breakpoint.as_ref(),
8511 BreakpointPromptEditAction::Log,
8512 window,
8513 cx,
8514 );
8515 })
8516 .log_err();
8517 }
8518 })
8519 .entry(condition_breakpoint_msg, None, {
8520 let breakpoint = breakpoint.clone();
8521 let weak_editor = weak_editor.clone();
8522 move |window, cx| {
8523 weak_editor
8524 .update(cx, |this, cx| {
8525 this.add_edit_breakpoint_block(
8526 anchor,
8527 breakpoint.as_ref(),
8528 BreakpointPromptEditAction::Condition,
8529 window,
8530 cx,
8531 );
8532 })
8533 .log_err();
8534 }
8535 })
8536 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8537 weak_editor
8538 .update(cx, |this, cx| {
8539 this.add_edit_breakpoint_block(
8540 anchor,
8541 breakpoint.as_ref(),
8542 BreakpointPromptEditAction::HitCondition,
8543 window,
8544 cx,
8545 );
8546 })
8547 .log_err();
8548 })
8549 })
8550 }
8551
8552 fn render_breakpoint(
8553 &self,
8554 position: Anchor,
8555 row: DisplayRow,
8556 breakpoint: &Breakpoint,
8557 state: Option<BreakpointSessionState>,
8558 cx: &mut Context<Self>,
8559 ) -> IconButton {
8560 let is_rejected = state.is_some_and(|s| !s.verified);
8561 // Is it a breakpoint that shows up when hovering over gutter?
8562 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8563 (false, false),
8564 |PhantomBreakpointIndicator {
8565 is_active,
8566 display_row,
8567 collides_with_existing_breakpoint,
8568 }| {
8569 (
8570 is_active && display_row == row,
8571 collides_with_existing_breakpoint,
8572 )
8573 },
8574 );
8575
8576 let (color, icon) = {
8577 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8578 (false, false) => ui::IconName::DebugBreakpoint,
8579 (true, false) => ui::IconName::DebugLogBreakpoint,
8580 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8581 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8582 };
8583
8584 let color = cx.theme().colors();
8585
8586 let color = if is_phantom {
8587 if collides_with_existing {
8588 Color::Custom(color.debugger_accent.blend(color.text.opacity(0.6)))
8589 } else {
8590 Color::Hint
8591 }
8592 } else if is_rejected {
8593 Color::Disabled
8594 } else {
8595 Color::Debugger
8596 };
8597
8598 (color, icon)
8599 };
8600
8601 let breakpoint = Arc::from(breakpoint.clone());
8602
8603 let alt_as_text = gpui::Keystroke {
8604 modifiers: Modifiers::secondary_key(),
8605 ..Default::default()
8606 };
8607 let primary_action_text = if breakpoint.is_disabled() {
8608 "Enable breakpoint"
8609 } else if is_phantom && !collides_with_existing {
8610 "Set breakpoint"
8611 } else {
8612 "Unset breakpoint"
8613 };
8614 let focus_handle = self.focus_handle.clone();
8615
8616 let meta = if is_rejected {
8617 SharedString::from("No executable code is associated with this line.")
8618 } else if collides_with_existing && !breakpoint.is_disabled() {
8619 SharedString::from(format!(
8620 "{alt_as_text}-click to disable,\nright-click for more options."
8621 ))
8622 } else {
8623 SharedString::from("Right-click for more options.")
8624 };
8625 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8626 .icon_size(IconSize::XSmall)
8627 .size(ui::ButtonSize::None)
8628 .when(is_rejected, |this| {
8629 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8630 })
8631 .icon_color(color)
8632 .style(ButtonStyle::Transparent)
8633 .on_click(cx.listener({
8634 move |editor, event: &ClickEvent, window, cx| {
8635 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8636 BreakpointEditAction::InvertState
8637 } else {
8638 BreakpointEditAction::Toggle
8639 };
8640
8641 window.focus(&editor.focus_handle(cx));
8642 editor.edit_breakpoint_at_anchor(
8643 position,
8644 breakpoint.as_ref().clone(),
8645 edit_action,
8646 cx,
8647 );
8648 }
8649 }))
8650 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8651 editor.set_breakpoint_context_menu(
8652 row,
8653 Some(position),
8654 event.position(),
8655 window,
8656 cx,
8657 );
8658 }))
8659 .tooltip(move |_window, cx| {
8660 Tooltip::with_meta_in(
8661 primary_action_text,
8662 Some(&ToggleBreakpoint),
8663 meta.clone(),
8664 &focus_handle,
8665 cx,
8666 )
8667 })
8668 }
8669
8670 fn build_tasks_context(
8671 project: &Entity<Project>,
8672 buffer: &Entity<Buffer>,
8673 buffer_row: u32,
8674 tasks: &Arc<RunnableTasks>,
8675 cx: &mut Context<Self>,
8676 ) -> Task<Option<task::TaskContext>> {
8677 let position = Point::new(buffer_row, tasks.column);
8678 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8679 let location = Location {
8680 buffer: buffer.clone(),
8681 range: range_start..range_start,
8682 };
8683 // Fill in the environmental variables from the tree-sitter captures
8684 let mut captured_task_variables = TaskVariables::default();
8685 for (capture_name, value) in tasks.extra_variables.clone() {
8686 captured_task_variables.insert(
8687 task::VariableName::Custom(capture_name.into()),
8688 value.clone(),
8689 );
8690 }
8691 project.update(cx, |project, cx| {
8692 project.task_store().update(cx, |task_store, cx| {
8693 task_store.task_context_for_location(captured_task_variables, location, cx)
8694 })
8695 })
8696 }
8697
8698 pub fn spawn_nearest_task(
8699 &mut self,
8700 action: &SpawnNearestTask,
8701 window: &mut Window,
8702 cx: &mut Context<Self>,
8703 ) {
8704 let Some((workspace, _)) = self.workspace.clone() else {
8705 return;
8706 };
8707 let Some(project) = self.project.clone() else {
8708 return;
8709 };
8710
8711 // Try to find a closest, enclosing node using tree-sitter that has a task
8712 let Some((buffer, buffer_row, tasks)) = self
8713 .find_enclosing_node_task(cx)
8714 // Or find the task that's closest in row-distance.
8715 .or_else(|| self.find_closest_task(cx))
8716 else {
8717 return;
8718 };
8719
8720 let reveal_strategy = action.reveal;
8721 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8722 cx.spawn_in(window, async move |_, cx| {
8723 let context = task_context.await?;
8724 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8725
8726 let resolved = &mut resolved_task.resolved;
8727 resolved.reveal = reveal_strategy;
8728
8729 workspace
8730 .update_in(cx, |workspace, window, cx| {
8731 workspace.schedule_resolved_task(
8732 task_source_kind,
8733 resolved_task,
8734 false,
8735 window,
8736 cx,
8737 );
8738 })
8739 .ok()
8740 })
8741 .detach();
8742 }
8743
8744 fn find_closest_task(
8745 &mut self,
8746 cx: &mut Context<Self>,
8747 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8748 let cursor_row = self
8749 .selections
8750 .newest_adjusted(&self.display_snapshot(cx))
8751 .head()
8752 .row;
8753
8754 let ((buffer_id, row), tasks) = self
8755 .tasks
8756 .iter()
8757 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8758
8759 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8760 let tasks = Arc::new(tasks.to_owned());
8761 Some((buffer, *row, tasks))
8762 }
8763
8764 fn find_enclosing_node_task(
8765 &mut self,
8766 cx: &mut Context<Self>,
8767 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8768 let snapshot = self.buffer.read(cx).snapshot(cx);
8769 let offset = self
8770 .selections
8771 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
8772 .head();
8773 let mut excerpt = snapshot.excerpt_containing(offset..offset)?;
8774 let offset = excerpt.map_offset_to_buffer(offset);
8775 let buffer_id = excerpt.buffer().remote_id();
8776
8777 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8778 let mut cursor = layer.node().walk();
8779
8780 while cursor.goto_first_child_for_byte(offset.0).is_some() {
8781 if cursor.node().end_byte() == offset.0 {
8782 cursor.goto_next_sibling();
8783 }
8784 }
8785
8786 // Ascend to the smallest ancestor that contains the range and has a task.
8787 loop {
8788 let node = cursor.node();
8789 let node_range = node.byte_range();
8790 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8791
8792 // Check if this node contains our offset
8793 if node_range.start <= offset.0 && node_range.end >= offset.0 {
8794 // If it contains offset, check for task
8795 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8796 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8797 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8798 }
8799 }
8800
8801 if !cursor.goto_parent() {
8802 break;
8803 }
8804 }
8805 None
8806 }
8807
8808 fn render_run_indicator(
8809 &self,
8810 _style: &EditorStyle,
8811 is_active: bool,
8812 row: DisplayRow,
8813 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8814 cx: &mut Context<Self>,
8815 ) -> IconButton {
8816 let color = Color::Muted;
8817 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8818
8819 IconButton::new(
8820 ("run_indicator", row.0 as usize),
8821 ui::IconName::PlayOutlined,
8822 )
8823 .shape(ui::IconButtonShape::Square)
8824 .icon_size(IconSize::XSmall)
8825 .icon_color(color)
8826 .toggle_state(is_active)
8827 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8828 let quick_launch = match e {
8829 ClickEvent::Keyboard(_) => true,
8830 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8831 };
8832
8833 window.focus(&editor.focus_handle(cx));
8834 editor.toggle_code_actions(
8835 &ToggleCodeActions {
8836 deployed_from: Some(CodeActionSource::RunMenu(row)),
8837 quick_launch,
8838 },
8839 window,
8840 cx,
8841 );
8842 }))
8843 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8844 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8845 }))
8846 }
8847
8848 pub fn context_menu_visible(&self) -> bool {
8849 !self.edit_prediction_preview_is_active()
8850 && self
8851 .context_menu
8852 .borrow()
8853 .as_ref()
8854 .is_some_and(|menu| menu.visible())
8855 }
8856
8857 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8858 self.context_menu
8859 .borrow()
8860 .as_ref()
8861 .map(|menu| menu.origin())
8862 }
8863
8864 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8865 self.context_menu_options = Some(options);
8866 }
8867
8868 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8869 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8870
8871 fn render_edit_prediction_popover(
8872 &mut self,
8873 text_bounds: &Bounds<Pixels>,
8874 content_origin: gpui::Point<Pixels>,
8875 right_margin: Pixels,
8876 editor_snapshot: &EditorSnapshot,
8877 visible_row_range: Range<DisplayRow>,
8878 scroll_top: ScrollOffset,
8879 scroll_bottom: ScrollOffset,
8880 line_layouts: &[LineWithInvisibles],
8881 line_height: Pixels,
8882 scroll_position: gpui::Point<ScrollOffset>,
8883 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8884 newest_selection_head: Option<DisplayPoint>,
8885 editor_width: Pixels,
8886 style: &EditorStyle,
8887 window: &mut Window,
8888 cx: &mut App,
8889 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8890 if self.mode().is_minimap() {
8891 return None;
8892 }
8893 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8894
8895 if self.edit_prediction_visible_in_cursor_popover(true) {
8896 return None;
8897 }
8898
8899 match &active_edit_prediction.completion {
8900 EditPrediction::MoveWithin { target, .. } => {
8901 let target_display_point = target.to_display_point(editor_snapshot);
8902
8903 if self.edit_prediction_requires_modifier() {
8904 if !self.edit_prediction_preview_is_active() {
8905 return None;
8906 }
8907
8908 self.render_edit_prediction_modifier_jump_popover(
8909 text_bounds,
8910 content_origin,
8911 visible_row_range,
8912 line_layouts,
8913 line_height,
8914 scroll_pixel_position,
8915 newest_selection_head,
8916 target_display_point,
8917 window,
8918 cx,
8919 )
8920 } else {
8921 self.render_edit_prediction_eager_jump_popover(
8922 text_bounds,
8923 content_origin,
8924 editor_snapshot,
8925 visible_row_range,
8926 scroll_top,
8927 scroll_bottom,
8928 line_height,
8929 scroll_pixel_position,
8930 target_display_point,
8931 editor_width,
8932 window,
8933 cx,
8934 )
8935 }
8936 }
8937 EditPrediction::Edit {
8938 display_mode: EditDisplayMode::Inline,
8939 ..
8940 } => None,
8941 EditPrediction::Edit {
8942 display_mode: EditDisplayMode::TabAccept,
8943 edits,
8944 ..
8945 } => {
8946 let range = &edits.first()?.0;
8947 let target_display_point = range.end.to_display_point(editor_snapshot);
8948
8949 self.render_edit_prediction_end_of_line_popover(
8950 "Accept",
8951 editor_snapshot,
8952 visible_row_range,
8953 target_display_point,
8954 line_height,
8955 scroll_pixel_position,
8956 content_origin,
8957 editor_width,
8958 window,
8959 cx,
8960 )
8961 }
8962 EditPrediction::Edit {
8963 edits,
8964 edit_preview,
8965 display_mode: EditDisplayMode::DiffPopover,
8966 snapshot,
8967 } => self.render_edit_prediction_diff_popover(
8968 text_bounds,
8969 content_origin,
8970 right_margin,
8971 editor_snapshot,
8972 visible_row_range,
8973 line_layouts,
8974 line_height,
8975 scroll_position,
8976 scroll_pixel_position,
8977 newest_selection_head,
8978 editor_width,
8979 style,
8980 edits,
8981 edit_preview,
8982 snapshot,
8983 window,
8984 cx,
8985 ),
8986 EditPrediction::MoveOutside { snapshot, .. } => {
8987 let mut element = self
8988 .render_edit_prediction_jump_outside_popover(snapshot, window, cx)
8989 .into_any();
8990
8991 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8992 let origin_x = text_bounds.size.width - size.width - px(30.);
8993 let origin = text_bounds.origin + gpui::Point::new(origin_x, px(16.));
8994 element.prepaint_at(origin, window, cx);
8995
8996 Some((element, origin))
8997 }
8998 }
8999 }
9000
9001 fn render_edit_prediction_modifier_jump_popover(
9002 &mut self,
9003 text_bounds: &Bounds<Pixels>,
9004 content_origin: gpui::Point<Pixels>,
9005 visible_row_range: Range<DisplayRow>,
9006 line_layouts: &[LineWithInvisibles],
9007 line_height: Pixels,
9008 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9009 newest_selection_head: Option<DisplayPoint>,
9010 target_display_point: DisplayPoint,
9011 window: &mut Window,
9012 cx: &mut App,
9013 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9014 let scrolled_content_origin =
9015 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
9016
9017 const SCROLL_PADDING_Y: Pixels = px(12.);
9018
9019 if target_display_point.row() < visible_row_range.start {
9020 return self.render_edit_prediction_scroll_popover(
9021 |_| SCROLL_PADDING_Y,
9022 IconName::ArrowUp,
9023 visible_row_range,
9024 line_layouts,
9025 newest_selection_head,
9026 scrolled_content_origin,
9027 window,
9028 cx,
9029 );
9030 } else if target_display_point.row() >= visible_row_range.end {
9031 return self.render_edit_prediction_scroll_popover(
9032 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
9033 IconName::ArrowDown,
9034 visible_row_range,
9035 line_layouts,
9036 newest_selection_head,
9037 scrolled_content_origin,
9038 window,
9039 cx,
9040 );
9041 }
9042
9043 const POLE_WIDTH: Pixels = px(2.);
9044
9045 let line_layout =
9046 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
9047 let target_column = target_display_point.column() as usize;
9048
9049 let target_x = line_layout.x_for_index(target_column);
9050 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
9051 - scroll_pixel_position.y;
9052
9053 let flag_on_right = target_x < text_bounds.size.width / 2.;
9054
9055 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
9056 border_color.l += 0.001;
9057
9058 let mut element = v_flex()
9059 .items_end()
9060 .when(flag_on_right, |el| el.items_start())
9061 .child(if flag_on_right {
9062 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9063 .rounded_bl(px(0.))
9064 .rounded_tl(px(0.))
9065 .border_l_2()
9066 .border_color(border_color)
9067 } else {
9068 self.render_edit_prediction_line_popover("Jump", None, window, cx)
9069 .rounded_br(px(0.))
9070 .rounded_tr(px(0.))
9071 .border_r_2()
9072 .border_color(border_color)
9073 })
9074 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
9075 .into_any();
9076
9077 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9078
9079 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
9080 - point(
9081 if flag_on_right {
9082 POLE_WIDTH
9083 } else {
9084 size.width - POLE_WIDTH
9085 },
9086 size.height - line_height,
9087 );
9088
9089 origin.x = origin.x.max(content_origin.x);
9090
9091 element.prepaint_at(origin, window, cx);
9092
9093 Some((element, origin))
9094 }
9095
9096 fn render_edit_prediction_scroll_popover(
9097 &mut self,
9098 to_y: impl Fn(Size<Pixels>) -> Pixels,
9099 scroll_icon: IconName,
9100 visible_row_range: Range<DisplayRow>,
9101 line_layouts: &[LineWithInvisibles],
9102 newest_selection_head: Option<DisplayPoint>,
9103 scrolled_content_origin: gpui::Point<Pixels>,
9104 window: &mut Window,
9105 cx: &mut App,
9106 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9107 let mut element = self
9108 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
9109 .into_any();
9110
9111 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9112
9113 let cursor = newest_selection_head?;
9114 let cursor_row_layout =
9115 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
9116 let cursor_column = cursor.column() as usize;
9117
9118 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
9119
9120 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
9121
9122 element.prepaint_at(origin, window, cx);
9123 Some((element, origin))
9124 }
9125
9126 fn render_edit_prediction_eager_jump_popover(
9127 &mut self,
9128 text_bounds: &Bounds<Pixels>,
9129 content_origin: gpui::Point<Pixels>,
9130 editor_snapshot: &EditorSnapshot,
9131 visible_row_range: Range<DisplayRow>,
9132 scroll_top: ScrollOffset,
9133 scroll_bottom: ScrollOffset,
9134 line_height: Pixels,
9135 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9136 target_display_point: DisplayPoint,
9137 editor_width: Pixels,
9138 window: &mut Window,
9139 cx: &mut App,
9140 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9141 if target_display_point.row().as_f64() < scroll_top {
9142 let mut element = self
9143 .render_edit_prediction_line_popover(
9144 "Jump to Edit",
9145 Some(IconName::ArrowUp),
9146 window,
9147 cx,
9148 )
9149 .into_any();
9150
9151 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9152 let offset = point(
9153 (text_bounds.size.width - size.width) / 2.,
9154 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9155 );
9156
9157 let origin = text_bounds.origin + offset;
9158 element.prepaint_at(origin, window, cx);
9159 Some((element, origin))
9160 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
9161 let mut element = self
9162 .render_edit_prediction_line_popover(
9163 "Jump to Edit",
9164 Some(IconName::ArrowDown),
9165 window,
9166 cx,
9167 )
9168 .into_any();
9169
9170 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9171 let offset = point(
9172 (text_bounds.size.width - size.width) / 2.,
9173 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
9174 );
9175
9176 let origin = text_bounds.origin + offset;
9177 element.prepaint_at(origin, window, cx);
9178 Some((element, origin))
9179 } else {
9180 self.render_edit_prediction_end_of_line_popover(
9181 "Jump to Edit",
9182 editor_snapshot,
9183 visible_row_range,
9184 target_display_point,
9185 line_height,
9186 scroll_pixel_position,
9187 content_origin,
9188 editor_width,
9189 window,
9190 cx,
9191 )
9192 }
9193 }
9194
9195 fn render_edit_prediction_end_of_line_popover(
9196 self: &mut Editor,
9197 label: &'static str,
9198 editor_snapshot: &EditorSnapshot,
9199 visible_row_range: Range<DisplayRow>,
9200 target_display_point: DisplayPoint,
9201 line_height: Pixels,
9202 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9203 content_origin: gpui::Point<Pixels>,
9204 editor_width: Pixels,
9205 window: &mut Window,
9206 cx: &mut App,
9207 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9208 let target_line_end = DisplayPoint::new(
9209 target_display_point.row(),
9210 editor_snapshot.line_len(target_display_point.row()),
9211 );
9212
9213 let mut element = self
9214 .render_edit_prediction_line_popover(label, None, window, cx)
9215 .into_any();
9216
9217 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9218
9219 let line_origin =
9220 self.display_to_pixel_point(target_line_end, editor_snapshot, window, cx)?;
9221
9222 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
9223 let mut origin = start_point
9224 + line_origin
9225 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
9226 origin.x = origin.x.max(content_origin.x);
9227
9228 let max_x = content_origin.x + editor_width - size.width;
9229
9230 if origin.x > max_x {
9231 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9232
9233 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9234 origin.y += offset;
9235 IconName::ArrowUp
9236 } else {
9237 origin.y -= offset;
9238 IconName::ArrowDown
9239 };
9240
9241 element = self
9242 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9243 .into_any();
9244
9245 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9246
9247 origin.x = content_origin.x + editor_width - size.width - px(2.);
9248 }
9249
9250 element.prepaint_at(origin, window, cx);
9251 Some((element, origin))
9252 }
9253
9254 fn render_edit_prediction_diff_popover(
9255 self: &Editor,
9256 text_bounds: &Bounds<Pixels>,
9257 content_origin: gpui::Point<Pixels>,
9258 right_margin: Pixels,
9259 editor_snapshot: &EditorSnapshot,
9260 visible_row_range: Range<DisplayRow>,
9261 line_layouts: &[LineWithInvisibles],
9262 line_height: Pixels,
9263 scroll_position: gpui::Point<ScrollOffset>,
9264 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9265 newest_selection_head: Option<DisplayPoint>,
9266 editor_width: Pixels,
9267 style: &EditorStyle,
9268 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9269 edit_preview: &Option<language::EditPreview>,
9270 snapshot: &language::BufferSnapshot,
9271 window: &mut Window,
9272 cx: &mut App,
9273 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9274 let edit_start = edits
9275 .first()
9276 .unwrap()
9277 .0
9278 .start
9279 .to_display_point(editor_snapshot);
9280 let edit_end = edits
9281 .last()
9282 .unwrap()
9283 .0
9284 .end
9285 .to_display_point(editor_snapshot);
9286
9287 let is_visible = visible_row_range.contains(&edit_start.row())
9288 || visible_row_range.contains(&edit_end.row());
9289 if !is_visible {
9290 return None;
9291 }
9292
9293 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9294 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9295 } else {
9296 // Fallback for providers without edit_preview
9297 crate::edit_prediction_fallback_text(edits, cx)
9298 };
9299
9300 let styled_text = highlighted_edits.to_styled_text(&style.text);
9301 let line_count = highlighted_edits.text.lines().count();
9302
9303 const BORDER_WIDTH: Pixels = px(1.);
9304
9305 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9306 let has_keybind = keybind.is_some();
9307
9308 let mut element = h_flex()
9309 .items_start()
9310 .child(
9311 h_flex()
9312 .bg(cx.theme().colors().editor_background)
9313 .border(BORDER_WIDTH)
9314 .shadow_xs()
9315 .border_color(cx.theme().colors().border)
9316 .rounded_l_lg()
9317 .when(line_count > 1, |el| el.rounded_br_lg())
9318 .pr_1()
9319 .child(styled_text),
9320 )
9321 .child(
9322 h_flex()
9323 .h(line_height + BORDER_WIDTH * 2.)
9324 .px_1p5()
9325 .gap_1()
9326 // Workaround: For some reason, there's a gap if we don't do this
9327 .ml(-BORDER_WIDTH)
9328 .shadow(vec![gpui::BoxShadow {
9329 color: gpui::black().opacity(0.05),
9330 offset: point(px(1.), px(1.)),
9331 blur_radius: px(2.),
9332 spread_radius: px(0.),
9333 }])
9334 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9335 .border(BORDER_WIDTH)
9336 .border_color(cx.theme().colors().border)
9337 .rounded_r_lg()
9338 .id("edit_prediction_diff_popover_keybind")
9339 .when(!has_keybind, |el| {
9340 let status_colors = cx.theme().status();
9341
9342 el.bg(status_colors.error_background)
9343 .border_color(status_colors.error.opacity(0.6))
9344 .child(Icon::new(IconName::Info).color(Color::Error))
9345 .cursor_default()
9346 .hoverable_tooltip(move |_window, cx| {
9347 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9348 })
9349 })
9350 .children(keybind),
9351 )
9352 .into_any();
9353
9354 let longest_row =
9355 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9356 let longest_line_width = if visible_row_range.contains(&longest_row) {
9357 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9358 } else {
9359 layout_line(
9360 longest_row,
9361 editor_snapshot,
9362 style,
9363 editor_width,
9364 |_| false,
9365 window,
9366 cx,
9367 )
9368 .width
9369 };
9370
9371 let viewport_bounds =
9372 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9373 right: -right_margin,
9374 ..Default::default()
9375 });
9376
9377 let x_after_longest = Pixels::from(
9378 ScrollPixelOffset::from(
9379 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9380 ) - scroll_pixel_position.x,
9381 );
9382
9383 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9384
9385 // Fully visible if it can be displayed within the window (allow overlapping other
9386 // panes). However, this is only allowed if the popover starts within text_bounds.
9387 let can_position_to_the_right = x_after_longest < text_bounds.right()
9388 && x_after_longest + element_bounds.width < viewport_bounds.right();
9389
9390 let mut origin = if can_position_to_the_right {
9391 point(
9392 x_after_longest,
9393 text_bounds.origin.y
9394 + Pixels::from(
9395 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9396 - scroll_pixel_position.y,
9397 ),
9398 )
9399 } else {
9400 let cursor_row = newest_selection_head.map(|head| head.row());
9401 let above_edit = edit_start
9402 .row()
9403 .0
9404 .checked_sub(line_count as u32)
9405 .map(DisplayRow);
9406 let below_edit = Some(edit_end.row() + 1);
9407 let above_cursor =
9408 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9409 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9410
9411 // Place the edit popover adjacent to the edit if there is a location
9412 // available that is onscreen and does not obscure the cursor. Otherwise,
9413 // place it adjacent to the cursor.
9414 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9415 .into_iter()
9416 .flatten()
9417 .find(|&start_row| {
9418 let end_row = start_row + line_count as u32;
9419 visible_row_range.contains(&start_row)
9420 && visible_row_range.contains(&end_row)
9421 && cursor_row
9422 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9423 })?;
9424
9425 content_origin
9426 + point(
9427 Pixels::from(-scroll_pixel_position.x),
9428 Pixels::from(
9429 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9430 ),
9431 )
9432 };
9433
9434 origin.x -= BORDER_WIDTH;
9435
9436 window.defer_draw(element, origin, 1);
9437
9438 // Do not return an element, since it will already be drawn due to defer_draw.
9439 None
9440 }
9441
9442 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9443 px(30.)
9444 }
9445
9446 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9447 if self.read_only(cx) {
9448 cx.theme().players().read_only()
9449 } else {
9450 self.style.as_ref().unwrap().local_player
9451 }
9452 }
9453
9454 fn render_edit_prediction_accept_keybind(
9455 &self,
9456 window: &mut Window,
9457 cx: &mut App,
9458 ) -> Option<AnyElement> {
9459 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9460 let accept_keystroke = accept_binding.keystroke()?;
9461
9462 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9463
9464 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9465 Color::Accent
9466 } else {
9467 Color::Muted
9468 };
9469
9470 h_flex()
9471 .px_0p5()
9472 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9473 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9474 .text_size(TextSize::XSmall.rems(cx))
9475 .child(h_flex().children(ui::render_modifiers(
9476 accept_keystroke.modifiers(),
9477 PlatformStyle::platform(),
9478 Some(modifiers_color),
9479 Some(IconSize::XSmall.rems().into()),
9480 true,
9481 )))
9482 .when(is_platform_style_mac, |parent| {
9483 parent.child(accept_keystroke.key().to_string())
9484 })
9485 .when(!is_platform_style_mac, |parent| {
9486 parent.child(
9487 Key::new(
9488 util::capitalize(accept_keystroke.key()),
9489 Some(Color::Default),
9490 )
9491 .size(Some(IconSize::XSmall.rems().into())),
9492 )
9493 })
9494 .into_any()
9495 .into()
9496 }
9497
9498 fn render_edit_prediction_line_popover(
9499 &self,
9500 label: impl Into<SharedString>,
9501 icon: Option<IconName>,
9502 window: &mut Window,
9503 cx: &mut App,
9504 ) -> Stateful<Div> {
9505 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9506
9507 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9508 let has_keybind = keybind.is_some();
9509
9510 h_flex()
9511 .id("ep-line-popover")
9512 .py_0p5()
9513 .pl_1()
9514 .pr(padding_right)
9515 .gap_1()
9516 .rounded_md()
9517 .border_1()
9518 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9519 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9520 .shadow_xs()
9521 .when(!has_keybind, |el| {
9522 let status_colors = cx.theme().status();
9523
9524 el.bg(status_colors.error_background)
9525 .border_color(status_colors.error.opacity(0.6))
9526 .pl_2()
9527 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9528 .cursor_default()
9529 .hoverable_tooltip(move |_window, cx| {
9530 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9531 })
9532 })
9533 .children(keybind)
9534 .child(
9535 Label::new(label)
9536 .size(LabelSize::Small)
9537 .when(!has_keybind, |el| {
9538 el.color(cx.theme().status().error.into()).strikethrough()
9539 }),
9540 )
9541 .when(!has_keybind, |el| {
9542 el.child(
9543 h_flex().ml_1().child(
9544 Icon::new(IconName::Info)
9545 .size(IconSize::Small)
9546 .color(cx.theme().status().error.into()),
9547 ),
9548 )
9549 })
9550 .when_some(icon, |element, icon| {
9551 element.child(
9552 div()
9553 .mt(px(1.5))
9554 .child(Icon::new(icon).size(IconSize::Small)),
9555 )
9556 })
9557 }
9558
9559 fn render_edit_prediction_jump_outside_popover(
9560 &self,
9561 snapshot: &BufferSnapshot,
9562 window: &mut Window,
9563 cx: &mut App,
9564 ) -> Stateful<Div> {
9565 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9566 let has_keybind = keybind.is_some();
9567
9568 let file_name = snapshot
9569 .file()
9570 .map(|file| SharedString::new(file.file_name(cx)))
9571 .unwrap_or(SharedString::new_static("untitled"));
9572
9573 h_flex()
9574 .id("ep-jump-outside-popover")
9575 .py_1()
9576 .px_2()
9577 .gap_1()
9578 .rounded_md()
9579 .border_1()
9580 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9581 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9582 .shadow_xs()
9583 .when(!has_keybind, |el| {
9584 let status_colors = cx.theme().status();
9585
9586 el.bg(status_colors.error_background)
9587 .border_color(status_colors.error.opacity(0.6))
9588 .pl_2()
9589 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9590 .cursor_default()
9591 .hoverable_tooltip(move |_window, cx| {
9592 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9593 })
9594 })
9595 .children(keybind)
9596 .child(
9597 Label::new(file_name)
9598 .size(LabelSize::Small)
9599 .buffer_font(cx)
9600 .when(!has_keybind, |el| {
9601 el.color(cx.theme().status().error.into()).strikethrough()
9602 }),
9603 )
9604 .when(!has_keybind, |el| {
9605 el.child(
9606 h_flex().ml_1().child(
9607 Icon::new(IconName::Info)
9608 .size(IconSize::Small)
9609 .color(cx.theme().status().error.into()),
9610 ),
9611 )
9612 })
9613 .child(
9614 div()
9615 .mt(px(1.5))
9616 .child(Icon::new(IconName::ArrowUpRight).size(IconSize::Small)),
9617 )
9618 }
9619
9620 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9621 let accent_color = cx.theme().colors().text_accent;
9622 let editor_bg_color = cx.theme().colors().editor_background;
9623 editor_bg_color.blend(accent_color.opacity(0.1))
9624 }
9625
9626 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9627 let accent_color = cx.theme().colors().text_accent;
9628 let editor_bg_color = cx.theme().colors().editor_background;
9629 editor_bg_color.blend(accent_color.opacity(0.6))
9630 }
9631 fn get_prediction_provider_icon_name(
9632 provider: &Option<RegisteredEditPredictionDelegate>,
9633 ) -> IconName {
9634 match provider {
9635 Some(provider) => match provider.provider.name() {
9636 "copilot" => IconName::Copilot,
9637 "supermaven" => IconName::Supermaven,
9638 _ => IconName::ZedPredict,
9639 },
9640 None => IconName::ZedPredict,
9641 }
9642 }
9643
9644 fn render_edit_prediction_cursor_popover(
9645 &self,
9646 min_width: Pixels,
9647 max_width: Pixels,
9648 cursor_point: Point,
9649 style: &EditorStyle,
9650 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9651 _window: &Window,
9652 cx: &mut Context<Editor>,
9653 ) -> Option<AnyElement> {
9654 let provider = self.edit_prediction_provider.as_ref()?;
9655 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9656
9657 let is_refreshing = provider.provider.is_refreshing(cx);
9658
9659 fn pending_completion_container(icon: IconName) -> Div {
9660 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9661 }
9662
9663 let completion = match &self.active_edit_prediction {
9664 Some(prediction) => {
9665 if !self.has_visible_completions_menu() {
9666 const RADIUS: Pixels = px(6.);
9667 const BORDER_WIDTH: Pixels = px(1.);
9668
9669 return Some(
9670 h_flex()
9671 .elevation_2(cx)
9672 .border(BORDER_WIDTH)
9673 .border_color(cx.theme().colors().border)
9674 .when(accept_keystroke.is_none(), |el| {
9675 el.border_color(cx.theme().status().error)
9676 })
9677 .rounded(RADIUS)
9678 .rounded_tl(px(0.))
9679 .overflow_hidden()
9680 .child(div().px_1p5().child(match &prediction.completion {
9681 EditPrediction::MoveWithin { target, snapshot } => {
9682 use text::ToPoint as _;
9683 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9684 {
9685 Icon::new(IconName::ZedPredictDown)
9686 } else {
9687 Icon::new(IconName::ZedPredictUp)
9688 }
9689 }
9690 EditPrediction::MoveOutside { .. } => {
9691 // TODO [zeta2] custom icon for external jump?
9692 Icon::new(provider_icon)
9693 }
9694 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9695 }))
9696 .child(
9697 h_flex()
9698 .gap_1()
9699 .py_1()
9700 .px_2()
9701 .rounded_r(RADIUS - BORDER_WIDTH)
9702 .border_l_1()
9703 .border_color(cx.theme().colors().border)
9704 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9705 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9706 el.child(
9707 Label::new("Hold")
9708 .size(LabelSize::Small)
9709 .when(accept_keystroke.is_none(), |el| {
9710 el.strikethrough()
9711 })
9712 .line_height_style(LineHeightStyle::UiLabel),
9713 )
9714 })
9715 .id("edit_prediction_cursor_popover_keybind")
9716 .when(accept_keystroke.is_none(), |el| {
9717 let status_colors = cx.theme().status();
9718
9719 el.bg(status_colors.error_background)
9720 .border_color(status_colors.error.opacity(0.6))
9721 .child(Icon::new(IconName::Info).color(Color::Error))
9722 .cursor_default()
9723 .hoverable_tooltip(move |_window, cx| {
9724 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9725 .into()
9726 })
9727 })
9728 .when_some(
9729 accept_keystroke.as_ref(),
9730 |el, accept_keystroke| {
9731 el.child(h_flex().children(ui::render_modifiers(
9732 accept_keystroke.modifiers(),
9733 PlatformStyle::platform(),
9734 Some(Color::Default),
9735 Some(IconSize::XSmall.rems().into()),
9736 false,
9737 )))
9738 },
9739 ),
9740 )
9741 .into_any(),
9742 );
9743 }
9744
9745 self.render_edit_prediction_cursor_popover_preview(
9746 prediction,
9747 cursor_point,
9748 style,
9749 cx,
9750 )?
9751 }
9752
9753 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9754 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9755 stale_completion,
9756 cursor_point,
9757 style,
9758 cx,
9759 )?,
9760
9761 None => pending_completion_container(provider_icon)
9762 .child(Label::new("...").size(LabelSize::Small)),
9763 },
9764
9765 None => pending_completion_container(provider_icon)
9766 .child(Label::new("...").size(LabelSize::Small)),
9767 };
9768
9769 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9770 completion
9771 .with_animation(
9772 "loading-completion",
9773 Animation::new(Duration::from_secs(2))
9774 .repeat()
9775 .with_easing(pulsating_between(0.4, 0.8)),
9776 |label, delta| label.opacity(delta),
9777 )
9778 .into_any_element()
9779 } else {
9780 completion.into_any_element()
9781 };
9782
9783 let has_completion = self.active_edit_prediction.is_some();
9784
9785 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9786 Some(
9787 h_flex()
9788 .min_w(min_width)
9789 .max_w(max_width)
9790 .flex_1()
9791 .elevation_2(cx)
9792 .border_color(cx.theme().colors().border)
9793 .child(
9794 div()
9795 .flex_1()
9796 .py_1()
9797 .px_2()
9798 .overflow_hidden()
9799 .child(completion),
9800 )
9801 .when_some(accept_keystroke, |el, accept_keystroke| {
9802 if !accept_keystroke.modifiers().modified() {
9803 return el;
9804 }
9805
9806 el.child(
9807 h_flex()
9808 .h_full()
9809 .border_l_1()
9810 .rounded_r_lg()
9811 .border_color(cx.theme().colors().border)
9812 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9813 .gap_1()
9814 .py_1()
9815 .px_2()
9816 .child(
9817 h_flex()
9818 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9819 .when(is_platform_style_mac, |parent| parent.gap_1())
9820 .child(h_flex().children(ui::render_modifiers(
9821 accept_keystroke.modifiers(),
9822 PlatformStyle::platform(),
9823 Some(if !has_completion {
9824 Color::Muted
9825 } else {
9826 Color::Default
9827 }),
9828 None,
9829 false,
9830 ))),
9831 )
9832 .child(Label::new("Preview").into_any_element())
9833 .opacity(if has_completion { 1.0 } else { 0.4 }),
9834 )
9835 })
9836 .into_any(),
9837 )
9838 }
9839
9840 fn render_edit_prediction_cursor_popover_preview(
9841 &self,
9842 completion: &EditPredictionState,
9843 cursor_point: Point,
9844 style: &EditorStyle,
9845 cx: &mut Context<Editor>,
9846 ) -> Option<Div> {
9847 use text::ToPoint as _;
9848
9849 fn render_relative_row_jump(
9850 prefix: impl Into<String>,
9851 current_row: u32,
9852 target_row: u32,
9853 ) -> Div {
9854 let (row_diff, arrow) = if target_row < current_row {
9855 (current_row - target_row, IconName::ArrowUp)
9856 } else {
9857 (target_row - current_row, IconName::ArrowDown)
9858 };
9859
9860 h_flex()
9861 .child(
9862 Label::new(format!("{}{}", prefix.into(), row_diff))
9863 .color(Color::Muted)
9864 .size(LabelSize::Small),
9865 )
9866 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9867 }
9868
9869 let supports_jump = self
9870 .edit_prediction_provider
9871 .as_ref()
9872 .map(|provider| provider.provider.supports_jump_to_edit())
9873 .unwrap_or(true);
9874
9875 match &completion.completion {
9876 EditPrediction::MoveWithin {
9877 target, snapshot, ..
9878 } => {
9879 if !supports_jump {
9880 return None;
9881 }
9882
9883 Some(
9884 h_flex()
9885 .px_2()
9886 .gap_2()
9887 .flex_1()
9888 .child(
9889 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9890 Icon::new(IconName::ZedPredictDown)
9891 } else {
9892 Icon::new(IconName::ZedPredictUp)
9893 },
9894 )
9895 .child(Label::new("Jump to Edit")),
9896 )
9897 }
9898 EditPrediction::MoveOutside { snapshot, .. } => {
9899 let file_name = snapshot
9900 .file()
9901 .map(|file| file.file_name(cx))
9902 .unwrap_or("untitled");
9903 Some(
9904 h_flex()
9905 .px_2()
9906 .gap_2()
9907 .flex_1()
9908 .child(Icon::new(IconName::ZedPredict))
9909 .child(Label::new(format!("Jump to {file_name}"))),
9910 )
9911 }
9912 EditPrediction::Edit {
9913 edits,
9914 edit_preview,
9915 snapshot,
9916 display_mode: _,
9917 } => {
9918 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9919
9920 let (highlighted_edits, has_more_lines) =
9921 if let Some(edit_preview) = edit_preview.as_ref() {
9922 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9923 .first_line_preview()
9924 } else {
9925 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9926 };
9927
9928 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9929 .with_default_highlights(&style.text, highlighted_edits.highlights);
9930
9931 let preview = h_flex()
9932 .gap_1()
9933 .min_w_16()
9934 .child(styled_text)
9935 .when(has_more_lines, |parent| parent.child("…"));
9936
9937 let left = if supports_jump && first_edit_row != cursor_point.row {
9938 render_relative_row_jump("", cursor_point.row, first_edit_row)
9939 .into_any_element()
9940 } else {
9941 let icon_name =
9942 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9943 Icon::new(icon_name).into_any_element()
9944 };
9945
9946 Some(
9947 h_flex()
9948 .h_full()
9949 .flex_1()
9950 .gap_2()
9951 .pr_1()
9952 .overflow_x_hidden()
9953 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9954 .child(left)
9955 .child(preview),
9956 )
9957 }
9958 }
9959 }
9960
9961 pub fn render_context_menu(
9962 &mut self,
9963 max_height_in_lines: u32,
9964 window: &mut Window,
9965 cx: &mut Context<Editor>,
9966 ) -> Option<AnyElement> {
9967 let menu = self.context_menu.borrow();
9968 let menu = menu.as_ref()?;
9969 if !menu.visible() {
9970 return None;
9971 };
9972 self.style
9973 .as_ref()
9974 .map(|style| menu.render(style, max_height_in_lines, window, cx))
9975 }
9976
9977 fn render_context_menu_aside(
9978 &mut self,
9979 max_size: Size<Pixels>,
9980 window: &mut Window,
9981 cx: &mut Context<Editor>,
9982 ) -> Option<AnyElement> {
9983 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9984 if menu.visible() {
9985 menu.render_aside(max_size, window, cx)
9986 } else {
9987 None
9988 }
9989 })
9990 }
9991
9992 fn hide_context_menu(
9993 &mut self,
9994 window: &mut Window,
9995 cx: &mut Context<Self>,
9996 ) -> Option<CodeContextMenu> {
9997 cx.notify();
9998 self.completion_tasks.clear();
9999 let context_menu = self.context_menu.borrow_mut().take();
10000 self.stale_edit_prediction_in_menu.take();
10001 self.update_visible_edit_prediction(window, cx);
10002 if let Some(CodeContextMenu::Completions(_)) = &context_menu
10003 && let Some(completion_provider) = &self.completion_provider
10004 {
10005 completion_provider.selection_changed(None, window, cx);
10006 }
10007 context_menu
10008 }
10009
10010 fn show_snippet_choices(
10011 &mut self,
10012 choices: &Vec<String>,
10013 selection: Range<Anchor>,
10014 cx: &mut Context<Self>,
10015 ) {
10016 let Some((_, buffer, _)) = self
10017 .buffer()
10018 .read(cx)
10019 .excerpt_containing(selection.start, cx)
10020 else {
10021 return;
10022 };
10023 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
10024 else {
10025 return;
10026 };
10027 if buffer != end_buffer {
10028 log::error!("expected anchor range to have matching buffer IDs");
10029 return;
10030 }
10031
10032 let id = post_inc(&mut self.next_completion_id);
10033 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
10034 let mut context_menu = self.context_menu.borrow_mut();
10035 let old_menu = context_menu.take();
10036 *context_menu = Some(CodeContextMenu::Completions(
10037 CompletionsMenu::new_snippet_choices(
10038 id,
10039 true,
10040 choices,
10041 selection,
10042 buffer,
10043 old_menu.map(|menu| menu.primary_scroll_handle()),
10044 snippet_sort_order,
10045 ),
10046 ));
10047 }
10048
10049 pub fn insert_snippet(
10050 &mut self,
10051 insertion_ranges: &[Range<MultiBufferOffset>],
10052 snippet: Snippet,
10053 window: &mut Window,
10054 cx: &mut Context<Self>,
10055 ) -> Result<()> {
10056 struct Tabstop<T> {
10057 is_end_tabstop: bool,
10058 ranges: Vec<Range<T>>,
10059 choices: Option<Vec<String>>,
10060 }
10061
10062 let tabstops = self.buffer.update(cx, |buffer, cx| {
10063 let snippet_text: Arc<str> = snippet.text.clone().into();
10064 let edits = insertion_ranges
10065 .iter()
10066 .cloned()
10067 .map(|range| (range, snippet_text.clone()));
10068 let autoindent_mode = AutoindentMode::Block {
10069 original_indent_columns: Vec::new(),
10070 };
10071 buffer.edit(edits, Some(autoindent_mode), cx);
10072
10073 let snapshot = &*buffer.read(cx);
10074 let snippet = &snippet;
10075 snippet
10076 .tabstops
10077 .iter()
10078 .map(|tabstop| {
10079 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
10080 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
10081 });
10082 let mut tabstop_ranges = tabstop
10083 .ranges
10084 .iter()
10085 .flat_map(|tabstop_range| {
10086 let mut delta = 0_isize;
10087 insertion_ranges.iter().map(move |insertion_range| {
10088 let insertion_start = insertion_range.start + delta;
10089 delta += snippet.text.len() as isize
10090 - (insertion_range.end - insertion_range.start) as isize;
10091
10092 let start =
10093 (insertion_start + tabstop_range.start).min(snapshot.len());
10094 let end = (insertion_start + tabstop_range.end).min(snapshot.len());
10095 snapshot.anchor_before(start)..snapshot.anchor_after(end)
10096 })
10097 })
10098 .collect::<Vec<_>>();
10099 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
10100
10101 Tabstop {
10102 is_end_tabstop,
10103 ranges: tabstop_ranges,
10104 choices: tabstop.choices.clone(),
10105 }
10106 })
10107 .collect::<Vec<_>>()
10108 });
10109 if let Some(tabstop) = tabstops.first() {
10110 self.change_selections(Default::default(), window, cx, |s| {
10111 // Reverse order so that the first range is the newest created selection.
10112 // Completions will use it and autoscroll will prioritize it.
10113 s.select_ranges(tabstop.ranges.iter().rev().cloned());
10114 });
10115
10116 if let Some(choices) = &tabstop.choices
10117 && let Some(selection) = tabstop.ranges.first()
10118 {
10119 self.show_snippet_choices(choices, selection.clone(), cx)
10120 }
10121
10122 // If we're already at the last tabstop and it's at the end of the snippet,
10123 // we're done, we don't need to keep the state around.
10124 if !tabstop.is_end_tabstop {
10125 let choices = tabstops
10126 .iter()
10127 .map(|tabstop| tabstop.choices.clone())
10128 .collect();
10129
10130 let ranges = tabstops
10131 .into_iter()
10132 .map(|tabstop| tabstop.ranges)
10133 .collect::<Vec<_>>();
10134
10135 self.snippet_stack.push(SnippetState {
10136 active_index: 0,
10137 ranges,
10138 choices,
10139 });
10140 }
10141
10142 // Check whether the just-entered snippet ends with an auto-closable bracket.
10143 if self.autoclose_regions.is_empty() {
10144 let snapshot = self.buffer.read(cx).snapshot(cx);
10145 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
10146 let selection_head = selection.head();
10147 let Some(scope) = snapshot.language_scope_at(selection_head) else {
10148 continue;
10149 };
10150
10151 let mut bracket_pair = None;
10152 let max_lookup_length = scope
10153 .brackets()
10154 .map(|(pair, _)| {
10155 pair.start
10156 .as_str()
10157 .chars()
10158 .count()
10159 .max(pair.end.as_str().chars().count())
10160 })
10161 .max();
10162 if let Some(max_lookup_length) = max_lookup_length {
10163 let next_text = snapshot
10164 .chars_at(selection_head)
10165 .take(max_lookup_length)
10166 .collect::<String>();
10167 let prev_text = snapshot
10168 .reversed_chars_at(selection_head)
10169 .take(max_lookup_length)
10170 .collect::<String>();
10171
10172 for (pair, enabled) in scope.brackets() {
10173 if enabled
10174 && pair.close
10175 && prev_text.starts_with(pair.start.as_str())
10176 && next_text.starts_with(pair.end.as_str())
10177 {
10178 bracket_pair = Some(pair.clone());
10179 break;
10180 }
10181 }
10182 }
10183
10184 if let Some(pair) = bracket_pair {
10185 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
10186 let autoclose_enabled =
10187 self.use_autoclose && snapshot_settings.use_autoclose;
10188 if autoclose_enabled {
10189 let start = snapshot.anchor_after(selection_head);
10190 let end = snapshot.anchor_after(selection_head);
10191 self.autoclose_regions.push(AutocloseRegion {
10192 selection_id: selection.id,
10193 range: start..end,
10194 pair,
10195 });
10196 }
10197 }
10198 }
10199 }
10200 }
10201 Ok(())
10202 }
10203
10204 pub fn move_to_next_snippet_tabstop(
10205 &mut self,
10206 window: &mut Window,
10207 cx: &mut Context<Self>,
10208 ) -> bool {
10209 self.move_to_snippet_tabstop(Bias::Right, window, cx)
10210 }
10211
10212 pub fn move_to_prev_snippet_tabstop(
10213 &mut self,
10214 window: &mut Window,
10215 cx: &mut Context<Self>,
10216 ) -> bool {
10217 self.move_to_snippet_tabstop(Bias::Left, window, cx)
10218 }
10219
10220 pub fn move_to_snippet_tabstop(
10221 &mut self,
10222 bias: Bias,
10223 window: &mut Window,
10224 cx: &mut Context<Self>,
10225 ) -> bool {
10226 if let Some(mut snippet) = self.snippet_stack.pop() {
10227 match bias {
10228 Bias::Left => {
10229 if snippet.active_index > 0 {
10230 snippet.active_index -= 1;
10231 } else {
10232 self.snippet_stack.push(snippet);
10233 return false;
10234 }
10235 }
10236 Bias::Right => {
10237 if snippet.active_index + 1 < snippet.ranges.len() {
10238 snippet.active_index += 1;
10239 } else {
10240 self.snippet_stack.push(snippet);
10241 return false;
10242 }
10243 }
10244 }
10245 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
10246 self.change_selections(Default::default(), window, cx, |s| {
10247 // Reverse order so that the first range is the newest created selection.
10248 // Completions will use it and autoscroll will prioritize it.
10249 s.select_ranges(current_ranges.iter().rev().cloned())
10250 });
10251
10252 if let Some(choices) = &snippet.choices[snippet.active_index]
10253 && let Some(selection) = current_ranges.first()
10254 {
10255 self.show_snippet_choices(choices, selection.clone(), cx);
10256 }
10257
10258 // If snippet state is not at the last tabstop, push it back on the stack
10259 if snippet.active_index + 1 < snippet.ranges.len() {
10260 self.snippet_stack.push(snippet);
10261 }
10262 return true;
10263 }
10264 }
10265
10266 false
10267 }
10268
10269 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
10270 self.transact(window, cx, |this, window, cx| {
10271 this.select_all(&SelectAll, window, cx);
10272 this.insert("", window, cx);
10273 });
10274 }
10275
10276 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
10277 if self.read_only(cx) {
10278 return;
10279 }
10280 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10281 self.transact(window, cx, |this, window, cx| {
10282 this.select_autoclose_pair(window, cx);
10283
10284 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
10285
10286 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
10287 if !this.linked_edit_ranges.is_empty() {
10288 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
10289 let snapshot = this.buffer.read(cx).snapshot(cx);
10290
10291 for selection in selections.iter() {
10292 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
10293 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10294 if selection_start.buffer_id != selection_end.buffer_id {
10295 continue;
10296 }
10297 if let Some(ranges) =
10298 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10299 {
10300 for (buffer, entries) in ranges {
10301 linked_ranges.entry(buffer).or_default().extend(entries);
10302 }
10303 }
10304 }
10305 }
10306
10307 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10308 for selection in &mut selections {
10309 if selection.is_empty() {
10310 let old_head = selection.head();
10311 let mut new_head =
10312 movement::left(&display_map, old_head.to_display_point(&display_map))
10313 .to_point(&display_map);
10314 if let Some((buffer, line_buffer_range)) = display_map
10315 .buffer_snapshot()
10316 .buffer_line_for_row(MultiBufferRow(old_head.row))
10317 {
10318 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10319 let indent_len = match indent_size.kind {
10320 IndentKind::Space => {
10321 buffer.settings_at(line_buffer_range.start, cx).tab_size
10322 }
10323 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10324 };
10325 if old_head.column <= indent_size.len && old_head.column > 0 {
10326 let indent_len = indent_len.get();
10327 new_head = cmp::min(
10328 new_head,
10329 MultiBufferPoint::new(
10330 old_head.row,
10331 ((old_head.column - 1) / indent_len) * indent_len,
10332 ),
10333 );
10334 }
10335 }
10336
10337 selection.set_head(new_head, SelectionGoal::None);
10338 }
10339 }
10340
10341 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10342 this.insert("", window, cx);
10343 let empty_str: Arc<str> = Arc::from("");
10344 for (buffer, edits) in linked_ranges {
10345 let snapshot = buffer.read(cx).snapshot();
10346 use text::ToPoint as TP;
10347
10348 let edits = edits
10349 .into_iter()
10350 .map(|range| {
10351 let end_point = TP::to_point(&range.end, &snapshot);
10352 let mut start_point = TP::to_point(&range.start, &snapshot);
10353
10354 if end_point == start_point {
10355 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10356 .saturating_sub(1);
10357 start_point =
10358 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10359 };
10360
10361 (start_point..end_point, empty_str.clone())
10362 })
10363 .sorted_by_key(|(range, _)| range.start)
10364 .collect::<Vec<_>>();
10365 buffer.update(cx, |this, cx| {
10366 this.edit(edits, None, cx);
10367 })
10368 }
10369 this.refresh_edit_prediction(true, false, window, cx);
10370 refresh_linked_ranges(this, window, cx);
10371 });
10372 }
10373
10374 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10375 if self.read_only(cx) {
10376 return;
10377 }
10378 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10379 self.transact(window, cx, |this, window, cx| {
10380 this.change_selections(Default::default(), window, cx, |s| {
10381 s.move_with(|map, selection| {
10382 if selection.is_empty() {
10383 let cursor = movement::right(map, selection.head());
10384 selection.end = cursor;
10385 selection.reversed = true;
10386 selection.goal = SelectionGoal::None;
10387 }
10388 })
10389 });
10390 this.insert("", window, cx);
10391 this.refresh_edit_prediction(true, false, window, cx);
10392 });
10393 }
10394
10395 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10396 if self.mode.is_single_line() {
10397 cx.propagate();
10398 return;
10399 }
10400
10401 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10402 if self.move_to_prev_snippet_tabstop(window, cx) {
10403 return;
10404 }
10405 self.outdent(&Outdent, window, cx);
10406 }
10407
10408 pub fn next_snippet_tabstop(
10409 &mut self,
10410 _: &NextSnippetTabstop,
10411 window: &mut Window,
10412 cx: &mut Context<Self>,
10413 ) {
10414 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10415 cx.propagate();
10416 return;
10417 }
10418
10419 if self.move_to_next_snippet_tabstop(window, cx) {
10420 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10421 return;
10422 }
10423 cx.propagate();
10424 }
10425
10426 pub fn previous_snippet_tabstop(
10427 &mut self,
10428 _: &PreviousSnippetTabstop,
10429 window: &mut Window,
10430 cx: &mut Context<Self>,
10431 ) {
10432 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10433 cx.propagate();
10434 return;
10435 }
10436
10437 if self.move_to_prev_snippet_tabstop(window, cx) {
10438 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10439 return;
10440 }
10441 cx.propagate();
10442 }
10443
10444 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10445 if self.mode.is_single_line() {
10446 cx.propagate();
10447 return;
10448 }
10449
10450 if self.move_to_next_snippet_tabstop(window, cx) {
10451 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10452 return;
10453 }
10454 if self.read_only(cx) {
10455 return;
10456 }
10457 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10458 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10459 let buffer = self.buffer.read(cx);
10460 let snapshot = buffer.snapshot(cx);
10461 let rows_iter = selections.iter().map(|s| s.head().row);
10462 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10463
10464 let has_some_cursor_in_whitespace = selections
10465 .iter()
10466 .filter(|selection| selection.is_empty())
10467 .any(|selection| {
10468 let cursor = selection.head();
10469 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10470 cursor.column < current_indent.len
10471 });
10472
10473 let mut edits = Vec::new();
10474 let mut prev_edited_row = 0;
10475 let mut row_delta = 0;
10476 for selection in &mut selections {
10477 if selection.start.row != prev_edited_row {
10478 row_delta = 0;
10479 }
10480 prev_edited_row = selection.end.row;
10481
10482 // If the selection is non-empty, then increase the indentation of the selected lines.
10483 if !selection.is_empty() {
10484 row_delta =
10485 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10486 continue;
10487 }
10488
10489 let cursor = selection.head();
10490 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10491 if let Some(suggested_indent) =
10492 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10493 {
10494 // Don't do anything if already at suggested indent
10495 // and there is any other cursor which is not
10496 if has_some_cursor_in_whitespace
10497 && cursor.column == current_indent.len
10498 && current_indent.len == suggested_indent.len
10499 {
10500 continue;
10501 }
10502
10503 // Adjust line and move cursor to suggested indent
10504 // if cursor is not at suggested indent
10505 if cursor.column < suggested_indent.len
10506 && cursor.column <= current_indent.len
10507 && current_indent.len <= suggested_indent.len
10508 {
10509 selection.start = Point::new(cursor.row, suggested_indent.len);
10510 selection.end = selection.start;
10511 if row_delta == 0 {
10512 edits.extend(Buffer::edit_for_indent_size_adjustment(
10513 cursor.row,
10514 current_indent,
10515 suggested_indent,
10516 ));
10517 row_delta = suggested_indent.len - current_indent.len;
10518 }
10519 continue;
10520 }
10521
10522 // If current indent is more than suggested indent
10523 // only move cursor to current indent and skip indent
10524 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10525 selection.start = Point::new(cursor.row, current_indent.len);
10526 selection.end = selection.start;
10527 continue;
10528 }
10529 }
10530
10531 // Otherwise, insert a hard or soft tab.
10532 let settings = buffer.language_settings_at(cursor, cx);
10533 let tab_size = if settings.hard_tabs {
10534 IndentSize::tab()
10535 } else {
10536 let tab_size = settings.tab_size.get();
10537 let indent_remainder = snapshot
10538 .text_for_range(Point::new(cursor.row, 0)..cursor)
10539 .flat_map(str::chars)
10540 .fold(row_delta % tab_size, |counter: u32, c| {
10541 if c == '\t' {
10542 0
10543 } else {
10544 (counter + 1) % tab_size
10545 }
10546 });
10547
10548 let chars_to_next_tab_stop = tab_size - indent_remainder;
10549 IndentSize::spaces(chars_to_next_tab_stop)
10550 };
10551 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10552 selection.end = selection.start;
10553 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10554 row_delta += tab_size.len;
10555 }
10556
10557 self.transact(window, cx, |this, window, cx| {
10558 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10559 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10560 this.refresh_edit_prediction(true, false, window, cx);
10561 });
10562 }
10563
10564 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10565 if self.read_only(cx) {
10566 return;
10567 }
10568 if self.mode.is_single_line() {
10569 cx.propagate();
10570 return;
10571 }
10572
10573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10574 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10575 let mut prev_edited_row = 0;
10576 let mut row_delta = 0;
10577 let mut edits = Vec::new();
10578 let buffer = self.buffer.read(cx);
10579 let snapshot = buffer.snapshot(cx);
10580 for selection in &mut selections {
10581 if selection.start.row != prev_edited_row {
10582 row_delta = 0;
10583 }
10584 prev_edited_row = selection.end.row;
10585
10586 row_delta =
10587 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10588 }
10589
10590 self.transact(window, cx, |this, window, cx| {
10591 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10592 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10593 });
10594 }
10595
10596 fn indent_selection(
10597 buffer: &MultiBuffer,
10598 snapshot: &MultiBufferSnapshot,
10599 selection: &mut Selection<Point>,
10600 edits: &mut Vec<(Range<Point>, String)>,
10601 delta_for_start_row: u32,
10602 cx: &App,
10603 ) -> u32 {
10604 let settings = buffer.language_settings_at(selection.start, cx);
10605 let tab_size = settings.tab_size.get();
10606 let indent_kind = if settings.hard_tabs {
10607 IndentKind::Tab
10608 } else {
10609 IndentKind::Space
10610 };
10611 let mut start_row = selection.start.row;
10612 let mut end_row = selection.end.row + 1;
10613
10614 // If a selection ends at the beginning of a line, don't indent
10615 // that last line.
10616 if selection.end.column == 0 && selection.end.row > selection.start.row {
10617 end_row -= 1;
10618 }
10619
10620 // Avoid re-indenting a row that has already been indented by a
10621 // previous selection, but still update this selection's column
10622 // to reflect that indentation.
10623 if delta_for_start_row > 0 {
10624 start_row += 1;
10625 selection.start.column += delta_for_start_row;
10626 if selection.end.row == selection.start.row {
10627 selection.end.column += delta_for_start_row;
10628 }
10629 }
10630
10631 let mut delta_for_end_row = 0;
10632 let has_multiple_rows = start_row + 1 != end_row;
10633 for row in start_row..end_row {
10634 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10635 let indent_delta = match (current_indent.kind, indent_kind) {
10636 (IndentKind::Space, IndentKind::Space) => {
10637 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10638 IndentSize::spaces(columns_to_next_tab_stop)
10639 }
10640 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10641 (_, IndentKind::Tab) => IndentSize::tab(),
10642 };
10643
10644 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10645 0
10646 } else {
10647 selection.start.column
10648 };
10649 let row_start = Point::new(row, start);
10650 edits.push((
10651 row_start..row_start,
10652 indent_delta.chars().collect::<String>(),
10653 ));
10654
10655 // Update this selection's endpoints to reflect the indentation.
10656 if row == selection.start.row {
10657 selection.start.column += indent_delta.len;
10658 }
10659 if row == selection.end.row {
10660 selection.end.column += indent_delta.len;
10661 delta_for_end_row = indent_delta.len;
10662 }
10663 }
10664
10665 if selection.start.row == selection.end.row {
10666 delta_for_start_row + delta_for_end_row
10667 } else {
10668 delta_for_end_row
10669 }
10670 }
10671
10672 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10673 if self.read_only(cx) {
10674 return;
10675 }
10676 if self.mode.is_single_line() {
10677 cx.propagate();
10678 return;
10679 }
10680
10681 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10682 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10683 let selections = self.selections.all::<Point>(&display_map);
10684 let mut deletion_ranges = Vec::new();
10685 let mut last_outdent = None;
10686 {
10687 let buffer = self.buffer.read(cx);
10688 let snapshot = buffer.snapshot(cx);
10689 for selection in &selections {
10690 let settings = buffer.language_settings_at(selection.start, cx);
10691 let tab_size = settings.tab_size.get();
10692 let mut rows = selection.spanned_rows(false, &display_map);
10693
10694 // Avoid re-outdenting a row that has already been outdented by a
10695 // previous selection.
10696 if let Some(last_row) = last_outdent
10697 && last_row == rows.start
10698 {
10699 rows.start = rows.start.next_row();
10700 }
10701 let has_multiple_rows = rows.len() > 1;
10702 for row in rows.iter_rows() {
10703 let indent_size = snapshot.indent_size_for_line(row);
10704 if indent_size.len > 0 {
10705 let deletion_len = match indent_size.kind {
10706 IndentKind::Space => {
10707 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10708 if columns_to_prev_tab_stop == 0 {
10709 tab_size
10710 } else {
10711 columns_to_prev_tab_stop
10712 }
10713 }
10714 IndentKind::Tab => 1,
10715 };
10716 let start = if has_multiple_rows
10717 || deletion_len > selection.start.column
10718 || indent_size.len < selection.start.column
10719 {
10720 0
10721 } else {
10722 selection.start.column - deletion_len
10723 };
10724 deletion_ranges.push(
10725 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10726 );
10727 last_outdent = Some(row);
10728 }
10729 }
10730 }
10731 }
10732
10733 self.transact(window, cx, |this, window, cx| {
10734 this.buffer.update(cx, |buffer, cx| {
10735 let empty_str: Arc<str> = Arc::default();
10736 buffer.edit(
10737 deletion_ranges
10738 .into_iter()
10739 .map(|range| (range, empty_str.clone())),
10740 None,
10741 cx,
10742 );
10743 });
10744 let selections = this
10745 .selections
10746 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10747 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10748 });
10749 }
10750
10751 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10752 if self.read_only(cx) {
10753 return;
10754 }
10755 if self.mode.is_single_line() {
10756 cx.propagate();
10757 return;
10758 }
10759
10760 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10761 let selections = self
10762 .selections
10763 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
10764 .into_iter()
10765 .map(|s| s.range());
10766
10767 self.transact(window, cx, |this, window, cx| {
10768 this.buffer.update(cx, |buffer, cx| {
10769 buffer.autoindent_ranges(selections, cx);
10770 });
10771 let selections = this
10772 .selections
10773 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
10774 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10775 });
10776 }
10777
10778 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10779 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10780 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10781 let selections = self.selections.all::<Point>(&display_map);
10782
10783 let mut new_cursors = Vec::new();
10784 let mut edit_ranges = Vec::new();
10785 let mut selections = selections.iter().peekable();
10786 while let Some(selection) = selections.next() {
10787 let mut rows = selection.spanned_rows(false, &display_map);
10788
10789 // Accumulate contiguous regions of rows that we want to delete.
10790 while let Some(next_selection) = selections.peek() {
10791 let next_rows = next_selection.spanned_rows(false, &display_map);
10792 if next_rows.start <= rows.end {
10793 rows.end = next_rows.end;
10794 selections.next().unwrap();
10795 } else {
10796 break;
10797 }
10798 }
10799
10800 let buffer = display_map.buffer_snapshot();
10801 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10802 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10803 // If there's a line after the range, delete the \n from the end of the row range
10804 (
10805 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10806 rows.end,
10807 )
10808 } else {
10809 // If there isn't a line after the range, delete the \n from the line before the
10810 // start of the row range
10811 edit_start = edit_start.saturating_sub_usize(1);
10812 (buffer.len(), rows.start.previous_row())
10813 };
10814
10815 let text_layout_details = self.text_layout_details(window);
10816 let x = display_map.x_for_display_point(
10817 selection.head().to_display_point(&display_map),
10818 &text_layout_details,
10819 );
10820 let row = Point::new(target_row.0, 0)
10821 .to_display_point(&display_map)
10822 .row();
10823 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10824
10825 new_cursors.push((
10826 selection.id,
10827 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10828 SelectionGoal::None,
10829 ));
10830 edit_ranges.push(edit_start..edit_end);
10831 }
10832
10833 self.transact(window, cx, |this, window, cx| {
10834 let buffer = this.buffer.update(cx, |buffer, cx| {
10835 let empty_str: Arc<str> = Arc::default();
10836 buffer.edit(
10837 edit_ranges
10838 .into_iter()
10839 .map(|range| (range, empty_str.clone())),
10840 None,
10841 cx,
10842 );
10843 buffer.snapshot(cx)
10844 });
10845 let new_selections = new_cursors
10846 .into_iter()
10847 .map(|(id, cursor, goal)| {
10848 let cursor = cursor.to_point(&buffer);
10849 Selection {
10850 id,
10851 start: cursor,
10852 end: cursor,
10853 reversed: false,
10854 goal,
10855 }
10856 })
10857 .collect();
10858
10859 this.change_selections(Default::default(), window, cx, |s| {
10860 s.select(new_selections);
10861 });
10862 });
10863 }
10864
10865 pub fn join_lines_impl(
10866 &mut self,
10867 insert_whitespace: bool,
10868 window: &mut Window,
10869 cx: &mut Context<Self>,
10870 ) {
10871 if self.read_only(cx) {
10872 return;
10873 }
10874 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10875 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10876 let start = MultiBufferRow(selection.start.row);
10877 // Treat single line selections as if they include the next line. Otherwise this action
10878 // would do nothing for single line selections individual cursors.
10879 let end = if selection.start.row == selection.end.row {
10880 MultiBufferRow(selection.start.row + 1)
10881 } else {
10882 MultiBufferRow(selection.end.row)
10883 };
10884
10885 if let Some(last_row_range) = row_ranges.last_mut()
10886 && start <= last_row_range.end
10887 {
10888 last_row_range.end = end;
10889 continue;
10890 }
10891 row_ranges.push(start..end);
10892 }
10893
10894 let snapshot = self.buffer.read(cx).snapshot(cx);
10895 let mut cursor_positions = Vec::new();
10896 for row_range in &row_ranges {
10897 let anchor = snapshot.anchor_before(Point::new(
10898 row_range.end.previous_row().0,
10899 snapshot.line_len(row_range.end.previous_row()),
10900 ));
10901 cursor_positions.push(anchor..anchor);
10902 }
10903
10904 self.transact(window, cx, |this, window, cx| {
10905 for row_range in row_ranges.into_iter().rev() {
10906 for row in row_range.iter_rows().rev() {
10907 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10908 let next_line_row = row.next_row();
10909 let indent = snapshot.indent_size_for_line(next_line_row);
10910 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10911
10912 let replace =
10913 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10914 " "
10915 } else {
10916 ""
10917 };
10918
10919 this.buffer.update(cx, |buffer, cx| {
10920 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10921 });
10922 }
10923 }
10924
10925 this.change_selections(Default::default(), window, cx, |s| {
10926 s.select_anchor_ranges(cursor_positions)
10927 });
10928 });
10929 }
10930
10931 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10932 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10933 self.join_lines_impl(true, window, cx);
10934 }
10935
10936 pub fn sort_lines_case_sensitive(
10937 &mut self,
10938 _: &SortLinesCaseSensitive,
10939 window: &mut Window,
10940 cx: &mut Context<Self>,
10941 ) {
10942 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10943 }
10944
10945 pub fn sort_lines_by_length(
10946 &mut self,
10947 _: &SortLinesByLength,
10948 window: &mut Window,
10949 cx: &mut Context<Self>,
10950 ) {
10951 self.manipulate_immutable_lines(window, cx, |lines| {
10952 lines.sort_by_key(|&line| line.chars().count())
10953 })
10954 }
10955
10956 pub fn sort_lines_case_insensitive(
10957 &mut self,
10958 _: &SortLinesCaseInsensitive,
10959 window: &mut Window,
10960 cx: &mut Context<Self>,
10961 ) {
10962 self.manipulate_immutable_lines(window, cx, |lines| {
10963 lines.sort_by_key(|line| line.to_lowercase())
10964 })
10965 }
10966
10967 pub fn unique_lines_case_insensitive(
10968 &mut self,
10969 _: &UniqueLinesCaseInsensitive,
10970 window: &mut Window,
10971 cx: &mut Context<Self>,
10972 ) {
10973 self.manipulate_immutable_lines(window, cx, |lines| {
10974 let mut seen = HashSet::default();
10975 lines.retain(|line| seen.insert(line.to_lowercase()));
10976 })
10977 }
10978
10979 pub fn unique_lines_case_sensitive(
10980 &mut self,
10981 _: &UniqueLinesCaseSensitive,
10982 window: &mut Window,
10983 cx: &mut Context<Self>,
10984 ) {
10985 self.manipulate_immutable_lines(window, cx, |lines| {
10986 let mut seen = HashSet::default();
10987 lines.retain(|line| seen.insert(*line));
10988 })
10989 }
10990
10991 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10992 let snapshot = self.buffer.read(cx).snapshot(cx);
10993 for selection in self.selections.disjoint_anchors_arc().iter() {
10994 if snapshot
10995 .language_at(selection.start)
10996 .and_then(|lang| lang.config().wrap_characters.as_ref())
10997 .is_some()
10998 {
10999 return true;
11000 }
11001 }
11002 false
11003 }
11004
11005 fn wrap_selections_in_tag(
11006 &mut self,
11007 _: &WrapSelectionsInTag,
11008 window: &mut Window,
11009 cx: &mut Context<Self>,
11010 ) {
11011 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11012
11013 let snapshot = self.buffer.read(cx).snapshot(cx);
11014
11015 let mut edits = Vec::new();
11016 let mut boundaries = Vec::new();
11017
11018 for selection in self
11019 .selections
11020 .all_adjusted(&self.display_snapshot(cx))
11021 .iter()
11022 {
11023 let Some(wrap_config) = snapshot
11024 .language_at(selection.start)
11025 .and_then(|lang| lang.config().wrap_characters.clone())
11026 else {
11027 continue;
11028 };
11029
11030 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
11031 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
11032
11033 let start_before = snapshot.anchor_before(selection.start);
11034 let end_after = snapshot.anchor_after(selection.end);
11035
11036 edits.push((start_before..start_before, open_tag));
11037 edits.push((end_after..end_after, close_tag));
11038
11039 boundaries.push((
11040 start_before,
11041 end_after,
11042 wrap_config.start_prefix.len(),
11043 wrap_config.end_suffix.len(),
11044 ));
11045 }
11046
11047 if edits.is_empty() {
11048 return;
11049 }
11050
11051 self.transact(window, cx, |this, window, cx| {
11052 let buffer = this.buffer.update(cx, |buffer, cx| {
11053 buffer.edit(edits, None, cx);
11054 buffer.snapshot(cx)
11055 });
11056
11057 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
11058 for (start_before, end_after, start_prefix_len, end_suffix_len) in
11059 boundaries.into_iter()
11060 {
11061 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
11062 let close_offset = end_after
11063 .to_offset(&buffer)
11064 .saturating_sub_usize(end_suffix_len);
11065 new_selections.push(open_offset..open_offset);
11066 new_selections.push(close_offset..close_offset);
11067 }
11068
11069 this.change_selections(Default::default(), window, cx, |s| {
11070 s.select_ranges(new_selections);
11071 });
11072
11073 this.request_autoscroll(Autoscroll::fit(), cx);
11074 });
11075 }
11076
11077 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
11078 let Some(project) = self.project.clone() else {
11079 return;
11080 };
11081 self.reload(project, window, cx)
11082 .detach_and_notify_err(window, cx);
11083 }
11084
11085 pub fn restore_file(
11086 &mut self,
11087 _: &::git::RestoreFile,
11088 window: &mut Window,
11089 cx: &mut Context<Self>,
11090 ) {
11091 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11092 let mut buffer_ids = HashSet::default();
11093 let snapshot = self.buffer().read(cx).snapshot(cx);
11094 for selection in self
11095 .selections
11096 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
11097 {
11098 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
11099 }
11100
11101 let buffer = self.buffer().read(cx);
11102 let ranges = buffer_ids
11103 .into_iter()
11104 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
11105 .collect::<Vec<_>>();
11106
11107 self.restore_hunks_in_ranges(ranges, window, cx);
11108 }
11109
11110 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
11111 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11112 let selections = self
11113 .selections
11114 .all(&self.display_snapshot(cx))
11115 .into_iter()
11116 .map(|s| s.range())
11117 .collect();
11118 self.restore_hunks_in_ranges(selections, window, cx);
11119 }
11120
11121 pub fn restore_hunks_in_ranges(
11122 &mut self,
11123 ranges: Vec<Range<Point>>,
11124 window: &mut Window,
11125 cx: &mut Context<Editor>,
11126 ) {
11127 let mut revert_changes = HashMap::default();
11128 let chunk_by = self
11129 .snapshot(window, cx)
11130 .hunks_for_ranges(ranges)
11131 .into_iter()
11132 .chunk_by(|hunk| hunk.buffer_id);
11133 for (buffer_id, hunks) in &chunk_by {
11134 let hunks = hunks.collect::<Vec<_>>();
11135 for hunk in &hunks {
11136 self.prepare_restore_change(&mut revert_changes, hunk, cx);
11137 }
11138 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
11139 }
11140 drop(chunk_by);
11141 if !revert_changes.is_empty() {
11142 self.transact(window, cx, |editor, window, cx| {
11143 editor.restore(revert_changes, window, cx);
11144 });
11145 }
11146 }
11147
11148 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
11149 if let Some(status) = self
11150 .addons
11151 .iter()
11152 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
11153 {
11154 return Some(status);
11155 }
11156 self.project
11157 .as_ref()?
11158 .read(cx)
11159 .status_for_buffer_id(buffer_id, cx)
11160 }
11161
11162 pub fn open_active_item_in_terminal(
11163 &mut self,
11164 _: &OpenInTerminal,
11165 window: &mut Window,
11166 cx: &mut Context<Self>,
11167 ) {
11168 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
11169 let project_path = buffer.read(cx).project_path(cx)?;
11170 let project = self.project()?.read(cx);
11171 let entry = project.entry_for_path(&project_path, cx)?;
11172 let parent = match &entry.canonical_path {
11173 Some(canonical_path) => canonical_path.to_path_buf(),
11174 None => project.absolute_path(&project_path, cx)?,
11175 }
11176 .parent()?
11177 .to_path_buf();
11178 Some(parent)
11179 }) {
11180 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
11181 }
11182 }
11183
11184 fn set_breakpoint_context_menu(
11185 &mut self,
11186 display_row: DisplayRow,
11187 position: Option<Anchor>,
11188 clicked_point: gpui::Point<Pixels>,
11189 window: &mut Window,
11190 cx: &mut Context<Self>,
11191 ) {
11192 let source = self
11193 .buffer
11194 .read(cx)
11195 .snapshot(cx)
11196 .anchor_before(Point::new(display_row.0, 0u32));
11197
11198 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
11199
11200 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
11201 self,
11202 source,
11203 clicked_point,
11204 context_menu,
11205 window,
11206 cx,
11207 );
11208 }
11209
11210 fn add_edit_breakpoint_block(
11211 &mut self,
11212 anchor: Anchor,
11213 breakpoint: &Breakpoint,
11214 edit_action: BreakpointPromptEditAction,
11215 window: &mut Window,
11216 cx: &mut Context<Self>,
11217 ) {
11218 let weak_editor = cx.weak_entity();
11219 let bp_prompt = cx.new(|cx| {
11220 BreakpointPromptEditor::new(
11221 weak_editor,
11222 anchor,
11223 breakpoint.clone(),
11224 edit_action,
11225 window,
11226 cx,
11227 )
11228 });
11229
11230 let height = bp_prompt.update(cx, |this, cx| {
11231 this.prompt
11232 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
11233 });
11234 let cloned_prompt = bp_prompt.clone();
11235 let blocks = vec![BlockProperties {
11236 style: BlockStyle::Sticky,
11237 placement: BlockPlacement::Above(anchor),
11238 height: Some(height),
11239 render: Arc::new(move |cx| {
11240 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
11241 cloned_prompt.clone().into_any_element()
11242 }),
11243 priority: 0,
11244 }];
11245
11246 let focus_handle = bp_prompt.focus_handle(cx);
11247 window.focus(&focus_handle);
11248
11249 let block_ids = self.insert_blocks(blocks, None, cx);
11250 bp_prompt.update(cx, |prompt, _| {
11251 prompt.add_block_ids(block_ids);
11252 });
11253 }
11254
11255 pub(crate) fn breakpoint_at_row(
11256 &self,
11257 row: u32,
11258 window: &mut Window,
11259 cx: &mut Context<Self>,
11260 ) -> Option<(Anchor, Breakpoint)> {
11261 let snapshot = self.snapshot(window, cx);
11262 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
11263
11264 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11265 }
11266
11267 pub(crate) fn breakpoint_at_anchor(
11268 &self,
11269 breakpoint_position: Anchor,
11270 snapshot: &EditorSnapshot,
11271 cx: &mut Context<Self>,
11272 ) -> Option<(Anchor, Breakpoint)> {
11273 let buffer = self
11274 .buffer
11275 .read(cx)
11276 .buffer_for_anchor(breakpoint_position, cx)?;
11277
11278 let enclosing_excerpt = breakpoint_position.excerpt_id;
11279 let buffer_snapshot = buffer.read(cx).snapshot();
11280
11281 let row = buffer_snapshot
11282 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
11283 .row;
11284
11285 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
11286 let anchor_end = snapshot
11287 .buffer_snapshot()
11288 .anchor_after(Point::new(row, line_len));
11289
11290 self.breakpoint_store
11291 .as_ref()?
11292 .read_with(cx, |breakpoint_store, cx| {
11293 breakpoint_store
11294 .breakpoints(
11295 &buffer,
11296 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
11297 &buffer_snapshot,
11298 cx,
11299 )
11300 .next()
11301 .and_then(|(bp, _)| {
11302 let breakpoint_row = buffer_snapshot
11303 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11304 .row;
11305
11306 if breakpoint_row == row {
11307 snapshot
11308 .buffer_snapshot()
11309 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11310 .map(|position| (position, bp.bp.clone()))
11311 } else {
11312 None
11313 }
11314 })
11315 })
11316 }
11317
11318 pub fn edit_log_breakpoint(
11319 &mut self,
11320 _: &EditLogBreakpoint,
11321 window: &mut Window,
11322 cx: &mut Context<Self>,
11323 ) {
11324 if self.breakpoint_store.is_none() {
11325 return;
11326 }
11327
11328 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11329 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11330 message: None,
11331 state: BreakpointState::Enabled,
11332 condition: None,
11333 hit_condition: None,
11334 });
11335
11336 self.add_edit_breakpoint_block(
11337 anchor,
11338 &breakpoint,
11339 BreakpointPromptEditAction::Log,
11340 window,
11341 cx,
11342 );
11343 }
11344 }
11345
11346 fn breakpoints_at_cursors(
11347 &self,
11348 window: &mut Window,
11349 cx: &mut Context<Self>,
11350 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11351 let snapshot = self.snapshot(window, cx);
11352 let cursors = self
11353 .selections
11354 .disjoint_anchors_arc()
11355 .iter()
11356 .map(|selection| {
11357 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11358
11359 let breakpoint_position = self
11360 .breakpoint_at_row(cursor_position.row, window, cx)
11361 .map(|bp| bp.0)
11362 .unwrap_or_else(|| {
11363 snapshot
11364 .display_snapshot
11365 .buffer_snapshot()
11366 .anchor_after(Point::new(cursor_position.row, 0))
11367 });
11368
11369 let breakpoint = self
11370 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11371 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11372
11373 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11374 })
11375 // 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.
11376 .collect::<HashMap<Anchor, _>>();
11377
11378 cursors.into_iter().collect()
11379 }
11380
11381 pub fn enable_breakpoint(
11382 &mut self,
11383 _: &crate::actions::EnableBreakpoint,
11384 window: &mut Window,
11385 cx: &mut Context<Self>,
11386 ) {
11387 if self.breakpoint_store.is_none() {
11388 return;
11389 }
11390
11391 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11392 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11393 continue;
11394 };
11395 self.edit_breakpoint_at_anchor(
11396 anchor,
11397 breakpoint,
11398 BreakpointEditAction::InvertState,
11399 cx,
11400 );
11401 }
11402 }
11403
11404 pub fn disable_breakpoint(
11405 &mut self,
11406 _: &crate::actions::DisableBreakpoint,
11407 window: &mut Window,
11408 cx: &mut Context<Self>,
11409 ) {
11410 if self.breakpoint_store.is_none() {
11411 return;
11412 }
11413
11414 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11415 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11416 continue;
11417 };
11418 self.edit_breakpoint_at_anchor(
11419 anchor,
11420 breakpoint,
11421 BreakpointEditAction::InvertState,
11422 cx,
11423 );
11424 }
11425 }
11426
11427 pub fn toggle_breakpoint(
11428 &mut self,
11429 _: &crate::actions::ToggleBreakpoint,
11430 window: &mut Window,
11431 cx: &mut Context<Self>,
11432 ) {
11433 if self.breakpoint_store.is_none() {
11434 return;
11435 }
11436
11437 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11438 if let Some(breakpoint) = breakpoint {
11439 self.edit_breakpoint_at_anchor(
11440 anchor,
11441 breakpoint,
11442 BreakpointEditAction::Toggle,
11443 cx,
11444 );
11445 } else {
11446 self.edit_breakpoint_at_anchor(
11447 anchor,
11448 Breakpoint::new_standard(),
11449 BreakpointEditAction::Toggle,
11450 cx,
11451 );
11452 }
11453 }
11454 }
11455
11456 pub fn edit_breakpoint_at_anchor(
11457 &mut self,
11458 breakpoint_position: Anchor,
11459 breakpoint: Breakpoint,
11460 edit_action: BreakpointEditAction,
11461 cx: &mut Context<Self>,
11462 ) {
11463 let Some(breakpoint_store) = &self.breakpoint_store else {
11464 return;
11465 };
11466
11467 let Some(buffer) = self
11468 .buffer
11469 .read(cx)
11470 .buffer_for_anchor(breakpoint_position, cx)
11471 else {
11472 return;
11473 };
11474
11475 breakpoint_store.update(cx, |breakpoint_store, cx| {
11476 breakpoint_store.toggle_breakpoint(
11477 buffer,
11478 BreakpointWithPosition {
11479 position: breakpoint_position.text_anchor,
11480 bp: breakpoint,
11481 },
11482 edit_action,
11483 cx,
11484 );
11485 });
11486
11487 cx.notify();
11488 }
11489
11490 #[cfg(any(test, feature = "test-support"))]
11491 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11492 self.breakpoint_store.clone()
11493 }
11494
11495 pub fn prepare_restore_change(
11496 &self,
11497 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11498 hunk: &MultiBufferDiffHunk,
11499 cx: &mut App,
11500 ) -> Option<()> {
11501 if hunk.is_created_file() {
11502 return None;
11503 }
11504 let buffer = self.buffer.read(cx);
11505 let diff = buffer.diff_for(hunk.buffer_id)?;
11506 let buffer = buffer.buffer(hunk.buffer_id)?;
11507 let buffer = buffer.read(cx);
11508 let original_text = diff
11509 .read(cx)
11510 .base_text()
11511 .as_rope()
11512 .slice(hunk.diff_base_byte_range.start.0..hunk.diff_base_byte_range.end.0);
11513 let buffer_snapshot = buffer.snapshot();
11514 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11515 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11516 probe
11517 .0
11518 .start
11519 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11520 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11521 }) {
11522 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11523 Some(())
11524 } else {
11525 None
11526 }
11527 }
11528
11529 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11530 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11531 }
11532
11533 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11534 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11535 }
11536
11537 pub fn rotate_selections_forward(
11538 &mut self,
11539 _: &RotateSelectionsForward,
11540 window: &mut Window,
11541 cx: &mut Context<Self>,
11542 ) {
11543 self.rotate_selections(window, cx, false)
11544 }
11545
11546 pub fn rotate_selections_backward(
11547 &mut self,
11548 _: &RotateSelectionsBackward,
11549 window: &mut Window,
11550 cx: &mut Context<Self>,
11551 ) {
11552 self.rotate_selections(window, cx, true)
11553 }
11554
11555 fn rotate_selections(&mut self, window: &mut Window, cx: &mut Context<Self>, reverse: bool) {
11556 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11557 let display_snapshot = self.display_snapshot(cx);
11558 let selections = self.selections.all::<MultiBufferOffset>(&display_snapshot);
11559
11560 if selections.len() < 2 {
11561 return;
11562 }
11563
11564 let (edits, new_selections) = {
11565 let buffer = self.buffer.read(cx).read(cx);
11566 let has_selections = selections.iter().any(|s| !s.is_empty());
11567 if has_selections {
11568 let mut selected_texts: Vec<String> = selections
11569 .iter()
11570 .map(|selection| {
11571 buffer
11572 .text_for_range(selection.start..selection.end)
11573 .collect()
11574 })
11575 .collect();
11576
11577 if reverse {
11578 selected_texts.rotate_left(1);
11579 } else {
11580 selected_texts.rotate_right(1);
11581 }
11582
11583 let mut offset_delta: i64 = 0;
11584 let mut new_selections = Vec::new();
11585 let edits: Vec<_> = selections
11586 .iter()
11587 .zip(selected_texts.iter())
11588 .map(|(selection, new_text)| {
11589 let old_len = (selection.end.0 - selection.start.0) as i64;
11590 let new_len = new_text.len() as i64;
11591 let adjusted_start =
11592 MultiBufferOffset((selection.start.0 as i64 + offset_delta) as usize);
11593 let adjusted_end =
11594 MultiBufferOffset((adjusted_start.0 as i64 + new_len) as usize);
11595
11596 new_selections.push(Selection {
11597 id: selection.id,
11598 start: adjusted_start,
11599 end: adjusted_end,
11600 reversed: selection.reversed,
11601 goal: selection.goal,
11602 });
11603
11604 offset_delta += new_len - old_len;
11605 (selection.start..selection.end, new_text.clone())
11606 })
11607 .collect();
11608 (edits, new_selections)
11609 } else {
11610 let mut all_rows: Vec<u32> = selections
11611 .iter()
11612 .map(|selection| buffer.offset_to_point(selection.start).row)
11613 .collect();
11614 all_rows.sort_unstable();
11615 all_rows.dedup();
11616
11617 if all_rows.len() < 2 {
11618 return;
11619 }
11620
11621 let line_ranges: Vec<Range<MultiBufferOffset>> = all_rows
11622 .iter()
11623 .map(|&row| {
11624 let start = Point::new(row, 0);
11625 let end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
11626 buffer.point_to_offset(start)..buffer.point_to_offset(end)
11627 })
11628 .collect();
11629
11630 let mut line_texts: Vec<String> = line_ranges
11631 .iter()
11632 .map(|range| buffer.text_for_range(range.clone()).collect())
11633 .collect();
11634
11635 if reverse {
11636 line_texts.rotate_left(1);
11637 } else {
11638 line_texts.rotate_right(1);
11639 }
11640
11641 let edits = line_ranges
11642 .iter()
11643 .zip(line_texts.iter())
11644 .map(|(range, new_text)| (range.clone(), new_text.clone()))
11645 .collect();
11646
11647 let num_rows = all_rows.len();
11648 let row_to_index: std::collections::HashMap<u32, usize> = all_rows
11649 .iter()
11650 .enumerate()
11651 .map(|(i, &row)| (row, i))
11652 .collect();
11653
11654 // Compute new line start offsets after rotation (handles CRLF)
11655 let newline_len = line_ranges[1].start.0 - line_ranges[0].end.0;
11656 let first_line_start = line_ranges[0].start.0;
11657 let mut new_line_starts: Vec<usize> = vec![first_line_start];
11658 for text in line_texts.iter().take(num_rows - 1) {
11659 let prev_start = *new_line_starts.last().unwrap();
11660 new_line_starts.push(prev_start + text.len() + newline_len);
11661 }
11662
11663 let new_selections = selections
11664 .iter()
11665 .map(|selection| {
11666 let point = buffer.offset_to_point(selection.start);
11667 let old_index = row_to_index[&point.row];
11668 let new_index = if reverse {
11669 (old_index + num_rows - 1) % num_rows
11670 } else {
11671 (old_index + 1) % num_rows
11672 };
11673 let new_offset =
11674 MultiBufferOffset(new_line_starts[new_index] + point.column as usize);
11675 Selection {
11676 id: selection.id,
11677 start: new_offset,
11678 end: new_offset,
11679 reversed: selection.reversed,
11680 goal: selection.goal,
11681 }
11682 })
11683 .collect();
11684
11685 (edits, new_selections)
11686 }
11687 };
11688
11689 self.transact(window, cx, |this, window, cx| {
11690 this.buffer.update(cx, |buffer, cx| {
11691 buffer.edit(edits, None, cx);
11692 });
11693 this.change_selections(Default::default(), window, cx, |s| {
11694 s.select(new_selections);
11695 });
11696 });
11697 }
11698
11699 fn manipulate_lines<M>(
11700 &mut self,
11701 window: &mut Window,
11702 cx: &mut Context<Self>,
11703 mut manipulate: M,
11704 ) where
11705 M: FnMut(&str) -> LineManipulationResult,
11706 {
11707 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11708
11709 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11710 let buffer = self.buffer.read(cx).snapshot(cx);
11711
11712 let mut edits = Vec::new();
11713
11714 let selections = self.selections.all::<Point>(&display_map);
11715 let mut selections = selections.iter().peekable();
11716 let mut contiguous_row_selections = Vec::new();
11717 let mut new_selections = Vec::new();
11718 let mut added_lines = 0;
11719 let mut removed_lines = 0;
11720
11721 while let Some(selection) = selections.next() {
11722 let (start_row, end_row) = consume_contiguous_rows(
11723 &mut contiguous_row_selections,
11724 selection,
11725 &display_map,
11726 &mut selections,
11727 );
11728
11729 let start_point = Point::new(start_row.0, 0);
11730 let end_point = Point::new(
11731 end_row.previous_row().0,
11732 buffer.line_len(end_row.previous_row()),
11733 );
11734 let text = buffer
11735 .text_for_range(start_point..end_point)
11736 .collect::<String>();
11737
11738 let LineManipulationResult {
11739 new_text,
11740 line_count_before,
11741 line_count_after,
11742 } = manipulate(&text);
11743
11744 edits.push((start_point..end_point, new_text));
11745
11746 // Selections must change based on added and removed line count
11747 let start_row =
11748 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11749 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11750 new_selections.push(Selection {
11751 id: selection.id,
11752 start: start_row,
11753 end: end_row,
11754 goal: SelectionGoal::None,
11755 reversed: selection.reversed,
11756 });
11757
11758 if line_count_after > line_count_before {
11759 added_lines += line_count_after - line_count_before;
11760 } else if line_count_before > line_count_after {
11761 removed_lines += line_count_before - line_count_after;
11762 }
11763 }
11764
11765 self.transact(window, cx, |this, window, cx| {
11766 let buffer = this.buffer.update(cx, |buffer, cx| {
11767 buffer.edit(edits, None, cx);
11768 buffer.snapshot(cx)
11769 });
11770
11771 // Recalculate offsets on newly edited buffer
11772 let new_selections = new_selections
11773 .iter()
11774 .map(|s| {
11775 let start_point = Point::new(s.start.0, 0);
11776 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11777 Selection {
11778 id: s.id,
11779 start: buffer.point_to_offset(start_point),
11780 end: buffer.point_to_offset(end_point),
11781 goal: s.goal,
11782 reversed: s.reversed,
11783 }
11784 })
11785 .collect();
11786
11787 this.change_selections(Default::default(), window, cx, |s| {
11788 s.select(new_selections);
11789 });
11790
11791 this.request_autoscroll(Autoscroll::fit(), cx);
11792 });
11793 }
11794
11795 fn manipulate_immutable_lines<Fn>(
11796 &mut self,
11797 window: &mut Window,
11798 cx: &mut Context<Self>,
11799 mut callback: Fn,
11800 ) where
11801 Fn: FnMut(&mut Vec<&str>),
11802 {
11803 self.manipulate_lines(window, cx, |text| {
11804 let mut lines: Vec<&str> = text.split('\n').collect();
11805 let line_count_before = lines.len();
11806
11807 callback(&mut lines);
11808
11809 LineManipulationResult {
11810 new_text: lines.join("\n"),
11811 line_count_before,
11812 line_count_after: lines.len(),
11813 }
11814 });
11815 }
11816
11817 fn manipulate_mutable_lines<Fn>(
11818 &mut self,
11819 window: &mut Window,
11820 cx: &mut Context<Self>,
11821 mut callback: Fn,
11822 ) where
11823 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11824 {
11825 self.manipulate_lines(window, cx, |text| {
11826 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11827 let line_count_before = lines.len();
11828
11829 callback(&mut lines);
11830
11831 LineManipulationResult {
11832 new_text: lines.join("\n"),
11833 line_count_before,
11834 line_count_after: lines.len(),
11835 }
11836 });
11837 }
11838
11839 pub fn convert_indentation_to_spaces(
11840 &mut self,
11841 _: &ConvertIndentationToSpaces,
11842 window: &mut Window,
11843 cx: &mut Context<Self>,
11844 ) {
11845 let settings = self.buffer.read(cx).language_settings(cx);
11846 let tab_size = settings.tab_size.get() as usize;
11847
11848 self.manipulate_mutable_lines(window, cx, |lines| {
11849 // Allocates a reasonably sized scratch buffer once for the whole loop
11850 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11851 // Avoids recomputing spaces that could be inserted many times
11852 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11853 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11854 .collect();
11855
11856 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11857 let mut chars = line.as_ref().chars();
11858 let mut col = 0;
11859 let mut changed = false;
11860
11861 for ch in chars.by_ref() {
11862 match ch {
11863 ' ' => {
11864 reindented_line.push(' ');
11865 col += 1;
11866 }
11867 '\t' => {
11868 // \t are converted to spaces depending on the current column
11869 let spaces_len = tab_size - (col % tab_size);
11870 reindented_line.extend(&space_cache[spaces_len - 1]);
11871 col += spaces_len;
11872 changed = true;
11873 }
11874 _ => {
11875 // If we dont append before break, the character is consumed
11876 reindented_line.push(ch);
11877 break;
11878 }
11879 }
11880 }
11881
11882 if !changed {
11883 reindented_line.clear();
11884 continue;
11885 }
11886 // Append the rest of the line and replace old reference with new one
11887 reindented_line.extend(chars);
11888 *line = Cow::Owned(reindented_line.clone());
11889 reindented_line.clear();
11890 }
11891 });
11892 }
11893
11894 pub fn convert_indentation_to_tabs(
11895 &mut self,
11896 _: &ConvertIndentationToTabs,
11897 window: &mut Window,
11898 cx: &mut Context<Self>,
11899 ) {
11900 let settings = self.buffer.read(cx).language_settings(cx);
11901 let tab_size = settings.tab_size.get() as usize;
11902
11903 self.manipulate_mutable_lines(window, cx, |lines| {
11904 // Allocates a reasonably sized buffer once for the whole loop
11905 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11906 // Avoids recomputing spaces that could be inserted many times
11907 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11908 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11909 .collect();
11910
11911 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11912 let mut chars = line.chars();
11913 let mut spaces_count = 0;
11914 let mut first_non_indent_char = None;
11915 let mut changed = false;
11916
11917 for ch in chars.by_ref() {
11918 match ch {
11919 ' ' => {
11920 // Keep track of spaces. Append \t when we reach tab_size
11921 spaces_count += 1;
11922 changed = true;
11923 if spaces_count == tab_size {
11924 reindented_line.push('\t');
11925 spaces_count = 0;
11926 }
11927 }
11928 '\t' => {
11929 reindented_line.push('\t');
11930 spaces_count = 0;
11931 }
11932 _ => {
11933 // Dont append it yet, we might have remaining spaces
11934 first_non_indent_char = Some(ch);
11935 break;
11936 }
11937 }
11938 }
11939
11940 if !changed {
11941 reindented_line.clear();
11942 continue;
11943 }
11944 // Remaining spaces that didn't make a full tab stop
11945 if spaces_count > 0 {
11946 reindented_line.extend(&space_cache[spaces_count - 1]);
11947 }
11948 // If we consume an extra character that was not indentation, add it back
11949 if let Some(extra_char) = first_non_indent_char {
11950 reindented_line.push(extra_char);
11951 }
11952 // Append the rest of the line and replace old reference with new one
11953 reindented_line.extend(chars);
11954 *line = Cow::Owned(reindented_line.clone());
11955 reindented_line.clear();
11956 }
11957 });
11958 }
11959
11960 pub fn convert_to_upper_case(
11961 &mut self,
11962 _: &ConvertToUpperCase,
11963 window: &mut Window,
11964 cx: &mut Context<Self>,
11965 ) {
11966 self.manipulate_text(window, cx, |text| text.to_uppercase())
11967 }
11968
11969 pub fn convert_to_lower_case(
11970 &mut self,
11971 _: &ConvertToLowerCase,
11972 window: &mut Window,
11973 cx: &mut Context<Self>,
11974 ) {
11975 self.manipulate_text(window, cx, |text| text.to_lowercase())
11976 }
11977
11978 pub fn convert_to_title_case(
11979 &mut self,
11980 _: &ConvertToTitleCase,
11981 window: &mut Window,
11982 cx: &mut Context<Self>,
11983 ) {
11984 self.manipulate_text(window, cx, |text| {
11985 text.split('\n')
11986 .map(|line| line.to_case(Case::Title))
11987 .join("\n")
11988 })
11989 }
11990
11991 pub fn convert_to_snake_case(
11992 &mut self,
11993 _: &ConvertToSnakeCase,
11994 window: &mut Window,
11995 cx: &mut Context<Self>,
11996 ) {
11997 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11998 }
11999
12000 pub fn convert_to_kebab_case(
12001 &mut self,
12002 _: &ConvertToKebabCase,
12003 window: &mut Window,
12004 cx: &mut Context<Self>,
12005 ) {
12006 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
12007 }
12008
12009 pub fn convert_to_upper_camel_case(
12010 &mut self,
12011 _: &ConvertToUpperCamelCase,
12012 window: &mut Window,
12013 cx: &mut Context<Self>,
12014 ) {
12015 self.manipulate_text(window, cx, |text| {
12016 text.split('\n')
12017 .map(|line| line.to_case(Case::UpperCamel))
12018 .join("\n")
12019 })
12020 }
12021
12022 pub fn convert_to_lower_camel_case(
12023 &mut self,
12024 _: &ConvertToLowerCamelCase,
12025 window: &mut Window,
12026 cx: &mut Context<Self>,
12027 ) {
12028 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
12029 }
12030
12031 pub fn convert_to_opposite_case(
12032 &mut self,
12033 _: &ConvertToOppositeCase,
12034 window: &mut Window,
12035 cx: &mut Context<Self>,
12036 ) {
12037 self.manipulate_text(window, cx, |text| {
12038 text.chars()
12039 .fold(String::with_capacity(text.len()), |mut t, c| {
12040 if c.is_uppercase() {
12041 t.extend(c.to_lowercase());
12042 } else {
12043 t.extend(c.to_uppercase());
12044 }
12045 t
12046 })
12047 })
12048 }
12049
12050 pub fn convert_to_sentence_case(
12051 &mut self,
12052 _: &ConvertToSentenceCase,
12053 window: &mut Window,
12054 cx: &mut Context<Self>,
12055 ) {
12056 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
12057 }
12058
12059 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
12060 self.manipulate_text(window, cx, |text| {
12061 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
12062 if has_upper_case_characters {
12063 text.to_lowercase()
12064 } else {
12065 text.to_uppercase()
12066 }
12067 })
12068 }
12069
12070 pub fn convert_to_rot13(
12071 &mut self,
12072 _: &ConvertToRot13,
12073 window: &mut Window,
12074 cx: &mut Context<Self>,
12075 ) {
12076 self.manipulate_text(window, cx, |text| {
12077 text.chars()
12078 .map(|c| match c {
12079 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
12080 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
12081 _ => c,
12082 })
12083 .collect()
12084 })
12085 }
12086
12087 pub fn convert_to_rot47(
12088 &mut self,
12089 _: &ConvertToRot47,
12090 window: &mut Window,
12091 cx: &mut Context<Self>,
12092 ) {
12093 self.manipulate_text(window, cx, |text| {
12094 text.chars()
12095 .map(|c| {
12096 let code_point = c as u32;
12097 if code_point >= 33 && code_point <= 126 {
12098 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
12099 }
12100 c
12101 })
12102 .collect()
12103 })
12104 }
12105
12106 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
12107 where
12108 Fn: FnMut(&str) -> String,
12109 {
12110 let buffer = self.buffer.read(cx).snapshot(cx);
12111
12112 let mut new_selections = Vec::new();
12113 let mut edits = Vec::new();
12114 let mut selection_adjustment = 0isize;
12115
12116 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
12117 let selection_is_empty = selection.is_empty();
12118
12119 let (start, end) = if selection_is_empty {
12120 let (word_range, _) = buffer.surrounding_word(selection.start, None);
12121 (word_range.start, word_range.end)
12122 } else {
12123 (
12124 buffer.point_to_offset(selection.start),
12125 buffer.point_to_offset(selection.end),
12126 )
12127 };
12128
12129 let text = buffer.text_for_range(start..end).collect::<String>();
12130 let old_length = text.len() as isize;
12131 let text = callback(&text);
12132
12133 new_selections.push(Selection {
12134 start: MultiBufferOffset((start.0 as isize - selection_adjustment) as usize),
12135 end: MultiBufferOffset(
12136 ((start.0 + text.len()) as isize - selection_adjustment) as usize,
12137 ),
12138 goal: SelectionGoal::None,
12139 id: selection.id,
12140 reversed: selection.reversed,
12141 });
12142
12143 selection_adjustment += old_length - text.len() as isize;
12144
12145 edits.push((start..end, text));
12146 }
12147
12148 self.transact(window, cx, |this, window, cx| {
12149 this.buffer.update(cx, |buffer, cx| {
12150 buffer.edit(edits, None, cx);
12151 });
12152
12153 this.change_selections(Default::default(), window, cx, |s| {
12154 s.select(new_selections);
12155 });
12156
12157 this.request_autoscroll(Autoscroll::fit(), cx);
12158 });
12159 }
12160
12161 pub fn move_selection_on_drop(
12162 &mut self,
12163 selection: &Selection<Anchor>,
12164 target: DisplayPoint,
12165 is_cut: bool,
12166 window: &mut Window,
12167 cx: &mut Context<Self>,
12168 ) {
12169 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12170 let buffer = display_map.buffer_snapshot();
12171 let mut edits = Vec::new();
12172 let insert_point = display_map
12173 .clip_point(target, Bias::Left)
12174 .to_point(&display_map);
12175 let text = buffer
12176 .text_for_range(selection.start..selection.end)
12177 .collect::<String>();
12178 if is_cut {
12179 edits.push(((selection.start..selection.end), String::new()));
12180 }
12181 let insert_anchor = buffer.anchor_before(insert_point);
12182 edits.push(((insert_anchor..insert_anchor), text));
12183 let last_edit_start = insert_anchor.bias_left(buffer);
12184 let last_edit_end = insert_anchor.bias_right(buffer);
12185 self.transact(window, cx, |this, window, cx| {
12186 this.buffer.update(cx, |buffer, cx| {
12187 buffer.edit(edits, None, cx);
12188 });
12189 this.change_selections(Default::default(), window, cx, |s| {
12190 s.select_anchor_ranges([last_edit_start..last_edit_end]);
12191 });
12192 });
12193 }
12194
12195 pub fn clear_selection_drag_state(&mut self) {
12196 self.selection_drag_state = SelectionDragState::None;
12197 }
12198
12199 pub fn duplicate(
12200 &mut self,
12201 upwards: bool,
12202 whole_lines: bool,
12203 window: &mut Window,
12204 cx: &mut Context<Self>,
12205 ) {
12206 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12207
12208 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12209 let buffer = display_map.buffer_snapshot();
12210 let selections = self.selections.all::<Point>(&display_map);
12211
12212 let mut edits = Vec::new();
12213 let mut selections_iter = selections.iter().peekable();
12214 while let Some(selection) = selections_iter.next() {
12215 let mut rows = selection.spanned_rows(false, &display_map);
12216 // duplicate line-wise
12217 if whole_lines || selection.start == selection.end {
12218 // Avoid duplicating the same lines twice.
12219 while let Some(next_selection) = selections_iter.peek() {
12220 let next_rows = next_selection.spanned_rows(false, &display_map);
12221 if next_rows.start < rows.end {
12222 rows.end = next_rows.end;
12223 selections_iter.next().unwrap();
12224 } else {
12225 break;
12226 }
12227 }
12228
12229 // Copy the text from the selected row region and splice it either at the start
12230 // or end of the region.
12231 let start = Point::new(rows.start.0, 0);
12232 let end = Point::new(
12233 rows.end.previous_row().0,
12234 buffer.line_len(rows.end.previous_row()),
12235 );
12236
12237 let mut text = buffer.text_for_range(start..end).collect::<String>();
12238
12239 let insert_location = if upwards {
12240 // When duplicating upward, we need to insert before the current line.
12241 // If we're on the last line and it doesn't end with a newline,
12242 // we need to add a newline before the duplicated content.
12243 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
12244 && buffer.max_point().column > 0
12245 && !text.ends_with('\n');
12246
12247 if needs_leading_newline {
12248 text.insert(0, '\n');
12249 end
12250 } else {
12251 text.push('\n');
12252 Point::new(rows.start.0, 0)
12253 }
12254 } else {
12255 text.push('\n');
12256 start
12257 };
12258 edits.push((insert_location..insert_location, text));
12259 } else {
12260 // duplicate character-wise
12261 let start = selection.start;
12262 let end = selection.end;
12263 let text = buffer.text_for_range(start..end).collect::<String>();
12264 edits.push((selection.end..selection.end, text));
12265 }
12266 }
12267
12268 self.transact(window, cx, |this, window, cx| {
12269 this.buffer.update(cx, |buffer, cx| {
12270 buffer.edit(edits, None, cx);
12271 });
12272
12273 // When duplicating upward with whole lines, move the cursor to the duplicated line
12274 if upwards && whole_lines {
12275 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
12276
12277 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12278 let mut new_ranges = Vec::new();
12279 let selections = s.all::<Point>(&display_map);
12280 let mut selections_iter = selections.iter().peekable();
12281
12282 while let Some(first_selection) = selections_iter.next() {
12283 // Group contiguous selections together to find the total row span
12284 let mut group_selections = vec![first_selection];
12285 let mut rows = first_selection.spanned_rows(false, &display_map);
12286
12287 while let Some(next_selection) = selections_iter.peek() {
12288 let next_rows = next_selection.spanned_rows(false, &display_map);
12289 if next_rows.start < rows.end {
12290 rows.end = next_rows.end;
12291 group_selections.push(selections_iter.next().unwrap());
12292 } else {
12293 break;
12294 }
12295 }
12296
12297 let row_count = rows.end.0 - rows.start.0;
12298
12299 // Move all selections in this group up by the total number of duplicated rows
12300 for selection in group_selections {
12301 let new_start = Point::new(
12302 selection.start.row.saturating_sub(row_count),
12303 selection.start.column,
12304 );
12305
12306 let new_end = Point::new(
12307 selection.end.row.saturating_sub(row_count),
12308 selection.end.column,
12309 );
12310
12311 new_ranges.push(new_start..new_end);
12312 }
12313 }
12314
12315 s.select_ranges(new_ranges);
12316 });
12317 }
12318
12319 this.request_autoscroll(Autoscroll::fit(), cx);
12320 });
12321 }
12322
12323 pub fn duplicate_line_up(
12324 &mut self,
12325 _: &DuplicateLineUp,
12326 window: &mut Window,
12327 cx: &mut Context<Self>,
12328 ) {
12329 self.duplicate(true, true, window, cx);
12330 }
12331
12332 pub fn duplicate_line_down(
12333 &mut self,
12334 _: &DuplicateLineDown,
12335 window: &mut Window,
12336 cx: &mut Context<Self>,
12337 ) {
12338 self.duplicate(false, true, window, cx);
12339 }
12340
12341 pub fn duplicate_selection(
12342 &mut self,
12343 _: &DuplicateSelection,
12344 window: &mut Window,
12345 cx: &mut Context<Self>,
12346 ) {
12347 self.duplicate(false, false, window, cx);
12348 }
12349
12350 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
12351 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12352 if self.mode.is_single_line() {
12353 cx.propagate();
12354 return;
12355 }
12356
12357 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12358 let buffer = self.buffer.read(cx).snapshot(cx);
12359
12360 let mut edits = Vec::new();
12361 let mut unfold_ranges = Vec::new();
12362 let mut refold_creases = Vec::new();
12363
12364 let selections = self.selections.all::<Point>(&display_map);
12365 let mut selections = selections.iter().peekable();
12366 let mut contiguous_row_selections = Vec::new();
12367 let mut new_selections = Vec::new();
12368
12369 while let Some(selection) = selections.next() {
12370 // Find all the selections that span a contiguous row range
12371 let (start_row, end_row) = consume_contiguous_rows(
12372 &mut contiguous_row_selections,
12373 selection,
12374 &display_map,
12375 &mut selections,
12376 );
12377
12378 // Move the text spanned by the row range to be before the line preceding the row range
12379 if start_row.0 > 0 {
12380 let range_to_move = Point::new(
12381 start_row.previous_row().0,
12382 buffer.line_len(start_row.previous_row()),
12383 )
12384 ..Point::new(
12385 end_row.previous_row().0,
12386 buffer.line_len(end_row.previous_row()),
12387 );
12388 let insertion_point = display_map
12389 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
12390 .0;
12391
12392 // Don't move lines across excerpts
12393 if buffer
12394 .excerpt_containing(insertion_point..range_to_move.end)
12395 .is_some()
12396 {
12397 let text = buffer
12398 .text_for_range(range_to_move.clone())
12399 .flat_map(|s| s.chars())
12400 .skip(1)
12401 .chain(['\n'])
12402 .collect::<String>();
12403
12404 edits.push((
12405 buffer.anchor_after(range_to_move.start)
12406 ..buffer.anchor_before(range_to_move.end),
12407 String::new(),
12408 ));
12409 let insertion_anchor = buffer.anchor_after(insertion_point);
12410 edits.push((insertion_anchor..insertion_anchor, text));
12411
12412 let row_delta = range_to_move.start.row - insertion_point.row + 1;
12413
12414 // Move selections up
12415 new_selections.extend(contiguous_row_selections.drain(..).map(
12416 |mut selection| {
12417 selection.start.row -= row_delta;
12418 selection.end.row -= row_delta;
12419 selection
12420 },
12421 ));
12422
12423 // Move folds up
12424 unfold_ranges.push(range_to_move.clone());
12425 for fold in display_map.folds_in_range(
12426 buffer.anchor_before(range_to_move.start)
12427 ..buffer.anchor_after(range_to_move.end),
12428 ) {
12429 let mut start = fold.range.start.to_point(&buffer);
12430 let mut end = fold.range.end.to_point(&buffer);
12431 start.row -= row_delta;
12432 end.row -= row_delta;
12433 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12434 }
12435 }
12436 }
12437
12438 // If we didn't move line(s), preserve the existing selections
12439 new_selections.append(&mut contiguous_row_selections);
12440 }
12441
12442 self.transact(window, cx, |this, window, cx| {
12443 this.unfold_ranges(&unfold_ranges, true, true, cx);
12444 this.buffer.update(cx, |buffer, cx| {
12445 for (range, text) in edits {
12446 buffer.edit([(range, text)], None, cx);
12447 }
12448 });
12449 this.fold_creases(refold_creases, true, window, cx);
12450 this.change_selections(Default::default(), window, cx, |s| {
12451 s.select(new_selections);
12452 })
12453 });
12454 }
12455
12456 pub fn move_line_down(
12457 &mut self,
12458 _: &MoveLineDown,
12459 window: &mut Window,
12460 cx: &mut Context<Self>,
12461 ) {
12462 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12463 if self.mode.is_single_line() {
12464 cx.propagate();
12465 return;
12466 }
12467
12468 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
12469 let buffer = self.buffer.read(cx).snapshot(cx);
12470
12471 let mut edits = Vec::new();
12472 let mut unfold_ranges = Vec::new();
12473 let mut refold_creases = Vec::new();
12474
12475 let selections = self.selections.all::<Point>(&display_map);
12476 let mut selections = selections.iter().peekable();
12477 let mut contiguous_row_selections = Vec::new();
12478 let mut new_selections = Vec::new();
12479
12480 while let Some(selection) = selections.next() {
12481 // Find all the selections that span a contiguous row range
12482 let (start_row, end_row) = consume_contiguous_rows(
12483 &mut contiguous_row_selections,
12484 selection,
12485 &display_map,
12486 &mut selections,
12487 );
12488
12489 // Move the text spanned by the row range to be after the last line of the row range
12490 if end_row.0 <= buffer.max_point().row {
12491 let range_to_move =
12492 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12493 let insertion_point = display_map
12494 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12495 .0;
12496
12497 // Don't move lines across excerpt boundaries
12498 if buffer
12499 .excerpt_containing(range_to_move.start..insertion_point)
12500 .is_some()
12501 {
12502 let mut text = String::from("\n");
12503 text.extend(buffer.text_for_range(range_to_move.clone()));
12504 text.pop(); // Drop trailing newline
12505 edits.push((
12506 buffer.anchor_after(range_to_move.start)
12507 ..buffer.anchor_before(range_to_move.end),
12508 String::new(),
12509 ));
12510 let insertion_anchor = buffer.anchor_after(insertion_point);
12511 edits.push((insertion_anchor..insertion_anchor, text));
12512
12513 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12514
12515 // Move selections down
12516 new_selections.extend(contiguous_row_selections.drain(..).map(
12517 |mut selection| {
12518 selection.start.row += row_delta;
12519 selection.end.row += row_delta;
12520 selection
12521 },
12522 ));
12523
12524 // Move folds down
12525 unfold_ranges.push(range_to_move.clone());
12526 for fold in display_map.folds_in_range(
12527 buffer.anchor_before(range_to_move.start)
12528 ..buffer.anchor_after(range_to_move.end),
12529 ) {
12530 let mut start = fold.range.start.to_point(&buffer);
12531 let mut end = fold.range.end.to_point(&buffer);
12532 start.row += row_delta;
12533 end.row += row_delta;
12534 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12535 }
12536 }
12537 }
12538
12539 // If we didn't move line(s), preserve the existing selections
12540 new_selections.append(&mut contiguous_row_selections);
12541 }
12542
12543 self.transact(window, cx, |this, window, cx| {
12544 this.unfold_ranges(&unfold_ranges, true, true, cx);
12545 this.buffer.update(cx, |buffer, cx| {
12546 for (range, text) in edits {
12547 buffer.edit([(range, text)], None, cx);
12548 }
12549 });
12550 this.fold_creases(refold_creases, true, window, cx);
12551 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12552 });
12553 }
12554
12555 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12556 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12557 let text_layout_details = &self.text_layout_details(window);
12558 self.transact(window, cx, |this, window, cx| {
12559 let edits = this.change_selections(Default::default(), window, cx, |s| {
12560 let mut edits: Vec<(Range<MultiBufferOffset>, String)> = Default::default();
12561 s.move_with(|display_map, selection| {
12562 if !selection.is_empty() {
12563 return;
12564 }
12565
12566 let mut head = selection.head();
12567 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12568 if head.column() == display_map.line_len(head.row()) {
12569 transpose_offset = display_map
12570 .buffer_snapshot()
12571 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12572 }
12573
12574 if transpose_offset == MultiBufferOffset(0) {
12575 return;
12576 }
12577
12578 *head.column_mut() += 1;
12579 head = display_map.clip_point(head, Bias::Right);
12580 let goal = SelectionGoal::HorizontalPosition(
12581 display_map
12582 .x_for_display_point(head, text_layout_details)
12583 .into(),
12584 );
12585 selection.collapse_to(head, goal);
12586
12587 let transpose_start = display_map
12588 .buffer_snapshot()
12589 .clip_offset(transpose_offset.saturating_sub_usize(1), Bias::Left);
12590 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12591 let transpose_end = display_map
12592 .buffer_snapshot()
12593 .clip_offset(transpose_offset + 1usize, Bias::Right);
12594 if let Some(ch) = display_map
12595 .buffer_snapshot()
12596 .chars_at(transpose_start)
12597 .next()
12598 {
12599 edits.push((transpose_start..transpose_offset, String::new()));
12600 edits.push((transpose_end..transpose_end, ch.to_string()));
12601 }
12602 }
12603 });
12604 edits
12605 });
12606 this.buffer
12607 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12608 let selections = this
12609 .selections
12610 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
12611 this.change_selections(Default::default(), window, cx, |s| {
12612 s.select(selections);
12613 });
12614 });
12615 }
12616
12617 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12618 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12619 if self.mode.is_single_line() {
12620 cx.propagate();
12621 return;
12622 }
12623
12624 self.rewrap_impl(RewrapOptions::default(), cx)
12625 }
12626
12627 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12628 let buffer = self.buffer.read(cx).snapshot(cx);
12629 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12630
12631 #[derive(Clone, Debug, PartialEq)]
12632 enum CommentFormat {
12633 /// single line comment, with prefix for line
12634 Line(String),
12635 /// single line within a block comment, with prefix for line
12636 BlockLine(String),
12637 /// a single line of a block comment that includes the initial delimiter
12638 BlockCommentWithStart(BlockCommentConfig),
12639 /// a single line of a block comment that includes the ending delimiter
12640 BlockCommentWithEnd(BlockCommentConfig),
12641 }
12642
12643 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12644 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12645 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12646 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12647 .peekable();
12648
12649 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12650 row
12651 } else {
12652 return Vec::new();
12653 };
12654
12655 let language_settings = buffer.language_settings_at(selection.head(), cx);
12656 let language_scope = buffer.language_scope_at(selection.head());
12657
12658 let indent_and_prefix_for_row =
12659 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12660 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12661 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12662 &language_scope
12663 {
12664 let indent_end = Point::new(row, indent.len);
12665 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12666 let line_text_after_indent = buffer
12667 .text_for_range(indent_end..line_end)
12668 .collect::<String>();
12669
12670 let is_within_comment_override = buffer
12671 .language_scope_at(indent_end)
12672 .is_some_and(|scope| scope.override_name() == Some("comment"));
12673 let comment_delimiters = if is_within_comment_override {
12674 // we are within a comment syntax node, but we don't
12675 // yet know what kind of comment: block, doc or line
12676 match (
12677 language_scope.documentation_comment(),
12678 language_scope.block_comment(),
12679 ) {
12680 (Some(config), _) | (_, Some(config))
12681 if buffer.contains_str_at(indent_end, &config.start) =>
12682 {
12683 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12684 }
12685 (Some(config), _) | (_, Some(config))
12686 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12687 {
12688 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12689 }
12690 (Some(config), _) | (_, Some(config))
12691 if buffer.contains_str_at(indent_end, &config.prefix) =>
12692 {
12693 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12694 }
12695 (_, _) => language_scope
12696 .line_comment_prefixes()
12697 .iter()
12698 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12699 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12700 }
12701 } else {
12702 // we not in an overridden comment node, but we may
12703 // be within a non-overridden line comment node
12704 language_scope
12705 .line_comment_prefixes()
12706 .iter()
12707 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12708 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12709 };
12710
12711 let rewrap_prefix = language_scope
12712 .rewrap_prefixes()
12713 .iter()
12714 .find_map(|prefix_regex| {
12715 prefix_regex.find(&line_text_after_indent).map(|mat| {
12716 if mat.start() == 0 {
12717 Some(mat.as_str().to_string())
12718 } else {
12719 None
12720 }
12721 })
12722 })
12723 .flatten();
12724 (comment_delimiters, rewrap_prefix)
12725 } else {
12726 (None, None)
12727 };
12728 (indent, comment_prefix, rewrap_prefix)
12729 };
12730
12731 let mut ranges = Vec::new();
12732 let from_empty_selection = selection.is_empty();
12733
12734 let mut current_range_start = first_row;
12735 let mut prev_row = first_row;
12736 let (
12737 mut current_range_indent,
12738 mut current_range_comment_delimiters,
12739 mut current_range_rewrap_prefix,
12740 ) = indent_and_prefix_for_row(first_row);
12741
12742 for row in non_blank_rows_iter.skip(1) {
12743 let has_paragraph_break = row > prev_row + 1;
12744
12745 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12746 indent_and_prefix_for_row(row);
12747
12748 let has_indent_change = row_indent != current_range_indent;
12749 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12750
12751 let has_boundary_change = has_comment_change
12752 || row_rewrap_prefix.is_some()
12753 || (has_indent_change && current_range_comment_delimiters.is_some());
12754
12755 if has_paragraph_break || has_boundary_change {
12756 ranges.push((
12757 language_settings.clone(),
12758 Point::new(current_range_start, 0)
12759 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12760 current_range_indent,
12761 current_range_comment_delimiters.clone(),
12762 current_range_rewrap_prefix.clone(),
12763 from_empty_selection,
12764 ));
12765 current_range_start = row;
12766 current_range_indent = row_indent;
12767 current_range_comment_delimiters = row_comment_delimiters;
12768 current_range_rewrap_prefix = row_rewrap_prefix;
12769 }
12770 prev_row = row;
12771 }
12772
12773 ranges.push((
12774 language_settings.clone(),
12775 Point::new(current_range_start, 0)
12776 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12777 current_range_indent,
12778 current_range_comment_delimiters,
12779 current_range_rewrap_prefix,
12780 from_empty_selection,
12781 ));
12782
12783 ranges
12784 });
12785
12786 let mut edits = Vec::new();
12787 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12788
12789 for (
12790 language_settings,
12791 wrap_range,
12792 mut indent_size,
12793 comment_prefix,
12794 rewrap_prefix,
12795 from_empty_selection,
12796 ) in wrap_ranges
12797 {
12798 let mut start_row = wrap_range.start.row;
12799 let mut end_row = wrap_range.end.row;
12800
12801 // Skip selections that overlap with a range that has already been rewrapped.
12802 let selection_range = start_row..end_row;
12803 if rewrapped_row_ranges
12804 .iter()
12805 .any(|range| range.overlaps(&selection_range))
12806 {
12807 continue;
12808 }
12809
12810 let tab_size = language_settings.tab_size;
12811
12812 let (line_prefix, inside_comment) = match &comment_prefix {
12813 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12814 (Some(prefix.as_str()), true)
12815 }
12816 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12817 (Some(prefix.as_ref()), true)
12818 }
12819 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12820 start: _,
12821 end: _,
12822 prefix,
12823 tab_size,
12824 })) => {
12825 indent_size.len += tab_size;
12826 (Some(prefix.as_ref()), true)
12827 }
12828 None => (None, false),
12829 };
12830 let indent_prefix = indent_size.chars().collect::<String>();
12831 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12832
12833 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12834 RewrapBehavior::InComments => inside_comment,
12835 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12836 RewrapBehavior::Anywhere => true,
12837 };
12838
12839 let should_rewrap = options.override_language_settings
12840 || allow_rewrap_based_on_language
12841 || self.hard_wrap.is_some();
12842 if !should_rewrap {
12843 continue;
12844 }
12845
12846 if from_empty_selection {
12847 'expand_upwards: while start_row > 0 {
12848 let prev_row = start_row - 1;
12849 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12850 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12851 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12852 {
12853 start_row = prev_row;
12854 } else {
12855 break 'expand_upwards;
12856 }
12857 }
12858
12859 'expand_downwards: while end_row < buffer.max_point().row {
12860 let next_row = end_row + 1;
12861 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12862 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12863 && !buffer.is_line_blank(MultiBufferRow(next_row))
12864 {
12865 end_row = next_row;
12866 } else {
12867 break 'expand_downwards;
12868 }
12869 }
12870 }
12871
12872 let start = Point::new(start_row, 0);
12873 let start_offset = ToOffset::to_offset(&start, &buffer);
12874 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12875 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12876 let mut first_line_delimiter = None;
12877 let mut last_line_delimiter = None;
12878 let Some(lines_without_prefixes) = selection_text
12879 .lines()
12880 .enumerate()
12881 .map(|(ix, line)| {
12882 let line_trimmed = line.trim_start();
12883 if rewrap_prefix.is_some() && ix > 0 {
12884 Ok(line_trimmed)
12885 } else if let Some(
12886 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12887 start,
12888 prefix,
12889 end,
12890 tab_size,
12891 })
12892 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12893 start,
12894 prefix,
12895 end,
12896 tab_size,
12897 }),
12898 ) = &comment_prefix
12899 {
12900 let line_trimmed = line_trimmed
12901 .strip_prefix(start.as_ref())
12902 .map(|s| {
12903 let mut indent_size = indent_size;
12904 indent_size.len -= tab_size;
12905 let indent_prefix: String = indent_size.chars().collect();
12906 first_line_delimiter = Some((indent_prefix, start));
12907 s.trim_start()
12908 })
12909 .unwrap_or(line_trimmed);
12910 let line_trimmed = line_trimmed
12911 .strip_suffix(end.as_ref())
12912 .map(|s| {
12913 last_line_delimiter = Some(end);
12914 s.trim_end()
12915 })
12916 .unwrap_or(line_trimmed);
12917 let line_trimmed = line_trimmed
12918 .strip_prefix(prefix.as_ref())
12919 .unwrap_or(line_trimmed);
12920 Ok(line_trimmed)
12921 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12922 line_trimmed.strip_prefix(prefix).with_context(|| {
12923 format!("line did not start with prefix {prefix:?}: {line:?}")
12924 })
12925 } else {
12926 line_trimmed
12927 .strip_prefix(&line_prefix.trim_start())
12928 .with_context(|| {
12929 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12930 })
12931 }
12932 })
12933 .collect::<Result<Vec<_>, _>>()
12934 .log_err()
12935 else {
12936 continue;
12937 };
12938
12939 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12940 buffer
12941 .language_settings_at(Point::new(start_row, 0), cx)
12942 .preferred_line_length as usize
12943 });
12944
12945 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12946 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12947 } else {
12948 line_prefix.clone()
12949 };
12950
12951 let wrapped_text = {
12952 let mut wrapped_text = wrap_with_prefix(
12953 line_prefix,
12954 subsequent_lines_prefix,
12955 lines_without_prefixes.join("\n"),
12956 wrap_column,
12957 tab_size,
12958 options.preserve_existing_whitespace,
12959 );
12960
12961 if let Some((indent, delimiter)) = first_line_delimiter {
12962 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12963 }
12964 if let Some(last_line) = last_line_delimiter {
12965 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12966 }
12967
12968 wrapped_text
12969 };
12970
12971 // TODO: should always use char-based diff while still supporting cursor behavior that
12972 // matches vim.
12973 let mut diff_options = DiffOptions::default();
12974 if options.override_language_settings {
12975 diff_options.max_word_diff_len = 0;
12976 diff_options.max_word_diff_line_count = 0;
12977 } else {
12978 diff_options.max_word_diff_len = usize::MAX;
12979 diff_options.max_word_diff_line_count = usize::MAX;
12980 }
12981
12982 for (old_range, new_text) in
12983 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12984 {
12985 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12986 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12987 edits.push((edit_start..edit_end, new_text));
12988 }
12989
12990 rewrapped_row_ranges.push(start_row..=end_row);
12991 }
12992
12993 self.buffer
12994 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12995 }
12996
12997 pub fn cut_common(
12998 &mut self,
12999 cut_no_selection_line: bool,
13000 window: &mut Window,
13001 cx: &mut Context<Self>,
13002 ) -> ClipboardItem {
13003 let mut text = String::new();
13004 let buffer = self.buffer.read(cx).snapshot(cx);
13005 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13006 let mut clipboard_selections = Vec::with_capacity(selections.len());
13007 {
13008 let max_point = buffer.max_point();
13009 let mut is_first = true;
13010 let mut prev_selection_was_entire_line = false;
13011 for selection in &mut selections {
13012 let is_entire_line =
13013 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
13014 if is_entire_line {
13015 selection.start = Point::new(selection.start.row, 0);
13016 if !selection.is_empty() && selection.end.column == 0 {
13017 selection.end = cmp::min(max_point, selection.end);
13018 } else {
13019 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
13020 }
13021 selection.goal = SelectionGoal::None;
13022 }
13023 if is_first {
13024 is_first = false;
13025 } else if !prev_selection_was_entire_line {
13026 text += "\n";
13027 }
13028 prev_selection_was_entire_line = is_entire_line;
13029 let mut len = 0;
13030 for chunk in buffer.text_for_range(selection.start..selection.end) {
13031 text.push_str(chunk);
13032 len += chunk.len();
13033 }
13034
13035 clipboard_selections.push(ClipboardSelection::for_buffer(
13036 len,
13037 is_entire_line,
13038 selection.range(),
13039 &buffer,
13040 self.project.as_ref(),
13041 cx,
13042 ));
13043 }
13044 }
13045
13046 self.transact(window, cx, |this, window, cx| {
13047 this.change_selections(Default::default(), window, cx, |s| {
13048 s.select(selections);
13049 });
13050 this.insert("", window, cx);
13051 });
13052 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
13053 }
13054
13055 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
13056 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13057 let item = self.cut_common(true, window, cx);
13058 cx.write_to_clipboard(item);
13059 }
13060
13061 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
13062 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13063 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13064 s.move_with(|snapshot, sel| {
13065 if sel.is_empty() {
13066 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
13067 }
13068 if sel.is_empty() {
13069 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13070 }
13071 });
13072 });
13073 let item = self.cut_common(false, window, cx);
13074 cx.set_global(KillRing(item))
13075 }
13076
13077 pub fn kill_ring_yank(
13078 &mut self,
13079 _: &KillRingYank,
13080 window: &mut Window,
13081 cx: &mut Context<Self>,
13082 ) {
13083 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13084 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
13085 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
13086 (kill_ring.text().to_string(), kill_ring.metadata_json())
13087 } else {
13088 return;
13089 }
13090 } else {
13091 return;
13092 };
13093 self.do_paste(&text, metadata, false, window, cx);
13094 }
13095
13096 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
13097 self.do_copy(true, cx);
13098 }
13099
13100 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
13101 self.do_copy(false, cx);
13102 }
13103
13104 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
13105 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
13106 let buffer = self.buffer.read(cx).read(cx);
13107 let mut text = String::new();
13108
13109 let mut clipboard_selections = Vec::with_capacity(selections.len());
13110 {
13111 let max_point = buffer.max_point();
13112 let mut is_first = true;
13113 let mut prev_selection_was_entire_line = false;
13114 for selection in &selections {
13115 let mut start = selection.start;
13116 let mut end = selection.end;
13117 let is_entire_line = selection.is_empty() || self.selections.line_mode();
13118 let mut add_trailing_newline = false;
13119 if is_entire_line {
13120 start = Point::new(start.row, 0);
13121 let next_line_start = Point::new(end.row + 1, 0);
13122 if next_line_start <= max_point {
13123 end = next_line_start;
13124 } else {
13125 // We're on the last line without a trailing newline.
13126 // Copy to the end of the line and add a newline afterwards.
13127 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
13128 add_trailing_newline = true;
13129 }
13130 }
13131
13132 let mut trimmed_selections = Vec::new();
13133 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
13134 let row = MultiBufferRow(start.row);
13135 let first_indent = buffer.indent_size_for_line(row);
13136 if first_indent.len == 0 || start.column > first_indent.len {
13137 trimmed_selections.push(start..end);
13138 } else {
13139 trimmed_selections.push(
13140 Point::new(row.0, first_indent.len)
13141 ..Point::new(row.0, buffer.line_len(row)),
13142 );
13143 for row in start.row + 1..=end.row {
13144 let mut line_len = buffer.line_len(MultiBufferRow(row));
13145 if row == end.row {
13146 line_len = end.column;
13147 }
13148 if line_len == 0 {
13149 trimmed_selections
13150 .push(Point::new(row, 0)..Point::new(row, line_len));
13151 continue;
13152 }
13153 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
13154 if row_indent_size.len >= first_indent.len {
13155 trimmed_selections.push(
13156 Point::new(row, first_indent.len)..Point::new(row, line_len),
13157 );
13158 } else {
13159 trimmed_selections.clear();
13160 trimmed_selections.push(start..end);
13161 break;
13162 }
13163 }
13164 }
13165 } else {
13166 trimmed_selections.push(start..end);
13167 }
13168
13169 for trimmed_range in trimmed_selections {
13170 if is_first {
13171 is_first = false;
13172 } else if !prev_selection_was_entire_line {
13173 text += "\n";
13174 }
13175 prev_selection_was_entire_line = is_entire_line;
13176 let mut len = 0;
13177 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
13178 text.push_str(chunk);
13179 len += chunk.len();
13180 }
13181 if add_trailing_newline {
13182 text.push('\n');
13183 len += 1;
13184 }
13185 clipboard_selections.push(ClipboardSelection::for_buffer(
13186 len,
13187 is_entire_line,
13188 trimmed_range,
13189 &buffer,
13190 self.project.as_ref(),
13191 cx,
13192 ));
13193 }
13194 }
13195 }
13196
13197 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
13198 text,
13199 clipboard_selections,
13200 ));
13201 }
13202
13203 pub fn do_paste(
13204 &mut self,
13205 text: &String,
13206 clipboard_selections: Option<Vec<ClipboardSelection>>,
13207 handle_entire_lines: bool,
13208 window: &mut Window,
13209 cx: &mut Context<Self>,
13210 ) {
13211 if self.read_only(cx) {
13212 return;
13213 }
13214
13215 let clipboard_text = Cow::Borrowed(text.as_str());
13216
13217 self.transact(window, cx, |this, window, cx| {
13218 let had_active_edit_prediction = this.has_active_edit_prediction();
13219 let display_map = this.display_snapshot(cx);
13220 let old_selections = this.selections.all::<MultiBufferOffset>(&display_map);
13221 let cursor_offset = this
13222 .selections
13223 .last::<MultiBufferOffset>(&display_map)
13224 .head();
13225
13226 if let Some(mut clipboard_selections) = clipboard_selections {
13227 let all_selections_were_entire_line =
13228 clipboard_selections.iter().all(|s| s.is_entire_line);
13229 let first_selection_indent_column =
13230 clipboard_selections.first().map(|s| s.first_line_indent);
13231 if clipboard_selections.len() != old_selections.len() {
13232 clipboard_selections.drain(..);
13233 }
13234 let mut auto_indent_on_paste = true;
13235
13236 this.buffer.update(cx, |buffer, cx| {
13237 let snapshot = buffer.read(cx);
13238 auto_indent_on_paste = snapshot
13239 .language_settings_at(cursor_offset, cx)
13240 .auto_indent_on_paste;
13241
13242 let mut start_offset = 0;
13243 let mut edits = Vec::new();
13244 let mut original_indent_columns = Vec::new();
13245 for (ix, selection) in old_selections.iter().enumerate() {
13246 let to_insert;
13247 let entire_line;
13248 let original_indent_column;
13249 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
13250 let end_offset = start_offset + clipboard_selection.len;
13251 to_insert = &clipboard_text[start_offset..end_offset];
13252 entire_line = clipboard_selection.is_entire_line;
13253 start_offset = if entire_line {
13254 end_offset
13255 } else {
13256 end_offset + 1
13257 };
13258 original_indent_column = Some(clipboard_selection.first_line_indent);
13259 } else {
13260 to_insert = &*clipboard_text;
13261 entire_line = all_selections_were_entire_line;
13262 original_indent_column = first_selection_indent_column
13263 }
13264
13265 let (range, to_insert) =
13266 if selection.is_empty() && handle_entire_lines && entire_line {
13267 // If the corresponding selection was empty when this slice of the
13268 // clipboard text was written, then the entire line containing the
13269 // selection was copied. If this selection is also currently empty,
13270 // then paste the line before the current line of the buffer.
13271 let column = selection.start.to_point(&snapshot).column as usize;
13272 let line_start = selection.start - column;
13273 (line_start..line_start, Cow::Borrowed(to_insert))
13274 } else {
13275 let language = snapshot.language_at(selection.head());
13276 let range = selection.range();
13277 if let Some(language) = language
13278 && language.name() == "Markdown".into()
13279 {
13280 edit_for_markdown_paste(
13281 &snapshot,
13282 range,
13283 to_insert,
13284 url::Url::parse(to_insert).ok(),
13285 )
13286 } else {
13287 (range, Cow::Borrowed(to_insert))
13288 }
13289 };
13290
13291 edits.push((range, to_insert));
13292 original_indent_columns.push(original_indent_column);
13293 }
13294 drop(snapshot);
13295
13296 buffer.edit(
13297 edits,
13298 if auto_indent_on_paste {
13299 Some(AutoindentMode::Block {
13300 original_indent_columns,
13301 })
13302 } else {
13303 None
13304 },
13305 cx,
13306 );
13307 });
13308
13309 let selections = this
13310 .selections
13311 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
13312 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
13313 } else {
13314 let url = url::Url::parse(&clipboard_text).ok();
13315
13316 let auto_indent_mode = if !clipboard_text.is_empty() {
13317 Some(AutoindentMode::Block {
13318 original_indent_columns: Vec::new(),
13319 })
13320 } else {
13321 None
13322 };
13323
13324 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
13325 let snapshot = buffer.snapshot(cx);
13326
13327 let anchors = old_selections
13328 .iter()
13329 .map(|s| {
13330 let anchor = snapshot.anchor_after(s.head());
13331 s.map(|_| anchor)
13332 })
13333 .collect::<Vec<_>>();
13334
13335 let mut edits = Vec::new();
13336
13337 for selection in old_selections.iter() {
13338 let language = snapshot.language_at(selection.head());
13339 let range = selection.range();
13340
13341 let (edit_range, edit_text) = if let Some(language) = language
13342 && language.name() == "Markdown".into()
13343 {
13344 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
13345 } else {
13346 (range, clipboard_text.clone())
13347 };
13348
13349 edits.push((edit_range, edit_text));
13350 }
13351
13352 drop(snapshot);
13353 buffer.edit(edits, auto_indent_mode, cx);
13354
13355 anchors
13356 });
13357
13358 this.change_selections(Default::default(), window, cx, |s| {
13359 s.select_anchors(selection_anchors);
13360 });
13361 }
13362
13363 // 🤔 | .. | show_in_menu |
13364 // | .. | true true
13365 // | had_edit_prediction | false true
13366
13367 let trigger_in_words =
13368 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
13369
13370 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
13371 });
13372 }
13373
13374 pub fn diff_clipboard_with_selection(
13375 &mut self,
13376 _: &DiffClipboardWithSelection,
13377 window: &mut Window,
13378 cx: &mut Context<Self>,
13379 ) {
13380 let selections = self
13381 .selections
13382 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
13383
13384 if selections.is_empty() {
13385 log::warn!("There should always be at least one selection in Zed. This is a bug.");
13386 return;
13387 };
13388
13389 let clipboard_text = match cx.read_from_clipboard() {
13390 Some(item) => match item.entries().first() {
13391 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
13392 _ => None,
13393 },
13394 None => None,
13395 };
13396
13397 let Some(clipboard_text) = clipboard_text else {
13398 log::warn!("Clipboard doesn't contain text.");
13399 return;
13400 };
13401
13402 window.dispatch_action(
13403 Box::new(DiffClipboardWithSelectionData {
13404 clipboard_text,
13405 editor: cx.entity(),
13406 }),
13407 cx,
13408 );
13409 }
13410
13411 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
13412 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13413 if let Some(item) = cx.read_from_clipboard() {
13414 let entries = item.entries();
13415
13416 match entries.first() {
13417 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
13418 // of all the pasted entries.
13419 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
13420 .do_paste(
13421 clipboard_string.text(),
13422 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
13423 true,
13424 window,
13425 cx,
13426 ),
13427 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
13428 }
13429 }
13430 }
13431
13432 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
13433 if self.read_only(cx) {
13434 return;
13435 }
13436
13437 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13438
13439 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
13440 if let Some((selections, _)) =
13441 self.selection_history.transaction(transaction_id).cloned()
13442 {
13443 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13444 s.select_anchors(selections.to_vec());
13445 });
13446 } else {
13447 log::error!(
13448 "No entry in selection_history found for undo. \
13449 This may correspond to a bug where undo does not update the selection. \
13450 If this is occurring, please add details to \
13451 https://github.com/zed-industries/zed/issues/22692"
13452 );
13453 }
13454 self.request_autoscroll(Autoscroll::fit(), cx);
13455 self.unmark_text(window, cx);
13456 self.refresh_edit_prediction(true, false, window, cx);
13457 cx.emit(EditorEvent::Edited { transaction_id });
13458 cx.emit(EditorEvent::TransactionUndone { transaction_id });
13459 }
13460 }
13461
13462 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
13463 if self.read_only(cx) {
13464 return;
13465 }
13466
13467 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13468
13469 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
13470 if let Some((_, Some(selections))) =
13471 self.selection_history.transaction(transaction_id).cloned()
13472 {
13473 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
13474 s.select_anchors(selections.to_vec());
13475 });
13476 } else {
13477 log::error!(
13478 "No entry in selection_history found for redo. \
13479 This may correspond to a bug where undo does not update the selection. \
13480 If this is occurring, please add details to \
13481 https://github.com/zed-industries/zed/issues/22692"
13482 );
13483 }
13484 self.request_autoscroll(Autoscroll::fit(), cx);
13485 self.unmark_text(window, cx);
13486 self.refresh_edit_prediction(true, false, window, cx);
13487 cx.emit(EditorEvent::Edited { transaction_id });
13488 }
13489 }
13490
13491 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
13492 self.buffer
13493 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
13494 }
13495
13496 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
13497 self.buffer
13498 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
13499 }
13500
13501 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
13502 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13503 self.change_selections(Default::default(), window, cx, |s| {
13504 s.move_with(|map, selection| {
13505 let cursor = if selection.is_empty() {
13506 movement::left(map, selection.start)
13507 } else {
13508 selection.start
13509 };
13510 selection.collapse_to(cursor, SelectionGoal::None);
13511 });
13512 })
13513 }
13514
13515 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13516 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13517 self.change_selections(Default::default(), window, cx, |s| {
13518 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13519 })
13520 }
13521
13522 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13523 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13524 self.change_selections(Default::default(), window, cx, |s| {
13525 s.move_with(|map, selection| {
13526 let cursor = if selection.is_empty() {
13527 movement::right(map, selection.end)
13528 } else {
13529 selection.end
13530 };
13531 selection.collapse_to(cursor, SelectionGoal::None)
13532 });
13533 })
13534 }
13535
13536 pub fn select_right(&mut self, _: &SelectRight, 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_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13540 });
13541 }
13542
13543 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13544 if self.take_rename(true, window, cx).is_some() {
13545 return;
13546 }
13547
13548 if self.mode.is_single_line() {
13549 cx.propagate();
13550 return;
13551 }
13552
13553 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13554
13555 let text_layout_details = &self.text_layout_details(window);
13556 let selection_count = self.selections.count();
13557 let first_selection = self.selections.first_anchor();
13558
13559 self.change_selections(Default::default(), window, cx, |s| {
13560 s.move_with(|map, selection| {
13561 if !selection.is_empty() {
13562 selection.goal = SelectionGoal::None;
13563 }
13564 let (cursor, goal) = movement::up(
13565 map,
13566 selection.start,
13567 selection.goal,
13568 false,
13569 text_layout_details,
13570 );
13571 selection.collapse_to(cursor, goal);
13572 });
13573 });
13574
13575 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13576 {
13577 cx.propagate();
13578 }
13579 }
13580
13581 pub fn move_up_by_lines(
13582 &mut self,
13583 action: &MoveUpByLines,
13584 window: &mut Window,
13585 cx: &mut Context<Self>,
13586 ) {
13587 if self.take_rename(true, window, cx).is_some() {
13588 return;
13589 }
13590
13591 if self.mode.is_single_line() {
13592 cx.propagate();
13593 return;
13594 }
13595
13596 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13597
13598 let text_layout_details = &self.text_layout_details(window);
13599
13600 self.change_selections(Default::default(), window, cx, |s| {
13601 s.move_with(|map, selection| {
13602 if !selection.is_empty() {
13603 selection.goal = SelectionGoal::None;
13604 }
13605 let (cursor, goal) = movement::up_by_rows(
13606 map,
13607 selection.start,
13608 action.lines,
13609 selection.goal,
13610 false,
13611 text_layout_details,
13612 );
13613 selection.collapse_to(cursor, goal);
13614 });
13615 })
13616 }
13617
13618 pub fn move_down_by_lines(
13619 &mut self,
13620 action: &MoveDownByLines,
13621 window: &mut Window,
13622 cx: &mut Context<Self>,
13623 ) {
13624 if self.take_rename(true, window, cx).is_some() {
13625 return;
13626 }
13627
13628 if self.mode.is_single_line() {
13629 cx.propagate();
13630 return;
13631 }
13632
13633 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13634
13635 let text_layout_details = &self.text_layout_details(window);
13636
13637 self.change_selections(Default::default(), window, cx, |s| {
13638 s.move_with(|map, selection| {
13639 if !selection.is_empty() {
13640 selection.goal = SelectionGoal::None;
13641 }
13642 let (cursor, goal) = movement::down_by_rows(
13643 map,
13644 selection.start,
13645 action.lines,
13646 selection.goal,
13647 false,
13648 text_layout_details,
13649 );
13650 selection.collapse_to(cursor, goal);
13651 });
13652 })
13653 }
13654
13655 pub fn select_down_by_lines(
13656 &mut self,
13657 action: &SelectDownByLines,
13658 window: &mut Window,
13659 cx: &mut Context<Self>,
13660 ) {
13661 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13662 let text_layout_details = &self.text_layout_details(window);
13663 self.change_selections(Default::default(), window, cx, |s| {
13664 s.move_heads_with(|map, head, goal| {
13665 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13666 })
13667 })
13668 }
13669
13670 pub fn select_up_by_lines(
13671 &mut self,
13672 action: &SelectUpByLines,
13673 window: &mut Window,
13674 cx: &mut Context<Self>,
13675 ) {
13676 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13677 let text_layout_details = &self.text_layout_details(window);
13678 self.change_selections(Default::default(), window, cx, |s| {
13679 s.move_heads_with(|map, head, goal| {
13680 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13681 })
13682 })
13683 }
13684
13685 pub fn select_page_up(
13686 &mut self,
13687 _: &SelectPageUp,
13688 window: &mut Window,
13689 cx: &mut Context<Self>,
13690 ) {
13691 let Some(row_count) = self.visible_row_count() else {
13692 return;
13693 };
13694
13695 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13696
13697 let text_layout_details = &self.text_layout_details(window);
13698
13699 self.change_selections(Default::default(), window, cx, |s| {
13700 s.move_heads_with(|map, head, goal| {
13701 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13702 })
13703 })
13704 }
13705
13706 pub fn move_page_up(
13707 &mut self,
13708 action: &MovePageUp,
13709 window: &mut Window,
13710 cx: &mut Context<Self>,
13711 ) {
13712 if self.take_rename(true, window, cx).is_some() {
13713 return;
13714 }
13715
13716 if self
13717 .context_menu
13718 .borrow_mut()
13719 .as_mut()
13720 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13721 .unwrap_or(false)
13722 {
13723 return;
13724 }
13725
13726 if matches!(self.mode, EditorMode::SingleLine) {
13727 cx.propagate();
13728 return;
13729 }
13730
13731 let Some(row_count) = self.visible_row_count() else {
13732 return;
13733 };
13734
13735 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13736
13737 let effects = if action.center_cursor {
13738 SelectionEffects::scroll(Autoscroll::center())
13739 } else {
13740 SelectionEffects::default()
13741 };
13742
13743 let text_layout_details = &self.text_layout_details(window);
13744
13745 self.change_selections(effects, window, cx, |s| {
13746 s.move_with(|map, selection| {
13747 if !selection.is_empty() {
13748 selection.goal = SelectionGoal::None;
13749 }
13750 let (cursor, goal) = movement::up_by_rows(
13751 map,
13752 selection.end,
13753 row_count,
13754 selection.goal,
13755 false,
13756 text_layout_details,
13757 );
13758 selection.collapse_to(cursor, goal);
13759 });
13760 });
13761 }
13762
13763 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13764 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13765 let text_layout_details = &self.text_layout_details(window);
13766 self.change_selections(Default::default(), window, cx, |s| {
13767 s.move_heads_with(|map, head, goal| {
13768 movement::up(map, head, goal, false, text_layout_details)
13769 })
13770 })
13771 }
13772
13773 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13774 self.take_rename(true, window, cx);
13775
13776 if self.mode.is_single_line() {
13777 cx.propagate();
13778 return;
13779 }
13780
13781 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13782
13783 let text_layout_details = &self.text_layout_details(window);
13784 let selection_count = self.selections.count();
13785 let first_selection = self.selections.first_anchor();
13786
13787 self.change_selections(Default::default(), window, cx, |s| {
13788 s.move_with(|map, selection| {
13789 if !selection.is_empty() {
13790 selection.goal = SelectionGoal::None;
13791 }
13792 let (cursor, goal) = movement::down(
13793 map,
13794 selection.end,
13795 selection.goal,
13796 false,
13797 text_layout_details,
13798 );
13799 selection.collapse_to(cursor, goal);
13800 });
13801 });
13802
13803 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13804 {
13805 cx.propagate();
13806 }
13807 }
13808
13809 pub fn select_page_down(
13810 &mut self,
13811 _: &SelectPageDown,
13812 window: &mut Window,
13813 cx: &mut Context<Self>,
13814 ) {
13815 let Some(row_count) = self.visible_row_count() else {
13816 return;
13817 };
13818
13819 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13820
13821 let text_layout_details = &self.text_layout_details(window);
13822
13823 self.change_selections(Default::default(), window, cx, |s| {
13824 s.move_heads_with(|map, head, goal| {
13825 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13826 })
13827 })
13828 }
13829
13830 pub fn move_page_down(
13831 &mut self,
13832 action: &MovePageDown,
13833 window: &mut Window,
13834 cx: &mut Context<Self>,
13835 ) {
13836 if self.take_rename(true, window, cx).is_some() {
13837 return;
13838 }
13839
13840 if self
13841 .context_menu
13842 .borrow_mut()
13843 .as_mut()
13844 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13845 .unwrap_or(false)
13846 {
13847 return;
13848 }
13849
13850 if matches!(self.mode, EditorMode::SingleLine) {
13851 cx.propagate();
13852 return;
13853 }
13854
13855 let Some(row_count) = self.visible_row_count() else {
13856 return;
13857 };
13858
13859 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13860
13861 let effects = if action.center_cursor {
13862 SelectionEffects::scroll(Autoscroll::center())
13863 } else {
13864 SelectionEffects::default()
13865 };
13866
13867 let text_layout_details = &self.text_layout_details(window);
13868 self.change_selections(effects, window, cx, |s| {
13869 s.move_with(|map, selection| {
13870 if !selection.is_empty() {
13871 selection.goal = SelectionGoal::None;
13872 }
13873 let (cursor, goal) = movement::down_by_rows(
13874 map,
13875 selection.end,
13876 row_count,
13877 selection.goal,
13878 false,
13879 text_layout_details,
13880 );
13881 selection.collapse_to(cursor, goal);
13882 });
13883 });
13884 }
13885
13886 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13887 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13888 let text_layout_details = &self.text_layout_details(window);
13889 self.change_selections(Default::default(), window, cx, |s| {
13890 s.move_heads_with(|map, head, goal| {
13891 movement::down(map, head, goal, false, text_layout_details)
13892 })
13893 });
13894 }
13895
13896 pub fn context_menu_first(
13897 &mut self,
13898 _: &ContextMenuFirst,
13899 window: &mut Window,
13900 cx: &mut Context<Self>,
13901 ) {
13902 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13903 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13904 }
13905 }
13906
13907 pub fn context_menu_prev(
13908 &mut self,
13909 _: &ContextMenuPrevious,
13910 window: &mut Window,
13911 cx: &mut Context<Self>,
13912 ) {
13913 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13914 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13915 }
13916 }
13917
13918 pub fn context_menu_next(
13919 &mut self,
13920 _: &ContextMenuNext,
13921 window: &mut Window,
13922 cx: &mut Context<Self>,
13923 ) {
13924 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13925 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13926 }
13927 }
13928
13929 pub fn context_menu_last(
13930 &mut self,
13931 _: &ContextMenuLast,
13932 window: &mut Window,
13933 cx: &mut Context<Self>,
13934 ) {
13935 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13936 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13937 }
13938 }
13939
13940 pub fn signature_help_prev(
13941 &mut self,
13942 _: &SignatureHelpPrevious,
13943 _: &mut Window,
13944 cx: &mut Context<Self>,
13945 ) {
13946 if let Some(popover) = self.signature_help_state.popover_mut() {
13947 if popover.current_signature == 0 {
13948 popover.current_signature = popover.signatures.len() - 1;
13949 } else {
13950 popover.current_signature -= 1;
13951 }
13952 cx.notify();
13953 }
13954 }
13955
13956 pub fn signature_help_next(
13957 &mut self,
13958 _: &SignatureHelpNext,
13959 _: &mut Window,
13960 cx: &mut Context<Self>,
13961 ) {
13962 if let Some(popover) = self.signature_help_state.popover_mut() {
13963 if popover.current_signature + 1 == popover.signatures.len() {
13964 popover.current_signature = 0;
13965 } else {
13966 popover.current_signature += 1;
13967 }
13968 cx.notify();
13969 }
13970 }
13971
13972 pub fn move_to_previous_word_start(
13973 &mut self,
13974 _: &MoveToPreviousWordStart,
13975 window: &mut Window,
13976 cx: &mut Context<Self>,
13977 ) {
13978 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13979 self.change_selections(Default::default(), window, cx, |s| {
13980 s.move_cursors_with(|map, head, _| {
13981 (
13982 movement::previous_word_start(map, head),
13983 SelectionGoal::None,
13984 )
13985 });
13986 })
13987 }
13988
13989 pub fn move_to_previous_subword_start(
13990 &mut self,
13991 _: &MoveToPreviousSubwordStart,
13992 window: &mut Window,
13993 cx: &mut Context<Self>,
13994 ) {
13995 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13996 self.change_selections(Default::default(), window, cx, |s| {
13997 s.move_cursors_with(|map, head, _| {
13998 (
13999 movement::previous_subword_start(map, head),
14000 SelectionGoal::None,
14001 )
14002 });
14003 })
14004 }
14005
14006 pub fn select_to_previous_word_start(
14007 &mut self,
14008 _: &SelectToPreviousWordStart,
14009 window: &mut Window,
14010 cx: &mut Context<Self>,
14011 ) {
14012 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14013 self.change_selections(Default::default(), window, cx, |s| {
14014 s.move_heads_with(|map, head, _| {
14015 (
14016 movement::previous_word_start(map, head),
14017 SelectionGoal::None,
14018 )
14019 });
14020 })
14021 }
14022
14023 pub fn select_to_previous_subword_start(
14024 &mut self,
14025 _: &SelectToPreviousSubwordStart,
14026 window: &mut Window,
14027 cx: &mut Context<Self>,
14028 ) {
14029 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14030 self.change_selections(Default::default(), window, cx, |s| {
14031 s.move_heads_with(|map, head, _| {
14032 (
14033 movement::previous_subword_start(map, head),
14034 SelectionGoal::None,
14035 )
14036 });
14037 })
14038 }
14039
14040 pub fn delete_to_previous_word_start(
14041 &mut self,
14042 action: &DeleteToPreviousWordStart,
14043 window: &mut Window,
14044 cx: &mut Context<Self>,
14045 ) {
14046 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14047 self.transact(window, cx, |this, window, cx| {
14048 this.select_autoclose_pair(window, cx);
14049 this.change_selections(Default::default(), window, cx, |s| {
14050 s.move_with(|map, selection| {
14051 if selection.is_empty() {
14052 let mut cursor = if action.ignore_newlines {
14053 movement::previous_word_start(map, selection.head())
14054 } else {
14055 movement::previous_word_start_or_newline(map, selection.head())
14056 };
14057 cursor = movement::adjust_greedy_deletion(
14058 map,
14059 selection.head(),
14060 cursor,
14061 action.ignore_brackets,
14062 );
14063 selection.set_head(cursor, SelectionGoal::None);
14064 }
14065 });
14066 });
14067 this.insert("", window, cx);
14068 });
14069 }
14070
14071 pub fn delete_to_previous_subword_start(
14072 &mut self,
14073 _: &DeleteToPreviousSubwordStart,
14074 window: &mut Window,
14075 cx: &mut Context<Self>,
14076 ) {
14077 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14078 self.transact(window, cx, |this, window, cx| {
14079 this.select_autoclose_pair(window, cx);
14080 this.change_selections(Default::default(), window, cx, |s| {
14081 s.move_with(|map, selection| {
14082 if selection.is_empty() {
14083 let mut cursor = movement::previous_subword_start(map, selection.head());
14084 cursor =
14085 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14086 selection.set_head(cursor, SelectionGoal::None);
14087 }
14088 });
14089 });
14090 this.insert("", window, cx);
14091 });
14092 }
14093
14094 pub fn move_to_next_word_end(
14095 &mut self,
14096 _: &MoveToNextWordEnd,
14097 window: &mut Window,
14098 cx: &mut Context<Self>,
14099 ) {
14100 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14101 self.change_selections(Default::default(), window, cx, |s| {
14102 s.move_cursors_with(|map, head, _| {
14103 (movement::next_word_end(map, head), SelectionGoal::None)
14104 });
14105 })
14106 }
14107
14108 pub fn move_to_next_subword_end(
14109 &mut self,
14110 _: &MoveToNextSubwordEnd,
14111 window: &mut Window,
14112 cx: &mut Context<Self>,
14113 ) {
14114 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14115 self.change_selections(Default::default(), window, cx, |s| {
14116 s.move_cursors_with(|map, head, _| {
14117 (movement::next_subword_end(map, head), SelectionGoal::None)
14118 });
14119 })
14120 }
14121
14122 pub fn select_to_next_word_end(
14123 &mut self,
14124 _: &SelectToNextWordEnd,
14125 window: &mut Window,
14126 cx: &mut Context<Self>,
14127 ) {
14128 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14129 self.change_selections(Default::default(), window, cx, |s| {
14130 s.move_heads_with(|map, head, _| {
14131 (movement::next_word_end(map, head), SelectionGoal::None)
14132 });
14133 })
14134 }
14135
14136 pub fn select_to_next_subword_end(
14137 &mut self,
14138 _: &SelectToNextSubwordEnd,
14139 window: &mut Window,
14140 cx: &mut Context<Self>,
14141 ) {
14142 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14143 self.change_selections(Default::default(), window, cx, |s| {
14144 s.move_heads_with(|map, head, _| {
14145 (movement::next_subword_end(map, head), SelectionGoal::None)
14146 });
14147 })
14148 }
14149
14150 pub fn delete_to_next_word_end(
14151 &mut self,
14152 action: &DeleteToNextWordEnd,
14153 window: &mut Window,
14154 cx: &mut Context<Self>,
14155 ) {
14156 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14157 self.transact(window, cx, |this, window, cx| {
14158 this.change_selections(Default::default(), window, cx, |s| {
14159 s.move_with(|map, selection| {
14160 if selection.is_empty() {
14161 let mut cursor = if action.ignore_newlines {
14162 movement::next_word_end(map, selection.head())
14163 } else {
14164 movement::next_word_end_or_newline(map, selection.head())
14165 };
14166 cursor = movement::adjust_greedy_deletion(
14167 map,
14168 selection.head(),
14169 cursor,
14170 action.ignore_brackets,
14171 );
14172 selection.set_head(cursor, SelectionGoal::None);
14173 }
14174 });
14175 });
14176 this.insert("", window, cx);
14177 });
14178 }
14179
14180 pub fn delete_to_next_subword_end(
14181 &mut self,
14182 _: &DeleteToNextSubwordEnd,
14183 window: &mut Window,
14184 cx: &mut Context<Self>,
14185 ) {
14186 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14187 self.transact(window, cx, |this, window, cx| {
14188 this.change_selections(Default::default(), window, cx, |s| {
14189 s.move_with(|map, selection| {
14190 if selection.is_empty() {
14191 let mut cursor = movement::next_subword_end(map, selection.head());
14192 cursor =
14193 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
14194 selection.set_head(cursor, SelectionGoal::None);
14195 }
14196 });
14197 });
14198 this.insert("", window, cx);
14199 });
14200 }
14201
14202 pub fn move_to_beginning_of_line(
14203 &mut self,
14204 action: &MoveToBeginningOfLine,
14205 window: &mut Window,
14206 cx: &mut Context<Self>,
14207 ) {
14208 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14209 self.change_selections(Default::default(), window, cx, |s| {
14210 s.move_cursors_with(|map, head, _| {
14211 (
14212 movement::indented_line_beginning(
14213 map,
14214 head,
14215 action.stop_at_soft_wraps,
14216 action.stop_at_indent,
14217 ),
14218 SelectionGoal::None,
14219 )
14220 });
14221 })
14222 }
14223
14224 pub fn select_to_beginning_of_line(
14225 &mut self,
14226 action: &SelectToBeginningOfLine,
14227 window: &mut Window,
14228 cx: &mut Context<Self>,
14229 ) {
14230 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14231 self.change_selections(Default::default(), window, cx, |s| {
14232 s.move_heads_with(|map, head, _| {
14233 (
14234 movement::indented_line_beginning(
14235 map,
14236 head,
14237 action.stop_at_soft_wraps,
14238 action.stop_at_indent,
14239 ),
14240 SelectionGoal::None,
14241 )
14242 });
14243 });
14244 }
14245
14246 pub fn delete_to_beginning_of_line(
14247 &mut self,
14248 action: &DeleteToBeginningOfLine,
14249 window: &mut Window,
14250 cx: &mut Context<Self>,
14251 ) {
14252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14253 self.transact(window, cx, |this, window, cx| {
14254 this.change_selections(Default::default(), window, cx, |s| {
14255 s.move_with(|_, selection| {
14256 selection.reversed = true;
14257 });
14258 });
14259
14260 this.select_to_beginning_of_line(
14261 &SelectToBeginningOfLine {
14262 stop_at_soft_wraps: false,
14263 stop_at_indent: action.stop_at_indent,
14264 },
14265 window,
14266 cx,
14267 );
14268 this.backspace(&Backspace, window, cx);
14269 });
14270 }
14271
14272 pub fn move_to_end_of_line(
14273 &mut self,
14274 action: &MoveToEndOfLine,
14275 window: &mut Window,
14276 cx: &mut Context<Self>,
14277 ) {
14278 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14279 self.change_selections(Default::default(), window, cx, |s| {
14280 s.move_cursors_with(|map, head, _| {
14281 (
14282 movement::line_end(map, head, action.stop_at_soft_wraps),
14283 SelectionGoal::None,
14284 )
14285 });
14286 })
14287 }
14288
14289 pub fn select_to_end_of_line(
14290 &mut self,
14291 action: &SelectToEndOfLine,
14292 window: &mut Window,
14293 cx: &mut Context<Self>,
14294 ) {
14295 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14296 self.change_selections(Default::default(), window, cx, |s| {
14297 s.move_heads_with(|map, head, _| {
14298 (
14299 movement::line_end(map, head, action.stop_at_soft_wraps),
14300 SelectionGoal::None,
14301 )
14302 });
14303 })
14304 }
14305
14306 pub fn delete_to_end_of_line(
14307 &mut self,
14308 _: &DeleteToEndOfLine,
14309 window: &mut Window,
14310 cx: &mut Context<Self>,
14311 ) {
14312 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14313 self.transact(window, cx, |this, window, cx| {
14314 this.select_to_end_of_line(
14315 &SelectToEndOfLine {
14316 stop_at_soft_wraps: false,
14317 },
14318 window,
14319 cx,
14320 );
14321 this.delete(&Delete, window, cx);
14322 });
14323 }
14324
14325 pub fn cut_to_end_of_line(
14326 &mut self,
14327 action: &CutToEndOfLine,
14328 window: &mut Window,
14329 cx: &mut Context<Self>,
14330 ) {
14331 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14332 self.transact(window, cx, |this, window, cx| {
14333 this.select_to_end_of_line(
14334 &SelectToEndOfLine {
14335 stop_at_soft_wraps: false,
14336 },
14337 window,
14338 cx,
14339 );
14340 if !action.stop_at_newlines {
14341 this.change_selections(Default::default(), window, cx, |s| {
14342 s.move_with(|_, sel| {
14343 if sel.is_empty() {
14344 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
14345 }
14346 });
14347 });
14348 }
14349 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14350 let item = this.cut_common(false, window, cx);
14351 cx.write_to_clipboard(item);
14352 });
14353 }
14354
14355 pub fn move_to_start_of_paragraph(
14356 &mut self,
14357 _: &MoveToStartOfParagraph,
14358 window: &mut Window,
14359 cx: &mut Context<Self>,
14360 ) {
14361 if matches!(self.mode, EditorMode::SingleLine) {
14362 cx.propagate();
14363 return;
14364 }
14365 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14366 self.change_selections(Default::default(), window, cx, |s| {
14367 s.move_with(|map, selection| {
14368 selection.collapse_to(
14369 movement::start_of_paragraph(map, selection.head(), 1),
14370 SelectionGoal::None,
14371 )
14372 });
14373 })
14374 }
14375
14376 pub fn move_to_end_of_paragraph(
14377 &mut self,
14378 _: &MoveToEndOfParagraph,
14379 window: &mut Window,
14380 cx: &mut Context<Self>,
14381 ) {
14382 if matches!(self.mode, EditorMode::SingleLine) {
14383 cx.propagate();
14384 return;
14385 }
14386 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14387 self.change_selections(Default::default(), window, cx, |s| {
14388 s.move_with(|map, selection| {
14389 selection.collapse_to(
14390 movement::end_of_paragraph(map, selection.head(), 1),
14391 SelectionGoal::None,
14392 )
14393 });
14394 })
14395 }
14396
14397 pub fn select_to_start_of_paragraph(
14398 &mut self,
14399 _: &SelectToStartOfParagraph,
14400 window: &mut Window,
14401 cx: &mut Context<Self>,
14402 ) {
14403 if matches!(self.mode, EditorMode::SingleLine) {
14404 cx.propagate();
14405 return;
14406 }
14407 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14408 self.change_selections(Default::default(), window, cx, |s| {
14409 s.move_heads_with(|map, head, _| {
14410 (
14411 movement::start_of_paragraph(map, head, 1),
14412 SelectionGoal::None,
14413 )
14414 });
14415 })
14416 }
14417
14418 pub fn select_to_end_of_paragraph(
14419 &mut self,
14420 _: &SelectToEndOfParagraph,
14421 window: &mut Window,
14422 cx: &mut Context<Self>,
14423 ) {
14424 if matches!(self.mode, EditorMode::SingleLine) {
14425 cx.propagate();
14426 return;
14427 }
14428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14429 self.change_selections(Default::default(), window, cx, |s| {
14430 s.move_heads_with(|map, head, _| {
14431 (
14432 movement::end_of_paragraph(map, head, 1),
14433 SelectionGoal::None,
14434 )
14435 });
14436 })
14437 }
14438
14439 pub fn move_to_start_of_excerpt(
14440 &mut self,
14441 _: &MoveToStartOfExcerpt,
14442 window: &mut Window,
14443 cx: &mut Context<Self>,
14444 ) {
14445 if matches!(self.mode, EditorMode::SingleLine) {
14446 cx.propagate();
14447 return;
14448 }
14449 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14450 self.change_selections(Default::default(), window, cx, |s| {
14451 s.move_with(|map, selection| {
14452 selection.collapse_to(
14453 movement::start_of_excerpt(
14454 map,
14455 selection.head(),
14456 workspace::searchable::Direction::Prev,
14457 ),
14458 SelectionGoal::None,
14459 )
14460 });
14461 })
14462 }
14463
14464 pub fn move_to_start_of_next_excerpt(
14465 &mut self,
14466 _: &MoveToStartOfNextExcerpt,
14467 window: &mut Window,
14468 cx: &mut Context<Self>,
14469 ) {
14470 if matches!(self.mode, EditorMode::SingleLine) {
14471 cx.propagate();
14472 return;
14473 }
14474
14475 self.change_selections(Default::default(), window, cx, |s| {
14476 s.move_with(|map, selection| {
14477 selection.collapse_to(
14478 movement::start_of_excerpt(
14479 map,
14480 selection.head(),
14481 workspace::searchable::Direction::Next,
14482 ),
14483 SelectionGoal::None,
14484 )
14485 });
14486 })
14487 }
14488
14489 pub fn move_to_end_of_excerpt(
14490 &mut self,
14491 _: &MoveToEndOfExcerpt,
14492 window: &mut Window,
14493 cx: &mut Context<Self>,
14494 ) {
14495 if matches!(self.mode, EditorMode::SingleLine) {
14496 cx.propagate();
14497 return;
14498 }
14499 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14500 self.change_selections(Default::default(), window, cx, |s| {
14501 s.move_with(|map, selection| {
14502 selection.collapse_to(
14503 movement::end_of_excerpt(
14504 map,
14505 selection.head(),
14506 workspace::searchable::Direction::Next,
14507 ),
14508 SelectionGoal::None,
14509 )
14510 });
14511 })
14512 }
14513
14514 pub fn move_to_end_of_previous_excerpt(
14515 &mut self,
14516 _: &MoveToEndOfPreviousExcerpt,
14517 window: &mut Window,
14518 cx: &mut Context<Self>,
14519 ) {
14520 if matches!(self.mode, EditorMode::SingleLine) {
14521 cx.propagate();
14522 return;
14523 }
14524 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14525 self.change_selections(Default::default(), window, cx, |s| {
14526 s.move_with(|map, selection| {
14527 selection.collapse_to(
14528 movement::end_of_excerpt(
14529 map,
14530 selection.head(),
14531 workspace::searchable::Direction::Prev,
14532 ),
14533 SelectionGoal::None,
14534 )
14535 });
14536 })
14537 }
14538
14539 pub fn select_to_start_of_excerpt(
14540 &mut self,
14541 _: &SelectToStartOfExcerpt,
14542 window: &mut Window,
14543 cx: &mut Context<Self>,
14544 ) {
14545 if matches!(self.mode, EditorMode::SingleLine) {
14546 cx.propagate();
14547 return;
14548 }
14549 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14550 self.change_selections(Default::default(), window, cx, |s| {
14551 s.move_heads_with(|map, head, _| {
14552 (
14553 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14554 SelectionGoal::None,
14555 )
14556 });
14557 })
14558 }
14559
14560 pub fn select_to_start_of_next_excerpt(
14561 &mut self,
14562 _: &SelectToStartOfNextExcerpt,
14563 window: &mut Window,
14564 cx: &mut Context<Self>,
14565 ) {
14566 if matches!(self.mode, EditorMode::SingleLine) {
14567 cx.propagate();
14568 return;
14569 }
14570 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14571 self.change_selections(Default::default(), window, cx, |s| {
14572 s.move_heads_with(|map, head, _| {
14573 (
14574 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14575 SelectionGoal::None,
14576 )
14577 });
14578 })
14579 }
14580
14581 pub fn select_to_end_of_excerpt(
14582 &mut self,
14583 _: &SelectToEndOfExcerpt,
14584 window: &mut Window,
14585 cx: &mut Context<Self>,
14586 ) {
14587 if matches!(self.mode, EditorMode::SingleLine) {
14588 cx.propagate();
14589 return;
14590 }
14591 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14592 self.change_selections(Default::default(), window, cx, |s| {
14593 s.move_heads_with(|map, head, _| {
14594 (
14595 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14596 SelectionGoal::None,
14597 )
14598 });
14599 })
14600 }
14601
14602 pub fn select_to_end_of_previous_excerpt(
14603 &mut self,
14604 _: &SelectToEndOfPreviousExcerpt,
14605 window: &mut Window,
14606 cx: &mut Context<Self>,
14607 ) {
14608 if matches!(self.mode, EditorMode::SingleLine) {
14609 cx.propagate();
14610 return;
14611 }
14612 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14613 self.change_selections(Default::default(), window, cx, |s| {
14614 s.move_heads_with(|map, head, _| {
14615 (
14616 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14617 SelectionGoal::None,
14618 )
14619 });
14620 })
14621 }
14622
14623 pub fn move_to_beginning(
14624 &mut self,
14625 _: &MoveToBeginning,
14626 window: &mut Window,
14627 cx: &mut Context<Self>,
14628 ) {
14629 if matches!(self.mode, EditorMode::SingleLine) {
14630 cx.propagate();
14631 return;
14632 }
14633 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14634 self.change_selections(Default::default(), window, cx, |s| {
14635 s.select_ranges(vec![Anchor::min()..Anchor::min()]);
14636 });
14637 }
14638
14639 pub fn select_to_beginning(
14640 &mut self,
14641 _: &SelectToBeginning,
14642 window: &mut Window,
14643 cx: &mut Context<Self>,
14644 ) {
14645 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14646 selection.set_head(Point::zero(), SelectionGoal::None);
14647 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14648 self.change_selections(Default::default(), window, cx, |s| {
14649 s.select(vec![selection]);
14650 });
14651 }
14652
14653 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14654 if matches!(self.mode, EditorMode::SingleLine) {
14655 cx.propagate();
14656 return;
14657 }
14658 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14659 let cursor = self.buffer.read(cx).read(cx).len();
14660 self.change_selections(Default::default(), window, cx, |s| {
14661 s.select_ranges(vec![cursor..cursor])
14662 });
14663 }
14664
14665 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14666 self.nav_history = nav_history;
14667 }
14668
14669 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14670 self.nav_history.as_ref()
14671 }
14672
14673 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14674 self.push_to_nav_history(
14675 self.selections.newest_anchor().head(),
14676 None,
14677 false,
14678 true,
14679 cx,
14680 );
14681 }
14682
14683 fn push_to_nav_history(
14684 &mut self,
14685 cursor_anchor: Anchor,
14686 new_position: Option<Point>,
14687 is_deactivate: bool,
14688 always: bool,
14689 cx: &mut Context<Self>,
14690 ) {
14691 if let Some(nav_history) = self.nav_history.as_mut() {
14692 let buffer = self.buffer.read(cx).read(cx);
14693 let cursor_position = cursor_anchor.to_point(&buffer);
14694 let scroll_state = self.scroll_manager.anchor();
14695 let scroll_top_row = scroll_state.top_row(&buffer);
14696 drop(buffer);
14697
14698 if let Some(new_position) = new_position {
14699 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14700 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14701 return;
14702 }
14703 }
14704
14705 nav_history.push(
14706 Some(NavigationData {
14707 cursor_anchor,
14708 cursor_position,
14709 scroll_anchor: scroll_state,
14710 scroll_top_row,
14711 }),
14712 cx,
14713 );
14714 cx.emit(EditorEvent::PushedToNavHistory {
14715 anchor: cursor_anchor,
14716 is_deactivate,
14717 })
14718 }
14719 }
14720
14721 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14722 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14723 let buffer = self.buffer.read(cx).snapshot(cx);
14724 let mut selection = self
14725 .selections
14726 .first::<MultiBufferOffset>(&self.display_snapshot(cx));
14727 selection.set_head(buffer.len(), SelectionGoal::None);
14728 self.change_selections(Default::default(), window, cx, |s| {
14729 s.select(vec![selection]);
14730 });
14731 }
14732
14733 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14734 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14735 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14736 s.select_ranges(vec![Anchor::min()..Anchor::max()]);
14737 });
14738 }
14739
14740 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14741 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14742 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14743 let mut selections = self.selections.all::<Point>(&display_map);
14744 let max_point = display_map.buffer_snapshot().max_point();
14745 for selection in &mut selections {
14746 let rows = selection.spanned_rows(true, &display_map);
14747 selection.start = Point::new(rows.start.0, 0);
14748 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14749 selection.reversed = false;
14750 }
14751 self.change_selections(Default::default(), window, cx, |s| {
14752 s.select(selections);
14753 });
14754 }
14755
14756 pub fn split_selection_into_lines(
14757 &mut self,
14758 action: &SplitSelectionIntoLines,
14759 window: &mut Window,
14760 cx: &mut Context<Self>,
14761 ) {
14762 let selections = self
14763 .selections
14764 .all::<Point>(&self.display_snapshot(cx))
14765 .into_iter()
14766 .map(|selection| selection.start..selection.end)
14767 .collect::<Vec<_>>();
14768 self.unfold_ranges(&selections, true, true, cx);
14769
14770 let mut new_selection_ranges = Vec::new();
14771 {
14772 let buffer = self.buffer.read(cx).read(cx);
14773 for selection in selections {
14774 for row in selection.start.row..selection.end.row {
14775 let line_start = Point::new(row, 0);
14776 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14777
14778 if action.keep_selections {
14779 // Keep the selection range for each line
14780 let selection_start = if row == selection.start.row {
14781 selection.start
14782 } else {
14783 line_start
14784 };
14785 new_selection_ranges.push(selection_start..line_end);
14786 } else {
14787 // Collapse to cursor at end of line
14788 new_selection_ranges.push(line_end..line_end);
14789 }
14790 }
14791
14792 let is_multiline_selection = selection.start.row != selection.end.row;
14793 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14794 // so this action feels more ergonomic when paired with other selection operations
14795 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14796 if !should_skip_last {
14797 if action.keep_selections {
14798 if is_multiline_selection {
14799 let line_start = Point::new(selection.end.row, 0);
14800 new_selection_ranges.push(line_start..selection.end);
14801 } else {
14802 new_selection_ranges.push(selection.start..selection.end);
14803 }
14804 } else {
14805 new_selection_ranges.push(selection.end..selection.end);
14806 }
14807 }
14808 }
14809 }
14810 self.change_selections(Default::default(), window, cx, |s| {
14811 s.select_ranges(new_selection_ranges);
14812 });
14813 }
14814
14815 pub fn add_selection_above(
14816 &mut self,
14817 action: &AddSelectionAbove,
14818 window: &mut Window,
14819 cx: &mut Context<Self>,
14820 ) {
14821 self.add_selection(true, action.skip_soft_wrap, window, cx);
14822 }
14823
14824 pub fn add_selection_below(
14825 &mut self,
14826 action: &AddSelectionBelow,
14827 window: &mut Window,
14828 cx: &mut Context<Self>,
14829 ) {
14830 self.add_selection(false, action.skip_soft_wrap, window, cx);
14831 }
14832
14833 fn add_selection(
14834 &mut self,
14835 above: bool,
14836 skip_soft_wrap: bool,
14837 window: &mut Window,
14838 cx: &mut Context<Self>,
14839 ) {
14840 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14841
14842 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14843 let all_selections = self.selections.all::<Point>(&display_map);
14844 let text_layout_details = self.text_layout_details(window);
14845
14846 let (mut columnar_selections, new_selections_to_columnarize) = {
14847 if let Some(state) = self.add_selections_state.as_ref() {
14848 let columnar_selection_ids: HashSet<_> = state
14849 .groups
14850 .iter()
14851 .flat_map(|group| group.stack.iter())
14852 .copied()
14853 .collect();
14854
14855 all_selections
14856 .into_iter()
14857 .partition(|s| columnar_selection_ids.contains(&s.id))
14858 } else {
14859 (Vec::new(), all_selections)
14860 }
14861 };
14862
14863 let mut state = self
14864 .add_selections_state
14865 .take()
14866 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14867
14868 for selection in new_selections_to_columnarize {
14869 let range = selection.display_range(&display_map).sorted();
14870 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14871 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14872 let positions = start_x.min(end_x)..start_x.max(end_x);
14873 let mut stack = Vec::new();
14874 for row in range.start.row().0..=range.end.row().0 {
14875 if let Some(selection) = self.selections.build_columnar_selection(
14876 &display_map,
14877 DisplayRow(row),
14878 &positions,
14879 selection.reversed,
14880 &text_layout_details,
14881 ) {
14882 stack.push(selection.id);
14883 columnar_selections.push(selection);
14884 }
14885 }
14886 if !stack.is_empty() {
14887 if above {
14888 stack.reverse();
14889 }
14890 state.groups.push(AddSelectionsGroup { above, stack });
14891 }
14892 }
14893
14894 let mut final_selections = Vec::new();
14895 let end_row = if above {
14896 DisplayRow(0)
14897 } else {
14898 display_map.max_point().row()
14899 };
14900
14901 let mut last_added_item_per_group = HashMap::default();
14902 for group in state.groups.iter_mut() {
14903 if let Some(last_id) = group.stack.last() {
14904 last_added_item_per_group.insert(*last_id, group);
14905 }
14906 }
14907
14908 for selection in columnar_selections {
14909 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14910 if above == group.above {
14911 let range = selection.display_range(&display_map).sorted();
14912 debug_assert_eq!(range.start.row(), range.end.row());
14913 let mut row = range.start.row();
14914 let positions =
14915 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14916 Pixels::from(start)..Pixels::from(end)
14917 } else {
14918 let start_x =
14919 display_map.x_for_display_point(range.start, &text_layout_details);
14920 let end_x =
14921 display_map.x_for_display_point(range.end, &text_layout_details);
14922 start_x.min(end_x)..start_x.max(end_x)
14923 };
14924
14925 let mut maybe_new_selection = None;
14926 let direction = if above { -1 } else { 1 };
14927
14928 while row != end_row {
14929 if skip_soft_wrap {
14930 row = display_map
14931 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14932 .row();
14933 } else if above {
14934 row.0 -= 1;
14935 } else {
14936 row.0 += 1;
14937 }
14938
14939 if let Some(new_selection) = self.selections.build_columnar_selection(
14940 &display_map,
14941 row,
14942 &positions,
14943 selection.reversed,
14944 &text_layout_details,
14945 ) {
14946 maybe_new_selection = Some(new_selection);
14947 break;
14948 }
14949 }
14950
14951 if let Some(new_selection) = maybe_new_selection {
14952 group.stack.push(new_selection.id);
14953 if above {
14954 final_selections.push(new_selection);
14955 final_selections.push(selection);
14956 } else {
14957 final_selections.push(selection);
14958 final_selections.push(new_selection);
14959 }
14960 } else {
14961 final_selections.push(selection);
14962 }
14963 } else {
14964 group.stack.pop();
14965 }
14966 } else {
14967 final_selections.push(selection);
14968 }
14969 }
14970
14971 self.change_selections(Default::default(), window, cx, |s| {
14972 s.select(final_selections);
14973 });
14974
14975 let final_selection_ids: HashSet<_> = self
14976 .selections
14977 .all::<Point>(&display_map)
14978 .iter()
14979 .map(|s| s.id)
14980 .collect();
14981 state.groups.retain_mut(|group| {
14982 // selections might get merged above so we remove invalid items from stacks
14983 group.stack.retain(|id| final_selection_ids.contains(id));
14984
14985 // single selection in stack can be treated as initial state
14986 group.stack.len() > 1
14987 });
14988
14989 if !state.groups.is_empty() {
14990 self.add_selections_state = Some(state);
14991 }
14992 }
14993
14994 pub fn insert_snippet_at_selections(
14995 &mut self,
14996 action: &InsertSnippet,
14997 window: &mut Window,
14998 cx: &mut Context<Self>,
14999 ) {
15000 self.try_insert_snippet_at_selections(action, window, cx)
15001 .log_err();
15002 }
15003
15004 fn try_insert_snippet_at_selections(
15005 &mut self,
15006 action: &InsertSnippet,
15007 window: &mut Window,
15008 cx: &mut Context<Self>,
15009 ) -> Result<()> {
15010 let insertion_ranges = self
15011 .selections
15012 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15013 .into_iter()
15014 .map(|selection| selection.range())
15015 .collect_vec();
15016
15017 let snippet = if let Some(snippet_body) = &action.snippet {
15018 if action.language.is_none() && action.name.is_none() {
15019 Snippet::parse(snippet_body)?
15020 } else {
15021 bail!("`snippet` is mutually exclusive with `language` and `name`")
15022 }
15023 } else if let Some(name) = &action.name {
15024 let project = self.project().context("no project")?;
15025 let snippet_store = project.read(cx).snippets().read(cx);
15026 let snippet = snippet_store
15027 .snippets_for(action.language.clone(), cx)
15028 .into_iter()
15029 .find(|snippet| snippet.name == *name)
15030 .context("snippet not found")?;
15031 Snippet::parse(&snippet.body)?
15032 } else {
15033 // todo(andrew): open modal to select snippet
15034 bail!("`name` or `snippet` is required")
15035 };
15036
15037 self.insert_snippet(&insertion_ranges, snippet, window, cx)
15038 }
15039
15040 fn select_match_ranges(
15041 &mut self,
15042 range: Range<MultiBufferOffset>,
15043 reversed: bool,
15044 replace_newest: bool,
15045 auto_scroll: Option<Autoscroll>,
15046 window: &mut Window,
15047 cx: &mut Context<Editor>,
15048 ) {
15049 self.unfold_ranges(
15050 std::slice::from_ref(&range),
15051 false,
15052 auto_scroll.is_some(),
15053 cx,
15054 );
15055 let effects = if let Some(scroll) = auto_scroll {
15056 SelectionEffects::scroll(scroll)
15057 } else {
15058 SelectionEffects::no_scroll()
15059 };
15060 self.change_selections(effects, window, cx, |s| {
15061 if replace_newest {
15062 s.delete(s.newest_anchor().id);
15063 }
15064 if reversed {
15065 s.insert_range(range.end..range.start);
15066 } else {
15067 s.insert_range(range);
15068 }
15069 });
15070 }
15071
15072 pub fn select_next_match_internal(
15073 &mut self,
15074 display_map: &DisplaySnapshot,
15075 replace_newest: bool,
15076 autoscroll: Option<Autoscroll>,
15077 window: &mut Window,
15078 cx: &mut Context<Self>,
15079 ) -> Result<()> {
15080 let buffer = display_map.buffer_snapshot();
15081 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15082 if let Some(mut select_next_state) = self.select_next_state.take() {
15083 let query = &select_next_state.query;
15084 if !select_next_state.done {
15085 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15086 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15087 let mut next_selected_range = None;
15088
15089 let bytes_after_last_selection =
15090 buffer.bytes_in_range(last_selection.end..buffer.len());
15091 let bytes_before_first_selection =
15092 buffer.bytes_in_range(MultiBufferOffset(0)..first_selection.start);
15093 let query_matches = query
15094 .stream_find_iter(bytes_after_last_selection)
15095 .map(|result| (last_selection.end, result))
15096 .chain(
15097 query
15098 .stream_find_iter(bytes_before_first_selection)
15099 .map(|result| (MultiBufferOffset(0), result)),
15100 );
15101
15102 for (start_offset, query_match) in query_matches {
15103 let query_match = query_match.unwrap(); // can only fail due to I/O
15104 let offset_range =
15105 start_offset + query_match.start()..start_offset + query_match.end();
15106
15107 if !select_next_state.wordwise
15108 || (!buffer.is_inside_word(offset_range.start, None)
15109 && !buffer.is_inside_word(offset_range.end, None))
15110 {
15111 let idx = selections
15112 .partition_point(|selection| selection.end <= offset_range.start);
15113 let overlaps = selections
15114 .get(idx)
15115 .map_or(false, |selection| selection.start < offset_range.end);
15116
15117 if !overlaps {
15118 next_selected_range = Some(offset_range);
15119 break;
15120 }
15121 }
15122 }
15123
15124 if let Some(next_selected_range) = next_selected_range {
15125 self.select_match_ranges(
15126 next_selected_range,
15127 last_selection.reversed,
15128 replace_newest,
15129 autoscroll,
15130 window,
15131 cx,
15132 );
15133 } else {
15134 select_next_state.done = true;
15135 }
15136 }
15137
15138 self.select_next_state = Some(select_next_state);
15139 } else {
15140 let mut only_carets = true;
15141 let mut same_text_selected = true;
15142 let mut selected_text = None;
15143
15144 let mut selections_iter = selections.iter().peekable();
15145 while let Some(selection) = selections_iter.next() {
15146 if selection.start != selection.end {
15147 only_carets = false;
15148 }
15149
15150 if same_text_selected {
15151 if selected_text.is_none() {
15152 selected_text =
15153 Some(buffer.text_for_range(selection.range()).collect::<String>());
15154 }
15155
15156 if let Some(next_selection) = selections_iter.peek() {
15157 if next_selection.len() == selection.len() {
15158 let next_selected_text = buffer
15159 .text_for_range(next_selection.range())
15160 .collect::<String>();
15161 if Some(next_selected_text) != selected_text {
15162 same_text_selected = false;
15163 selected_text = None;
15164 }
15165 } else {
15166 same_text_selected = false;
15167 selected_text = None;
15168 }
15169 }
15170 }
15171 }
15172
15173 if only_carets {
15174 for selection in &mut selections {
15175 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15176 selection.start = word_range.start;
15177 selection.end = word_range.end;
15178 selection.goal = SelectionGoal::None;
15179 selection.reversed = false;
15180 self.select_match_ranges(
15181 selection.start..selection.end,
15182 selection.reversed,
15183 replace_newest,
15184 autoscroll,
15185 window,
15186 cx,
15187 );
15188 }
15189
15190 if selections.len() == 1 {
15191 let selection = selections
15192 .last()
15193 .expect("ensured that there's only one selection");
15194 let query = buffer
15195 .text_for_range(selection.start..selection.end)
15196 .collect::<String>();
15197 let is_empty = query.is_empty();
15198 let select_state = SelectNextState {
15199 query: self.build_query(&[query], cx)?,
15200 wordwise: true,
15201 done: is_empty,
15202 };
15203 self.select_next_state = Some(select_state);
15204 } else {
15205 self.select_next_state = None;
15206 }
15207 } else if let Some(selected_text) = selected_text {
15208 self.select_next_state = Some(SelectNextState {
15209 query: self.build_query(&[selected_text], cx)?,
15210 wordwise: false,
15211 done: false,
15212 });
15213 self.select_next_match_internal(
15214 display_map,
15215 replace_newest,
15216 autoscroll,
15217 window,
15218 cx,
15219 )?;
15220 }
15221 }
15222 Ok(())
15223 }
15224
15225 pub fn select_all_matches(
15226 &mut self,
15227 _action: &SelectAllMatches,
15228 window: &mut Window,
15229 cx: &mut Context<Self>,
15230 ) -> Result<()> {
15231 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15232
15233 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15234
15235 self.select_next_match_internal(&display_map, false, None, window, cx)?;
15236 let Some(select_next_state) = self.select_next_state.as_mut() else {
15237 return Ok(());
15238 };
15239 if select_next_state.done {
15240 return Ok(());
15241 }
15242
15243 let mut new_selections = Vec::new();
15244
15245 let reversed = self
15246 .selections
15247 .oldest::<MultiBufferOffset>(&display_map)
15248 .reversed;
15249 let buffer = display_map.buffer_snapshot();
15250 let query_matches = select_next_state
15251 .query
15252 .stream_find_iter(buffer.bytes_in_range(MultiBufferOffset(0)..buffer.len()));
15253
15254 for query_match in query_matches.into_iter() {
15255 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
15256 let offset_range = if reversed {
15257 MultiBufferOffset(query_match.end())..MultiBufferOffset(query_match.start())
15258 } else {
15259 MultiBufferOffset(query_match.start())..MultiBufferOffset(query_match.end())
15260 };
15261
15262 if !select_next_state.wordwise
15263 || (!buffer.is_inside_word(offset_range.start, None)
15264 && !buffer.is_inside_word(offset_range.end, None))
15265 {
15266 new_selections.push(offset_range.start..offset_range.end);
15267 }
15268 }
15269
15270 select_next_state.done = true;
15271
15272 if new_selections.is_empty() {
15273 log::error!("bug: new_selections is empty in select_all_matches");
15274 return Ok(());
15275 }
15276
15277 self.unfold_ranges(&new_selections.clone(), false, false, cx);
15278 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
15279 selections.select_ranges(new_selections)
15280 });
15281
15282 Ok(())
15283 }
15284
15285 pub fn select_next(
15286 &mut self,
15287 action: &SelectNext,
15288 window: &mut Window,
15289 cx: &mut Context<Self>,
15290 ) -> Result<()> {
15291 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15292 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15293 self.select_next_match_internal(
15294 &display_map,
15295 action.replace_newest,
15296 Some(Autoscroll::newest()),
15297 window,
15298 cx,
15299 )?;
15300 Ok(())
15301 }
15302
15303 pub fn select_previous(
15304 &mut self,
15305 action: &SelectPrevious,
15306 window: &mut Window,
15307 cx: &mut Context<Self>,
15308 ) -> Result<()> {
15309 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15310 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15311 let buffer = display_map.buffer_snapshot();
15312 let mut selections = self.selections.all::<MultiBufferOffset>(&display_map);
15313 if let Some(mut select_prev_state) = self.select_prev_state.take() {
15314 let query = &select_prev_state.query;
15315 if !select_prev_state.done {
15316 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
15317 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
15318 let mut next_selected_range = None;
15319 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
15320 let bytes_before_last_selection =
15321 buffer.reversed_bytes_in_range(MultiBufferOffset(0)..last_selection.start);
15322 let bytes_after_first_selection =
15323 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
15324 let query_matches = query
15325 .stream_find_iter(bytes_before_last_selection)
15326 .map(|result| (last_selection.start, result))
15327 .chain(
15328 query
15329 .stream_find_iter(bytes_after_first_selection)
15330 .map(|result| (buffer.len(), result)),
15331 );
15332 for (end_offset, query_match) in query_matches {
15333 let query_match = query_match.unwrap(); // can only fail due to I/O
15334 let offset_range =
15335 end_offset - query_match.end()..end_offset - query_match.start();
15336
15337 if !select_prev_state.wordwise
15338 || (!buffer.is_inside_word(offset_range.start, None)
15339 && !buffer.is_inside_word(offset_range.end, None))
15340 {
15341 next_selected_range = Some(offset_range);
15342 break;
15343 }
15344 }
15345
15346 if let Some(next_selected_range) = next_selected_range {
15347 self.select_match_ranges(
15348 next_selected_range,
15349 last_selection.reversed,
15350 action.replace_newest,
15351 Some(Autoscroll::newest()),
15352 window,
15353 cx,
15354 );
15355 } else {
15356 select_prev_state.done = true;
15357 }
15358 }
15359
15360 self.select_prev_state = Some(select_prev_state);
15361 } else {
15362 let mut only_carets = true;
15363 let mut same_text_selected = true;
15364 let mut selected_text = None;
15365
15366 let mut selections_iter = selections.iter().peekable();
15367 while let Some(selection) = selections_iter.next() {
15368 if selection.start != selection.end {
15369 only_carets = false;
15370 }
15371
15372 if same_text_selected {
15373 if selected_text.is_none() {
15374 selected_text =
15375 Some(buffer.text_for_range(selection.range()).collect::<String>());
15376 }
15377
15378 if let Some(next_selection) = selections_iter.peek() {
15379 if next_selection.len() == selection.len() {
15380 let next_selected_text = buffer
15381 .text_for_range(next_selection.range())
15382 .collect::<String>();
15383 if Some(next_selected_text) != selected_text {
15384 same_text_selected = false;
15385 selected_text = None;
15386 }
15387 } else {
15388 same_text_selected = false;
15389 selected_text = None;
15390 }
15391 }
15392 }
15393 }
15394
15395 if only_carets {
15396 for selection in &mut selections {
15397 let (word_range, _) = buffer.surrounding_word(selection.start, None);
15398 selection.start = word_range.start;
15399 selection.end = word_range.end;
15400 selection.goal = SelectionGoal::None;
15401 selection.reversed = false;
15402 self.select_match_ranges(
15403 selection.start..selection.end,
15404 selection.reversed,
15405 action.replace_newest,
15406 Some(Autoscroll::newest()),
15407 window,
15408 cx,
15409 );
15410 }
15411 if selections.len() == 1 {
15412 let selection = selections
15413 .last()
15414 .expect("ensured that there's only one selection");
15415 let query = buffer
15416 .text_for_range(selection.start..selection.end)
15417 .collect::<String>();
15418 let is_empty = query.is_empty();
15419 let select_state = SelectNextState {
15420 query: self.build_query(&[query.chars().rev().collect::<String>()], cx)?,
15421 wordwise: true,
15422 done: is_empty,
15423 };
15424 self.select_prev_state = Some(select_state);
15425 } else {
15426 self.select_prev_state = None;
15427 }
15428 } else if let Some(selected_text) = selected_text {
15429 self.select_prev_state = Some(SelectNextState {
15430 query: self
15431 .build_query(&[selected_text.chars().rev().collect::<String>()], cx)?,
15432 wordwise: false,
15433 done: false,
15434 });
15435 self.select_previous(action, window, cx)?;
15436 }
15437 }
15438 Ok(())
15439 }
15440
15441 /// Builds an `AhoCorasick` automaton from the provided patterns, while
15442 /// setting the case sensitivity based on the global
15443 /// `SelectNextCaseSensitive` setting, if set, otherwise based on the
15444 /// editor's settings.
15445 fn build_query<I, P>(&self, patterns: I, cx: &Context<Self>) -> Result<AhoCorasick, BuildError>
15446 where
15447 I: IntoIterator<Item = P>,
15448 P: AsRef<[u8]>,
15449 {
15450 let case_sensitive = self.select_next_is_case_sensitive.map_or_else(
15451 || EditorSettings::get_global(cx).search.case_sensitive,
15452 |value| value,
15453 );
15454
15455 let mut builder = AhoCorasickBuilder::new();
15456 builder.ascii_case_insensitive(!case_sensitive);
15457 builder.build(patterns)
15458 }
15459
15460 pub fn find_next_match(
15461 &mut self,
15462 _: &FindNextMatch,
15463 window: &mut Window,
15464 cx: &mut Context<Self>,
15465 ) -> Result<()> {
15466 let selections = self.selections.disjoint_anchors_arc();
15467 match selections.first() {
15468 Some(first) if selections.len() >= 2 => {
15469 self.change_selections(Default::default(), window, cx, |s| {
15470 s.select_ranges([first.range()]);
15471 });
15472 }
15473 _ => self.select_next(
15474 &SelectNext {
15475 replace_newest: true,
15476 },
15477 window,
15478 cx,
15479 )?,
15480 }
15481 Ok(())
15482 }
15483
15484 pub fn find_previous_match(
15485 &mut self,
15486 _: &FindPreviousMatch,
15487 window: &mut Window,
15488 cx: &mut Context<Self>,
15489 ) -> Result<()> {
15490 let selections = self.selections.disjoint_anchors_arc();
15491 match selections.last() {
15492 Some(last) if selections.len() >= 2 => {
15493 self.change_selections(Default::default(), window, cx, |s| {
15494 s.select_ranges([last.range()]);
15495 });
15496 }
15497 _ => self.select_previous(
15498 &SelectPrevious {
15499 replace_newest: true,
15500 },
15501 window,
15502 cx,
15503 )?,
15504 }
15505 Ok(())
15506 }
15507
15508 pub fn toggle_comments(
15509 &mut self,
15510 action: &ToggleComments,
15511 window: &mut Window,
15512 cx: &mut Context<Self>,
15513 ) {
15514 if self.read_only(cx) {
15515 return;
15516 }
15517 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
15518 let text_layout_details = &self.text_layout_details(window);
15519 self.transact(window, cx, |this, window, cx| {
15520 let mut selections = this
15521 .selections
15522 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
15523 let mut edits = Vec::new();
15524 let mut selection_edit_ranges = Vec::new();
15525 let mut last_toggled_row = None;
15526 let snapshot = this.buffer.read(cx).read(cx);
15527 let empty_str: Arc<str> = Arc::default();
15528 let mut suffixes_inserted = Vec::new();
15529 let ignore_indent = action.ignore_indent;
15530
15531 fn comment_prefix_range(
15532 snapshot: &MultiBufferSnapshot,
15533 row: MultiBufferRow,
15534 comment_prefix: &str,
15535 comment_prefix_whitespace: &str,
15536 ignore_indent: bool,
15537 ) -> Range<Point> {
15538 let indent_size = if ignore_indent {
15539 0
15540 } else {
15541 snapshot.indent_size_for_line(row).len
15542 };
15543
15544 let start = Point::new(row.0, indent_size);
15545
15546 let mut line_bytes = snapshot
15547 .bytes_in_range(start..snapshot.max_point())
15548 .flatten()
15549 .copied();
15550
15551 // If this line currently begins with the line comment prefix, then record
15552 // the range containing the prefix.
15553 if line_bytes
15554 .by_ref()
15555 .take(comment_prefix.len())
15556 .eq(comment_prefix.bytes())
15557 {
15558 // Include any whitespace that matches the comment prefix.
15559 let matching_whitespace_len = line_bytes
15560 .zip(comment_prefix_whitespace.bytes())
15561 .take_while(|(a, b)| a == b)
15562 .count() as u32;
15563 let end = Point::new(
15564 start.row,
15565 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
15566 );
15567 start..end
15568 } else {
15569 start..start
15570 }
15571 }
15572
15573 fn comment_suffix_range(
15574 snapshot: &MultiBufferSnapshot,
15575 row: MultiBufferRow,
15576 comment_suffix: &str,
15577 comment_suffix_has_leading_space: bool,
15578 ) -> Range<Point> {
15579 let end = Point::new(row.0, snapshot.line_len(row));
15580 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15581
15582 let mut line_end_bytes = snapshot
15583 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15584 .flatten()
15585 .copied();
15586
15587 let leading_space_len = if suffix_start_column > 0
15588 && line_end_bytes.next() == Some(b' ')
15589 && comment_suffix_has_leading_space
15590 {
15591 1
15592 } else {
15593 0
15594 };
15595
15596 // If this line currently begins with the line comment prefix, then record
15597 // the range containing the prefix.
15598 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15599 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15600 start..end
15601 } else {
15602 end..end
15603 }
15604 }
15605
15606 // TODO: Handle selections that cross excerpts
15607 for selection in &mut selections {
15608 let start_column = snapshot
15609 .indent_size_for_line(MultiBufferRow(selection.start.row))
15610 .len;
15611 let language = if let Some(language) =
15612 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15613 {
15614 language
15615 } else {
15616 continue;
15617 };
15618
15619 selection_edit_ranges.clear();
15620
15621 // If multiple selections contain a given row, avoid processing that
15622 // row more than once.
15623 let mut start_row = MultiBufferRow(selection.start.row);
15624 if last_toggled_row == Some(start_row) {
15625 start_row = start_row.next_row();
15626 }
15627 let end_row =
15628 if selection.end.row > selection.start.row && selection.end.column == 0 {
15629 MultiBufferRow(selection.end.row - 1)
15630 } else {
15631 MultiBufferRow(selection.end.row)
15632 };
15633 last_toggled_row = Some(end_row);
15634
15635 if start_row > end_row {
15636 continue;
15637 }
15638
15639 // If the language has line comments, toggle those.
15640 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15641
15642 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15643 if ignore_indent {
15644 full_comment_prefixes = full_comment_prefixes
15645 .into_iter()
15646 .map(|s| Arc::from(s.trim_end()))
15647 .collect();
15648 }
15649
15650 if !full_comment_prefixes.is_empty() {
15651 let first_prefix = full_comment_prefixes
15652 .first()
15653 .expect("prefixes is non-empty");
15654 let prefix_trimmed_lengths = full_comment_prefixes
15655 .iter()
15656 .map(|p| p.trim_end_matches(' ').len())
15657 .collect::<SmallVec<[usize; 4]>>();
15658
15659 let mut all_selection_lines_are_comments = true;
15660
15661 for row in start_row.0..=end_row.0 {
15662 let row = MultiBufferRow(row);
15663 if start_row < end_row && snapshot.is_line_blank(row) {
15664 continue;
15665 }
15666
15667 let prefix_range = full_comment_prefixes
15668 .iter()
15669 .zip(prefix_trimmed_lengths.iter().copied())
15670 .map(|(prefix, trimmed_prefix_len)| {
15671 comment_prefix_range(
15672 snapshot.deref(),
15673 row,
15674 &prefix[..trimmed_prefix_len],
15675 &prefix[trimmed_prefix_len..],
15676 ignore_indent,
15677 )
15678 })
15679 .max_by_key(|range| range.end.column - range.start.column)
15680 .expect("prefixes is non-empty");
15681
15682 if prefix_range.is_empty() {
15683 all_selection_lines_are_comments = false;
15684 }
15685
15686 selection_edit_ranges.push(prefix_range);
15687 }
15688
15689 if all_selection_lines_are_comments {
15690 edits.extend(
15691 selection_edit_ranges
15692 .iter()
15693 .cloned()
15694 .map(|range| (range, empty_str.clone())),
15695 );
15696 } else {
15697 let min_column = selection_edit_ranges
15698 .iter()
15699 .map(|range| range.start.column)
15700 .min()
15701 .unwrap_or(0);
15702 edits.extend(selection_edit_ranges.iter().map(|range| {
15703 let position = Point::new(range.start.row, min_column);
15704 (position..position, first_prefix.clone())
15705 }));
15706 }
15707 } else if let Some(BlockCommentConfig {
15708 start: full_comment_prefix,
15709 end: comment_suffix,
15710 ..
15711 }) = language.block_comment()
15712 {
15713 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15714 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15715 let prefix_range = comment_prefix_range(
15716 snapshot.deref(),
15717 start_row,
15718 comment_prefix,
15719 comment_prefix_whitespace,
15720 ignore_indent,
15721 );
15722 let suffix_range = comment_suffix_range(
15723 snapshot.deref(),
15724 end_row,
15725 comment_suffix.trim_start_matches(' '),
15726 comment_suffix.starts_with(' '),
15727 );
15728
15729 if prefix_range.is_empty() || suffix_range.is_empty() {
15730 edits.push((
15731 prefix_range.start..prefix_range.start,
15732 full_comment_prefix.clone(),
15733 ));
15734 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15735 suffixes_inserted.push((end_row, comment_suffix.len()));
15736 } else {
15737 edits.push((prefix_range, empty_str.clone()));
15738 edits.push((suffix_range, empty_str.clone()));
15739 }
15740 } else {
15741 continue;
15742 }
15743 }
15744
15745 drop(snapshot);
15746 this.buffer.update(cx, |buffer, cx| {
15747 buffer.edit(edits, None, cx);
15748 });
15749
15750 // Adjust selections so that they end before any comment suffixes that
15751 // were inserted.
15752 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15753 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15754 let snapshot = this.buffer.read(cx).read(cx);
15755 for selection in &mut selections {
15756 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15757 match row.cmp(&MultiBufferRow(selection.end.row)) {
15758 Ordering::Less => {
15759 suffixes_inserted.next();
15760 continue;
15761 }
15762 Ordering::Greater => break,
15763 Ordering::Equal => {
15764 if selection.end.column == snapshot.line_len(row) {
15765 if selection.is_empty() {
15766 selection.start.column -= suffix_len as u32;
15767 }
15768 selection.end.column -= suffix_len as u32;
15769 }
15770 break;
15771 }
15772 }
15773 }
15774 }
15775
15776 drop(snapshot);
15777 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15778
15779 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15780 let selections_on_single_row = selections.windows(2).all(|selections| {
15781 selections[0].start.row == selections[1].start.row
15782 && selections[0].end.row == selections[1].end.row
15783 && selections[0].start.row == selections[0].end.row
15784 });
15785 let selections_selecting = selections
15786 .iter()
15787 .any(|selection| selection.start != selection.end);
15788 let advance_downwards = action.advance_downwards
15789 && selections_on_single_row
15790 && !selections_selecting
15791 && !matches!(this.mode, EditorMode::SingleLine);
15792
15793 if advance_downwards {
15794 let snapshot = this.buffer.read(cx).snapshot(cx);
15795
15796 this.change_selections(Default::default(), window, cx, |s| {
15797 s.move_cursors_with(|display_snapshot, display_point, _| {
15798 let mut point = display_point.to_point(display_snapshot);
15799 point.row += 1;
15800 point = snapshot.clip_point(point, Bias::Left);
15801 let display_point = point.to_display_point(display_snapshot);
15802 let goal = SelectionGoal::HorizontalPosition(
15803 display_snapshot
15804 .x_for_display_point(display_point, text_layout_details)
15805 .into(),
15806 );
15807 (display_point, goal)
15808 })
15809 });
15810 }
15811 });
15812 }
15813
15814 pub fn select_enclosing_symbol(
15815 &mut self,
15816 _: &SelectEnclosingSymbol,
15817 window: &mut Window,
15818 cx: &mut Context<Self>,
15819 ) {
15820 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15821
15822 let buffer = self.buffer.read(cx).snapshot(cx);
15823 let old_selections = self
15824 .selections
15825 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15826 .into_boxed_slice();
15827
15828 fn update_selection(
15829 selection: &Selection<MultiBufferOffset>,
15830 buffer_snap: &MultiBufferSnapshot,
15831 ) -> Option<Selection<MultiBufferOffset>> {
15832 let cursor = selection.head();
15833 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15834 for symbol in symbols.iter().rev() {
15835 let start = symbol.range.start.to_offset(buffer_snap);
15836 let end = symbol.range.end.to_offset(buffer_snap);
15837 let new_range = start..end;
15838 if start < selection.start || end > selection.end {
15839 return Some(Selection {
15840 id: selection.id,
15841 start: new_range.start,
15842 end: new_range.end,
15843 goal: SelectionGoal::None,
15844 reversed: selection.reversed,
15845 });
15846 }
15847 }
15848 None
15849 }
15850
15851 let mut selected_larger_symbol = false;
15852 let new_selections = old_selections
15853 .iter()
15854 .map(|selection| match update_selection(selection, &buffer) {
15855 Some(new_selection) => {
15856 if new_selection.range() != selection.range() {
15857 selected_larger_symbol = true;
15858 }
15859 new_selection
15860 }
15861 None => selection.clone(),
15862 })
15863 .collect::<Vec<_>>();
15864
15865 if selected_larger_symbol {
15866 self.change_selections(Default::default(), window, cx, |s| {
15867 s.select(new_selections);
15868 });
15869 }
15870 }
15871
15872 pub fn select_larger_syntax_node(
15873 &mut self,
15874 _: &SelectLargerSyntaxNode,
15875 window: &mut Window,
15876 cx: &mut Context<Self>,
15877 ) {
15878 let Some(visible_row_count) = self.visible_row_count() else {
15879 return;
15880 };
15881 let old_selections: Box<[_]> = self
15882 .selections
15883 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
15884 .into();
15885 if old_selections.is_empty() {
15886 return;
15887 }
15888
15889 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15890
15891 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15892 let buffer = self.buffer.read(cx).snapshot(cx);
15893
15894 let mut selected_larger_node = false;
15895 let mut new_selections = old_selections
15896 .iter()
15897 .map(|selection| {
15898 let old_range = selection.start..selection.end;
15899
15900 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15901 // manually select word at selection
15902 if ["string_content", "inline"].contains(&node.kind()) {
15903 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15904 // ignore if word is already selected
15905 if !word_range.is_empty() && old_range != word_range {
15906 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15907 // only select word if start and end point belongs to same word
15908 if word_range == last_word_range {
15909 selected_larger_node = true;
15910 return Selection {
15911 id: selection.id,
15912 start: word_range.start,
15913 end: word_range.end,
15914 goal: SelectionGoal::None,
15915 reversed: selection.reversed,
15916 };
15917 }
15918 }
15919 }
15920 }
15921
15922 let mut new_range = old_range.clone();
15923 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15924 new_range = range;
15925 if !node.is_named() {
15926 continue;
15927 }
15928 if !display_map.intersects_fold(new_range.start)
15929 && !display_map.intersects_fold(new_range.end)
15930 {
15931 break;
15932 }
15933 }
15934
15935 selected_larger_node |= new_range != old_range;
15936 Selection {
15937 id: selection.id,
15938 start: new_range.start,
15939 end: new_range.end,
15940 goal: SelectionGoal::None,
15941 reversed: selection.reversed,
15942 }
15943 })
15944 .collect::<Vec<_>>();
15945
15946 if !selected_larger_node {
15947 return; // don't put this call in the history
15948 }
15949
15950 // scroll based on transformation done to the last selection created by the user
15951 let (last_old, last_new) = old_selections
15952 .last()
15953 .zip(new_selections.last().cloned())
15954 .expect("old_selections isn't empty");
15955
15956 // revert selection
15957 let is_selection_reversed = {
15958 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15959 new_selections.last_mut().expect("checked above").reversed =
15960 should_newest_selection_be_reversed;
15961 should_newest_selection_be_reversed
15962 };
15963
15964 if selected_larger_node {
15965 self.select_syntax_node_history.disable_clearing = true;
15966 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15967 s.select(new_selections.clone());
15968 });
15969 self.select_syntax_node_history.disable_clearing = false;
15970 }
15971
15972 let start_row = last_new.start.to_display_point(&display_map).row().0;
15973 let end_row = last_new.end.to_display_point(&display_map).row().0;
15974 let selection_height = end_row - start_row + 1;
15975 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15976
15977 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15978 let scroll_behavior = if fits_on_the_screen {
15979 self.request_autoscroll(Autoscroll::fit(), cx);
15980 SelectSyntaxNodeScrollBehavior::FitSelection
15981 } else if is_selection_reversed {
15982 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15983 SelectSyntaxNodeScrollBehavior::CursorTop
15984 } else {
15985 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15986 SelectSyntaxNodeScrollBehavior::CursorBottom
15987 };
15988
15989 self.select_syntax_node_history.push((
15990 old_selections,
15991 scroll_behavior,
15992 is_selection_reversed,
15993 ));
15994 }
15995
15996 pub fn select_smaller_syntax_node(
15997 &mut self,
15998 _: &SelectSmallerSyntaxNode,
15999 window: &mut Window,
16000 cx: &mut Context<Self>,
16001 ) {
16002 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16003
16004 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
16005 self.select_syntax_node_history.pop()
16006 {
16007 if let Some(selection) = selections.last_mut() {
16008 selection.reversed = is_selection_reversed;
16009 }
16010
16011 self.select_syntax_node_history.disable_clearing = true;
16012 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
16013 s.select(selections.to_vec());
16014 });
16015 self.select_syntax_node_history.disable_clearing = false;
16016
16017 match scroll_behavior {
16018 SelectSyntaxNodeScrollBehavior::CursorTop => {
16019 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
16020 }
16021 SelectSyntaxNodeScrollBehavior::FitSelection => {
16022 self.request_autoscroll(Autoscroll::fit(), cx);
16023 }
16024 SelectSyntaxNodeScrollBehavior::CursorBottom => {
16025 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
16026 }
16027 }
16028 }
16029 }
16030
16031 pub fn unwrap_syntax_node(
16032 &mut self,
16033 _: &UnwrapSyntaxNode,
16034 window: &mut Window,
16035 cx: &mut Context<Self>,
16036 ) {
16037 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16038
16039 let buffer = self.buffer.read(cx).snapshot(cx);
16040 let selections = self
16041 .selections
16042 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16043 .into_iter()
16044 // subtracting the offset requires sorting
16045 .sorted_by_key(|i| i.start);
16046
16047 let full_edits = selections
16048 .into_iter()
16049 .filter_map(|selection| {
16050 let child = if selection.is_empty()
16051 && let Some((_, ancestor_range)) =
16052 buffer.syntax_ancestor(selection.start..selection.end)
16053 {
16054 ancestor_range
16055 } else {
16056 selection.range()
16057 };
16058
16059 let mut parent = child.clone();
16060 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
16061 parent = ancestor_range;
16062 if parent.start < child.start || parent.end > child.end {
16063 break;
16064 }
16065 }
16066
16067 if parent == child {
16068 return None;
16069 }
16070 let text = buffer.text_for_range(child).collect::<String>();
16071 Some((selection.id, parent, text))
16072 })
16073 .collect::<Vec<_>>();
16074 if full_edits.is_empty() {
16075 return;
16076 }
16077
16078 self.transact(window, cx, |this, window, cx| {
16079 this.buffer.update(cx, |buffer, cx| {
16080 buffer.edit(
16081 full_edits
16082 .iter()
16083 .map(|(_, p, t)| (p.clone(), t.clone()))
16084 .collect::<Vec<_>>(),
16085 None,
16086 cx,
16087 );
16088 });
16089 this.change_selections(Default::default(), window, cx, |s| {
16090 let mut offset = 0;
16091 let mut selections = vec![];
16092 for (id, parent, text) in full_edits {
16093 let start = parent.start - offset;
16094 offset += (parent.end - parent.start) - text.len();
16095 selections.push(Selection {
16096 id,
16097 start,
16098 end: start + text.len(),
16099 reversed: false,
16100 goal: Default::default(),
16101 });
16102 }
16103 s.select(selections);
16104 });
16105 });
16106 }
16107
16108 pub fn select_next_syntax_node(
16109 &mut self,
16110 _: &SelectNextSyntaxNode,
16111 window: &mut Window,
16112 cx: &mut Context<Self>,
16113 ) {
16114 let old_selections: Box<[_]> = self
16115 .selections
16116 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16117 .into();
16118 if old_selections.is_empty() {
16119 return;
16120 }
16121
16122 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16123
16124 let buffer = self.buffer.read(cx).snapshot(cx);
16125 let mut selected_sibling = false;
16126
16127 let new_selections = old_selections
16128 .iter()
16129 .map(|selection| {
16130 let old_range = selection.start..selection.end;
16131
16132 let old_range =
16133 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16134 let excerpt = buffer.excerpt_containing(old_range.clone());
16135
16136 if let Some(mut excerpt) = excerpt
16137 && let Some(node) = excerpt
16138 .buffer()
16139 .syntax_next_sibling(excerpt.map_range_to_buffer(old_range))
16140 {
16141 let new_range = excerpt.map_range_from_buffer(
16142 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16143 );
16144 selected_sibling = true;
16145 Selection {
16146 id: selection.id,
16147 start: new_range.start,
16148 end: new_range.end,
16149 goal: SelectionGoal::None,
16150 reversed: selection.reversed,
16151 }
16152 } else {
16153 selection.clone()
16154 }
16155 })
16156 .collect::<Vec<_>>();
16157
16158 if selected_sibling {
16159 self.change_selections(
16160 SelectionEffects::scroll(Autoscroll::fit()),
16161 window,
16162 cx,
16163 |s| {
16164 s.select(new_selections);
16165 },
16166 );
16167 }
16168 }
16169
16170 pub fn select_prev_syntax_node(
16171 &mut self,
16172 _: &SelectPreviousSyntaxNode,
16173 window: &mut Window,
16174 cx: &mut Context<Self>,
16175 ) {
16176 let old_selections: Box<[_]> = self
16177 .selections
16178 .all::<MultiBufferOffset>(&self.display_snapshot(cx))
16179 .into();
16180 if old_selections.is_empty() {
16181 return;
16182 }
16183
16184 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16185
16186 let buffer = self.buffer.read(cx).snapshot(cx);
16187 let mut selected_sibling = false;
16188
16189 let new_selections = old_selections
16190 .iter()
16191 .map(|selection| {
16192 let old_range = selection.start..selection.end;
16193 let old_range =
16194 old_range.start.to_offset(&buffer)..old_range.end.to_offset(&buffer);
16195 let excerpt = buffer.excerpt_containing(old_range.clone());
16196
16197 if let Some(mut excerpt) = excerpt
16198 && let Some(node) = excerpt
16199 .buffer()
16200 .syntax_prev_sibling(excerpt.map_range_to_buffer(old_range))
16201 {
16202 let new_range = excerpt.map_range_from_buffer(
16203 BufferOffset(node.byte_range().start)..BufferOffset(node.byte_range().end),
16204 );
16205 selected_sibling = true;
16206 Selection {
16207 id: selection.id,
16208 start: new_range.start,
16209 end: new_range.end,
16210 goal: SelectionGoal::None,
16211 reversed: selection.reversed,
16212 }
16213 } else {
16214 selection.clone()
16215 }
16216 })
16217 .collect::<Vec<_>>();
16218
16219 if selected_sibling {
16220 self.change_selections(
16221 SelectionEffects::scroll(Autoscroll::fit()),
16222 window,
16223 cx,
16224 |s| {
16225 s.select(new_selections);
16226 },
16227 );
16228 }
16229 }
16230
16231 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
16232 if !EditorSettings::get_global(cx).gutter.runnables {
16233 self.clear_tasks();
16234 return Task::ready(());
16235 }
16236 let project = self.project().map(Entity::downgrade);
16237 let task_sources = self.lsp_task_sources(cx);
16238 let multi_buffer = self.buffer.downgrade();
16239 cx.spawn_in(window, async move |editor, cx| {
16240 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
16241 let Some(project) = project.and_then(|p| p.upgrade()) else {
16242 return;
16243 };
16244 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
16245 this.display_map.update(cx, |map, cx| map.snapshot(cx))
16246 }) else {
16247 return;
16248 };
16249
16250 let hide_runnables = project
16251 .update(cx, |project, _| project.is_via_collab())
16252 .unwrap_or(true);
16253 if hide_runnables {
16254 return;
16255 }
16256 let new_rows =
16257 cx.background_spawn({
16258 let snapshot = display_snapshot.clone();
16259 async move {
16260 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
16261 }
16262 })
16263 .await;
16264 let Ok(lsp_tasks) =
16265 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
16266 else {
16267 return;
16268 };
16269 let lsp_tasks = lsp_tasks.await;
16270
16271 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
16272 lsp_tasks
16273 .into_iter()
16274 .flat_map(|(kind, tasks)| {
16275 tasks.into_iter().filter_map(move |(location, task)| {
16276 Some((kind.clone(), location?, task))
16277 })
16278 })
16279 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
16280 let buffer = location.target.buffer;
16281 let buffer_snapshot = buffer.read(cx).snapshot();
16282 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
16283 |(excerpt_id, snapshot, _)| {
16284 if snapshot.remote_id() == buffer_snapshot.remote_id() {
16285 display_snapshot
16286 .buffer_snapshot()
16287 .anchor_in_excerpt(excerpt_id, location.target.range.start)
16288 } else {
16289 None
16290 }
16291 },
16292 );
16293 if let Some(offset) = offset {
16294 let task_buffer_range =
16295 location.target.range.to_point(&buffer_snapshot);
16296 let context_buffer_range =
16297 task_buffer_range.to_offset(&buffer_snapshot);
16298 let context_range = BufferOffset(context_buffer_range.start)
16299 ..BufferOffset(context_buffer_range.end);
16300
16301 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
16302 .or_insert_with(|| RunnableTasks {
16303 templates: Vec::new(),
16304 offset,
16305 column: task_buffer_range.start.column,
16306 extra_variables: HashMap::default(),
16307 context_range,
16308 })
16309 .templates
16310 .push((kind, task.original_task().clone()));
16311 }
16312
16313 acc
16314 })
16315 }) else {
16316 return;
16317 };
16318
16319 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
16320 buffer.language_settings(cx).tasks.prefer_lsp
16321 }) else {
16322 return;
16323 };
16324
16325 let rows = Self::runnable_rows(
16326 project,
16327 display_snapshot,
16328 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
16329 new_rows,
16330 cx.clone(),
16331 )
16332 .await;
16333 editor
16334 .update(cx, |editor, _| {
16335 editor.clear_tasks();
16336 for (key, mut value) in rows {
16337 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
16338 value.templates.extend(lsp_tasks.templates);
16339 }
16340
16341 editor.insert_tasks(key, value);
16342 }
16343 for (key, value) in lsp_tasks_by_rows {
16344 editor.insert_tasks(key, value);
16345 }
16346 })
16347 .ok();
16348 })
16349 }
16350 fn fetch_runnable_ranges(
16351 snapshot: &DisplaySnapshot,
16352 range: Range<Anchor>,
16353 ) -> Vec<(Range<MultiBufferOffset>, language::RunnableRange)> {
16354 snapshot.buffer_snapshot().runnable_ranges(range).collect()
16355 }
16356
16357 fn runnable_rows(
16358 project: Entity<Project>,
16359 snapshot: DisplaySnapshot,
16360 prefer_lsp: bool,
16361 runnable_ranges: Vec<(Range<MultiBufferOffset>, language::RunnableRange)>,
16362 cx: AsyncWindowContext,
16363 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
16364 cx.spawn(async move |cx| {
16365 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
16366 for (run_range, mut runnable) in runnable_ranges {
16367 let Some(tasks) = cx
16368 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
16369 .ok()
16370 else {
16371 continue;
16372 };
16373 let mut tasks = tasks.await;
16374
16375 if prefer_lsp {
16376 tasks.retain(|(task_kind, _)| {
16377 !matches!(task_kind, TaskSourceKind::Language { .. })
16378 });
16379 }
16380 if tasks.is_empty() {
16381 continue;
16382 }
16383
16384 let point = run_range.start.to_point(&snapshot.buffer_snapshot());
16385 let Some(row) = snapshot
16386 .buffer_snapshot()
16387 .buffer_line_for_row(MultiBufferRow(point.row))
16388 .map(|(_, range)| range.start.row)
16389 else {
16390 continue;
16391 };
16392
16393 let context_range =
16394 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
16395 runnable_rows.push((
16396 (runnable.buffer_id, row),
16397 RunnableTasks {
16398 templates: tasks,
16399 offset: snapshot.buffer_snapshot().anchor_before(run_range.start),
16400 context_range,
16401 column: point.column,
16402 extra_variables: runnable.extra_captures,
16403 },
16404 ));
16405 }
16406 runnable_rows
16407 })
16408 }
16409
16410 fn templates_with_tags(
16411 project: &Entity<Project>,
16412 runnable: &mut Runnable,
16413 cx: &mut App,
16414 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
16415 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
16416 let (worktree_id, file) = project
16417 .buffer_for_id(runnable.buffer, cx)
16418 .and_then(|buffer| buffer.read(cx).file())
16419 .map(|file| (file.worktree_id(cx), file.clone()))
16420 .unzip();
16421
16422 (
16423 project.task_store().read(cx).task_inventory().cloned(),
16424 worktree_id,
16425 file,
16426 )
16427 });
16428
16429 let tags = mem::take(&mut runnable.tags);
16430 let language = runnable.language.clone();
16431 cx.spawn(async move |cx| {
16432 let mut templates_with_tags = Vec::new();
16433 if let Some(inventory) = inventory {
16434 for RunnableTag(tag) in tags {
16435 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
16436 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
16437 }) else {
16438 return templates_with_tags;
16439 };
16440 templates_with_tags.extend(new_tasks.await.into_iter().filter(
16441 move |(_, template)| {
16442 template.tags.iter().any(|source_tag| source_tag == &tag)
16443 },
16444 ));
16445 }
16446 }
16447 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
16448
16449 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
16450 // Strongest source wins; if we have worktree tag binding, prefer that to
16451 // global and language bindings;
16452 // if we have a global binding, prefer that to language binding.
16453 let first_mismatch = templates_with_tags
16454 .iter()
16455 .position(|(tag_source, _)| tag_source != leading_tag_source);
16456 if let Some(index) = first_mismatch {
16457 templates_with_tags.truncate(index);
16458 }
16459 }
16460
16461 templates_with_tags
16462 })
16463 }
16464
16465 pub fn move_to_enclosing_bracket(
16466 &mut self,
16467 _: &MoveToEnclosingBracket,
16468 window: &mut Window,
16469 cx: &mut Context<Self>,
16470 ) {
16471 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16472 self.change_selections(Default::default(), window, cx, |s| {
16473 s.move_offsets_with(|snapshot, selection| {
16474 let Some(enclosing_bracket_ranges) =
16475 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
16476 else {
16477 return;
16478 };
16479
16480 let mut best_length = usize::MAX;
16481 let mut best_inside = false;
16482 let mut best_in_bracket_range = false;
16483 let mut best_destination = None;
16484 for (open, close) in enclosing_bracket_ranges {
16485 let close = close.to_inclusive();
16486 let length = *close.end() - open.start;
16487 let inside = selection.start >= open.end && selection.end <= *close.start();
16488 let in_bracket_range = open.to_inclusive().contains(&selection.head())
16489 || close.contains(&selection.head());
16490
16491 // If best is next to a bracket and current isn't, skip
16492 if !in_bracket_range && best_in_bracket_range {
16493 continue;
16494 }
16495
16496 // Prefer smaller lengths unless best is inside and current isn't
16497 if length > best_length && (best_inside || !inside) {
16498 continue;
16499 }
16500
16501 best_length = length;
16502 best_inside = inside;
16503 best_in_bracket_range = in_bracket_range;
16504 best_destination = Some(
16505 if close.contains(&selection.start) && close.contains(&selection.end) {
16506 if inside { open.end } else { open.start }
16507 } else if inside {
16508 *close.start()
16509 } else {
16510 *close.end()
16511 },
16512 );
16513 }
16514
16515 if let Some(destination) = best_destination {
16516 selection.collapse_to(destination, SelectionGoal::None);
16517 }
16518 })
16519 });
16520 }
16521
16522 pub fn undo_selection(
16523 &mut self,
16524 _: &UndoSelection,
16525 window: &mut Window,
16526 cx: &mut Context<Self>,
16527 ) {
16528 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16529 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
16530 self.selection_history.mode = SelectionHistoryMode::Undoing;
16531 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16532 this.end_selection(window, cx);
16533 this.change_selections(
16534 SelectionEffects::scroll(Autoscroll::newest()),
16535 window,
16536 cx,
16537 |s| s.select_anchors(entry.selections.to_vec()),
16538 );
16539 });
16540 self.selection_history.mode = SelectionHistoryMode::Normal;
16541
16542 self.select_next_state = entry.select_next_state;
16543 self.select_prev_state = entry.select_prev_state;
16544 self.add_selections_state = entry.add_selections_state;
16545 }
16546 }
16547
16548 pub fn redo_selection(
16549 &mut self,
16550 _: &RedoSelection,
16551 window: &mut Window,
16552 cx: &mut Context<Self>,
16553 ) {
16554 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16555 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
16556 self.selection_history.mode = SelectionHistoryMode::Redoing;
16557 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
16558 this.end_selection(window, cx);
16559 this.change_selections(
16560 SelectionEffects::scroll(Autoscroll::newest()),
16561 window,
16562 cx,
16563 |s| s.select_anchors(entry.selections.to_vec()),
16564 );
16565 });
16566 self.selection_history.mode = SelectionHistoryMode::Normal;
16567
16568 self.select_next_state = entry.select_next_state;
16569 self.select_prev_state = entry.select_prev_state;
16570 self.add_selections_state = entry.add_selections_state;
16571 }
16572 }
16573
16574 pub fn expand_excerpts(
16575 &mut self,
16576 action: &ExpandExcerpts,
16577 _: &mut Window,
16578 cx: &mut Context<Self>,
16579 ) {
16580 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
16581 }
16582
16583 pub fn expand_excerpts_down(
16584 &mut self,
16585 action: &ExpandExcerptsDown,
16586 _: &mut Window,
16587 cx: &mut Context<Self>,
16588 ) {
16589 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16590 }
16591
16592 pub fn expand_excerpts_up(
16593 &mut self,
16594 action: &ExpandExcerptsUp,
16595 _: &mut Window,
16596 cx: &mut Context<Self>,
16597 ) {
16598 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16599 }
16600
16601 pub fn expand_excerpts_for_direction(
16602 &mut self,
16603 lines: u32,
16604 direction: ExpandExcerptDirection,
16605
16606 cx: &mut Context<Self>,
16607 ) {
16608 let selections = self.selections.disjoint_anchors_arc();
16609
16610 let lines = if lines == 0 {
16611 EditorSettings::get_global(cx).expand_excerpt_lines
16612 } else {
16613 lines
16614 };
16615
16616 self.buffer.update(cx, |buffer, cx| {
16617 let snapshot = buffer.snapshot(cx);
16618 let mut excerpt_ids = selections
16619 .iter()
16620 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16621 .collect::<Vec<_>>();
16622 excerpt_ids.sort();
16623 excerpt_ids.dedup();
16624 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16625 })
16626 }
16627
16628 pub fn expand_excerpt(
16629 &mut self,
16630 excerpt: ExcerptId,
16631 direction: ExpandExcerptDirection,
16632 window: &mut Window,
16633 cx: &mut Context<Self>,
16634 ) {
16635 let current_scroll_position = self.scroll_position(cx);
16636 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16637 let mut scroll = None;
16638
16639 if direction == ExpandExcerptDirection::Down {
16640 let multi_buffer = self.buffer.read(cx);
16641 let snapshot = multi_buffer.snapshot(cx);
16642 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16643 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16644 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16645 {
16646 let buffer_snapshot = buffer.read(cx).snapshot();
16647 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16648 let last_row = buffer_snapshot.max_point().row;
16649 let lines_below = last_row.saturating_sub(excerpt_end_row);
16650 if lines_below >= lines_to_expand {
16651 scroll = Some(
16652 current_scroll_position
16653 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16654 );
16655 }
16656 }
16657 }
16658 if direction == ExpandExcerptDirection::Up
16659 && self
16660 .buffer
16661 .read(cx)
16662 .snapshot(cx)
16663 .excerpt_before(excerpt)
16664 .is_none()
16665 {
16666 scroll = Some(current_scroll_position);
16667 }
16668
16669 self.buffer.update(cx, |buffer, cx| {
16670 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16671 });
16672
16673 if let Some(new_scroll_position) = scroll {
16674 self.set_scroll_position(new_scroll_position, window, cx);
16675 }
16676 }
16677
16678 pub fn go_to_singleton_buffer_point(
16679 &mut self,
16680 point: Point,
16681 window: &mut Window,
16682 cx: &mut Context<Self>,
16683 ) {
16684 self.go_to_singleton_buffer_range(point..point, window, cx);
16685 }
16686
16687 pub fn go_to_singleton_buffer_range(
16688 &mut self,
16689 range: Range<Point>,
16690 window: &mut Window,
16691 cx: &mut Context<Self>,
16692 ) {
16693 let multibuffer = self.buffer().read(cx);
16694 let Some(buffer) = multibuffer.as_singleton() else {
16695 return;
16696 };
16697 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16698 return;
16699 };
16700 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16701 return;
16702 };
16703 self.change_selections(
16704 SelectionEffects::default().nav_history(true),
16705 window,
16706 cx,
16707 |s| s.select_anchor_ranges([start..end]),
16708 );
16709 }
16710
16711 pub fn go_to_diagnostic(
16712 &mut self,
16713 action: &GoToDiagnostic,
16714 window: &mut Window,
16715 cx: &mut Context<Self>,
16716 ) {
16717 if !self.diagnostics_enabled() {
16718 return;
16719 }
16720 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16721 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16722 }
16723
16724 pub fn go_to_prev_diagnostic(
16725 &mut self,
16726 action: &GoToPreviousDiagnostic,
16727 window: &mut Window,
16728 cx: &mut Context<Self>,
16729 ) {
16730 if !self.diagnostics_enabled() {
16731 return;
16732 }
16733 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16734 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16735 }
16736
16737 pub fn go_to_diagnostic_impl(
16738 &mut self,
16739 direction: Direction,
16740 severity: GoToDiagnosticSeverityFilter,
16741 window: &mut Window,
16742 cx: &mut Context<Self>,
16743 ) {
16744 let buffer = self.buffer.read(cx).snapshot(cx);
16745 let selection = self
16746 .selections
16747 .newest::<MultiBufferOffset>(&self.display_snapshot(cx));
16748
16749 let mut active_group_id = None;
16750 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16751 && active_group.active_range.start.to_offset(&buffer) == selection.start
16752 {
16753 active_group_id = Some(active_group.group_id);
16754 }
16755
16756 fn filtered<'a>(
16757 severity: GoToDiagnosticSeverityFilter,
16758 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>>,
16759 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, MultiBufferOffset>> {
16760 diagnostics
16761 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16762 .filter(|entry| entry.range.start != entry.range.end)
16763 .filter(|entry| !entry.diagnostic.is_unnecessary)
16764 }
16765
16766 let before = filtered(
16767 severity,
16768 buffer
16769 .diagnostics_in_range(MultiBufferOffset(0)..selection.start)
16770 .filter(|entry| entry.range.start <= selection.start),
16771 );
16772 let after = filtered(
16773 severity,
16774 buffer
16775 .diagnostics_in_range(selection.start..buffer.len())
16776 .filter(|entry| entry.range.start >= selection.start),
16777 );
16778
16779 let mut found: Option<DiagnosticEntryRef<MultiBufferOffset>> = None;
16780 if direction == Direction::Prev {
16781 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16782 {
16783 for diagnostic in prev_diagnostics.into_iter().rev() {
16784 if diagnostic.range.start != selection.start
16785 || active_group_id
16786 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16787 {
16788 found = Some(diagnostic);
16789 break 'outer;
16790 }
16791 }
16792 }
16793 } else {
16794 for diagnostic in after.chain(before) {
16795 if diagnostic.range.start != selection.start
16796 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16797 {
16798 found = Some(diagnostic);
16799 break;
16800 }
16801 }
16802 }
16803 let Some(next_diagnostic) = found else {
16804 return;
16805 };
16806
16807 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16808 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16809 return;
16810 };
16811 let snapshot = self.snapshot(window, cx);
16812 if snapshot.intersects_fold(next_diagnostic.range.start) {
16813 self.unfold_ranges(
16814 std::slice::from_ref(&next_diagnostic.range),
16815 true,
16816 false,
16817 cx,
16818 );
16819 }
16820 self.change_selections(Default::default(), window, cx, |s| {
16821 s.select_ranges(vec![
16822 next_diagnostic.range.start..next_diagnostic.range.start,
16823 ])
16824 });
16825 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16826 self.refresh_edit_prediction(false, true, window, cx);
16827 }
16828
16829 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16830 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16831 let snapshot = self.snapshot(window, cx);
16832 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16833 self.go_to_hunk_before_or_after_position(
16834 &snapshot,
16835 selection.head(),
16836 Direction::Next,
16837 window,
16838 cx,
16839 );
16840 }
16841
16842 pub fn go_to_hunk_before_or_after_position(
16843 &mut self,
16844 snapshot: &EditorSnapshot,
16845 position: Point,
16846 direction: Direction,
16847 window: &mut Window,
16848 cx: &mut Context<Editor>,
16849 ) {
16850 let row = if direction == Direction::Next {
16851 self.hunk_after_position(snapshot, position)
16852 .map(|hunk| hunk.row_range.start)
16853 } else {
16854 self.hunk_before_position(snapshot, position)
16855 };
16856
16857 if let Some(row) = row {
16858 let destination = Point::new(row.0, 0);
16859 let autoscroll = Autoscroll::center();
16860
16861 self.unfold_ranges(&[destination..destination], false, false, cx);
16862 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16863 s.select_ranges([destination..destination]);
16864 });
16865 }
16866 }
16867
16868 fn hunk_after_position(
16869 &mut self,
16870 snapshot: &EditorSnapshot,
16871 position: Point,
16872 ) -> Option<MultiBufferDiffHunk> {
16873 snapshot
16874 .buffer_snapshot()
16875 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16876 .find(|hunk| hunk.row_range.start.0 > position.row)
16877 .or_else(|| {
16878 snapshot
16879 .buffer_snapshot()
16880 .diff_hunks_in_range(Point::zero()..position)
16881 .find(|hunk| hunk.row_range.end.0 < position.row)
16882 })
16883 }
16884
16885 fn go_to_prev_hunk(
16886 &mut self,
16887 _: &GoToPreviousHunk,
16888 window: &mut Window,
16889 cx: &mut Context<Self>,
16890 ) {
16891 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16892 let snapshot = self.snapshot(window, cx);
16893 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16894 self.go_to_hunk_before_or_after_position(
16895 &snapshot,
16896 selection.head(),
16897 Direction::Prev,
16898 window,
16899 cx,
16900 );
16901 }
16902
16903 fn hunk_before_position(
16904 &mut self,
16905 snapshot: &EditorSnapshot,
16906 position: Point,
16907 ) -> Option<MultiBufferRow> {
16908 snapshot
16909 .buffer_snapshot()
16910 .diff_hunk_before(position)
16911 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16912 }
16913
16914 fn go_to_next_change(
16915 &mut self,
16916 _: &GoToNextChange,
16917 window: &mut Window,
16918 cx: &mut Context<Self>,
16919 ) {
16920 if let Some(selections) = self
16921 .change_list
16922 .next_change(1, Direction::Next)
16923 .map(|s| s.to_vec())
16924 {
16925 self.change_selections(Default::default(), window, cx, |s| {
16926 let map = s.display_snapshot();
16927 s.select_display_ranges(selections.iter().map(|a| {
16928 let point = a.to_display_point(&map);
16929 point..point
16930 }))
16931 })
16932 }
16933 }
16934
16935 fn go_to_previous_change(
16936 &mut self,
16937 _: &GoToPreviousChange,
16938 window: &mut Window,
16939 cx: &mut Context<Self>,
16940 ) {
16941 if let Some(selections) = self
16942 .change_list
16943 .next_change(1, Direction::Prev)
16944 .map(|s| s.to_vec())
16945 {
16946 self.change_selections(Default::default(), window, cx, |s| {
16947 let map = s.display_snapshot();
16948 s.select_display_ranges(selections.iter().map(|a| {
16949 let point = a.to_display_point(&map);
16950 point..point
16951 }))
16952 })
16953 }
16954 }
16955
16956 pub fn go_to_next_document_highlight(
16957 &mut self,
16958 _: &GoToNextDocumentHighlight,
16959 window: &mut Window,
16960 cx: &mut Context<Self>,
16961 ) {
16962 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16963 }
16964
16965 pub fn go_to_prev_document_highlight(
16966 &mut self,
16967 _: &GoToPreviousDocumentHighlight,
16968 window: &mut Window,
16969 cx: &mut Context<Self>,
16970 ) {
16971 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16972 }
16973
16974 pub fn go_to_document_highlight_before_or_after_position(
16975 &mut self,
16976 direction: Direction,
16977 window: &mut Window,
16978 cx: &mut Context<Editor>,
16979 ) {
16980 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16981 let snapshot = self.snapshot(window, cx);
16982 let buffer = &snapshot.buffer_snapshot();
16983 let position = self
16984 .selections
16985 .newest::<Point>(&snapshot.display_snapshot)
16986 .head();
16987 let anchor_position = buffer.anchor_after(position);
16988
16989 // Get all document highlights (both read and write)
16990 let mut all_highlights = Vec::new();
16991
16992 if let Some((_, read_highlights)) = self
16993 .background_highlights
16994 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16995 {
16996 all_highlights.extend(read_highlights.iter());
16997 }
16998
16999 if let Some((_, write_highlights)) = self
17000 .background_highlights
17001 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
17002 {
17003 all_highlights.extend(write_highlights.iter());
17004 }
17005
17006 if all_highlights.is_empty() {
17007 return;
17008 }
17009
17010 // Sort highlights by position
17011 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
17012
17013 let target_highlight = match direction {
17014 Direction::Next => {
17015 // Find the first highlight after the current position
17016 all_highlights
17017 .iter()
17018 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
17019 }
17020 Direction::Prev => {
17021 // Find the last highlight before the current position
17022 all_highlights
17023 .iter()
17024 .rev()
17025 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
17026 }
17027 };
17028
17029 if let Some(highlight) = target_highlight {
17030 let destination = highlight.start.to_point(buffer);
17031 let autoscroll = Autoscroll::center();
17032
17033 self.unfold_ranges(&[destination..destination], false, false, cx);
17034 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
17035 s.select_ranges([destination..destination]);
17036 });
17037 }
17038 }
17039
17040 fn go_to_line<T: 'static>(
17041 &mut self,
17042 position: Anchor,
17043 highlight_color: Option<Hsla>,
17044 window: &mut Window,
17045 cx: &mut Context<Self>,
17046 ) {
17047 let snapshot = self.snapshot(window, cx).display_snapshot;
17048 let position = position.to_point(&snapshot.buffer_snapshot());
17049 let start = snapshot
17050 .buffer_snapshot()
17051 .clip_point(Point::new(position.row, 0), Bias::Left);
17052 let end = start + Point::new(1, 0);
17053 let start = snapshot.buffer_snapshot().anchor_before(start);
17054 let end = snapshot.buffer_snapshot().anchor_before(end);
17055
17056 self.highlight_rows::<T>(
17057 start..end,
17058 highlight_color
17059 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
17060 Default::default(),
17061 cx,
17062 );
17063
17064 if self.buffer.read(cx).is_singleton() {
17065 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
17066 }
17067 }
17068
17069 pub fn go_to_definition(
17070 &mut self,
17071 _: &GoToDefinition,
17072 window: &mut Window,
17073 cx: &mut Context<Self>,
17074 ) -> Task<Result<Navigated>> {
17075 let definition =
17076 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
17077 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
17078 cx.spawn_in(window, async move |editor, cx| {
17079 if definition.await? == Navigated::Yes {
17080 return Ok(Navigated::Yes);
17081 }
17082 match fallback_strategy {
17083 GoToDefinitionFallback::None => Ok(Navigated::No),
17084 GoToDefinitionFallback::FindAllReferences => {
17085 match editor.update_in(cx, |editor, window, cx| {
17086 editor.find_all_references(&FindAllReferences::default(), window, cx)
17087 })? {
17088 Some(references) => references.await,
17089 None => Ok(Navigated::No),
17090 }
17091 }
17092 }
17093 })
17094 }
17095
17096 pub fn go_to_declaration(
17097 &mut self,
17098 _: &GoToDeclaration,
17099 window: &mut Window,
17100 cx: &mut Context<Self>,
17101 ) -> Task<Result<Navigated>> {
17102 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
17103 }
17104
17105 pub fn go_to_declaration_split(
17106 &mut self,
17107 _: &GoToDeclaration,
17108 window: &mut Window,
17109 cx: &mut Context<Self>,
17110 ) -> Task<Result<Navigated>> {
17111 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
17112 }
17113
17114 pub fn go_to_implementation(
17115 &mut self,
17116 _: &GoToImplementation,
17117 window: &mut Window,
17118 cx: &mut Context<Self>,
17119 ) -> Task<Result<Navigated>> {
17120 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
17121 }
17122
17123 pub fn go_to_implementation_split(
17124 &mut self,
17125 _: &GoToImplementationSplit,
17126 window: &mut Window,
17127 cx: &mut Context<Self>,
17128 ) -> Task<Result<Navigated>> {
17129 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
17130 }
17131
17132 pub fn go_to_type_definition(
17133 &mut self,
17134 _: &GoToTypeDefinition,
17135 window: &mut Window,
17136 cx: &mut Context<Self>,
17137 ) -> Task<Result<Navigated>> {
17138 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
17139 }
17140
17141 pub fn go_to_definition_split(
17142 &mut self,
17143 _: &GoToDefinitionSplit,
17144 window: &mut Window,
17145 cx: &mut Context<Self>,
17146 ) -> Task<Result<Navigated>> {
17147 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
17148 }
17149
17150 pub fn go_to_type_definition_split(
17151 &mut self,
17152 _: &GoToTypeDefinitionSplit,
17153 window: &mut Window,
17154 cx: &mut Context<Self>,
17155 ) -> Task<Result<Navigated>> {
17156 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
17157 }
17158
17159 fn go_to_definition_of_kind(
17160 &mut self,
17161 kind: GotoDefinitionKind,
17162 split: bool,
17163 window: &mut Window,
17164 cx: &mut Context<Self>,
17165 ) -> Task<Result<Navigated>> {
17166 let Some(provider) = self.semantics_provider.clone() else {
17167 return Task::ready(Ok(Navigated::No));
17168 };
17169 let head = self
17170 .selections
17171 .newest::<MultiBufferOffset>(&self.display_snapshot(cx))
17172 .head();
17173 let buffer = self.buffer.read(cx);
17174 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
17175 return Task::ready(Ok(Navigated::No));
17176 };
17177 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
17178 return Task::ready(Ok(Navigated::No));
17179 };
17180
17181 cx.spawn_in(window, async move |editor, cx| {
17182 let Some(definitions) = definitions.await? else {
17183 return Ok(Navigated::No);
17184 };
17185 let navigated = editor
17186 .update_in(cx, |editor, window, cx| {
17187 editor.navigate_to_hover_links(
17188 Some(kind),
17189 definitions
17190 .into_iter()
17191 .filter(|location| {
17192 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
17193 })
17194 .map(HoverLink::Text)
17195 .collect::<Vec<_>>(),
17196 split,
17197 window,
17198 cx,
17199 )
17200 })?
17201 .await?;
17202 anyhow::Ok(navigated)
17203 })
17204 }
17205
17206 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
17207 let selection = self.selections.newest_anchor();
17208 let head = selection.head();
17209 let tail = selection.tail();
17210
17211 let Some((buffer, start_position)) =
17212 self.buffer.read(cx).text_anchor_for_position(head, cx)
17213 else {
17214 return;
17215 };
17216
17217 let end_position = if head != tail {
17218 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
17219 return;
17220 };
17221 Some(pos)
17222 } else {
17223 None
17224 };
17225
17226 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
17227 let url = if let Some(end_pos) = end_position {
17228 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
17229 } else {
17230 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
17231 };
17232
17233 if let Some(url) = url {
17234 cx.update(|window, cx| {
17235 if parse_zed_link(&url, cx).is_some() {
17236 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
17237 } else {
17238 cx.open_url(&url);
17239 }
17240 })?;
17241 }
17242
17243 anyhow::Ok(())
17244 });
17245
17246 url_finder.detach();
17247 }
17248
17249 pub fn open_selected_filename(
17250 &mut self,
17251 _: &OpenSelectedFilename,
17252 window: &mut Window,
17253 cx: &mut Context<Self>,
17254 ) {
17255 let Some(workspace) = self.workspace() else {
17256 return;
17257 };
17258
17259 let position = self.selections.newest_anchor().head();
17260
17261 let Some((buffer, buffer_position)) =
17262 self.buffer.read(cx).text_anchor_for_position(position, cx)
17263 else {
17264 return;
17265 };
17266
17267 let project = self.project.clone();
17268
17269 cx.spawn_in(window, async move |_, cx| {
17270 let result = find_file(&buffer, project, buffer_position, cx).await;
17271
17272 if let Some((_, path)) = result {
17273 workspace
17274 .update_in(cx, |workspace, window, cx| {
17275 workspace.open_resolved_path(path, window, cx)
17276 })?
17277 .await?;
17278 }
17279 anyhow::Ok(())
17280 })
17281 .detach();
17282 }
17283
17284 pub(crate) fn navigate_to_hover_links(
17285 &mut self,
17286 kind: Option<GotoDefinitionKind>,
17287 definitions: Vec<HoverLink>,
17288 split: bool,
17289 window: &mut Window,
17290 cx: &mut Context<Editor>,
17291 ) -> Task<Result<Navigated>> {
17292 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
17293 let mut first_url_or_file = None;
17294 let definitions: Vec<_> = definitions
17295 .into_iter()
17296 .filter_map(|def| match def {
17297 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
17298 HoverLink::InlayHint(lsp_location, server_id) => {
17299 let computation =
17300 self.compute_target_location(lsp_location, server_id, window, cx);
17301 Some(cx.background_spawn(computation))
17302 }
17303 HoverLink::Url(url) => {
17304 first_url_or_file = Some(Either::Left(url));
17305 None
17306 }
17307 HoverLink::File(path) => {
17308 first_url_or_file = Some(Either::Right(path));
17309 None
17310 }
17311 })
17312 .collect();
17313
17314 let workspace = self.workspace();
17315
17316 cx.spawn_in(window, async move |editor, cx| {
17317 let locations: Vec<Location> = future::join_all(definitions)
17318 .await
17319 .into_iter()
17320 .filter_map(|location| location.transpose())
17321 .collect::<Result<_>>()
17322 .context("location tasks")?;
17323 let mut locations = cx.update(|_, cx| {
17324 locations
17325 .into_iter()
17326 .map(|location| {
17327 let buffer = location.buffer.read(cx);
17328 (location.buffer, location.range.to_point(buffer))
17329 })
17330 .into_group_map()
17331 })?;
17332 let mut num_locations = 0;
17333 for ranges in locations.values_mut() {
17334 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17335 ranges.dedup();
17336 num_locations += ranges.len();
17337 }
17338
17339 if num_locations > 1 {
17340 let tab_kind = match kind {
17341 Some(GotoDefinitionKind::Implementation) => "Implementations",
17342 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
17343 Some(GotoDefinitionKind::Declaration) => "Declarations",
17344 Some(GotoDefinitionKind::Type) => "Types",
17345 };
17346 let title = editor
17347 .update_in(cx, |_, _, cx| {
17348 let target = locations
17349 .iter()
17350 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17351 .map(|(buffer, location)| {
17352 buffer
17353 .read(cx)
17354 .text_for_range(location.clone())
17355 .collect::<String>()
17356 })
17357 .filter(|text| !text.contains('\n'))
17358 .unique()
17359 .take(3)
17360 .join(", ");
17361 if target.is_empty() {
17362 tab_kind.to_owned()
17363 } else {
17364 format!("{tab_kind} for {target}")
17365 }
17366 })
17367 .context("buffer title")?;
17368
17369 let Some(workspace) = workspace else {
17370 return Ok(Navigated::No);
17371 };
17372
17373 let opened = workspace
17374 .update_in(cx, |workspace, window, cx| {
17375 let allow_preview = PreviewTabsSettings::get_global(cx)
17376 .enable_preview_multibuffer_from_code_navigation;
17377 Self::open_locations_in_multibuffer(
17378 workspace,
17379 locations,
17380 title,
17381 split,
17382 allow_preview,
17383 MultibufferSelectionMode::First,
17384 window,
17385 cx,
17386 )
17387 })
17388 .is_ok();
17389
17390 anyhow::Ok(Navigated::from_bool(opened))
17391 } else if num_locations == 0 {
17392 // If there is one url or file, open it directly
17393 match first_url_or_file {
17394 Some(Either::Left(url)) => {
17395 cx.update(|_, cx| cx.open_url(&url))?;
17396 Ok(Navigated::Yes)
17397 }
17398 Some(Either::Right(path)) => {
17399 // TODO(andrew): respect preview tab settings
17400 // `enable_keep_preview_on_code_navigation` and
17401 // `enable_preview_file_from_code_navigation`
17402 let Some(workspace) = workspace else {
17403 return Ok(Navigated::No);
17404 };
17405 workspace
17406 .update_in(cx, |workspace, window, cx| {
17407 workspace.open_resolved_path(path, window, cx)
17408 })?
17409 .await?;
17410 Ok(Navigated::Yes)
17411 }
17412 None => Ok(Navigated::No),
17413 }
17414 } else {
17415 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17416 let target_range = target_ranges.first().unwrap().clone();
17417
17418 editor.update_in(cx, |editor, window, cx| {
17419 let range = target_range.to_point(target_buffer.read(cx));
17420 let range = editor.range_for_match(&range);
17421 let range = collapse_multiline_range(range);
17422
17423 if !split
17424 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
17425 {
17426 editor.go_to_singleton_buffer_range(range, window, cx);
17427 } else {
17428 let Some(workspace) = workspace else {
17429 return Navigated::No;
17430 };
17431 let pane = workspace.read(cx).active_pane().clone();
17432 window.defer(cx, move |window, cx| {
17433 let target_editor: Entity<Self> =
17434 workspace.update(cx, |workspace, cx| {
17435 let pane = if split {
17436 workspace.adjacent_pane(window, cx)
17437 } else {
17438 workspace.active_pane().clone()
17439 };
17440
17441 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17442 let keep_old_preview = preview_tabs_settings
17443 .enable_keep_preview_on_code_navigation;
17444 let allow_new_preview = preview_tabs_settings
17445 .enable_preview_file_from_code_navigation;
17446
17447 workspace.open_project_item(
17448 pane,
17449 target_buffer.clone(),
17450 true,
17451 true,
17452 keep_old_preview,
17453 allow_new_preview,
17454 window,
17455 cx,
17456 )
17457 });
17458 target_editor.update(cx, |target_editor, cx| {
17459 // When selecting a definition in a different buffer, disable the nav history
17460 // to avoid creating a history entry at the previous cursor location.
17461 pane.update(cx, |pane, _| pane.disable_history());
17462 target_editor.go_to_singleton_buffer_range(range, window, cx);
17463 pane.update(cx, |pane, _| pane.enable_history());
17464 });
17465 });
17466 }
17467 Navigated::Yes
17468 })
17469 }
17470 })
17471 }
17472
17473 fn compute_target_location(
17474 &self,
17475 lsp_location: lsp::Location,
17476 server_id: LanguageServerId,
17477 window: &mut Window,
17478 cx: &mut Context<Self>,
17479 ) -> Task<anyhow::Result<Option<Location>>> {
17480 let Some(project) = self.project.clone() else {
17481 return Task::ready(Ok(None));
17482 };
17483
17484 cx.spawn_in(window, async move |editor, cx| {
17485 let location_task = editor.update(cx, |_, cx| {
17486 project.update(cx, |project, cx| {
17487 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
17488 })
17489 })?;
17490 let location = Some({
17491 let target_buffer_handle = location_task.await.context("open local buffer")?;
17492 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
17493 let target_start = target_buffer
17494 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
17495 let target_end = target_buffer
17496 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
17497 target_buffer.anchor_after(target_start)
17498 ..target_buffer.anchor_before(target_end)
17499 })?;
17500 Location {
17501 buffer: target_buffer_handle,
17502 range,
17503 }
17504 });
17505 Ok(location)
17506 })
17507 }
17508
17509 fn go_to_next_reference(
17510 &mut self,
17511 _: &GoToNextReference,
17512 window: &mut Window,
17513 cx: &mut Context<Self>,
17514 ) {
17515 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
17516 if let Some(task) = task {
17517 task.detach();
17518 };
17519 }
17520
17521 fn go_to_prev_reference(
17522 &mut self,
17523 _: &GoToPreviousReference,
17524 window: &mut Window,
17525 cx: &mut Context<Self>,
17526 ) {
17527 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
17528 if let Some(task) = task {
17529 task.detach();
17530 };
17531 }
17532
17533 pub fn go_to_reference_before_or_after_position(
17534 &mut self,
17535 direction: Direction,
17536 count: usize,
17537 window: &mut Window,
17538 cx: &mut Context<Self>,
17539 ) -> Option<Task<Result<()>>> {
17540 let selection = self.selections.newest_anchor();
17541 let head = selection.head();
17542
17543 let multi_buffer = self.buffer.read(cx);
17544
17545 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
17546 let workspace = self.workspace()?;
17547 let project = workspace.read(cx).project().clone();
17548 let references =
17549 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
17550 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
17551 let Some(locations) = references.await? else {
17552 return Ok(());
17553 };
17554
17555 if locations.is_empty() {
17556 // totally normal - the cursor may be on something which is not
17557 // a symbol (e.g. a keyword)
17558 log::info!("no references found under cursor");
17559 return Ok(());
17560 }
17561
17562 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
17563
17564 let (locations, current_location_index) =
17565 multi_buffer.update(cx, |multi_buffer, cx| {
17566 let mut locations = locations
17567 .into_iter()
17568 .filter_map(|loc| {
17569 let start = multi_buffer.buffer_anchor_to_anchor(
17570 &loc.buffer,
17571 loc.range.start,
17572 cx,
17573 )?;
17574 let end = multi_buffer.buffer_anchor_to_anchor(
17575 &loc.buffer,
17576 loc.range.end,
17577 cx,
17578 )?;
17579 Some(start..end)
17580 })
17581 .collect::<Vec<_>>();
17582
17583 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17584 // There is an O(n) implementation, but given this list will be
17585 // small (usually <100 items), the extra O(log(n)) factor isn't
17586 // worth the (surprisingly large amount of) extra complexity.
17587 locations
17588 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
17589
17590 let head_offset = head.to_offset(&multi_buffer_snapshot);
17591
17592 let current_location_index = locations.iter().position(|loc| {
17593 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
17594 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
17595 });
17596
17597 (locations, current_location_index)
17598 })?;
17599
17600 let Some(current_location_index) = current_location_index else {
17601 // This indicates something has gone wrong, because we already
17602 // handle the "no references" case above
17603 log::error!(
17604 "failed to find current reference under cursor. Total references: {}",
17605 locations.len()
17606 );
17607 return Ok(());
17608 };
17609
17610 let destination_location_index = match direction {
17611 Direction::Next => (current_location_index + count) % locations.len(),
17612 Direction::Prev => {
17613 (current_location_index + locations.len() - count % locations.len())
17614 % locations.len()
17615 }
17616 };
17617
17618 // TODO(cameron): is this needed?
17619 // the thinking is to avoid "jumping to the current location" (avoid
17620 // polluting "jumplist" in vim terms)
17621 if current_location_index == destination_location_index {
17622 return Ok(());
17623 }
17624
17625 let Range { start, end } = locations[destination_location_index];
17626
17627 editor.update_in(cx, |editor, window, cx| {
17628 let effects = SelectionEffects::default();
17629
17630 editor.unfold_ranges(&[start..end], false, false, cx);
17631 editor.change_selections(effects, window, cx, |s| {
17632 s.select_ranges([start..start]);
17633 });
17634 })?;
17635
17636 Ok(())
17637 }))
17638 }
17639
17640 pub fn find_all_references(
17641 &mut self,
17642 action: &FindAllReferences,
17643 window: &mut Window,
17644 cx: &mut Context<Self>,
17645 ) -> Option<Task<Result<Navigated>>> {
17646 let always_open_multibuffer = action.always_open_multibuffer;
17647 let selection = self.selections.newest_anchor();
17648 let multi_buffer = self.buffer.read(cx);
17649 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17650 let selection_offset = selection.map(|anchor| anchor.to_offset(&multi_buffer_snapshot));
17651 let selection_point = selection.map(|anchor| anchor.to_point(&multi_buffer_snapshot));
17652 let head = selection_offset.head();
17653
17654 let head_anchor = multi_buffer_snapshot.anchor_at(
17655 head,
17656 if head < selection_offset.tail() {
17657 Bias::Right
17658 } else {
17659 Bias::Left
17660 },
17661 );
17662
17663 match self
17664 .find_all_references_task_sources
17665 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17666 {
17667 Ok(_) => {
17668 log::info!(
17669 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17670 );
17671 return None;
17672 }
17673 Err(i) => {
17674 self.find_all_references_task_sources.insert(i, head_anchor);
17675 }
17676 }
17677
17678 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17679 let workspace = self.workspace()?;
17680 let project = workspace.read(cx).project().clone();
17681 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17682 Some(cx.spawn_in(window, async move |editor, cx| {
17683 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17684 if let Ok(i) = editor
17685 .find_all_references_task_sources
17686 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17687 {
17688 editor.find_all_references_task_sources.remove(i);
17689 }
17690 });
17691
17692 let Some(locations) = references.await? else {
17693 return anyhow::Ok(Navigated::No);
17694 };
17695 let mut locations = cx.update(|_, cx| {
17696 locations
17697 .into_iter()
17698 .map(|location| {
17699 let buffer = location.buffer.read(cx);
17700 (location.buffer, location.range.to_point(buffer))
17701 })
17702 // if special-casing the single-match case, remove ranges
17703 // that intersect current selection
17704 .filter(|(location_buffer, location)| {
17705 if always_open_multibuffer || &buffer != location_buffer {
17706 return true;
17707 }
17708
17709 !location.contains_inclusive(&selection_point.range())
17710 })
17711 .into_group_map()
17712 })?;
17713 if locations.is_empty() {
17714 return anyhow::Ok(Navigated::No);
17715 }
17716 for ranges in locations.values_mut() {
17717 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17718 ranges.dedup();
17719 }
17720 let mut num_locations = 0;
17721 for ranges in locations.values_mut() {
17722 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17723 ranges.dedup();
17724 num_locations += ranges.len();
17725 }
17726
17727 if num_locations == 1 && !always_open_multibuffer {
17728 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
17729 let target_range = target_ranges.first().unwrap().clone();
17730
17731 return editor.update_in(cx, |editor, window, cx| {
17732 let range = target_range.to_point(target_buffer.read(cx));
17733 let range = editor.range_for_match(&range);
17734 let range = range.start..range.start;
17735
17736 if Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref() {
17737 editor.go_to_singleton_buffer_range(range, window, cx);
17738 } else {
17739 let pane = workspace.read(cx).active_pane().clone();
17740 window.defer(cx, move |window, cx| {
17741 let target_editor: Entity<Self> =
17742 workspace.update(cx, |workspace, cx| {
17743 let pane = workspace.active_pane().clone();
17744
17745 let preview_tabs_settings = PreviewTabsSettings::get_global(cx);
17746 let keep_old_preview = preview_tabs_settings
17747 .enable_keep_preview_on_code_navigation;
17748 let allow_new_preview = preview_tabs_settings
17749 .enable_preview_file_from_code_navigation;
17750
17751 workspace.open_project_item(
17752 pane,
17753 target_buffer.clone(),
17754 true,
17755 true,
17756 keep_old_preview,
17757 allow_new_preview,
17758 window,
17759 cx,
17760 )
17761 });
17762 target_editor.update(cx, |target_editor, cx| {
17763 // When selecting a definition in a different buffer, disable the nav history
17764 // to avoid creating a history entry at the previous cursor location.
17765 pane.update(cx, |pane, _| pane.disable_history());
17766 target_editor.go_to_singleton_buffer_range(range, window, cx);
17767 pane.update(cx, |pane, _| pane.enable_history());
17768 });
17769 });
17770 }
17771 Navigated::No
17772 });
17773 }
17774
17775 workspace.update_in(cx, |workspace, window, cx| {
17776 let target = locations
17777 .iter()
17778 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17779 .map(|(buffer, location)| {
17780 buffer
17781 .read(cx)
17782 .text_for_range(location.clone())
17783 .collect::<String>()
17784 })
17785 .filter(|text| !text.contains('\n'))
17786 .unique()
17787 .take(3)
17788 .join(", ");
17789 let title = if target.is_empty() {
17790 "References".to_owned()
17791 } else {
17792 format!("References to {target}")
17793 };
17794 let allow_preview = PreviewTabsSettings::get_global(cx)
17795 .enable_preview_multibuffer_from_code_navigation;
17796 Self::open_locations_in_multibuffer(
17797 workspace,
17798 locations,
17799 title,
17800 false,
17801 allow_preview,
17802 MultibufferSelectionMode::First,
17803 window,
17804 cx,
17805 );
17806 Navigated::Yes
17807 })
17808 }))
17809 }
17810
17811 /// Opens a multibuffer with the given project locations in it.
17812 pub fn open_locations_in_multibuffer(
17813 workspace: &mut Workspace,
17814 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17815 title: String,
17816 split: bool,
17817 allow_preview: bool,
17818 multibuffer_selection_mode: MultibufferSelectionMode,
17819 window: &mut Window,
17820 cx: &mut Context<Workspace>,
17821 ) {
17822 if locations.is_empty() {
17823 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17824 return;
17825 }
17826
17827 let capability = workspace.project().read(cx).capability();
17828 let mut ranges = <Vec<Range<Anchor>>>::new();
17829
17830 // a key to find existing multibuffer editors with the same set of locations
17831 // to prevent us from opening more and more multibuffer tabs for searches and the like
17832 let mut key = (title.clone(), vec![]);
17833 let excerpt_buffer = cx.new(|cx| {
17834 let key = &mut key.1;
17835 let mut multibuffer = MultiBuffer::new(capability);
17836 for (buffer, mut ranges_for_buffer) in locations {
17837 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17838 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17839 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17840 PathKey::for_buffer(&buffer, cx),
17841 buffer.clone(),
17842 ranges_for_buffer,
17843 multibuffer_context_lines(cx),
17844 cx,
17845 );
17846 ranges.extend(new_ranges)
17847 }
17848
17849 multibuffer.with_title(title)
17850 });
17851 let existing = workspace.active_pane().update(cx, |pane, cx| {
17852 pane.items()
17853 .filter_map(|item| item.downcast::<Editor>())
17854 .find(|editor| {
17855 editor
17856 .read(cx)
17857 .lookup_key
17858 .as_ref()
17859 .and_then(|it| {
17860 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17861 })
17862 .is_some_and(|it| *it == key)
17863 })
17864 });
17865 let was_existing = existing.is_some();
17866 let editor = existing.unwrap_or_else(|| {
17867 cx.new(|cx| {
17868 let mut editor = Editor::for_multibuffer(
17869 excerpt_buffer,
17870 Some(workspace.project().clone()),
17871 window,
17872 cx,
17873 );
17874 editor.lookup_key = Some(Box::new(key));
17875 editor
17876 })
17877 });
17878 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17879 MultibufferSelectionMode::First => {
17880 if let Some(first_range) = ranges.first() {
17881 editor.change_selections(
17882 SelectionEffects::no_scroll(),
17883 window,
17884 cx,
17885 |selections| {
17886 selections.clear_disjoint();
17887 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17888 },
17889 );
17890 }
17891 editor.highlight_background::<Self>(
17892 &ranges,
17893 |_, theme| theme.colors().editor_highlighted_line_background,
17894 cx,
17895 );
17896 }
17897 MultibufferSelectionMode::All => {
17898 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17899 selections.clear_disjoint();
17900 selections.select_anchor_ranges(ranges);
17901 });
17902 }
17903 });
17904
17905 let item = Box::new(editor);
17906
17907 let pane = if split {
17908 workspace.adjacent_pane(window, cx)
17909 } else {
17910 workspace.active_pane().clone()
17911 };
17912 let activate_pane = split;
17913
17914 let mut destination_index = None;
17915 pane.update(cx, |pane, cx| {
17916 if allow_preview && !was_existing {
17917 destination_index = pane.replace_preview_item_id(item.item_id(), window, cx);
17918 }
17919 if was_existing && !allow_preview {
17920 pane.unpreview_item_if_preview(item.item_id());
17921 }
17922 pane.add_item(item, activate_pane, true, destination_index, window, cx);
17923 });
17924 }
17925
17926 pub fn rename(
17927 &mut self,
17928 _: &Rename,
17929 window: &mut Window,
17930 cx: &mut Context<Self>,
17931 ) -> Option<Task<Result<()>>> {
17932 use language::ToOffset as _;
17933
17934 let provider = self.semantics_provider.clone()?;
17935 let selection = self.selections.newest_anchor().clone();
17936 let (cursor_buffer, cursor_buffer_position) = self
17937 .buffer
17938 .read(cx)
17939 .text_anchor_for_position(selection.head(), cx)?;
17940 let (tail_buffer, cursor_buffer_position_end) = self
17941 .buffer
17942 .read(cx)
17943 .text_anchor_for_position(selection.tail(), cx)?;
17944 if tail_buffer != cursor_buffer {
17945 return None;
17946 }
17947
17948 let snapshot = cursor_buffer.read(cx).snapshot();
17949 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17950 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17951 let prepare_rename = provider
17952 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17953 .unwrap_or_else(|| Task::ready(Ok(None)));
17954 drop(snapshot);
17955
17956 Some(cx.spawn_in(window, async move |this, cx| {
17957 let rename_range = if let Some(range) = prepare_rename.await? {
17958 Some(range)
17959 } else {
17960 this.update(cx, |this, cx| {
17961 let buffer = this.buffer.read(cx).snapshot(cx);
17962 let mut buffer_highlights = this
17963 .document_highlights_for_position(selection.head(), &buffer)
17964 .filter(|highlight| {
17965 highlight.start.excerpt_id == selection.head().excerpt_id
17966 && highlight.end.excerpt_id == selection.head().excerpt_id
17967 });
17968 buffer_highlights
17969 .next()
17970 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17971 })?
17972 };
17973 if let Some(rename_range) = rename_range {
17974 this.update_in(cx, |this, window, cx| {
17975 let snapshot = cursor_buffer.read(cx).snapshot();
17976 let rename_buffer_range = rename_range.to_offset(&snapshot);
17977 let cursor_offset_in_rename_range =
17978 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17979 let cursor_offset_in_rename_range_end =
17980 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17981
17982 this.take_rename(false, window, cx);
17983 let buffer = this.buffer.read(cx).read(cx);
17984 let cursor_offset = selection.head().to_offset(&buffer);
17985 let rename_start =
17986 cursor_offset.saturating_sub_usize(cursor_offset_in_rename_range);
17987 let rename_end = rename_start + rename_buffer_range.len();
17988 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17989 let mut old_highlight_id = None;
17990 let old_name: Arc<str> = buffer
17991 .chunks(rename_start..rename_end, true)
17992 .map(|chunk| {
17993 if old_highlight_id.is_none() {
17994 old_highlight_id = chunk.syntax_highlight_id;
17995 }
17996 chunk.text
17997 })
17998 .collect::<String>()
17999 .into();
18000
18001 drop(buffer);
18002
18003 // Position the selection in the rename editor so that it matches the current selection.
18004 this.show_local_selections = false;
18005 let rename_editor = cx.new(|cx| {
18006 let mut editor = Editor::single_line(window, cx);
18007 editor.buffer.update(cx, |buffer, cx| {
18008 buffer.edit(
18009 [(MultiBufferOffset(0)..MultiBufferOffset(0), old_name.clone())],
18010 None,
18011 cx,
18012 )
18013 });
18014 let cursor_offset_in_rename_range =
18015 MultiBufferOffset(cursor_offset_in_rename_range);
18016 let cursor_offset_in_rename_range_end =
18017 MultiBufferOffset(cursor_offset_in_rename_range_end);
18018 let rename_selection_range = match cursor_offset_in_rename_range
18019 .cmp(&cursor_offset_in_rename_range_end)
18020 {
18021 Ordering::Equal => {
18022 editor.select_all(&SelectAll, window, cx);
18023 return editor;
18024 }
18025 Ordering::Less => {
18026 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
18027 }
18028 Ordering::Greater => {
18029 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
18030 }
18031 };
18032 if rename_selection_range.end.0 > old_name.len() {
18033 editor.select_all(&SelectAll, window, cx);
18034 } else {
18035 editor.change_selections(Default::default(), window, cx, |s| {
18036 s.select_ranges([rename_selection_range]);
18037 });
18038 }
18039 editor
18040 });
18041 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
18042 if e == &EditorEvent::Focused {
18043 cx.emit(EditorEvent::FocusedIn)
18044 }
18045 })
18046 .detach();
18047
18048 let write_highlights =
18049 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
18050 let read_highlights =
18051 this.clear_background_highlights::<DocumentHighlightRead>(cx);
18052 let ranges = write_highlights
18053 .iter()
18054 .flat_map(|(_, ranges)| ranges.iter())
18055 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
18056 .cloned()
18057 .collect();
18058
18059 this.highlight_text::<Rename>(
18060 ranges,
18061 HighlightStyle {
18062 fade_out: Some(0.6),
18063 ..Default::default()
18064 },
18065 cx,
18066 );
18067 let rename_focus_handle = rename_editor.focus_handle(cx);
18068 window.focus(&rename_focus_handle);
18069 let block_id = this.insert_blocks(
18070 [BlockProperties {
18071 style: BlockStyle::Flex,
18072 placement: BlockPlacement::Below(range.start),
18073 height: Some(1),
18074 render: Arc::new({
18075 let rename_editor = rename_editor.clone();
18076 move |cx: &mut BlockContext| {
18077 let mut text_style = cx.editor_style.text.clone();
18078 if let Some(highlight_style) = old_highlight_id
18079 .and_then(|h| h.style(&cx.editor_style.syntax))
18080 {
18081 text_style = text_style.highlight(highlight_style);
18082 }
18083 div()
18084 .block_mouse_except_scroll()
18085 .pl(cx.anchor_x)
18086 .child(EditorElement::new(
18087 &rename_editor,
18088 EditorStyle {
18089 background: cx.theme().system().transparent,
18090 local_player: cx.editor_style.local_player,
18091 text: text_style,
18092 scrollbar_width: cx.editor_style.scrollbar_width,
18093 syntax: cx.editor_style.syntax.clone(),
18094 status: cx.editor_style.status.clone(),
18095 inlay_hints_style: HighlightStyle {
18096 font_weight: Some(FontWeight::BOLD),
18097 ..make_inlay_hints_style(cx.app)
18098 },
18099 edit_prediction_styles: make_suggestion_styles(
18100 cx.app,
18101 ),
18102 ..EditorStyle::default()
18103 },
18104 ))
18105 .into_any_element()
18106 }
18107 }),
18108 priority: 0,
18109 }],
18110 Some(Autoscroll::fit()),
18111 cx,
18112 )[0];
18113 this.pending_rename = Some(RenameState {
18114 range,
18115 old_name,
18116 editor: rename_editor,
18117 block_id,
18118 });
18119 })?;
18120 }
18121
18122 Ok(())
18123 }))
18124 }
18125
18126 pub fn confirm_rename(
18127 &mut self,
18128 _: &ConfirmRename,
18129 window: &mut Window,
18130 cx: &mut Context<Self>,
18131 ) -> Option<Task<Result<()>>> {
18132 let rename = self.take_rename(false, window, cx)?;
18133 let workspace = self.workspace()?.downgrade();
18134 let (buffer, start) = self
18135 .buffer
18136 .read(cx)
18137 .text_anchor_for_position(rename.range.start, cx)?;
18138 let (end_buffer, _) = self
18139 .buffer
18140 .read(cx)
18141 .text_anchor_for_position(rename.range.end, cx)?;
18142 if buffer != end_buffer {
18143 return None;
18144 }
18145
18146 let old_name = rename.old_name;
18147 let new_name = rename.editor.read(cx).text(cx);
18148
18149 let rename = self.semantics_provider.as_ref()?.perform_rename(
18150 &buffer,
18151 start,
18152 new_name.clone(),
18153 cx,
18154 )?;
18155
18156 Some(cx.spawn_in(window, async move |editor, cx| {
18157 let project_transaction = rename.await?;
18158 Self::open_project_transaction(
18159 &editor,
18160 workspace,
18161 project_transaction,
18162 format!("Rename: {} → {}", old_name, new_name),
18163 cx,
18164 )
18165 .await?;
18166
18167 editor.update(cx, |editor, cx| {
18168 editor.refresh_document_highlights(cx);
18169 })?;
18170 Ok(())
18171 }))
18172 }
18173
18174 fn take_rename(
18175 &mut self,
18176 moving_cursor: bool,
18177 window: &mut Window,
18178 cx: &mut Context<Self>,
18179 ) -> Option<RenameState> {
18180 let rename = self.pending_rename.take()?;
18181 if rename.editor.focus_handle(cx).is_focused(window) {
18182 window.focus(&self.focus_handle);
18183 }
18184
18185 self.remove_blocks(
18186 [rename.block_id].into_iter().collect(),
18187 Some(Autoscroll::fit()),
18188 cx,
18189 );
18190 self.clear_highlights::<Rename>(cx);
18191 self.show_local_selections = true;
18192
18193 if moving_cursor {
18194 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
18195 editor
18196 .selections
18197 .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
18198 .head()
18199 });
18200
18201 // Update the selection to match the position of the selection inside
18202 // the rename editor.
18203 let snapshot = self.buffer.read(cx).read(cx);
18204 let rename_range = rename.range.to_offset(&snapshot);
18205 let cursor_in_editor = snapshot
18206 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
18207 .min(rename_range.end);
18208 drop(snapshot);
18209
18210 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18211 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
18212 });
18213 } else {
18214 self.refresh_document_highlights(cx);
18215 }
18216
18217 Some(rename)
18218 }
18219
18220 pub fn pending_rename(&self) -> Option<&RenameState> {
18221 self.pending_rename.as_ref()
18222 }
18223
18224 fn format(
18225 &mut self,
18226 _: &Format,
18227 window: &mut Window,
18228 cx: &mut Context<Self>,
18229 ) -> Option<Task<Result<()>>> {
18230 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18231
18232 let project = match &self.project {
18233 Some(project) => project.clone(),
18234 None => return None,
18235 };
18236
18237 Some(self.perform_format(
18238 project,
18239 FormatTrigger::Manual,
18240 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
18241 window,
18242 cx,
18243 ))
18244 }
18245
18246 fn format_selections(
18247 &mut self,
18248 _: &FormatSelections,
18249 window: &mut Window,
18250 cx: &mut Context<Self>,
18251 ) -> Option<Task<Result<()>>> {
18252 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18253
18254 let project = match &self.project {
18255 Some(project) => project.clone(),
18256 None => return None,
18257 };
18258
18259 let ranges = self
18260 .selections
18261 .all_adjusted(&self.display_snapshot(cx))
18262 .into_iter()
18263 .map(|selection| selection.range())
18264 .collect_vec();
18265
18266 Some(self.perform_format(
18267 project,
18268 FormatTrigger::Manual,
18269 FormatTarget::Ranges(ranges),
18270 window,
18271 cx,
18272 ))
18273 }
18274
18275 fn perform_format(
18276 &mut self,
18277 project: Entity<Project>,
18278 trigger: FormatTrigger,
18279 target: FormatTarget,
18280 window: &mut Window,
18281 cx: &mut Context<Self>,
18282 ) -> Task<Result<()>> {
18283 let buffer = self.buffer.clone();
18284 let (buffers, target) = match target {
18285 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
18286 FormatTarget::Ranges(selection_ranges) => {
18287 let multi_buffer = buffer.read(cx);
18288 let snapshot = multi_buffer.read(cx);
18289 let mut buffers = HashSet::default();
18290 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
18291 BTreeMap::new();
18292 for selection_range in selection_ranges {
18293 for (buffer, buffer_range, _) in
18294 snapshot.range_to_buffer_ranges(selection_range)
18295 {
18296 let buffer_id = buffer.remote_id();
18297 let start = buffer.anchor_before(buffer_range.start);
18298 let end = buffer.anchor_after(buffer_range.end);
18299 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
18300 buffer_id_to_ranges
18301 .entry(buffer_id)
18302 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
18303 .or_insert_with(|| vec![start..end]);
18304 }
18305 }
18306 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
18307 }
18308 };
18309
18310 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
18311 let selections_prev = transaction_id_prev
18312 .and_then(|transaction_id_prev| {
18313 // default to selections as they were after the last edit, if we have them,
18314 // instead of how they are now.
18315 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
18316 // will take you back to where you made the last edit, instead of staying where you scrolled
18317 self.selection_history
18318 .transaction(transaction_id_prev)
18319 .map(|t| t.0.clone())
18320 })
18321 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
18322
18323 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
18324 let format = project.update(cx, |project, cx| {
18325 project.format(buffers, target, true, trigger, cx)
18326 });
18327
18328 cx.spawn_in(window, async move |editor, cx| {
18329 let transaction = futures::select_biased! {
18330 transaction = format.log_err().fuse() => transaction,
18331 () = timeout => {
18332 log::warn!("timed out waiting for formatting");
18333 None
18334 }
18335 };
18336
18337 buffer
18338 .update(cx, |buffer, cx| {
18339 if let Some(transaction) = transaction
18340 && !buffer.is_singleton()
18341 {
18342 buffer.push_transaction(&transaction.0, cx);
18343 }
18344 cx.notify();
18345 })
18346 .ok();
18347
18348 if let Some(transaction_id_now) =
18349 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
18350 {
18351 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
18352 if has_new_transaction {
18353 _ = editor.update(cx, |editor, _| {
18354 editor
18355 .selection_history
18356 .insert_transaction(transaction_id_now, selections_prev);
18357 });
18358 }
18359 }
18360
18361 Ok(())
18362 })
18363 }
18364
18365 fn organize_imports(
18366 &mut self,
18367 _: &OrganizeImports,
18368 window: &mut Window,
18369 cx: &mut Context<Self>,
18370 ) -> Option<Task<Result<()>>> {
18371 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
18372 let project = match &self.project {
18373 Some(project) => project.clone(),
18374 None => return None,
18375 };
18376 Some(self.perform_code_action_kind(
18377 project,
18378 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
18379 window,
18380 cx,
18381 ))
18382 }
18383
18384 fn perform_code_action_kind(
18385 &mut self,
18386 project: Entity<Project>,
18387 kind: CodeActionKind,
18388 window: &mut Window,
18389 cx: &mut Context<Self>,
18390 ) -> Task<Result<()>> {
18391 let buffer = self.buffer.clone();
18392 let buffers = buffer.read(cx).all_buffers();
18393 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
18394 let apply_action = project.update(cx, |project, cx| {
18395 project.apply_code_action_kind(buffers, kind, true, cx)
18396 });
18397 cx.spawn_in(window, async move |_, cx| {
18398 let transaction = futures::select_biased! {
18399 () = timeout => {
18400 log::warn!("timed out waiting for executing code action");
18401 None
18402 }
18403 transaction = apply_action.log_err().fuse() => transaction,
18404 };
18405 buffer
18406 .update(cx, |buffer, cx| {
18407 // check if we need this
18408 if let Some(transaction) = transaction
18409 && !buffer.is_singleton()
18410 {
18411 buffer.push_transaction(&transaction.0, cx);
18412 }
18413 cx.notify();
18414 })
18415 .ok();
18416 Ok(())
18417 })
18418 }
18419
18420 pub fn restart_language_server(
18421 &mut self,
18422 _: &RestartLanguageServer,
18423 _: &mut Window,
18424 cx: &mut Context<Self>,
18425 ) {
18426 if let Some(project) = self.project.clone() {
18427 self.buffer.update(cx, |multi_buffer, cx| {
18428 project.update(cx, |project, cx| {
18429 project.restart_language_servers_for_buffers(
18430 multi_buffer.all_buffers().into_iter().collect(),
18431 HashSet::default(),
18432 cx,
18433 );
18434 });
18435 })
18436 }
18437 }
18438
18439 pub fn stop_language_server(
18440 &mut self,
18441 _: &StopLanguageServer,
18442 _: &mut Window,
18443 cx: &mut Context<Self>,
18444 ) {
18445 if let Some(project) = self.project.clone() {
18446 self.buffer.update(cx, |multi_buffer, cx| {
18447 project.update(cx, |project, cx| {
18448 project.stop_language_servers_for_buffers(
18449 multi_buffer.all_buffers().into_iter().collect(),
18450 HashSet::default(),
18451 cx,
18452 );
18453 });
18454 });
18455 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
18456 }
18457 }
18458
18459 fn cancel_language_server_work(
18460 workspace: &mut Workspace,
18461 _: &actions::CancelLanguageServerWork,
18462 _: &mut Window,
18463 cx: &mut Context<Workspace>,
18464 ) {
18465 let project = workspace.project();
18466 let buffers = workspace
18467 .active_item(cx)
18468 .and_then(|item| item.act_as::<Editor>(cx))
18469 .map_or(HashSet::default(), |editor| {
18470 editor.read(cx).buffer.read(cx).all_buffers()
18471 });
18472 project.update(cx, |project, cx| {
18473 project.cancel_language_server_work_for_buffers(buffers, cx);
18474 });
18475 }
18476
18477 fn show_character_palette(
18478 &mut self,
18479 _: &ShowCharacterPalette,
18480 window: &mut Window,
18481 _: &mut Context<Self>,
18482 ) {
18483 window.show_character_palette();
18484 }
18485
18486 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
18487 if !self.diagnostics_enabled() {
18488 return;
18489 }
18490
18491 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
18492 let buffer = self.buffer.read(cx).snapshot(cx);
18493 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
18494 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
18495 let is_valid = buffer
18496 .diagnostics_in_range::<MultiBufferOffset>(primary_range_start..primary_range_end)
18497 .any(|entry| {
18498 entry.diagnostic.is_primary
18499 && !entry.range.is_empty()
18500 && entry.range.start == primary_range_start
18501 && entry.diagnostic.message == active_diagnostics.active_message
18502 });
18503
18504 if !is_valid {
18505 self.dismiss_diagnostics(cx);
18506 }
18507 }
18508 }
18509
18510 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
18511 match &self.active_diagnostics {
18512 ActiveDiagnostic::Group(group) => Some(group),
18513 _ => None,
18514 }
18515 }
18516
18517 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
18518 if !self.diagnostics_enabled() {
18519 return;
18520 }
18521 self.dismiss_diagnostics(cx);
18522 self.active_diagnostics = ActiveDiagnostic::All;
18523 }
18524
18525 fn activate_diagnostics(
18526 &mut self,
18527 buffer_id: BufferId,
18528 diagnostic: DiagnosticEntryRef<'_, MultiBufferOffset>,
18529 window: &mut Window,
18530 cx: &mut Context<Self>,
18531 ) {
18532 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18533 return;
18534 }
18535 self.dismiss_diagnostics(cx);
18536 let snapshot = self.snapshot(window, cx);
18537 let buffer = self.buffer.read(cx).snapshot(cx);
18538 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
18539 return;
18540 };
18541
18542 let diagnostic_group = buffer
18543 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
18544 .collect::<Vec<_>>();
18545
18546 let language_registry = self
18547 .project()
18548 .map(|project| project.read(cx).languages().clone());
18549
18550 let blocks = renderer.render_group(
18551 diagnostic_group,
18552 buffer_id,
18553 snapshot,
18554 cx.weak_entity(),
18555 language_registry,
18556 cx,
18557 );
18558
18559 let blocks = self.display_map.update(cx, |display_map, cx| {
18560 display_map.insert_blocks(blocks, cx).into_iter().collect()
18561 });
18562 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
18563 active_range: buffer.anchor_before(diagnostic.range.start)
18564 ..buffer.anchor_after(diagnostic.range.end),
18565 active_message: diagnostic.diagnostic.message.clone(),
18566 group_id: diagnostic.diagnostic.group_id,
18567 blocks,
18568 });
18569 cx.notify();
18570 }
18571
18572 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
18573 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
18574 return;
18575 };
18576
18577 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
18578 if let ActiveDiagnostic::Group(group) = prev {
18579 self.display_map.update(cx, |display_map, cx| {
18580 display_map.remove_blocks(group.blocks, cx);
18581 });
18582 cx.notify();
18583 }
18584 }
18585
18586 /// Disable inline diagnostics rendering for this editor.
18587 pub fn disable_inline_diagnostics(&mut self) {
18588 self.inline_diagnostics_enabled = false;
18589 self.inline_diagnostics_update = Task::ready(());
18590 self.inline_diagnostics.clear();
18591 }
18592
18593 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
18594 self.diagnostics_enabled = false;
18595 self.dismiss_diagnostics(cx);
18596 self.inline_diagnostics_update = Task::ready(());
18597 self.inline_diagnostics.clear();
18598 }
18599
18600 pub fn disable_word_completions(&mut self) {
18601 self.word_completions_enabled = false;
18602 }
18603
18604 pub fn diagnostics_enabled(&self) -> bool {
18605 self.diagnostics_enabled && self.mode.is_full()
18606 }
18607
18608 pub fn inline_diagnostics_enabled(&self) -> bool {
18609 self.inline_diagnostics_enabled && self.diagnostics_enabled()
18610 }
18611
18612 pub fn show_inline_diagnostics(&self) -> bool {
18613 self.show_inline_diagnostics
18614 }
18615
18616 pub fn toggle_inline_diagnostics(
18617 &mut self,
18618 _: &ToggleInlineDiagnostics,
18619 window: &mut Window,
18620 cx: &mut Context<Editor>,
18621 ) {
18622 self.show_inline_diagnostics = !self.show_inline_diagnostics;
18623 self.refresh_inline_diagnostics(false, window, cx);
18624 }
18625
18626 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
18627 self.diagnostics_max_severity = severity;
18628 self.display_map.update(cx, |display_map, _| {
18629 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
18630 });
18631 }
18632
18633 pub fn toggle_diagnostics(
18634 &mut self,
18635 _: &ToggleDiagnostics,
18636 window: &mut Window,
18637 cx: &mut Context<Editor>,
18638 ) {
18639 if !self.diagnostics_enabled() {
18640 return;
18641 }
18642
18643 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18644 EditorSettings::get_global(cx)
18645 .diagnostics_max_severity
18646 .filter(|severity| severity != &DiagnosticSeverity::Off)
18647 .unwrap_or(DiagnosticSeverity::Hint)
18648 } else {
18649 DiagnosticSeverity::Off
18650 };
18651 self.set_max_diagnostics_severity(new_severity, cx);
18652 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
18653 self.active_diagnostics = ActiveDiagnostic::None;
18654 self.inline_diagnostics_update = Task::ready(());
18655 self.inline_diagnostics.clear();
18656 } else {
18657 self.refresh_inline_diagnostics(false, window, cx);
18658 }
18659
18660 cx.notify();
18661 }
18662
18663 pub fn toggle_minimap(
18664 &mut self,
18665 _: &ToggleMinimap,
18666 window: &mut Window,
18667 cx: &mut Context<Editor>,
18668 ) {
18669 if self.supports_minimap(cx) {
18670 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
18671 }
18672 }
18673
18674 fn refresh_inline_diagnostics(
18675 &mut self,
18676 debounce: bool,
18677 window: &mut Window,
18678 cx: &mut Context<Self>,
18679 ) {
18680 let max_severity = ProjectSettings::get_global(cx)
18681 .diagnostics
18682 .inline
18683 .max_severity
18684 .unwrap_or(self.diagnostics_max_severity);
18685
18686 if !self.inline_diagnostics_enabled()
18687 || !self.diagnostics_enabled()
18688 || !self.show_inline_diagnostics
18689 || max_severity == DiagnosticSeverity::Off
18690 {
18691 self.inline_diagnostics_update = Task::ready(());
18692 self.inline_diagnostics.clear();
18693 return;
18694 }
18695
18696 let debounce_ms = ProjectSettings::get_global(cx)
18697 .diagnostics
18698 .inline
18699 .update_debounce_ms;
18700 let debounce = if debounce && debounce_ms > 0 {
18701 Some(Duration::from_millis(debounce_ms))
18702 } else {
18703 None
18704 };
18705 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18706 if let Some(debounce) = debounce {
18707 cx.background_executor().timer(debounce).await;
18708 }
18709 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18710 editor
18711 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18712 .ok()
18713 }) else {
18714 return;
18715 };
18716
18717 let new_inline_diagnostics = cx
18718 .background_spawn(async move {
18719 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18720 for diagnostic_entry in
18721 snapshot.diagnostics_in_range(MultiBufferOffset(0)..snapshot.len())
18722 {
18723 let message = diagnostic_entry
18724 .diagnostic
18725 .message
18726 .split_once('\n')
18727 .map(|(line, _)| line)
18728 .map(SharedString::new)
18729 .unwrap_or_else(|| {
18730 SharedString::new(&*diagnostic_entry.diagnostic.message)
18731 });
18732 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18733 let (Ok(i) | Err(i)) = inline_diagnostics
18734 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18735 inline_diagnostics.insert(
18736 i,
18737 (
18738 start_anchor,
18739 InlineDiagnostic {
18740 message,
18741 group_id: diagnostic_entry.diagnostic.group_id,
18742 start: diagnostic_entry.range.start.to_point(&snapshot),
18743 is_primary: diagnostic_entry.diagnostic.is_primary,
18744 severity: diagnostic_entry.diagnostic.severity,
18745 },
18746 ),
18747 );
18748 }
18749 inline_diagnostics
18750 })
18751 .await;
18752
18753 editor
18754 .update(cx, |editor, cx| {
18755 editor.inline_diagnostics = new_inline_diagnostics;
18756 cx.notify();
18757 })
18758 .ok();
18759 });
18760 }
18761
18762 fn pull_diagnostics(
18763 &mut self,
18764 buffer_id: Option<BufferId>,
18765 window: &Window,
18766 cx: &mut Context<Self>,
18767 ) -> Option<()> {
18768 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18769 return None;
18770 }
18771 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18772 .diagnostics
18773 .lsp_pull_diagnostics;
18774 if !pull_diagnostics_settings.enabled {
18775 return None;
18776 }
18777 let project = self.project()?.downgrade();
18778
18779 let mut edited_buffer_ids = HashSet::default();
18780 let mut edited_worktree_ids = HashSet::default();
18781 let edited_buffers = match buffer_id {
18782 Some(buffer_id) => {
18783 let buffer = self.buffer().read(cx).buffer(buffer_id)?;
18784 let worktree_id = buffer.read(cx).file().map(|f| f.worktree_id(cx))?;
18785 edited_buffer_ids.insert(buffer.read(cx).remote_id());
18786 edited_worktree_ids.insert(worktree_id);
18787 vec![buffer]
18788 }
18789 None => self
18790 .buffer()
18791 .read(cx)
18792 .all_buffers()
18793 .into_iter()
18794 .filter(|buffer| {
18795 let buffer = buffer.read(cx);
18796 match buffer.file().map(|f| f.worktree_id(cx)) {
18797 Some(worktree_id) => {
18798 edited_buffer_ids.insert(buffer.remote_id());
18799 edited_worktree_ids.insert(worktree_id);
18800 true
18801 }
18802 None => false,
18803 }
18804 })
18805 .collect::<Vec<_>>(),
18806 };
18807
18808 if edited_buffers.is_empty() {
18809 self.pull_diagnostics_task = Task::ready(());
18810 self.pull_diagnostics_background_task = Task::ready(());
18811 return None;
18812 }
18813
18814 let mut already_used_buffers = HashSet::default();
18815 let related_open_buffers = self
18816 .workspace
18817 .as_ref()
18818 .and_then(|(workspace, _)| workspace.upgrade())
18819 .into_iter()
18820 .flat_map(|workspace| workspace.read(cx).panes())
18821 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
18822 .filter(|editor| editor != &cx.entity())
18823 .flat_map(|editor| editor.read(cx).buffer().read(cx).all_buffers())
18824 .filter(|buffer| {
18825 let buffer = buffer.read(cx);
18826 let buffer_id = buffer.remote_id();
18827 if already_used_buffers.insert(buffer_id) {
18828 if let Some(worktree_id) = buffer.file().map(|f| f.worktree_id(cx)) {
18829 return !edited_buffer_ids.contains(&buffer_id)
18830 && !edited_worktree_ids.contains(&worktree_id);
18831 }
18832 }
18833 false
18834 })
18835 .collect::<Vec<_>>();
18836
18837 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18838 let make_spawn = |buffers: Vec<Entity<Buffer>>, delay: Duration| {
18839 if buffers.is_empty() {
18840 return Task::ready(());
18841 }
18842 let project_weak = project.clone();
18843 cx.spawn_in(window, async move |_, cx| {
18844 cx.background_executor().timer(delay).await;
18845
18846 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18847 buffers
18848 .into_iter()
18849 .filter_map(|buffer| {
18850 project_weak
18851 .update(cx, |project, cx| {
18852 project.lsp_store().update(cx, |lsp_store, cx| {
18853 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18854 })
18855 })
18856 .ok()
18857 })
18858 .collect::<FuturesUnordered<_>>()
18859 }) else {
18860 return;
18861 };
18862
18863 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18864 if let Err(e) = pull_task {
18865 log::error!("Failed to update project diagnostics: {e:#}");
18866 }
18867 }
18868 })
18869 };
18870
18871 self.pull_diagnostics_task = make_spawn(edited_buffers, debounce);
18872 self.pull_diagnostics_background_task = make_spawn(related_open_buffers, debounce * 2);
18873
18874 Some(())
18875 }
18876
18877 pub fn set_selections_from_remote(
18878 &mut self,
18879 selections: Vec<Selection<Anchor>>,
18880 pending_selection: Option<Selection<Anchor>>,
18881 window: &mut Window,
18882 cx: &mut Context<Self>,
18883 ) {
18884 let old_cursor_position = self.selections.newest_anchor().head();
18885 self.selections
18886 .change_with(&self.display_snapshot(cx), |s| {
18887 s.select_anchors(selections);
18888 if let Some(pending_selection) = pending_selection {
18889 s.set_pending(pending_selection, SelectMode::Character);
18890 } else {
18891 s.clear_pending();
18892 }
18893 });
18894 self.selections_did_change(
18895 false,
18896 &old_cursor_position,
18897 SelectionEffects::default(),
18898 window,
18899 cx,
18900 );
18901 }
18902
18903 pub fn transact(
18904 &mut self,
18905 window: &mut Window,
18906 cx: &mut Context<Self>,
18907 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18908 ) -> Option<TransactionId> {
18909 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18910 this.start_transaction_at(Instant::now(), window, cx);
18911 update(this, window, cx);
18912 this.end_transaction_at(Instant::now(), cx)
18913 })
18914 }
18915
18916 pub fn start_transaction_at(
18917 &mut self,
18918 now: Instant,
18919 window: &mut Window,
18920 cx: &mut Context<Self>,
18921 ) -> Option<TransactionId> {
18922 self.end_selection(window, cx);
18923 if let Some(tx_id) = self
18924 .buffer
18925 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18926 {
18927 self.selection_history
18928 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18929 cx.emit(EditorEvent::TransactionBegun {
18930 transaction_id: tx_id,
18931 });
18932 Some(tx_id)
18933 } else {
18934 None
18935 }
18936 }
18937
18938 pub fn end_transaction_at(
18939 &mut self,
18940 now: Instant,
18941 cx: &mut Context<Self>,
18942 ) -> Option<TransactionId> {
18943 if let Some(transaction_id) = self
18944 .buffer
18945 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18946 {
18947 if let Some((_, end_selections)) =
18948 self.selection_history.transaction_mut(transaction_id)
18949 {
18950 *end_selections = Some(self.selections.disjoint_anchors_arc());
18951 } else {
18952 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18953 }
18954
18955 cx.emit(EditorEvent::Edited { transaction_id });
18956 Some(transaction_id)
18957 } else {
18958 None
18959 }
18960 }
18961
18962 pub fn modify_transaction_selection_history(
18963 &mut self,
18964 transaction_id: TransactionId,
18965 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18966 ) -> bool {
18967 self.selection_history
18968 .transaction_mut(transaction_id)
18969 .map(modify)
18970 .is_some()
18971 }
18972
18973 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18974 if self.selection_mark_mode {
18975 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18976 s.move_with(|_, sel| {
18977 sel.collapse_to(sel.head(), SelectionGoal::None);
18978 });
18979 })
18980 }
18981 self.selection_mark_mode = true;
18982 cx.notify();
18983 }
18984
18985 pub fn swap_selection_ends(
18986 &mut self,
18987 _: &actions::SwapSelectionEnds,
18988 window: &mut Window,
18989 cx: &mut Context<Self>,
18990 ) {
18991 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18992 s.move_with(|_, sel| {
18993 if sel.start != sel.end {
18994 sel.reversed = !sel.reversed
18995 }
18996 });
18997 });
18998 self.request_autoscroll(Autoscroll::newest(), cx);
18999 cx.notify();
19000 }
19001
19002 pub fn toggle_focus(
19003 workspace: &mut Workspace,
19004 _: &actions::ToggleFocus,
19005 window: &mut Window,
19006 cx: &mut Context<Workspace>,
19007 ) {
19008 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
19009 return;
19010 };
19011 workspace.activate_item(&item, true, true, window, cx);
19012 }
19013
19014 pub fn toggle_fold(
19015 &mut self,
19016 _: &actions::ToggleFold,
19017 window: &mut Window,
19018 cx: &mut Context<Self>,
19019 ) {
19020 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19021 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19022 let selection = self.selections.newest::<Point>(&display_map);
19023
19024 let range = if selection.is_empty() {
19025 let point = selection.head().to_display_point(&display_map);
19026 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19027 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19028 .to_point(&display_map);
19029 start..end
19030 } else {
19031 selection.range()
19032 };
19033 if display_map.folds_in_range(range).next().is_some() {
19034 self.unfold_lines(&Default::default(), window, cx)
19035 } else {
19036 self.fold(&Default::default(), window, cx)
19037 }
19038 } else {
19039 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19040 let buffer_ids: HashSet<_> = self
19041 .selections
19042 .disjoint_anchor_ranges()
19043 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19044 .collect();
19045
19046 let should_unfold = buffer_ids
19047 .iter()
19048 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19049
19050 for buffer_id in buffer_ids {
19051 if should_unfold {
19052 self.unfold_buffer(buffer_id, cx);
19053 } else {
19054 self.fold_buffer(buffer_id, cx);
19055 }
19056 }
19057 }
19058 }
19059
19060 pub fn toggle_fold_recursive(
19061 &mut self,
19062 _: &actions::ToggleFoldRecursive,
19063 window: &mut Window,
19064 cx: &mut Context<Self>,
19065 ) {
19066 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
19067
19068 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19069 let range = if selection.is_empty() {
19070 let point = selection.head().to_display_point(&display_map);
19071 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
19072 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
19073 .to_point(&display_map);
19074 start..end
19075 } else {
19076 selection.range()
19077 };
19078 if display_map.folds_in_range(range).next().is_some() {
19079 self.unfold_recursive(&Default::default(), window, cx)
19080 } else {
19081 self.fold_recursive(&Default::default(), window, cx)
19082 }
19083 }
19084
19085 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
19086 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19087 let mut to_fold = Vec::new();
19088 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19089 let selections = self.selections.all_adjusted(&display_map);
19090
19091 for selection in selections {
19092 let range = selection.range().sorted();
19093 let buffer_start_row = range.start.row;
19094
19095 if range.start.row != range.end.row {
19096 let mut found = false;
19097 let mut row = range.start.row;
19098 while row <= range.end.row {
19099 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19100 {
19101 found = true;
19102 row = crease.range().end.row + 1;
19103 to_fold.push(crease);
19104 } else {
19105 row += 1
19106 }
19107 }
19108 if found {
19109 continue;
19110 }
19111 }
19112
19113 for row in (0..=range.start.row).rev() {
19114 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
19115 && crease.range().end.row >= buffer_start_row
19116 {
19117 to_fold.push(crease);
19118 if row <= range.start.row {
19119 break;
19120 }
19121 }
19122 }
19123 }
19124
19125 self.fold_creases(to_fold, true, window, cx);
19126 } else {
19127 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19128 let buffer_ids = self
19129 .selections
19130 .disjoint_anchor_ranges()
19131 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19132 .collect::<HashSet<_>>();
19133 for buffer_id in buffer_ids {
19134 self.fold_buffer(buffer_id, cx);
19135 }
19136 }
19137 }
19138
19139 pub fn toggle_fold_all(
19140 &mut self,
19141 _: &actions::ToggleFoldAll,
19142 window: &mut Window,
19143 cx: &mut Context<Self>,
19144 ) {
19145 if self.buffer.read(cx).is_singleton() {
19146 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19147 let has_folds = display_map
19148 .folds_in_range(MultiBufferOffset(0)..display_map.buffer_snapshot().len())
19149 .next()
19150 .is_some();
19151
19152 if has_folds {
19153 self.unfold_all(&actions::UnfoldAll, window, cx);
19154 } else {
19155 self.fold_all(&actions::FoldAll, window, cx);
19156 }
19157 } else {
19158 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
19159 let should_unfold = buffer_ids
19160 .iter()
19161 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
19162
19163 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19164 editor
19165 .update_in(cx, |editor, _, cx| {
19166 for buffer_id in buffer_ids {
19167 if should_unfold {
19168 editor.unfold_buffer(buffer_id, cx);
19169 } else {
19170 editor.fold_buffer(buffer_id, cx);
19171 }
19172 }
19173 })
19174 .ok();
19175 });
19176 }
19177 }
19178
19179 fn fold_at_level(
19180 &mut self,
19181 fold_at: &FoldAtLevel,
19182 window: &mut Window,
19183 cx: &mut Context<Self>,
19184 ) {
19185 if !self.buffer.read(cx).is_singleton() {
19186 return;
19187 }
19188
19189 let fold_at_level = fold_at.0;
19190 let snapshot = self.buffer.read(cx).snapshot(cx);
19191 let mut to_fold = Vec::new();
19192 let mut stack = vec![(0, snapshot.max_row().0, 1)];
19193
19194 let row_ranges_to_keep: Vec<Range<u32>> = self
19195 .selections
19196 .all::<Point>(&self.display_snapshot(cx))
19197 .into_iter()
19198 .map(|sel| sel.start.row..sel.end.row)
19199 .collect();
19200
19201 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
19202 while start_row < end_row {
19203 match self
19204 .snapshot(window, cx)
19205 .crease_for_buffer_row(MultiBufferRow(start_row))
19206 {
19207 Some(crease) => {
19208 let nested_start_row = crease.range().start.row + 1;
19209 let nested_end_row = crease.range().end.row;
19210
19211 if current_level < fold_at_level {
19212 stack.push((nested_start_row, nested_end_row, current_level + 1));
19213 } else if current_level == fold_at_level {
19214 // Fold iff there is no selection completely contained within the fold region
19215 if !row_ranges_to_keep.iter().any(|selection| {
19216 selection.end >= nested_start_row
19217 && selection.start <= nested_end_row
19218 }) {
19219 to_fold.push(crease);
19220 }
19221 }
19222
19223 start_row = nested_end_row + 1;
19224 }
19225 None => start_row += 1,
19226 }
19227 }
19228 }
19229
19230 self.fold_creases(to_fold, true, window, cx);
19231 }
19232
19233 pub fn fold_at_level_1(
19234 &mut self,
19235 _: &actions::FoldAtLevel1,
19236 window: &mut Window,
19237 cx: &mut Context<Self>,
19238 ) {
19239 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
19240 }
19241
19242 pub fn fold_at_level_2(
19243 &mut self,
19244 _: &actions::FoldAtLevel2,
19245 window: &mut Window,
19246 cx: &mut Context<Self>,
19247 ) {
19248 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
19249 }
19250
19251 pub fn fold_at_level_3(
19252 &mut self,
19253 _: &actions::FoldAtLevel3,
19254 window: &mut Window,
19255 cx: &mut Context<Self>,
19256 ) {
19257 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
19258 }
19259
19260 pub fn fold_at_level_4(
19261 &mut self,
19262 _: &actions::FoldAtLevel4,
19263 window: &mut Window,
19264 cx: &mut Context<Self>,
19265 ) {
19266 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
19267 }
19268
19269 pub fn fold_at_level_5(
19270 &mut self,
19271 _: &actions::FoldAtLevel5,
19272 window: &mut Window,
19273 cx: &mut Context<Self>,
19274 ) {
19275 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
19276 }
19277
19278 pub fn fold_at_level_6(
19279 &mut self,
19280 _: &actions::FoldAtLevel6,
19281 window: &mut Window,
19282 cx: &mut Context<Self>,
19283 ) {
19284 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
19285 }
19286
19287 pub fn fold_at_level_7(
19288 &mut self,
19289 _: &actions::FoldAtLevel7,
19290 window: &mut Window,
19291 cx: &mut Context<Self>,
19292 ) {
19293 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
19294 }
19295
19296 pub fn fold_at_level_8(
19297 &mut self,
19298 _: &actions::FoldAtLevel8,
19299 window: &mut Window,
19300 cx: &mut Context<Self>,
19301 ) {
19302 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
19303 }
19304
19305 pub fn fold_at_level_9(
19306 &mut self,
19307 _: &actions::FoldAtLevel9,
19308 window: &mut Window,
19309 cx: &mut Context<Self>,
19310 ) {
19311 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
19312 }
19313
19314 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
19315 if self.buffer.read(cx).is_singleton() {
19316 let mut fold_ranges = Vec::new();
19317 let snapshot = self.buffer.read(cx).snapshot(cx);
19318
19319 for row in 0..snapshot.max_row().0 {
19320 if let Some(foldable_range) = self
19321 .snapshot(window, cx)
19322 .crease_for_buffer_row(MultiBufferRow(row))
19323 {
19324 fold_ranges.push(foldable_range);
19325 }
19326 }
19327
19328 self.fold_creases(fold_ranges, true, window, cx);
19329 } else {
19330 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
19331 editor
19332 .update_in(cx, |editor, _, cx| {
19333 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19334 editor.fold_buffer(buffer_id, cx);
19335 }
19336 })
19337 .ok();
19338 });
19339 }
19340 }
19341
19342 pub fn fold_function_bodies(
19343 &mut self,
19344 _: &actions::FoldFunctionBodies,
19345 window: &mut Window,
19346 cx: &mut Context<Self>,
19347 ) {
19348 let snapshot = self.buffer.read(cx).snapshot(cx);
19349
19350 let ranges = snapshot
19351 .text_object_ranges(
19352 MultiBufferOffset(0)..snapshot.len(),
19353 TreeSitterOptions::default(),
19354 )
19355 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
19356 .collect::<Vec<_>>();
19357
19358 let creases = ranges
19359 .into_iter()
19360 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
19361 .collect();
19362
19363 self.fold_creases(creases, true, window, cx);
19364 }
19365
19366 pub fn fold_recursive(
19367 &mut self,
19368 _: &actions::FoldRecursive,
19369 window: &mut Window,
19370 cx: &mut Context<Self>,
19371 ) {
19372 let mut to_fold = Vec::new();
19373 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19374 let selections = self.selections.all_adjusted(&display_map);
19375
19376 for selection in selections {
19377 let range = selection.range().sorted();
19378 let buffer_start_row = range.start.row;
19379
19380 if range.start.row != range.end.row {
19381 let mut found = false;
19382 for row in range.start.row..=range.end.row {
19383 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19384 found = true;
19385 to_fold.push(crease);
19386 }
19387 }
19388 if found {
19389 continue;
19390 }
19391 }
19392
19393 for row in (0..=range.start.row).rev() {
19394 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
19395 if crease.range().end.row >= buffer_start_row {
19396 to_fold.push(crease);
19397 } else {
19398 break;
19399 }
19400 }
19401 }
19402 }
19403
19404 self.fold_creases(to_fold, true, window, cx);
19405 }
19406
19407 pub fn fold_at(
19408 &mut self,
19409 buffer_row: MultiBufferRow,
19410 window: &mut Window,
19411 cx: &mut Context<Self>,
19412 ) {
19413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19414
19415 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
19416 let autoscroll = self
19417 .selections
19418 .all::<Point>(&display_map)
19419 .iter()
19420 .any(|selection| crease.range().overlaps(&selection.range()));
19421
19422 self.fold_creases(vec![crease], autoscroll, window, cx);
19423 }
19424 }
19425
19426 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
19427 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
19428 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19429 let buffer = display_map.buffer_snapshot();
19430 let selections = self.selections.all::<Point>(&display_map);
19431 let ranges = selections
19432 .iter()
19433 .map(|s| {
19434 let range = s.display_range(&display_map).sorted();
19435 let mut start = range.start.to_point(&display_map);
19436 let mut end = range.end.to_point(&display_map);
19437 start.column = 0;
19438 end.column = buffer.line_len(MultiBufferRow(end.row));
19439 start..end
19440 })
19441 .collect::<Vec<_>>();
19442
19443 self.unfold_ranges(&ranges, true, true, cx);
19444 } else {
19445 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
19446 let buffer_ids = self
19447 .selections
19448 .disjoint_anchor_ranges()
19449 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
19450 .collect::<HashSet<_>>();
19451 for buffer_id in buffer_ids {
19452 self.unfold_buffer(buffer_id, cx);
19453 }
19454 }
19455 }
19456
19457 pub fn unfold_recursive(
19458 &mut self,
19459 _: &UnfoldRecursive,
19460 _window: &mut Window,
19461 cx: &mut Context<Self>,
19462 ) {
19463 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19464 let selections = self.selections.all::<Point>(&display_map);
19465 let ranges = selections
19466 .iter()
19467 .map(|s| {
19468 let mut range = s.display_range(&display_map).sorted();
19469 *range.start.column_mut() = 0;
19470 *range.end.column_mut() = display_map.line_len(range.end.row());
19471 let start = range.start.to_point(&display_map);
19472 let end = range.end.to_point(&display_map);
19473 start..end
19474 })
19475 .collect::<Vec<_>>();
19476
19477 self.unfold_ranges(&ranges, true, true, cx);
19478 }
19479
19480 pub fn unfold_at(
19481 &mut self,
19482 buffer_row: MultiBufferRow,
19483 _window: &mut Window,
19484 cx: &mut Context<Self>,
19485 ) {
19486 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19487
19488 let intersection_range = Point::new(buffer_row.0, 0)
19489 ..Point::new(
19490 buffer_row.0,
19491 display_map.buffer_snapshot().line_len(buffer_row),
19492 );
19493
19494 let autoscroll = self
19495 .selections
19496 .all::<Point>(&display_map)
19497 .iter()
19498 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
19499
19500 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
19501 }
19502
19503 pub fn unfold_all(
19504 &mut self,
19505 _: &actions::UnfoldAll,
19506 _window: &mut Window,
19507 cx: &mut Context<Self>,
19508 ) {
19509 if self.buffer.read(cx).is_singleton() {
19510 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19511 self.unfold_ranges(
19512 &[MultiBufferOffset(0)..display_map.buffer_snapshot().len()],
19513 true,
19514 true,
19515 cx,
19516 );
19517 } else {
19518 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
19519 editor
19520 .update(cx, |editor, cx| {
19521 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
19522 editor.unfold_buffer(buffer_id, cx);
19523 }
19524 })
19525 .ok();
19526 });
19527 }
19528 }
19529
19530 pub fn fold_selected_ranges(
19531 &mut self,
19532 _: &FoldSelectedRanges,
19533 window: &mut Window,
19534 cx: &mut Context<Self>,
19535 ) {
19536 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19537 let selections = self.selections.all_adjusted(&display_map);
19538 let ranges = selections
19539 .into_iter()
19540 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
19541 .collect::<Vec<_>>();
19542 self.fold_creases(ranges, true, window, cx);
19543 }
19544
19545 pub fn fold_ranges<T: ToOffset + Clone>(
19546 &mut self,
19547 ranges: Vec<Range<T>>,
19548 auto_scroll: bool,
19549 window: &mut Window,
19550 cx: &mut Context<Self>,
19551 ) {
19552 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
19553 let ranges = ranges
19554 .into_iter()
19555 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
19556 .collect::<Vec<_>>();
19557 self.fold_creases(ranges, auto_scroll, window, cx);
19558 }
19559
19560 pub fn fold_creases<T: ToOffset + Clone>(
19561 &mut self,
19562 creases: Vec<Crease<T>>,
19563 auto_scroll: bool,
19564 _window: &mut Window,
19565 cx: &mut Context<Self>,
19566 ) {
19567 if creases.is_empty() {
19568 return;
19569 }
19570
19571 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
19572
19573 if auto_scroll {
19574 self.request_autoscroll(Autoscroll::fit(), cx);
19575 }
19576
19577 cx.notify();
19578
19579 self.scrollbar_marker_state.dirty = true;
19580 self.folds_did_change(cx);
19581 }
19582
19583 /// Removes any folds whose ranges intersect any of the given ranges.
19584 pub fn unfold_ranges<T: ToOffset + Clone>(
19585 &mut self,
19586 ranges: &[Range<T>],
19587 inclusive: bool,
19588 auto_scroll: bool,
19589 cx: &mut Context<Self>,
19590 ) {
19591 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19592 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
19593 });
19594 self.folds_did_change(cx);
19595 }
19596
19597 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19598 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
19599 return;
19600 }
19601
19602 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19603 self.display_map.update(cx, |display_map, cx| {
19604 display_map.fold_buffers([buffer_id], cx)
19605 });
19606
19607 let snapshot = self.display_snapshot(cx);
19608 self.selections.change_with(&snapshot, |selections| {
19609 selections.remove_selections_from_buffer(buffer_id);
19610 });
19611
19612 cx.emit(EditorEvent::BufferFoldToggled {
19613 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
19614 folded: true,
19615 });
19616 cx.notify();
19617 }
19618
19619 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19620 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
19621 return;
19622 }
19623 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
19624 self.display_map.update(cx, |display_map, cx| {
19625 display_map.unfold_buffers([buffer_id], cx);
19626 });
19627 cx.emit(EditorEvent::BufferFoldToggled {
19628 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
19629 folded: false,
19630 });
19631 cx.notify();
19632 }
19633
19634 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
19635 self.display_map.read(cx).is_buffer_folded(buffer)
19636 }
19637
19638 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
19639 self.display_map.read(cx).folded_buffers()
19640 }
19641
19642 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
19643 self.display_map.update(cx, |display_map, cx| {
19644 display_map.disable_header_for_buffer(buffer_id, cx);
19645 });
19646 cx.notify();
19647 }
19648
19649 /// Removes any folds with the given ranges.
19650 pub fn remove_folds_with_type<T: ToOffset + Clone>(
19651 &mut self,
19652 ranges: &[Range<T>],
19653 type_id: TypeId,
19654 auto_scroll: bool,
19655 cx: &mut Context<Self>,
19656 ) {
19657 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
19658 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
19659 });
19660 self.folds_did_change(cx);
19661 }
19662
19663 fn remove_folds_with<T: ToOffset + Clone>(
19664 &mut self,
19665 ranges: &[Range<T>],
19666 auto_scroll: bool,
19667 cx: &mut Context<Self>,
19668 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
19669 ) {
19670 if ranges.is_empty() {
19671 return;
19672 }
19673
19674 let mut buffers_affected = HashSet::default();
19675 let multi_buffer = self.buffer().read(cx);
19676 for range in ranges {
19677 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
19678 buffers_affected.insert(buffer.read(cx).remote_id());
19679 };
19680 }
19681
19682 self.display_map.update(cx, update);
19683
19684 if auto_scroll {
19685 self.request_autoscroll(Autoscroll::fit(), cx);
19686 }
19687
19688 cx.notify();
19689 self.scrollbar_marker_state.dirty = true;
19690 self.active_indent_guides_state.dirty = true;
19691 }
19692
19693 pub fn update_renderer_widths(
19694 &mut self,
19695 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
19696 cx: &mut Context<Self>,
19697 ) -> bool {
19698 self.display_map
19699 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
19700 }
19701
19702 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
19703 self.display_map.read(cx).fold_placeholder.clone()
19704 }
19705
19706 pub fn set_use_base_text_line_numbers(&mut self, show: bool, _cx: &mut Context<Self>) {
19707 self.use_base_text_line_numbers = show;
19708 }
19709
19710 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
19711 self.buffer.update(cx, |buffer, cx| {
19712 buffer.set_all_diff_hunks_expanded(cx);
19713 });
19714 }
19715
19716 pub fn expand_all_diff_hunks(
19717 &mut self,
19718 _: &ExpandAllDiffHunks,
19719 _window: &mut Window,
19720 cx: &mut Context<Self>,
19721 ) {
19722 self.buffer.update(cx, |buffer, cx| {
19723 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19724 });
19725 }
19726
19727 pub fn collapse_all_diff_hunks(
19728 &mut self,
19729 _: &CollapseAllDiffHunks,
19730 _window: &mut Window,
19731 cx: &mut Context<Self>,
19732 ) {
19733 self.buffer.update(cx, |buffer, cx| {
19734 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
19735 });
19736 }
19737
19738 pub fn toggle_selected_diff_hunks(
19739 &mut self,
19740 _: &ToggleSelectedDiffHunks,
19741 _window: &mut Window,
19742 cx: &mut Context<Self>,
19743 ) {
19744 let ranges: Vec<_> = self
19745 .selections
19746 .disjoint_anchors()
19747 .iter()
19748 .map(|s| s.range())
19749 .collect();
19750 self.toggle_diff_hunks_in_ranges(ranges, cx);
19751 }
19752
19753 pub fn diff_hunks_in_ranges<'a>(
19754 &'a self,
19755 ranges: &'a [Range<Anchor>],
19756 buffer: &'a MultiBufferSnapshot,
19757 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19758 ranges.iter().flat_map(move |range| {
19759 let end_excerpt_id = range.end.excerpt_id;
19760 let range = range.to_point(buffer);
19761 let mut peek_end = range.end;
19762 if range.end.row < buffer.max_row().0 {
19763 peek_end = Point::new(range.end.row + 1, 0);
19764 }
19765 buffer
19766 .diff_hunks_in_range(range.start..peek_end)
19767 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19768 })
19769 }
19770
19771 pub fn has_stageable_diff_hunks_in_ranges(
19772 &self,
19773 ranges: &[Range<Anchor>],
19774 snapshot: &MultiBufferSnapshot,
19775 ) -> bool {
19776 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19777 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19778 }
19779
19780 pub fn toggle_staged_selected_diff_hunks(
19781 &mut self,
19782 _: &::git::ToggleStaged,
19783 _: &mut Window,
19784 cx: &mut Context<Self>,
19785 ) {
19786 let snapshot = self.buffer.read(cx).snapshot(cx);
19787 let ranges: Vec<_> = self
19788 .selections
19789 .disjoint_anchors()
19790 .iter()
19791 .map(|s| s.range())
19792 .collect();
19793 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19794 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19795 }
19796
19797 pub fn set_render_diff_hunk_controls(
19798 &mut self,
19799 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19800 cx: &mut Context<Self>,
19801 ) {
19802 self.render_diff_hunk_controls = render_diff_hunk_controls;
19803 cx.notify();
19804 }
19805
19806 pub fn stage_and_next(
19807 &mut self,
19808 _: &::git::StageAndNext,
19809 window: &mut Window,
19810 cx: &mut Context<Self>,
19811 ) {
19812 self.do_stage_or_unstage_and_next(true, window, cx);
19813 }
19814
19815 pub fn unstage_and_next(
19816 &mut self,
19817 _: &::git::UnstageAndNext,
19818 window: &mut Window,
19819 cx: &mut Context<Self>,
19820 ) {
19821 self.do_stage_or_unstage_and_next(false, window, cx);
19822 }
19823
19824 pub fn stage_or_unstage_diff_hunks(
19825 &mut self,
19826 stage: bool,
19827 ranges: Vec<Range<Anchor>>,
19828 cx: &mut Context<Self>,
19829 ) {
19830 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19831 cx.spawn(async move |this, cx| {
19832 task.await?;
19833 this.update(cx, |this, cx| {
19834 let snapshot = this.buffer.read(cx).snapshot(cx);
19835 let chunk_by = this
19836 .diff_hunks_in_ranges(&ranges, &snapshot)
19837 .chunk_by(|hunk| hunk.buffer_id);
19838 for (buffer_id, hunks) in &chunk_by {
19839 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19840 }
19841 })
19842 })
19843 .detach_and_log_err(cx);
19844 }
19845
19846 fn save_buffers_for_ranges_if_needed(
19847 &mut self,
19848 ranges: &[Range<Anchor>],
19849 cx: &mut Context<Editor>,
19850 ) -> Task<Result<()>> {
19851 let multibuffer = self.buffer.read(cx);
19852 let snapshot = multibuffer.read(cx);
19853 let buffer_ids: HashSet<_> = ranges
19854 .iter()
19855 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19856 .collect();
19857 drop(snapshot);
19858
19859 let mut buffers = HashSet::default();
19860 for buffer_id in buffer_ids {
19861 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19862 let buffer = buffer_entity.read(cx);
19863 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19864 {
19865 buffers.insert(buffer_entity);
19866 }
19867 }
19868 }
19869
19870 if let Some(project) = &self.project {
19871 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19872 } else {
19873 Task::ready(Ok(()))
19874 }
19875 }
19876
19877 fn do_stage_or_unstage_and_next(
19878 &mut self,
19879 stage: bool,
19880 window: &mut Window,
19881 cx: &mut Context<Self>,
19882 ) {
19883 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19884
19885 if ranges.iter().any(|range| range.start != range.end) {
19886 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19887 return;
19888 }
19889
19890 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19891 let snapshot = self.snapshot(window, cx);
19892 let position = self
19893 .selections
19894 .newest::<Point>(&snapshot.display_snapshot)
19895 .head();
19896 let mut row = snapshot
19897 .buffer_snapshot()
19898 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19899 .find(|hunk| hunk.row_range.start.0 > position.row)
19900 .map(|hunk| hunk.row_range.start);
19901
19902 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19903 // Outside of the project diff editor, wrap around to the beginning.
19904 if !all_diff_hunks_expanded {
19905 row = row.or_else(|| {
19906 snapshot
19907 .buffer_snapshot()
19908 .diff_hunks_in_range(Point::zero()..position)
19909 .find(|hunk| hunk.row_range.end.0 < position.row)
19910 .map(|hunk| hunk.row_range.start)
19911 });
19912 }
19913
19914 if let Some(row) = row {
19915 let destination = Point::new(row.0, 0);
19916 let autoscroll = Autoscroll::center();
19917
19918 self.unfold_ranges(&[destination..destination], false, false, cx);
19919 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19920 s.select_ranges([destination..destination]);
19921 });
19922 }
19923 }
19924
19925 fn do_stage_or_unstage(
19926 &self,
19927 stage: bool,
19928 buffer_id: BufferId,
19929 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19930 cx: &mut App,
19931 ) -> Option<()> {
19932 let project = self.project()?;
19933 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19934 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19935 let buffer_snapshot = buffer.read(cx).snapshot();
19936 let file_exists = buffer_snapshot
19937 .file()
19938 .is_some_and(|file| file.disk_state().exists());
19939 diff.update(cx, |diff, cx| {
19940 diff.stage_or_unstage_hunks(
19941 stage,
19942 &hunks
19943 .map(|hunk| buffer_diff::DiffHunk {
19944 buffer_range: hunk.buffer_range,
19945 // We don't need to pass in word diffs here because they're only used for rendering and
19946 // this function changes internal state
19947 base_word_diffs: Vec::default(),
19948 buffer_word_diffs: Vec::default(),
19949 diff_base_byte_range: hunk.diff_base_byte_range.start.0
19950 ..hunk.diff_base_byte_range.end.0,
19951 secondary_status: hunk.secondary_status,
19952 range: Point::zero()..Point::zero(), // unused
19953 })
19954 .collect::<Vec<_>>(),
19955 &buffer_snapshot,
19956 file_exists,
19957 cx,
19958 )
19959 });
19960 None
19961 }
19962
19963 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19964 let ranges: Vec<_> = self
19965 .selections
19966 .disjoint_anchors()
19967 .iter()
19968 .map(|s| s.range())
19969 .collect();
19970 self.buffer
19971 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19972 }
19973
19974 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19975 self.buffer.update(cx, |buffer, cx| {
19976 let ranges = vec![Anchor::min()..Anchor::max()];
19977 if !buffer.all_diff_hunks_expanded()
19978 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19979 {
19980 buffer.collapse_diff_hunks(ranges, cx);
19981 true
19982 } else {
19983 false
19984 }
19985 })
19986 }
19987
19988 fn has_any_expanded_diff_hunks(&self, cx: &App) -> bool {
19989 if self.buffer.read(cx).all_diff_hunks_expanded() {
19990 return true;
19991 }
19992 let ranges = vec![Anchor::min()..Anchor::max()];
19993 self.buffer
19994 .read(cx)
19995 .has_expanded_diff_hunks_in_ranges(&ranges, cx)
19996 }
19997
19998 fn toggle_diff_hunks_in_ranges(
19999 &mut self,
20000 ranges: Vec<Range<Anchor>>,
20001 cx: &mut Context<Editor>,
20002 ) {
20003 self.buffer.update(cx, |buffer, cx| {
20004 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
20005 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
20006 })
20007 }
20008
20009 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
20010 self.buffer.update(cx, |buffer, cx| {
20011 let snapshot = buffer.snapshot(cx);
20012 let excerpt_id = range.end.excerpt_id;
20013 let point_range = range.to_point(&snapshot);
20014 let expand = !buffer.single_hunk_is_expanded(range, cx);
20015 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
20016 })
20017 }
20018
20019 pub(crate) fn apply_all_diff_hunks(
20020 &mut self,
20021 _: &ApplyAllDiffHunks,
20022 window: &mut Window,
20023 cx: &mut Context<Self>,
20024 ) {
20025 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20026
20027 let buffers = self.buffer.read(cx).all_buffers();
20028 for branch_buffer in buffers {
20029 branch_buffer.update(cx, |branch_buffer, cx| {
20030 branch_buffer.merge_into_base(Vec::new(), cx);
20031 });
20032 }
20033
20034 if let Some(project) = self.project.clone() {
20035 self.save(
20036 SaveOptions {
20037 format: true,
20038 autosave: false,
20039 },
20040 project,
20041 window,
20042 cx,
20043 )
20044 .detach_and_log_err(cx);
20045 }
20046 }
20047
20048 pub(crate) fn apply_selected_diff_hunks(
20049 &mut self,
20050 _: &ApplyDiffHunk,
20051 window: &mut Window,
20052 cx: &mut Context<Self>,
20053 ) {
20054 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20055 let snapshot = self.snapshot(window, cx);
20056 let hunks = snapshot.hunks_for_ranges(
20057 self.selections
20058 .all(&snapshot.display_snapshot)
20059 .into_iter()
20060 .map(|selection| selection.range()),
20061 );
20062 let mut ranges_by_buffer = HashMap::default();
20063 self.transact(window, cx, |editor, _window, cx| {
20064 for hunk in hunks {
20065 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
20066 ranges_by_buffer
20067 .entry(buffer.clone())
20068 .or_insert_with(Vec::new)
20069 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
20070 }
20071 }
20072
20073 for (buffer, ranges) in ranges_by_buffer {
20074 buffer.update(cx, |buffer, cx| {
20075 buffer.merge_into_base(ranges, cx);
20076 });
20077 }
20078 });
20079
20080 if let Some(project) = self.project.clone() {
20081 self.save(
20082 SaveOptions {
20083 format: true,
20084 autosave: false,
20085 },
20086 project,
20087 window,
20088 cx,
20089 )
20090 .detach_and_log_err(cx);
20091 }
20092 }
20093
20094 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
20095 if hovered != self.gutter_hovered {
20096 self.gutter_hovered = hovered;
20097 cx.notify();
20098 }
20099 }
20100
20101 pub fn insert_blocks(
20102 &mut self,
20103 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
20104 autoscroll: Option<Autoscroll>,
20105 cx: &mut Context<Self>,
20106 ) -> Vec<CustomBlockId> {
20107 let blocks = self
20108 .display_map
20109 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
20110 if let Some(autoscroll) = autoscroll {
20111 self.request_autoscroll(autoscroll, cx);
20112 }
20113 cx.notify();
20114 blocks
20115 }
20116
20117 pub fn resize_blocks(
20118 &mut self,
20119 heights: HashMap<CustomBlockId, u32>,
20120 autoscroll: Option<Autoscroll>,
20121 cx: &mut Context<Self>,
20122 ) {
20123 self.display_map
20124 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
20125 if let Some(autoscroll) = autoscroll {
20126 self.request_autoscroll(autoscroll, cx);
20127 }
20128 cx.notify();
20129 }
20130
20131 pub fn replace_blocks(
20132 &mut self,
20133 renderers: HashMap<CustomBlockId, RenderBlock>,
20134 autoscroll: Option<Autoscroll>,
20135 cx: &mut Context<Self>,
20136 ) {
20137 self.display_map
20138 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
20139 if let Some(autoscroll) = autoscroll {
20140 self.request_autoscroll(autoscroll, cx);
20141 }
20142 cx.notify();
20143 }
20144
20145 pub fn remove_blocks(
20146 &mut self,
20147 block_ids: HashSet<CustomBlockId>,
20148 autoscroll: Option<Autoscroll>,
20149 cx: &mut Context<Self>,
20150 ) {
20151 self.display_map.update(cx, |display_map, cx| {
20152 display_map.remove_blocks(block_ids, cx)
20153 });
20154 if let Some(autoscroll) = autoscroll {
20155 self.request_autoscroll(autoscroll, cx);
20156 }
20157 cx.notify();
20158 }
20159
20160 pub fn row_for_block(
20161 &self,
20162 block_id: CustomBlockId,
20163 cx: &mut Context<Self>,
20164 ) -> Option<DisplayRow> {
20165 self.display_map
20166 .update(cx, |map, cx| map.row_for_block(block_id, cx))
20167 }
20168
20169 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
20170 self.focused_block = Some(focused_block);
20171 }
20172
20173 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
20174 self.focused_block.take()
20175 }
20176
20177 pub fn insert_creases(
20178 &mut self,
20179 creases: impl IntoIterator<Item = Crease<Anchor>>,
20180 cx: &mut Context<Self>,
20181 ) -> Vec<CreaseId> {
20182 self.display_map
20183 .update(cx, |map, cx| map.insert_creases(creases, cx))
20184 }
20185
20186 pub fn remove_creases(
20187 &mut self,
20188 ids: impl IntoIterator<Item = CreaseId>,
20189 cx: &mut Context<Self>,
20190 ) -> Vec<(CreaseId, Range<Anchor>)> {
20191 self.display_map
20192 .update(cx, |map, cx| map.remove_creases(ids, cx))
20193 }
20194
20195 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
20196 self.display_map
20197 .update(cx, |map, cx| map.snapshot(cx))
20198 .longest_row()
20199 }
20200
20201 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
20202 self.display_map
20203 .update(cx, |map, cx| map.snapshot(cx))
20204 .max_point()
20205 }
20206
20207 pub fn text(&self, cx: &App) -> String {
20208 self.buffer.read(cx).read(cx).text()
20209 }
20210
20211 pub fn is_empty(&self, cx: &App) -> bool {
20212 self.buffer.read(cx).read(cx).is_empty()
20213 }
20214
20215 pub fn text_option(&self, cx: &App) -> Option<String> {
20216 let text = self.text(cx);
20217 let text = text.trim();
20218
20219 if text.is_empty() {
20220 return None;
20221 }
20222
20223 Some(text.to_string())
20224 }
20225
20226 pub fn set_text(
20227 &mut self,
20228 text: impl Into<Arc<str>>,
20229 window: &mut Window,
20230 cx: &mut Context<Self>,
20231 ) {
20232 self.transact(window, cx, |this, _, cx| {
20233 this.buffer
20234 .read(cx)
20235 .as_singleton()
20236 .expect("you can only call set_text on editors for singleton buffers")
20237 .update(cx, |buffer, cx| buffer.set_text(text, cx));
20238 });
20239 }
20240
20241 pub fn display_text(&self, cx: &mut App) -> String {
20242 self.display_map
20243 .update(cx, |map, cx| map.snapshot(cx))
20244 .text()
20245 }
20246
20247 fn create_minimap(
20248 &self,
20249 minimap_settings: MinimapSettings,
20250 window: &mut Window,
20251 cx: &mut Context<Self>,
20252 ) -> Option<Entity<Self>> {
20253 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
20254 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
20255 }
20256
20257 fn initialize_new_minimap(
20258 &self,
20259 minimap_settings: MinimapSettings,
20260 window: &mut Window,
20261 cx: &mut Context<Self>,
20262 ) -> Entity<Self> {
20263 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
20264
20265 let mut minimap = Editor::new_internal(
20266 EditorMode::Minimap {
20267 parent: cx.weak_entity(),
20268 },
20269 self.buffer.clone(),
20270 None,
20271 Some(self.display_map.clone()),
20272 window,
20273 cx,
20274 );
20275 minimap.scroll_manager.clone_state(&self.scroll_manager);
20276 minimap.set_text_style_refinement(TextStyleRefinement {
20277 font_size: Some(MINIMAP_FONT_SIZE),
20278 font_weight: Some(MINIMAP_FONT_WEIGHT),
20279 ..Default::default()
20280 });
20281 minimap.update_minimap_configuration(minimap_settings, cx);
20282 cx.new(|_| minimap)
20283 }
20284
20285 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
20286 let current_line_highlight = minimap_settings
20287 .current_line_highlight
20288 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
20289 self.set_current_line_highlight(Some(current_line_highlight));
20290 }
20291
20292 pub fn minimap(&self) -> Option<&Entity<Self>> {
20293 self.minimap
20294 .as_ref()
20295 .filter(|_| self.minimap_visibility.visible())
20296 }
20297
20298 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
20299 let mut wrap_guides = smallvec![];
20300
20301 if self.show_wrap_guides == Some(false) {
20302 return wrap_guides;
20303 }
20304
20305 let settings = self.buffer.read(cx).language_settings(cx);
20306 if settings.show_wrap_guides {
20307 match self.soft_wrap_mode(cx) {
20308 SoftWrap::Column(soft_wrap) => {
20309 wrap_guides.push((soft_wrap as usize, true));
20310 }
20311 SoftWrap::Bounded(soft_wrap) => {
20312 wrap_guides.push((soft_wrap as usize, true));
20313 }
20314 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
20315 }
20316 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
20317 }
20318
20319 wrap_guides
20320 }
20321
20322 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
20323 let settings = self.buffer.read(cx).language_settings(cx);
20324 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
20325 match mode {
20326 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
20327 SoftWrap::None
20328 }
20329 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
20330 language_settings::SoftWrap::PreferredLineLength => {
20331 SoftWrap::Column(settings.preferred_line_length)
20332 }
20333 language_settings::SoftWrap::Bounded => {
20334 SoftWrap::Bounded(settings.preferred_line_length)
20335 }
20336 }
20337 }
20338
20339 pub fn set_soft_wrap_mode(
20340 &mut self,
20341 mode: language_settings::SoftWrap,
20342
20343 cx: &mut Context<Self>,
20344 ) {
20345 self.soft_wrap_mode_override = Some(mode);
20346 cx.notify();
20347 }
20348
20349 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
20350 self.hard_wrap = hard_wrap;
20351 cx.notify();
20352 }
20353
20354 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
20355 self.text_style_refinement = Some(style);
20356 }
20357
20358 /// called by the Element so we know what style we were most recently rendered with.
20359 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
20360 // We intentionally do not inform the display map about the minimap style
20361 // so that wrapping is not recalculated and stays consistent for the editor
20362 // and its linked minimap.
20363 if !self.mode.is_minimap() {
20364 let font = style.text.font();
20365 let font_size = style.text.font_size.to_pixels(window.rem_size());
20366 let display_map = self
20367 .placeholder_display_map
20368 .as_ref()
20369 .filter(|_| self.is_empty(cx))
20370 .unwrap_or(&self.display_map);
20371
20372 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
20373 }
20374 self.style = Some(style);
20375 }
20376
20377 pub fn style(&mut self, cx: &App) -> &EditorStyle {
20378 if self.style.is_none() {
20379 self.style = Some(self.create_style(cx));
20380 }
20381 self.style.as_ref().unwrap()
20382 }
20383
20384 // Called by the element. This method is not designed to be called outside of the editor
20385 // element's layout code because it does not notify when rewrapping is computed synchronously.
20386 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
20387 if self.is_empty(cx) {
20388 self.placeholder_display_map
20389 .as_ref()
20390 .map_or(false, |display_map| {
20391 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
20392 })
20393 } else {
20394 self.display_map
20395 .update(cx, |map, cx| map.set_wrap_width(width, cx))
20396 }
20397 }
20398
20399 pub fn set_soft_wrap(&mut self) {
20400 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
20401 }
20402
20403 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
20404 if self.soft_wrap_mode_override.is_some() {
20405 self.soft_wrap_mode_override.take();
20406 } else {
20407 let soft_wrap = match self.soft_wrap_mode(cx) {
20408 SoftWrap::GitDiff => return,
20409 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
20410 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
20411 language_settings::SoftWrap::None
20412 }
20413 };
20414 self.soft_wrap_mode_override = Some(soft_wrap);
20415 }
20416 cx.notify();
20417 }
20418
20419 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
20420 let Some(workspace) = self.workspace() else {
20421 return;
20422 };
20423 let fs = workspace.read(cx).app_state().fs.clone();
20424 let current_show = TabBarSettings::get_global(cx).show;
20425 update_settings_file(fs, cx, move |setting, _| {
20426 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
20427 });
20428 }
20429
20430 pub fn toggle_indent_guides(
20431 &mut self,
20432 _: &ToggleIndentGuides,
20433 _: &mut Window,
20434 cx: &mut Context<Self>,
20435 ) {
20436 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
20437 self.buffer
20438 .read(cx)
20439 .language_settings(cx)
20440 .indent_guides
20441 .enabled
20442 });
20443 self.show_indent_guides = Some(!currently_enabled);
20444 cx.notify();
20445 }
20446
20447 fn should_show_indent_guides(&self) -> Option<bool> {
20448 self.show_indent_guides
20449 }
20450
20451 pub fn disable_indent_guides_for_buffer(
20452 &mut self,
20453 buffer_id: BufferId,
20454 cx: &mut Context<Self>,
20455 ) {
20456 self.buffers_with_disabled_indent_guides.insert(buffer_id);
20457 cx.notify();
20458 }
20459
20460 pub fn has_indent_guides_disabled_for_buffer(&self, buffer_id: BufferId) -> bool {
20461 self.buffers_with_disabled_indent_guides
20462 .contains(&buffer_id)
20463 }
20464
20465 pub fn toggle_line_numbers(
20466 &mut self,
20467 _: &ToggleLineNumbers,
20468 _: &mut Window,
20469 cx: &mut Context<Self>,
20470 ) {
20471 let mut editor_settings = EditorSettings::get_global(cx).clone();
20472 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
20473 EditorSettings::override_global(editor_settings, cx);
20474 }
20475
20476 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
20477 if let Some(show_line_numbers) = self.show_line_numbers {
20478 return show_line_numbers;
20479 }
20480 EditorSettings::get_global(cx).gutter.line_numbers
20481 }
20482
20483 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
20484 match (
20485 self.use_relative_line_numbers,
20486 EditorSettings::get_global(cx).relative_line_numbers,
20487 ) {
20488 (None, setting) => setting,
20489 (Some(false), _) => RelativeLineNumbers::Disabled,
20490 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
20491 (Some(true), _) => RelativeLineNumbers::Enabled,
20492 }
20493 }
20494
20495 pub fn toggle_relative_line_numbers(
20496 &mut self,
20497 _: &ToggleRelativeLineNumbers,
20498 _: &mut Window,
20499 cx: &mut Context<Self>,
20500 ) {
20501 let is_relative = self.relative_line_numbers(cx);
20502 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
20503 }
20504
20505 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
20506 self.use_relative_line_numbers = is_relative;
20507 cx.notify();
20508 }
20509
20510 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
20511 self.show_gutter = show_gutter;
20512 cx.notify();
20513 }
20514
20515 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
20516 self.show_scrollbars = ScrollbarAxes {
20517 horizontal: show,
20518 vertical: show,
20519 };
20520 cx.notify();
20521 }
20522
20523 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20524 self.show_scrollbars.vertical = show;
20525 cx.notify();
20526 }
20527
20528 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
20529 self.show_scrollbars.horizontal = show;
20530 cx.notify();
20531 }
20532
20533 pub fn set_minimap_visibility(
20534 &mut self,
20535 minimap_visibility: MinimapVisibility,
20536 window: &mut Window,
20537 cx: &mut Context<Self>,
20538 ) {
20539 if self.minimap_visibility != minimap_visibility {
20540 if minimap_visibility.visible() && self.minimap.is_none() {
20541 let minimap_settings = EditorSettings::get_global(cx).minimap;
20542 self.minimap =
20543 self.create_minimap(minimap_settings.with_show_override(), window, cx);
20544 }
20545 self.minimap_visibility = minimap_visibility;
20546 cx.notify();
20547 }
20548 }
20549
20550 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20551 self.set_show_scrollbars(false, cx);
20552 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
20553 }
20554
20555 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
20556 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
20557 }
20558
20559 /// Normally the text in full mode and auto height editors is padded on the
20560 /// left side by roughly half a character width for improved hit testing.
20561 ///
20562 /// Use this method to disable this for cases where this is not wanted (e.g.
20563 /// if you want to align the editor text with some other text above or below)
20564 /// or if you want to add this padding to single-line editors.
20565 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
20566 self.offset_content = offset_content;
20567 cx.notify();
20568 }
20569
20570 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
20571 self.show_line_numbers = Some(show_line_numbers);
20572 cx.notify();
20573 }
20574
20575 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
20576 self.disable_expand_excerpt_buttons = true;
20577 cx.notify();
20578 }
20579
20580 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
20581 self.show_git_diff_gutter = Some(show_git_diff_gutter);
20582 cx.notify();
20583 }
20584
20585 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
20586 self.show_code_actions = Some(show_code_actions);
20587 cx.notify();
20588 }
20589
20590 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
20591 self.show_runnables = Some(show_runnables);
20592 cx.notify();
20593 }
20594
20595 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
20596 self.show_breakpoints = Some(show_breakpoints);
20597 cx.notify();
20598 }
20599
20600 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
20601 if self.display_map.read(cx).masked != masked {
20602 self.display_map.update(cx, |map, _| map.masked = masked);
20603 }
20604 cx.notify()
20605 }
20606
20607 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
20608 self.show_wrap_guides = Some(show_wrap_guides);
20609 cx.notify();
20610 }
20611
20612 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
20613 self.show_indent_guides = Some(show_indent_guides);
20614 cx.notify();
20615 }
20616
20617 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
20618 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
20619 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
20620 && let Some(dir) = file.abs_path(cx).parent()
20621 {
20622 return Some(dir.to_owned());
20623 }
20624 }
20625
20626 None
20627 }
20628
20629 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
20630 self.active_excerpt(cx)?
20631 .1
20632 .read(cx)
20633 .file()
20634 .and_then(|f| f.as_local())
20635 }
20636
20637 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
20638 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20639 let buffer = buffer.read(cx);
20640 if let Some(project_path) = buffer.project_path(cx) {
20641 let project = self.project()?.read(cx);
20642 project.absolute_path(&project_path, cx)
20643 } else {
20644 buffer
20645 .file()
20646 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
20647 }
20648 })
20649 }
20650
20651 pub fn reveal_in_finder(
20652 &mut self,
20653 _: &RevealInFileManager,
20654 _window: &mut Window,
20655 cx: &mut Context<Self>,
20656 ) {
20657 if let Some(target) = self.target_file(cx) {
20658 cx.reveal_path(&target.abs_path(cx));
20659 }
20660 }
20661
20662 pub fn copy_path(
20663 &mut self,
20664 _: &zed_actions::workspace::CopyPath,
20665 _window: &mut Window,
20666 cx: &mut Context<Self>,
20667 ) {
20668 if let Some(path) = self.target_file_abs_path(cx)
20669 && let Some(path) = path.to_str()
20670 {
20671 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20672 } else {
20673 cx.propagate();
20674 }
20675 }
20676
20677 pub fn copy_relative_path(
20678 &mut self,
20679 _: &zed_actions::workspace::CopyRelativePath,
20680 _window: &mut Window,
20681 cx: &mut Context<Self>,
20682 ) {
20683 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20684 let project = self.project()?.read(cx);
20685 let path = buffer.read(cx).file()?.path();
20686 let path = path.display(project.path_style(cx));
20687 Some(path)
20688 }) {
20689 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
20690 } else {
20691 cx.propagate();
20692 }
20693 }
20694
20695 /// Returns the project path for the editor's buffer, if any buffer is
20696 /// opened in the editor.
20697 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
20698 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
20699 buffer.read(cx).project_path(cx)
20700 } else {
20701 None
20702 }
20703 }
20704
20705 // Returns true if the editor handled a go-to-line request
20706 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
20707 maybe!({
20708 let breakpoint_store = self.breakpoint_store.as_ref()?;
20709
20710 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
20711 else {
20712 self.clear_row_highlights::<ActiveDebugLine>();
20713 return None;
20714 };
20715
20716 let position = active_stack_frame.position;
20717 let buffer_id = position.buffer_id?;
20718 let snapshot = self
20719 .project
20720 .as_ref()?
20721 .read(cx)
20722 .buffer_for_id(buffer_id, cx)?
20723 .read(cx)
20724 .snapshot();
20725
20726 let mut handled = false;
20727 for (id, ExcerptRange { context, .. }) in
20728 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
20729 {
20730 if context.start.cmp(&position, &snapshot).is_ge()
20731 || context.end.cmp(&position, &snapshot).is_lt()
20732 {
20733 continue;
20734 }
20735 let snapshot = self.buffer.read(cx).snapshot(cx);
20736 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
20737
20738 handled = true;
20739 self.clear_row_highlights::<ActiveDebugLine>();
20740
20741 self.go_to_line::<ActiveDebugLine>(
20742 multibuffer_anchor,
20743 Some(cx.theme().colors().editor_debugger_active_line_background),
20744 window,
20745 cx,
20746 );
20747
20748 cx.notify();
20749 }
20750
20751 handled.then_some(())
20752 })
20753 .is_some()
20754 }
20755
20756 pub fn copy_file_name_without_extension(
20757 &mut self,
20758 _: &CopyFileNameWithoutExtension,
20759 _: &mut Window,
20760 cx: &mut Context<Self>,
20761 ) {
20762 if let Some(file_stem) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20763 let file = buffer.read(cx).file()?;
20764 file.path().file_stem()
20765 }) {
20766 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
20767 }
20768 }
20769
20770 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
20771 if let Some(file_name) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
20772 let file = buffer.read(cx).file()?;
20773 Some(file.file_name(cx))
20774 }) {
20775 cx.write_to_clipboard(ClipboardItem::new_string(file_name.to_string()));
20776 }
20777 }
20778
20779 pub fn toggle_git_blame(
20780 &mut self,
20781 _: &::git::Blame,
20782 window: &mut Window,
20783 cx: &mut Context<Self>,
20784 ) {
20785 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20786
20787 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20788 self.start_git_blame(true, window, cx);
20789 }
20790
20791 cx.notify();
20792 }
20793
20794 pub fn toggle_git_blame_inline(
20795 &mut self,
20796 _: &ToggleGitBlameInline,
20797 window: &mut Window,
20798 cx: &mut Context<Self>,
20799 ) {
20800 self.toggle_git_blame_inline_internal(true, window, cx);
20801 cx.notify();
20802 }
20803
20804 pub fn open_git_blame_commit(
20805 &mut self,
20806 _: &OpenGitBlameCommit,
20807 window: &mut Window,
20808 cx: &mut Context<Self>,
20809 ) {
20810 self.open_git_blame_commit_internal(window, cx);
20811 }
20812
20813 fn open_git_blame_commit_internal(
20814 &mut self,
20815 window: &mut Window,
20816 cx: &mut Context<Self>,
20817 ) -> Option<()> {
20818 let blame = self.blame.as_ref()?;
20819 let snapshot = self.snapshot(window, cx);
20820 let cursor = self
20821 .selections
20822 .newest::<Point>(&snapshot.display_snapshot)
20823 .head();
20824 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20825 let (_, blame_entry) = blame
20826 .update(cx, |blame, cx| {
20827 blame
20828 .blame_for_rows(
20829 &[RowInfo {
20830 buffer_id: Some(buffer.remote_id()),
20831 buffer_row: Some(point.row),
20832 ..Default::default()
20833 }],
20834 cx,
20835 )
20836 .next()
20837 })
20838 .flatten()?;
20839 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20840 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20841 let workspace = self.workspace()?.downgrade();
20842 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20843 None
20844 }
20845
20846 pub fn git_blame_inline_enabled(&self) -> bool {
20847 self.git_blame_inline_enabled
20848 }
20849
20850 pub fn toggle_selection_menu(
20851 &mut self,
20852 _: &ToggleSelectionMenu,
20853 _: &mut Window,
20854 cx: &mut Context<Self>,
20855 ) {
20856 self.show_selection_menu = self
20857 .show_selection_menu
20858 .map(|show_selections_menu| !show_selections_menu)
20859 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20860
20861 cx.notify();
20862 }
20863
20864 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20865 self.show_selection_menu
20866 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20867 }
20868
20869 fn start_git_blame(
20870 &mut self,
20871 user_triggered: bool,
20872 window: &mut Window,
20873 cx: &mut Context<Self>,
20874 ) {
20875 if let Some(project) = self.project() {
20876 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20877 && buffer.read(cx).file().is_none()
20878 {
20879 return;
20880 }
20881
20882 let focused = self.focus_handle(cx).contains_focused(window, cx);
20883
20884 let project = project.clone();
20885 let blame = cx
20886 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20887 self.blame_subscription =
20888 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20889 self.blame = Some(blame);
20890 }
20891 }
20892
20893 fn toggle_git_blame_inline_internal(
20894 &mut self,
20895 user_triggered: bool,
20896 window: &mut Window,
20897 cx: &mut Context<Self>,
20898 ) {
20899 if self.git_blame_inline_enabled {
20900 self.git_blame_inline_enabled = false;
20901 self.show_git_blame_inline = false;
20902 self.show_git_blame_inline_delay_task.take();
20903 } else {
20904 self.git_blame_inline_enabled = true;
20905 self.start_git_blame_inline(user_triggered, window, cx);
20906 }
20907
20908 cx.notify();
20909 }
20910
20911 fn start_git_blame_inline(
20912 &mut self,
20913 user_triggered: bool,
20914 window: &mut Window,
20915 cx: &mut Context<Self>,
20916 ) {
20917 self.start_git_blame(user_triggered, window, cx);
20918
20919 if ProjectSettings::get_global(cx)
20920 .git
20921 .inline_blame_delay()
20922 .is_some()
20923 {
20924 self.start_inline_blame_timer(window, cx);
20925 } else {
20926 self.show_git_blame_inline = true
20927 }
20928 }
20929
20930 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20931 self.blame.as_ref()
20932 }
20933
20934 pub fn show_git_blame_gutter(&self) -> bool {
20935 self.show_git_blame_gutter
20936 }
20937
20938 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20939 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20940 }
20941
20942 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20943 self.show_git_blame_inline
20944 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20945 && !self.newest_selection_head_on_empty_line(cx)
20946 && self.has_blame_entries(cx)
20947 }
20948
20949 fn has_blame_entries(&self, cx: &App) -> bool {
20950 self.blame()
20951 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20952 }
20953
20954 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20955 let cursor_anchor = self.selections.newest_anchor().head();
20956
20957 let snapshot = self.buffer.read(cx).snapshot(cx);
20958 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20959
20960 snapshot.line_len(buffer_row) == 0
20961 }
20962
20963 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20964 let buffer_and_selection = maybe!({
20965 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20966 let selection_range = selection.range();
20967
20968 let multi_buffer = self.buffer().read(cx);
20969 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20970 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20971
20972 let (buffer, range, _) = if selection.reversed {
20973 buffer_ranges.first()
20974 } else {
20975 buffer_ranges.last()
20976 }?;
20977
20978 let start_row_in_buffer = text::ToPoint::to_point(&range.start, buffer).row;
20979 let end_row_in_buffer = text::ToPoint::to_point(&range.end, buffer).row;
20980
20981 let Some(buffer_diff) = multi_buffer.diff_for(buffer.remote_id()) else {
20982 let selection = start_row_in_buffer..end_row_in_buffer;
20983
20984 return Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection));
20985 };
20986
20987 let buffer_diff_snapshot = buffer_diff.read(cx).snapshot(cx);
20988
20989 Some((
20990 multi_buffer.buffer(buffer.remote_id()).unwrap(),
20991 buffer_diff_snapshot.row_to_base_text_row(start_row_in_buffer, buffer)
20992 ..buffer_diff_snapshot.row_to_base_text_row(end_row_in_buffer, buffer),
20993 ))
20994 });
20995
20996 let Some((buffer, selection)) = buffer_and_selection else {
20997 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20998 };
20999
21000 let Some(project) = self.project() else {
21001 return Task::ready(Err(anyhow!("editor does not have project")));
21002 };
21003
21004 project.update(cx, |project, cx| {
21005 project.get_permalink_to_line(&buffer, selection, cx)
21006 })
21007 }
21008
21009 pub fn copy_permalink_to_line(
21010 &mut self,
21011 _: &CopyPermalinkToLine,
21012 window: &mut Window,
21013 cx: &mut Context<Self>,
21014 ) {
21015 let permalink_task = self.get_permalink_to_line(cx);
21016 let workspace = self.workspace();
21017
21018 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21019 Ok(permalink) => {
21020 cx.update(|_, cx| {
21021 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
21022 })
21023 .ok();
21024 }
21025 Err(err) => {
21026 let message = format!("Failed to copy permalink: {err}");
21027
21028 anyhow::Result::<()>::Err(err).log_err();
21029
21030 if let Some(workspace) = workspace {
21031 workspace
21032 .update_in(cx, |workspace, _, cx| {
21033 struct CopyPermalinkToLine;
21034
21035 workspace.show_toast(
21036 Toast::new(
21037 NotificationId::unique::<CopyPermalinkToLine>(),
21038 message,
21039 ),
21040 cx,
21041 )
21042 })
21043 .ok();
21044 }
21045 }
21046 })
21047 .detach();
21048 }
21049
21050 pub fn copy_file_location(
21051 &mut self,
21052 _: &CopyFileLocation,
21053 _: &mut Window,
21054 cx: &mut Context<Self>,
21055 ) {
21056 let selection = self
21057 .selections
21058 .newest::<Point>(&self.display_snapshot(cx))
21059 .start
21060 .row
21061 + 1;
21062 if let Some(file_location) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
21063 let project = self.project()?.read(cx);
21064 let file = buffer.read(cx).file()?;
21065 let path = file.path().display(project.path_style(cx));
21066
21067 Some(format!("{path}:{selection}"))
21068 }) {
21069 cx.write_to_clipboard(ClipboardItem::new_string(file_location));
21070 }
21071 }
21072
21073 pub fn open_permalink_to_line(
21074 &mut self,
21075 _: &OpenPermalinkToLine,
21076 window: &mut Window,
21077 cx: &mut Context<Self>,
21078 ) {
21079 let permalink_task = self.get_permalink_to_line(cx);
21080 let workspace = self.workspace();
21081
21082 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
21083 Ok(permalink) => {
21084 cx.update(|_, cx| {
21085 cx.open_url(permalink.as_ref());
21086 })
21087 .ok();
21088 }
21089 Err(err) => {
21090 let message = format!("Failed to open permalink: {err}");
21091
21092 anyhow::Result::<()>::Err(err).log_err();
21093
21094 if let Some(workspace) = workspace {
21095 workspace
21096 .update(cx, |workspace, cx| {
21097 struct OpenPermalinkToLine;
21098
21099 workspace.show_toast(
21100 Toast::new(
21101 NotificationId::unique::<OpenPermalinkToLine>(),
21102 message,
21103 ),
21104 cx,
21105 )
21106 })
21107 .ok();
21108 }
21109 }
21110 })
21111 .detach();
21112 }
21113
21114 pub fn insert_uuid_v4(
21115 &mut self,
21116 _: &InsertUuidV4,
21117 window: &mut Window,
21118 cx: &mut Context<Self>,
21119 ) {
21120 self.insert_uuid(UuidVersion::V4, window, cx);
21121 }
21122
21123 pub fn insert_uuid_v7(
21124 &mut self,
21125 _: &InsertUuidV7,
21126 window: &mut Window,
21127 cx: &mut Context<Self>,
21128 ) {
21129 self.insert_uuid(UuidVersion::V7, window, cx);
21130 }
21131
21132 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
21133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
21134 self.transact(window, cx, |this, window, cx| {
21135 let edits = this
21136 .selections
21137 .all::<Point>(&this.display_snapshot(cx))
21138 .into_iter()
21139 .map(|selection| {
21140 let uuid = match version {
21141 UuidVersion::V4 => uuid::Uuid::new_v4(),
21142 UuidVersion::V7 => uuid::Uuid::now_v7(),
21143 };
21144
21145 (selection.range(), uuid.to_string())
21146 });
21147 this.edit(edits, cx);
21148 this.refresh_edit_prediction(true, false, window, cx);
21149 });
21150 }
21151
21152 pub fn open_selections_in_multibuffer(
21153 &mut self,
21154 _: &OpenSelectionsInMultibuffer,
21155 window: &mut Window,
21156 cx: &mut Context<Self>,
21157 ) {
21158 let multibuffer = self.buffer.read(cx);
21159
21160 let Some(buffer) = multibuffer.as_singleton() else {
21161 return;
21162 };
21163
21164 let Some(workspace) = self.workspace() else {
21165 return;
21166 };
21167
21168 let title = multibuffer.title(cx).to_string();
21169
21170 let locations = self
21171 .selections
21172 .all_anchors(&self.display_snapshot(cx))
21173 .iter()
21174 .map(|selection| {
21175 (
21176 buffer.clone(),
21177 (selection.start.text_anchor..selection.end.text_anchor)
21178 .to_point(buffer.read(cx)),
21179 )
21180 })
21181 .into_group_map();
21182
21183 cx.spawn_in(window, async move |_, cx| {
21184 workspace.update_in(cx, |workspace, window, cx| {
21185 Self::open_locations_in_multibuffer(
21186 workspace,
21187 locations,
21188 format!("Selections for '{title}'"),
21189 false,
21190 false,
21191 MultibufferSelectionMode::All,
21192 window,
21193 cx,
21194 );
21195 })
21196 })
21197 .detach();
21198 }
21199
21200 /// Adds a row highlight for the given range. If a row has multiple highlights, the
21201 /// last highlight added will be used.
21202 ///
21203 /// If the range ends at the beginning of a line, then that line will not be highlighted.
21204 pub fn highlight_rows<T: 'static>(
21205 &mut self,
21206 range: Range<Anchor>,
21207 color: Hsla,
21208 options: RowHighlightOptions,
21209 cx: &mut Context<Self>,
21210 ) {
21211 let snapshot = self.buffer().read(cx).snapshot(cx);
21212 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21213 let ix = row_highlights.binary_search_by(|highlight| {
21214 Ordering::Equal
21215 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
21216 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
21217 });
21218
21219 if let Err(mut ix) = ix {
21220 let index = post_inc(&mut self.highlight_order);
21221
21222 // If this range intersects with the preceding highlight, then merge it with
21223 // the preceding highlight. Otherwise insert a new highlight.
21224 let mut merged = false;
21225 if ix > 0 {
21226 let prev_highlight = &mut row_highlights[ix - 1];
21227 if prev_highlight
21228 .range
21229 .end
21230 .cmp(&range.start, &snapshot)
21231 .is_ge()
21232 {
21233 ix -= 1;
21234 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
21235 prev_highlight.range.end = range.end;
21236 }
21237 merged = true;
21238 prev_highlight.index = index;
21239 prev_highlight.color = color;
21240 prev_highlight.options = options;
21241 }
21242 }
21243
21244 if !merged {
21245 row_highlights.insert(
21246 ix,
21247 RowHighlight {
21248 range,
21249 index,
21250 color,
21251 options,
21252 type_id: TypeId::of::<T>(),
21253 },
21254 );
21255 }
21256
21257 // If any of the following highlights intersect with this one, merge them.
21258 while let Some(next_highlight) = row_highlights.get(ix + 1) {
21259 let highlight = &row_highlights[ix];
21260 if next_highlight
21261 .range
21262 .start
21263 .cmp(&highlight.range.end, &snapshot)
21264 .is_le()
21265 {
21266 if next_highlight
21267 .range
21268 .end
21269 .cmp(&highlight.range.end, &snapshot)
21270 .is_gt()
21271 {
21272 row_highlights[ix].range.end = next_highlight.range.end;
21273 }
21274 row_highlights.remove(ix + 1);
21275 } else {
21276 break;
21277 }
21278 }
21279 }
21280 }
21281
21282 /// Remove any highlighted row ranges of the given type that intersect the
21283 /// given ranges.
21284 pub fn remove_highlighted_rows<T: 'static>(
21285 &mut self,
21286 ranges_to_remove: Vec<Range<Anchor>>,
21287 cx: &mut Context<Self>,
21288 ) {
21289 let snapshot = self.buffer().read(cx).snapshot(cx);
21290 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
21291 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21292 row_highlights.retain(|highlight| {
21293 while let Some(range_to_remove) = ranges_to_remove.peek() {
21294 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
21295 Ordering::Less | Ordering::Equal => {
21296 ranges_to_remove.next();
21297 }
21298 Ordering::Greater => {
21299 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
21300 Ordering::Less | Ordering::Equal => {
21301 return false;
21302 }
21303 Ordering::Greater => break,
21304 }
21305 }
21306 }
21307 }
21308
21309 true
21310 })
21311 }
21312
21313 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
21314 pub fn clear_row_highlights<T: 'static>(&mut self) {
21315 self.highlighted_rows.remove(&TypeId::of::<T>());
21316 }
21317
21318 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
21319 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
21320 self.highlighted_rows
21321 .get(&TypeId::of::<T>())
21322 .map_or(&[] as &[_], |vec| vec.as_slice())
21323 .iter()
21324 .map(|highlight| (highlight.range.clone(), highlight.color))
21325 }
21326
21327 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
21328 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
21329 /// Allows to ignore certain kinds of highlights.
21330 pub fn highlighted_display_rows(
21331 &self,
21332 window: &mut Window,
21333 cx: &mut App,
21334 ) -> BTreeMap<DisplayRow, LineHighlight> {
21335 let snapshot = self.snapshot(window, cx);
21336 let mut used_highlight_orders = HashMap::default();
21337 self.highlighted_rows
21338 .iter()
21339 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
21340 .fold(
21341 BTreeMap::<DisplayRow, LineHighlight>::new(),
21342 |mut unique_rows, highlight| {
21343 let start = highlight.range.start.to_display_point(&snapshot);
21344 let end = highlight.range.end.to_display_point(&snapshot);
21345 let start_row = start.row().0;
21346 let end_row = if !highlight.range.end.text_anchor.is_max() && end.column() == 0
21347 {
21348 end.row().0.saturating_sub(1)
21349 } else {
21350 end.row().0
21351 };
21352 for row in start_row..=end_row {
21353 let used_index =
21354 used_highlight_orders.entry(row).or_insert(highlight.index);
21355 if highlight.index >= *used_index {
21356 *used_index = highlight.index;
21357 unique_rows.insert(
21358 DisplayRow(row),
21359 LineHighlight {
21360 include_gutter: highlight.options.include_gutter,
21361 border: None,
21362 background: highlight.color.into(),
21363 type_id: Some(highlight.type_id),
21364 },
21365 );
21366 }
21367 }
21368 unique_rows
21369 },
21370 )
21371 }
21372
21373 pub fn highlighted_display_row_for_autoscroll(
21374 &self,
21375 snapshot: &DisplaySnapshot,
21376 ) -> Option<DisplayRow> {
21377 self.highlighted_rows
21378 .values()
21379 .flat_map(|highlighted_rows| highlighted_rows.iter())
21380 .filter_map(|highlight| {
21381 if highlight.options.autoscroll {
21382 Some(highlight.range.start.to_display_point(snapshot).row())
21383 } else {
21384 None
21385 }
21386 })
21387 .min()
21388 }
21389
21390 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
21391 self.highlight_background::<SearchWithinRange>(
21392 ranges,
21393 |_, colors| colors.colors().editor_document_highlight_read_background,
21394 cx,
21395 )
21396 }
21397
21398 pub fn set_breadcrumb_header(&mut self, new_header: String) {
21399 self.breadcrumb_header = Some(new_header);
21400 }
21401
21402 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
21403 self.clear_background_highlights::<SearchWithinRange>(cx);
21404 }
21405
21406 pub fn highlight_background<T: 'static>(
21407 &mut self,
21408 ranges: &[Range<Anchor>],
21409 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21410 cx: &mut Context<Self>,
21411 ) {
21412 self.background_highlights.insert(
21413 HighlightKey::Type(TypeId::of::<T>()),
21414 (Arc::new(color_fetcher), Arc::from(ranges)),
21415 );
21416 self.scrollbar_marker_state.dirty = true;
21417 cx.notify();
21418 }
21419
21420 pub fn highlight_background_key<T: 'static>(
21421 &mut self,
21422 key: usize,
21423 ranges: &[Range<Anchor>],
21424 color_fetcher: impl Fn(&usize, &Theme) -> Hsla + Send + Sync + 'static,
21425 cx: &mut Context<Self>,
21426 ) {
21427 self.background_highlights.insert(
21428 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21429 (Arc::new(color_fetcher), Arc::from(ranges)),
21430 );
21431 self.scrollbar_marker_state.dirty = true;
21432 cx.notify();
21433 }
21434
21435 pub fn clear_background_highlights<T: 'static>(
21436 &mut self,
21437 cx: &mut Context<Self>,
21438 ) -> Option<BackgroundHighlight> {
21439 let text_highlights = self
21440 .background_highlights
21441 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
21442 if !text_highlights.1.is_empty() {
21443 self.scrollbar_marker_state.dirty = true;
21444 cx.notify();
21445 }
21446 Some(text_highlights)
21447 }
21448
21449 pub fn highlight_gutter<T: 'static>(
21450 &mut self,
21451 ranges: impl Into<Vec<Range<Anchor>>>,
21452 color_fetcher: fn(&App) -> Hsla,
21453 cx: &mut Context<Self>,
21454 ) {
21455 self.gutter_highlights
21456 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
21457 cx.notify();
21458 }
21459
21460 pub fn clear_gutter_highlights<T: 'static>(
21461 &mut self,
21462 cx: &mut Context<Self>,
21463 ) -> Option<GutterHighlight> {
21464 cx.notify();
21465 self.gutter_highlights.remove(&TypeId::of::<T>())
21466 }
21467
21468 pub fn insert_gutter_highlight<T: 'static>(
21469 &mut self,
21470 range: Range<Anchor>,
21471 color_fetcher: fn(&App) -> Hsla,
21472 cx: &mut Context<Self>,
21473 ) {
21474 let snapshot = self.buffer().read(cx).snapshot(cx);
21475 let mut highlights = self
21476 .gutter_highlights
21477 .remove(&TypeId::of::<T>())
21478 .map(|(_, highlights)| highlights)
21479 .unwrap_or_default();
21480 let ix = highlights.binary_search_by(|highlight| {
21481 Ordering::Equal
21482 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
21483 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
21484 });
21485 if let Err(ix) = ix {
21486 highlights.insert(ix, range);
21487 }
21488 self.gutter_highlights
21489 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
21490 }
21491
21492 pub fn remove_gutter_highlights<T: 'static>(
21493 &mut self,
21494 ranges_to_remove: Vec<Range<Anchor>>,
21495 cx: &mut Context<Self>,
21496 ) {
21497 let snapshot = self.buffer().read(cx).snapshot(cx);
21498 let Some((color_fetcher, mut gutter_highlights)) =
21499 self.gutter_highlights.remove(&TypeId::of::<T>())
21500 else {
21501 return;
21502 };
21503 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
21504 gutter_highlights.retain(|highlight| {
21505 while let Some(range_to_remove) = ranges_to_remove.peek() {
21506 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
21507 Ordering::Less | Ordering::Equal => {
21508 ranges_to_remove.next();
21509 }
21510 Ordering::Greater => {
21511 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
21512 Ordering::Less | Ordering::Equal => {
21513 return false;
21514 }
21515 Ordering::Greater => break,
21516 }
21517 }
21518 }
21519 }
21520
21521 true
21522 });
21523 self.gutter_highlights
21524 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
21525 }
21526
21527 #[cfg(feature = "test-support")]
21528 pub fn all_text_highlights(
21529 &self,
21530 window: &mut Window,
21531 cx: &mut Context<Self>,
21532 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
21533 let snapshot = self.snapshot(window, cx);
21534 self.display_map.update(cx, |display_map, _| {
21535 display_map
21536 .all_text_highlights()
21537 .map(|highlight| {
21538 let (style, ranges) = highlight.as_ref();
21539 (
21540 *style,
21541 ranges
21542 .iter()
21543 .map(|range| range.clone().to_display_points(&snapshot))
21544 .collect(),
21545 )
21546 })
21547 .collect()
21548 })
21549 }
21550
21551 #[cfg(feature = "test-support")]
21552 pub fn all_text_background_highlights(
21553 &self,
21554 window: &mut Window,
21555 cx: &mut Context<Self>,
21556 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21557 let snapshot = self.snapshot(window, cx);
21558 let buffer = &snapshot.buffer_snapshot();
21559 let start = buffer.anchor_before(MultiBufferOffset(0));
21560 let end = buffer.anchor_after(buffer.len());
21561 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
21562 }
21563
21564 #[cfg(any(test, feature = "test-support"))]
21565 pub fn sorted_background_highlights_in_range(
21566 &self,
21567 search_range: Range<Anchor>,
21568 display_snapshot: &DisplaySnapshot,
21569 theme: &Theme,
21570 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21571 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
21572 res.sort_by(|a, b| {
21573 a.0.start
21574 .cmp(&b.0.start)
21575 .then_with(|| a.0.end.cmp(&b.0.end))
21576 .then_with(|| a.1.cmp(&b.1))
21577 });
21578 res
21579 }
21580
21581 #[cfg(feature = "test-support")]
21582 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
21583 let snapshot = self.buffer().read(cx).snapshot(cx);
21584
21585 let highlights = self
21586 .background_highlights
21587 .get(&HighlightKey::Type(TypeId::of::<
21588 items::BufferSearchHighlights,
21589 >()));
21590
21591 if let Some((_color, ranges)) = highlights {
21592 ranges
21593 .iter()
21594 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
21595 .collect_vec()
21596 } else {
21597 vec![]
21598 }
21599 }
21600
21601 fn document_highlights_for_position<'a>(
21602 &'a self,
21603 position: Anchor,
21604 buffer: &'a MultiBufferSnapshot,
21605 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
21606 let read_highlights = self
21607 .background_highlights
21608 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
21609 .map(|h| &h.1);
21610 let write_highlights = self
21611 .background_highlights
21612 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
21613 .map(|h| &h.1);
21614 let left_position = position.bias_left(buffer);
21615 let right_position = position.bias_right(buffer);
21616 read_highlights
21617 .into_iter()
21618 .chain(write_highlights)
21619 .flat_map(move |ranges| {
21620 let start_ix = match ranges.binary_search_by(|probe| {
21621 let cmp = probe.end.cmp(&left_position, buffer);
21622 if cmp.is_ge() {
21623 Ordering::Greater
21624 } else {
21625 Ordering::Less
21626 }
21627 }) {
21628 Ok(i) | Err(i) => i,
21629 };
21630
21631 ranges[start_ix..]
21632 .iter()
21633 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
21634 })
21635 }
21636
21637 pub fn has_background_highlights<T: 'static>(&self) -> bool {
21638 self.background_highlights
21639 .get(&HighlightKey::Type(TypeId::of::<T>()))
21640 .is_some_and(|(_, highlights)| !highlights.is_empty())
21641 }
21642
21643 /// Returns all background highlights for a given range.
21644 ///
21645 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
21646 pub fn background_highlights_in_range(
21647 &self,
21648 search_range: Range<Anchor>,
21649 display_snapshot: &DisplaySnapshot,
21650 theme: &Theme,
21651 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21652 let mut results = Vec::new();
21653 for (color_fetcher, ranges) in self.background_highlights.values() {
21654 let start_ix = match ranges.binary_search_by(|probe| {
21655 let cmp = probe
21656 .end
21657 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21658 if cmp.is_gt() {
21659 Ordering::Greater
21660 } else {
21661 Ordering::Less
21662 }
21663 }) {
21664 Ok(i) | Err(i) => i,
21665 };
21666 for (index, range) in ranges[start_ix..].iter().enumerate() {
21667 if range
21668 .start
21669 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21670 .is_ge()
21671 {
21672 break;
21673 }
21674
21675 let color = color_fetcher(&(start_ix + index), theme);
21676 let start = range.start.to_display_point(display_snapshot);
21677 let end = range.end.to_display_point(display_snapshot);
21678 results.push((start..end, color))
21679 }
21680 }
21681 results
21682 }
21683
21684 pub fn gutter_highlights_in_range(
21685 &self,
21686 search_range: Range<Anchor>,
21687 display_snapshot: &DisplaySnapshot,
21688 cx: &App,
21689 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
21690 let mut results = Vec::new();
21691 for (color_fetcher, ranges) in self.gutter_highlights.values() {
21692 let color = color_fetcher(cx);
21693 let start_ix = match ranges.binary_search_by(|probe| {
21694 let cmp = probe
21695 .end
21696 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
21697 if cmp.is_gt() {
21698 Ordering::Greater
21699 } else {
21700 Ordering::Less
21701 }
21702 }) {
21703 Ok(i) | Err(i) => i,
21704 };
21705 for range in &ranges[start_ix..] {
21706 if range
21707 .start
21708 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
21709 .is_ge()
21710 {
21711 break;
21712 }
21713
21714 let start = range.start.to_display_point(display_snapshot);
21715 let end = range.end.to_display_point(display_snapshot);
21716 results.push((start..end, color))
21717 }
21718 }
21719 results
21720 }
21721
21722 /// Get the text ranges corresponding to the redaction query
21723 pub fn redacted_ranges(
21724 &self,
21725 search_range: Range<Anchor>,
21726 display_snapshot: &DisplaySnapshot,
21727 cx: &App,
21728 ) -> Vec<Range<DisplayPoint>> {
21729 display_snapshot
21730 .buffer_snapshot()
21731 .redacted_ranges(search_range, |file| {
21732 if let Some(file) = file {
21733 file.is_private()
21734 && EditorSettings::get(
21735 Some(SettingsLocation {
21736 worktree_id: file.worktree_id(cx),
21737 path: file.path().as_ref(),
21738 }),
21739 cx,
21740 )
21741 .redact_private_values
21742 } else {
21743 false
21744 }
21745 })
21746 .map(|range| {
21747 range.start.to_display_point(display_snapshot)
21748 ..range.end.to_display_point(display_snapshot)
21749 })
21750 .collect()
21751 }
21752
21753 pub fn highlight_text_key<T: 'static>(
21754 &mut self,
21755 key: usize,
21756 ranges: Vec<Range<Anchor>>,
21757 style: HighlightStyle,
21758 merge: bool,
21759 cx: &mut Context<Self>,
21760 ) {
21761 self.display_map.update(cx, |map, cx| {
21762 map.highlight_text(
21763 HighlightKey::TypePlus(TypeId::of::<T>(), key),
21764 ranges,
21765 style,
21766 merge,
21767 cx,
21768 );
21769 });
21770 cx.notify();
21771 }
21772
21773 pub fn highlight_text<T: 'static>(
21774 &mut self,
21775 ranges: Vec<Range<Anchor>>,
21776 style: HighlightStyle,
21777 cx: &mut Context<Self>,
21778 ) {
21779 self.display_map.update(cx, |map, cx| {
21780 map.highlight_text(
21781 HighlightKey::Type(TypeId::of::<T>()),
21782 ranges,
21783 style,
21784 false,
21785 cx,
21786 )
21787 });
21788 cx.notify();
21789 }
21790
21791 pub fn text_highlights<'a, T: 'static>(
21792 &'a self,
21793 cx: &'a App,
21794 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
21795 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
21796 }
21797
21798 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
21799 let cleared = self
21800 .display_map
21801 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
21802 if cleared {
21803 cx.notify();
21804 }
21805 }
21806
21807 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
21808 (self.read_only(cx) || self.blink_manager.read(cx).visible())
21809 && self.focus_handle.is_focused(window)
21810 }
21811
21812 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21813 self.show_cursor_when_unfocused = is_enabled;
21814 cx.notify();
21815 }
21816
21817 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21818 cx.notify();
21819 }
21820
21821 fn on_debug_session_event(
21822 &mut self,
21823 _session: Entity<Session>,
21824 event: &SessionEvent,
21825 cx: &mut Context<Self>,
21826 ) {
21827 if let SessionEvent::InvalidateInlineValue = event {
21828 self.refresh_inline_values(cx);
21829 }
21830 }
21831
21832 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21833 let Some(project) = self.project.clone() else {
21834 return;
21835 };
21836
21837 if !self.inline_value_cache.enabled {
21838 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21839 self.splice_inlays(&inlays, Vec::new(), cx);
21840 return;
21841 }
21842
21843 let current_execution_position = self
21844 .highlighted_rows
21845 .get(&TypeId::of::<ActiveDebugLine>())
21846 .and_then(|lines| lines.last().map(|line| line.range.end));
21847
21848 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21849 let inline_values = editor
21850 .update(cx, |editor, cx| {
21851 let Some(current_execution_position) = current_execution_position else {
21852 return Some(Task::ready(Ok(Vec::new())));
21853 };
21854
21855 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21856 let snapshot = buffer.snapshot(cx);
21857
21858 let excerpt = snapshot.excerpt_containing(
21859 current_execution_position..current_execution_position,
21860 )?;
21861
21862 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21863 })?;
21864
21865 let range =
21866 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21867
21868 project.inline_values(buffer, range, cx)
21869 })
21870 .ok()
21871 .flatten()?
21872 .await
21873 .context("refreshing debugger inlays")
21874 .log_err()?;
21875
21876 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21877
21878 for (buffer_id, inline_value) in inline_values
21879 .into_iter()
21880 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21881 {
21882 buffer_inline_values
21883 .entry(buffer_id)
21884 .or_default()
21885 .push(inline_value);
21886 }
21887
21888 editor
21889 .update(cx, |editor, cx| {
21890 let snapshot = editor.buffer.read(cx).snapshot(cx);
21891 let mut new_inlays = Vec::default();
21892
21893 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21894 let buffer_id = buffer_snapshot.remote_id();
21895 buffer_inline_values
21896 .get(&buffer_id)
21897 .into_iter()
21898 .flatten()
21899 .for_each(|hint| {
21900 let inlay = Inlay::debugger(
21901 post_inc(&mut editor.next_inlay_id),
21902 Anchor::in_buffer(excerpt_id, hint.position),
21903 hint.text(),
21904 );
21905 if !inlay.text().chars().contains(&'\n') {
21906 new_inlays.push(inlay);
21907 }
21908 });
21909 }
21910
21911 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21912 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21913
21914 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21915 })
21916 .ok()?;
21917 Some(())
21918 });
21919 }
21920
21921 fn on_buffer_event(
21922 &mut self,
21923 multibuffer: &Entity<MultiBuffer>,
21924 event: &multi_buffer::Event,
21925 window: &mut Window,
21926 cx: &mut Context<Self>,
21927 ) {
21928 match event {
21929 multi_buffer::Event::Edited { edited_buffer } => {
21930 self.scrollbar_marker_state.dirty = true;
21931 self.active_indent_guides_state.dirty = true;
21932 self.refresh_active_diagnostics(cx);
21933 self.refresh_code_actions(window, cx);
21934 self.refresh_single_line_folds(window, cx);
21935 self.refresh_matching_bracket_highlights(window, cx);
21936 if self.has_active_edit_prediction() {
21937 self.update_visible_edit_prediction(window, cx);
21938 }
21939
21940 if let Some(buffer) = edited_buffer {
21941 if buffer.read(cx).file().is_none() {
21942 cx.emit(EditorEvent::TitleChanged);
21943 }
21944
21945 if self.project.is_some() {
21946 let buffer_id = buffer.read(cx).remote_id();
21947 self.register_buffer(buffer_id, cx);
21948 self.update_lsp_data(Some(buffer_id), window, cx);
21949 self.refresh_inlay_hints(
21950 InlayHintRefreshReason::BufferEdited(buffer_id),
21951 cx,
21952 );
21953 }
21954 }
21955
21956 cx.emit(EditorEvent::BufferEdited);
21957 cx.emit(SearchEvent::MatchesInvalidated);
21958
21959 let Some(project) = &self.project else { return };
21960 let (telemetry, is_via_ssh) = {
21961 let project = project.read(cx);
21962 let telemetry = project.client().telemetry().clone();
21963 let is_via_ssh = project.is_via_remote_server();
21964 (telemetry, is_via_ssh)
21965 };
21966 telemetry.log_edit_event("editor", is_via_ssh);
21967 }
21968 multi_buffer::Event::ExcerptsAdded {
21969 buffer,
21970 predecessor,
21971 excerpts,
21972 } => {
21973 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21974 let buffer_id = buffer.read(cx).remote_id();
21975 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21976 && let Some(project) = &self.project
21977 {
21978 update_uncommitted_diff_for_buffer(
21979 cx.entity(),
21980 project,
21981 [buffer.clone()],
21982 self.buffer.clone(),
21983 cx,
21984 )
21985 .detach();
21986 }
21987 self.update_lsp_data(Some(buffer_id), window, cx);
21988 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21989 self.colorize_brackets(false, cx);
21990 cx.emit(EditorEvent::ExcerptsAdded {
21991 buffer: buffer.clone(),
21992 predecessor: *predecessor,
21993 excerpts: excerpts.clone(),
21994 });
21995 }
21996 multi_buffer::Event::ExcerptsRemoved {
21997 ids,
21998 removed_buffer_ids,
21999 } => {
22000 if let Some(inlay_hints) = &mut self.inlay_hints {
22001 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
22002 }
22003 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
22004 for buffer_id in removed_buffer_ids {
22005 self.registered_buffers.remove(buffer_id);
22006 }
22007 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22008 cx.emit(EditorEvent::ExcerptsRemoved {
22009 ids: ids.clone(),
22010 removed_buffer_ids: removed_buffer_ids.clone(),
22011 });
22012 }
22013 multi_buffer::Event::ExcerptsEdited {
22014 excerpt_ids,
22015 buffer_ids,
22016 } => {
22017 self.display_map.update(cx, |map, cx| {
22018 map.unfold_buffers(buffer_ids.iter().copied(), cx)
22019 });
22020 cx.emit(EditorEvent::ExcerptsEdited {
22021 ids: excerpt_ids.clone(),
22022 });
22023 }
22024 multi_buffer::Event::ExcerptsExpanded { ids } => {
22025 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
22026 self.refresh_document_highlights(cx);
22027 for id in ids {
22028 self.fetched_tree_sitter_chunks.remove(id);
22029 }
22030 self.colorize_brackets(false, cx);
22031 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
22032 }
22033 multi_buffer::Event::Reparsed(buffer_id) => {
22034 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22035 self.refresh_selected_text_highlights(true, window, cx);
22036 self.colorize_brackets(true, cx);
22037 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22038
22039 cx.emit(EditorEvent::Reparsed(*buffer_id));
22040 }
22041 multi_buffer::Event::DiffHunksToggled => {
22042 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22043 }
22044 multi_buffer::Event::LanguageChanged(buffer_id, is_fresh_language) => {
22045 if !is_fresh_language {
22046 self.registered_buffers.remove(&buffer_id);
22047 }
22048 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
22049 cx.emit(EditorEvent::Reparsed(*buffer_id));
22050 cx.notify();
22051 }
22052 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
22053 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
22054 multi_buffer::Event::FileHandleChanged
22055 | multi_buffer::Event::Reloaded
22056 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
22057 multi_buffer::Event::DiagnosticsUpdated => {
22058 self.update_diagnostics_state(window, cx);
22059 }
22060 _ => {}
22061 };
22062 }
22063
22064 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
22065 if !self.diagnostics_enabled() {
22066 return;
22067 }
22068 self.refresh_active_diagnostics(cx);
22069 self.refresh_inline_diagnostics(true, window, cx);
22070 self.scrollbar_marker_state.dirty = true;
22071 cx.notify();
22072 }
22073
22074 pub fn start_temporary_diff_override(&mut self) {
22075 self.load_diff_task.take();
22076 self.temporary_diff_override = true;
22077 }
22078
22079 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
22080 self.temporary_diff_override = false;
22081 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
22082 self.buffer.update(cx, |buffer, cx| {
22083 buffer.set_all_diff_hunks_collapsed(cx);
22084 });
22085
22086 if let Some(project) = self.project.clone() {
22087 self.load_diff_task = Some(
22088 update_uncommitted_diff_for_buffer(
22089 cx.entity(),
22090 &project,
22091 self.buffer.read(cx).all_buffers(),
22092 self.buffer.clone(),
22093 cx,
22094 )
22095 .shared(),
22096 );
22097 }
22098 }
22099
22100 fn on_display_map_changed(
22101 &mut self,
22102 _: Entity<DisplayMap>,
22103 _: &mut Window,
22104 cx: &mut Context<Self>,
22105 ) {
22106 cx.notify();
22107 }
22108
22109 fn fetch_accent_data(&self, cx: &App) -> Option<AccentData> {
22110 if !self.mode.is_full() {
22111 return None;
22112 }
22113
22114 let theme_settings = theme::ThemeSettings::get_global(cx);
22115 let theme = cx.theme();
22116 let accent_colors = theme.accents().clone();
22117
22118 let accent_overrides = theme_settings
22119 .theme_overrides
22120 .get(theme.name.as_ref())
22121 .map(|theme_style| &theme_style.accents)
22122 .into_iter()
22123 .flatten()
22124 .chain(
22125 theme_settings
22126 .experimental_theme_overrides
22127 .as_ref()
22128 .map(|overrides| &overrides.accents)
22129 .into_iter()
22130 .flatten(),
22131 )
22132 .flat_map(|accent| accent.0.clone())
22133 .collect();
22134
22135 Some(AccentData {
22136 colors: accent_colors,
22137 overrides: accent_overrides,
22138 })
22139 }
22140
22141 fn fetch_applicable_language_settings(
22142 &self,
22143 cx: &App,
22144 ) -> HashMap<Option<LanguageName>, LanguageSettings> {
22145 if !self.mode.is_full() {
22146 return HashMap::default();
22147 }
22148
22149 self.buffer().read(cx).all_buffers().into_iter().fold(
22150 HashMap::default(),
22151 |mut acc, buffer| {
22152 let buffer = buffer.read(cx);
22153 let language = buffer.language().map(|language| language.name());
22154 if let hash_map::Entry::Vacant(v) = acc.entry(language.clone()) {
22155 let file = buffer.file();
22156 v.insert(language_settings(language, file, cx).into_owned());
22157 }
22158 acc
22159 },
22160 )
22161 }
22162
22163 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22164 let new_language_settings = self.fetch_applicable_language_settings(cx);
22165 let language_settings_changed = new_language_settings != self.applicable_language_settings;
22166 self.applicable_language_settings = new_language_settings;
22167
22168 let new_accents = self.fetch_accent_data(cx);
22169 let accents_changed = new_accents != self.accent_data;
22170 self.accent_data = new_accents;
22171
22172 if self.diagnostics_enabled() {
22173 let new_severity = EditorSettings::get_global(cx)
22174 .diagnostics_max_severity
22175 .unwrap_or(DiagnosticSeverity::Hint);
22176 self.set_max_diagnostics_severity(new_severity, cx);
22177 }
22178 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
22179 self.update_edit_prediction_settings(cx);
22180 self.refresh_edit_prediction(true, false, window, cx);
22181 self.refresh_inline_values(cx);
22182 self.refresh_inlay_hints(
22183 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
22184 self.selections.newest_anchor().head(),
22185 &self.buffer.read(cx).snapshot(cx),
22186 cx,
22187 )),
22188 cx,
22189 );
22190
22191 let old_cursor_shape = self.cursor_shape;
22192 let old_show_breadcrumbs = self.show_breadcrumbs;
22193
22194 {
22195 let editor_settings = EditorSettings::get_global(cx);
22196 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
22197 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
22198 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
22199 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
22200 }
22201
22202 if old_cursor_shape != self.cursor_shape {
22203 cx.emit(EditorEvent::CursorShapeChanged);
22204 }
22205
22206 if old_show_breadcrumbs != self.show_breadcrumbs {
22207 cx.emit(EditorEvent::BreadcrumbsChanged);
22208 }
22209
22210 let project_settings = ProjectSettings::get_global(cx);
22211 self.buffer_serialization = self
22212 .should_serialize_buffer()
22213 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
22214
22215 if self.mode.is_full() {
22216 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
22217 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
22218 if self.show_inline_diagnostics != show_inline_diagnostics {
22219 self.show_inline_diagnostics = show_inline_diagnostics;
22220 self.refresh_inline_diagnostics(false, window, cx);
22221 }
22222
22223 if self.git_blame_inline_enabled != inline_blame_enabled {
22224 self.toggle_git_blame_inline_internal(false, window, cx);
22225 }
22226
22227 let minimap_settings = EditorSettings::get_global(cx).minimap;
22228 if self.minimap_visibility != MinimapVisibility::Disabled {
22229 if self.minimap_visibility.settings_visibility()
22230 != minimap_settings.minimap_enabled()
22231 {
22232 self.set_minimap_visibility(
22233 MinimapVisibility::for_mode(self.mode(), cx),
22234 window,
22235 cx,
22236 );
22237 } else if let Some(minimap_entity) = self.minimap.as_ref() {
22238 minimap_entity.update(cx, |minimap_editor, cx| {
22239 minimap_editor.update_minimap_configuration(minimap_settings, cx)
22240 })
22241 }
22242 }
22243
22244 if language_settings_changed || accents_changed {
22245 self.colorize_brackets(true, cx);
22246 }
22247
22248 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
22249 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
22250 }) {
22251 if !inlay_splice.is_empty() {
22252 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
22253 }
22254 self.refresh_colors_for_visible_range(None, window, cx);
22255 }
22256 }
22257
22258 cx.notify();
22259 }
22260
22261 pub fn set_searchable(&mut self, searchable: bool) {
22262 self.searchable = searchable;
22263 }
22264
22265 pub fn searchable(&self) -> bool {
22266 self.searchable
22267 }
22268
22269 pub fn open_excerpts_in_split(
22270 &mut self,
22271 _: &OpenExcerptsSplit,
22272 window: &mut Window,
22273 cx: &mut Context<Self>,
22274 ) {
22275 self.open_excerpts_common(None, true, window, cx)
22276 }
22277
22278 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
22279 self.open_excerpts_common(None, false, window, cx)
22280 }
22281
22282 fn open_excerpts_common(
22283 &mut self,
22284 jump_data: Option<JumpData>,
22285 split: bool,
22286 window: &mut Window,
22287 cx: &mut Context<Self>,
22288 ) {
22289 let Some(workspace) = self.workspace() else {
22290 cx.propagate();
22291 return;
22292 };
22293
22294 if self.buffer.read(cx).is_singleton() {
22295 cx.propagate();
22296 return;
22297 }
22298
22299 let mut new_selections_by_buffer = HashMap::default();
22300 match &jump_data {
22301 Some(JumpData::MultiBufferPoint {
22302 excerpt_id,
22303 position,
22304 anchor,
22305 line_offset_from_top,
22306 }) => {
22307 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
22308 if let Some(buffer) = multi_buffer_snapshot
22309 .buffer_id_for_excerpt(*excerpt_id)
22310 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
22311 {
22312 let buffer_snapshot = buffer.read(cx).snapshot();
22313 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
22314 language::ToPoint::to_point(anchor, &buffer_snapshot)
22315 } else {
22316 buffer_snapshot.clip_point(*position, Bias::Left)
22317 };
22318 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
22319 new_selections_by_buffer.insert(
22320 buffer,
22321 (
22322 vec![BufferOffset(jump_to_offset)..BufferOffset(jump_to_offset)],
22323 Some(*line_offset_from_top),
22324 ),
22325 );
22326 }
22327 }
22328 Some(JumpData::MultiBufferRow {
22329 row,
22330 line_offset_from_top,
22331 }) => {
22332 let point = MultiBufferPoint::new(row.0, 0);
22333 if let Some((buffer, buffer_point, _)) =
22334 self.buffer.read(cx).point_to_buffer_point(point, cx)
22335 {
22336 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
22337 new_selections_by_buffer
22338 .entry(buffer)
22339 .or_insert((Vec::new(), Some(*line_offset_from_top)))
22340 .0
22341 .push(BufferOffset(buffer_offset)..BufferOffset(buffer_offset))
22342 }
22343 }
22344 None => {
22345 let selections = self
22346 .selections
22347 .all::<MultiBufferOffset>(&self.display_snapshot(cx));
22348 let multi_buffer = self.buffer.read(cx);
22349 for selection in selections {
22350 for (snapshot, range, _, anchor) in multi_buffer
22351 .snapshot(cx)
22352 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
22353 {
22354 if let Some(anchor) = anchor {
22355 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
22356 else {
22357 continue;
22358 };
22359 let offset = text::ToOffset::to_offset(
22360 &anchor.text_anchor,
22361 &buffer_handle.read(cx).snapshot(),
22362 );
22363 let range = BufferOffset(offset)..BufferOffset(offset);
22364 new_selections_by_buffer
22365 .entry(buffer_handle)
22366 .or_insert((Vec::new(), None))
22367 .0
22368 .push(range)
22369 } else {
22370 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
22371 else {
22372 continue;
22373 };
22374 new_selections_by_buffer
22375 .entry(buffer_handle)
22376 .or_insert((Vec::new(), None))
22377 .0
22378 .push(range)
22379 }
22380 }
22381 }
22382 }
22383 }
22384
22385 new_selections_by_buffer
22386 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
22387
22388 if new_selections_by_buffer.is_empty() {
22389 return;
22390 }
22391
22392 // We defer the pane interaction because we ourselves are a workspace item
22393 // and activating a new item causes the pane to call a method on us reentrantly,
22394 // which panics if we're on the stack.
22395 window.defer(cx, move |window, cx| {
22396 workspace.update(cx, |workspace, cx| {
22397 let pane = if split {
22398 workspace.adjacent_pane(window, cx)
22399 } else {
22400 workspace.active_pane().clone()
22401 };
22402
22403 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
22404 let buffer_read = buffer.read(cx);
22405 let (has_file, is_project_file) = if let Some(file) = buffer_read.file() {
22406 (true, project::File::from_dyn(Some(file)).is_some())
22407 } else {
22408 (false, false)
22409 };
22410
22411 // If project file is none workspace.open_project_item will fail to open the excerpt
22412 // in a pre existing workspace item if one exists, because Buffer entity_id will be None
22413 // so we check if there's a tab match in that case first
22414 let editor = (!has_file || !is_project_file)
22415 .then(|| {
22416 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
22417 // so `workspace.open_project_item` will never find them, always opening a new editor.
22418 // Instead, we try to activate the existing editor in the pane first.
22419 let (editor, pane_item_index, pane_item_id) =
22420 pane.read(cx).items().enumerate().find_map(|(i, item)| {
22421 let editor = item.downcast::<Editor>()?;
22422 let singleton_buffer =
22423 editor.read(cx).buffer().read(cx).as_singleton()?;
22424 if singleton_buffer == buffer {
22425 Some((editor, i, item.item_id()))
22426 } else {
22427 None
22428 }
22429 })?;
22430 pane.update(cx, |pane, cx| {
22431 pane.activate_item(pane_item_index, true, true, window, cx);
22432 if !PreviewTabsSettings::get_global(cx)
22433 .enable_preview_from_multibuffer
22434 {
22435 pane.unpreview_item_if_preview(pane_item_id);
22436 }
22437 });
22438 Some(editor)
22439 })
22440 .flatten()
22441 .unwrap_or_else(|| {
22442 let keep_old_preview = PreviewTabsSettings::get_global(cx)
22443 .enable_keep_preview_on_code_navigation;
22444 let allow_new_preview =
22445 PreviewTabsSettings::get_global(cx).enable_preview_from_multibuffer;
22446 workspace.open_project_item::<Self>(
22447 pane.clone(),
22448 buffer,
22449 true,
22450 true,
22451 keep_old_preview,
22452 allow_new_preview,
22453 window,
22454 cx,
22455 )
22456 });
22457
22458 editor.update(cx, |editor, cx| {
22459 if has_file && !is_project_file {
22460 editor.set_read_only(true);
22461 }
22462 let autoscroll = match scroll_offset {
22463 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
22464 None => Autoscroll::newest(),
22465 };
22466 let nav_history = editor.nav_history.take();
22467 let multibuffer_snapshot = editor.buffer().read(cx).snapshot(cx);
22468 let Some((&excerpt_id, _, buffer_snapshot)) =
22469 multibuffer_snapshot.as_singleton()
22470 else {
22471 return;
22472 };
22473 editor.change_selections(
22474 SelectionEffects::scroll(autoscroll),
22475 window,
22476 cx,
22477 |s| {
22478 s.select_ranges(ranges.into_iter().map(|range| {
22479 let range = buffer_snapshot.anchor_before(range.start)
22480 ..buffer_snapshot.anchor_after(range.end);
22481 multibuffer_snapshot
22482 .anchor_range_in_excerpt(excerpt_id, range)
22483 .unwrap()
22484 }));
22485 },
22486 );
22487 editor.nav_history = nav_history;
22488 });
22489 }
22490 })
22491 });
22492 }
22493
22494 // Allow opening excerpts for buffers that either belong to the current project
22495 // or represent synthetic/non-local files (e.g., git blobs). File-less buffers
22496 // are also supported so tests and other in-memory views keep working.
22497 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
22498 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some() || !file.is_local())
22499 }
22500
22501 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<MultiBufferOffsetUtf16>>> {
22502 let snapshot = self.buffer.read(cx).read(cx);
22503 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
22504 Some(
22505 ranges
22506 .iter()
22507 .map(move |range| {
22508 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
22509 })
22510 .collect(),
22511 )
22512 }
22513
22514 fn selection_replacement_ranges(
22515 &self,
22516 range: Range<MultiBufferOffsetUtf16>,
22517 cx: &mut App,
22518 ) -> Vec<Range<MultiBufferOffsetUtf16>> {
22519 let selections = self
22520 .selections
22521 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22522 let newest_selection = selections
22523 .iter()
22524 .max_by_key(|selection| selection.id)
22525 .unwrap();
22526 let start_delta = range.start.0.0 as isize - newest_selection.start.0.0 as isize;
22527 let end_delta = range.end.0.0 as isize - newest_selection.end.0.0 as isize;
22528 let snapshot = self.buffer.read(cx).read(cx);
22529 selections
22530 .into_iter()
22531 .map(|mut selection| {
22532 selection.start.0.0 =
22533 (selection.start.0.0 as isize).saturating_add(start_delta) as usize;
22534 selection.end.0.0 = (selection.end.0.0 as isize).saturating_add(end_delta) as usize;
22535 snapshot.clip_offset_utf16(selection.start, Bias::Left)
22536 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
22537 })
22538 .collect()
22539 }
22540
22541 fn report_editor_event(
22542 &self,
22543 reported_event: ReportEditorEvent,
22544 file_extension: Option<String>,
22545 cx: &App,
22546 ) {
22547 if cfg!(any(test, feature = "test-support")) {
22548 return;
22549 }
22550
22551 let Some(project) = &self.project else { return };
22552
22553 // If None, we are in a file without an extension
22554 let file = self
22555 .buffer
22556 .read(cx)
22557 .as_singleton()
22558 .and_then(|b| b.read(cx).file());
22559 let file_extension = file_extension.or(file
22560 .as_ref()
22561 .and_then(|file| Path::new(file.file_name(cx)).extension())
22562 .and_then(|e| e.to_str())
22563 .map(|a| a.to_string()));
22564
22565 let vim_mode = vim_mode_setting::VimModeSetting::try_get(cx)
22566 .map(|vim_mode| vim_mode.0)
22567 .unwrap_or(false);
22568
22569 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
22570 let copilot_enabled = edit_predictions_provider
22571 == language::language_settings::EditPredictionProvider::Copilot;
22572 let copilot_enabled_for_language = self
22573 .buffer
22574 .read(cx)
22575 .language_settings(cx)
22576 .show_edit_predictions;
22577
22578 let project = project.read(cx);
22579 let event_type = reported_event.event_type();
22580
22581 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
22582 telemetry::event!(
22583 event_type,
22584 type = if auto_saved {"autosave"} else {"manual"},
22585 file_extension,
22586 vim_mode,
22587 copilot_enabled,
22588 copilot_enabled_for_language,
22589 edit_predictions_provider,
22590 is_via_ssh = project.is_via_remote_server(),
22591 );
22592 } else {
22593 telemetry::event!(
22594 event_type,
22595 file_extension,
22596 vim_mode,
22597 copilot_enabled,
22598 copilot_enabled_for_language,
22599 edit_predictions_provider,
22600 is_via_ssh = project.is_via_remote_server(),
22601 );
22602 };
22603 }
22604
22605 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
22606 /// with each line being an array of {text, highlight} objects.
22607 fn copy_highlight_json(
22608 &mut self,
22609 _: &CopyHighlightJson,
22610 window: &mut Window,
22611 cx: &mut Context<Self>,
22612 ) {
22613 #[derive(Serialize)]
22614 struct Chunk<'a> {
22615 text: String,
22616 highlight: Option<&'a str>,
22617 }
22618
22619 let snapshot = self.buffer.read(cx).snapshot(cx);
22620 let range = self
22621 .selected_text_range(false, window, cx)
22622 .and_then(|selection| {
22623 if selection.range.is_empty() {
22624 None
22625 } else {
22626 Some(
22627 snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22628 selection.range.start,
22629 )))
22630 ..snapshot.offset_utf16_to_offset(MultiBufferOffsetUtf16(OffsetUtf16(
22631 selection.range.end,
22632 ))),
22633 )
22634 }
22635 })
22636 .unwrap_or_else(|| MultiBufferOffset(0)..snapshot.len());
22637
22638 let chunks = snapshot.chunks(range, true);
22639 let mut lines = Vec::new();
22640 let mut line: VecDeque<Chunk> = VecDeque::new();
22641
22642 let Some(style) = self.style.as_ref() else {
22643 return;
22644 };
22645
22646 for chunk in chunks {
22647 let highlight = chunk
22648 .syntax_highlight_id
22649 .and_then(|id| id.name(&style.syntax));
22650 let mut chunk_lines = chunk.text.split('\n').peekable();
22651 while let Some(text) = chunk_lines.next() {
22652 let mut merged_with_last_token = false;
22653 if let Some(last_token) = line.back_mut()
22654 && last_token.highlight == highlight
22655 {
22656 last_token.text.push_str(text);
22657 merged_with_last_token = true;
22658 }
22659
22660 if !merged_with_last_token {
22661 line.push_back(Chunk {
22662 text: text.into(),
22663 highlight,
22664 });
22665 }
22666
22667 if chunk_lines.peek().is_some() {
22668 if line.len() > 1 && line.front().unwrap().text.is_empty() {
22669 line.pop_front();
22670 }
22671 if line.len() > 1 && line.back().unwrap().text.is_empty() {
22672 line.pop_back();
22673 }
22674
22675 lines.push(mem::take(&mut line));
22676 }
22677 }
22678 }
22679
22680 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
22681 return;
22682 };
22683 cx.write_to_clipboard(ClipboardItem::new_string(lines));
22684 }
22685
22686 pub fn open_context_menu(
22687 &mut self,
22688 _: &OpenContextMenu,
22689 window: &mut Window,
22690 cx: &mut Context<Self>,
22691 ) {
22692 self.request_autoscroll(Autoscroll::newest(), cx);
22693 let position = self
22694 .selections
22695 .newest_display(&self.display_snapshot(cx))
22696 .start;
22697 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
22698 }
22699
22700 pub fn replay_insert_event(
22701 &mut self,
22702 text: &str,
22703 relative_utf16_range: Option<Range<isize>>,
22704 window: &mut Window,
22705 cx: &mut Context<Self>,
22706 ) {
22707 if !self.input_enabled {
22708 cx.emit(EditorEvent::InputIgnored { text: text.into() });
22709 return;
22710 }
22711 if let Some(relative_utf16_range) = relative_utf16_range {
22712 let selections = self
22713 .selections
22714 .all::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
22715 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22716 let new_ranges = selections.into_iter().map(|range| {
22717 let start = MultiBufferOffsetUtf16(OffsetUtf16(
22718 range
22719 .head()
22720 .0
22721 .0
22722 .saturating_add_signed(relative_utf16_range.start),
22723 ));
22724 let end = MultiBufferOffsetUtf16(OffsetUtf16(
22725 range
22726 .head()
22727 .0
22728 .0
22729 .saturating_add_signed(relative_utf16_range.end),
22730 ));
22731 start..end
22732 });
22733 s.select_ranges(new_ranges);
22734 });
22735 }
22736
22737 self.handle_input(text, window, cx);
22738 }
22739
22740 pub fn is_focused(&self, window: &Window) -> bool {
22741 self.focus_handle.is_focused(window)
22742 }
22743
22744 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22745 cx.emit(EditorEvent::Focused);
22746
22747 if let Some(descendant) = self
22748 .last_focused_descendant
22749 .take()
22750 .and_then(|descendant| descendant.upgrade())
22751 {
22752 window.focus(&descendant);
22753 } else {
22754 if let Some(blame) = self.blame.as_ref() {
22755 blame.update(cx, GitBlame::focus)
22756 }
22757
22758 self.blink_manager.update(cx, BlinkManager::enable);
22759 self.show_cursor_names(window, cx);
22760 self.buffer.update(cx, |buffer, cx| {
22761 buffer.finalize_last_transaction(cx);
22762 if self.leader_id.is_none() {
22763 buffer.set_active_selections(
22764 &self.selections.disjoint_anchors_arc(),
22765 self.selections.line_mode(),
22766 self.cursor_shape,
22767 cx,
22768 );
22769 }
22770 });
22771
22772 if let Some(position_map) = self.last_position_map.clone() {
22773 EditorElement::mouse_moved(
22774 self,
22775 &MouseMoveEvent {
22776 position: window.mouse_position(),
22777 pressed_button: None,
22778 modifiers: window.modifiers(),
22779 },
22780 &position_map,
22781 window,
22782 cx,
22783 );
22784 }
22785 }
22786 }
22787
22788 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
22789 cx.emit(EditorEvent::FocusedIn)
22790 }
22791
22792 fn handle_focus_out(
22793 &mut self,
22794 event: FocusOutEvent,
22795 _window: &mut Window,
22796 cx: &mut Context<Self>,
22797 ) {
22798 if event.blurred != self.focus_handle {
22799 self.last_focused_descendant = Some(event.blurred);
22800 }
22801 self.selection_drag_state = SelectionDragState::None;
22802 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
22803 }
22804
22805 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22806 self.blink_manager.update(cx, BlinkManager::disable);
22807 self.buffer
22808 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
22809
22810 if let Some(blame) = self.blame.as_ref() {
22811 blame.update(cx, GitBlame::blur)
22812 }
22813 if !self.hover_state.focused(window, cx) {
22814 hide_hover(self, cx);
22815 }
22816 if !self
22817 .context_menu
22818 .borrow()
22819 .as_ref()
22820 .is_some_and(|context_menu| context_menu.focused(window, cx))
22821 {
22822 self.hide_context_menu(window, cx);
22823 }
22824 self.take_active_edit_prediction(cx);
22825 cx.emit(EditorEvent::Blurred);
22826 cx.notify();
22827 }
22828
22829 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
22830 let mut pending: String = window
22831 .pending_input_keystrokes()
22832 .into_iter()
22833 .flatten()
22834 .filter_map(|keystroke| keystroke.key_char.clone())
22835 .collect();
22836
22837 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
22838 pending = "".to_string();
22839 }
22840
22841 let existing_pending = self
22842 .text_highlights::<PendingInput>(cx)
22843 .map(|(_, ranges)| ranges.to_vec());
22844 if existing_pending.is_none() && pending.is_empty() {
22845 return;
22846 }
22847 let transaction =
22848 self.transact(window, cx, |this, window, cx| {
22849 let selections = this
22850 .selections
22851 .all::<MultiBufferOffset>(&this.display_snapshot(cx));
22852 let edits = selections
22853 .iter()
22854 .map(|selection| (selection.end..selection.end, pending.clone()));
22855 this.edit(edits, cx);
22856 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22857 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
22858 sel.start + ix * pending.len()..sel.end + ix * pending.len()
22859 }));
22860 });
22861 if let Some(existing_ranges) = existing_pending {
22862 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
22863 this.edit(edits, cx);
22864 }
22865 });
22866
22867 let snapshot = self.snapshot(window, cx);
22868 let ranges = self
22869 .selections
22870 .all::<MultiBufferOffset>(&snapshot.display_snapshot)
22871 .into_iter()
22872 .map(|selection| {
22873 snapshot.buffer_snapshot().anchor_after(selection.end)
22874 ..snapshot
22875 .buffer_snapshot()
22876 .anchor_before(selection.end + pending.len())
22877 })
22878 .collect();
22879
22880 if pending.is_empty() {
22881 self.clear_highlights::<PendingInput>(cx);
22882 } else {
22883 self.highlight_text::<PendingInput>(
22884 ranges,
22885 HighlightStyle {
22886 underline: Some(UnderlineStyle {
22887 thickness: px(1.),
22888 color: None,
22889 wavy: false,
22890 }),
22891 ..Default::default()
22892 },
22893 cx,
22894 );
22895 }
22896
22897 self.ime_transaction = self.ime_transaction.or(transaction);
22898 if let Some(transaction) = self.ime_transaction {
22899 self.buffer.update(cx, |buffer, cx| {
22900 buffer.group_until_transaction(transaction, cx);
22901 });
22902 }
22903
22904 if self.text_highlights::<PendingInput>(cx).is_none() {
22905 self.ime_transaction.take();
22906 }
22907 }
22908
22909 pub fn register_action_renderer(
22910 &mut self,
22911 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
22912 ) -> Subscription {
22913 let id = self.next_editor_action_id.post_inc();
22914 self.editor_actions
22915 .borrow_mut()
22916 .insert(id, Box::new(listener));
22917
22918 let editor_actions = self.editor_actions.clone();
22919 Subscription::new(move || {
22920 editor_actions.borrow_mut().remove(&id);
22921 })
22922 }
22923
22924 pub fn register_action<A: Action>(
22925 &mut self,
22926 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
22927 ) -> Subscription {
22928 let id = self.next_editor_action_id.post_inc();
22929 let listener = Arc::new(listener);
22930 self.editor_actions.borrow_mut().insert(
22931 id,
22932 Box::new(move |_, window, _| {
22933 let listener = listener.clone();
22934 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
22935 let action = action.downcast_ref().unwrap();
22936 if phase == DispatchPhase::Bubble {
22937 listener(action, window, cx)
22938 }
22939 })
22940 }),
22941 );
22942
22943 let editor_actions = self.editor_actions.clone();
22944 Subscription::new(move || {
22945 editor_actions.borrow_mut().remove(&id);
22946 })
22947 }
22948
22949 pub fn file_header_size(&self) -> u32 {
22950 FILE_HEADER_HEIGHT
22951 }
22952
22953 pub fn restore(
22954 &mut self,
22955 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22956 window: &mut Window,
22957 cx: &mut Context<Self>,
22958 ) {
22959 let workspace = self.workspace();
22960 let project = self.project();
22961 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22962 let mut tasks = Vec::new();
22963 for (buffer_id, changes) in revert_changes {
22964 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22965 buffer.update(cx, |buffer, cx| {
22966 buffer.edit(
22967 changes
22968 .into_iter()
22969 .map(|(range, text)| (range, text.to_string())),
22970 None,
22971 cx,
22972 );
22973 });
22974
22975 if let Some(project) =
22976 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22977 {
22978 project.update(cx, |project, cx| {
22979 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22980 })
22981 }
22982 }
22983 }
22984 tasks
22985 });
22986 cx.spawn_in(window, async move |_, cx| {
22987 for (buffer, task) in save_tasks {
22988 let result = task.await;
22989 if result.is_err() {
22990 let Some(path) = buffer
22991 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22992 .ok()
22993 else {
22994 continue;
22995 };
22996 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22997 let Some(task) = cx
22998 .update_window_entity(workspace, |workspace, window, cx| {
22999 workspace
23000 .open_path_preview(path, None, false, false, false, window, cx)
23001 })
23002 .ok()
23003 else {
23004 continue;
23005 };
23006 task.await.log_err();
23007 }
23008 }
23009 }
23010 })
23011 .detach();
23012 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
23013 selections.refresh()
23014 });
23015 }
23016
23017 pub fn to_pixel_point(
23018 &mut self,
23019 source: multi_buffer::Anchor,
23020 editor_snapshot: &EditorSnapshot,
23021 window: &mut Window,
23022 cx: &App,
23023 ) -> Option<gpui::Point<Pixels>> {
23024 let source_point = source.to_display_point(editor_snapshot);
23025 self.display_to_pixel_point(source_point, editor_snapshot, window, cx)
23026 }
23027
23028 pub fn display_to_pixel_point(
23029 &mut self,
23030 source: DisplayPoint,
23031 editor_snapshot: &EditorSnapshot,
23032 window: &mut Window,
23033 cx: &App,
23034 ) -> Option<gpui::Point<Pixels>> {
23035 let line_height = self.style(cx).text.line_height_in_pixels(window.rem_size());
23036 let text_layout_details = self.text_layout_details(window);
23037 let scroll_top = text_layout_details
23038 .scroll_anchor
23039 .scroll_position(editor_snapshot)
23040 .y;
23041
23042 if source.row().as_f64() < scroll_top.floor() {
23043 return None;
23044 }
23045 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
23046 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
23047 Some(gpui::Point::new(source_x, source_y))
23048 }
23049
23050 pub fn has_visible_completions_menu(&self) -> bool {
23051 !self.edit_prediction_preview_is_active()
23052 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
23053 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
23054 })
23055 }
23056
23057 pub fn register_addon<T: Addon>(&mut self, instance: T) {
23058 if self.mode.is_minimap() {
23059 return;
23060 }
23061 self.addons
23062 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
23063 }
23064
23065 pub fn unregister_addon<T: Addon>(&mut self) {
23066 self.addons.remove(&std::any::TypeId::of::<T>());
23067 }
23068
23069 pub fn addon<T: Addon>(&self) -> Option<&T> {
23070 let type_id = std::any::TypeId::of::<T>();
23071 self.addons
23072 .get(&type_id)
23073 .and_then(|item| item.to_any().downcast_ref::<T>())
23074 }
23075
23076 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
23077 let type_id = std::any::TypeId::of::<T>();
23078 self.addons
23079 .get_mut(&type_id)
23080 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
23081 }
23082
23083 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
23084 let text_layout_details = self.text_layout_details(window);
23085 let style = &text_layout_details.editor_style;
23086 let font_id = window.text_system().resolve_font(&style.text.font());
23087 let font_size = style.text.font_size.to_pixels(window.rem_size());
23088 let line_height = style.text.line_height_in_pixels(window.rem_size());
23089 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
23090 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
23091
23092 CharacterDimensions {
23093 em_width,
23094 em_advance,
23095 line_height,
23096 }
23097 }
23098
23099 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
23100 self.load_diff_task.clone()
23101 }
23102
23103 fn read_metadata_from_db(
23104 &mut self,
23105 item_id: u64,
23106 workspace_id: WorkspaceId,
23107 window: &mut Window,
23108 cx: &mut Context<Editor>,
23109 ) {
23110 if self.buffer_kind(cx) == ItemBufferKind::Singleton
23111 && !self.mode.is_minimap()
23112 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
23113 {
23114 let buffer_snapshot = OnceCell::new();
23115
23116 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
23117 && !folds.is_empty()
23118 {
23119 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23120 self.fold_ranges(
23121 folds
23122 .into_iter()
23123 .map(|(start, end)| {
23124 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23125 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23126 })
23127 .collect(),
23128 false,
23129 window,
23130 cx,
23131 );
23132 }
23133
23134 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
23135 && !selections.is_empty()
23136 {
23137 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
23138 // skip adding the initial selection to selection history
23139 self.selection_history.mode = SelectionHistoryMode::Skipping;
23140 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
23141 s.select_ranges(selections.into_iter().map(|(start, end)| {
23142 snapshot.clip_offset(MultiBufferOffset(start), Bias::Left)
23143 ..snapshot.clip_offset(MultiBufferOffset(end), Bias::Right)
23144 }));
23145 });
23146 self.selection_history.mode = SelectionHistoryMode::Normal;
23147 };
23148 }
23149
23150 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
23151 }
23152
23153 fn update_lsp_data(
23154 &mut self,
23155 for_buffer: Option<BufferId>,
23156 window: &mut Window,
23157 cx: &mut Context<'_, Self>,
23158 ) {
23159 self.pull_diagnostics(for_buffer, window, cx);
23160 self.refresh_colors_for_visible_range(for_buffer, window, cx);
23161 }
23162
23163 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
23164 if self.ignore_lsp_data() {
23165 return;
23166 }
23167 for (_, (visible_buffer, _, _)) in self.visible_excerpts(true, cx) {
23168 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
23169 }
23170 }
23171
23172 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
23173 if self.ignore_lsp_data() {
23174 return;
23175 }
23176
23177 if !self.registered_buffers.contains_key(&buffer_id)
23178 && let Some(project) = self.project.as_ref()
23179 {
23180 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
23181 project.update(cx, |project, cx| {
23182 self.registered_buffers.insert(
23183 buffer_id,
23184 project.register_buffer_with_language_servers(&buffer, cx),
23185 );
23186 });
23187 } else {
23188 self.registered_buffers.remove(&buffer_id);
23189 }
23190 }
23191 }
23192
23193 fn ignore_lsp_data(&self) -> bool {
23194 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
23195 // skip any LSP updates for it.
23196 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
23197 }
23198
23199 fn create_style(&self, cx: &App) -> EditorStyle {
23200 let settings = ThemeSettings::get_global(cx);
23201
23202 let mut text_style = match self.mode {
23203 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23204 color: cx.theme().colors().editor_foreground,
23205 font_family: settings.ui_font.family.clone(),
23206 font_features: settings.ui_font.features.clone(),
23207 font_fallbacks: settings.ui_font.fallbacks.clone(),
23208 font_size: rems(0.875).into(),
23209 font_weight: settings.ui_font.weight,
23210 line_height: relative(settings.buffer_line_height.value()),
23211 ..Default::default()
23212 },
23213 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
23214 color: cx.theme().colors().editor_foreground,
23215 font_family: settings.buffer_font.family.clone(),
23216 font_features: settings.buffer_font.features.clone(),
23217 font_fallbacks: settings.buffer_font.fallbacks.clone(),
23218 font_size: settings.buffer_font_size(cx).into(),
23219 font_weight: settings.buffer_font.weight,
23220 line_height: relative(settings.buffer_line_height.value()),
23221 ..Default::default()
23222 },
23223 };
23224 if let Some(text_style_refinement) = &self.text_style_refinement {
23225 text_style.refine(text_style_refinement)
23226 }
23227
23228 let background = match self.mode {
23229 EditorMode::SingleLine => cx.theme().system().transparent,
23230 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
23231 EditorMode::Full { .. } => cx.theme().colors().editor_background,
23232 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
23233 };
23234
23235 EditorStyle {
23236 background,
23237 border: cx.theme().colors().border,
23238 local_player: cx.theme().players().local(),
23239 text: text_style,
23240 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
23241 syntax: cx.theme().syntax().clone(),
23242 status: cx.theme().status().clone(),
23243 inlay_hints_style: make_inlay_hints_style(cx),
23244 edit_prediction_styles: make_suggestion_styles(cx),
23245 unnecessary_code_fade: settings.unnecessary_code_fade,
23246 show_underlines: self.diagnostics_enabled(),
23247 }
23248 }
23249}
23250
23251fn edit_for_markdown_paste<'a>(
23252 buffer: &MultiBufferSnapshot,
23253 range: Range<MultiBufferOffset>,
23254 to_insert: &'a str,
23255 url: Option<url::Url>,
23256) -> (Range<MultiBufferOffset>, Cow<'a, str>) {
23257 if url.is_none() {
23258 return (range, Cow::Borrowed(to_insert));
23259 };
23260
23261 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
23262
23263 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
23264 Cow::Borrowed(to_insert)
23265 } else {
23266 Cow::Owned(format!("[{old_text}]({to_insert})"))
23267 };
23268 (range, new_text)
23269}
23270
23271fn process_completion_for_edit(
23272 completion: &Completion,
23273 intent: CompletionIntent,
23274 buffer: &Entity<Buffer>,
23275 cursor_position: &text::Anchor,
23276 cx: &mut Context<Editor>,
23277) -> CompletionEdit {
23278 let buffer = buffer.read(cx);
23279 let buffer_snapshot = buffer.snapshot();
23280 let (snippet, new_text) = if completion.is_snippet() {
23281 let mut snippet_source = completion.new_text.clone();
23282 // Workaround for typescript language server issues so that methods don't expand within
23283 // strings and functions with type expressions. The previous point is used because the query
23284 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
23285 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
23286 let previous_point = if previous_point.column > 0 {
23287 cursor_position.to_previous_offset(&buffer_snapshot)
23288 } else {
23289 cursor_position.to_offset(&buffer_snapshot)
23290 };
23291 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
23292 && scope.prefers_label_for_snippet_in_completion()
23293 && let Some(label) = completion.label()
23294 && matches!(
23295 completion.kind(),
23296 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
23297 )
23298 {
23299 snippet_source = label;
23300 }
23301 match Snippet::parse(&snippet_source).log_err() {
23302 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
23303 None => (None, completion.new_text.clone()),
23304 }
23305 } else {
23306 (None, completion.new_text.clone())
23307 };
23308
23309 let mut range_to_replace = {
23310 let replace_range = &completion.replace_range;
23311 if let CompletionSource::Lsp {
23312 insert_range: Some(insert_range),
23313 ..
23314 } = &completion.source
23315 {
23316 debug_assert_eq!(
23317 insert_range.start, replace_range.start,
23318 "insert_range and replace_range should start at the same position"
23319 );
23320 debug_assert!(
23321 insert_range
23322 .start
23323 .cmp(cursor_position, &buffer_snapshot)
23324 .is_le(),
23325 "insert_range should start before or at cursor position"
23326 );
23327 debug_assert!(
23328 replace_range
23329 .start
23330 .cmp(cursor_position, &buffer_snapshot)
23331 .is_le(),
23332 "replace_range should start before or at cursor position"
23333 );
23334
23335 let should_replace = match intent {
23336 CompletionIntent::CompleteWithInsert => false,
23337 CompletionIntent::CompleteWithReplace => true,
23338 CompletionIntent::Complete | CompletionIntent::Compose => {
23339 let insert_mode =
23340 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
23341 .completions
23342 .lsp_insert_mode;
23343 match insert_mode {
23344 LspInsertMode::Insert => false,
23345 LspInsertMode::Replace => true,
23346 LspInsertMode::ReplaceSubsequence => {
23347 let mut text_to_replace = buffer.chars_for_range(
23348 buffer.anchor_before(replace_range.start)
23349 ..buffer.anchor_after(replace_range.end),
23350 );
23351 let mut current_needle = text_to_replace.next();
23352 for haystack_ch in completion.label.text.chars() {
23353 if let Some(needle_ch) = current_needle
23354 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
23355 {
23356 current_needle = text_to_replace.next();
23357 }
23358 }
23359 current_needle.is_none()
23360 }
23361 LspInsertMode::ReplaceSuffix => {
23362 if replace_range
23363 .end
23364 .cmp(cursor_position, &buffer_snapshot)
23365 .is_gt()
23366 {
23367 let range_after_cursor = *cursor_position..replace_range.end;
23368 let text_after_cursor = buffer
23369 .text_for_range(
23370 buffer.anchor_before(range_after_cursor.start)
23371 ..buffer.anchor_after(range_after_cursor.end),
23372 )
23373 .collect::<String>()
23374 .to_ascii_lowercase();
23375 completion
23376 .label
23377 .text
23378 .to_ascii_lowercase()
23379 .ends_with(&text_after_cursor)
23380 } else {
23381 true
23382 }
23383 }
23384 }
23385 }
23386 };
23387
23388 if should_replace {
23389 replace_range.clone()
23390 } else {
23391 insert_range.clone()
23392 }
23393 } else {
23394 replace_range.clone()
23395 }
23396 };
23397
23398 if range_to_replace
23399 .end
23400 .cmp(cursor_position, &buffer_snapshot)
23401 .is_lt()
23402 {
23403 range_to_replace.end = *cursor_position;
23404 }
23405
23406 let replace_range = range_to_replace.to_offset(buffer);
23407 CompletionEdit {
23408 new_text,
23409 replace_range: BufferOffset(replace_range.start)..BufferOffset(replace_range.end),
23410 snippet,
23411 }
23412}
23413
23414struct CompletionEdit {
23415 new_text: String,
23416 replace_range: Range<BufferOffset>,
23417 snippet: Option<Snippet>,
23418}
23419
23420fn insert_extra_newline_brackets(
23421 buffer: &MultiBufferSnapshot,
23422 range: Range<MultiBufferOffset>,
23423 language: &language::LanguageScope,
23424) -> bool {
23425 let leading_whitespace_len = buffer
23426 .reversed_chars_at(range.start)
23427 .take_while(|c| c.is_whitespace() && *c != '\n')
23428 .map(|c| c.len_utf8())
23429 .sum::<usize>();
23430 let trailing_whitespace_len = buffer
23431 .chars_at(range.end)
23432 .take_while(|c| c.is_whitespace() && *c != '\n')
23433 .map(|c| c.len_utf8())
23434 .sum::<usize>();
23435 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
23436
23437 language.brackets().any(|(pair, enabled)| {
23438 let pair_start = pair.start.trim_end();
23439 let pair_end = pair.end.trim_start();
23440
23441 enabled
23442 && pair.newline
23443 && buffer.contains_str_at(range.end, pair_end)
23444 && buffer.contains_str_at(
23445 range.start.saturating_sub_usize(pair_start.len()),
23446 pair_start,
23447 )
23448 })
23449}
23450
23451fn insert_extra_newline_tree_sitter(
23452 buffer: &MultiBufferSnapshot,
23453 range: Range<MultiBufferOffset>,
23454) -> bool {
23455 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
23456 [(buffer, range, _)] => (*buffer, range.clone()),
23457 _ => return false,
23458 };
23459 let pair = {
23460 let mut result: Option<BracketMatch<usize>> = None;
23461
23462 for pair in buffer
23463 .all_bracket_ranges(range.start.0..range.end.0)
23464 .filter(move |pair| {
23465 pair.open_range.start <= range.start.0 && pair.close_range.end >= range.end.0
23466 })
23467 {
23468 let len = pair.close_range.end - pair.open_range.start;
23469
23470 if let Some(existing) = &result {
23471 let existing_len = existing.close_range.end - existing.open_range.start;
23472 if len > existing_len {
23473 continue;
23474 }
23475 }
23476
23477 result = Some(pair);
23478 }
23479
23480 result
23481 };
23482 let Some(pair) = pair else {
23483 return false;
23484 };
23485 pair.newline_only
23486 && buffer
23487 .chars_for_range(pair.open_range.end..range.start.0)
23488 .chain(buffer.chars_for_range(range.end.0..pair.close_range.start))
23489 .all(|c| c.is_whitespace() && c != '\n')
23490}
23491
23492fn update_uncommitted_diff_for_buffer(
23493 editor: Entity<Editor>,
23494 project: &Entity<Project>,
23495 buffers: impl IntoIterator<Item = Entity<Buffer>>,
23496 buffer: Entity<MultiBuffer>,
23497 cx: &mut App,
23498) -> Task<()> {
23499 let mut tasks = Vec::new();
23500 project.update(cx, |project, cx| {
23501 for buffer in buffers {
23502 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
23503 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
23504 }
23505 }
23506 });
23507 cx.spawn(async move |cx| {
23508 let diffs = future::join_all(tasks).await;
23509 if editor
23510 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
23511 .unwrap_or(false)
23512 {
23513 return;
23514 }
23515
23516 buffer
23517 .update(cx, |buffer, cx| {
23518 for diff in diffs.into_iter().flatten() {
23519 buffer.add_diff(diff, cx);
23520 }
23521 })
23522 .ok();
23523 })
23524}
23525
23526fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
23527 let tab_size = tab_size.get() as usize;
23528 let mut width = offset;
23529
23530 for ch in text.chars() {
23531 width += if ch == '\t' {
23532 tab_size - (width % tab_size)
23533 } else {
23534 1
23535 };
23536 }
23537
23538 width - offset
23539}
23540
23541#[cfg(test)]
23542mod tests {
23543 use super::*;
23544
23545 #[test]
23546 fn test_string_size_with_expanded_tabs() {
23547 let nz = |val| NonZeroU32::new(val).unwrap();
23548 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
23549 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
23550 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
23551 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
23552 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
23553 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
23554 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
23555 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
23556 }
23557}
23558
23559/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
23560struct WordBreakingTokenizer<'a> {
23561 input: &'a str,
23562}
23563
23564impl<'a> WordBreakingTokenizer<'a> {
23565 fn new(input: &'a str) -> Self {
23566 Self { input }
23567 }
23568}
23569
23570fn is_char_ideographic(ch: char) -> bool {
23571 use unicode_script::Script::*;
23572 use unicode_script::UnicodeScript;
23573 matches!(ch.script(), Han | Tangut | Yi)
23574}
23575
23576fn is_grapheme_ideographic(text: &str) -> bool {
23577 text.chars().any(is_char_ideographic)
23578}
23579
23580fn is_grapheme_whitespace(text: &str) -> bool {
23581 text.chars().any(|x| x.is_whitespace())
23582}
23583
23584fn should_stay_with_preceding_ideograph(text: &str) -> bool {
23585 text.chars()
23586 .next()
23587 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
23588}
23589
23590#[derive(PartialEq, Eq, Debug, Clone, Copy)]
23591enum WordBreakToken<'a> {
23592 Word { token: &'a str, grapheme_len: usize },
23593 InlineWhitespace { token: &'a str, grapheme_len: usize },
23594 Newline,
23595}
23596
23597impl<'a> Iterator for WordBreakingTokenizer<'a> {
23598 /// Yields a span, the count of graphemes in the token, and whether it was
23599 /// whitespace. Note that it also breaks at word boundaries.
23600 type Item = WordBreakToken<'a>;
23601
23602 fn next(&mut self) -> Option<Self::Item> {
23603 use unicode_segmentation::UnicodeSegmentation;
23604 if self.input.is_empty() {
23605 return None;
23606 }
23607
23608 let mut iter = self.input.graphemes(true).peekable();
23609 let mut offset = 0;
23610 let mut grapheme_len = 0;
23611 if let Some(first_grapheme) = iter.next() {
23612 let is_newline = first_grapheme == "\n";
23613 let is_whitespace = is_grapheme_whitespace(first_grapheme);
23614 offset += first_grapheme.len();
23615 grapheme_len += 1;
23616 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
23617 if let Some(grapheme) = iter.peek().copied()
23618 && should_stay_with_preceding_ideograph(grapheme)
23619 {
23620 offset += grapheme.len();
23621 grapheme_len += 1;
23622 }
23623 } else {
23624 let mut words = self.input[offset..].split_word_bound_indices().peekable();
23625 let mut next_word_bound = words.peek().copied();
23626 if next_word_bound.is_some_and(|(i, _)| i == 0) {
23627 next_word_bound = words.next();
23628 }
23629 while let Some(grapheme) = iter.peek().copied() {
23630 if next_word_bound.is_some_and(|(i, _)| i == offset) {
23631 break;
23632 };
23633 if is_grapheme_whitespace(grapheme) != is_whitespace
23634 || (grapheme == "\n") != is_newline
23635 {
23636 break;
23637 };
23638 offset += grapheme.len();
23639 grapheme_len += 1;
23640 iter.next();
23641 }
23642 }
23643 let token = &self.input[..offset];
23644 self.input = &self.input[offset..];
23645 if token == "\n" {
23646 Some(WordBreakToken::Newline)
23647 } else if is_whitespace {
23648 Some(WordBreakToken::InlineWhitespace {
23649 token,
23650 grapheme_len,
23651 })
23652 } else {
23653 Some(WordBreakToken::Word {
23654 token,
23655 grapheme_len,
23656 })
23657 }
23658 } else {
23659 None
23660 }
23661 }
23662}
23663
23664#[test]
23665fn test_word_breaking_tokenizer() {
23666 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
23667 ("", &[]),
23668 (" ", &[whitespace(" ", 2)]),
23669 ("Ʒ", &[word("Ʒ", 1)]),
23670 ("Ǽ", &[word("Ǽ", 1)]),
23671 ("⋑", &[word("⋑", 1)]),
23672 ("⋑⋑", &[word("⋑⋑", 2)]),
23673 (
23674 "原理,进而",
23675 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
23676 ),
23677 (
23678 "hello world",
23679 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
23680 ),
23681 (
23682 "hello, world",
23683 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
23684 ),
23685 (
23686 " hello world",
23687 &[
23688 whitespace(" ", 2),
23689 word("hello", 5),
23690 whitespace(" ", 1),
23691 word("world", 5),
23692 ],
23693 ),
23694 (
23695 "这是什么 \n 钢笔",
23696 &[
23697 word("这", 1),
23698 word("是", 1),
23699 word("什", 1),
23700 word("么", 1),
23701 whitespace(" ", 1),
23702 newline(),
23703 whitespace(" ", 1),
23704 word("钢", 1),
23705 word("笔", 1),
23706 ],
23707 ),
23708 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
23709 ];
23710
23711 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23712 WordBreakToken::Word {
23713 token,
23714 grapheme_len,
23715 }
23716 }
23717
23718 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
23719 WordBreakToken::InlineWhitespace {
23720 token,
23721 grapheme_len,
23722 }
23723 }
23724
23725 fn newline() -> WordBreakToken<'static> {
23726 WordBreakToken::Newline
23727 }
23728
23729 for (input, result) in tests {
23730 assert_eq!(
23731 WordBreakingTokenizer::new(input)
23732 .collect::<Vec<_>>()
23733 .as_slice(),
23734 *result,
23735 );
23736 }
23737}
23738
23739fn wrap_with_prefix(
23740 first_line_prefix: String,
23741 subsequent_lines_prefix: String,
23742 unwrapped_text: String,
23743 wrap_column: usize,
23744 tab_size: NonZeroU32,
23745 preserve_existing_whitespace: bool,
23746) -> String {
23747 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
23748 let subsequent_lines_prefix_len =
23749 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
23750 let mut wrapped_text = String::new();
23751 let mut current_line = first_line_prefix;
23752 let mut is_first_line = true;
23753
23754 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
23755 let mut current_line_len = first_line_prefix_len;
23756 let mut in_whitespace = false;
23757 for token in tokenizer {
23758 let have_preceding_whitespace = in_whitespace;
23759 match token {
23760 WordBreakToken::Word {
23761 token,
23762 grapheme_len,
23763 } => {
23764 in_whitespace = false;
23765 let current_prefix_len = if is_first_line {
23766 first_line_prefix_len
23767 } else {
23768 subsequent_lines_prefix_len
23769 };
23770 if current_line_len + grapheme_len > wrap_column
23771 && current_line_len != current_prefix_len
23772 {
23773 wrapped_text.push_str(current_line.trim_end());
23774 wrapped_text.push('\n');
23775 is_first_line = false;
23776 current_line = subsequent_lines_prefix.clone();
23777 current_line_len = subsequent_lines_prefix_len;
23778 }
23779 current_line.push_str(token);
23780 current_line_len += grapheme_len;
23781 }
23782 WordBreakToken::InlineWhitespace {
23783 mut token,
23784 mut grapheme_len,
23785 } => {
23786 in_whitespace = true;
23787 if have_preceding_whitespace && !preserve_existing_whitespace {
23788 continue;
23789 }
23790 if !preserve_existing_whitespace {
23791 // Keep a single whitespace grapheme as-is
23792 if let Some(first) =
23793 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
23794 {
23795 token = first;
23796 } else {
23797 token = " ";
23798 }
23799 grapheme_len = 1;
23800 }
23801 let current_prefix_len = if is_first_line {
23802 first_line_prefix_len
23803 } else {
23804 subsequent_lines_prefix_len
23805 };
23806 if current_line_len + grapheme_len > wrap_column {
23807 wrapped_text.push_str(current_line.trim_end());
23808 wrapped_text.push('\n');
23809 is_first_line = false;
23810 current_line = subsequent_lines_prefix.clone();
23811 current_line_len = subsequent_lines_prefix_len;
23812 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
23813 current_line.push_str(token);
23814 current_line_len += grapheme_len;
23815 }
23816 }
23817 WordBreakToken::Newline => {
23818 in_whitespace = true;
23819 let current_prefix_len = if is_first_line {
23820 first_line_prefix_len
23821 } else {
23822 subsequent_lines_prefix_len
23823 };
23824 if preserve_existing_whitespace {
23825 wrapped_text.push_str(current_line.trim_end());
23826 wrapped_text.push('\n');
23827 is_first_line = false;
23828 current_line = subsequent_lines_prefix.clone();
23829 current_line_len = subsequent_lines_prefix_len;
23830 } else if have_preceding_whitespace {
23831 continue;
23832 } else if current_line_len + 1 > wrap_column
23833 && current_line_len != current_prefix_len
23834 {
23835 wrapped_text.push_str(current_line.trim_end());
23836 wrapped_text.push('\n');
23837 is_first_line = false;
23838 current_line = subsequent_lines_prefix.clone();
23839 current_line_len = subsequent_lines_prefix_len;
23840 } else if current_line_len != current_prefix_len {
23841 current_line.push(' ');
23842 current_line_len += 1;
23843 }
23844 }
23845 }
23846 }
23847
23848 if !current_line.is_empty() {
23849 wrapped_text.push_str(¤t_line);
23850 }
23851 wrapped_text
23852}
23853
23854#[test]
23855fn test_wrap_with_prefix() {
23856 assert_eq!(
23857 wrap_with_prefix(
23858 "# ".to_string(),
23859 "# ".to_string(),
23860 "abcdefg".to_string(),
23861 4,
23862 NonZeroU32::new(4).unwrap(),
23863 false,
23864 ),
23865 "# abcdefg"
23866 );
23867 assert_eq!(
23868 wrap_with_prefix(
23869 "".to_string(),
23870 "".to_string(),
23871 "\thello world".to_string(),
23872 8,
23873 NonZeroU32::new(4).unwrap(),
23874 false,
23875 ),
23876 "hello\nworld"
23877 );
23878 assert_eq!(
23879 wrap_with_prefix(
23880 "// ".to_string(),
23881 "// ".to_string(),
23882 "xx \nyy zz aa bb cc".to_string(),
23883 12,
23884 NonZeroU32::new(4).unwrap(),
23885 false,
23886 ),
23887 "// xx yy zz\n// aa bb cc"
23888 );
23889 assert_eq!(
23890 wrap_with_prefix(
23891 String::new(),
23892 String::new(),
23893 "这是什么 \n 钢笔".to_string(),
23894 3,
23895 NonZeroU32::new(4).unwrap(),
23896 false,
23897 ),
23898 "这是什\n么 钢\n笔"
23899 );
23900 assert_eq!(
23901 wrap_with_prefix(
23902 String::new(),
23903 String::new(),
23904 format!("foo{}bar", '\u{2009}'), // thin space
23905 80,
23906 NonZeroU32::new(4).unwrap(),
23907 false,
23908 ),
23909 format!("foo{}bar", '\u{2009}')
23910 );
23911}
23912
23913pub trait CollaborationHub {
23914 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
23915 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
23916 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
23917}
23918
23919impl CollaborationHub for Entity<Project> {
23920 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
23921 self.read(cx).collaborators()
23922 }
23923
23924 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
23925 self.read(cx).user_store().read(cx).participant_indices()
23926 }
23927
23928 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
23929 let this = self.read(cx);
23930 let user_ids = this.collaborators().values().map(|c| c.user_id);
23931 this.user_store().read(cx).participant_names(user_ids, cx)
23932 }
23933}
23934
23935pub trait SemanticsProvider {
23936 fn hover(
23937 &self,
23938 buffer: &Entity<Buffer>,
23939 position: text::Anchor,
23940 cx: &mut App,
23941 ) -> Option<Task<Option<Vec<project::Hover>>>>;
23942
23943 fn inline_values(
23944 &self,
23945 buffer_handle: Entity<Buffer>,
23946 range: Range<text::Anchor>,
23947 cx: &mut App,
23948 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
23949
23950 fn applicable_inlay_chunks(
23951 &self,
23952 buffer: &Entity<Buffer>,
23953 ranges: &[Range<text::Anchor>],
23954 cx: &mut App,
23955 ) -> Vec<Range<BufferRow>>;
23956
23957 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
23958
23959 fn inlay_hints(
23960 &self,
23961 invalidate: InvalidationStrategy,
23962 buffer: Entity<Buffer>,
23963 ranges: Vec<Range<text::Anchor>>,
23964 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23965 cx: &mut App,
23966 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
23967
23968 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
23969
23970 fn document_highlights(
23971 &self,
23972 buffer: &Entity<Buffer>,
23973 position: text::Anchor,
23974 cx: &mut App,
23975 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
23976
23977 fn definitions(
23978 &self,
23979 buffer: &Entity<Buffer>,
23980 position: text::Anchor,
23981 kind: GotoDefinitionKind,
23982 cx: &mut App,
23983 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23984
23985 fn range_for_rename(
23986 &self,
23987 buffer: &Entity<Buffer>,
23988 position: text::Anchor,
23989 cx: &mut App,
23990 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23991
23992 fn perform_rename(
23993 &self,
23994 buffer: &Entity<Buffer>,
23995 position: text::Anchor,
23996 new_name: String,
23997 cx: &mut App,
23998 ) -> Option<Task<Result<ProjectTransaction>>>;
23999}
24000
24001pub trait CompletionProvider {
24002 fn completions(
24003 &self,
24004 excerpt_id: ExcerptId,
24005 buffer: &Entity<Buffer>,
24006 buffer_position: text::Anchor,
24007 trigger: CompletionContext,
24008 window: &mut Window,
24009 cx: &mut Context<Editor>,
24010 ) -> Task<Result<Vec<CompletionResponse>>>;
24011
24012 fn resolve_completions(
24013 &self,
24014 _buffer: Entity<Buffer>,
24015 _completion_indices: Vec<usize>,
24016 _completions: Rc<RefCell<Box<[Completion]>>>,
24017 _cx: &mut Context<Editor>,
24018 ) -> Task<Result<bool>> {
24019 Task::ready(Ok(false))
24020 }
24021
24022 fn apply_additional_edits_for_completion(
24023 &self,
24024 _buffer: Entity<Buffer>,
24025 _completions: Rc<RefCell<Box<[Completion]>>>,
24026 _completion_index: usize,
24027 _push_to_history: bool,
24028 _cx: &mut Context<Editor>,
24029 ) -> Task<Result<Option<language::Transaction>>> {
24030 Task::ready(Ok(None))
24031 }
24032
24033 fn is_completion_trigger(
24034 &self,
24035 buffer: &Entity<Buffer>,
24036 position: language::Anchor,
24037 text: &str,
24038 trigger_in_words: bool,
24039 cx: &mut Context<Editor>,
24040 ) -> bool;
24041
24042 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
24043
24044 fn sort_completions(&self) -> bool {
24045 true
24046 }
24047
24048 fn filter_completions(&self) -> bool {
24049 true
24050 }
24051
24052 fn show_snippets(&self) -> bool {
24053 false
24054 }
24055}
24056
24057pub trait CodeActionProvider {
24058 fn id(&self) -> Arc<str>;
24059
24060 fn code_actions(
24061 &self,
24062 buffer: &Entity<Buffer>,
24063 range: Range<text::Anchor>,
24064 window: &mut Window,
24065 cx: &mut App,
24066 ) -> Task<Result<Vec<CodeAction>>>;
24067
24068 fn apply_code_action(
24069 &self,
24070 buffer_handle: Entity<Buffer>,
24071 action: CodeAction,
24072 excerpt_id: ExcerptId,
24073 push_to_history: bool,
24074 window: &mut Window,
24075 cx: &mut App,
24076 ) -> Task<Result<ProjectTransaction>>;
24077}
24078
24079impl CodeActionProvider for Entity<Project> {
24080 fn id(&self) -> Arc<str> {
24081 "project".into()
24082 }
24083
24084 fn code_actions(
24085 &self,
24086 buffer: &Entity<Buffer>,
24087 range: Range<text::Anchor>,
24088 _window: &mut Window,
24089 cx: &mut App,
24090 ) -> Task<Result<Vec<CodeAction>>> {
24091 self.update(cx, |project, cx| {
24092 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
24093 let code_actions = project.code_actions(buffer, range, None, cx);
24094 cx.background_spawn(async move {
24095 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
24096 Ok(code_lens_actions
24097 .context("code lens fetch")?
24098 .into_iter()
24099 .flatten()
24100 .chain(
24101 code_actions
24102 .context("code action fetch")?
24103 .into_iter()
24104 .flatten(),
24105 )
24106 .collect())
24107 })
24108 })
24109 }
24110
24111 fn apply_code_action(
24112 &self,
24113 buffer_handle: Entity<Buffer>,
24114 action: CodeAction,
24115 _excerpt_id: ExcerptId,
24116 push_to_history: bool,
24117 _window: &mut Window,
24118 cx: &mut App,
24119 ) -> Task<Result<ProjectTransaction>> {
24120 self.update(cx, |project, cx| {
24121 project.apply_code_action(buffer_handle, action, push_to_history, cx)
24122 })
24123 }
24124}
24125
24126fn snippet_completions(
24127 project: &Project,
24128 buffer: &Entity<Buffer>,
24129 buffer_anchor: text::Anchor,
24130 classifier: CharClassifier,
24131 cx: &mut App,
24132) -> Task<Result<CompletionResponse>> {
24133 let languages = buffer.read(cx).languages_at(buffer_anchor);
24134 let snippet_store = project.snippets().read(cx);
24135
24136 let scopes: Vec<_> = languages
24137 .iter()
24138 .filter_map(|language| {
24139 let language_name = language.lsp_id();
24140 let snippets = snippet_store.snippets_for(Some(language_name), cx);
24141
24142 if snippets.is_empty() {
24143 None
24144 } else {
24145 Some((language.default_scope(), snippets))
24146 }
24147 })
24148 .collect();
24149
24150 if scopes.is_empty() {
24151 return Task::ready(Ok(CompletionResponse {
24152 completions: vec![],
24153 display_options: CompletionDisplayOptions::default(),
24154 is_incomplete: false,
24155 }));
24156 }
24157
24158 let snapshot = buffer.read(cx).text_snapshot();
24159 let executor = cx.background_executor().clone();
24160
24161 cx.background_spawn(async move {
24162 let is_word_char = |c| classifier.is_word(c);
24163
24164 let mut is_incomplete = false;
24165 let mut completions: Vec<Completion> = Vec::new();
24166
24167 const MAX_PREFIX_LEN: usize = 128;
24168 let buffer_offset = text::ToOffset::to_offset(&buffer_anchor, &snapshot);
24169 let window_start = buffer_offset.saturating_sub(MAX_PREFIX_LEN);
24170 let window_start = snapshot.clip_offset(window_start, Bias::Left);
24171
24172 let max_buffer_window: String = snapshot
24173 .text_for_range(window_start..buffer_offset)
24174 .collect();
24175
24176 if max_buffer_window.is_empty() {
24177 return Ok(CompletionResponse {
24178 completions: vec![],
24179 display_options: CompletionDisplayOptions::default(),
24180 is_incomplete: true,
24181 });
24182 }
24183
24184 for (_scope, snippets) in scopes.into_iter() {
24185 // Sort snippets by word count to match longer snippet prefixes first.
24186 let mut sorted_snippet_candidates = snippets
24187 .iter()
24188 .enumerate()
24189 .flat_map(|(snippet_ix, snippet)| {
24190 snippet
24191 .prefix
24192 .iter()
24193 .enumerate()
24194 .map(move |(prefix_ix, prefix)| {
24195 let word_count =
24196 snippet_candidate_suffixes(prefix, is_word_char).count();
24197 ((snippet_ix, prefix_ix), prefix, word_count)
24198 })
24199 })
24200 .collect_vec();
24201 sorted_snippet_candidates
24202 .sort_unstable_by_key(|(_, _, word_count)| Reverse(*word_count));
24203
24204 // Each prefix may be matched multiple times; the completion menu must filter out duplicates.
24205
24206 let buffer_windows = snippet_candidate_suffixes(&max_buffer_window, is_word_char)
24207 .take(
24208 sorted_snippet_candidates
24209 .first()
24210 .map(|(_, _, word_count)| *word_count)
24211 .unwrap_or_default(),
24212 )
24213 .collect_vec();
24214
24215 const MAX_RESULTS: usize = 100;
24216 // Each match also remembers how many characters from the buffer it consumed
24217 let mut matches: Vec<(StringMatch, usize)> = vec![];
24218
24219 let mut snippet_list_cutoff_index = 0;
24220 for (buffer_index, buffer_window) in buffer_windows.iter().enumerate().rev() {
24221 let word_count = buffer_index + 1;
24222 // Increase `snippet_list_cutoff_index` until we have all of the
24223 // snippets with sufficiently many words.
24224 while sorted_snippet_candidates
24225 .get(snippet_list_cutoff_index)
24226 .is_some_and(|(_ix, _prefix, snippet_word_count)| {
24227 *snippet_word_count >= word_count
24228 })
24229 {
24230 snippet_list_cutoff_index += 1;
24231 }
24232
24233 // Take only the candidates with at least `word_count` many words
24234 let snippet_candidates_at_word_len =
24235 &sorted_snippet_candidates[..snippet_list_cutoff_index];
24236
24237 let candidates = snippet_candidates_at_word_len
24238 .iter()
24239 .map(|(_snippet_ix, prefix, _snippet_word_count)| prefix)
24240 .enumerate() // index in `sorted_snippet_candidates`
24241 // First char must match
24242 .filter(|(_ix, prefix)| {
24243 itertools::equal(
24244 prefix
24245 .chars()
24246 .next()
24247 .into_iter()
24248 .flat_map(|c| c.to_lowercase()),
24249 buffer_window
24250 .chars()
24251 .next()
24252 .into_iter()
24253 .flat_map(|c| c.to_lowercase()),
24254 )
24255 })
24256 .map(|(ix, prefix)| StringMatchCandidate::new(ix, prefix))
24257 .collect::<Vec<StringMatchCandidate>>();
24258
24259 matches.extend(
24260 fuzzy::match_strings(
24261 &candidates,
24262 &buffer_window,
24263 buffer_window.chars().any(|c| c.is_uppercase()),
24264 true,
24265 MAX_RESULTS - matches.len(), // always prioritize longer snippets
24266 &Default::default(),
24267 executor.clone(),
24268 )
24269 .await
24270 .into_iter()
24271 .map(|string_match| (string_match, buffer_window.len())),
24272 );
24273
24274 if matches.len() >= MAX_RESULTS {
24275 break;
24276 }
24277 }
24278
24279 let to_lsp = |point: &text::Anchor| {
24280 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
24281 point_to_lsp(end)
24282 };
24283 let lsp_end = to_lsp(&buffer_anchor);
24284
24285 if matches.len() >= MAX_RESULTS {
24286 is_incomplete = true;
24287 }
24288
24289 completions.extend(matches.iter().map(|(string_match, buffer_window_len)| {
24290 let ((snippet_index, prefix_index), matching_prefix, _snippet_word_count) =
24291 sorted_snippet_candidates[string_match.candidate_id];
24292 let snippet = &snippets[snippet_index];
24293 let start = buffer_offset - buffer_window_len;
24294 let start = snapshot.anchor_before(start);
24295 let range = start..buffer_anchor;
24296 let lsp_start = to_lsp(&start);
24297 let lsp_range = lsp::Range {
24298 start: lsp_start,
24299 end: lsp_end,
24300 };
24301 Completion {
24302 replace_range: range,
24303 new_text: snippet.body.clone(),
24304 source: CompletionSource::Lsp {
24305 insert_range: None,
24306 server_id: LanguageServerId(usize::MAX),
24307 resolved: true,
24308 lsp_completion: Box::new(lsp::CompletionItem {
24309 label: snippet.prefix.first().unwrap().clone(),
24310 kind: Some(CompletionItemKind::SNIPPET),
24311 label_details: snippet.description.as_ref().map(|description| {
24312 lsp::CompletionItemLabelDetails {
24313 detail: Some(description.clone()),
24314 description: None,
24315 }
24316 }),
24317 insert_text_format: Some(InsertTextFormat::SNIPPET),
24318 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
24319 lsp::InsertReplaceEdit {
24320 new_text: snippet.body.clone(),
24321 insert: lsp_range,
24322 replace: lsp_range,
24323 },
24324 )),
24325 filter_text: Some(snippet.body.clone()),
24326 sort_text: Some(char::MAX.to_string()),
24327 ..lsp::CompletionItem::default()
24328 }),
24329 lsp_defaults: None,
24330 },
24331 label: CodeLabel {
24332 text: matching_prefix.clone(),
24333 runs: Vec::new(),
24334 filter_range: 0..matching_prefix.len(),
24335 },
24336 icon_path: None,
24337 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
24338 single_line: snippet.name.clone().into(),
24339 plain_text: snippet
24340 .description
24341 .clone()
24342 .map(|description| description.into()),
24343 }),
24344 insert_text_mode: None,
24345 confirm: None,
24346 match_start: Some(start),
24347 snippet_deduplication_key: Some((snippet_index, prefix_index)),
24348 }
24349 }));
24350 }
24351
24352 Ok(CompletionResponse {
24353 completions,
24354 display_options: CompletionDisplayOptions::default(),
24355 is_incomplete,
24356 })
24357 })
24358}
24359
24360impl CompletionProvider for Entity<Project> {
24361 fn completions(
24362 &self,
24363 _excerpt_id: ExcerptId,
24364 buffer: &Entity<Buffer>,
24365 buffer_position: text::Anchor,
24366 options: CompletionContext,
24367 _window: &mut Window,
24368 cx: &mut Context<Editor>,
24369 ) -> Task<Result<Vec<CompletionResponse>>> {
24370 self.update(cx, |project, cx| {
24371 let task = project.completions(buffer, buffer_position, options, cx);
24372 cx.background_spawn(task)
24373 })
24374 }
24375
24376 fn resolve_completions(
24377 &self,
24378 buffer: Entity<Buffer>,
24379 completion_indices: Vec<usize>,
24380 completions: Rc<RefCell<Box<[Completion]>>>,
24381 cx: &mut Context<Editor>,
24382 ) -> Task<Result<bool>> {
24383 self.update(cx, |project, cx| {
24384 project.lsp_store().update(cx, |lsp_store, cx| {
24385 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
24386 })
24387 })
24388 }
24389
24390 fn apply_additional_edits_for_completion(
24391 &self,
24392 buffer: Entity<Buffer>,
24393 completions: Rc<RefCell<Box<[Completion]>>>,
24394 completion_index: usize,
24395 push_to_history: bool,
24396 cx: &mut Context<Editor>,
24397 ) -> Task<Result<Option<language::Transaction>>> {
24398 self.update(cx, |project, cx| {
24399 project.lsp_store().update(cx, |lsp_store, cx| {
24400 lsp_store.apply_additional_edits_for_completion(
24401 buffer,
24402 completions,
24403 completion_index,
24404 push_to_history,
24405 cx,
24406 )
24407 })
24408 })
24409 }
24410
24411 fn is_completion_trigger(
24412 &self,
24413 buffer: &Entity<Buffer>,
24414 position: language::Anchor,
24415 text: &str,
24416 trigger_in_words: bool,
24417 cx: &mut Context<Editor>,
24418 ) -> bool {
24419 let mut chars = text.chars();
24420 let char = if let Some(char) = chars.next() {
24421 char
24422 } else {
24423 return false;
24424 };
24425 if chars.next().is_some() {
24426 return false;
24427 }
24428
24429 let buffer = buffer.read(cx);
24430 let snapshot = buffer.snapshot();
24431 let classifier = snapshot
24432 .char_classifier_at(position)
24433 .scope_context(Some(CharScopeContext::Completion));
24434 if trigger_in_words && classifier.is_word(char) {
24435 return true;
24436 }
24437
24438 buffer.completion_triggers().contains(text)
24439 }
24440
24441 fn show_snippets(&self) -> bool {
24442 true
24443 }
24444}
24445
24446impl SemanticsProvider for Entity<Project> {
24447 fn hover(
24448 &self,
24449 buffer: &Entity<Buffer>,
24450 position: text::Anchor,
24451 cx: &mut App,
24452 ) -> Option<Task<Option<Vec<project::Hover>>>> {
24453 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
24454 }
24455
24456 fn document_highlights(
24457 &self,
24458 buffer: &Entity<Buffer>,
24459 position: text::Anchor,
24460 cx: &mut App,
24461 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
24462 Some(self.update(cx, |project, cx| {
24463 project.document_highlights(buffer, position, cx)
24464 }))
24465 }
24466
24467 fn definitions(
24468 &self,
24469 buffer: &Entity<Buffer>,
24470 position: text::Anchor,
24471 kind: GotoDefinitionKind,
24472 cx: &mut App,
24473 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
24474 Some(self.update(cx, |project, cx| match kind {
24475 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
24476 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
24477 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
24478 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
24479 }))
24480 }
24481
24482 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
24483 self.update(cx, |project, cx| {
24484 if project
24485 .active_debug_session(cx)
24486 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
24487 {
24488 return true;
24489 }
24490
24491 buffer.update(cx, |buffer, cx| {
24492 project.any_language_server_supports_inlay_hints(buffer, cx)
24493 })
24494 })
24495 }
24496
24497 fn inline_values(
24498 &self,
24499 buffer_handle: Entity<Buffer>,
24500 range: Range<text::Anchor>,
24501 cx: &mut App,
24502 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
24503 self.update(cx, |project, cx| {
24504 let (session, active_stack_frame) = project.active_debug_session(cx)?;
24505
24506 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
24507 })
24508 }
24509
24510 fn applicable_inlay_chunks(
24511 &self,
24512 buffer: &Entity<Buffer>,
24513 ranges: &[Range<text::Anchor>],
24514 cx: &mut App,
24515 ) -> Vec<Range<BufferRow>> {
24516 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24517 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
24518 })
24519 }
24520
24521 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
24522 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
24523 lsp_store.invalidate_inlay_hints(for_buffers)
24524 });
24525 }
24526
24527 fn inlay_hints(
24528 &self,
24529 invalidate: InvalidationStrategy,
24530 buffer: Entity<Buffer>,
24531 ranges: Vec<Range<text::Anchor>>,
24532 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
24533 cx: &mut App,
24534 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
24535 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
24536 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
24537 }))
24538 }
24539
24540 fn range_for_rename(
24541 &self,
24542 buffer: &Entity<Buffer>,
24543 position: text::Anchor,
24544 cx: &mut App,
24545 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
24546 Some(self.update(cx, |project, cx| {
24547 let buffer = buffer.clone();
24548 let task = project.prepare_rename(buffer.clone(), position, cx);
24549 cx.spawn(async move |_, cx| {
24550 Ok(match task.await? {
24551 PrepareRenameResponse::Success(range) => Some(range),
24552 PrepareRenameResponse::InvalidPosition => None,
24553 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
24554 // Fallback on using TreeSitter info to determine identifier range
24555 buffer.read_with(cx, |buffer, _| {
24556 let snapshot = buffer.snapshot();
24557 let (range, kind) = snapshot.surrounding_word(position, None);
24558 if kind != Some(CharKind::Word) {
24559 return None;
24560 }
24561 Some(
24562 snapshot.anchor_before(range.start)
24563 ..snapshot.anchor_after(range.end),
24564 )
24565 })?
24566 }
24567 })
24568 })
24569 }))
24570 }
24571
24572 fn perform_rename(
24573 &self,
24574 buffer: &Entity<Buffer>,
24575 position: text::Anchor,
24576 new_name: String,
24577 cx: &mut App,
24578 ) -> Option<Task<Result<ProjectTransaction>>> {
24579 Some(self.update(cx, |project, cx| {
24580 project.perform_rename(buffer.clone(), position, new_name, cx)
24581 }))
24582 }
24583}
24584
24585fn consume_contiguous_rows(
24586 contiguous_row_selections: &mut Vec<Selection<Point>>,
24587 selection: &Selection<Point>,
24588 display_map: &DisplaySnapshot,
24589 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
24590) -> (MultiBufferRow, MultiBufferRow) {
24591 contiguous_row_selections.push(selection.clone());
24592 let start_row = starting_row(selection, display_map);
24593 let mut end_row = ending_row(selection, display_map);
24594
24595 while let Some(next_selection) = selections.peek() {
24596 if next_selection.start.row <= end_row.0 {
24597 end_row = ending_row(next_selection, display_map);
24598 contiguous_row_selections.push(selections.next().unwrap().clone());
24599 } else {
24600 break;
24601 }
24602 }
24603 (start_row, end_row)
24604}
24605
24606fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24607 if selection.start.column > 0 {
24608 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
24609 } else {
24610 MultiBufferRow(selection.start.row)
24611 }
24612}
24613
24614fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
24615 if next_selection.end.column > 0 || next_selection.is_empty() {
24616 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
24617 } else {
24618 MultiBufferRow(next_selection.end.row)
24619 }
24620}
24621
24622impl EditorSnapshot {
24623 pub fn remote_selections_in_range<'a>(
24624 &'a self,
24625 range: &'a Range<Anchor>,
24626 collaboration_hub: &dyn CollaborationHub,
24627 cx: &'a App,
24628 ) -> impl 'a + Iterator<Item = RemoteSelection> {
24629 let participant_names = collaboration_hub.user_names(cx);
24630 let participant_indices = collaboration_hub.user_participant_indices(cx);
24631 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
24632 let collaborators_by_replica_id = collaborators_by_peer_id
24633 .values()
24634 .map(|collaborator| (collaborator.replica_id, collaborator))
24635 .collect::<HashMap<_, _>>();
24636 self.buffer_snapshot()
24637 .selections_in_range(range, false)
24638 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
24639 if replica_id == ReplicaId::AGENT {
24640 Some(RemoteSelection {
24641 replica_id,
24642 selection,
24643 cursor_shape,
24644 line_mode,
24645 collaborator_id: CollaboratorId::Agent,
24646 user_name: Some("Agent".into()),
24647 color: cx.theme().players().agent(),
24648 })
24649 } else {
24650 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
24651 let participant_index = participant_indices.get(&collaborator.user_id).copied();
24652 let user_name = participant_names.get(&collaborator.user_id).cloned();
24653 Some(RemoteSelection {
24654 replica_id,
24655 selection,
24656 cursor_shape,
24657 line_mode,
24658 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
24659 user_name,
24660 color: if let Some(index) = participant_index {
24661 cx.theme().players().color_for_participant(index.0)
24662 } else {
24663 cx.theme().players().absent()
24664 },
24665 })
24666 }
24667 })
24668 }
24669
24670 pub fn hunks_for_ranges(
24671 &self,
24672 ranges: impl IntoIterator<Item = Range<Point>>,
24673 ) -> Vec<MultiBufferDiffHunk> {
24674 let mut hunks = Vec::new();
24675 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
24676 HashMap::default();
24677 for query_range in ranges {
24678 let query_rows =
24679 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
24680 for hunk in self.buffer_snapshot().diff_hunks_in_range(
24681 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
24682 ) {
24683 // Include deleted hunks that are adjacent to the query range, because
24684 // otherwise they would be missed.
24685 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
24686 if hunk.status().is_deleted() {
24687 intersects_range |= hunk.row_range.start == query_rows.end;
24688 intersects_range |= hunk.row_range.end == query_rows.start;
24689 }
24690 if intersects_range {
24691 if !processed_buffer_rows
24692 .entry(hunk.buffer_id)
24693 .or_default()
24694 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
24695 {
24696 continue;
24697 }
24698 hunks.push(hunk);
24699 }
24700 }
24701 }
24702
24703 hunks
24704 }
24705
24706 fn display_diff_hunks_for_rows<'a>(
24707 &'a self,
24708 display_rows: Range<DisplayRow>,
24709 folded_buffers: &'a HashSet<BufferId>,
24710 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
24711 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
24712 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
24713
24714 self.buffer_snapshot()
24715 .diff_hunks_in_range(buffer_start..buffer_end)
24716 .filter_map(|hunk| {
24717 if folded_buffers.contains(&hunk.buffer_id) {
24718 return None;
24719 }
24720
24721 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
24722 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
24723
24724 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
24725 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
24726
24727 let display_hunk = if hunk_display_start.column() != 0 {
24728 DisplayDiffHunk::Folded {
24729 display_row: hunk_display_start.row(),
24730 }
24731 } else {
24732 let mut end_row = hunk_display_end.row();
24733 if hunk_display_end.column() > 0 {
24734 end_row.0 += 1;
24735 }
24736 let is_created_file = hunk.is_created_file();
24737
24738 DisplayDiffHunk::Unfolded {
24739 status: hunk.status(),
24740 diff_base_byte_range: hunk.diff_base_byte_range.start.0
24741 ..hunk.diff_base_byte_range.end.0,
24742 word_diffs: hunk.word_diffs,
24743 display_row_range: hunk_display_start.row()..end_row,
24744 multi_buffer_range: Anchor::range_in_buffer(
24745 hunk.excerpt_id,
24746 hunk.buffer_range,
24747 ),
24748 is_created_file,
24749 }
24750 };
24751
24752 Some(display_hunk)
24753 })
24754 }
24755
24756 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
24757 self.display_snapshot
24758 .buffer_snapshot()
24759 .language_at(position)
24760 }
24761
24762 pub fn is_focused(&self) -> bool {
24763 self.is_focused
24764 }
24765
24766 pub fn placeholder_text(&self) -> Option<String> {
24767 self.placeholder_display_snapshot
24768 .as_ref()
24769 .map(|display_map| display_map.text())
24770 }
24771
24772 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
24773 self.scroll_anchor.scroll_position(&self.display_snapshot)
24774 }
24775
24776 pub fn gutter_dimensions(
24777 &self,
24778 font_id: FontId,
24779 font_size: Pixels,
24780 style: &EditorStyle,
24781 window: &mut Window,
24782 cx: &App,
24783 ) -> GutterDimensions {
24784 if self.show_gutter
24785 && let Some(ch_width) = cx.text_system().ch_width(font_id, font_size).log_err()
24786 && let Some(ch_advance) = cx.text_system().ch_advance(font_id, font_size).log_err()
24787 {
24788 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
24789 matches!(
24790 ProjectSettings::get_global(cx).git.git_gutter,
24791 GitGutterSetting::TrackedFiles
24792 )
24793 });
24794 let gutter_settings = EditorSettings::get_global(cx).gutter;
24795 let show_line_numbers = self
24796 .show_line_numbers
24797 .unwrap_or(gutter_settings.line_numbers);
24798 let line_gutter_width = if show_line_numbers {
24799 // Avoid flicker-like gutter resizes when the line number gains another digit by
24800 // only resizing the gutter on files with > 10**min_line_number_digits lines.
24801 let min_width_for_number_on_gutter =
24802 ch_advance * gutter_settings.min_line_number_digits as f32;
24803 self.max_line_number_width(style, window)
24804 .max(min_width_for_number_on_gutter)
24805 } else {
24806 0.0.into()
24807 };
24808
24809 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
24810 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
24811
24812 let git_blame_entries_width =
24813 self.git_blame_gutter_max_author_length
24814 .map(|max_author_length| {
24815 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
24816 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
24817
24818 /// The number of characters to dedicate to gaps and margins.
24819 const SPACING_WIDTH: usize = 4;
24820
24821 let max_char_count = max_author_length.min(renderer.max_author_length())
24822 + ::git::SHORT_SHA_LENGTH
24823 + MAX_RELATIVE_TIMESTAMP.len()
24824 + SPACING_WIDTH;
24825
24826 ch_advance * max_char_count
24827 });
24828
24829 let is_singleton = self.buffer_snapshot().is_singleton();
24830
24831 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
24832 left_padding += if !is_singleton {
24833 ch_width * 4.0
24834 } else if show_runnables || show_breakpoints {
24835 ch_width * 3.0
24836 } else if show_git_gutter && show_line_numbers {
24837 ch_width * 2.0
24838 } else if show_git_gutter || show_line_numbers {
24839 ch_width
24840 } else {
24841 px(0.)
24842 };
24843
24844 let shows_folds = is_singleton && gutter_settings.folds;
24845
24846 let right_padding = if shows_folds && show_line_numbers {
24847 ch_width * 4.0
24848 } else if shows_folds || (!is_singleton && show_line_numbers) {
24849 ch_width * 3.0
24850 } else if show_line_numbers {
24851 ch_width
24852 } else {
24853 px(0.)
24854 };
24855
24856 GutterDimensions {
24857 left_padding,
24858 right_padding,
24859 width: line_gutter_width + left_padding + right_padding,
24860 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
24861 git_blame_entries_width,
24862 }
24863 } else if self.offset_content {
24864 GutterDimensions::default_with_margin(font_id, font_size, cx)
24865 } else {
24866 GutterDimensions::default()
24867 }
24868 }
24869
24870 pub fn render_crease_toggle(
24871 &self,
24872 buffer_row: MultiBufferRow,
24873 row_contains_cursor: bool,
24874 editor: Entity<Editor>,
24875 window: &mut Window,
24876 cx: &mut App,
24877 ) -> Option<AnyElement> {
24878 let folded = self.is_line_folded(buffer_row);
24879 let mut is_foldable = false;
24880
24881 if let Some(crease) = self
24882 .crease_snapshot
24883 .query_row(buffer_row, self.buffer_snapshot())
24884 {
24885 is_foldable = true;
24886 match crease {
24887 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
24888 if let Some(render_toggle) = render_toggle {
24889 let toggle_callback =
24890 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
24891 if folded {
24892 editor.update(cx, |editor, cx| {
24893 editor.fold_at(buffer_row, window, cx)
24894 });
24895 } else {
24896 editor.update(cx, |editor, cx| {
24897 editor.unfold_at(buffer_row, window, cx)
24898 });
24899 }
24900 });
24901 return Some((render_toggle)(
24902 buffer_row,
24903 folded,
24904 toggle_callback,
24905 window,
24906 cx,
24907 ));
24908 }
24909 }
24910 }
24911 }
24912
24913 is_foldable |= self.starts_indent(buffer_row);
24914
24915 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
24916 Some(
24917 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
24918 .toggle_state(folded)
24919 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
24920 if folded {
24921 this.unfold_at(buffer_row, window, cx);
24922 } else {
24923 this.fold_at(buffer_row, window, cx);
24924 }
24925 }))
24926 .into_any_element(),
24927 )
24928 } else {
24929 None
24930 }
24931 }
24932
24933 pub fn render_crease_trailer(
24934 &self,
24935 buffer_row: MultiBufferRow,
24936 window: &mut Window,
24937 cx: &mut App,
24938 ) -> Option<AnyElement> {
24939 let folded = self.is_line_folded(buffer_row);
24940 if let Crease::Inline { render_trailer, .. } = self
24941 .crease_snapshot
24942 .query_row(buffer_row, self.buffer_snapshot())?
24943 {
24944 let render_trailer = render_trailer.as_ref()?;
24945 Some(render_trailer(buffer_row, folded, window, cx))
24946 } else {
24947 None
24948 }
24949 }
24950
24951 pub fn max_line_number_width(&self, style: &EditorStyle, window: &mut Window) -> Pixels {
24952 let digit_count = self.widest_line_number().ilog10() + 1;
24953 column_pixels(style, digit_count as usize, window)
24954 }
24955}
24956
24957pub fn column_pixels(style: &EditorStyle, column: usize, window: &Window) -> Pixels {
24958 let font_size = style.text.font_size.to_pixels(window.rem_size());
24959 let layout = window.text_system().shape_line(
24960 SharedString::from(" ".repeat(column)),
24961 font_size,
24962 &[TextRun {
24963 len: column,
24964 font: style.text.font(),
24965 color: Hsla::default(),
24966 ..Default::default()
24967 }],
24968 None,
24969 );
24970
24971 layout.width
24972}
24973
24974impl Deref for EditorSnapshot {
24975 type Target = DisplaySnapshot;
24976
24977 fn deref(&self) -> &Self::Target {
24978 &self.display_snapshot
24979 }
24980}
24981
24982#[derive(Clone, Debug, PartialEq, Eq)]
24983pub enum EditorEvent {
24984 InputIgnored {
24985 text: Arc<str>,
24986 },
24987 InputHandled {
24988 utf16_range_to_replace: Option<Range<isize>>,
24989 text: Arc<str>,
24990 },
24991 ExcerptsAdded {
24992 buffer: Entity<Buffer>,
24993 predecessor: ExcerptId,
24994 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
24995 },
24996 ExcerptsRemoved {
24997 ids: Vec<ExcerptId>,
24998 removed_buffer_ids: Vec<BufferId>,
24999 },
25000 BufferFoldToggled {
25001 ids: Vec<ExcerptId>,
25002 folded: bool,
25003 },
25004 ExcerptsEdited {
25005 ids: Vec<ExcerptId>,
25006 },
25007 ExcerptsExpanded {
25008 ids: Vec<ExcerptId>,
25009 },
25010 BufferEdited,
25011 Edited {
25012 transaction_id: clock::Lamport,
25013 },
25014 Reparsed(BufferId),
25015 Focused,
25016 FocusedIn,
25017 Blurred,
25018 DirtyChanged,
25019 Saved,
25020 TitleChanged,
25021 SelectionsChanged {
25022 local: bool,
25023 },
25024 ScrollPositionChanged {
25025 local: bool,
25026 autoscroll: bool,
25027 },
25028 TransactionUndone {
25029 transaction_id: clock::Lamport,
25030 },
25031 TransactionBegun {
25032 transaction_id: clock::Lamport,
25033 },
25034 CursorShapeChanged,
25035 BreadcrumbsChanged,
25036 PushedToNavHistory {
25037 anchor: Anchor,
25038 is_deactivate: bool,
25039 },
25040}
25041
25042impl EventEmitter<EditorEvent> for Editor {}
25043
25044impl Focusable for Editor {
25045 fn focus_handle(&self, _cx: &App) -> FocusHandle {
25046 self.focus_handle.clone()
25047 }
25048}
25049
25050impl Render for Editor {
25051 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25052 EditorElement::new(&cx.entity(), self.create_style(cx))
25053 }
25054}
25055
25056impl EntityInputHandler for Editor {
25057 fn text_for_range(
25058 &mut self,
25059 range_utf16: Range<usize>,
25060 adjusted_range: &mut Option<Range<usize>>,
25061 _: &mut Window,
25062 cx: &mut Context<Self>,
25063 ) -> Option<String> {
25064 let snapshot = self.buffer.read(cx).read(cx);
25065 let start = snapshot.clip_offset_utf16(
25066 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)),
25067 Bias::Left,
25068 );
25069 let end = snapshot.clip_offset_utf16(
25070 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end)),
25071 Bias::Right,
25072 );
25073 if (start.0.0..end.0.0) != range_utf16 {
25074 adjusted_range.replace(start.0.0..end.0.0);
25075 }
25076 Some(snapshot.text_for_range(start..end).collect())
25077 }
25078
25079 fn selected_text_range(
25080 &mut self,
25081 ignore_disabled_input: bool,
25082 _: &mut Window,
25083 cx: &mut Context<Self>,
25084 ) -> Option<UTF16Selection> {
25085 // Prevent the IME menu from appearing when holding down an alphabetic key
25086 // while input is disabled.
25087 if !ignore_disabled_input && !self.input_enabled {
25088 return None;
25089 }
25090
25091 let selection = self
25092 .selections
25093 .newest::<MultiBufferOffsetUtf16>(&self.display_snapshot(cx));
25094 let range = selection.range();
25095
25096 Some(UTF16Selection {
25097 range: range.start.0.0..range.end.0.0,
25098 reversed: selection.reversed,
25099 })
25100 }
25101
25102 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
25103 let snapshot = self.buffer.read(cx).read(cx);
25104 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
25105 Some(range.start.to_offset_utf16(&snapshot).0.0..range.end.to_offset_utf16(&snapshot).0.0)
25106 }
25107
25108 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
25109 self.clear_highlights::<InputComposition>(cx);
25110 self.ime_transaction.take();
25111 }
25112
25113 fn replace_text_in_range(
25114 &mut self,
25115 range_utf16: Option<Range<usize>>,
25116 text: &str,
25117 window: &mut Window,
25118 cx: &mut Context<Self>,
25119 ) {
25120 if !self.input_enabled {
25121 cx.emit(EditorEvent::InputIgnored { text: text.into() });
25122 return;
25123 }
25124
25125 self.transact(window, cx, |this, window, cx| {
25126 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
25127 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25128 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25129 Some(this.selection_replacement_ranges(range_utf16, cx))
25130 } else {
25131 this.marked_text_ranges(cx)
25132 };
25133
25134 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
25135 let newest_selection_id = this.selections.newest_anchor().id;
25136 this.selections
25137 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25138 .iter()
25139 .zip(ranges_to_replace.iter())
25140 .find_map(|(selection, range)| {
25141 if selection.id == newest_selection_id {
25142 Some(
25143 (range.start.0.0 as isize - selection.head().0.0 as isize)
25144 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25145 )
25146 } else {
25147 None
25148 }
25149 })
25150 });
25151
25152 cx.emit(EditorEvent::InputHandled {
25153 utf16_range_to_replace: range_to_replace,
25154 text: text.into(),
25155 });
25156
25157 if let Some(new_selected_ranges) = new_selected_ranges {
25158 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25159 selections.select_ranges(new_selected_ranges)
25160 });
25161 this.backspace(&Default::default(), window, cx);
25162 }
25163
25164 this.handle_input(text, window, cx);
25165 });
25166
25167 if let Some(transaction) = self.ime_transaction {
25168 self.buffer.update(cx, |buffer, cx| {
25169 buffer.group_until_transaction(transaction, cx);
25170 });
25171 }
25172
25173 self.unmark_text(window, cx);
25174 }
25175
25176 fn replace_and_mark_text_in_range(
25177 &mut self,
25178 range_utf16: Option<Range<usize>>,
25179 text: &str,
25180 new_selected_range_utf16: Option<Range<usize>>,
25181 window: &mut Window,
25182 cx: &mut Context<Self>,
25183 ) {
25184 if !self.input_enabled {
25185 return;
25186 }
25187
25188 let transaction = self.transact(window, cx, |this, window, cx| {
25189 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
25190 let snapshot = this.buffer.read(cx).read(cx);
25191 if let Some(relative_range_utf16) = range_utf16.as_ref() {
25192 for marked_range in &mut marked_ranges {
25193 marked_range.end = marked_range.start + relative_range_utf16.end;
25194 marked_range.start += relative_range_utf16.start;
25195 marked_range.start =
25196 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
25197 marked_range.end =
25198 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
25199 }
25200 }
25201 Some(marked_ranges)
25202 } else if let Some(range_utf16) = range_utf16 {
25203 let range_utf16 = MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start))
25204 ..MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.end));
25205 Some(this.selection_replacement_ranges(range_utf16, cx))
25206 } else {
25207 None
25208 };
25209
25210 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
25211 let newest_selection_id = this.selections.newest_anchor().id;
25212 this.selections
25213 .all::<MultiBufferOffsetUtf16>(&this.display_snapshot(cx))
25214 .iter()
25215 .zip(ranges_to_replace.iter())
25216 .find_map(|(selection, range)| {
25217 if selection.id == newest_selection_id {
25218 Some(
25219 (range.start.0.0 as isize - selection.head().0.0 as isize)
25220 ..(range.end.0.0 as isize - selection.head().0.0 as isize),
25221 )
25222 } else {
25223 None
25224 }
25225 })
25226 });
25227
25228 cx.emit(EditorEvent::InputHandled {
25229 utf16_range_to_replace: range_to_replace,
25230 text: text.into(),
25231 });
25232
25233 if let Some(ranges) = ranges_to_replace {
25234 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
25235 s.select_ranges(ranges)
25236 });
25237 }
25238
25239 let marked_ranges = {
25240 let snapshot = this.buffer.read(cx).read(cx);
25241 this.selections
25242 .disjoint_anchors_arc()
25243 .iter()
25244 .map(|selection| {
25245 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
25246 })
25247 .collect::<Vec<_>>()
25248 };
25249
25250 if text.is_empty() {
25251 this.unmark_text(window, cx);
25252 } else {
25253 this.highlight_text::<InputComposition>(
25254 marked_ranges.clone(),
25255 HighlightStyle {
25256 underline: Some(UnderlineStyle {
25257 thickness: px(1.),
25258 color: None,
25259 wavy: false,
25260 }),
25261 ..Default::default()
25262 },
25263 cx,
25264 );
25265 }
25266
25267 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
25268 let use_autoclose = this.use_autoclose;
25269 let use_auto_surround = this.use_auto_surround;
25270 this.set_use_autoclose(false);
25271 this.set_use_auto_surround(false);
25272 this.handle_input(text, window, cx);
25273 this.set_use_autoclose(use_autoclose);
25274 this.set_use_auto_surround(use_auto_surround);
25275
25276 if let Some(new_selected_range) = new_selected_range_utf16 {
25277 let snapshot = this.buffer.read(cx).read(cx);
25278 let new_selected_ranges = marked_ranges
25279 .into_iter()
25280 .map(|marked_range| {
25281 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
25282 let new_start = MultiBufferOffsetUtf16(OffsetUtf16(
25283 insertion_start.0 + new_selected_range.start,
25284 ));
25285 let new_end = MultiBufferOffsetUtf16(OffsetUtf16(
25286 insertion_start.0 + new_selected_range.end,
25287 ));
25288 snapshot.clip_offset_utf16(new_start, Bias::Left)
25289 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
25290 })
25291 .collect::<Vec<_>>();
25292
25293 drop(snapshot);
25294 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
25295 selections.select_ranges(new_selected_ranges)
25296 });
25297 }
25298 });
25299
25300 self.ime_transaction = self.ime_transaction.or(transaction);
25301 if let Some(transaction) = self.ime_transaction {
25302 self.buffer.update(cx, |buffer, cx| {
25303 buffer.group_until_transaction(transaction, cx);
25304 });
25305 }
25306
25307 if self.text_highlights::<InputComposition>(cx).is_none() {
25308 self.ime_transaction.take();
25309 }
25310 }
25311
25312 fn bounds_for_range(
25313 &mut self,
25314 range_utf16: Range<usize>,
25315 element_bounds: gpui::Bounds<Pixels>,
25316 window: &mut Window,
25317 cx: &mut Context<Self>,
25318 ) -> Option<gpui::Bounds<Pixels>> {
25319 let text_layout_details = self.text_layout_details(window);
25320 let CharacterDimensions {
25321 em_width,
25322 em_advance,
25323 line_height,
25324 } = self.character_dimensions(window);
25325
25326 let snapshot = self.snapshot(window, cx);
25327 let scroll_position = snapshot.scroll_position();
25328 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
25329
25330 let start =
25331 MultiBufferOffsetUtf16(OffsetUtf16(range_utf16.start)).to_display_point(&snapshot);
25332 let x = Pixels::from(
25333 ScrollOffset::from(
25334 snapshot.x_for_display_point(start, &text_layout_details)
25335 + self.gutter_dimensions.full_width(),
25336 ) - scroll_left,
25337 );
25338 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
25339
25340 Some(Bounds {
25341 origin: element_bounds.origin + point(x, y),
25342 size: size(em_width, line_height),
25343 })
25344 }
25345
25346 fn character_index_for_point(
25347 &mut self,
25348 point: gpui::Point<Pixels>,
25349 _window: &mut Window,
25350 _cx: &mut Context<Self>,
25351 ) -> Option<usize> {
25352 let position_map = self.last_position_map.as_ref()?;
25353 if !position_map.text_hitbox.contains(&point) {
25354 return None;
25355 }
25356 let display_point = position_map.point_for_position(point).previous_valid;
25357 let anchor = position_map
25358 .snapshot
25359 .display_point_to_anchor(display_point, Bias::Left);
25360 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
25361 Some(utf16_offset.0.0)
25362 }
25363
25364 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
25365 self.input_enabled
25366 }
25367}
25368
25369trait SelectionExt {
25370 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
25371 fn spanned_rows(
25372 &self,
25373 include_end_if_at_line_start: bool,
25374 map: &DisplaySnapshot,
25375 ) -> Range<MultiBufferRow>;
25376}
25377
25378impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
25379 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
25380 let start = self
25381 .start
25382 .to_point(map.buffer_snapshot())
25383 .to_display_point(map);
25384 let end = self
25385 .end
25386 .to_point(map.buffer_snapshot())
25387 .to_display_point(map);
25388 if self.reversed {
25389 end..start
25390 } else {
25391 start..end
25392 }
25393 }
25394
25395 fn spanned_rows(
25396 &self,
25397 include_end_if_at_line_start: bool,
25398 map: &DisplaySnapshot,
25399 ) -> Range<MultiBufferRow> {
25400 let start = self.start.to_point(map.buffer_snapshot());
25401 let mut end = self.end.to_point(map.buffer_snapshot());
25402 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
25403 end.row -= 1;
25404 }
25405
25406 let buffer_start = map.prev_line_boundary(start).0;
25407 let buffer_end = map.next_line_boundary(end).0;
25408 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
25409 }
25410}
25411
25412impl<T: InvalidationRegion> InvalidationStack<T> {
25413 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
25414 where
25415 S: Clone + ToOffset,
25416 {
25417 while let Some(region) = self.last() {
25418 let all_selections_inside_invalidation_ranges =
25419 if selections.len() == region.ranges().len() {
25420 selections
25421 .iter()
25422 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
25423 .all(|(selection, invalidation_range)| {
25424 let head = selection.head().to_offset(buffer);
25425 invalidation_range.start <= head && invalidation_range.end >= head
25426 })
25427 } else {
25428 false
25429 };
25430
25431 if all_selections_inside_invalidation_ranges {
25432 break;
25433 } else {
25434 self.pop();
25435 }
25436 }
25437 }
25438}
25439
25440impl<T> Default for InvalidationStack<T> {
25441 fn default() -> Self {
25442 Self(Default::default())
25443 }
25444}
25445
25446impl<T> Deref for InvalidationStack<T> {
25447 type Target = Vec<T>;
25448
25449 fn deref(&self) -> &Self::Target {
25450 &self.0
25451 }
25452}
25453
25454impl<T> DerefMut for InvalidationStack<T> {
25455 fn deref_mut(&mut self) -> &mut Self::Target {
25456 &mut self.0
25457 }
25458}
25459
25460impl InvalidationRegion for SnippetState {
25461 fn ranges(&self) -> &[Range<Anchor>] {
25462 &self.ranges[self.active_index]
25463 }
25464}
25465
25466fn edit_prediction_edit_text(
25467 current_snapshot: &BufferSnapshot,
25468 edits: &[(Range<Anchor>, impl AsRef<str>)],
25469 edit_preview: &EditPreview,
25470 include_deletions: bool,
25471 cx: &App,
25472) -> HighlightedText {
25473 let edits = edits
25474 .iter()
25475 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
25476 .collect::<Vec<_>>();
25477
25478 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
25479}
25480
25481fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
25482 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
25483 // Just show the raw edit text with basic styling
25484 let mut text = String::new();
25485 let mut highlights = Vec::new();
25486
25487 let insertion_highlight_style = HighlightStyle {
25488 color: Some(cx.theme().colors().text),
25489 ..Default::default()
25490 };
25491
25492 for (_, edit_text) in edits {
25493 let start_offset = text.len();
25494 text.push_str(edit_text);
25495 let end_offset = text.len();
25496
25497 if start_offset < end_offset {
25498 highlights.push((start_offset..end_offset, insertion_highlight_style));
25499 }
25500 }
25501
25502 HighlightedText {
25503 text: text.into(),
25504 highlights,
25505 }
25506}
25507
25508pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
25509 match severity {
25510 lsp::DiagnosticSeverity::ERROR => colors.error,
25511 lsp::DiagnosticSeverity::WARNING => colors.warning,
25512 lsp::DiagnosticSeverity::INFORMATION => colors.info,
25513 lsp::DiagnosticSeverity::HINT => colors.info,
25514 _ => colors.ignored,
25515 }
25516}
25517
25518pub fn styled_runs_for_code_label<'a>(
25519 label: &'a CodeLabel,
25520 syntax_theme: &'a theme::SyntaxTheme,
25521 local_player: &'a theme::PlayerColor,
25522) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
25523 let fade_out = HighlightStyle {
25524 fade_out: Some(0.35),
25525 ..Default::default()
25526 };
25527
25528 let mut prev_end = label.filter_range.end;
25529 label
25530 .runs
25531 .iter()
25532 .enumerate()
25533 .flat_map(move |(ix, (range, highlight_id))| {
25534 let style = if *highlight_id == language::HighlightId::TABSTOP_INSERT_ID {
25535 HighlightStyle {
25536 color: Some(local_player.cursor),
25537 ..Default::default()
25538 }
25539 } else if *highlight_id == language::HighlightId::TABSTOP_REPLACE_ID {
25540 HighlightStyle {
25541 background_color: Some(local_player.selection),
25542 ..Default::default()
25543 }
25544 } else if let Some(style) = highlight_id.style(syntax_theme) {
25545 style
25546 } else {
25547 return Default::default();
25548 };
25549 let muted_style = style.highlight(fade_out);
25550
25551 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
25552 if range.start >= label.filter_range.end {
25553 if range.start > prev_end {
25554 runs.push((prev_end..range.start, fade_out));
25555 }
25556 runs.push((range.clone(), muted_style));
25557 } else if range.end <= label.filter_range.end {
25558 runs.push((range.clone(), style));
25559 } else {
25560 runs.push((range.start..label.filter_range.end, style));
25561 runs.push((label.filter_range.end..range.end, muted_style));
25562 }
25563 prev_end = cmp::max(prev_end, range.end);
25564
25565 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
25566 runs.push((prev_end..label.text.len(), fade_out));
25567 }
25568
25569 runs
25570 })
25571}
25572
25573pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
25574 let mut prev_index = 0;
25575 let mut prev_codepoint: Option<char> = None;
25576 text.char_indices()
25577 .chain([(text.len(), '\0')])
25578 .filter_map(move |(index, codepoint)| {
25579 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25580 let is_boundary = index == text.len()
25581 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
25582 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
25583 if is_boundary {
25584 let chunk = &text[prev_index..index];
25585 prev_index = index;
25586 Some(chunk)
25587 } else {
25588 None
25589 }
25590 })
25591}
25592
25593/// Given a string of text immediately before the cursor, iterates over possible
25594/// strings a snippet could match to. More precisely: returns an iterator over
25595/// suffixes of `text` created by splitting at word boundaries (before & after
25596/// every non-word character).
25597///
25598/// Shorter suffixes are returned first.
25599pub(crate) fn snippet_candidate_suffixes(
25600 text: &str,
25601 is_word_char: impl Fn(char) -> bool,
25602) -> impl std::iter::Iterator<Item = &str> {
25603 let mut prev_index = text.len();
25604 let mut prev_codepoint = None;
25605 text.char_indices()
25606 .rev()
25607 .chain([(0, '\0')])
25608 .filter_map(move |(index, codepoint)| {
25609 let prev_index = std::mem::replace(&mut prev_index, index);
25610 let prev_codepoint = prev_codepoint.replace(codepoint)?;
25611 if is_word_char(prev_codepoint) && is_word_char(codepoint) {
25612 None
25613 } else {
25614 let chunk = &text[prev_index..]; // go to end of string
25615 Some(chunk)
25616 }
25617 })
25618}
25619
25620pub trait RangeToAnchorExt: Sized {
25621 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
25622
25623 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
25624 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
25625 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
25626 }
25627}
25628
25629impl<T: ToOffset> RangeToAnchorExt for Range<T> {
25630 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
25631 let start_offset = self.start.to_offset(snapshot);
25632 let end_offset = self.end.to_offset(snapshot);
25633 if start_offset == end_offset {
25634 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
25635 } else {
25636 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
25637 }
25638 }
25639}
25640
25641pub trait RowExt {
25642 fn as_f64(&self) -> f64;
25643
25644 fn next_row(&self) -> Self;
25645
25646 fn previous_row(&self) -> Self;
25647
25648 fn minus(&self, other: Self) -> u32;
25649}
25650
25651impl RowExt for DisplayRow {
25652 fn as_f64(&self) -> f64 {
25653 self.0 as _
25654 }
25655
25656 fn next_row(&self) -> Self {
25657 Self(self.0 + 1)
25658 }
25659
25660 fn previous_row(&self) -> Self {
25661 Self(self.0.saturating_sub(1))
25662 }
25663
25664 fn minus(&self, other: Self) -> u32 {
25665 self.0 - other.0
25666 }
25667}
25668
25669impl RowExt for MultiBufferRow {
25670 fn as_f64(&self) -> f64 {
25671 self.0 as _
25672 }
25673
25674 fn next_row(&self) -> Self {
25675 Self(self.0 + 1)
25676 }
25677
25678 fn previous_row(&self) -> Self {
25679 Self(self.0.saturating_sub(1))
25680 }
25681
25682 fn minus(&self, other: Self) -> u32 {
25683 self.0 - other.0
25684 }
25685}
25686
25687trait RowRangeExt {
25688 type Row;
25689
25690 fn len(&self) -> usize;
25691
25692 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
25693}
25694
25695impl RowRangeExt for Range<MultiBufferRow> {
25696 type Row = MultiBufferRow;
25697
25698 fn len(&self) -> usize {
25699 (self.end.0 - self.start.0) as usize
25700 }
25701
25702 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
25703 (self.start.0..self.end.0).map(MultiBufferRow)
25704 }
25705}
25706
25707impl RowRangeExt for Range<DisplayRow> {
25708 type Row = DisplayRow;
25709
25710 fn len(&self) -> usize {
25711 (self.end.0 - self.start.0) as usize
25712 }
25713
25714 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
25715 (self.start.0..self.end.0).map(DisplayRow)
25716 }
25717}
25718
25719/// If select range has more than one line, we
25720/// just point the cursor to range.start.
25721fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
25722 if range.start.row == range.end.row {
25723 range
25724 } else {
25725 range.start..range.start
25726 }
25727}
25728pub struct KillRing(ClipboardItem);
25729impl Global for KillRing {}
25730
25731const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
25732
25733enum BreakpointPromptEditAction {
25734 Log,
25735 Condition,
25736 HitCondition,
25737}
25738
25739struct BreakpointPromptEditor {
25740 pub(crate) prompt: Entity<Editor>,
25741 editor: WeakEntity<Editor>,
25742 breakpoint_anchor: Anchor,
25743 breakpoint: Breakpoint,
25744 edit_action: BreakpointPromptEditAction,
25745 block_ids: HashSet<CustomBlockId>,
25746 editor_margins: Arc<Mutex<EditorMargins>>,
25747 _subscriptions: Vec<Subscription>,
25748}
25749
25750impl BreakpointPromptEditor {
25751 const MAX_LINES: u8 = 4;
25752
25753 fn new(
25754 editor: WeakEntity<Editor>,
25755 breakpoint_anchor: Anchor,
25756 breakpoint: Breakpoint,
25757 edit_action: BreakpointPromptEditAction,
25758 window: &mut Window,
25759 cx: &mut Context<Self>,
25760 ) -> Self {
25761 let base_text = match edit_action {
25762 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
25763 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
25764 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
25765 }
25766 .map(|msg| msg.to_string())
25767 .unwrap_or_default();
25768
25769 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
25770 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
25771
25772 let prompt = cx.new(|cx| {
25773 let mut prompt = Editor::new(
25774 EditorMode::AutoHeight {
25775 min_lines: 1,
25776 max_lines: Some(Self::MAX_LINES as usize),
25777 },
25778 buffer,
25779 None,
25780 window,
25781 cx,
25782 );
25783 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
25784 prompt.set_show_cursor_when_unfocused(false, cx);
25785 prompt.set_placeholder_text(
25786 match edit_action {
25787 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
25788 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
25789 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
25790 },
25791 window,
25792 cx,
25793 );
25794
25795 prompt
25796 });
25797
25798 Self {
25799 prompt,
25800 editor,
25801 breakpoint_anchor,
25802 breakpoint,
25803 edit_action,
25804 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
25805 block_ids: Default::default(),
25806 _subscriptions: vec![],
25807 }
25808 }
25809
25810 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
25811 self.block_ids.extend(block_ids)
25812 }
25813
25814 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
25815 if let Some(editor) = self.editor.upgrade() {
25816 let message = self
25817 .prompt
25818 .read(cx)
25819 .buffer
25820 .read(cx)
25821 .as_singleton()
25822 .expect("A multi buffer in breakpoint prompt isn't possible")
25823 .read(cx)
25824 .as_rope()
25825 .to_string();
25826
25827 editor.update(cx, |editor, cx| {
25828 editor.edit_breakpoint_at_anchor(
25829 self.breakpoint_anchor,
25830 self.breakpoint.clone(),
25831 match self.edit_action {
25832 BreakpointPromptEditAction::Log => {
25833 BreakpointEditAction::EditLogMessage(message.into())
25834 }
25835 BreakpointPromptEditAction::Condition => {
25836 BreakpointEditAction::EditCondition(message.into())
25837 }
25838 BreakpointPromptEditAction::HitCondition => {
25839 BreakpointEditAction::EditHitCondition(message.into())
25840 }
25841 },
25842 cx,
25843 );
25844
25845 editor.remove_blocks(self.block_ids.clone(), None, cx);
25846 cx.focus_self(window);
25847 });
25848 }
25849 }
25850
25851 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
25852 self.editor
25853 .update(cx, |editor, cx| {
25854 editor.remove_blocks(self.block_ids.clone(), None, cx);
25855 window.focus(&editor.focus_handle);
25856 })
25857 .log_err();
25858 }
25859
25860 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
25861 let settings = ThemeSettings::get_global(cx);
25862 let text_style = TextStyle {
25863 color: if self.prompt.read(cx).read_only(cx) {
25864 cx.theme().colors().text_disabled
25865 } else {
25866 cx.theme().colors().text
25867 },
25868 font_family: settings.buffer_font.family.clone(),
25869 font_fallbacks: settings.buffer_font.fallbacks.clone(),
25870 font_size: settings.buffer_font_size(cx).into(),
25871 font_weight: settings.buffer_font.weight,
25872 line_height: relative(settings.buffer_line_height.value()),
25873 ..Default::default()
25874 };
25875 EditorElement::new(
25876 &self.prompt,
25877 EditorStyle {
25878 background: cx.theme().colors().editor_background,
25879 local_player: cx.theme().players().local(),
25880 text: text_style,
25881 ..Default::default()
25882 },
25883 )
25884 }
25885}
25886
25887impl Render for BreakpointPromptEditor {
25888 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25889 let editor_margins = *self.editor_margins.lock();
25890 let gutter_dimensions = editor_margins.gutter;
25891 h_flex()
25892 .key_context("Editor")
25893 .bg(cx.theme().colors().editor_background)
25894 .border_y_1()
25895 .border_color(cx.theme().status().info_border)
25896 .size_full()
25897 .py(window.line_height() / 2.5)
25898 .on_action(cx.listener(Self::confirm))
25899 .on_action(cx.listener(Self::cancel))
25900 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
25901 .child(div().flex_1().child(self.render_prompt_editor(cx)))
25902 }
25903}
25904
25905impl Focusable for BreakpointPromptEditor {
25906 fn focus_handle(&self, cx: &App) -> FocusHandle {
25907 self.prompt.focus_handle(cx)
25908 }
25909}
25910
25911fn all_edits_insertions_or_deletions(
25912 edits: &Vec<(Range<Anchor>, Arc<str>)>,
25913 snapshot: &MultiBufferSnapshot,
25914) -> bool {
25915 let mut all_insertions = true;
25916 let mut all_deletions = true;
25917
25918 for (range, new_text) in edits.iter() {
25919 let range_is_empty = range.to_offset(snapshot).is_empty();
25920 let text_is_empty = new_text.is_empty();
25921
25922 if range_is_empty != text_is_empty {
25923 if range_is_empty {
25924 all_deletions = false;
25925 } else {
25926 all_insertions = false;
25927 }
25928 } else {
25929 return false;
25930 }
25931
25932 if !all_insertions && !all_deletions {
25933 return false;
25934 }
25935 }
25936 all_insertions || all_deletions
25937}
25938
25939struct MissingEditPredictionKeybindingTooltip;
25940
25941impl Render for MissingEditPredictionKeybindingTooltip {
25942 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
25943 ui::tooltip_container(cx, |container, cx| {
25944 container
25945 .flex_shrink_0()
25946 .max_w_80()
25947 .min_h(rems_from_px(124.))
25948 .justify_between()
25949 .child(
25950 v_flex()
25951 .flex_1()
25952 .text_ui_sm(cx)
25953 .child(Label::new("Conflict with Accept Keybinding"))
25954 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
25955 )
25956 .child(
25957 h_flex()
25958 .pb_1()
25959 .gap_1()
25960 .items_end()
25961 .w_full()
25962 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
25963 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
25964 }))
25965 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
25966 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
25967 })),
25968 )
25969 })
25970 }
25971}
25972
25973#[derive(Debug, Clone, Copy, PartialEq)]
25974pub struct LineHighlight {
25975 pub background: Background,
25976 pub border: Option<gpui::Hsla>,
25977 pub include_gutter: bool,
25978 pub type_id: Option<TypeId>,
25979}
25980
25981struct LineManipulationResult {
25982 pub new_text: String,
25983 pub line_count_before: usize,
25984 pub line_count_after: usize,
25985}
25986
25987fn render_diff_hunk_controls(
25988 row: u32,
25989 status: &DiffHunkStatus,
25990 hunk_range: Range<Anchor>,
25991 is_created_file: bool,
25992 line_height: Pixels,
25993 editor: &Entity<Editor>,
25994 _window: &mut Window,
25995 cx: &mut App,
25996) -> AnyElement {
25997 h_flex()
25998 .h(line_height)
25999 .mr_1()
26000 .gap_1()
26001 .px_0p5()
26002 .pb_1()
26003 .border_x_1()
26004 .border_b_1()
26005 .border_color(cx.theme().colors().border_variant)
26006 .rounded_b_lg()
26007 .bg(cx.theme().colors().editor_background)
26008 .gap_1()
26009 .block_mouse_except_scroll()
26010 .shadow_md()
26011 .child(if status.has_secondary_hunk() {
26012 Button::new(("stage", row as u64), "Stage")
26013 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26014 .tooltip({
26015 let focus_handle = editor.focus_handle(cx);
26016 move |_window, cx| {
26017 Tooltip::for_action_in(
26018 "Stage Hunk",
26019 &::git::ToggleStaged,
26020 &focus_handle,
26021 cx,
26022 )
26023 }
26024 })
26025 .on_click({
26026 let editor = editor.clone();
26027 move |_event, _window, cx| {
26028 editor.update(cx, |editor, cx| {
26029 editor.stage_or_unstage_diff_hunks(
26030 true,
26031 vec![hunk_range.start..hunk_range.start],
26032 cx,
26033 );
26034 });
26035 }
26036 })
26037 } else {
26038 Button::new(("unstage", row as u64), "Unstage")
26039 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
26040 .tooltip({
26041 let focus_handle = editor.focus_handle(cx);
26042 move |_window, cx| {
26043 Tooltip::for_action_in(
26044 "Unstage Hunk",
26045 &::git::ToggleStaged,
26046 &focus_handle,
26047 cx,
26048 )
26049 }
26050 })
26051 .on_click({
26052 let editor = editor.clone();
26053 move |_event, _window, cx| {
26054 editor.update(cx, |editor, cx| {
26055 editor.stage_or_unstage_diff_hunks(
26056 false,
26057 vec![hunk_range.start..hunk_range.start],
26058 cx,
26059 );
26060 });
26061 }
26062 })
26063 })
26064 .child(
26065 Button::new(("restore", row as u64), "Restore")
26066 .tooltip({
26067 let focus_handle = editor.focus_handle(cx);
26068 move |_window, cx| {
26069 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
26070 }
26071 })
26072 .on_click({
26073 let editor = editor.clone();
26074 move |_event, window, cx| {
26075 editor.update(cx, |editor, cx| {
26076 let snapshot = editor.snapshot(window, cx);
26077 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
26078 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
26079 });
26080 }
26081 })
26082 .disabled(is_created_file),
26083 )
26084 .when(
26085 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
26086 |el| {
26087 el.child(
26088 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
26089 .shape(IconButtonShape::Square)
26090 .icon_size(IconSize::Small)
26091 // .disabled(!has_multiple_hunks)
26092 .tooltip({
26093 let focus_handle = editor.focus_handle(cx);
26094 move |_window, cx| {
26095 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
26096 }
26097 })
26098 .on_click({
26099 let editor = editor.clone();
26100 move |_event, window, cx| {
26101 editor.update(cx, |editor, cx| {
26102 let snapshot = editor.snapshot(window, cx);
26103 let position =
26104 hunk_range.end.to_point(&snapshot.buffer_snapshot());
26105 editor.go_to_hunk_before_or_after_position(
26106 &snapshot,
26107 position,
26108 Direction::Next,
26109 window,
26110 cx,
26111 );
26112 editor.expand_selected_diff_hunks(cx);
26113 });
26114 }
26115 }),
26116 )
26117 .child(
26118 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
26119 .shape(IconButtonShape::Square)
26120 .icon_size(IconSize::Small)
26121 // .disabled(!has_multiple_hunks)
26122 .tooltip({
26123 let focus_handle = editor.focus_handle(cx);
26124 move |_window, cx| {
26125 Tooltip::for_action_in(
26126 "Previous Hunk",
26127 &GoToPreviousHunk,
26128 &focus_handle,
26129 cx,
26130 )
26131 }
26132 })
26133 .on_click({
26134 let editor = editor.clone();
26135 move |_event, window, cx| {
26136 editor.update(cx, |editor, cx| {
26137 let snapshot = editor.snapshot(window, cx);
26138 let point =
26139 hunk_range.start.to_point(&snapshot.buffer_snapshot());
26140 editor.go_to_hunk_before_or_after_position(
26141 &snapshot,
26142 point,
26143 Direction::Prev,
26144 window,
26145 cx,
26146 );
26147 editor.expand_selected_diff_hunks(cx);
26148 });
26149 }
26150 }),
26151 )
26152 },
26153 )
26154 .into_any_element()
26155}
26156
26157pub fn multibuffer_context_lines(cx: &App) -> u32 {
26158 EditorSettings::try_get(cx)
26159 .map(|settings| settings.excerpt_context_lines)
26160 .unwrap_or(2)
26161 .min(32)
26162}