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;
15mod blink_manager;
16mod clangd_ext;
17pub mod code_context_menus;
18pub mod display_map;
19mod editor_settings;
20mod element;
21mod git;
22mod highlight_matching_bracket;
23mod hover_links;
24pub mod hover_popover;
25mod indent_guides;
26mod inlays;
27pub mod items;
28mod jsx_tag_auto_close;
29mod linked_editing_ranges;
30mod lsp_colors;
31mod lsp_ext;
32mod mouse_context_menu;
33pub mod movement;
34mod persistence;
35mod rust_analyzer_ext;
36pub mod scroll;
37mod selections_collection;
38pub mod tasks;
39
40#[cfg(test)]
41mod code_completion_tests;
42#[cfg(test)]
43mod edit_prediction_tests;
44#[cfg(test)]
45mod editor_tests;
46mod signature_help;
47#[cfg(any(test, feature = "test-support"))]
48pub mod test;
49
50pub(crate) use actions::*;
51pub use display_map::{ChunkRenderer, ChunkRendererContext, DisplayPoint, FoldPlaceholder};
52pub use edit_prediction::Direction;
53pub use editor_settings::{
54 CurrentLineHighlight, DocumentColorsRenderMode, EditorSettings, HideMouseMode,
55 ScrollBeyondLastLine, ScrollbarAxes, SearchSettings, ShowMinimap,
56};
57pub use element::{
58 CursorLayout, EditorElement, HighlightedRange, HighlightedRangeLine, PointForPosition,
59};
60pub use git::blame::BlameRenderer;
61pub use hover_popover::hover_markdown_style;
62pub use inlays::Inlay;
63pub use items::MAX_TAB_TITLE_LEN;
64pub use lsp::CompletionContext;
65pub use lsp_ext::lsp_tasks;
66pub use multi_buffer::{
67 Anchor, AnchorRangeExt, ExcerptId, ExcerptRange, MultiBuffer, MultiBufferSnapshot, PathKey,
68 RowInfo, ToOffset, ToPoint,
69};
70pub use text::Bias;
71
72use ::git::{
73 Restore,
74 blame::{BlameEntry, ParsedCommitMessage},
75 status::FileStatus,
76};
77use aho_corasick::AhoCorasick;
78use anyhow::{Context as _, Result, anyhow};
79use blink_manager::BlinkManager;
80use buffer_diff::DiffHunkStatus;
81use client::{Collaborator, ParticipantIndex, parse_zed_link};
82use clock::ReplicaId;
83use code_context_menus::{
84 AvailableCodeAction, CodeActionContents, CodeActionsItem, CodeActionsMenu, CodeContextMenu,
85 CompletionsMenu, ContextMenuOrigin,
86};
87use collections::{BTreeMap, HashMap, HashSet, VecDeque};
88use convert_case::{Case, Casing};
89use dap::TelemetrySpawnLocation;
90use display_map::*;
91use edit_prediction::{EditPredictionProvider, EditPredictionProviderHandle};
92use editor_settings::{GoToDefinitionFallback, Minimap as MinimapSettings};
93use element::{AcceptEditPredictionBinding, LineWithInvisibles, PositionMap, layout_line};
94use futures::{
95 FutureExt, StreamExt as _,
96 future::{self, Shared, join},
97 stream::FuturesUnordered,
98};
99use fuzzy::{StringMatch, StringMatchCandidate};
100use git::blame::{GitBlame, GlobalBlameRenderer};
101use gpui::{
102 Action, Animation, AnimationExt, AnyElement, App, AppContext, AsyncWindowContext,
103 AvailableSpace, Background, Bounds, ClickEvent, ClipboardEntry, ClipboardItem, Context,
104 DispatchPhase, Edges, Entity, EntityInputHandler, EventEmitter, FocusHandle, FocusOutEvent,
105 Focusable, FontId, FontWeight, Global, HighlightStyle, Hsla, KeyContext, Modifiers,
106 MouseButton, MouseDownEvent, PaintQuad, ParentElement, Pixels, Render, ScrollHandle,
107 SharedString, Size, Stateful, Styled, Subscription, Task, TextStyle, TextStyleRefinement,
108 UTF16Selection, UnderlineStyle, UniformListScrollHandle, WeakEntity, WeakFocusHandle, Window,
109 div, point, prelude::*, pulsating_between, px, relative, size,
110};
111use hover_links::{HoverLink, HoveredLinkState, find_file};
112use hover_popover::{HoverState, hide_hover};
113use indent_guides::ActiveIndentGuidesState;
114use inlays::{InlaySplice, inlay_hints::InlayHintRefreshReason};
115use itertools::{Either, Itertools};
116use language::{
117 AutoindentMode, BlockCommentConfig, BracketMatch, BracketPair, Buffer, BufferRow,
118 BufferSnapshot, Capability, CharClassifier, CharKind, CharScopeContext, CodeLabel, CursorShape,
119 DiagnosticEntryRef, DiffOptions, EditPredictionsMode, EditPreview, HighlightedText, IndentKind,
120 IndentSize, Language, OffsetRangeExt, OutlineItem, Point, Runnable, RunnableRange, Selection,
121 SelectionGoal, TextObject, TransactionId, TreeSitterOptions, WordsQuery,
122 language_settings::{
123 self, LspInsertMode, RewrapBehavior, WordsCompletionMode, all_language_settings,
124 language_settings,
125 },
126 point_from_lsp, point_to_lsp, text_diff_with_options,
127};
128use linked_editing_ranges::refresh_linked_ranges;
129use lsp::{
130 CodeActionKind, CompletionItemKind, CompletionTriggerKind, InsertTextFormat, InsertTextMode,
131 LanguageServerId,
132};
133use lsp_colors::LspColorData;
134use markdown::Markdown;
135use mouse_context_menu::MouseContextMenu;
136use movement::TextLayoutDetails;
137use multi_buffer::{
138 ExcerptInfo, ExpandExcerptDirection, MultiBufferDiffHunk, MultiBufferPoint, MultiBufferRow,
139};
140use parking_lot::Mutex;
141use persistence::DB;
142use project::{
143 BreakpointWithPosition, CodeAction, Completion, CompletionDisplayOptions, CompletionIntent,
144 CompletionResponse, CompletionSource, DisableAiSettings, DocumentHighlight, InlayHint, InlayId,
145 InvalidationStrategy, Location, LocationLink, PrepareRenameResponse, Project, ProjectItem,
146 ProjectPath, ProjectTransaction, TaskSourceKind,
147 debugger::{
148 breakpoint_store::{
149 Breakpoint, BreakpointEditAction, BreakpointSessionState, BreakpointState,
150 BreakpointStore, BreakpointStoreEvent,
151 },
152 session::{Session, SessionEvent},
153 },
154 git_store::GitStoreEvent,
155 lsp_store::{
156 CacheInlayHints, CompletionDocumentation, FormatTrigger, LspFormatTarget,
157 OpenLspBufferHandle,
158 },
159 project_settings::{DiagnosticSeverity, GoToDiagnosticSeverityFilter, ProjectSettings},
160};
161use rand::seq::SliceRandom;
162use rpc::{ErrorCode, ErrorExt, proto::PeerId};
163use scroll::{Autoscroll, OngoingScroll, ScrollAnchor, ScrollManager};
164use selections_collection::{MutableSelectionsCollection, SelectionsCollection};
165use serde::{Deserialize, Serialize};
166use settings::{
167 GitGutterSetting, RelativeLineNumbers, Settings, SettingsLocation, SettingsStore,
168 update_settings_file,
169};
170use smallvec::{SmallVec, smallvec};
171use snippet::Snippet;
172use std::{
173 any::{Any, TypeId},
174 borrow::Cow,
175 cell::{OnceCell, RefCell},
176 cmp::{self, Ordering, Reverse},
177 iter::{self, Peekable},
178 mem,
179 num::NonZeroU32,
180 ops::{Deref, DerefMut, Not, Range, RangeInclusive},
181 path::{Path, PathBuf},
182 rc::Rc,
183 sync::Arc,
184 time::{Duration, Instant},
185};
186use task::{ResolvedTask, RunnableTag, TaskTemplate, TaskVariables};
187use text::{BufferId, FromAnchor, OffsetUtf16, Rope, ToOffset as _};
188use theme::{
189 ActiveTheme, PlayerColor, StatusColors, SyntaxTheme, Theme, ThemeSettings,
190 observe_buffer_font_size_adjustment,
191};
192use ui::{
193 ButtonSize, ButtonStyle, ContextMenu, Disclosure, IconButton, IconButtonShape, IconName,
194 IconSize, Indicator, Key, Tooltip, h_flex, prelude::*, scrollbars::ScrollbarAutoHide,
195};
196use util::{RangeExt, ResultExt, TryFutureExt, maybe, post_inc};
197use workspace::{
198 CollaboratorId, Item as WorkspaceItem, ItemId, ItemNavHistory, OpenInTerminal, OpenTerminal,
199 RestoreOnStartupBehavior, SERIALIZATION_THROTTLE_TIME, SplitDirection, TabBarSettings, Toast,
200 ViewId, Workspace, WorkspaceId, WorkspaceSettings,
201 item::{ItemBufferKind, ItemHandle, PreviewTabsSettings, SaveOptions},
202 notifications::{DetachAndPromptErr, NotificationId, NotifyTaskExt},
203 searchable::SearchEvent,
204};
205
206use crate::{
207 code_context_menus::CompletionsMenuSource,
208 editor_settings::MultiCursorModifier,
209 hover_links::{find_url, find_url_from_range},
210 inlays::{
211 InlineValueCache,
212 inlay_hints::{LspInlayHintData, inlay_hint_settings},
213 },
214 scroll::{ScrollOffset, ScrollPixelOffset},
215 selections_collection::resolve_selections_wrapping_blocks,
216 signature_help::{SignatureHelpHiddenBy, SignatureHelpState},
217};
218
219pub const FILE_HEADER_HEIGHT: u32 = 2;
220pub const MULTI_BUFFER_EXCERPT_HEADER_HEIGHT: u32 = 1;
221const CURSOR_BLINK_INTERVAL: Duration = Duration::from_millis(500);
222const MAX_LINE_LEN: usize = 1024;
223const MIN_NAVIGATION_HISTORY_ROW_DELTA: i64 = 10;
224const MAX_SELECTION_HISTORY_LEN: usize = 1024;
225pub(crate) const CURSORS_VISIBLE_FOR: Duration = Duration::from_millis(2000);
226#[doc(hidden)]
227pub const CODE_ACTIONS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(250);
228pub const SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(100);
229
230pub(crate) const CODE_ACTION_TIMEOUT: Duration = Duration::from_secs(5);
231pub(crate) const FORMAT_TIMEOUT: Duration = Duration::from_secs(5);
232pub(crate) const SCROLL_CENTER_TOP_BOTTOM_DEBOUNCE_TIMEOUT: Duration = Duration::from_secs(1);
233pub const FETCH_COLORS_DEBOUNCE_TIMEOUT: Duration = Duration::from_millis(150);
234
235pub(crate) const EDIT_PREDICTION_KEY_CONTEXT: &str = "edit_prediction";
236pub(crate) const EDIT_PREDICTION_CONFLICT_KEY_CONTEXT: &str = "edit_prediction_conflict";
237pub(crate) const MINIMAP_FONT_SIZE: AbsoluteLength = AbsoluteLength::Pixels(px(2.));
238
239pub type RenderDiffHunkControlsFn = Arc<
240 dyn Fn(
241 u32,
242 &DiffHunkStatus,
243 Range<Anchor>,
244 bool,
245 Pixels,
246 &Entity<Editor>,
247 &mut Window,
248 &mut App,
249 ) -> AnyElement,
250>;
251
252enum ReportEditorEvent {
253 Saved { auto_saved: bool },
254 EditorOpened,
255 Closed,
256}
257
258impl ReportEditorEvent {
259 pub fn event_type(&self) -> &'static str {
260 match self {
261 Self::Saved { .. } => "Editor Saved",
262 Self::EditorOpened => "Editor Opened",
263 Self::Closed => "Editor Closed",
264 }
265 }
266}
267
268pub enum ActiveDebugLine {}
269pub enum DebugStackFrameLine {}
270enum DocumentHighlightRead {}
271enum DocumentHighlightWrite {}
272enum InputComposition {}
273pub enum PendingInput {}
274enum SelectedTextHighlight {}
275
276pub enum ConflictsOuter {}
277pub enum ConflictsOurs {}
278pub enum ConflictsTheirs {}
279pub enum ConflictsOursMarker {}
280pub enum ConflictsTheirsMarker {}
281
282#[derive(Debug, Copy, Clone, PartialEq, Eq)]
283pub enum Navigated {
284 Yes,
285 No,
286}
287
288impl Navigated {
289 pub fn from_bool(yes: bool) -> Navigated {
290 if yes { Navigated::Yes } else { Navigated::No }
291 }
292}
293
294#[derive(Debug, Clone, PartialEq, Eq)]
295enum DisplayDiffHunk {
296 Folded {
297 display_row: DisplayRow,
298 },
299 Unfolded {
300 is_created_file: bool,
301 diff_base_byte_range: Range<usize>,
302 display_row_range: Range<DisplayRow>,
303 multi_buffer_range: Range<Anchor>,
304 status: DiffHunkStatus,
305 },
306}
307
308pub enum HideMouseCursorOrigin {
309 TypingAction,
310 MovementAction,
311}
312
313pub fn init(cx: &mut App) {
314 cx.set_global(GlobalBlameRenderer(Arc::new(())));
315
316 workspace::register_project_item::<Editor>(cx);
317 workspace::FollowableViewRegistry::register::<Editor>(cx);
318 workspace::register_serializable_item::<Editor>(cx);
319
320 cx.observe_new(
321 |workspace: &mut Workspace, _: Option<&mut Window>, _cx: &mut Context<Workspace>| {
322 workspace.register_action(Editor::new_file);
323 workspace.register_action(Editor::new_file_split);
324 workspace.register_action(Editor::new_file_vertical);
325 workspace.register_action(Editor::new_file_horizontal);
326 workspace.register_action(Editor::cancel_language_server_work);
327 workspace.register_action(Editor::toggle_focus);
328 },
329 )
330 .detach();
331
332 cx.on_action(move |_: &workspace::NewFile, cx| {
333 let app_state = workspace::AppState::global(cx);
334 if let Some(app_state) = app_state.upgrade() {
335 workspace::open_new(
336 Default::default(),
337 app_state,
338 cx,
339 |workspace, window, cx| {
340 Editor::new_file(workspace, &Default::default(), window, cx)
341 },
342 )
343 .detach();
344 }
345 });
346 cx.on_action(move |_: &workspace::NewWindow, cx| {
347 let app_state = workspace::AppState::global(cx);
348 if let Some(app_state) = app_state.upgrade() {
349 workspace::open_new(
350 Default::default(),
351 app_state,
352 cx,
353 |workspace, window, cx| {
354 cx.activate(true);
355 Editor::new_file(workspace, &Default::default(), window, cx)
356 },
357 )
358 .detach();
359 }
360 });
361}
362
363pub fn set_blame_renderer(renderer: impl BlameRenderer + 'static, cx: &mut App) {
364 cx.set_global(GlobalBlameRenderer(Arc::new(renderer)));
365}
366
367pub trait DiagnosticRenderer {
368 fn render_group(
369 &self,
370 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
371 buffer_id: BufferId,
372 snapshot: EditorSnapshot,
373 editor: WeakEntity<Editor>,
374 cx: &mut App,
375 ) -> Vec<BlockProperties<Anchor>>;
376
377 fn render_hover(
378 &self,
379 diagnostic_group: Vec<DiagnosticEntryRef<'_, Point>>,
380 range: Range<Point>,
381 buffer_id: BufferId,
382 cx: &mut App,
383 ) -> Option<Entity<markdown::Markdown>>;
384
385 fn open_link(
386 &self,
387 editor: &mut Editor,
388 link: SharedString,
389 window: &mut Window,
390 cx: &mut Context<Editor>,
391 );
392}
393
394pub(crate) struct GlobalDiagnosticRenderer(pub Arc<dyn DiagnosticRenderer>);
395
396impl GlobalDiagnosticRenderer {
397 fn global(cx: &App) -> Option<Arc<dyn DiagnosticRenderer>> {
398 cx.try_global::<Self>().map(|g| g.0.clone())
399 }
400}
401
402impl gpui::Global for GlobalDiagnosticRenderer {}
403pub fn set_diagnostic_renderer(renderer: impl DiagnosticRenderer + 'static, cx: &mut App) {
404 cx.set_global(GlobalDiagnosticRenderer(Arc::new(renderer)));
405}
406
407pub struct SearchWithinRange;
408
409trait InvalidationRegion {
410 fn ranges(&self) -> &[Range<Anchor>];
411}
412
413#[derive(Clone, Debug, PartialEq)]
414pub enum SelectPhase {
415 Begin {
416 position: DisplayPoint,
417 add: bool,
418 click_count: usize,
419 },
420 BeginColumnar {
421 position: DisplayPoint,
422 reset: bool,
423 mode: ColumnarMode,
424 goal_column: u32,
425 },
426 Extend {
427 position: DisplayPoint,
428 click_count: usize,
429 },
430 Update {
431 position: DisplayPoint,
432 goal_column: u32,
433 scroll_delta: gpui::Point<f32>,
434 },
435 End,
436}
437
438#[derive(Clone, Debug, PartialEq)]
439pub enum ColumnarMode {
440 FromMouse,
441 FromSelection,
442}
443
444#[derive(Clone, Debug)]
445pub enum SelectMode {
446 Character,
447 Word(Range<Anchor>),
448 Line(Range<Anchor>),
449 All,
450}
451
452#[derive(Copy, Clone, Default, PartialEq, Eq, Debug)]
453pub enum SizingBehavior {
454 /// The editor will layout itself using `size_full` and will include the vertical
455 /// scroll margin as requested by user settings.
456 #[default]
457 Default,
458 /// The editor will layout itself using `size_full`, but will not have any
459 /// vertical overscroll.
460 ExcludeOverscrollMargin,
461 /// The editor will request a vertical size according to its content and will be
462 /// layouted without a vertical scroll margin.
463 SizeByContent,
464}
465
466#[derive(Clone, PartialEq, Eq, Debug)]
467pub enum EditorMode {
468 SingleLine,
469 AutoHeight {
470 min_lines: usize,
471 max_lines: Option<usize>,
472 },
473 Full {
474 /// When set to `true`, the editor will scale its UI elements with the buffer font size.
475 scale_ui_elements_with_buffer_font_size: bool,
476 /// When set to `true`, the editor will render a background for the active line.
477 show_active_line_background: bool,
478 /// Determines the sizing behavior for this editor
479 sizing_behavior: SizingBehavior,
480 },
481 Minimap {
482 parent: WeakEntity<Editor>,
483 },
484}
485
486impl EditorMode {
487 pub fn full() -> Self {
488 Self::Full {
489 scale_ui_elements_with_buffer_font_size: true,
490 show_active_line_background: true,
491 sizing_behavior: SizingBehavior::Default,
492 }
493 }
494
495 #[inline]
496 pub fn is_full(&self) -> bool {
497 matches!(self, Self::Full { .. })
498 }
499
500 #[inline]
501 pub fn is_single_line(&self) -> bool {
502 matches!(self, Self::SingleLine { .. })
503 }
504
505 #[inline]
506 fn is_minimap(&self) -> bool {
507 matches!(self, Self::Minimap { .. })
508 }
509}
510
511#[derive(Copy, Clone, Debug)]
512pub enum SoftWrap {
513 /// Prefer not to wrap at all.
514 ///
515 /// Note: this is currently internal, as actually limited by [`crate::MAX_LINE_LEN`] until it wraps.
516 /// The mode is used inside git diff hunks, where it's seems currently more useful to not wrap as much as possible.
517 GitDiff,
518 /// Prefer a single line generally, unless an overly long line is encountered.
519 None,
520 /// Soft wrap lines that exceed the editor width.
521 EditorWidth,
522 /// Soft wrap lines at the preferred line length.
523 Column(u32),
524 /// Soft wrap line at the preferred line length or the editor width (whichever is smaller).
525 Bounded(u32),
526}
527
528#[derive(Clone)]
529pub struct EditorStyle {
530 pub background: Hsla,
531 pub border: Hsla,
532 pub local_player: PlayerColor,
533 pub text: TextStyle,
534 pub scrollbar_width: Pixels,
535 pub syntax: Arc<SyntaxTheme>,
536 pub status: StatusColors,
537 pub inlay_hints_style: HighlightStyle,
538 pub edit_prediction_styles: EditPredictionStyles,
539 pub unnecessary_code_fade: f32,
540 pub show_underlines: bool,
541}
542
543impl Default for EditorStyle {
544 fn default() -> Self {
545 Self {
546 background: Hsla::default(),
547 border: Hsla::default(),
548 local_player: PlayerColor::default(),
549 text: TextStyle::default(),
550 scrollbar_width: Pixels::default(),
551 syntax: Default::default(),
552 // HACK: Status colors don't have a real default.
553 // We should look into removing the status colors from the editor
554 // style and retrieve them directly from the theme.
555 status: StatusColors::dark(),
556 inlay_hints_style: HighlightStyle::default(),
557 edit_prediction_styles: EditPredictionStyles {
558 insertion: HighlightStyle::default(),
559 whitespace: HighlightStyle::default(),
560 },
561 unnecessary_code_fade: Default::default(),
562 show_underlines: true,
563 }
564 }
565}
566
567pub fn make_inlay_hints_style(cx: &mut App) -> HighlightStyle {
568 let show_background = language_settings::language_settings(None, None, cx)
569 .inlay_hints
570 .show_background;
571
572 let mut style = cx.theme().syntax().get("hint");
573
574 if style.color.is_none() {
575 style.color = Some(cx.theme().status().hint);
576 }
577
578 if !show_background {
579 style.background_color = None;
580 return style;
581 }
582
583 if style.background_color.is_none() {
584 style.background_color = Some(cx.theme().status().hint_background);
585 }
586
587 style
588}
589
590pub fn make_suggestion_styles(cx: &mut App) -> EditPredictionStyles {
591 EditPredictionStyles {
592 insertion: HighlightStyle {
593 color: Some(cx.theme().status().predictive),
594 ..HighlightStyle::default()
595 },
596 whitespace: HighlightStyle {
597 background_color: Some(cx.theme().status().created_background),
598 ..HighlightStyle::default()
599 },
600 }
601}
602
603type CompletionId = usize;
604
605pub(crate) enum EditDisplayMode {
606 TabAccept,
607 DiffPopover,
608 Inline,
609}
610
611enum EditPrediction {
612 Edit {
613 edits: Vec<(Range<Anchor>, Arc<str>)>,
614 edit_preview: Option<EditPreview>,
615 display_mode: EditDisplayMode,
616 snapshot: BufferSnapshot,
617 },
618 /// Move to a specific location in the active editor
619 MoveWithin {
620 target: Anchor,
621 snapshot: BufferSnapshot,
622 },
623 /// Move to a specific location in a different editor (not the active one)
624 MoveOutside {
625 target: language::Anchor,
626 snapshot: BufferSnapshot,
627 },
628}
629
630struct EditPredictionState {
631 inlay_ids: Vec<InlayId>,
632 completion: EditPrediction,
633 completion_id: Option<SharedString>,
634 invalidation_range: Option<Range<Anchor>>,
635}
636
637enum EditPredictionSettings {
638 Disabled,
639 Enabled {
640 show_in_menu: bool,
641 preview_requires_modifier: bool,
642 },
643}
644
645enum EditPredictionHighlight {}
646
647#[derive(Debug, Clone)]
648struct InlineDiagnostic {
649 message: SharedString,
650 group_id: usize,
651 is_primary: bool,
652 start: Point,
653 severity: lsp::DiagnosticSeverity,
654}
655
656pub enum MenuEditPredictionsPolicy {
657 Never,
658 ByProvider,
659}
660
661pub enum EditPredictionPreview {
662 /// Modifier is not pressed
663 Inactive { released_too_fast: bool },
664 /// Modifier pressed
665 Active {
666 since: Instant,
667 previous_scroll_position: Option<ScrollAnchor>,
668 },
669}
670
671impl EditPredictionPreview {
672 pub fn released_too_fast(&self) -> bool {
673 match self {
674 EditPredictionPreview::Inactive { released_too_fast } => *released_too_fast,
675 EditPredictionPreview::Active { .. } => false,
676 }
677 }
678
679 pub fn set_previous_scroll_position(&mut self, scroll_position: Option<ScrollAnchor>) {
680 if let EditPredictionPreview::Active {
681 previous_scroll_position,
682 ..
683 } = self
684 {
685 *previous_scroll_position = scroll_position;
686 }
687 }
688}
689
690pub struct ContextMenuOptions {
691 pub min_entries_visible: usize,
692 pub max_entries_visible: usize,
693 pub placement: Option<ContextMenuPlacement>,
694}
695
696#[derive(Debug, Clone, PartialEq, Eq)]
697pub enum ContextMenuPlacement {
698 Above,
699 Below,
700}
701
702#[derive(Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Debug, Default)]
703struct EditorActionId(usize);
704
705impl EditorActionId {
706 pub fn post_inc(&mut self) -> Self {
707 let answer = self.0;
708
709 *self = Self(answer + 1);
710
711 Self(answer)
712 }
713}
714
715// type GetFieldEditorTheme = dyn Fn(&theme::Theme) -> theme::FieldEditor;
716// type OverrideTextStyle = dyn Fn(&EditorStyle) -> Option<HighlightStyle>;
717
718type BackgroundHighlight = (fn(&Theme) -> Hsla, Arc<[Range<Anchor>]>);
719type GutterHighlight = (fn(&App) -> Hsla, Vec<Range<Anchor>>);
720
721#[derive(Default)]
722struct ScrollbarMarkerState {
723 scrollbar_size: Size<Pixels>,
724 dirty: bool,
725 markers: Arc<[PaintQuad]>,
726 pending_refresh: Option<Task<Result<()>>>,
727}
728
729impl ScrollbarMarkerState {
730 fn should_refresh(&self, scrollbar_size: Size<Pixels>) -> bool {
731 self.pending_refresh.is_none() && (self.scrollbar_size != scrollbar_size || self.dirty)
732 }
733}
734
735#[derive(Clone, Copy, PartialEq, Eq)]
736pub enum MinimapVisibility {
737 Disabled,
738 Enabled {
739 /// The configuration currently present in the users settings.
740 setting_configuration: bool,
741 /// Whether to override the currently set visibility from the users setting.
742 toggle_override: bool,
743 },
744}
745
746impl MinimapVisibility {
747 fn for_mode(mode: &EditorMode, cx: &App) -> Self {
748 if mode.is_full() {
749 Self::Enabled {
750 setting_configuration: EditorSettings::get_global(cx).minimap.minimap_enabled(),
751 toggle_override: false,
752 }
753 } else {
754 Self::Disabled
755 }
756 }
757
758 fn hidden(&self) -> Self {
759 match *self {
760 Self::Enabled {
761 setting_configuration,
762 ..
763 } => Self::Enabled {
764 setting_configuration,
765 toggle_override: setting_configuration,
766 },
767 Self::Disabled => Self::Disabled,
768 }
769 }
770
771 fn disabled(&self) -> bool {
772 matches!(*self, Self::Disabled)
773 }
774
775 fn settings_visibility(&self) -> bool {
776 match *self {
777 Self::Enabled {
778 setting_configuration,
779 ..
780 } => setting_configuration,
781 _ => false,
782 }
783 }
784
785 fn visible(&self) -> bool {
786 match *self {
787 Self::Enabled {
788 setting_configuration,
789 toggle_override,
790 } => setting_configuration ^ toggle_override,
791 _ => false,
792 }
793 }
794
795 fn toggle_visibility(&self) -> Self {
796 match *self {
797 Self::Enabled {
798 toggle_override,
799 setting_configuration,
800 } => Self::Enabled {
801 setting_configuration,
802 toggle_override: !toggle_override,
803 },
804 Self::Disabled => Self::Disabled,
805 }
806 }
807}
808
809#[derive(Debug, Clone, Copy, PartialEq, Eq)]
810pub enum BufferSerialization {
811 All,
812 NonDirtyBuffers,
813}
814
815impl BufferSerialization {
816 fn new(restore_unsaved_buffers: bool) -> Self {
817 if restore_unsaved_buffers {
818 Self::All
819 } else {
820 Self::NonDirtyBuffers
821 }
822 }
823}
824
825#[derive(Clone, Debug)]
826struct RunnableTasks {
827 templates: Vec<(TaskSourceKind, TaskTemplate)>,
828 offset: multi_buffer::Anchor,
829 // We need the column at which the task context evaluation should take place (when we're spawning it via gutter).
830 column: u32,
831 // Values of all named captures, including those starting with '_'
832 extra_variables: HashMap<String, String>,
833 // Full range of the tagged region. We use it to determine which `extra_variables` to grab for context resolution in e.g. a modal.
834 context_range: Range<BufferOffset>,
835}
836
837impl RunnableTasks {
838 fn resolve<'a>(
839 &'a self,
840 cx: &'a task::TaskContext,
841 ) -> impl Iterator<Item = (TaskSourceKind, ResolvedTask)> + 'a {
842 self.templates.iter().filter_map(|(kind, template)| {
843 template
844 .resolve_task(&kind.to_id_base(), cx)
845 .map(|task| (kind.clone(), task))
846 })
847 }
848}
849
850#[derive(Clone)]
851pub struct ResolvedTasks {
852 templates: SmallVec<[(TaskSourceKind, ResolvedTask); 1]>,
853 position: Anchor,
854}
855
856#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
857struct BufferOffset(usize);
858
859/// Addons allow storing per-editor state in other crates (e.g. Vim)
860pub trait Addon: 'static {
861 fn extend_key_context(&self, _: &mut KeyContext, _: &App) {}
862
863 fn render_buffer_header_controls(
864 &self,
865 _: &ExcerptInfo,
866 _: &Window,
867 _: &App,
868 ) -> Option<AnyElement> {
869 None
870 }
871
872 fn override_status_for_buffer_id(&self, _: BufferId, _: &App) -> Option<FileStatus> {
873 None
874 }
875
876 fn to_any(&self) -> &dyn std::any::Any;
877
878 fn to_any_mut(&mut self) -> Option<&mut dyn std::any::Any> {
879 None
880 }
881}
882
883struct ChangeLocation {
884 current: Option<Vec<Anchor>>,
885 original: Vec<Anchor>,
886}
887impl ChangeLocation {
888 fn locations(&self) -> &[Anchor] {
889 self.current.as_ref().unwrap_or(&self.original)
890 }
891}
892
893/// A set of caret positions, registered when the editor was edited.
894pub struct ChangeList {
895 changes: Vec<ChangeLocation>,
896 /// Currently "selected" change.
897 position: Option<usize>,
898}
899
900impl ChangeList {
901 pub fn new() -> Self {
902 Self {
903 changes: Vec::new(),
904 position: None,
905 }
906 }
907
908 /// Moves to the next change in the list (based on the direction given) and returns the caret positions for the next change.
909 /// If reaches the end of the list in the direction, returns the corresponding change until called for a different direction.
910 pub fn next_change(&mut self, count: usize, direction: Direction) -> Option<&[Anchor]> {
911 if self.changes.is_empty() {
912 return None;
913 }
914
915 let prev = self.position.unwrap_or(self.changes.len());
916 let next = if direction == Direction::Prev {
917 prev.saturating_sub(count)
918 } else {
919 (prev + count).min(self.changes.len() - 1)
920 };
921 self.position = Some(next);
922 self.changes.get(next).map(|change| change.locations())
923 }
924
925 /// Adds a new change to the list, resetting the change list position.
926 pub fn push_to_change_list(&mut self, group: bool, new_positions: Vec<Anchor>) {
927 self.position.take();
928 if let Some(last) = self.changes.last_mut()
929 && group
930 {
931 last.current = Some(new_positions)
932 } else {
933 self.changes.push(ChangeLocation {
934 original: new_positions,
935 current: None,
936 });
937 }
938 }
939
940 pub fn last(&self) -> Option<&[Anchor]> {
941 self.changes.last().map(|change| change.locations())
942 }
943
944 pub fn last_before_grouping(&self) -> Option<&[Anchor]> {
945 self.changes.last().map(|change| change.original.as_slice())
946 }
947
948 pub fn invert_last_group(&mut self) {
949 if let Some(last) = self.changes.last_mut()
950 && let Some(current) = last.current.as_mut()
951 {
952 mem::swap(&mut last.original, current);
953 }
954 }
955}
956
957#[derive(Clone)]
958struct InlineBlamePopoverState {
959 scroll_handle: ScrollHandle,
960 commit_message: Option<ParsedCommitMessage>,
961 markdown: Entity<Markdown>,
962}
963
964struct InlineBlamePopover {
965 position: gpui::Point<Pixels>,
966 hide_task: Option<Task<()>>,
967 popover_bounds: Option<Bounds<Pixels>>,
968 popover_state: InlineBlamePopoverState,
969 keyboard_grace: bool,
970}
971
972enum SelectionDragState {
973 /// State when no drag related activity is detected.
974 None,
975 /// State when the mouse is down on a selection that is about to be dragged.
976 ReadyToDrag {
977 selection: Selection<Anchor>,
978 click_position: gpui::Point<Pixels>,
979 mouse_down_time: Instant,
980 },
981 /// State when the mouse is dragging the selection in the editor.
982 Dragging {
983 selection: Selection<Anchor>,
984 drop_cursor: Selection<Anchor>,
985 hide_drop_cursor: bool,
986 },
987}
988
989enum ColumnarSelectionState {
990 FromMouse {
991 selection_tail: Anchor,
992 display_point: Option<DisplayPoint>,
993 },
994 FromSelection {
995 selection_tail: Anchor,
996 },
997}
998
999/// Represents a breakpoint indicator that shows up when hovering over lines in the gutter that don't have
1000/// a breakpoint on them.
1001#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1002struct PhantomBreakpointIndicator {
1003 display_row: DisplayRow,
1004 /// There's a small debounce between hovering over the line and showing the indicator.
1005 /// We don't want to show the indicator when moving the mouse from editor to e.g. project panel.
1006 is_active: bool,
1007 collides_with_existing_breakpoint: bool,
1008}
1009
1010/// Zed's primary implementation of text input, allowing users to edit a [`MultiBuffer`].
1011///
1012/// See the [module level documentation](self) for more information.
1013pub struct Editor {
1014 focus_handle: FocusHandle,
1015 last_focused_descendant: Option<WeakFocusHandle>,
1016 /// The text buffer being edited
1017 buffer: Entity<MultiBuffer>,
1018 /// Map of how text in the buffer should be displayed.
1019 /// Handles soft wraps, folds, fake inlay text insertions, etc.
1020 pub display_map: Entity<DisplayMap>,
1021 placeholder_display_map: Option<Entity<DisplayMap>>,
1022 pub selections: SelectionsCollection,
1023 pub scroll_manager: ScrollManager,
1024 /// When inline assist editors are linked, they all render cursors because
1025 /// typing enters text into each of them, even the ones that aren't focused.
1026 pub(crate) show_cursor_when_unfocused: bool,
1027 columnar_selection_state: Option<ColumnarSelectionState>,
1028 add_selections_state: Option<AddSelectionsState>,
1029 select_next_state: Option<SelectNextState>,
1030 select_prev_state: Option<SelectNextState>,
1031 selection_history: SelectionHistory,
1032 defer_selection_effects: bool,
1033 deferred_selection_effects_state: Option<DeferredSelectionEffectsState>,
1034 autoclose_regions: Vec<AutocloseRegion>,
1035 snippet_stack: InvalidationStack<SnippetState>,
1036 select_syntax_node_history: SelectSyntaxNodeHistory,
1037 ime_transaction: Option<TransactionId>,
1038 pub diagnostics_max_severity: DiagnosticSeverity,
1039 active_diagnostics: ActiveDiagnostic,
1040 show_inline_diagnostics: bool,
1041 inline_diagnostics_update: Task<()>,
1042 inline_diagnostics_enabled: bool,
1043 diagnostics_enabled: bool,
1044 word_completions_enabled: bool,
1045 inline_diagnostics: Vec<(Anchor, InlineDiagnostic)>,
1046 soft_wrap_mode_override: Option<language_settings::SoftWrap>,
1047 hard_wrap: Option<usize>,
1048 project: Option<Entity<Project>>,
1049 semantics_provider: Option<Rc<dyn SemanticsProvider>>,
1050 completion_provider: Option<Rc<dyn CompletionProvider>>,
1051 collaboration_hub: Option<Box<dyn CollaborationHub>>,
1052 blink_manager: Entity<BlinkManager>,
1053 show_cursor_names: bool,
1054 hovered_cursors: HashMap<HoveredCursor, Task<()>>,
1055 pub show_local_selections: bool,
1056 mode: EditorMode,
1057 show_breadcrumbs: bool,
1058 show_gutter: bool,
1059 show_scrollbars: ScrollbarAxes,
1060 minimap_visibility: MinimapVisibility,
1061 offset_content: bool,
1062 disable_expand_excerpt_buttons: bool,
1063 show_line_numbers: Option<bool>,
1064 use_relative_line_numbers: Option<bool>,
1065 show_git_diff_gutter: Option<bool>,
1066 show_code_actions: Option<bool>,
1067 show_runnables: Option<bool>,
1068 show_breakpoints: Option<bool>,
1069 show_wrap_guides: Option<bool>,
1070 show_indent_guides: Option<bool>,
1071 highlight_order: usize,
1072 highlighted_rows: HashMap<TypeId, Vec<RowHighlight>>,
1073 background_highlights: HashMap<HighlightKey, BackgroundHighlight>,
1074 gutter_highlights: HashMap<TypeId, GutterHighlight>,
1075 scrollbar_marker_state: ScrollbarMarkerState,
1076 active_indent_guides_state: ActiveIndentGuidesState,
1077 nav_history: Option<ItemNavHistory>,
1078 context_menu: RefCell<Option<CodeContextMenu>>,
1079 context_menu_options: Option<ContextMenuOptions>,
1080 mouse_context_menu: Option<MouseContextMenu>,
1081 completion_tasks: Vec<(CompletionId, Task<()>)>,
1082 inline_blame_popover: Option<InlineBlamePopover>,
1083 inline_blame_popover_show_task: Option<Task<()>>,
1084 signature_help_state: SignatureHelpState,
1085 auto_signature_help: Option<bool>,
1086 find_all_references_task_sources: Vec<Anchor>,
1087 next_completion_id: CompletionId,
1088 available_code_actions: Option<(Location, Rc<[AvailableCodeAction]>)>,
1089 code_actions_task: Option<Task<Result<()>>>,
1090 quick_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1091 debounced_selection_highlight_task: Option<(Range<Anchor>, Task<()>)>,
1092 document_highlights_task: Option<Task<()>>,
1093 linked_editing_range_task: Option<Task<Option<()>>>,
1094 linked_edit_ranges: linked_editing_ranges::LinkedEditingRanges,
1095 pending_rename: Option<RenameState>,
1096 searchable: bool,
1097 cursor_shape: CursorShape,
1098 current_line_highlight: Option<CurrentLineHighlight>,
1099 autoindent_mode: Option<AutoindentMode>,
1100 workspace: Option<(WeakEntity<Workspace>, Option<WorkspaceId>)>,
1101 input_enabled: bool,
1102 use_modal_editing: bool,
1103 read_only: bool,
1104 leader_id: Option<CollaboratorId>,
1105 remote_id: Option<ViewId>,
1106 pub hover_state: HoverState,
1107 pending_mouse_down: Option<Rc<RefCell<Option<MouseDownEvent>>>>,
1108 gutter_hovered: bool,
1109 hovered_link_state: Option<HoveredLinkState>,
1110 edit_prediction_provider: Option<RegisteredEditPredictionProvider>,
1111 code_action_providers: Vec<Rc<dyn CodeActionProvider>>,
1112 active_edit_prediction: Option<EditPredictionState>,
1113 /// Used to prevent flickering as the user types while the menu is open
1114 stale_edit_prediction_in_menu: Option<EditPredictionState>,
1115 edit_prediction_settings: EditPredictionSettings,
1116 edit_predictions_hidden_for_vim_mode: bool,
1117 show_edit_predictions_override: Option<bool>,
1118 menu_edit_predictions_policy: MenuEditPredictionsPolicy,
1119 edit_prediction_preview: EditPredictionPreview,
1120 edit_prediction_indent_conflict: bool,
1121 edit_prediction_requires_modifier_in_indent_conflict: bool,
1122 next_inlay_id: usize,
1123 next_color_inlay_id: usize,
1124 _subscriptions: Vec<Subscription>,
1125 pixel_position_of_newest_cursor: Option<gpui::Point<Pixels>>,
1126 gutter_dimensions: GutterDimensions,
1127 style: Option<EditorStyle>,
1128 text_style_refinement: Option<TextStyleRefinement>,
1129 next_editor_action_id: EditorActionId,
1130 editor_actions: Rc<
1131 RefCell<BTreeMap<EditorActionId, Box<dyn Fn(&Editor, &mut Window, &mut Context<Self>)>>>,
1132 >,
1133 use_autoclose: bool,
1134 use_auto_surround: bool,
1135 auto_replace_emoji_shortcode: bool,
1136 jsx_tag_auto_close_enabled_in_any_buffer: bool,
1137 show_git_blame_gutter: bool,
1138 show_git_blame_inline: bool,
1139 show_git_blame_inline_delay_task: Option<Task<()>>,
1140 git_blame_inline_enabled: bool,
1141 render_diff_hunk_controls: RenderDiffHunkControlsFn,
1142 buffer_serialization: Option<BufferSerialization>,
1143 show_selection_menu: Option<bool>,
1144 blame: Option<Entity<GitBlame>>,
1145 blame_subscription: Option<Subscription>,
1146 custom_context_menu: Option<
1147 Box<
1148 dyn 'static
1149 + Fn(
1150 &mut Self,
1151 DisplayPoint,
1152 &mut Window,
1153 &mut Context<Self>,
1154 ) -> Option<Entity<ui::ContextMenu>>,
1155 >,
1156 >,
1157 last_bounds: Option<Bounds<Pixels>>,
1158 last_position_map: Option<Rc<PositionMap>>,
1159 expect_bounds_change: Option<Bounds<Pixels>>,
1160 tasks: BTreeMap<(BufferId, BufferRow), RunnableTasks>,
1161 tasks_update_task: Option<Task<()>>,
1162 breakpoint_store: Option<Entity<BreakpointStore>>,
1163 gutter_breakpoint_indicator: (Option<PhantomBreakpointIndicator>, Option<Task<()>>),
1164 hovered_diff_hunk_row: Option<DisplayRow>,
1165 pull_diagnostics_task: Task<()>,
1166 in_project_search: bool,
1167 previous_search_ranges: Option<Arc<[Range<Anchor>]>>,
1168 breadcrumb_header: Option<String>,
1169 focused_block: Option<FocusedBlock>,
1170 next_scroll_position: NextScrollCursorCenterTopBottom,
1171 addons: HashMap<TypeId, Box<dyn Addon>>,
1172 registered_buffers: HashMap<BufferId, OpenLspBufferHandle>,
1173 load_diff_task: Option<Shared<Task<()>>>,
1174 /// Whether we are temporarily displaying a diff other than git's
1175 temporary_diff_override: bool,
1176 selection_mark_mode: bool,
1177 toggle_fold_multiple_buffers: Task<()>,
1178 _scroll_cursor_center_top_bottom_task: Task<()>,
1179 serialize_selections: Task<()>,
1180 serialize_folds: Task<()>,
1181 mouse_cursor_hidden: bool,
1182 minimap: Option<Entity<Self>>,
1183 hide_mouse_mode: HideMouseMode,
1184 pub change_list: ChangeList,
1185 inline_value_cache: InlineValueCache,
1186
1187 selection_drag_state: SelectionDragState,
1188 colors: Option<LspColorData>,
1189 post_scroll_update: Task<()>,
1190 refresh_colors_task: Task<()>,
1191 inlay_hints: Option<LspInlayHintData>,
1192 folding_newlines: Task<()>,
1193 pub lookup_key: Option<Box<dyn Any + Send + Sync>>,
1194}
1195
1196fn debounce_value(debounce_ms: u64) -> Option<Duration> {
1197 if debounce_ms > 0 {
1198 Some(Duration::from_millis(debounce_ms))
1199 } else {
1200 None
1201 }
1202}
1203
1204#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
1205enum NextScrollCursorCenterTopBottom {
1206 #[default]
1207 Center,
1208 Top,
1209 Bottom,
1210}
1211
1212impl NextScrollCursorCenterTopBottom {
1213 fn next(&self) -> Self {
1214 match self {
1215 Self::Center => Self::Top,
1216 Self::Top => Self::Bottom,
1217 Self::Bottom => Self::Center,
1218 }
1219 }
1220}
1221
1222#[derive(Clone)]
1223pub struct EditorSnapshot {
1224 pub mode: EditorMode,
1225 show_gutter: bool,
1226 show_line_numbers: Option<bool>,
1227 show_git_diff_gutter: Option<bool>,
1228 show_code_actions: Option<bool>,
1229 show_runnables: Option<bool>,
1230 show_breakpoints: Option<bool>,
1231 git_blame_gutter_max_author_length: Option<usize>,
1232 pub display_snapshot: DisplaySnapshot,
1233 pub placeholder_display_snapshot: Option<DisplaySnapshot>,
1234 is_focused: bool,
1235 scroll_anchor: ScrollAnchor,
1236 ongoing_scroll: OngoingScroll,
1237 current_line_highlight: CurrentLineHighlight,
1238 gutter_hovered: bool,
1239}
1240
1241#[derive(Default, Debug, Clone, Copy)]
1242pub struct GutterDimensions {
1243 pub left_padding: Pixels,
1244 pub right_padding: Pixels,
1245 pub width: Pixels,
1246 pub margin: Pixels,
1247 pub git_blame_entries_width: Option<Pixels>,
1248}
1249
1250impl GutterDimensions {
1251 fn default_with_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Self {
1252 Self {
1253 margin: Self::default_gutter_margin(font_id, font_size, cx),
1254 ..Default::default()
1255 }
1256 }
1257
1258 fn default_gutter_margin(font_id: FontId, font_size: Pixels, cx: &App) -> Pixels {
1259 -cx.text_system().descent(font_id, font_size)
1260 }
1261 /// The full width of the space taken up by the gutter.
1262 pub fn full_width(&self) -> Pixels {
1263 self.margin + self.width
1264 }
1265
1266 /// The width of the space reserved for the fold indicators,
1267 /// use alongside 'justify_end' and `gutter_width` to
1268 /// right align content with the line numbers
1269 pub fn fold_area_width(&self) -> Pixels {
1270 self.margin + self.right_padding
1271 }
1272}
1273
1274struct CharacterDimensions {
1275 em_width: Pixels,
1276 em_advance: Pixels,
1277 line_height: Pixels,
1278}
1279
1280#[derive(Debug)]
1281pub struct RemoteSelection {
1282 pub replica_id: ReplicaId,
1283 pub selection: Selection<Anchor>,
1284 pub cursor_shape: CursorShape,
1285 pub collaborator_id: CollaboratorId,
1286 pub line_mode: bool,
1287 pub user_name: Option<SharedString>,
1288 pub color: PlayerColor,
1289}
1290
1291#[derive(Clone, Debug)]
1292struct SelectionHistoryEntry {
1293 selections: Arc<[Selection<Anchor>]>,
1294 select_next_state: Option<SelectNextState>,
1295 select_prev_state: Option<SelectNextState>,
1296 add_selections_state: Option<AddSelectionsState>,
1297}
1298
1299#[derive(Copy, Clone, Debug, PartialEq, Eq)]
1300enum SelectionHistoryMode {
1301 Normal,
1302 Undoing,
1303 Redoing,
1304 Skipping,
1305}
1306
1307#[derive(Clone, PartialEq, Eq, Hash)]
1308struct HoveredCursor {
1309 replica_id: ReplicaId,
1310 selection_id: usize,
1311}
1312
1313impl Default for SelectionHistoryMode {
1314 fn default() -> Self {
1315 Self::Normal
1316 }
1317}
1318
1319#[derive(Debug)]
1320/// SelectionEffects controls the side-effects of updating the selection.
1321///
1322/// The default behaviour does "what you mostly want":
1323/// - it pushes to the nav history if the cursor moved by >10 lines
1324/// - it re-triggers completion requests
1325/// - it scrolls to fit
1326///
1327/// You might want to modify these behaviours. For example when doing a "jump"
1328/// like go to definition, we always want to add to nav history; but when scrolling
1329/// in vim mode we never do.
1330///
1331/// Similarly, you might want to disable scrolling if you don't want the viewport to
1332/// move.
1333#[derive(Clone)]
1334pub struct SelectionEffects {
1335 nav_history: Option<bool>,
1336 completions: bool,
1337 scroll: Option<Autoscroll>,
1338}
1339
1340impl Default for SelectionEffects {
1341 fn default() -> Self {
1342 Self {
1343 nav_history: None,
1344 completions: true,
1345 scroll: Some(Autoscroll::fit()),
1346 }
1347 }
1348}
1349impl SelectionEffects {
1350 pub fn scroll(scroll: Autoscroll) -> Self {
1351 Self {
1352 scroll: Some(scroll),
1353 ..Default::default()
1354 }
1355 }
1356
1357 pub fn no_scroll() -> Self {
1358 Self {
1359 scroll: None,
1360 ..Default::default()
1361 }
1362 }
1363
1364 pub fn completions(self, completions: bool) -> Self {
1365 Self {
1366 completions,
1367 ..self
1368 }
1369 }
1370
1371 pub fn nav_history(self, nav_history: bool) -> Self {
1372 Self {
1373 nav_history: Some(nav_history),
1374 ..self
1375 }
1376 }
1377}
1378
1379struct DeferredSelectionEffectsState {
1380 changed: bool,
1381 effects: SelectionEffects,
1382 old_cursor_position: Anchor,
1383 history_entry: SelectionHistoryEntry,
1384}
1385
1386#[derive(Default)]
1387struct SelectionHistory {
1388 #[allow(clippy::type_complexity)]
1389 selections_by_transaction:
1390 HashMap<TransactionId, (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)>,
1391 mode: SelectionHistoryMode,
1392 undo_stack: VecDeque<SelectionHistoryEntry>,
1393 redo_stack: VecDeque<SelectionHistoryEntry>,
1394}
1395
1396impl SelectionHistory {
1397 #[track_caller]
1398 fn insert_transaction(
1399 &mut self,
1400 transaction_id: TransactionId,
1401 selections: Arc<[Selection<Anchor>]>,
1402 ) {
1403 if selections.is_empty() {
1404 log::error!(
1405 "SelectionHistory::insert_transaction called with empty selections. Caller: {}",
1406 std::panic::Location::caller()
1407 );
1408 return;
1409 }
1410 self.selections_by_transaction
1411 .insert(transaction_id, (selections, None));
1412 }
1413
1414 #[allow(clippy::type_complexity)]
1415 fn transaction(
1416 &self,
1417 transaction_id: TransactionId,
1418 ) -> Option<&(Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1419 self.selections_by_transaction.get(&transaction_id)
1420 }
1421
1422 #[allow(clippy::type_complexity)]
1423 fn transaction_mut(
1424 &mut self,
1425 transaction_id: TransactionId,
1426 ) -> Option<&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)> {
1427 self.selections_by_transaction.get_mut(&transaction_id)
1428 }
1429
1430 fn push(&mut self, entry: SelectionHistoryEntry) {
1431 if !entry.selections.is_empty() {
1432 match self.mode {
1433 SelectionHistoryMode::Normal => {
1434 self.push_undo(entry);
1435 self.redo_stack.clear();
1436 }
1437 SelectionHistoryMode::Undoing => self.push_redo(entry),
1438 SelectionHistoryMode::Redoing => self.push_undo(entry),
1439 SelectionHistoryMode::Skipping => {}
1440 }
1441 }
1442 }
1443
1444 fn push_undo(&mut self, entry: SelectionHistoryEntry) {
1445 if self
1446 .undo_stack
1447 .back()
1448 .is_none_or(|e| e.selections != entry.selections)
1449 {
1450 self.undo_stack.push_back(entry);
1451 if self.undo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1452 self.undo_stack.pop_front();
1453 }
1454 }
1455 }
1456
1457 fn push_redo(&mut self, entry: SelectionHistoryEntry) {
1458 if self
1459 .redo_stack
1460 .back()
1461 .is_none_or(|e| e.selections != entry.selections)
1462 {
1463 self.redo_stack.push_back(entry);
1464 if self.redo_stack.len() > MAX_SELECTION_HISTORY_LEN {
1465 self.redo_stack.pop_front();
1466 }
1467 }
1468 }
1469}
1470
1471#[derive(Clone, Copy)]
1472pub struct RowHighlightOptions {
1473 pub autoscroll: bool,
1474 pub include_gutter: bool,
1475}
1476
1477impl Default for RowHighlightOptions {
1478 fn default() -> Self {
1479 Self {
1480 autoscroll: Default::default(),
1481 include_gutter: true,
1482 }
1483 }
1484}
1485
1486struct RowHighlight {
1487 index: usize,
1488 range: Range<Anchor>,
1489 color: Hsla,
1490 options: RowHighlightOptions,
1491 type_id: TypeId,
1492}
1493
1494#[derive(Clone, Debug)]
1495struct AddSelectionsState {
1496 groups: Vec<AddSelectionsGroup>,
1497}
1498
1499#[derive(Clone, Debug)]
1500struct AddSelectionsGroup {
1501 above: bool,
1502 stack: Vec<usize>,
1503}
1504
1505#[derive(Clone)]
1506struct SelectNextState {
1507 query: AhoCorasick,
1508 wordwise: bool,
1509 done: bool,
1510}
1511
1512impl std::fmt::Debug for SelectNextState {
1513 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1514 f.debug_struct(std::any::type_name::<Self>())
1515 .field("wordwise", &self.wordwise)
1516 .field("done", &self.done)
1517 .finish()
1518 }
1519}
1520
1521#[derive(Debug)]
1522struct AutocloseRegion {
1523 selection_id: usize,
1524 range: Range<Anchor>,
1525 pair: BracketPair,
1526}
1527
1528#[derive(Debug)]
1529struct SnippetState {
1530 ranges: Vec<Vec<Range<Anchor>>>,
1531 active_index: usize,
1532 choices: Vec<Option<Vec<String>>>,
1533}
1534
1535#[doc(hidden)]
1536pub struct RenameState {
1537 pub range: Range<Anchor>,
1538 pub old_name: Arc<str>,
1539 pub editor: Entity<Editor>,
1540 block_id: CustomBlockId,
1541}
1542
1543struct InvalidationStack<T>(Vec<T>);
1544
1545struct RegisteredEditPredictionProvider {
1546 provider: Arc<dyn EditPredictionProviderHandle>,
1547 _subscription: Subscription,
1548}
1549
1550#[derive(Debug, PartialEq, Eq)]
1551pub struct ActiveDiagnosticGroup {
1552 pub active_range: Range<Anchor>,
1553 pub active_message: String,
1554 pub group_id: usize,
1555 pub blocks: HashSet<CustomBlockId>,
1556}
1557
1558#[derive(Debug, PartialEq, Eq)]
1559
1560pub(crate) enum ActiveDiagnostic {
1561 None,
1562 All,
1563 Group(ActiveDiagnosticGroup),
1564}
1565
1566#[derive(Serialize, Deserialize, Clone, Debug)]
1567pub struct ClipboardSelection {
1568 /// The number of bytes in this selection.
1569 pub len: usize,
1570 /// Whether this was a full-line selection.
1571 pub is_entire_line: bool,
1572 /// The indentation of the first line when this content was originally copied.
1573 pub first_line_indent: u32,
1574}
1575
1576// selections, scroll behavior, was newest selection reversed
1577type SelectSyntaxNodeHistoryState = (
1578 Box<[Selection<usize>]>,
1579 SelectSyntaxNodeScrollBehavior,
1580 bool,
1581);
1582
1583#[derive(Default)]
1584struct SelectSyntaxNodeHistory {
1585 stack: Vec<SelectSyntaxNodeHistoryState>,
1586 // disable temporarily to allow changing selections without losing the stack
1587 pub disable_clearing: bool,
1588}
1589
1590impl SelectSyntaxNodeHistory {
1591 pub fn try_clear(&mut self) {
1592 if !self.disable_clearing {
1593 self.stack.clear();
1594 }
1595 }
1596
1597 pub fn push(&mut self, selection: SelectSyntaxNodeHistoryState) {
1598 self.stack.push(selection);
1599 }
1600
1601 pub fn pop(&mut self) -> Option<SelectSyntaxNodeHistoryState> {
1602 self.stack.pop()
1603 }
1604}
1605
1606enum SelectSyntaxNodeScrollBehavior {
1607 CursorTop,
1608 FitSelection,
1609 CursorBottom,
1610}
1611
1612#[derive(Debug)]
1613pub(crate) struct NavigationData {
1614 cursor_anchor: Anchor,
1615 cursor_position: Point,
1616 scroll_anchor: ScrollAnchor,
1617 scroll_top_row: u32,
1618}
1619
1620#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1621pub enum GotoDefinitionKind {
1622 Symbol,
1623 Declaration,
1624 Type,
1625 Implementation,
1626}
1627
1628pub enum FormatTarget {
1629 Buffers(HashSet<Entity<Buffer>>),
1630 Ranges(Vec<Range<MultiBufferPoint>>),
1631}
1632
1633pub(crate) struct FocusedBlock {
1634 id: BlockId,
1635 focus_handle: WeakFocusHandle,
1636}
1637
1638#[derive(Clone)]
1639enum JumpData {
1640 MultiBufferRow {
1641 row: MultiBufferRow,
1642 line_offset_from_top: u32,
1643 },
1644 MultiBufferPoint {
1645 excerpt_id: ExcerptId,
1646 position: Point,
1647 anchor: text::Anchor,
1648 line_offset_from_top: u32,
1649 },
1650}
1651
1652pub enum MultibufferSelectionMode {
1653 First,
1654 All,
1655}
1656
1657#[derive(Clone, Copy, Debug, Default)]
1658pub struct RewrapOptions {
1659 pub override_language_settings: bool,
1660 pub preserve_existing_whitespace: bool,
1661}
1662
1663impl Editor {
1664 pub fn single_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1665 let buffer = cx.new(|cx| Buffer::local("", cx));
1666 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1667 Self::new(EditorMode::SingleLine, buffer, None, window, cx)
1668 }
1669
1670 pub fn multi_line(window: &mut Window, cx: &mut Context<Self>) -> Self {
1671 let buffer = cx.new(|cx| Buffer::local("", cx));
1672 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1673 Self::new(EditorMode::full(), buffer, None, window, cx)
1674 }
1675
1676 pub fn auto_height(
1677 min_lines: usize,
1678 max_lines: usize,
1679 window: &mut Window,
1680 cx: &mut Context<Self>,
1681 ) -> Self {
1682 let buffer = cx.new(|cx| Buffer::local("", cx));
1683 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1684 Self::new(
1685 EditorMode::AutoHeight {
1686 min_lines,
1687 max_lines: Some(max_lines),
1688 },
1689 buffer,
1690 None,
1691 window,
1692 cx,
1693 )
1694 }
1695
1696 /// Creates a new auto-height editor with a minimum number of lines but no maximum.
1697 /// The editor grows as tall as needed to fit its content.
1698 pub fn auto_height_unbounded(
1699 min_lines: usize,
1700 window: &mut Window,
1701 cx: &mut Context<Self>,
1702 ) -> Self {
1703 let buffer = cx.new(|cx| Buffer::local("", cx));
1704 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1705 Self::new(
1706 EditorMode::AutoHeight {
1707 min_lines,
1708 max_lines: None,
1709 },
1710 buffer,
1711 None,
1712 window,
1713 cx,
1714 )
1715 }
1716
1717 pub fn for_buffer(
1718 buffer: Entity<Buffer>,
1719 project: Option<Entity<Project>>,
1720 window: &mut Window,
1721 cx: &mut Context<Self>,
1722 ) -> Self {
1723 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
1724 Self::new(EditorMode::full(), buffer, project, window, cx)
1725 }
1726
1727 pub fn for_multibuffer(
1728 buffer: Entity<MultiBuffer>,
1729 project: Option<Entity<Project>>,
1730 window: &mut Window,
1731 cx: &mut Context<Self>,
1732 ) -> Self {
1733 Self::new(EditorMode::full(), buffer, project, window, cx)
1734 }
1735
1736 pub fn clone(&self, window: &mut Window, cx: &mut Context<Self>) -> Self {
1737 let mut clone = Self::new(
1738 self.mode.clone(),
1739 self.buffer.clone(),
1740 self.project.clone(),
1741 window,
1742 cx,
1743 );
1744 self.display_map.update(cx, |display_map, cx| {
1745 let snapshot = display_map.snapshot(cx);
1746 clone.display_map.update(cx, |display_map, cx| {
1747 display_map.set_state(&snapshot, cx);
1748 });
1749 });
1750 clone.folds_did_change(cx);
1751 clone.selections.clone_state(&self.selections);
1752 clone.scroll_manager.clone_state(&self.scroll_manager);
1753 clone.searchable = self.searchable;
1754 clone.read_only = self.read_only;
1755 clone
1756 }
1757
1758 pub fn new(
1759 mode: EditorMode,
1760 buffer: Entity<MultiBuffer>,
1761 project: Option<Entity<Project>>,
1762 window: &mut Window,
1763 cx: &mut Context<Self>,
1764 ) -> Self {
1765 Editor::new_internal(mode, buffer, project, None, window, cx)
1766 }
1767
1768 pub fn sticky_headers(&self, cx: &App) -> Option<Vec<OutlineItem<Anchor>>> {
1769 let multi_buffer = self.buffer().read(cx);
1770 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
1771 let multi_buffer_visible_start = self
1772 .scroll_manager
1773 .anchor()
1774 .anchor
1775 .to_point(&multi_buffer_snapshot);
1776 let max_row = multi_buffer_snapshot.max_point().row;
1777
1778 let start_row = (multi_buffer_visible_start.row).min(max_row);
1779 let end_row = (multi_buffer_visible_start.row + 10).min(max_row);
1780
1781 if let Some((excerpt_id, buffer_id, buffer)) = multi_buffer.read(cx).as_singleton() {
1782 let outline_items = buffer
1783 .outline_items_containing(
1784 Point::new(start_row, 0)..Point::new(end_row, 0),
1785 true,
1786 self.style().map(|style| style.syntax.as_ref()),
1787 )
1788 .into_iter()
1789 .map(|outline_item| OutlineItem {
1790 depth: outline_item.depth,
1791 range: Anchor::range_in_buffer(*excerpt_id, buffer_id, outline_item.range),
1792 source_range_for_text: Anchor::range_in_buffer(
1793 *excerpt_id,
1794 buffer_id,
1795 outline_item.source_range_for_text,
1796 ),
1797 text: outline_item.text,
1798 highlight_ranges: outline_item.highlight_ranges,
1799 name_ranges: outline_item.name_ranges,
1800 body_range: outline_item
1801 .body_range
1802 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1803 annotation_range: outline_item
1804 .annotation_range
1805 .map(|range| Anchor::range_in_buffer(*excerpt_id, buffer_id, range)),
1806 });
1807 return Some(outline_items.collect());
1808 }
1809
1810 None
1811 }
1812
1813 fn new_internal(
1814 mode: EditorMode,
1815 multi_buffer: Entity<MultiBuffer>,
1816 project: Option<Entity<Project>>,
1817 display_map: Option<Entity<DisplayMap>>,
1818 window: &mut Window,
1819 cx: &mut Context<Self>,
1820 ) -> Self {
1821 debug_assert!(
1822 display_map.is_none() || mode.is_minimap(),
1823 "Providing a display map for a new editor is only intended for the minimap and might have unintended side effects otherwise!"
1824 );
1825
1826 let full_mode = mode.is_full();
1827 let is_minimap = mode.is_minimap();
1828 let diagnostics_max_severity = if full_mode {
1829 EditorSettings::get_global(cx)
1830 .diagnostics_max_severity
1831 .unwrap_or(DiagnosticSeverity::Hint)
1832 } else {
1833 DiagnosticSeverity::Off
1834 };
1835 let style = window.text_style();
1836 let font_size = style.font_size.to_pixels(window.rem_size());
1837 let editor = cx.entity().downgrade();
1838 let fold_placeholder = FoldPlaceholder {
1839 constrain_width: false,
1840 render: Arc::new(move |fold_id, fold_range, cx| {
1841 let editor = editor.clone();
1842 div()
1843 .id(fold_id)
1844 .bg(cx.theme().colors().ghost_element_background)
1845 .hover(|style| style.bg(cx.theme().colors().ghost_element_hover))
1846 .active(|style| style.bg(cx.theme().colors().ghost_element_active))
1847 .rounded_xs()
1848 .size_full()
1849 .cursor_pointer()
1850 .child("⋯")
1851 .on_mouse_down(MouseButton::Left, |_, _, cx| cx.stop_propagation())
1852 .on_click(move |_, _window, cx| {
1853 editor
1854 .update(cx, |editor, cx| {
1855 editor.unfold_ranges(
1856 &[fold_range.start..fold_range.end],
1857 true,
1858 false,
1859 cx,
1860 );
1861 cx.stop_propagation();
1862 })
1863 .ok();
1864 })
1865 .into_any()
1866 }),
1867 merge_adjacent: true,
1868 ..FoldPlaceholder::default()
1869 };
1870 let display_map = display_map.unwrap_or_else(|| {
1871 cx.new(|cx| {
1872 DisplayMap::new(
1873 multi_buffer.clone(),
1874 style.font(),
1875 font_size,
1876 None,
1877 FILE_HEADER_HEIGHT,
1878 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
1879 fold_placeholder,
1880 diagnostics_max_severity,
1881 cx,
1882 )
1883 })
1884 });
1885
1886 let selections = SelectionsCollection::new();
1887
1888 let blink_manager = cx.new(|cx| {
1889 let mut blink_manager = BlinkManager::new(CURSOR_BLINK_INTERVAL, cx);
1890 if is_minimap {
1891 blink_manager.disable(cx);
1892 }
1893 blink_manager
1894 });
1895
1896 let soft_wrap_mode_override =
1897 matches!(mode, EditorMode::SingleLine).then(|| language_settings::SoftWrap::None);
1898
1899 let mut project_subscriptions = Vec::new();
1900 if full_mode && let Some(project) = project.as_ref() {
1901 project_subscriptions.push(cx.subscribe_in(
1902 project,
1903 window,
1904 |editor, _, event, window, cx| match event {
1905 project::Event::RefreshCodeLens => {
1906 // we always query lens with actions, without storing them, always refreshing them
1907 }
1908 project::Event::RefreshInlayHints {
1909 server_id,
1910 request_id,
1911 } => {
1912 editor.refresh_inlay_hints(
1913 InlayHintRefreshReason::RefreshRequested {
1914 server_id: *server_id,
1915 request_id: *request_id,
1916 },
1917 cx,
1918 );
1919 }
1920 project::Event::LanguageServerRemoved(..) => {
1921 if editor.tasks_update_task.is_none() {
1922 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1923 }
1924 editor.registered_buffers.clear();
1925 editor.register_visible_buffers(cx);
1926 }
1927 project::Event::LanguageServerAdded(..) => {
1928 if editor.tasks_update_task.is_none() {
1929 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
1930 }
1931 }
1932 project::Event::SnippetEdit(id, snippet_edits) => {
1933 if let Some(buffer) = editor.buffer.read(cx).buffer(*id) {
1934 let focus_handle = editor.focus_handle(cx);
1935 if focus_handle.is_focused(window) {
1936 let snapshot = buffer.read(cx).snapshot();
1937 for (range, snippet) in snippet_edits {
1938 let editor_range =
1939 language::range_from_lsp(*range).to_offset(&snapshot);
1940 editor
1941 .insert_snippet(
1942 &[editor_range],
1943 snippet.clone(),
1944 window,
1945 cx,
1946 )
1947 .ok();
1948 }
1949 }
1950 }
1951 }
1952 project::Event::LanguageServerBufferRegistered { buffer_id, .. } => {
1953 let buffer_id = *buffer_id;
1954 if editor.buffer().read(cx).buffer(buffer_id).is_some() {
1955 editor.register_buffer(buffer_id, cx);
1956 editor.update_lsp_data(Some(buffer_id), window, cx);
1957 editor.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
1958 refresh_linked_ranges(editor, window, cx);
1959 editor.refresh_code_actions(window, cx);
1960 editor.refresh_document_highlights(cx);
1961 }
1962 }
1963
1964 project::Event::EntryRenamed(transaction, project_path, abs_path) => {
1965 let Some(workspace) = editor.workspace() else {
1966 return;
1967 };
1968 let Some(active_editor) = workspace.read(cx).active_item_as::<Self>(cx)
1969 else {
1970 return;
1971 };
1972
1973 if active_editor.entity_id() == cx.entity_id() {
1974 let entity_id = cx.entity_id();
1975 workspace.update(cx, |this, cx| {
1976 this.panes_mut()
1977 .iter_mut()
1978 .filter(|pane| pane.entity_id() != entity_id)
1979 .for_each(|p| {
1980 p.update(cx, |pane, _| {
1981 pane.nav_history_mut().rename_item(
1982 entity_id,
1983 project_path.clone(),
1984 abs_path.clone().into(),
1985 );
1986 })
1987 });
1988 });
1989 let edited_buffers_already_open = {
1990 let other_editors: Vec<Entity<Editor>> = workspace
1991 .read(cx)
1992 .panes()
1993 .iter()
1994 .flat_map(|pane| pane.read(cx).items_of_type::<Editor>())
1995 .filter(|editor| editor.entity_id() != cx.entity_id())
1996 .collect();
1997
1998 transaction.0.keys().all(|buffer| {
1999 other_editors.iter().any(|editor| {
2000 let multi_buffer = editor.read(cx).buffer();
2001 multi_buffer.read(cx).is_singleton()
2002 && multi_buffer.read(cx).as_singleton().map_or(
2003 false,
2004 |singleton| {
2005 singleton.entity_id() == buffer.entity_id()
2006 },
2007 )
2008 })
2009 })
2010 };
2011 if !edited_buffers_already_open {
2012 let workspace = workspace.downgrade();
2013 let transaction = transaction.clone();
2014 cx.defer_in(window, move |_, window, cx| {
2015 cx.spawn_in(window, async move |editor, cx| {
2016 Self::open_project_transaction(
2017 &editor,
2018 workspace,
2019 transaction,
2020 "Rename".to_string(),
2021 cx,
2022 )
2023 .await
2024 .ok()
2025 })
2026 .detach();
2027 });
2028 }
2029 }
2030 }
2031
2032 _ => {}
2033 },
2034 ));
2035 if let Some(task_inventory) = project
2036 .read(cx)
2037 .task_store()
2038 .read(cx)
2039 .task_inventory()
2040 .cloned()
2041 {
2042 project_subscriptions.push(cx.observe_in(
2043 &task_inventory,
2044 window,
2045 |editor, _, window, cx| {
2046 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2047 },
2048 ));
2049 };
2050
2051 project_subscriptions.push(cx.subscribe_in(
2052 &project.read(cx).breakpoint_store(),
2053 window,
2054 |editor, _, event, window, cx| match event {
2055 BreakpointStoreEvent::ClearDebugLines => {
2056 editor.clear_row_highlights::<ActiveDebugLine>();
2057 editor.refresh_inline_values(cx);
2058 }
2059 BreakpointStoreEvent::SetDebugLine => {
2060 if editor.go_to_active_debug_line(window, cx) {
2061 cx.stop_propagation();
2062 }
2063
2064 editor.refresh_inline_values(cx);
2065 }
2066 _ => {}
2067 },
2068 ));
2069 let git_store = project.read(cx).git_store().clone();
2070 let project = project.clone();
2071 project_subscriptions.push(cx.subscribe(&git_store, move |this, _, event, cx| {
2072 if let GitStoreEvent::RepositoryAdded = event {
2073 this.load_diff_task = Some(
2074 update_uncommitted_diff_for_buffer(
2075 cx.entity(),
2076 &project,
2077 this.buffer.read(cx).all_buffers(),
2078 this.buffer.clone(),
2079 cx,
2080 )
2081 .shared(),
2082 );
2083 }
2084 }));
2085 }
2086
2087 let buffer_snapshot = multi_buffer.read(cx).snapshot(cx);
2088
2089 let inlay_hint_settings =
2090 inlay_hint_settings(selections.newest_anchor().head(), &buffer_snapshot, cx);
2091 let focus_handle = cx.focus_handle();
2092 if !is_minimap {
2093 cx.on_focus(&focus_handle, window, Self::handle_focus)
2094 .detach();
2095 cx.on_focus_in(&focus_handle, window, Self::handle_focus_in)
2096 .detach();
2097 cx.on_focus_out(&focus_handle, window, Self::handle_focus_out)
2098 .detach();
2099 cx.on_blur(&focus_handle, window, Self::handle_blur)
2100 .detach();
2101 cx.observe_pending_input(window, Self::observe_pending_input)
2102 .detach();
2103 }
2104
2105 let show_indent_guides =
2106 if matches!(mode, EditorMode::SingleLine | EditorMode::Minimap { .. }) {
2107 Some(false)
2108 } else {
2109 None
2110 };
2111
2112 let breakpoint_store = match (&mode, project.as_ref()) {
2113 (EditorMode::Full { .. }, Some(project)) => Some(project.read(cx).breakpoint_store()),
2114 _ => None,
2115 };
2116
2117 let mut code_action_providers = Vec::new();
2118 let mut load_uncommitted_diff = None;
2119 if let Some(project) = project.clone() {
2120 load_uncommitted_diff = Some(
2121 update_uncommitted_diff_for_buffer(
2122 cx.entity(),
2123 &project,
2124 multi_buffer.read(cx).all_buffers(),
2125 multi_buffer.clone(),
2126 cx,
2127 )
2128 .shared(),
2129 );
2130 code_action_providers.push(Rc::new(project) as Rc<_>);
2131 }
2132
2133 let mut editor = Self {
2134 focus_handle,
2135 show_cursor_when_unfocused: false,
2136 last_focused_descendant: None,
2137 buffer: multi_buffer.clone(),
2138 display_map: display_map.clone(),
2139 placeholder_display_map: None,
2140 selections,
2141 scroll_manager: ScrollManager::new(cx),
2142 columnar_selection_state: None,
2143 add_selections_state: None,
2144 select_next_state: None,
2145 select_prev_state: None,
2146 selection_history: SelectionHistory::default(),
2147 defer_selection_effects: false,
2148 deferred_selection_effects_state: None,
2149 autoclose_regions: Vec::new(),
2150 snippet_stack: InvalidationStack::default(),
2151 select_syntax_node_history: SelectSyntaxNodeHistory::default(),
2152 ime_transaction: None,
2153 active_diagnostics: ActiveDiagnostic::None,
2154 show_inline_diagnostics: ProjectSettings::get_global(cx).diagnostics.inline.enabled,
2155 inline_diagnostics_update: Task::ready(()),
2156 inline_diagnostics: Vec::new(),
2157 soft_wrap_mode_override,
2158 diagnostics_max_severity,
2159 hard_wrap: None,
2160 completion_provider: project.clone().map(|project| Rc::new(project) as _),
2161 semantics_provider: project.clone().map(|project| Rc::new(project) as _),
2162 collaboration_hub: project.clone().map(|project| Box::new(project) as _),
2163 project,
2164 blink_manager: blink_manager.clone(),
2165 show_local_selections: true,
2166 show_scrollbars: ScrollbarAxes {
2167 horizontal: full_mode,
2168 vertical: full_mode,
2169 },
2170 minimap_visibility: MinimapVisibility::for_mode(&mode, cx),
2171 offset_content: !matches!(mode, EditorMode::SingleLine),
2172 show_breadcrumbs: EditorSettings::get_global(cx).toolbar.breadcrumbs,
2173 show_gutter: full_mode,
2174 show_line_numbers: (!full_mode).then_some(false),
2175 use_relative_line_numbers: None,
2176 disable_expand_excerpt_buttons: !full_mode,
2177 show_git_diff_gutter: None,
2178 show_code_actions: None,
2179 show_runnables: None,
2180 show_breakpoints: None,
2181 show_wrap_guides: None,
2182 show_indent_guides,
2183 highlight_order: 0,
2184 highlighted_rows: HashMap::default(),
2185 background_highlights: HashMap::default(),
2186 gutter_highlights: HashMap::default(),
2187 scrollbar_marker_state: ScrollbarMarkerState::default(),
2188 active_indent_guides_state: ActiveIndentGuidesState::default(),
2189 nav_history: None,
2190 context_menu: RefCell::new(None),
2191 context_menu_options: None,
2192 mouse_context_menu: None,
2193 completion_tasks: Vec::new(),
2194 inline_blame_popover: None,
2195 inline_blame_popover_show_task: None,
2196 signature_help_state: SignatureHelpState::default(),
2197 auto_signature_help: None,
2198 find_all_references_task_sources: Vec::new(),
2199 next_completion_id: 0,
2200 next_inlay_id: 0,
2201 code_action_providers,
2202 available_code_actions: None,
2203 code_actions_task: None,
2204 quick_selection_highlight_task: None,
2205 debounced_selection_highlight_task: None,
2206 document_highlights_task: None,
2207 linked_editing_range_task: None,
2208 pending_rename: None,
2209 searchable: !is_minimap,
2210 cursor_shape: EditorSettings::get_global(cx)
2211 .cursor_shape
2212 .unwrap_or_default(),
2213 current_line_highlight: None,
2214 autoindent_mode: Some(AutoindentMode::EachLine),
2215
2216 workspace: None,
2217 input_enabled: !is_minimap,
2218 use_modal_editing: full_mode,
2219 read_only: is_minimap,
2220 use_autoclose: true,
2221 use_auto_surround: true,
2222 auto_replace_emoji_shortcode: false,
2223 jsx_tag_auto_close_enabled_in_any_buffer: false,
2224 leader_id: None,
2225 remote_id: None,
2226 hover_state: HoverState::default(),
2227 pending_mouse_down: None,
2228 hovered_link_state: None,
2229 edit_prediction_provider: None,
2230 active_edit_prediction: None,
2231 stale_edit_prediction_in_menu: None,
2232 edit_prediction_preview: EditPredictionPreview::Inactive {
2233 released_too_fast: false,
2234 },
2235 inline_diagnostics_enabled: full_mode,
2236 diagnostics_enabled: full_mode,
2237 word_completions_enabled: full_mode,
2238 inline_value_cache: InlineValueCache::new(inlay_hint_settings.show_value_hints),
2239 gutter_hovered: false,
2240 pixel_position_of_newest_cursor: None,
2241 last_bounds: None,
2242 last_position_map: None,
2243 expect_bounds_change: None,
2244 gutter_dimensions: GutterDimensions::default(),
2245 style: None,
2246 show_cursor_names: false,
2247 hovered_cursors: HashMap::default(),
2248 next_editor_action_id: EditorActionId::default(),
2249 editor_actions: Rc::default(),
2250 edit_predictions_hidden_for_vim_mode: false,
2251 show_edit_predictions_override: None,
2252 menu_edit_predictions_policy: MenuEditPredictionsPolicy::ByProvider,
2253 edit_prediction_settings: EditPredictionSettings::Disabled,
2254 edit_prediction_indent_conflict: false,
2255 edit_prediction_requires_modifier_in_indent_conflict: true,
2256 custom_context_menu: None,
2257 show_git_blame_gutter: false,
2258 show_git_blame_inline: false,
2259 show_selection_menu: None,
2260 show_git_blame_inline_delay_task: None,
2261 git_blame_inline_enabled: full_mode
2262 && ProjectSettings::get_global(cx).git.inline_blame.enabled,
2263 render_diff_hunk_controls: Arc::new(render_diff_hunk_controls),
2264 buffer_serialization: is_minimap.not().then(|| {
2265 BufferSerialization::new(
2266 ProjectSettings::get_global(cx)
2267 .session
2268 .restore_unsaved_buffers,
2269 )
2270 }),
2271 blame: None,
2272 blame_subscription: None,
2273 tasks: BTreeMap::default(),
2274
2275 breakpoint_store,
2276 gutter_breakpoint_indicator: (None, None),
2277 hovered_diff_hunk_row: None,
2278 _subscriptions: (!is_minimap)
2279 .then(|| {
2280 vec![
2281 cx.observe(&multi_buffer, Self::on_buffer_changed),
2282 cx.subscribe_in(&multi_buffer, window, Self::on_buffer_event),
2283 cx.observe_in(&display_map, window, Self::on_display_map_changed),
2284 cx.observe(&blink_manager, |_, _, cx| cx.notify()),
2285 cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
2286 observe_buffer_font_size_adjustment(cx, |_, cx| cx.notify()),
2287 cx.observe_window_activation(window, |editor, window, cx| {
2288 let active = window.is_window_active();
2289 editor.blink_manager.update(cx, |blink_manager, cx| {
2290 if active {
2291 blink_manager.enable(cx);
2292 } else {
2293 blink_manager.disable(cx);
2294 }
2295 });
2296 if active {
2297 editor.show_mouse_cursor(cx);
2298 }
2299 }),
2300 ]
2301 })
2302 .unwrap_or_default(),
2303 tasks_update_task: None,
2304 pull_diagnostics_task: Task::ready(()),
2305 colors: None,
2306 refresh_colors_task: Task::ready(()),
2307 inlay_hints: None,
2308 next_color_inlay_id: 0,
2309 post_scroll_update: Task::ready(()),
2310 linked_edit_ranges: Default::default(),
2311 in_project_search: false,
2312 previous_search_ranges: None,
2313 breadcrumb_header: None,
2314 focused_block: None,
2315 next_scroll_position: NextScrollCursorCenterTopBottom::default(),
2316 addons: HashMap::default(),
2317 registered_buffers: HashMap::default(),
2318 _scroll_cursor_center_top_bottom_task: Task::ready(()),
2319 selection_mark_mode: false,
2320 toggle_fold_multiple_buffers: Task::ready(()),
2321 serialize_selections: Task::ready(()),
2322 serialize_folds: Task::ready(()),
2323 text_style_refinement: None,
2324 load_diff_task: load_uncommitted_diff,
2325 temporary_diff_override: false,
2326 mouse_cursor_hidden: false,
2327 minimap: None,
2328 hide_mouse_mode: EditorSettings::get_global(cx)
2329 .hide_mouse
2330 .unwrap_or_default(),
2331 change_list: ChangeList::new(),
2332 mode,
2333 selection_drag_state: SelectionDragState::None,
2334 folding_newlines: Task::ready(()),
2335 lookup_key: None,
2336 };
2337
2338 if is_minimap {
2339 return editor;
2340 }
2341
2342 if let Some(breakpoints) = editor.breakpoint_store.as_ref() {
2343 editor
2344 ._subscriptions
2345 .push(cx.observe(breakpoints, |_, _, cx| {
2346 cx.notify();
2347 }));
2348 }
2349 editor.tasks_update_task = Some(editor.refresh_runnables(window, cx));
2350 editor._subscriptions.extend(project_subscriptions);
2351
2352 editor._subscriptions.push(cx.subscribe_in(
2353 &cx.entity(),
2354 window,
2355 |editor, _, e: &EditorEvent, window, cx| match e {
2356 EditorEvent::ScrollPositionChanged { local, .. } => {
2357 if *local {
2358 editor.hide_signature_help(cx, SignatureHelpHiddenBy::Escape);
2359 editor.inline_blame_popover.take();
2360 let new_anchor = editor.scroll_manager.anchor();
2361 let snapshot = editor.snapshot(window, cx);
2362 editor.update_restoration_data(cx, move |data| {
2363 data.scroll_position = (
2364 new_anchor.top_row(snapshot.buffer_snapshot()),
2365 new_anchor.offset,
2366 );
2367 });
2368
2369 editor.post_scroll_update = cx.spawn_in(window, async move |editor, cx| {
2370 cx.background_executor()
2371 .timer(Duration::from_millis(50))
2372 .await;
2373 editor
2374 .update_in(cx, |editor, window, cx| {
2375 editor.register_visible_buffers(cx);
2376 editor.refresh_colors_for_visible_range(None, window, cx);
2377 editor.refresh_inlay_hints(
2378 InlayHintRefreshReason::NewLinesShown,
2379 cx,
2380 );
2381 })
2382 .ok();
2383 });
2384 }
2385 }
2386 EditorEvent::Edited { .. } => {
2387 if vim_flavor(cx).is_none() {
2388 let display_map = editor.display_snapshot(cx);
2389 let selections = editor.selections.all_adjusted_display(&display_map);
2390 let pop_state = editor
2391 .change_list
2392 .last()
2393 .map(|previous| {
2394 previous.len() == selections.len()
2395 && previous.iter().enumerate().all(|(ix, p)| {
2396 p.to_display_point(&display_map).row()
2397 == selections[ix].head().row()
2398 })
2399 })
2400 .unwrap_or(false);
2401 let new_positions = selections
2402 .into_iter()
2403 .map(|s| display_map.display_point_to_anchor(s.head(), Bias::Left))
2404 .collect();
2405 editor
2406 .change_list
2407 .push_to_change_list(pop_state, new_positions);
2408 }
2409 }
2410 _ => (),
2411 },
2412 ));
2413
2414 if let Some(dap_store) = editor
2415 .project
2416 .as_ref()
2417 .map(|project| project.read(cx).dap_store())
2418 {
2419 let weak_editor = cx.weak_entity();
2420
2421 editor
2422 ._subscriptions
2423 .push(
2424 cx.observe_new::<project::debugger::session::Session>(move |_, _, cx| {
2425 let session_entity = cx.entity();
2426 weak_editor
2427 .update(cx, |editor, cx| {
2428 editor._subscriptions.push(
2429 cx.subscribe(&session_entity, Self::on_debug_session_event),
2430 );
2431 })
2432 .ok();
2433 }),
2434 );
2435
2436 for session in dap_store.read(cx).sessions().cloned().collect::<Vec<_>>() {
2437 editor
2438 ._subscriptions
2439 .push(cx.subscribe(&session, Self::on_debug_session_event));
2440 }
2441 }
2442
2443 // skip adding the initial selection to selection history
2444 editor.selection_history.mode = SelectionHistoryMode::Skipping;
2445 editor.end_selection(window, cx);
2446 editor.selection_history.mode = SelectionHistoryMode::Normal;
2447
2448 editor.scroll_manager.show_scrollbars(window, cx);
2449 jsx_tag_auto_close::refresh_enabled_in_any_buffer(&mut editor, &multi_buffer, cx);
2450
2451 if full_mode {
2452 let should_auto_hide_scrollbars = cx.should_auto_hide_scrollbars();
2453 cx.set_global(ScrollbarAutoHide(should_auto_hide_scrollbars));
2454
2455 if editor.git_blame_inline_enabled {
2456 editor.start_git_blame_inline(false, window, cx);
2457 }
2458
2459 editor.go_to_active_debug_line(window, cx);
2460
2461 editor.minimap =
2462 editor.create_minimap(EditorSettings::get_global(cx).minimap, window, cx);
2463 editor.colors = Some(LspColorData::new(cx));
2464 editor.inlay_hints = Some(LspInlayHintData::new(inlay_hint_settings));
2465
2466 if let Some(buffer) = multi_buffer.read(cx).as_singleton() {
2467 editor.register_buffer(buffer.read(cx).remote_id(), cx);
2468 }
2469 editor.update_lsp_data(None, window, cx);
2470 editor.report_editor_event(ReportEditorEvent::EditorOpened, None, cx);
2471 }
2472
2473 editor
2474 }
2475
2476 pub fn display_snapshot(&self, cx: &mut App) -> DisplaySnapshot {
2477 self.display_map.update(cx, |map, cx| map.snapshot(cx))
2478 }
2479
2480 pub fn deploy_mouse_context_menu(
2481 &mut self,
2482 position: gpui::Point<Pixels>,
2483 context_menu: Entity<ContextMenu>,
2484 window: &mut Window,
2485 cx: &mut Context<Self>,
2486 ) {
2487 self.mouse_context_menu = Some(MouseContextMenu::new(
2488 self,
2489 crate::mouse_context_menu::MenuPosition::PinnedToScreen(position),
2490 context_menu,
2491 window,
2492 cx,
2493 ));
2494 }
2495
2496 pub fn mouse_menu_is_focused(&self, window: &Window, cx: &App) -> bool {
2497 self.mouse_context_menu
2498 .as_ref()
2499 .is_some_and(|menu| menu.context_menu.focus_handle(cx).is_focused(window))
2500 }
2501
2502 pub fn is_range_selected(&mut self, range: &Range<Anchor>, cx: &mut Context<Self>) -> bool {
2503 if self
2504 .selections
2505 .pending_anchor()
2506 .is_some_and(|pending_selection| {
2507 let snapshot = self.buffer().read(cx).snapshot(cx);
2508 pending_selection.range().includes(range, &snapshot)
2509 })
2510 {
2511 return true;
2512 }
2513
2514 self.selections
2515 .disjoint_in_range::<usize>(range.clone(), &self.display_snapshot(cx))
2516 .into_iter()
2517 .any(|selection| {
2518 // This is needed to cover a corner case, if we just check for an existing
2519 // selection in the fold range, having a cursor at the start of the fold
2520 // marks it as selected. Non-empty selections don't cause this.
2521 let length = selection.end - selection.start;
2522 length > 0
2523 })
2524 }
2525
2526 pub fn key_context(&self, window: &mut Window, cx: &mut App) -> KeyContext {
2527 self.key_context_internal(self.has_active_edit_prediction(), window, cx)
2528 }
2529
2530 fn key_context_internal(
2531 &self,
2532 has_active_edit_prediction: bool,
2533 window: &mut Window,
2534 cx: &mut App,
2535 ) -> KeyContext {
2536 let mut key_context = KeyContext::new_with_defaults();
2537 key_context.add("Editor");
2538 let mode = match self.mode {
2539 EditorMode::SingleLine => "single_line",
2540 EditorMode::AutoHeight { .. } => "auto_height",
2541 EditorMode::Minimap { .. } => "minimap",
2542 EditorMode::Full { .. } => "full",
2543 };
2544
2545 if EditorSettings::jupyter_enabled(cx) {
2546 key_context.add("jupyter");
2547 }
2548
2549 key_context.set("mode", mode);
2550 if self.pending_rename.is_some() {
2551 key_context.add("renaming");
2552 }
2553
2554 if let Some(snippet_stack) = self.snippet_stack.last() {
2555 key_context.add("in_snippet");
2556
2557 if snippet_stack.active_index > 0 {
2558 key_context.add("has_previous_tabstop");
2559 }
2560
2561 if snippet_stack.active_index < snippet_stack.ranges.len().saturating_sub(1) {
2562 key_context.add("has_next_tabstop");
2563 }
2564 }
2565
2566 match self.context_menu.borrow().as_ref() {
2567 Some(CodeContextMenu::Completions(menu)) => {
2568 if menu.visible() {
2569 key_context.add("menu");
2570 key_context.add("showing_completions");
2571 }
2572 }
2573 Some(CodeContextMenu::CodeActions(menu)) => {
2574 if menu.visible() {
2575 key_context.add("menu");
2576 key_context.add("showing_code_actions")
2577 }
2578 }
2579 None => {}
2580 }
2581
2582 if self.signature_help_state.has_multiple_signatures() {
2583 key_context.add("showing_signature_help");
2584 }
2585
2586 // Disable vim contexts when a sub-editor (e.g. rename/inline assistant) is focused.
2587 if !self.focus_handle(cx).contains_focused(window, cx)
2588 || (self.is_focused(window) || self.mouse_menu_is_focused(window, cx))
2589 {
2590 for addon in self.addons.values() {
2591 addon.extend_key_context(&mut key_context, cx)
2592 }
2593 }
2594
2595 if let Some(singleton_buffer) = self.buffer.read(cx).as_singleton() {
2596 if let Some(extension) = singleton_buffer.read(cx).file().and_then(|file| {
2597 Some(
2598 file.full_path(cx)
2599 .extension()?
2600 .to_string_lossy()
2601 .into_owned(),
2602 )
2603 }) {
2604 key_context.set("extension", extension);
2605 }
2606 } else {
2607 key_context.add("multibuffer");
2608 }
2609
2610 if has_active_edit_prediction {
2611 if self.edit_prediction_in_conflict() {
2612 key_context.add(EDIT_PREDICTION_CONFLICT_KEY_CONTEXT);
2613 } else {
2614 key_context.add(EDIT_PREDICTION_KEY_CONTEXT);
2615 key_context.add("copilot_suggestion");
2616 }
2617 }
2618
2619 if self.selection_mark_mode {
2620 key_context.add("selection_mode");
2621 }
2622
2623 let disjoint = self.selections.disjoint_anchors();
2624 let snapshot = self.snapshot(window, cx);
2625 let snapshot = snapshot.buffer_snapshot();
2626 if self.mode == EditorMode::SingleLine
2627 && let [selection] = disjoint
2628 && selection.start == selection.end
2629 && selection.end.to_offset(snapshot) == snapshot.len()
2630 {
2631 key_context.add("end_of_input");
2632 }
2633
2634 key_context
2635 }
2636
2637 pub fn last_bounds(&self) -> Option<&Bounds<Pixels>> {
2638 self.last_bounds.as_ref()
2639 }
2640
2641 fn show_mouse_cursor(&mut self, cx: &mut Context<Self>) {
2642 if self.mouse_cursor_hidden {
2643 self.mouse_cursor_hidden = false;
2644 cx.notify();
2645 }
2646 }
2647
2648 pub fn hide_mouse_cursor(&mut self, origin: HideMouseCursorOrigin, cx: &mut Context<Self>) {
2649 let hide_mouse_cursor = match origin {
2650 HideMouseCursorOrigin::TypingAction => {
2651 matches!(
2652 self.hide_mouse_mode,
2653 HideMouseMode::OnTyping | HideMouseMode::OnTypingAndMovement
2654 )
2655 }
2656 HideMouseCursorOrigin::MovementAction => {
2657 matches!(self.hide_mouse_mode, HideMouseMode::OnTypingAndMovement)
2658 }
2659 };
2660 if self.mouse_cursor_hidden != hide_mouse_cursor {
2661 self.mouse_cursor_hidden = hide_mouse_cursor;
2662 cx.notify();
2663 }
2664 }
2665
2666 pub fn edit_prediction_in_conflict(&self) -> bool {
2667 if !self.show_edit_predictions_in_menu() {
2668 return false;
2669 }
2670
2671 let showing_completions = self
2672 .context_menu
2673 .borrow()
2674 .as_ref()
2675 .is_some_and(|context| matches!(context, CodeContextMenu::Completions(_)));
2676
2677 showing_completions
2678 || self.edit_prediction_requires_modifier()
2679 // Require modifier key when the cursor is on leading whitespace, to allow `tab`
2680 // bindings to insert tab characters.
2681 || (self.edit_prediction_requires_modifier_in_indent_conflict && self.edit_prediction_indent_conflict)
2682 }
2683
2684 pub fn accept_edit_prediction_keybind(
2685 &self,
2686 accept_partial: bool,
2687 window: &mut Window,
2688 cx: &mut App,
2689 ) -> AcceptEditPredictionBinding {
2690 let key_context = self.key_context_internal(true, window, cx);
2691 let in_conflict = self.edit_prediction_in_conflict();
2692
2693 let bindings = if accept_partial {
2694 window.bindings_for_action_in_context(&AcceptPartialEditPrediction, key_context)
2695 } else {
2696 window.bindings_for_action_in_context(&AcceptEditPrediction, key_context)
2697 };
2698
2699 // TODO: if the binding contains multiple keystrokes, display all of them, not
2700 // just the first one.
2701 AcceptEditPredictionBinding(bindings.into_iter().rev().find(|binding| {
2702 !in_conflict
2703 || binding
2704 .keystrokes()
2705 .first()
2706 .is_some_and(|keystroke| keystroke.modifiers().modified())
2707 }))
2708 }
2709
2710 pub fn new_file(
2711 workspace: &mut Workspace,
2712 _: &workspace::NewFile,
2713 window: &mut Window,
2714 cx: &mut Context<Workspace>,
2715 ) {
2716 Self::new_in_workspace(workspace, window, cx).detach_and_prompt_err(
2717 "Failed to create buffer",
2718 window,
2719 cx,
2720 |e, _, _| match e.error_code() {
2721 ErrorCode::RemoteUpgradeRequired => Some(format!(
2722 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2723 e.error_tag("required").unwrap_or("the latest version")
2724 )),
2725 _ => None,
2726 },
2727 );
2728 }
2729
2730 pub fn new_in_workspace(
2731 workspace: &mut Workspace,
2732 window: &mut Window,
2733 cx: &mut Context<Workspace>,
2734 ) -> Task<Result<Entity<Editor>>> {
2735 let project = workspace.project().clone();
2736 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2737
2738 cx.spawn_in(window, async move |workspace, cx| {
2739 let buffer = create.await?;
2740 workspace.update_in(cx, |workspace, window, cx| {
2741 let editor =
2742 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx));
2743 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
2744 editor
2745 })
2746 })
2747 }
2748
2749 fn new_file_vertical(
2750 workspace: &mut Workspace,
2751 _: &workspace::NewFileSplitVertical,
2752 window: &mut Window,
2753 cx: &mut Context<Workspace>,
2754 ) {
2755 Self::new_file_in_direction(workspace, SplitDirection::vertical(cx), window, cx)
2756 }
2757
2758 fn new_file_horizontal(
2759 workspace: &mut Workspace,
2760 _: &workspace::NewFileSplitHorizontal,
2761 window: &mut Window,
2762 cx: &mut Context<Workspace>,
2763 ) {
2764 Self::new_file_in_direction(workspace, SplitDirection::horizontal(cx), window, cx)
2765 }
2766
2767 fn new_file_split(
2768 workspace: &mut Workspace,
2769 action: &workspace::NewFileSplit,
2770 window: &mut Window,
2771 cx: &mut Context<Workspace>,
2772 ) {
2773 Self::new_file_in_direction(workspace, action.0, window, cx)
2774 }
2775
2776 fn new_file_in_direction(
2777 workspace: &mut Workspace,
2778 direction: SplitDirection,
2779 window: &mut Window,
2780 cx: &mut Context<Workspace>,
2781 ) {
2782 let project = workspace.project().clone();
2783 let create = project.update(cx, |project, cx| project.create_buffer(true, cx));
2784
2785 cx.spawn_in(window, async move |workspace, cx| {
2786 let buffer = create.await?;
2787 workspace.update_in(cx, move |workspace, window, cx| {
2788 workspace.split_item(
2789 direction,
2790 Box::new(
2791 cx.new(|cx| Editor::for_buffer(buffer, Some(project.clone()), window, cx)),
2792 ),
2793 window,
2794 cx,
2795 )
2796 })?;
2797 anyhow::Ok(())
2798 })
2799 .detach_and_prompt_err("Failed to create buffer", window, cx, |e, _, _| {
2800 match e.error_code() {
2801 ErrorCode::RemoteUpgradeRequired => Some(format!(
2802 "The remote instance of Zed does not support this yet. It must be upgraded to {}",
2803 e.error_tag("required").unwrap_or("the latest version")
2804 )),
2805 _ => None,
2806 }
2807 });
2808 }
2809
2810 pub fn leader_id(&self) -> Option<CollaboratorId> {
2811 self.leader_id
2812 }
2813
2814 pub fn buffer(&self) -> &Entity<MultiBuffer> {
2815 &self.buffer
2816 }
2817
2818 pub fn project(&self) -> Option<&Entity<Project>> {
2819 self.project.as_ref()
2820 }
2821
2822 pub fn workspace(&self) -> Option<Entity<Workspace>> {
2823 self.workspace.as_ref()?.0.upgrade()
2824 }
2825
2826 /// Returns the workspace serialization ID if this editor should be serialized.
2827 fn workspace_serialization_id(&self, _cx: &App) -> Option<WorkspaceId> {
2828 self.workspace
2829 .as_ref()
2830 .filter(|_| self.should_serialize_buffer())
2831 .and_then(|workspace| workspace.1)
2832 }
2833
2834 pub fn title<'a>(&self, cx: &'a App) -> Cow<'a, str> {
2835 self.buffer().read(cx).title(cx)
2836 }
2837
2838 pub fn snapshot(&self, window: &Window, cx: &mut App) -> EditorSnapshot {
2839 let git_blame_gutter_max_author_length = self
2840 .render_git_blame_gutter(cx)
2841 .then(|| {
2842 if let Some(blame) = self.blame.as_ref() {
2843 let max_author_length =
2844 blame.update(cx, |blame, cx| blame.max_author_length(cx));
2845 Some(max_author_length)
2846 } else {
2847 None
2848 }
2849 })
2850 .flatten();
2851
2852 EditorSnapshot {
2853 mode: self.mode.clone(),
2854 show_gutter: self.show_gutter,
2855 show_line_numbers: self.show_line_numbers,
2856 show_git_diff_gutter: self.show_git_diff_gutter,
2857 show_code_actions: self.show_code_actions,
2858 show_runnables: self.show_runnables,
2859 show_breakpoints: self.show_breakpoints,
2860 git_blame_gutter_max_author_length,
2861 display_snapshot: self.display_map.update(cx, |map, cx| map.snapshot(cx)),
2862 placeholder_display_snapshot: self
2863 .placeholder_display_map
2864 .as_ref()
2865 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx))),
2866 scroll_anchor: self.scroll_manager.anchor(),
2867 ongoing_scroll: self.scroll_manager.ongoing_scroll(),
2868 is_focused: self.focus_handle.is_focused(window),
2869 current_line_highlight: self
2870 .current_line_highlight
2871 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight),
2872 gutter_hovered: self.gutter_hovered,
2873 }
2874 }
2875
2876 pub fn language_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<Language>> {
2877 self.buffer.read(cx).language_at(point, cx)
2878 }
2879
2880 pub fn file_at<T: ToOffset>(&self, point: T, cx: &App) -> Option<Arc<dyn language::File>> {
2881 self.buffer.read(cx).read(cx).file_at(point).cloned()
2882 }
2883
2884 pub fn active_excerpt(
2885 &self,
2886 cx: &App,
2887 ) -> Option<(ExcerptId, Entity<Buffer>, Range<text::Anchor>)> {
2888 self.buffer
2889 .read(cx)
2890 .excerpt_containing(self.selections.newest_anchor().head(), cx)
2891 }
2892
2893 pub fn mode(&self) -> &EditorMode {
2894 &self.mode
2895 }
2896
2897 pub fn set_mode(&mut self, mode: EditorMode) {
2898 self.mode = mode;
2899 }
2900
2901 pub fn collaboration_hub(&self) -> Option<&dyn CollaborationHub> {
2902 self.collaboration_hub.as_deref()
2903 }
2904
2905 pub fn set_collaboration_hub(&mut self, hub: Box<dyn CollaborationHub>) {
2906 self.collaboration_hub = Some(hub);
2907 }
2908
2909 pub fn set_in_project_search(&mut self, in_project_search: bool) {
2910 self.in_project_search = in_project_search;
2911 }
2912
2913 pub fn set_custom_context_menu(
2914 &mut self,
2915 f: impl 'static
2916 + Fn(
2917 &mut Self,
2918 DisplayPoint,
2919 &mut Window,
2920 &mut Context<Self>,
2921 ) -> Option<Entity<ui::ContextMenu>>,
2922 ) {
2923 self.custom_context_menu = Some(Box::new(f))
2924 }
2925
2926 pub fn set_completion_provider(&mut self, provider: Option<Rc<dyn CompletionProvider>>) {
2927 self.completion_provider = provider;
2928 }
2929
2930 #[cfg(any(test, feature = "test-support"))]
2931 pub fn completion_provider(&self) -> Option<Rc<dyn CompletionProvider>> {
2932 self.completion_provider.clone()
2933 }
2934
2935 pub fn semantics_provider(&self) -> Option<Rc<dyn SemanticsProvider>> {
2936 self.semantics_provider.clone()
2937 }
2938
2939 pub fn set_semantics_provider(&mut self, provider: Option<Rc<dyn SemanticsProvider>>) {
2940 self.semantics_provider = provider;
2941 }
2942
2943 pub fn set_edit_prediction_provider<T>(
2944 &mut self,
2945 provider: Option<Entity<T>>,
2946 window: &mut Window,
2947 cx: &mut Context<Self>,
2948 ) where
2949 T: EditPredictionProvider,
2950 {
2951 self.edit_prediction_provider = provider.map(|provider| RegisteredEditPredictionProvider {
2952 _subscription: cx.observe_in(&provider, window, |this, _, window, cx| {
2953 if this.focus_handle.is_focused(window) {
2954 this.update_visible_edit_prediction(window, cx);
2955 }
2956 }),
2957 provider: Arc::new(provider),
2958 });
2959 self.update_edit_prediction_settings(cx);
2960 self.refresh_edit_prediction(false, false, window, cx);
2961 }
2962
2963 pub fn placeholder_text(&self, cx: &mut App) -> Option<String> {
2964 self.placeholder_display_map
2965 .as_ref()
2966 .map(|display_map| display_map.update(cx, |map, cx| map.snapshot(cx)).text())
2967 }
2968
2969 pub fn set_placeholder_text(
2970 &mut self,
2971 placeholder_text: &str,
2972 window: &mut Window,
2973 cx: &mut Context<Self>,
2974 ) {
2975 let multibuffer = cx
2976 .new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local(placeholder_text, cx)), cx));
2977
2978 let style = window.text_style();
2979
2980 self.placeholder_display_map = Some(cx.new(|cx| {
2981 DisplayMap::new(
2982 multibuffer,
2983 style.font(),
2984 style.font_size.to_pixels(window.rem_size()),
2985 None,
2986 FILE_HEADER_HEIGHT,
2987 MULTI_BUFFER_EXCERPT_HEADER_HEIGHT,
2988 Default::default(),
2989 DiagnosticSeverity::Off,
2990 cx,
2991 )
2992 }));
2993 cx.notify();
2994 }
2995
2996 pub fn set_cursor_shape(&mut self, cursor_shape: CursorShape, cx: &mut Context<Self>) {
2997 self.cursor_shape = cursor_shape;
2998
2999 // Disrupt blink for immediate user feedback that the cursor shape has changed
3000 self.blink_manager.update(cx, BlinkManager::show_cursor);
3001
3002 cx.notify();
3003 }
3004
3005 pub fn set_current_line_highlight(
3006 &mut self,
3007 current_line_highlight: Option<CurrentLineHighlight>,
3008 ) {
3009 self.current_line_highlight = current_line_highlight;
3010 }
3011
3012 pub fn range_for_match<T: std::marker::Copy>(
3013 &self,
3014 range: &Range<T>,
3015 collapse: bool,
3016 ) -> Range<T> {
3017 if collapse {
3018 return range.start..range.start;
3019 }
3020 range.clone()
3021 }
3022
3023 pub fn set_clip_at_line_ends(&mut self, clip: bool, cx: &mut Context<Self>) {
3024 if self.display_map.read(cx).clip_at_line_ends != clip {
3025 self.display_map
3026 .update(cx, |map, _| map.clip_at_line_ends = clip);
3027 }
3028 }
3029
3030 pub fn set_input_enabled(&mut self, input_enabled: bool) {
3031 self.input_enabled = input_enabled;
3032 }
3033
3034 pub fn set_edit_predictions_hidden_for_vim_mode(
3035 &mut self,
3036 hidden: bool,
3037 window: &mut Window,
3038 cx: &mut Context<Self>,
3039 ) {
3040 if hidden != self.edit_predictions_hidden_for_vim_mode {
3041 self.edit_predictions_hidden_for_vim_mode = hidden;
3042 if hidden {
3043 self.update_visible_edit_prediction(window, cx);
3044 } else {
3045 self.refresh_edit_prediction(true, false, window, cx);
3046 }
3047 }
3048 }
3049
3050 pub fn set_menu_edit_predictions_policy(&mut self, value: MenuEditPredictionsPolicy) {
3051 self.menu_edit_predictions_policy = value;
3052 }
3053
3054 pub fn set_autoindent(&mut self, autoindent: bool) {
3055 if autoindent {
3056 self.autoindent_mode = Some(AutoindentMode::EachLine);
3057 } else {
3058 self.autoindent_mode = None;
3059 }
3060 }
3061
3062 pub fn read_only(&self, cx: &App) -> bool {
3063 self.read_only || self.buffer.read(cx).read_only()
3064 }
3065
3066 pub fn set_read_only(&mut self, read_only: bool) {
3067 self.read_only = read_only;
3068 }
3069
3070 pub fn set_use_autoclose(&mut self, autoclose: bool) {
3071 self.use_autoclose = autoclose;
3072 }
3073
3074 pub fn set_use_auto_surround(&mut self, auto_surround: bool) {
3075 self.use_auto_surround = auto_surround;
3076 }
3077
3078 pub fn set_auto_replace_emoji_shortcode(&mut self, auto_replace: bool) {
3079 self.auto_replace_emoji_shortcode = auto_replace;
3080 }
3081
3082 pub fn set_should_serialize(&mut self, should_serialize: bool, cx: &App) {
3083 self.buffer_serialization = should_serialize.then(|| {
3084 BufferSerialization::new(
3085 ProjectSettings::get_global(cx)
3086 .session
3087 .restore_unsaved_buffers,
3088 )
3089 })
3090 }
3091
3092 fn should_serialize_buffer(&self) -> bool {
3093 self.buffer_serialization.is_some()
3094 }
3095
3096 pub fn toggle_edit_predictions(
3097 &mut self,
3098 _: &ToggleEditPrediction,
3099 window: &mut Window,
3100 cx: &mut Context<Self>,
3101 ) {
3102 if self.show_edit_predictions_override.is_some() {
3103 self.set_show_edit_predictions(None, window, cx);
3104 } else {
3105 let show_edit_predictions = !self.edit_predictions_enabled();
3106 self.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
3107 }
3108 }
3109
3110 pub fn set_show_edit_predictions(
3111 &mut self,
3112 show_edit_predictions: Option<bool>,
3113 window: &mut Window,
3114 cx: &mut Context<Self>,
3115 ) {
3116 self.show_edit_predictions_override = show_edit_predictions;
3117 self.update_edit_prediction_settings(cx);
3118
3119 if let Some(false) = show_edit_predictions {
3120 self.discard_edit_prediction(false, cx);
3121 } else {
3122 self.refresh_edit_prediction(false, true, window, cx);
3123 }
3124 }
3125
3126 fn edit_predictions_disabled_in_scope(
3127 &self,
3128 buffer: &Entity<Buffer>,
3129 buffer_position: language::Anchor,
3130 cx: &App,
3131 ) -> bool {
3132 let snapshot = buffer.read(cx).snapshot();
3133 let settings = snapshot.settings_at(buffer_position, cx);
3134
3135 let Some(scope) = snapshot.language_scope_at(buffer_position) else {
3136 return false;
3137 };
3138
3139 scope.override_name().is_some_and(|scope_name| {
3140 settings
3141 .edit_predictions_disabled_in
3142 .iter()
3143 .any(|s| s == scope_name)
3144 })
3145 }
3146
3147 pub fn set_use_modal_editing(&mut self, to: bool) {
3148 self.use_modal_editing = to;
3149 }
3150
3151 pub fn use_modal_editing(&self) -> bool {
3152 self.use_modal_editing
3153 }
3154
3155 fn selections_did_change(
3156 &mut self,
3157 local: bool,
3158 old_cursor_position: &Anchor,
3159 effects: SelectionEffects,
3160 window: &mut Window,
3161 cx: &mut Context<Self>,
3162 ) {
3163 window.invalidate_character_coordinates();
3164
3165 // Copy selections to primary selection buffer
3166 #[cfg(any(target_os = "linux", target_os = "freebsd"))]
3167 if local {
3168 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3169 let buffer_handle = self.buffer.read(cx).read(cx);
3170
3171 let mut text = String::new();
3172 for (index, selection) in selections.iter().enumerate() {
3173 let text_for_selection = buffer_handle
3174 .text_for_range(selection.start..selection.end)
3175 .collect::<String>();
3176
3177 text.push_str(&text_for_selection);
3178 if index != selections.len() - 1 {
3179 text.push('\n');
3180 }
3181 }
3182
3183 if !text.is_empty() {
3184 cx.write_to_primary(ClipboardItem::new_string(text));
3185 }
3186 }
3187
3188 let selection_anchors = self.selections.disjoint_anchors_arc();
3189
3190 if self.focus_handle.is_focused(window) && self.leader_id.is_none() {
3191 self.buffer.update(cx, |buffer, cx| {
3192 buffer.set_active_selections(
3193 &selection_anchors,
3194 self.selections.line_mode(),
3195 self.cursor_shape,
3196 cx,
3197 )
3198 });
3199 }
3200 let display_map = self
3201 .display_map
3202 .update(cx, |display_map, cx| display_map.snapshot(cx));
3203 let buffer = display_map.buffer_snapshot();
3204 if self.selections.count() == 1 {
3205 self.add_selections_state = None;
3206 }
3207 self.select_next_state = None;
3208 self.select_prev_state = None;
3209 self.select_syntax_node_history.try_clear();
3210 self.invalidate_autoclose_regions(&selection_anchors, buffer);
3211 self.snippet_stack.invalidate(&selection_anchors, buffer);
3212 self.take_rename(false, window, cx);
3213
3214 let newest_selection = self.selections.newest_anchor();
3215 let new_cursor_position = newest_selection.head();
3216 let selection_start = newest_selection.start;
3217
3218 if effects.nav_history.is_none() || effects.nav_history == Some(true) {
3219 self.push_to_nav_history(
3220 *old_cursor_position,
3221 Some(new_cursor_position.to_point(buffer)),
3222 false,
3223 effects.nav_history == Some(true),
3224 cx,
3225 );
3226 }
3227
3228 if local {
3229 if let Some(buffer_id) = new_cursor_position.buffer_id {
3230 self.register_buffer(buffer_id, cx);
3231 }
3232
3233 let mut context_menu = self.context_menu.borrow_mut();
3234 let completion_menu = match context_menu.as_ref() {
3235 Some(CodeContextMenu::Completions(menu)) => Some(menu),
3236 Some(CodeContextMenu::CodeActions(_)) => {
3237 *context_menu = None;
3238 None
3239 }
3240 None => None,
3241 };
3242 let completion_position = completion_menu.map(|menu| menu.initial_position);
3243 drop(context_menu);
3244
3245 if effects.completions
3246 && let Some(completion_position) = completion_position
3247 {
3248 let start_offset = selection_start.to_offset(buffer);
3249 let position_matches = start_offset == completion_position.to_offset(buffer);
3250 let continue_showing = if position_matches {
3251 if self.snippet_stack.is_empty() {
3252 buffer.char_kind_before(start_offset, Some(CharScopeContext::Completion))
3253 == Some(CharKind::Word)
3254 } else {
3255 // Snippet choices can be shown even when the cursor is in whitespace.
3256 // Dismissing the menu with actions like backspace is handled by
3257 // invalidation regions.
3258 true
3259 }
3260 } else {
3261 false
3262 };
3263
3264 if continue_showing {
3265 self.open_or_update_completions_menu(None, None, false, window, cx);
3266 } else {
3267 self.hide_context_menu(window, cx);
3268 }
3269 }
3270
3271 hide_hover(self, cx);
3272
3273 if old_cursor_position.to_display_point(&display_map).row()
3274 != new_cursor_position.to_display_point(&display_map).row()
3275 {
3276 self.available_code_actions.take();
3277 }
3278 self.refresh_code_actions(window, cx);
3279 self.refresh_document_highlights(cx);
3280 refresh_linked_ranges(self, window, cx);
3281
3282 self.refresh_selected_text_highlights(false, window, cx);
3283 self.refresh_matching_bracket_highlights(window, cx);
3284 self.update_visible_edit_prediction(window, cx);
3285 self.edit_prediction_requires_modifier_in_indent_conflict = true;
3286 self.inline_blame_popover.take();
3287 if self.git_blame_inline_enabled {
3288 self.start_inline_blame_timer(window, cx);
3289 }
3290 }
3291
3292 self.blink_manager.update(cx, BlinkManager::pause_blinking);
3293 cx.emit(EditorEvent::SelectionsChanged { local });
3294
3295 let selections = &self.selections.disjoint_anchors_arc();
3296 if selections.len() == 1 {
3297 cx.emit(SearchEvent::ActiveMatchChanged)
3298 }
3299 if local && let Some((_, _, buffer_snapshot)) = buffer.as_singleton() {
3300 let inmemory_selections = selections
3301 .iter()
3302 .map(|s| {
3303 text::ToPoint::to_point(&s.range().start.text_anchor, buffer_snapshot)
3304 ..text::ToPoint::to_point(&s.range().end.text_anchor, buffer_snapshot)
3305 })
3306 .collect();
3307 self.update_restoration_data(cx, |data| {
3308 data.selections = inmemory_selections;
3309 });
3310
3311 if WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
3312 && let Some(workspace_id) = self.workspace_serialization_id(cx)
3313 {
3314 let snapshot = self.buffer().read(cx).snapshot(cx);
3315 let selections = selections.clone();
3316 let background_executor = cx.background_executor().clone();
3317 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3318 self.serialize_selections = cx.background_spawn(async move {
3319 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3320 let db_selections = selections
3321 .iter()
3322 .map(|selection| {
3323 (
3324 selection.start.to_offset(&snapshot),
3325 selection.end.to_offset(&snapshot),
3326 )
3327 })
3328 .collect();
3329
3330 DB.save_editor_selections(editor_id, workspace_id, db_selections)
3331 .await
3332 .with_context(|| {
3333 format!(
3334 "persisting editor selections for editor {editor_id}, \
3335 workspace {workspace_id:?}"
3336 )
3337 })
3338 .log_err();
3339 });
3340 }
3341 }
3342
3343 cx.notify();
3344 }
3345
3346 fn folds_did_change(&mut self, cx: &mut Context<Self>) {
3347 use text::ToOffset as _;
3348 use text::ToPoint as _;
3349
3350 if self.mode.is_minimap()
3351 || WorkspaceSettings::get(None, cx).restore_on_startup == RestoreOnStartupBehavior::None
3352 {
3353 return;
3354 }
3355
3356 if !self.buffer().read(cx).is_singleton() {
3357 return;
3358 }
3359
3360 let display_snapshot = self
3361 .display_map
3362 .update(cx, |display_map, cx| display_map.snapshot(cx));
3363 let Some((.., snapshot)) = display_snapshot.buffer_snapshot().as_singleton() else {
3364 return;
3365 };
3366 let inmemory_folds = display_snapshot
3367 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3368 .map(|fold| {
3369 fold.range.start.text_anchor.to_point(&snapshot)
3370 ..fold.range.end.text_anchor.to_point(&snapshot)
3371 })
3372 .collect();
3373 self.update_restoration_data(cx, |data| {
3374 data.folds = inmemory_folds;
3375 });
3376
3377 let Some(workspace_id) = self.workspace_serialization_id(cx) else {
3378 return;
3379 };
3380 let background_executor = cx.background_executor().clone();
3381 let editor_id = cx.entity().entity_id().as_u64() as ItemId;
3382 let db_folds = display_snapshot
3383 .folds_in_range(0..display_snapshot.buffer_snapshot().len())
3384 .map(|fold| {
3385 (
3386 fold.range.start.text_anchor.to_offset(&snapshot),
3387 fold.range.end.text_anchor.to_offset(&snapshot),
3388 )
3389 })
3390 .collect();
3391 self.serialize_folds = cx.background_spawn(async move {
3392 background_executor.timer(SERIALIZATION_THROTTLE_TIME).await;
3393 DB.save_editor_folds(editor_id, workspace_id, db_folds)
3394 .await
3395 .with_context(|| {
3396 format!(
3397 "persisting editor folds for editor {editor_id}, workspace {workspace_id:?}"
3398 )
3399 })
3400 .log_err();
3401 });
3402 }
3403
3404 pub fn sync_selections(
3405 &mut self,
3406 other: Entity<Editor>,
3407 cx: &mut Context<Self>,
3408 ) -> gpui::Subscription {
3409 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3410 if !other_selections.is_empty() {
3411 self.selections
3412 .change_with(&self.display_snapshot(cx), |selections| {
3413 selections.select_anchors(other_selections);
3414 });
3415 }
3416
3417 let other_subscription = cx.subscribe(&other, |this, other, other_evt, cx| {
3418 if let EditorEvent::SelectionsChanged { local: true } = other_evt {
3419 let other_selections = other.read(cx).selections.disjoint_anchors().to_vec();
3420 if other_selections.is_empty() {
3421 return;
3422 }
3423 let snapshot = this.display_snapshot(cx);
3424 this.selections.change_with(&snapshot, |selections| {
3425 selections.select_anchors(other_selections);
3426 });
3427 }
3428 });
3429
3430 let this_subscription = cx.subscribe_self::<EditorEvent>(move |this, this_evt, cx| {
3431 if let EditorEvent::SelectionsChanged { local: true } = this_evt {
3432 let these_selections = this.selections.disjoint_anchors().to_vec();
3433 if these_selections.is_empty() {
3434 return;
3435 }
3436 other.update(cx, |other_editor, cx| {
3437 let snapshot = other_editor.display_snapshot(cx);
3438 other_editor
3439 .selections
3440 .change_with(&snapshot, |selections| {
3441 selections.select_anchors(these_selections);
3442 })
3443 });
3444 }
3445 });
3446
3447 Subscription::join(other_subscription, this_subscription)
3448 }
3449
3450 /// Changes selections using the provided mutation function. Changes to `self.selections` occur
3451 /// immediately, but when run within `transact` or `with_selection_effects_deferred` other
3452 /// effects of selection change occur at the end of the transaction.
3453 pub fn change_selections<R>(
3454 &mut self,
3455 effects: SelectionEffects,
3456 window: &mut Window,
3457 cx: &mut Context<Self>,
3458 change: impl FnOnce(&mut MutableSelectionsCollection<'_, '_>) -> R,
3459 ) -> R {
3460 let snapshot = self.display_snapshot(cx);
3461 if let Some(state) = &mut self.deferred_selection_effects_state {
3462 state.effects.scroll = effects.scroll.or(state.effects.scroll);
3463 state.effects.completions = effects.completions;
3464 state.effects.nav_history = effects.nav_history.or(state.effects.nav_history);
3465 let (changed, result) = self.selections.change_with(&snapshot, change);
3466 state.changed |= changed;
3467 return result;
3468 }
3469 let mut state = DeferredSelectionEffectsState {
3470 changed: false,
3471 effects,
3472 old_cursor_position: self.selections.newest_anchor().head(),
3473 history_entry: SelectionHistoryEntry {
3474 selections: self.selections.disjoint_anchors_arc(),
3475 select_next_state: self.select_next_state.clone(),
3476 select_prev_state: self.select_prev_state.clone(),
3477 add_selections_state: self.add_selections_state.clone(),
3478 },
3479 };
3480 let (changed, result) = self.selections.change_with(&snapshot, change);
3481 state.changed = state.changed || changed;
3482 if self.defer_selection_effects {
3483 self.deferred_selection_effects_state = Some(state);
3484 } else {
3485 self.apply_selection_effects(state, window, cx);
3486 }
3487 result
3488 }
3489
3490 /// Defers the effects of selection change, so that the effects of multiple calls to
3491 /// `change_selections` are applied at the end. This way these intermediate states aren't added
3492 /// to selection history and the state of popovers based on selection position aren't
3493 /// erroneously updated.
3494 pub fn with_selection_effects_deferred<R>(
3495 &mut self,
3496 window: &mut Window,
3497 cx: &mut Context<Self>,
3498 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>) -> R,
3499 ) -> R {
3500 let already_deferred = self.defer_selection_effects;
3501 self.defer_selection_effects = true;
3502 let result = update(self, window, cx);
3503 if !already_deferred {
3504 self.defer_selection_effects = false;
3505 if let Some(state) = self.deferred_selection_effects_state.take() {
3506 self.apply_selection_effects(state, window, cx);
3507 }
3508 }
3509 result
3510 }
3511
3512 fn apply_selection_effects(
3513 &mut self,
3514 state: DeferredSelectionEffectsState,
3515 window: &mut Window,
3516 cx: &mut Context<Self>,
3517 ) {
3518 if state.changed {
3519 self.selection_history.push(state.history_entry);
3520
3521 if let Some(autoscroll) = state.effects.scroll {
3522 self.request_autoscroll(autoscroll, cx);
3523 }
3524
3525 let old_cursor_position = &state.old_cursor_position;
3526
3527 self.selections_did_change(true, old_cursor_position, state.effects, window, cx);
3528
3529 if self.should_open_signature_help_automatically(old_cursor_position, cx) {
3530 self.show_signature_help(&ShowSignatureHelp, window, cx);
3531 }
3532 }
3533 }
3534
3535 pub fn edit<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3536 where
3537 I: IntoIterator<Item = (Range<S>, T)>,
3538 S: ToOffset,
3539 T: Into<Arc<str>>,
3540 {
3541 if self.read_only(cx) {
3542 return;
3543 }
3544
3545 self.buffer
3546 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
3547 }
3548
3549 pub fn edit_with_autoindent<I, S, T>(&mut self, edits: I, cx: &mut Context<Self>)
3550 where
3551 I: IntoIterator<Item = (Range<S>, T)>,
3552 S: ToOffset,
3553 T: Into<Arc<str>>,
3554 {
3555 if self.read_only(cx) {
3556 return;
3557 }
3558
3559 self.buffer.update(cx, |buffer, cx| {
3560 buffer.edit(edits, self.autoindent_mode.clone(), cx)
3561 });
3562 }
3563
3564 pub fn edit_with_block_indent<I, S, T>(
3565 &mut self,
3566 edits: I,
3567 original_indent_columns: Vec<Option<u32>>,
3568 cx: &mut Context<Self>,
3569 ) where
3570 I: IntoIterator<Item = (Range<S>, T)>,
3571 S: ToOffset,
3572 T: Into<Arc<str>>,
3573 {
3574 if self.read_only(cx) {
3575 return;
3576 }
3577
3578 self.buffer.update(cx, |buffer, cx| {
3579 buffer.edit(
3580 edits,
3581 Some(AutoindentMode::Block {
3582 original_indent_columns,
3583 }),
3584 cx,
3585 )
3586 });
3587 }
3588
3589 fn select(&mut self, phase: SelectPhase, window: &mut Window, cx: &mut Context<Self>) {
3590 self.hide_context_menu(window, cx);
3591
3592 match phase {
3593 SelectPhase::Begin {
3594 position,
3595 add,
3596 click_count,
3597 } => self.begin_selection(position, add, click_count, window, cx),
3598 SelectPhase::BeginColumnar {
3599 position,
3600 goal_column,
3601 reset,
3602 mode,
3603 } => self.begin_columnar_selection(position, goal_column, reset, mode, window, cx),
3604 SelectPhase::Extend {
3605 position,
3606 click_count,
3607 } => self.extend_selection(position, click_count, window, cx),
3608 SelectPhase::Update {
3609 position,
3610 goal_column,
3611 scroll_delta,
3612 } => self.update_selection(position, goal_column, scroll_delta, window, cx),
3613 SelectPhase::End => self.end_selection(window, cx),
3614 }
3615 }
3616
3617 fn extend_selection(
3618 &mut self,
3619 position: DisplayPoint,
3620 click_count: usize,
3621 window: &mut Window,
3622 cx: &mut Context<Self>,
3623 ) {
3624 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3625 let tail = self.selections.newest::<usize>(&display_map).tail();
3626 let click_count = click_count.max(match self.selections.select_mode() {
3627 SelectMode::Character => 1,
3628 SelectMode::Word(_) => 2,
3629 SelectMode::Line(_) => 3,
3630 SelectMode::All => 4,
3631 });
3632 self.begin_selection(position, false, click_count, window, cx);
3633
3634 let tail_anchor = display_map.buffer_snapshot().anchor_before(tail);
3635
3636 let current_selection = match self.selections.select_mode() {
3637 SelectMode::Character | SelectMode::All => tail_anchor..tail_anchor,
3638 SelectMode::Word(range) | SelectMode::Line(range) => range.clone(),
3639 };
3640
3641 let mut pending_selection = self
3642 .selections
3643 .pending_anchor()
3644 .cloned()
3645 .expect("extend_selection not called with pending selection");
3646
3647 if pending_selection
3648 .start
3649 .cmp(¤t_selection.start, display_map.buffer_snapshot())
3650 == Ordering::Greater
3651 {
3652 pending_selection.start = current_selection.start;
3653 }
3654 if pending_selection
3655 .end
3656 .cmp(¤t_selection.end, display_map.buffer_snapshot())
3657 == Ordering::Less
3658 {
3659 pending_selection.end = current_selection.end;
3660 pending_selection.reversed = true;
3661 }
3662
3663 let mut pending_mode = self.selections.pending_mode().unwrap();
3664 match &mut pending_mode {
3665 SelectMode::Word(range) | SelectMode::Line(range) => *range = current_selection,
3666 _ => {}
3667 }
3668
3669 let effects = if EditorSettings::get_global(cx).autoscroll_on_clicks {
3670 SelectionEffects::scroll(Autoscroll::fit())
3671 } else {
3672 SelectionEffects::no_scroll()
3673 };
3674
3675 self.change_selections(effects, window, cx, |s| {
3676 s.set_pending(pending_selection.clone(), pending_mode);
3677 s.set_is_extending(true);
3678 });
3679 }
3680
3681 fn begin_selection(
3682 &mut self,
3683 position: DisplayPoint,
3684 add: bool,
3685 click_count: usize,
3686 window: &mut Window,
3687 cx: &mut Context<Self>,
3688 ) {
3689 if !self.focus_handle.is_focused(window) {
3690 self.last_focused_descendant = None;
3691 window.focus(&self.focus_handle);
3692 }
3693
3694 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3695 let buffer = display_map.buffer_snapshot();
3696 let position = display_map.clip_point(position, Bias::Left);
3697
3698 let start;
3699 let end;
3700 let mode;
3701 let mut auto_scroll;
3702 match click_count {
3703 1 => {
3704 start = buffer.anchor_before(position.to_point(&display_map));
3705 end = start;
3706 mode = SelectMode::Character;
3707 auto_scroll = true;
3708 }
3709 2 => {
3710 let position = display_map
3711 .clip_point(position, Bias::Left)
3712 .to_offset(&display_map, Bias::Left);
3713 let (range, _) = buffer.surrounding_word(position, None);
3714 start = buffer.anchor_before(range.start);
3715 end = buffer.anchor_before(range.end);
3716 mode = SelectMode::Word(start..end);
3717 auto_scroll = true;
3718 }
3719 3 => {
3720 let position = display_map
3721 .clip_point(position, Bias::Left)
3722 .to_point(&display_map);
3723 let line_start = display_map.prev_line_boundary(position).0;
3724 let next_line_start = buffer.clip_point(
3725 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3726 Bias::Left,
3727 );
3728 start = buffer.anchor_before(line_start);
3729 end = buffer.anchor_before(next_line_start);
3730 mode = SelectMode::Line(start..end);
3731 auto_scroll = true;
3732 }
3733 _ => {
3734 start = buffer.anchor_before(0);
3735 end = buffer.anchor_before(buffer.len());
3736 mode = SelectMode::All;
3737 auto_scroll = false;
3738 }
3739 }
3740 auto_scroll &= EditorSettings::get_global(cx).autoscroll_on_clicks;
3741
3742 let point_to_delete: Option<usize> = {
3743 let selected_points: Vec<Selection<Point>> =
3744 self.selections.disjoint_in_range(start..end, &display_map);
3745
3746 if !add || click_count > 1 {
3747 None
3748 } else if !selected_points.is_empty() {
3749 Some(selected_points[0].id)
3750 } else {
3751 let clicked_point_already_selected =
3752 self.selections.disjoint_anchors().iter().find(|selection| {
3753 selection.start.to_point(buffer) == start.to_point(buffer)
3754 || selection.end.to_point(buffer) == end.to_point(buffer)
3755 });
3756
3757 clicked_point_already_selected.map(|selection| selection.id)
3758 }
3759 };
3760
3761 let selections_count = self.selections.count();
3762 let effects = if auto_scroll {
3763 SelectionEffects::default()
3764 } else {
3765 SelectionEffects::no_scroll()
3766 };
3767
3768 self.change_selections(effects, window, cx, |s| {
3769 if let Some(point_to_delete) = point_to_delete {
3770 s.delete(point_to_delete);
3771
3772 if selections_count == 1 {
3773 s.set_pending_anchor_range(start..end, mode);
3774 }
3775 } else {
3776 if !add {
3777 s.clear_disjoint();
3778 }
3779
3780 s.set_pending_anchor_range(start..end, mode);
3781 }
3782 });
3783 }
3784
3785 fn begin_columnar_selection(
3786 &mut self,
3787 position: DisplayPoint,
3788 goal_column: u32,
3789 reset: bool,
3790 mode: ColumnarMode,
3791 window: &mut Window,
3792 cx: &mut Context<Self>,
3793 ) {
3794 if !self.focus_handle.is_focused(window) {
3795 self.last_focused_descendant = None;
3796 window.focus(&self.focus_handle);
3797 }
3798
3799 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3800
3801 if reset {
3802 let pointer_position = display_map
3803 .buffer_snapshot()
3804 .anchor_before(position.to_point(&display_map));
3805
3806 self.change_selections(
3807 SelectionEffects::scroll(Autoscroll::newest()),
3808 window,
3809 cx,
3810 |s| {
3811 s.clear_disjoint();
3812 s.set_pending_anchor_range(
3813 pointer_position..pointer_position,
3814 SelectMode::Character,
3815 );
3816 },
3817 );
3818 };
3819
3820 let tail = self.selections.newest::<Point>(&display_map).tail();
3821 let selection_anchor = display_map.buffer_snapshot().anchor_before(tail);
3822 self.columnar_selection_state = match mode {
3823 ColumnarMode::FromMouse => Some(ColumnarSelectionState::FromMouse {
3824 selection_tail: selection_anchor,
3825 display_point: if reset {
3826 if position.column() != goal_column {
3827 Some(DisplayPoint::new(position.row(), goal_column))
3828 } else {
3829 None
3830 }
3831 } else {
3832 None
3833 },
3834 }),
3835 ColumnarMode::FromSelection => Some(ColumnarSelectionState::FromSelection {
3836 selection_tail: selection_anchor,
3837 }),
3838 };
3839
3840 if !reset {
3841 self.select_columns(position, goal_column, &display_map, window, cx);
3842 }
3843 }
3844
3845 fn update_selection(
3846 &mut self,
3847 position: DisplayPoint,
3848 goal_column: u32,
3849 scroll_delta: gpui::Point<f32>,
3850 window: &mut Window,
3851 cx: &mut Context<Self>,
3852 ) {
3853 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
3854
3855 if self.columnar_selection_state.is_some() {
3856 self.select_columns(position, goal_column, &display_map, window, cx);
3857 } else if let Some(mut pending) = self.selections.pending_anchor().cloned() {
3858 let buffer = display_map.buffer_snapshot();
3859 let head;
3860 let tail;
3861 let mode = self.selections.pending_mode().unwrap();
3862 match &mode {
3863 SelectMode::Character => {
3864 head = position.to_point(&display_map);
3865 tail = pending.tail().to_point(buffer);
3866 }
3867 SelectMode::Word(original_range) => {
3868 let offset = display_map
3869 .clip_point(position, Bias::Left)
3870 .to_offset(&display_map, Bias::Left);
3871 let original_range = original_range.to_offset(buffer);
3872
3873 let head_offset = if buffer.is_inside_word(offset, None)
3874 || original_range.contains(&offset)
3875 {
3876 let (word_range, _) = buffer.surrounding_word(offset, None);
3877 if word_range.start < original_range.start {
3878 word_range.start
3879 } else {
3880 word_range.end
3881 }
3882 } else {
3883 offset
3884 };
3885
3886 head = head_offset.to_point(buffer);
3887 if head_offset <= original_range.start {
3888 tail = original_range.end.to_point(buffer);
3889 } else {
3890 tail = original_range.start.to_point(buffer);
3891 }
3892 }
3893 SelectMode::Line(original_range) => {
3894 let original_range = original_range.to_point(display_map.buffer_snapshot());
3895
3896 let position = display_map
3897 .clip_point(position, Bias::Left)
3898 .to_point(&display_map);
3899 let line_start = display_map.prev_line_boundary(position).0;
3900 let next_line_start = buffer.clip_point(
3901 display_map.next_line_boundary(position).0 + Point::new(1, 0),
3902 Bias::Left,
3903 );
3904
3905 if line_start < original_range.start {
3906 head = line_start
3907 } else {
3908 head = next_line_start
3909 }
3910
3911 if head <= original_range.start {
3912 tail = original_range.end;
3913 } else {
3914 tail = original_range.start;
3915 }
3916 }
3917 SelectMode::All => {
3918 return;
3919 }
3920 };
3921
3922 if head < tail {
3923 pending.start = buffer.anchor_before(head);
3924 pending.end = buffer.anchor_before(tail);
3925 pending.reversed = true;
3926 } else {
3927 pending.start = buffer.anchor_before(tail);
3928 pending.end = buffer.anchor_before(head);
3929 pending.reversed = false;
3930 }
3931
3932 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3933 s.set_pending(pending.clone(), mode);
3934 });
3935 } else {
3936 log::error!("update_selection dispatched with no pending selection");
3937 return;
3938 }
3939
3940 self.apply_scroll_delta(scroll_delta, window, cx);
3941 cx.notify();
3942 }
3943
3944 fn end_selection(&mut self, window: &mut Window, cx: &mut Context<Self>) {
3945 self.columnar_selection_state.take();
3946 if let Some(pending_mode) = self.selections.pending_mode() {
3947 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
3948 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3949 s.select(selections);
3950 s.clear_pending();
3951 if s.is_extending() {
3952 s.set_is_extending(false);
3953 } else {
3954 s.set_select_mode(pending_mode);
3955 }
3956 });
3957 }
3958 }
3959
3960 fn select_columns(
3961 &mut self,
3962 head: DisplayPoint,
3963 goal_column: u32,
3964 display_map: &DisplaySnapshot,
3965 window: &mut Window,
3966 cx: &mut Context<Self>,
3967 ) {
3968 let Some(columnar_state) = self.columnar_selection_state.as_ref() else {
3969 return;
3970 };
3971
3972 let tail = match columnar_state {
3973 ColumnarSelectionState::FromMouse {
3974 selection_tail,
3975 display_point,
3976 } => display_point.unwrap_or_else(|| selection_tail.to_display_point(display_map)),
3977 ColumnarSelectionState::FromSelection { selection_tail } => {
3978 selection_tail.to_display_point(display_map)
3979 }
3980 };
3981
3982 let start_row = cmp::min(tail.row(), head.row());
3983 let end_row = cmp::max(tail.row(), head.row());
3984 let start_column = cmp::min(tail.column(), goal_column);
3985 let end_column = cmp::max(tail.column(), goal_column);
3986 let reversed = start_column < tail.column();
3987
3988 let selection_ranges = (start_row.0..=end_row.0)
3989 .map(DisplayRow)
3990 .filter_map(|row| {
3991 if (matches!(columnar_state, ColumnarSelectionState::FromMouse { .. })
3992 || start_column <= display_map.line_len(row))
3993 && !display_map.is_block_line(row)
3994 {
3995 let start = display_map
3996 .clip_point(DisplayPoint::new(row, start_column), Bias::Left)
3997 .to_point(display_map);
3998 let end = display_map
3999 .clip_point(DisplayPoint::new(row, end_column), Bias::Right)
4000 .to_point(display_map);
4001 if reversed {
4002 Some(end..start)
4003 } else {
4004 Some(start..end)
4005 }
4006 } else {
4007 None
4008 }
4009 })
4010 .collect::<Vec<_>>();
4011 if selection_ranges.is_empty() {
4012 return;
4013 }
4014
4015 let ranges = match columnar_state {
4016 ColumnarSelectionState::FromMouse { .. } => {
4017 let mut non_empty_ranges = selection_ranges
4018 .iter()
4019 .filter(|selection_range| selection_range.start != selection_range.end)
4020 .peekable();
4021 if non_empty_ranges.peek().is_some() {
4022 non_empty_ranges.cloned().collect()
4023 } else {
4024 selection_ranges
4025 }
4026 }
4027 _ => selection_ranges,
4028 };
4029
4030 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
4031 s.select_ranges(ranges);
4032 });
4033 cx.notify();
4034 }
4035
4036 pub fn has_non_empty_selection(&self, snapshot: &DisplaySnapshot) -> bool {
4037 self.selections
4038 .all_adjusted(snapshot)
4039 .iter()
4040 .any(|selection| !selection.is_empty())
4041 }
4042
4043 pub fn has_pending_nonempty_selection(&self) -> bool {
4044 let pending_nonempty_selection = match self.selections.pending_anchor() {
4045 Some(Selection { start, end, .. }) => start != end,
4046 None => false,
4047 };
4048
4049 pending_nonempty_selection
4050 || (self.columnar_selection_state.is_some()
4051 && self.selections.disjoint_anchors().len() > 1)
4052 }
4053
4054 pub fn has_pending_selection(&self) -> bool {
4055 self.selections.pending_anchor().is_some() || self.columnar_selection_state.is_some()
4056 }
4057
4058 pub fn cancel(&mut self, _: &Cancel, window: &mut Window, cx: &mut Context<Self>) {
4059 self.selection_mark_mode = false;
4060 self.selection_drag_state = SelectionDragState::None;
4061
4062 if self.clear_expanded_diff_hunks(cx) {
4063 cx.notify();
4064 return;
4065 }
4066 if self.dismiss_menus_and_popups(true, window, cx) {
4067 return;
4068 }
4069
4070 if self.mode.is_full()
4071 && self.change_selections(Default::default(), window, cx, |s| s.try_cancel())
4072 {
4073 return;
4074 }
4075
4076 cx.propagate();
4077 }
4078
4079 pub fn dismiss_menus_and_popups(
4080 &mut self,
4081 is_user_requested: bool,
4082 window: &mut Window,
4083 cx: &mut Context<Self>,
4084 ) -> bool {
4085 if self.take_rename(false, window, cx).is_some() {
4086 return true;
4087 }
4088
4089 if self.hide_blame_popover(true, cx) {
4090 return true;
4091 }
4092
4093 if hide_hover(self, cx) {
4094 return true;
4095 }
4096
4097 if self.hide_signature_help(cx, SignatureHelpHiddenBy::Escape) {
4098 return true;
4099 }
4100
4101 if self.hide_context_menu(window, cx).is_some() {
4102 return true;
4103 }
4104
4105 if self.mouse_context_menu.take().is_some() {
4106 return true;
4107 }
4108
4109 if is_user_requested && self.discard_edit_prediction(true, cx) {
4110 return true;
4111 }
4112
4113 if self.snippet_stack.pop().is_some() {
4114 return true;
4115 }
4116
4117 if self.mode.is_full() && matches!(self.active_diagnostics, ActiveDiagnostic::Group(_)) {
4118 self.dismiss_diagnostics(cx);
4119 return true;
4120 }
4121
4122 false
4123 }
4124
4125 fn linked_editing_ranges_for(
4126 &self,
4127 selection: Range<text::Anchor>,
4128 cx: &App,
4129 ) -> Option<HashMap<Entity<Buffer>, Vec<Range<text::Anchor>>>> {
4130 if self.linked_edit_ranges.is_empty() {
4131 return None;
4132 }
4133 let ((base_range, linked_ranges), buffer_snapshot, buffer) =
4134 selection.end.buffer_id.and_then(|end_buffer_id| {
4135 if selection.start.buffer_id != Some(end_buffer_id) {
4136 return None;
4137 }
4138 let buffer = self.buffer.read(cx).buffer(end_buffer_id)?;
4139 let snapshot = buffer.read(cx).snapshot();
4140 self.linked_edit_ranges
4141 .get(end_buffer_id, selection.start..selection.end, &snapshot)
4142 .map(|ranges| (ranges, snapshot, buffer))
4143 })?;
4144 use text::ToOffset as TO;
4145 // find offset from the start of current range to current cursor position
4146 let start_byte_offset = TO::to_offset(&base_range.start, &buffer_snapshot);
4147
4148 let start_offset = TO::to_offset(&selection.start, &buffer_snapshot);
4149 let start_difference = start_offset - start_byte_offset;
4150 let end_offset = TO::to_offset(&selection.end, &buffer_snapshot);
4151 let end_difference = end_offset - start_byte_offset;
4152 // Current range has associated linked ranges.
4153 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4154 for range in linked_ranges.iter() {
4155 let start_offset = TO::to_offset(&range.start, &buffer_snapshot);
4156 let end_offset = start_offset + end_difference;
4157 let start_offset = start_offset + start_difference;
4158 if start_offset > buffer_snapshot.len() || end_offset > buffer_snapshot.len() {
4159 continue;
4160 }
4161 if self.selections.disjoint_anchor_ranges().any(|s| {
4162 if s.start.buffer_id != selection.start.buffer_id
4163 || s.end.buffer_id != selection.end.buffer_id
4164 {
4165 return false;
4166 }
4167 TO::to_offset(&s.start.text_anchor, &buffer_snapshot) <= end_offset
4168 && TO::to_offset(&s.end.text_anchor, &buffer_snapshot) >= start_offset
4169 }) {
4170 continue;
4171 }
4172 let start = buffer_snapshot.anchor_after(start_offset);
4173 let end = buffer_snapshot.anchor_after(end_offset);
4174 linked_edits
4175 .entry(buffer.clone())
4176 .or_default()
4177 .push(start..end);
4178 }
4179 Some(linked_edits)
4180 }
4181
4182 pub fn handle_input(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
4183 let text: Arc<str> = text.into();
4184
4185 if self.read_only(cx) {
4186 return;
4187 }
4188
4189 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4190
4191 let selections = self.selections.all_adjusted(&self.display_snapshot(cx));
4192 let mut bracket_inserted = false;
4193 let mut edits = Vec::new();
4194 let mut linked_edits = HashMap::<_, Vec<_>>::default();
4195 let mut new_selections = Vec::with_capacity(selections.len());
4196 let mut new_autoclose_regions = Vec::new();
4197 let snapshot = self.buffer.read(cx).read(cx);
4198 let mut clear_linked_edit_ranges = false;
4199
4200 for (selection, autoclose_region) in
4201 self.selections_with_autoclose_regions(selections, &snapshot)
4202 {
4203 if let Some(scope) = snapshot.language_scope_at(selection.head()) {
4204 // Determine if the inserted text matches the opening or closing
4205 // bracket of any of this language's bracket pairs.
4206 let mut bracket_pair = None;
4207 let mut is_bracket_pair_start = false;
4208 let mut is_bracket_pair_end = false;
4209 if !text.is_empty() {
4210 let mut bracket_pair_matching_end = None;
4211 // `text` can be empty when a user is using IME (e.g. Chinese Wubi Simplified)
4212 // and they are removing the character that triggered IME popup.
4213 for (pair, enabled) in scope.brackets() {
4214 if !pair.close && !pair.surround {
4215 continue;
4216 }
4217
4218 if enabled && pair.start.ends_with(text.as_ref()) {
4219 let prefix_len = pair.start.len() - text.len();
4220 let preceding_text_matches_prefix = prefix_len == 0
4221 || (selection.start.column >= (prefix_len as u32)
4222 && snapshot.contains_str_at(
4223 Point::new(
4224 selection.start.row,
4225 selection.start.column - (prefix_len as u32),
4226 ),
4227 &pair.start[..prefix_len],
4228 ));
4229 if preceding_text_matches_prefix {
4230 bracket_pair = Some(pair.clone());
4231 is_bracket_pair_start = true;
4232 break;
4233 }
4234 }
4235 if pair.end.as_str() == text.as_ref() && bracket_pair_matching_end.is_none()
4236 {
4237 // take first bracket pair matching end, but don't break in case a later bracket
4238 // pair matches start
4239 bracket_pair_matching_end = Some(pair.clone());
4240 }
4241 }
4242 if let Some(end) = bracket_pair_matching_end
4243 && bracket_pair.is_none()
4244 {
4245 bracket_pair = Some(end);
4246 is_bracket_pair_end = true;
4247 }
4248 }
4249
4250 if let Some(bracket_pair) = bracket_pair {
4251 let snapshot_settings = snapshot.language_settings_at(selection.start, cx);
4252 let autoclose = self.use_autoclose && snapshot_settings.use_autoclose;
4253 let auto_surround =
4254 self.use_auto_surround && snapshot_settings.use_auto_surround;
4255 if selection.is_empty() {
4256 if is_bracket_pair_start {
4257 // If the inserted text is a suffix of an opening bracket and the
4258 // selection is preceded by the rest of the opening bracket, then
4259 // insert the closing bracket.
4260 let following_text_allows_autoclose = snapshot
4261 .chars_at(selection.start)
4262 .next()
4263 .is_none_or(|c| scope.should_autoclose_before(c));
4264
4265 let preceding_text_allows_autoclose = selection.start.column == 0
4266 || snapshot
4267 .reversed_chars_at(selection.start)
4268 .next()
4269 .is_none_or(|c| {
4270 bracket_pair.start != bracket_pair.end
4271 || !snapshot
4272 .char_classifier_at(selection.start)
4273 .is_word(c)
4274 });
4275
4276 let is_closing_quote = if bracket_pair.end == bracket_pair.start
4277 && bracket_pair.start.len() == 1
4278 {
4279 let target = bracket_pair.start.chars().next().unwrap();
4280 let current_line_count = snapshot
4281 .reversed_chars_at(selection.start)
4282 .take_while(|&c| c != '\n')
4283 .filter(|&c| c == target)
4284 .count();
4285 current_line_count % 2 == 1
4286 } else {
4287 false
4288 };
4289
4290 if autoclose
4291 && bracket_pair.close
4292 && following_text_allows_autoclose
4293 && preceding_text_allows_autoclose
4294 && !is_closing_quote
4295 {
4296 let anchor = snapshot.anchor_before(selection.end);
4297 new_selections.push((selection.map(|_| anchor), text.len()));
4298 new_autoclose_regions.push((
4299 anchor,
4300 text.len(),
4301 selection.id,
4302 bracket_pair.clone(),
4303 ));
4304 edits.push((
4305 selection.range(),
4306 format!("{}{}", text, bracket_pair.end).into(),
4307 ));
4308 bracket_inserted = true;
4309 continue;
4310 }
4311 }
4312
4313 if let Some(region) = autoclose_region {
4314 // If the selection is followed by an auto-inserted closing bracket,
4315 // then don't insert that closing bracket again; just move the selection
4316 // past the closing bracket.
4317 let should_skip = selection.end == region.range.end.to_point(&snapshot)
4318 && text.as_ref() == region.pair.end.as_str()
4319 && snapshot.contains_str_at(region.range.end, text.as_ref());
4320 if should_skip {
4321 let anchor = snapshot.anchor_after(selection.end);
4322 new_selections
4323 .push((selection.map(|_| anchor), region.pair.end.len()));
4324 continue;
4325 }
4326 }
4327
4328 let always_treat_brackets_as_autoclosed = snapshot
4329 .language_settings_at(selection.start, cx)
4330 .always_treat_brackets_as_autoclosed;
4331 if always_treat_brackets_as_autoclosed
4332 && is_bracket_pair_end
4333 && snapshot.contains_str_at(selection.end, text.as_ref())
4334 {
4335 // Otherwise, when `always_treat_brackets_as_autoclosed` is set to `true
4336 // and the inserted text is a closing bracket and the selection is followed
4337 // by the closing bracket then move the selection past the closing bracket.
4338 let anchor = snapshot.anchor_after(selection.end);
4339 new_selections.push((selection.map(|_| anchor), text.len()));
4340 continue;
4341 }
4342 }
4343 // If an opening bracket is 1 character long and is typed while
4344 // text is selected, then surround that text with the bracket pair.
4345 else if auto_surround
4346 && bracket_pair.surround
4347 && is_bracket_pair_start
4348 && bracket_pair.start.chars().count() == 1
4349 {
4350 edits.push((selection.start..selection.start, text.clone()));
4351 edits.push((
4352 selection.end..selection.end,
4353 bracket_pair.end.as_str().into(),
4354 ));
4355 bracket_inserted = true;
4356 new_selections.push((
4357 Selection {
4358 id: selection.id,
4359 start: snapshot.anchor_after(selection.start),
4360 end: snapshot.anchor_before(selection.end),
4361 reversed: selection.reversed,
4362 goal: selection.goal,
4363 },
4364 0,
4365 ));
4366 continue;
4367 }
4368 }
4369 }
4370
4371 if self.auto_replace_emoji_shortcode
4372 && selection.is_empty()
4373 && text.as_ref().ends_with(':')
4374 && let Some(possible_emoji_short_code) =
4375 Self::find_possible_emoji_shortcode_at_position(&snapshot, selection.start)
4376 && !possible_emoji_short_code.is_empty()
4377 && let Some(emoji) = emojis::get_by_shortcode(&possible_emoji_short_code)
4378 {
4379 let emoji_shortcode_start = Point::new(
4380 selection.start.row,
4381 selection.start.column - possible_emoji_short_code.len() as u32 - 1,
4382 );
4383
4384 // Remove shortcode from buffer
4385 edits.push((
4386 emoji_shortcode_start..selection.start,
4387 "".to_string().into(),
4388 ));
4389 new_selections.push((
4390 Selection {
4391 id: selection.id,
4392 start: snapshot.anchor_after(emoji_shortcode_start),
4393 end: snapshot.anchor_before(selection.start),
4394 reversed: selection.reversed,
4395 goal: selection.goal,
4396 },
4397 0,
4398 ));
4399
4400 // Insert emoji
4401 let selection_start_anchor = snapshot.anchor_after(selection.start);
4402 new_selections.push((selection.map(|_| selection_start_anchor), 0));
4403 edits.push((selection.start..selection.end, emoji.to_string().into()));
4404
4405 continue;
4406 }
4407
4408 // If not handling any auto-close operation, then just replace the selected
4409 // text with the given input and move the selection to the end of the
4410 // newly inserted text.
4411 let anchor = snapshot.anchor_after(selection.end);
4412 if !self.linked_edit_ranges.is_empty() {
4413 let start_anchor = snapshot.anchor_before(selection.start);
4414
4415 let is_word_char = text.chars().next().is_none_or(|char| {
4416 let classifier = snapshot
4417 .char_classifier_at(start_anchor.to_offset(&snapshot))
4418 .scope_context(Some(CharScopeContext::LinkedEdit));
4419 classifier.is_word(char)
4420 });
4421
4422 if is_word_char {
4423 if let Some(ranges) = self
4424 .linked_editing_ranges_for(start_anchor.text_anchor..anchor.text_anchor, cx)
4425 {
4426 for (buffer, edits) in ranges {
4427 linked_edits
4428 .entry(buffer.clone())
4429 .or_default()
4430 .extend(edits.into_iter().map(|range| (range, text.clone())));
4431 }
4432 }
4433 } else {
4434 clear_linked_edit_ranges = true;
4435 }
4436 }
4437
4438 new_selections.push((selection.map(|_| anchor), 0));
4439 edits.push((selection.start..selection.end, text.clone()));
4440 }
4441
4442 drop(snapshot);
4443
4444 self.transact(window, cx, |this, window, cx| {
4445 if clear_linked_edit_ranges {
4446 this.linked_edit_ranges.clear();
4447 }
4448 let initial_buffer_versions =
4449 jsx_tag_auto_close::construct_initial_buffer_versions_map(this, &edits, cx);
4450
4451 this.buffer.update(cx, |buffer, cx| {
4452 buffer.edit(edits, this.autoindent_mode.clone(), cx);
4453 });
4454 for (buffer, edits) in linked_edits {
4455 buffer.update(cx, |buffer, cx| {
4456 let snapshot = buffer.snapshot();
4457 let edits = edits
4458 .into_iter()
4459 .map(|(range, text)| {
4460 use text::ToPoint as TP;
4461 let end_point = TP::to_point(&range.end, &snapshot);
4462 let start_point = TP::to_point(&range.start, &snapshot);
4463 (start_point..end_point, text)
4464 })
4465 .sorted_by_key(|(range, _)| range.start);
4466 buffer.edit(edits, None, cx);
4467 })
4468 }
4469 let new_anchor_selections = new_selections.iter().map(|e| &e.0);
4470 let new_selection_deltas = new_selections.iter().map(|e| e.1);
4471 let map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
4472 let new_selections =
4473 resolve_selections_wrapping_blocks::<usize, _>(new_anchor_selections, &map)
4474 .zip(new_selection_deltas)
4475 .map(|(selection, delta)| Selection {
4476 id: selection.id,
4477 start: selection.start + delta,
4478 end: selection.end + delta,
4479 reversed: selection.reversed,
4480 goal: SelectionGoal::None,
4481 })
4482 .collect::<Vec<_>>();
4483
4484 let mut i = 0;
4485 for (position, delta, selection_id, pair) in new_autoclose_regions {
4486 let position = position.to_offset(map.buffer_snapshot()) + delta;
4487 let start = map.buffer_snapshot().anchor_before(position);
4488 let end = map.buffer_snapshot().anchor_after(position);
4489 while let Some(existing_state) = this.autoclose_regions.get(i) {
4490 match existing_state
4491 .range
4492 .start
4493 .cmp(&start, map.buffer_snapshot())
4494 {
4495 Ordering::Less => i += 1,
4496 Ordering::Greater => break,
4497 Ordering::Equal => {
4498 match end.cmp(&existing_state.range.end, map.buffer_snapshot()) {
4499 Ordering::Less => i += 1,
4500 Ordering::Equal => break,
4501 Ordering::Greater => break,
4502 }
4503 }
4504 }
4505 }
4506 this.autoclose_regions.insert(
4507 i,
4508 AutocloseRegion {
4509 selection_id,
4510 range: start..end,
4511 pair,
4512 },
4513 );
4514 }
4515
4516 let had_active_edit_prediction = this.has_active_edit_prediction();
4517 this.change_selections(
4518 SelectionEffects::scroll(Autoscroll::fit()).completions(false),
4519 window,
4520 cx,
4521 |s| s.select(new_selections),
4522 );
4523
4524 if !bracket_inserted
4525 && let Some(on_type_format_task) =
4526 this.trigger_on_type_formatting(text.to_string(), window, cx)
4527 {
4528 on_type_format_task.detach_and_log_err(cx);
4529 }
4530
4531 let editor_settings = EditorSettings::get_global(cx);
4532 if bracket_inserted
4533 && (editor_settings.auto_signature_help
4534 || editor_settings.show_signature_help_after_edits)
4535 {
4536 this.show_signature_help(&ShowSignatureHelp, window, cx);
4537 }
4538
4539 let trigger_in_words =
4540 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
4541 if this.hard_wrap.is_some() {
4542 let latest: Range<Point> = this.selections.newest(&map).range();
4543 if latest.is_empty()
4544 && this
4545 .buffer()
4546 .read(cx)
4547 .snapshot(cx)
4548 .line_len(MultiBufferRow(latest.start.row))
4549 == latest.start.column
4550 {
4551 this.rewrap_impl(
4552 RewrapOptions {
4553 override_language_settings: true,
4554 preserve_existing_whitespace: true,
4555 },
4556 cx,
4557 )
4558 }
4559 }
4560 this.trigger_completion_on_input(&text, trigger_in_words, window, cx);
4561 refresh_linked_ranges(this, window, cx);
4562 this.refresh_edit_prediction(true, false, window, cx);
4563 jsx_tag_auto_close::handle_from(this, initial_buffer_versions, window, cx);
4564 });
4565 }
4566
4567 fn find_possible_emoji_shortcode_at_position(
4568 snapshot: &MultiBufferSnapshot,
4569 position: Point,
4570 ) -> Option<String> {
4571 let mut chars = Vec::new();
4572 let mut found_colon = false;
4573 for char in snapshot.reversed_chars_at(position).take(100) {
4574 // Found a possible emoji shortcode in the middle of the buffer
4575 if found_colon {
4576 if char.is_whitespace() {
4577 chars.reverse();
4578 return Some(chars.iter().collect());
4579 }
4580 // If the previous character is not a whitespace, we are in the middle of a word
4581 // and we only want to complete the shortcode if the word is made up of other emojis
4582 let mut containing_word = String::new();
4583 for ch in snapshot
4584 .reversed_chars_at(position)
4585 .skip(chars.len() + 1)
4586 .take(100)
4587 {
4588 if ch.is_whitespace() {
4589 break;
4590 }
4591 containing_word.push(ch);
4592 }
4593 let containing_word = containing_word.chars().rev().collect::<String>();
4594 if util::word_consists_of_emojis(containing_word.as_str()) {
4595 chars.reverse();
4596 return Some(chars.iter().collect());
4597 }
4598 }
4599
4600 if char.is_whitespace() || !char.is_ascii() {
4601 return None;
4602 }
4603 if char == ':' {
4604 found_colon = true;
4605 } else {
4606 chars.push(char);
4607 }
4608 }
4609 // Found a possible emoji shortcode at the beginning of the buffer
4610 chars.reverse();
4611 Some(chars.iter().collect())
4612 }
4613
4614 pub fn newline(&mut self, _: &Newline, window: &mut Window, cx: &mut Context<Self>) {
4615 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4616 self.transact(window, cx, |this, window, cx| {
4617 let (edits_with_flags, selection_info): (Vec<_>, Vec<_>) = {
4618 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
4619 let multi_buffer = this.buffer.read(cx);
4620 let buffer = multi_buffer.snapshot(cx);
4621 selections
4622 .iter()
4623 .map(|selection| {
4624 let start_point = selection.start.to_point(&buffer);
4625 let mut existing_indent =
4626 buffer.indent_size_for_line(MultiBufferRow(start_point.row));
4627 existing_indent.len = cmp::min(existing_indent.len, start_point.column);
4628 let start = selection.start;
4629 let end = selection.end;
4630 let selection_is_empty = start == end;
4631 let language_scope = buffer.language_scope_at(start);
4632 let (
4633 comment_delimiter,
4634 doc_delimiter,
4635 insert_extra_newline,
4636 indent_on_newline,
4637 indent_on_extra_newline,
4638 ) = if let Some(language) = &language_scope {
4639 let mut insert_extra_newline =
4640 insert_extra_newline_brackets(&buffer, start..end, language)
4641 || insert_extra_newline_tree_sitter(&buffer, start..end);
4642
4643 // Comment extension on newline is allowed only for cursor selections
4644 let comment_delimiter = maybe!({
4645 if !selection_is_empty {
4646 return None;
4647 }
4648
4649 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4650 return None;
4651 }
4652
4653 let delimiters = language.line_comment_prefixes();
4654 let max_len_of_delimiter =
4655 delimiters.iter().map(|delimiter| delimiter.len()).max()?;
4656 let (snapshot, range) =
4657 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4658
4659 let num_of_whitespaces = snapshot
4660 .chars_for_range(range.clone())
4661 .take_while(|c| c.is_whitespace())
4662 .count();
4663 let comment_candidate = snapshot
4664 .chars_for_range(range.clone())
4665 .skip(num_of_whitespaces)
4666 .take(max_len_of_delimiter)
4667 .collect::<String>();
4668 let (delimiter, trimmed_len) = delimiters
4669 .iter()
4670 .filter_map(|delimiter| {
4671 let prefix = delimiter.trim_end();
4672 if comment_candidate.starts_with(prefix) {
4673 Some((delimiter, prefix.len()))
4674 } else {
4675 None
4676 }
4677 })
4678 .max_by_key(|(_, len)| *len)?;
4679
4680 if let Some(BlockCommentConfig {
4681 start: block_start, ..
4682 }) = language.block_comment()
4683 {
4684 let block_start_trimmed = block_start.trim_end();
4685 if block_start_trimmed.starts_with(delimiter.trim_end()) {
4686 let line_content = snapshot
4687 .chars_for_range(range)
4688 .skip(num_of_whitespaces)
4689 .take(block_start_trimmed.len())
4690 .collect::<String>();
4691
4692 if line_content.starts_with(block_start_trimmed) {
4693 return None;
4694 }
4695 }
4696 }
4697
4698 let cursor_is_placed_after_comment_marker =
4699 num_of_whitespaces + trimmed_len <= start_point.column as usize;
4700 if cursor_is_placed_after_comment_marker {
4701 Some(delimiter.clone())
4702 } else {
4703 None
4704 }
4705 });
4706
4707 let mut indent_on_newline = IndentSize::spaces(0);
4708 let mut indent_on_extra_newline = IndentSize::spaces(0);
4709
4710 let doc_delimiter = maybe!({
4711 if !selection_is_empty {
4712 return None;
4713 }
4714
4715 if !multi_buffer.language_settings(cx).extend_comment_on_newline {
4716 return None;
4717 }
4718
4719 let BlockCommentConfig {
4720 start: start_tag,
4721 end: end_tag,
4722 prefix: delimiter,
4723 tab_size: len,
4724 } = language.documentation_comment()?;
4725 let is_within_block_comment = buffer
4726 .language_scope_at(start_point)
4727 .is_some_and(|scope| scope.override_name() == Some("comment"));
4728 if !is_within_block_comment {
4729 return None;
4730 }
4731
4732 let (snapshot, range) =
4733 buffer.buffer_line_for_row(MultiBufferRow(start_point.row))?;
4734
4735 let num_of_whitespaces = snapshot
4736 .chars_for_range(range.clone())
4737 .take_while(|c| c.is_whitespace())
4738 .count();
4739
4740 // 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.
4741 let column = start_point.column;
4742 let cursor_is_after_start_tag = {
4743 let start_tag_len = start_tag.len();
4744 let start_tag_line = snapshot
4745 .chars_for_range(range.clone())
4746 .skip(num_of_whitespaces)
4747 .take(start_tag_len)
4748 .collect::<String>();
4749 if start_tag_line.starts_with(start_tag.as_ref()) {
4750 num_of_whitespaces + start_tag_len <= column as usize
4751 } else {
4752 false
4753 }
4754 };
4755
4756 let cursor_is_after_delimiter = {
4757 let delimiter_trim = delimiter.trim_end();
4758 let delimiter_line = snapshot
4759 .chars_for_range(range.clone())
4760 .skip(num_of_whitespaces)
4761 .take(delimiter_trim.len())
4762 .collect::<String>();
4763 if delimiter_line.starts_with(delimiter_trim) {
4764 num_of_whitespaces + delimiter_trim.len() <= column as usize
4765 } else {
4766 false
4767 }
4768 };
4769
4770 let cursor_is_before_end_tag_if_exists = {
4771 let mut char_position = 0u32;
4772 let mut end_tag_offset = None;
4773
4774 'outer: for chunk in snapshot.text_for_range(range) {
4775 if let Some(byte_pos) = chunk.find(&**end_tag) {
4776 let chars_before_match =
4777 chunk[..byte_pos].chars().count() as u32;
4778 end_tag_offset =
4779 Some(char_position + chars_before_match);
4780 break 'outer;
4781 }
4782 char_position += chunk.chars().count() as u32;
4783 }
4784
4785 if let Some(end_tag_offset) = end_tag_offset {
4786 let cursor_is_before_end_tag = column <= end_tag_offset;
4787 if cursor_is_after_start_tag {
4788 if cursor_is_before_end_tag {
4789 insert_extra_newline = true;
4790 }
4791 let cursor_is_at_start_of_end_tag =
4792 column == end_tag_offset;
4793 if cursor_is_at_start_of_end_tag {
4794 indent_on_extra_newline.len = *len;
4795 }
4796 }
4797 cursor_is_before_end_tag
4798 } else {
4799 true
4800 }
4801 };
4802
4803 if (cursor_is_after_start_tag || cursor_is_after_delimiter)
4804 && cursor_is_before_end_tag_if_exists
4805 {
4806 if cursor_is_after_start_tag {
4807 indent_on_newline.len = *len;
4808 }
4809 Some(delimiter.clone())
4810 } else {
4811 None
4812 }
4813 });
4814
4815 (
4816 comment_delimiter,
4817 doc_delimiter,
4818 insert_extra_newline,
4819 indent_on_newline,
4820 indent_on_extra_newline,
4821 )
4822 } else {
4823 (
4824 None,
4825 None,
4826 false,
4827 IndentSize::default(),
4828 IndentSize::default(),
4829 )
4830 };
4831
4832 let prevent_auto_indent = doc_delimiter.is_some();
4833 let delimiter = comment_delimiter.or(doc_delimiter);
4834
4835 let capacity_for_delimiter =
4836 delimiter.as_deref().map(str::len).unwrap_or_default();
4837 let mut new_text = String::with_capacity(
4838 1 + capacity_for_delimiter
4839 + existing_indent.len as usize
4840 + indent_on_newline.len as usize
4841 + indent_on_extra_newline.len as usize,
4842 );
4843 new_text.push('\n');
4844 new_text.extend(existing_indent.chars());
4845 new_text.extend(indent_on_newline.chars());
4846
4847 if let Some(delimiter) = &delimiter {
4848 new_text.push_str(delimiter);
4849 }
4850
4851 if insert_extra_newline {
4852 new_text.push('\n');
4853 new_text.extend(existing_indent.chars());
4854 new_text.extend(indent_on_extra_newline.chars());
4855 }
4856
4857 let anchor = buffer.anchor_after(end);
4858 let new_selection = selection.map(|_| anchor);
4859 (
4860 ((start..end, new_text), prevent_auto_indent),
4861 (insert_extra_newline, new_selection),
4862 )
4863 })
4864 .unzip()
4865 };
4866
4867 let mut auto_indent_edits = Vec::new();
4868 let mut edits = Vec::new();
4869 for (edit, prevent_auto_indent) in edits_with_flags {
4870 if prevent_auto_indent {
4871 edits.push(edit);
4872 } else {
4873 auto_indent_edits.push(edit);
4874 }
4875 }
4876 if !edits.is_empty() {
4877 this.edit(edits, cx);
4878 }
4879 if !auto_indent_edits.is_empty() {
4880 this.edit_with_autoindent(auto_indent_edits, cx);
4881 }
4882
4883 let buffer = this.buffer.read(cx).snapshot(cx);
4884 let new_selections = selection_info
4885 .into_iter()
4886 .map(|(extra_newline_inserted, new_selection)| {
4887 let mut cursor = new_selection.end.to_point(&buffer);
4888 if extra_newline_inserted {
4889 cursor.row -= 1;
4890 cursor.column = buffer.line_len(MultiBufferRow(cursor.row));
4891 }
4892 new_selection.map(|_| cursor)
4893 })
4894 .collect();
4895
4896 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
4897 this.refresh_edit_prediction(true, false, window, cx);
4898 });
4899 }
4900
4901 pub fn newline_above(&mut self, _: &NewlineAbove, window: &mut Window, cx: &mut Context<Self>) {
4902 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4903
4904 let buffer = self.buffer.read(cx);
4905 let snapshot = buffer.snapshot(cx);
4906
4907 let mut edits = Vec::new();
4908 let mut rows = Vec::new();
4909
4910 for (rows_inserted, selection) in self
4911 .selections
4912 .all_adjusted(&self.display_snapshot(cx))
4913 .into_iter()
4914 .enumerate()
4915 {
4916 let cursor = selection.head();
4917 let row = cursor.row;
4918
4919 let start_of_line = snapshot.clip_point(Point::new(row, 0), Bias::Left);
4920
4921 let newline = "\n".to_string();
4922 edits.push((start_of_line..start_of_line, newline));
4923
4924 rows.push(row + rows_inserted as u32);
4925 }
4926
4927 self.transact(window, cx, |editor, window, cx| {
4928 editor.edit(edits, cx);
4929
4930 editor.change_selections(Default::default(), window, cx, |s| {
4931 let mut index = 0;
4932 s.move_cursors_with(|map, _, _| {
4933 let row = rows[index];
4934 index += 1;
4935
4936 let point = Point::new(row, 0);
4937 let boundary = map.next_line_boundary(point).1;
4938 let clipped = map.clip_point(boundary, Bias::Left);
4939
4940 (clipped, SelectionGoal::None)
4941 });
4942 });
4943
4944 let mut indent_edits = Vec::new();
4945 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
4946 for row in rows {
4947 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
4948 for (row, indent) in indents {
4949 if indent.len == 0 {
4950 continue;
4951 }
4952
4953 let text = match indent.kind {
4954 IndentKind::Space => " ".repeat(indent.len as usize),
4955 IndentKind::Tab => "\t".repeat(indent.len as usize),
4956 };
4957 let point = Point::new(row.0, 0);
4958 indent_edits.push((point..point, text));
4959 }
4960 }
4961 editor.edit(indent_edits, cx);
4962 });
4963 }
4964
4965 pub fn newline_below(&mut self, _: &NewlineBelow, window: &mut Window, cx: &mut Context<Self>) {
4966 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
4967
4968 let buffer = self.buffer.read(cx);
4969 let snapshot = buffer.snapshot(cx);
4970
4971 let mut edits = Vec::new();
4972 let mut rows = Vec::new();
4973 let mut rows_inserted = 0;
4974
4975 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
4976 let cursor = selection.head();
4977 let row = cursor.row;
4978
4979 let point = Point::new(row + 1, 0);
4980 let start_of_line = snapshot.clip_point(point, Bias::Left);
4981
4982 let newline = "\n".to_string();
4983 edits.push((start_of_line..start_of_line, newline));
4984
4985 rows_inserted += 1;
4986 rows.push(row + rows_inserted);
4987 }
4988
4989 self.transact(window, cx, |editor, window, cx| {
4990 editor.edit(edits, cx);
4991
4992 editor.change_selections(Default::default(), window, cx, |s| {
4993 let mut index = 0;
4994 s.move_cursors_with(|map, _, _| {
4995 let row = rows[index];
4996 index += 1;
4997
4998 let point = Point::new(row, 0);
4999 let boundary = map.next_line_boundary(point).1;
5000 let clipped = map.clip_point(boundary, Bias::Left);
5001
5002 (clipped, SelectionGoal::None)
5003 });
5004 });
5005
5006 let mut indent_edits = Vec::new();
5007 let multibuffer_snapshot = editor.buffer.read(cx).snapshot(cx);
5008 for row in rows {
5009 let indents = multibuffer_snapshot.suggested_indents(row..row + 1, cx);
5010 for (row, indent) in indents {
5011 if indent.len == 0 {
5012 continue;
5013 }
5014
5015 let text = match indent.kind {
5016 IndentKind::Space => " ".repeat(indent.len as usize),
5017 IndentKind::Tab => "\t".repeat(indent.len as usize),
5018 };
5019 let point = Point::new(row.0, 0);
5020 indent_edits.push((point..point, text));
5021 }
5022 }
5023 editor.edit(indent_edits, cx);
5024 });
5025 }
5026
5027 pub fn insert(&mut self, text: &str, window: &mut Window, cx: &mut Context<Self>) {
5028 let autoindent = text.is_empty().not().then(|| AutoindentMode::Block {
5029 original_indent_columns: Vec::new(),
5030 });
5031 self.insert_with_autoindent_mode(text, autoindent, window, cx);
5032 }
5033
5034 fn insert_with_autoindent_mode(
5035 &mut self,
5036 text: &str,
5037 autoindent_mode: Option<AutoindentMode>,
5038 window: &mut Window,
5039 cx: &mut Context<Self>,
5040 ) {
5041 if self.read_only(cx) {
5042 return;
5043 }
5044
5045 let text: Arc<str> = text.into();
5046 self.transact(window, cx, |this, window, cx| {
5047 let old_selections = this.selections.all_adjusted(&this.display_snapshot(cx));
5048 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
5049 let anchors = {
5050 let snapshot = buffer.read(cx);
5051 old_selections
5052 .iter()
5053 .map(|s| {
5054 let anchor = snapshot.anchor_after(s.head());
5055 s.map(|_| anchor)
5056 })
5057 .collect::<Vec<_>>()
5058 };
5059 buffer.edit(
5060 old_selections
5061 .iter()
5062 .map(|s| (s.start..s.end, text.clone())),
5063 autoindent_mode,
5064 cx,
5065 );
5066 anchors
5067 });
5068
5069 this.change_selections(Default::default(), window, cx, |s| {
5070 s.select_anchors(selection_anchors);
5071 });
5072
5073 cx.notify();
5074 });
5075 }
5076
5077 fn trigger_completion_on_input(
5078 &mut self,
5079 text: &str,
5080 trigger_in_words: bool,
5081 window: &mut Window,
5082 cx: &mut Context<Self>,
5083 ) {
5084 let completions_source = self
5085 .context_menu
5086 .borrow()
5087 .as_ref()
5088 .and_then(|menu| match menu {
5089 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5090 CodeContextMenu::CodeActions(_) => None,
5091 });
5092
5093 match completions_source {
5094 Some(CompletionsMenuSource::Words { .. }) => {
5095 self.open_or_update_completions_menu(
5096 Some(CompletionsMenuSource::Words {
5097 ignore_threshold: false,
5098 }),
5099 None,
5100 trigger_in_words,
5101 window,
5102 cx,
5103 );
5104 }
5105 _ => self.open_or_update_completions_menu(
5106 None,
5107 Some(text.to_owned()).filter(|x| !x.is_empty()),
5108 true,
5109 window,
5110 cx,
5111 ),
5112 }
5113 }
5114
5115 /// If any empty selections is touching the start of its innermost containing autoclose
5116 /// region, expand it to select the brackets.
5117 fn select_autoclose_pair(&mut self, window: &mut Window, cx: &mut Context<Self>) {
5118 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5119 let buffer = self.buffer.read(cx).read(cx);
5120 let new_selections = self
5121 .selections_with_autoclose_regions(selections, &buffer)
5122 .map(|(mut selection, region)| {
5123 if !selection.is_empty() {
5124 return selection;
5125 }
5126
5127 if let Some(region) = region {
5128 let mut range = region.range.to_offset(&buffer);
5129 if selection.start == range.start && range.start >= region.pair.start.len() {
5130 range.start -= region.pair.start.len();
5131 if buffer.contains_str_at(range.start, ®ion.pair.start)
5132 && buffer.contains_str_at(range.end, ®ion.pair.end)
5133 {
5134 range.end += region.pair.end.len();
5135 selection.start = range.start;
5136 selection.end = range.end;
5137
5138 return selection;
5139 }
5140 }
5141 }
5142
5143 let always_treat_brackets_as_autoclosed = buffer
5144 .language_settings_at(selection.start, cx)
5145 .always_treat_brackets_as_autoclosed;
5146
5147 if !always_treat_brackets_as_autoclosed {
5148 return selection;
5149 }
5150
5151 if let Some(scope) = buffer.language_scope_at(selection.start) {
5152 for (pair, enabled) in scope.brackets() {
5153 if !enabled || !pair.close {
5154 continue;
5155 }
5156
5157 if buffer.contains_str_at(selection.start, &pair.end) {
5158 let pair_start_len = pair.start.len();
5159 if buffer.contains_str_at(
5160 selection.start.saturating_sub(pair_start_len),
5161 &pair.start,
5162 ) {
5163 selection.start -= pair_start_len;
5164 selection.end += pair.end.len();
5165
5166 return selection;
5167 }
5168 }
5169 }
5170 }
5171
5172 selection
5173 })
5174 .collect();
5175
5176 drop(buffer);
5177 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
5178 selections.select(new_selections)
5179 });
5180 }
5181
5182 /// Iterate the given selections, and for each one, find the smallest surrounding
5183 /// autoclose region. This uses the ordering of the selections and the autoclose
5184 /// regions to avoid repeated comparisons.
5185 fn selections_with_autoclose_regions<'a, D: ToOffset + Clone>(
5186 &'a self,
5187 selections: impl IntoIterator<Item = Selection<D>>,
5188 buffer: &'a MultiBufferSnapshot,
5189 ) -> impl Iterator<Item = (Selection<D>, Option<&'a AutocloseRegion>)> {
5190 let mut i = 0;
5191 let mut regions = self.autoclose_regions.as_slice();
5192 selections.into_iter().map(move |selection| {
5193 let range = selection.start.to_offset(buffer)..selection.end.to_offset(buffer);
5194
5195 let mut enclosing = None;
5196 while let Some(pair_state) = regions.get(i) {
5197 if pair_state.range.end.to_offset(buffer) < range.start {
5198 regions = ®ions[i + 1..];
5199 i = 0;
5200 } else if pair_state.range.start.to_offset(buffer) > range.end {
5201 break;
5202 } else {
5203 if pair_state.selection_id == selection.id {
5204 enclosing = Some(pair_state);
5205 }
5206 i += 1;
5207 }
5208 }
5209
5210 (selection, enclosing)
5211 })
5212 }
5213
5214 /// Remove any autoclose regions that no longer contain their selection or have invalid anchors in ranges.
5215 fn invalidate_autoclose_regions(
5216 &mut self,
5217 mut selections: &[Selection<Anchor>],
5218 buffer: &MultiBufferSnapshot,
5219 ) {
5220 self.autoclose_regions.retain(|state| {
5221 if !state.range.start.is_valid(buffer) || !state.range.end.is_valid(buffer) {
5222 return false;
5223 }
5224
5225 let mut i = 0;
5226 while let Some(selection) = selections.get(i) {
5227 if selection.end.cmp(&state.range.start, buffer).is_lt() {
5228 selections = &selections[1..];
5229 continue;
5230 }
5231 if selection.start.cmp(&state.range.end, buffer).is_gt() {
5232 break;
5233 }
5234 if selection.id == state.selection_id {
5235 return true;
5236 } else {
5237 i += 1;
5238 }
5239 }
5240 false
5241 });
5242 }
5243
5244 fn completion_query(buffer: &MultiBufferSnapshot, position: impl ToOffset) -> Option<String> {
5245 let offset = position.to_offset(buffer);
5246 let (word_range, kind) =
5247 buffer.surrounding_word(offset, Some(CharScopeContext::Completion));
5248 if offset > word_range.start && kind == Some(CharKind::Word) {
5249 Some(
5250 buffer
5251 .text_for_range(word_range.start..offset)
5252 .collect::<String>(),
5253 )
5254 } else {
5255 None
5256 }
5257 }
5258
5259 pub fn visible_excerpts(
5260 &self,
5261 cx: &mut Context<Editor>,
5262 ) -> HashMap<ExcerptId, (Entity<Buffer>, clock::Global, Range<usize>)> {
5263 let Some(project) = self.project() else {
5264 return HashMap::default();
5265 };
5266 let project = project.read(cx);
5267 let multi_buffer = self.buffer().read(cx);
5268 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
5269 let multi_buffer_visible_start = self
5270 .scroll_manager
5271 .anchor()
5272 .anchor
5273 .to_point(&multi_buffer_snapshot);
5274 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
5275 multi_buffer_visible_start
5276 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
5277 Bias::Left,
5278 );
5279 multi_buffer_snapshot
5280 .range_to_buffer_ranges(multi_buffer_visible_start..multi_buffer_visible_end)
5281 .into_iter()
5282 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty())
5283 .filter_map(|(buffer, excerpt_visible_range, excerpt_id)| {
5284 let buffer_file = project::File::from_dyn(buffer.file())?;
5285 let buffer_worktree = project.worktree_for_id(buffer_file.worktree_id(cx), cx)?;
5286 let worktree_entry = buffer_worktree
5287 .read(cx)
5288 .entry_for_id(buffer_file.project_entry_id()?)?;
5289 if worktree_entry.is_ignored {
5290 None
5291 } else {
5292 Some((
5293 excerpt_id,
5294 (
5295 multi_buffer.buffer(buffer.remote_id()).unwrap(),
5296 buffer.version().clone(),
5297 excerpt_visible_range,
5298 ),
5299 ))
5300 }
5301 })
5302 .collect()
5303 }
5304
5305 pub fn text_layout_details(&self, window: &mut Window) -> TextLayoutDetails {
5306 TextLayoutDetails {
5307 text_system: window.text_system().clone(),
5308 editor_style: self.style.clone().unwrap(),
5309 rem_size: window.rem_size(),
5310 scroll_anchor: self.scroll_manager.anchor(),
5311 visible_rows: self.visible_line_count(),
5312 vertical_scroll_margin: self.scroll_manager.vertical_scroll_margin,
5313 }
5314 }
5315
5316 fn trigger_on_type_formatting(
5317 &self,
5318 input: String,
5319 window: &mut Window,
5320 cx: &mut Context<Self>,
5321 ) -> Option<Task<Result<()>>> {
5322 if input.len() != 1 {
5323 return None;
5324 }
5325
5326 let project = self.project()?;
5327 let position = self.selections.newest_anchor().head();
5328 let (buffer, buffer_position) = self
5329 .buffer
5330 .read(cx)
5331 .text_anchor_for_position(position, cx)?;
5332
5333 let settings = language_settings::language_settings(
5334 buffer
5335 .read(cx)
5336 .language_at(buffer_position)
5337 .map(|l| l.name()),
5338 buffer.read(cx).file(),
5339 cx,
5340 );
5341 if !settings.use_on_type_format {
5342 return None;
5343 }
5344
5345 // OnTypeFormatting returns a list of edits, no need to pass them between Zed instances,
5346 // hence we do LSP request & edit on host side only — add formats to host's history.
5347 let push_to_lsp_host_history = true;
5348 // If this is not the host, append its history with new edits.
5349 let push_to_client_history = project.read(cx).is_via_collab();
5350
5351 let on_type_formatting = project.update(cx, |project, cx| {
5352 project.on_type_format(
5353 buffer.clone(),
5354 buffer_position,
5355 input,
5356 push_to_lsp_host_history,
5357 cx,
5358 )
5359 });
5360 Some(cx.spawn_in(window, async move |editor, cx| {
5361 if let Some(transaction) = on_type_formatting.await? {
5362 if push_to_client_history {
5363 buffer
5364 .update(cx, |buffer, _| {
5365 buffer.push_transaction(transaction, Instant::now());
5366 buffer.finalize_last_transaction();
5367 })
5368 .ok();
5369 }
5370 editor.update(cx, |editor, cx| {
5371 editor.refresh_document_highlights(cx);
5372 })?;
5373 }
5374 Ok(())
5375 }))
5376 }
5377
5378 pub fn show_word_completions(
5379 &mut self,
5380 _: &ShowWordCompletions,
5381 window: &mut Window,
5382 cx: &mut Context<Self>,
5383 ) {
5384 self.open_or_update_completions_menu(
5385 Some(CompletionsMenuSource::Words {
5386 ignore_threshold: true,
5387 }),
5388 None,
5389 false,
5390 window,
5391 cx,
5392 );
5393 }
5394
5395 pub fn show_completions(
5396 &mut self,
5397 _: &ShowCompletions,
5398 window: &mut Window,
5399 cx: &mut Context<Self>,
5400 ) {
5401 self.open_or_update_completions_menu(None, None, false, window, cx);
5402 }
5403
5404 fn open_or_update_completions_menu(
5405 &mut self,
5406 requested_source: Option<CompletionsMenuSource>,
5407 trigger: Option<String>,
5408 trigger_in_words: bool,
5409 window: &mut Window,
5410 cx: &mut Context<Self>,
5411 ) {
5412 if self.pending_rename.is_some() {
5413 return;
5414 }
5415
5416 let completions_source = self
5417 .context_menu
5418 .borrow()
5419 .as_ref()
5420 .and_then(|menu| match menu {
5421 CodeContextMenu::Completions(completions_menu) => Some(completions_menu.source),
5422 CodeContextMenu::CodeActions(_) => None,
5423 });
5424
5425 let multibuffer_snapshot = self.buffer.read(cx).read(cx);
5426
5427 // Typically `start` == `end`, but with snippet tabstop choices the default choice is
5428 // inserted and selected. To handle that case, the start of the selection is used so that
5429 // the menu starts with all choices.
5430 let position = self
5431 .selections
5432 .newest_anchor()
5433 .start
5434 .bias_right(&multibuffer_snapshot);
5435 if position.diff_base_anchor.is_some() {
5436 return;
5437 }
5438 let buffer_position = multibuffer_snapshot.anchor_before(position);
5439 let Some(buffer) = buffer_position
5440 .buffer_id
5441 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
5442 else {
5443 return;
5444 };
5445 let buffer_snapshot = buffer.read(cx).snapshot();
5446
5447 let query: Option<Arc<String>> =
5448 Self::completion_query(&multibuffer_snapshot, buffer_position)
5449 .map(|query| query.into());
5450
5451 drop(multibuffer_snapshot);
5452
5453 // Hide the current completions menu when query is empty. Without this, cached
5454 // completions from before the trigger char may be reused (#32774).
5455 if query.is_none() {
5456 let menu_is_open = matches!(
5457 self.context_menu.borrow().as_ref(),
5458 Some(CodeContextMenu::Completions(_))
5459 );
5460 if menu_is_open {
5461 self.hide_context_menu(window, cx);
5462 }
5463 }
5464
5465 let mut ignore_word_threshold = false;
5466 let provider = match requested_source {
5467 Some(CompletionsMenuSource::Normal) | None => self.completion_provider.clone(),
5468 Some(CompletionsMenuSource::Words { ignore_threshold }) => {
5469 ignore_word_threshold = ignore_threshold;
5470 None
5471 }
5472 Some(CompletionsMenuSource::SnippetChoices)
5473 | Some(CompletionsMenuSource::SnippetsOnly) => {
5474 log::error!("bug: SnippetChoices requested_source is not handled");
5475 None
5476 }
5477 };
5478
5479 let sort_completions = provider
5480 .as_ref()
5481 .is_some_and(|provider| provider.sort_completions());
5482
5483 let filter_completions = provider
5484 .as_ref()
5485 .is_none_or(|provider| provider.filter_completions());
5486
5487 let was_snippets_only = matches!(
5488 completions_source,
5489 Some(CompletionsMenuSource::SnippetsOnly)
5490 );
5491
5492 if let Some(CodeContextMenu::Completions(menu)) = self.context_menu.borrow_mut().as_mut() {
5493 if filter_completions {
5494 menu.filter(query.clone(), provider.clone(), window, cx);
5495 }
5496 // When `is_incomplete` is false, no need to re-query completions when the current query
5497 // is a suffix of the initial query.
5498 let was_complete = !menu.is_incomplete;
5499 if was_complete && !was_snippets_only {
5500 // If the new query is a suffix of the old query (typing more characters) and
5501 // the previous result was complete, the existing completions can be filtered.
5502 //
5503 // Note that this is always true for snippet completions.
5504 let query_matches = match (&menu.initial_query, &query) {
5505 (Some(initial_query), Some(query)) => query.starts_with(initial_query.as_ref()),
5506 (None, _) => true,
5507 _ => false,
5508 };
5509 if query_matches {
5510 let position_matches = if menu.initial_position == position {
5511 true
5512 } else {
5513 let snapshot = self.buffer.read(cx).read(cx);
5514 menu.initial_position.to_offset(&snapshot) == position.to_offset(&snapshot)
5515 };
5516 if position_matches {
5517 return;
5518 }
5519 }
5520 }
5521 };
5522
5523 let Anchor {
5524 excerpt_id: buffer_excerpt_id,
5525 text_anchor: buffer_position,
5526 ..
5527 } = buffer_position;
5528
5529 let (word_replace_range, word_to_exclude) = if let (word_range, Some(CharKind::Word)) =
5530 buffer_snapshot.surrounding_word(buffer_position, None)
5531 {
5532 let word_to_exclude = buffer_snapshot
5533 .text_for_range(word_range.clone())
5534 .collect::<String>();
5535 (
5536 buffer_snapshot.anchor_before(word_range.start)
5537 ..buffer_snapshot.anchor_after(buffer_position),
5538 Some(word_to_exclude),
5539 )
5540 } else {
5541 (buffer_position..buffer_position, None)
5542 };
5543
5544 let language = buffer_snapshot
5545 .language_at(buffer_position)
5546 .map(|language| language.name());
5547
5548 let completion_settings = language_settings(language.clone(), buffer_snapshot.file(), cx)
5549 .completions
5550 .clone();
5551
5552 let show_completion_documentation = buffer_snapshot
5553 .settings_at(buffer_position, cx)
5554 .show_completion_documentation;
5555
5556 // The document can be large, so stay in reasonable bounds when searching for words,
5557 // otherwise completion pop-up might be slow to appear.
5558 const WORD_LOOKUP_ROWS: u32 = 5_000;
5559 let buffer_row = text::ToPoint::to_point(&buffer_position, &buffer_snapshot).row;
5560 let min_word_search = buffer_snapshot.clip_point(
5561 Point::new(buffer_row.saturating_sub(WORD_LOOKUP_ROWS), 0),
5562 Bias::Left,
5563 );
5564 let max_word_search = buffer_snapshot.clip_point(
5565 Point::new(buffer_row + WORD_LOOKUP_ROWS, 0).min(buffer_snapshot.max_point()),
5566 Bias::Right,
5567 );
5568 let word_search_range = buffer_snapshot.point_to_offset(min_word_search)
5569 ..buffer_snapshot.point_to_offset(max_word_search);
5570
5571 let skip_digits = query
5572 .as_ref()
5573 .is_none_or(|query| !query.chars().any(|c| c.is_digit(10)));
5574
5575 let load_provider_completions = provider.as_ref().is_some_and(|provider| {
5576 trigger.as_ref().is_none_or(|trigger| {
5577 provider.is_completion_trigger(
5578 &buffer,
5579 position.text_anchor,
5580 trigger,
5581 trigger_in_words,
5582 completions_source.is_some(),
5583 cx,
5584 )
5585 })
5586 });
5587
5588 let provider_responses = if let Some(provider) = &provider
5589 && load_provider_completions
5590 {
5591 let trigger_character =
5592 trigger.filter(|trigger| buffer.read(cx).completion_triggers().contains(trigger));
5593 let completion_context = CompletionContext {
5594 trigger_kind: match &trigger_character {
5595 Some(_) => CompletionTriggerKind::TRIGGER_CHARACTER,
5596 None => CompletionTriggerKind::INVOKED,
5597 },
5598 trigger_character,
5599 };
5600
5601 provider.completions(
5602 buffer_excerpt_id,
5603 &buffer,
5604 buffer_position,
5605 completion_context,
5606 window,
5607 cx,
5608 )
5609 } else {
5610 Task::ready(Ok(Vec::new()))
5611 };
5612
5613 let load_word_completions = if !self.word_completions_enabled {
5614 false
5615 } else if requested_source
5616 == Some(CompletionsMenuSource::Words {
5617 ignore_threshold: true,
5618 })
5619 {
5620 true
5621 } else {
5622 load_provider_completions
5623 && completion_settings.words != WordsCompletionMode::Disabled
5624 && (ignore_word_threshold || {
5625 let words_min_length = completion_settings.words_min_length;
5626 // check whether word has at least `words_min_length` characters
5627 let query_chars = query.iter().flat_map(|q| q.chars());
5628 query_chars.take(words_min_length).count() == words_min_length
5629 })
5630 };
5631
5632 let mut words = if load_word_completions {
5633 cx.background_spawn(async move {
5634 buffer_snapshot.words_in_range(WordsQuery {
5635 fuzzy_contents: None,
5636 range: word_search_range,
5637 skip_digits,
5638 })
5639 })
5640 } else {
5641 Task::ready(BTreeMap::default())
5642 };
5643
5644 let snippets = if let Some(provider) = &provider
5645 && provider.show_snippets()
5646 && let Some(project) = self.project()
5647 {
5648 project.update(cx, |project, cx| {
5649 snippet_completions(project, &buffer, buffer_position, cx)
5650 })
5651 } else {
5652 Task::ready(Ok(CompletionResponse {
5653 completions: Vec::new(),
5654 display_options: Default::default(),
5655 is_incomplete: false,
5656 }))
5657 };
5658
5659 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
5660
5661 let id = post_inc(&mut self.next_completion_id);
5662 let task = cx.spawn_in(window, async move |editor, cx| {
5663 let Ok(()) = editor.update(cx, |this, _| {
5664 this.completion_tasks.retain(|(task_id, _)| *task_id >= id);
5665 }) else {
5666 return;
5667 };
5668
5669 // TODO: Ideally completions from different sources would be selectively re-queried, so
5670 // that having one source with `is_incomplete: true` doesn't cause all to be re-queried.
5671 let mut completions = Vec::new();
5672 let mut is_incomplete = false;
5673 let mut display_options: Option<CompletionDisplayOptions> = None;
5674 if let Some(provider_responses) = provider_responses.await.log_err()
5675 && !provider_responses.is_empty()
5676 {
5677 for response in provider_responses {
5678 completions.extend(response.completions);
5679 is_incomplete = is_incomplete || response.is_incomplete;
5680 match display_options.as_mut() {
5681 None => {
5682 display_options = Some(response.display_options);
5683 }
5684 Some(options) => options.merge(&response.display_options),
5685 }
5686 }
5687 if completion_settings.words == WordsCompletionMode::Fallback {
5688 words = Task::ready(BTreeMap::default());
5689 }
5690 }
5691 let display_options = display_options.unwrap_or_default();
5692
5693 let mut words = words.await;
5694 if let Some(word_to_exclude) = &word_to_exclude {
5695 words.remove(word_to_exclude);
5696 }
5697 for lsp_completion in &completions {
5698 words.remove(&lsp_completion.new_text);
5699 }
5700 completions.extend(words.into_iter().map(|(word, word_range)| Completion {
5701 replace_range: word_replace_range.clone(),
5702 new_text: word.clone(),
5703 label: CodeLabel::plain(word, None),
5704 icon_path: None,
5705 documentation: None,
5706 source: CompletionSource::BufferWord {
5707 word_range,
5708 resolved: false,
5709 },
5710 insert_text_mode: Some(InsertTextMode::AS_IS),
5711 confirm: None,
5712 }));
5713
5714 completions.extend(
5715 snippets
5716 .await
5717 .into_iter()
5718 .flat_map(|response| response.completions),
5719 );
5720
5721 let menu = if completions.is_empty() {
5722 None
5723 } else {
5724 let Ok((mut menu, matches_task)) = editor.update(cx, |editor, cx| {
5725 let languages = editor
5726 .workspace
5727 .as_ref()
5728 .and_then(|(workspace, _)| workspace.upgrade())
5729 .map(|workspace| workspace.read(cx).app_state().languages.clone());
5730 let menu = CompletionsMenu::new(
5731 id,
5732 requested_source.unwrap_or(if load_provider_completions {
5733 CompletionsMenuSource::Normal
5734 } else {
5735 CompletionsMenuSource::SnippetsOnly
5736 }),
5737 sort_completions,
5738 show_completion_documentation,
5739 position,
5740 query.clone(),
5741 is_incomplete,
5742 buffer.clone(),
5743 completions.into(),
5744 display_options,
5745 snippet_sort_order,
5746 languages,
5747 language,
5748 cx,
5749 );
5750
5751 let query = if filter_completions { query } else { None };
5752 let matches_task = if let Some(query) = query {
5753 menu.do_async_filtering(query, cx)
5754 } else {
5755 Task::ready(menu.unfiltered_matches())
5756 };
5757 (menu, matches_task)
5758 }) else {
5759 return;
5760 };
5761
5762 let matches = matches_task.await;
5763
5764 let Ok(()) = editor.update_in(cx, |editor, window, cx| {
5765 // Newer menu already set, so exit.
5766 if let Some(CodeContextMenu::Completions(prev_menu)) =
5767 editor.context_menu.borrow().as_ref()
5768 && prev_menu.id > id
5769 {
5770 return;
5771 };
5772
5773 // Only valid to take prev_menu because it the new menu is immediately set
5774 // below, or the menu is hidden.
5775 if let Some(CodeContextMenu::Completions(prev_menu)) =
5776 editor.context_menu.borrow_mut().take()
5777 {
5778 let position_matches =
5779 if prev_menu.initial_position == menu.initial_position {
5780 true
5781 } else {
5782 let snapshot = editor.buffer.read(cx).read(cx);
5783 prev_menu.initial_position.to_offset(&snapshot)
5784 == menu.initial_position.to_offset(&snapshot)
5785 };
5786 if position_matches {
5787 // Preserve markdown cache before `set_filter_results` because it will
5788 // try to populate the documentation cache.
5789 menu.preserve_markdown_cache(prev_menu);
5790 }
5791 };
5792
5793 menu.set_filter_results(matches, provider, window, cx);
5794 }) else {
5795 return;
5796 };
5797
5798 menu.visible().then_some(menu)
5799 };
5800
5801 editor
5802 .update_in(cx, |editor, window, cx| {
5803 if editor.focus_handle.is_focused(window)
5804 && let Some(menu) = menu
5805 {
5806 *editor.context_menu.borrow_mut() =
5807 Some(CodeContextMenu::Completions(menu));
5808
5809 crate::hover_popover::hide_hover(editor, cx);
5810 if editor.show_edit_predictions_in_menu() {
5811 editor.update_visible_edit_prediction(window, cx);
5812 } else {
5813 editor.discard_edit_prediction(false, cx);
5814 }
5815
5816 cx.notify();
5817 return;
5818 }
5819
5820 if editor.completion_tasks.len() <= 1 {
5821 // If there are no more completion tasks and the last menu was empty, we should hide it.
5822 let was_hidden = editor.hide_context_menu(window, cx).is_none();
5823 // If it was already hidden and we don't show edit predictions in the menu,
5824 // we should also show the edit prediction when available.
5825 if was_hidden && editor.show_edit_predictions_in_menu() {
5826 editor.update_visible_edit_prediction(window, cx);
5827 }
5828 }
5829 })
5830 .ok();
5831 });
5832
5833 self.completion_tasks.push((id, task));
5834 }
5835
5836 #[cfg(feature = "test-support")]
5837 pub fn current_completions(&self) -> Option<Vec<project::Completion>> {
5838 let menu = self.context_menu.borrow();
5839 if let CodeContextMenu::Completions(menu) = menu.as_ref()? {
5840 let completions = menu.completions.borrow();
5841 Some(completions.to_vec())
5842 } else {
5843 None
5844 }
5845 }
5846
5847 pub fn with_completions_menu_matching_id<R>(
5848 &self,
5849 id: CompletionId,
5850 f: impl FnOnce(Option<&mut CompletionsMenu>) -> R,
5851 ) -> R {
5852 let mut context_menu = self.context_menu.borrow_mut();
5853 let Some(CodeContextMenu::Completions(completions_menu)) = &mut *context_menu else {
5854 return f(None);
5855 };
5856 if completions_menu.id != id {
5857 return f(None);
5858 }
5859 f(Some(completions_menu))
5860 }
5861
5862 pub fn confirm_completion(
5863 &mut self,
5864 action: &ConfirmCompletion,
5865 window: &mut Window,
5866 cx: &mut Context<Self>,
5867 ) -> Option<Task<Result<()>>> {
5868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5869 self.do_completion(action.item_ix, CompletionIntent::Complete, window, cx)
5870 }
5871
5872 pub fn confirm_completion_insert(
5873 &mut self,
5874 _: &ConfirmCompletionInsert,
5875 window: &mut Window,
5876 cx: &mut Context<Self>,
5877 ) -> Option<Task<Result<()>>> {
5878 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5879 self.do_completion(None, CompletionIntent::CompleteWithInsert, window, cx)
5880 }
5881
5882 pub fn confirm_completion_replace(
5883 &mut self,
5884 _: &ConfirmCompletionReplace,
5885 window: &mut Window,
5886 cx: &mut Context<Self>,
5887 ) -> Option<Task<Result<()>>> {
5888 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5889 self.do_completion(None, CompletionIntent::CompleteWithReplace, window, cx)
5890 }
5891
5892 pub fn compose_completion(
5893 &mut self,
5894 action: &ComposeCompletion,
5895 window: &mut Window,
5896 cx: &mut Context<Self>,
5897 ) -> Option<Task<Result<()>>> {
5898 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
5899 self.do_completion(action.item_ix, CompletionIntent::Compose, window, cx)
5900 }
5901
5902 fn do_completion(
5903 &mut self,
5904 item_ix: Option<usize>,
5905 intent: CompletionIntent,
5906 window: &mut Window,
5907 cx: &mut Context<Editor>,
5908 ) -> Option<Task<Result<()>>> {
5909 use language::ToOffset as _;
5910
5911 let CodeContextMenu::Completions(completions_menu) = self.hide_context_menu(window, cx)?
5912 else {
5913 return None;
5914 };
5915
5916 let candidate_id = {
5917 let entries = completions_menu.entries.borrow();
5918 let mat = entries.get(item_ix.unwrap_or(completions_menu.selected_item))?;
5919 if self.show_edit_predictions_in_menu() {
5920 self.discard_edit_prediction(true, cx);
5921 }
5922 mat.candidate_id
5923 };
5924
5925 let completion = completions_menu
5926 .completions
5927 .borrow()
5928 .get(candidate_id)?
5929 .clone();
5930 cx.stop_propagation();
5931
5932 let buffer_handle = completions_menu.buffer.clone();
5933
5934 let CompletionEdit {
5935 new_text,
5936 snippet,
5937 replace_range,
5938 } = process_completion_for_edit(
5939 &completion,
5940 intent,
5941 &buffer_handle,
5942 &completions_menu.initial_position.text_anchor,
5943 cx,
5944 );
5945
5946 let buffer = buffer_handle.read(cx);
5947 let snapshot = self.buffer.read(cx).snapshot(cx);
5948 let newest_anchor = self.selections.newest_anchor();
5949 let replace_range_multibuffer = {
5950 let mut excerpt = snapshot.excerpt_containing(newest_anchor.range()).unwrap();
5951 excerpt.map_range_from_buffer(replace_range.clone())
5952 };
5953 if snapshot.buffer_id_for_anchor(newest_anchor.head()) != Some(buffer.remote_id()) {
5954 return None;
5955 }
5956
5957 let old_text = buffer
5958 .text_for_range(replace_range.clone())
5959 .collect::<String>();
5960 let lookbehind = newest_anchor
5961 .start
5962 .text_anchor
5963 .to_offset(buffer)
5964 .saturating_sub(replace_range.start);
5965 let lookahead = replace_range
5966 .end
5967 .saturating_sub(newest_anchor.end.text_anchor.to_offset(buffer));
5968 let prefix = &old_text[..old_text.len().saturating_sub(lookahead)];
5969 let suffix = &old_text[lookbehind.min(old_text.len())..];
5970
5971 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
5972 let mut ranges = Vec::new();
5973 let mut linked_edits = HashMap::<_, Vec<_>>::default();
5974
5975 for selection in &selections {
5976 let range = if selection.id == newest_anchor.id {
5977 replace_range_multibuffer.clone()
5978 } else {
5979 let mut range = selection.range();
5980
5981 // if prefix is present, don't duplicate it
5982 if snapshot.contains_str_at(range.start.saturating_sub(lookbehind), prefix) {
5983 range.start = range.start.saturating_sub(lookbehind);
5984
5985 // if suffix is also present, mimic the newest cursor and replace it
5986 if selection.id != newest_anchor.id
5987 && snapshot.contains_str_at(range.end, suffix)
5988 {
5989 range.end += lookahead;
5990 }
5991 }
5992 range
5993 };
5994
5995 ranges.push(range.clone());
5996
5997 if !self.linked_edit_ranges.is_empty() {
5998 let start_anchor = snapshot.anchor_before(range.start);
5999 let end_anchor = snapshot.anchor_after(range.end);
6000 if let Some(ranges) = self
6001 .linked_editing_ranges_for(start_anchor.text_anchor..end_anchor.text_anchor, cx)
6002 {
6003 for (buffer, edits) in ranges {
6004 linked_edits
6005 .entry(buffer.clone())
6006 .or_default()
6007 .extend(edits.into_iter().map(|range| (range, new_text.to_owned())));
6008 }
6009 }
6010 }
6011 }
6012
6013 let common_prefix_len = old_text
6014 .chars()
6015 .zip(new_text.chars())
6016 .take_while(|(a, b)| a == b)
6017 .map(|(a, _)| a.len_utf8())
6018 .sum::<usize>();
6019
6020 cx.emit(EditorEvent::InputHandled {
6021 utf16_range_to_replace: None,
6022 text: new_text[common_prefix_len..].into(),
6023 });
6024
6025 self.transact(window, cx, |editor, window, cx| {
6026 if let Some(mut snippet) = snippet {
6027 snippet.text = new_text.to_string();
6028 editor
6029 .insert_snippet(&ranges, snippet, window, cx)
6030 .log_err();
6031 } else {
6032 editor.buffer.update(cx, |multi_buffer, cx| {
6033 let auto_indent = match completion.insert_text_mode {
6034 Some(InsertTextMode::AS_IS) => None,
6035 _ => editor.autoindent_mode.clone(),
6036 };
6037 let edits = ranges.into_iter().map(|range| (range, new_text.as_str()));
6038 multi_buffer.edit(edits, auto_indent, cx);
6039 });
6040 }
6041 for (buffer, edits) in linked_edits {
6042 buffer.update(cx, |buffer, cx| {
6043 let snapshot = buffer.snapshot();
6044 let edits = edits
6045 .into_iter()
6046 .map(|(range, text)| {
6047 use text::ToPoint as TP;
6048 let end_point = TP::to_point(&range.end, &snapshot);
6049 let start_point = TP::to_point(&range.start, &snapshot);
6050 (start_point..end_point, text)
6051 })
6052 .sorted_by_key(|(range, _)| range.start);
6053 buffer.edit(edits, None, cx);
6054 })
6055 }
6056
6057 editor.refresh_edit_prediction(true, false, window, cx);
6058 });
6059 self.invalidate_autoclose_regions(&self.selections.disjoint_anchors_arc(), &snapshot);
6060
6061 let show_new_completions_on_confirm = completion
6062 .confirm
6063 .as_ref()
6064 .is_some_and(|confirm| confirm(intent, window, cx));
6065 if show_new_completions_on_confirm {
6066 self.open_or_update_completions_menu(None, None, false, window, cx);
6067 }
6068
6069 let provider = self.completion_provider.as_ref()?;
6070 drop(completion);
6071 let apply_edits = provider.apply_additional_edits_for_completion(
6072 buffer_handle,
6073 completions_menu.completions.clone(),
6074 candidate_id,
6075 true,
6076 cx,
6077 );
6078
6079 let editor_settings = EditorSettings::get_global(cx);
6080 if editor_settings.show_signature_help_after_edits || editor_settings.auto_signature_help {
6081 // After the code completion is finished, users often want to know what signatures are needed.
6082 // so we should automatically call signature_help
6083 self.show_signature_help(&ShowSignatureHelp, window, cx);
6084 }
6085
6086 Some(cx.foreground_executor().spawn(async move {
6087 apply_edits.await?;
6088 Ok(())
6089 }))
6090 }
6091
6092 pub fn toggle_code_actions(
6093 &mut self,
6094 action: &ToggleCodeActions,
6095 window: &mut Window,
6096 cx: &mut Context<Self>,
6097 ) {
6098 let quick_launch = action.quick_launch;
6099 let mut context_menu = self.context_menu.borrow_mut();
6100 if let Some(CodeContextMenu::CodeActions(code_actions)) = context_menu.as_ref() {
6101 if code_actions.deployed_from == action.deployed_from {
6102 // Toggle if we're selecting the same one
6103 *context_menu = None;
6104 cx.notify();
6105 return;
6106 } else {
6107 // Otherwise, clear it and start a new one
6108 *context_menu = None;
6109 cx.notify();
6110 }
6111 }
6112 drop(context_menu);
6113 let snapshot = self.snapshot(window, cx);
6114 let deployed_from = action.deployed_from.clone();
6115 let action = action.clone();
6116 self.completion_tasks.clear();
6117 self.discard_edit_prediction(false, cx);
6118
6119 let multibuffer_point = match &action.deployed_from {
6120 Some(CodeActionSource::Indicator(row)) | Some(CodeActionSource::RunMenu(row)) => {
6121 DisplayPoint::new(*row, 0).to_point(&snapshot)
6122 }
6123 _ => self
6124 .selections
6125 .newest::<Point>(&snapshot.display_snapshot)
6126 .head(),
6127 };
6128 let Some((buffer, buffer_row)) = snapshot
6129 .buffer_snapshot()
6130 .buffer_line_for_row(MultiBufferRow(multibuffer_point.row))
6131 .and_then(|(buffer_snapshot, range)| {
6132 self.buffer()
6133 .read(cx)
6134 .buffer(buffer_snapshot.remote_id())
6135 .map(|buffer| (buffer, range.start.row))
6136 })
6137 else {
6138 return;
6139 };
6140 let buffer_id = buffer.read(cx).remote_id();
6141 let tasks = self
6142 .tasks
6143 .get(&(buffer_id, buffer_row))
6144 .map(|t| Arc::new(t.to_owned()));
6145
6146 if !self.focus_handle.is_focused(window) {
6147 return;
6148 }
6149 let project = self.project.clone();
6150
6151 let code_actions_task = match deployed_from {
6152 Some(CodeActionSource::RunMenu(_)) => Task::ready(None),
6153 _ => self.code_actions(buffer_row, window, cx),
6154 };
6155
6156 let runnable_task = match deployed_from {
6157 Some(CodeActionSource::Indicator(_)) => Task::ready(Ok(Default::default())),
6158 _ => {
6159 let mut task_context_task = Task::ready(None);
6160 if let Some(tasks) = &tasks
6161 && let Some(project) = project
6162 {
6163 task_context_task =
6164 Self::build_tasks_context(&project, &buffer, buffer_row, tasks, cx);
6165 }
6166
6167 cx.spawn_in(window, {
6168 let buffer = buffer.clone();
6169 async move |editor, cx| {
6170 let task_context = task_context_task.await;
6171
6172 let resolved_tasks =
6173 tasks
6174 .zip(task_context.clone())
6175 .map(|(tasks, task_context)| ResolvedTasks {
6176 templates: tasks.resolve(&task_context).collect(),
6177 position: snapshot.buffer_snapshot().anchor_before(Point::new(
6178 multibuffer_point.row,
6179 tasks.column,
6180 )),
6181 });
6182 let debug_scenarios = editor
6183 .update(cx, |editor, cx| {
6184 editor.debug_scenarios(&resolved_tasks, &buffer, cx)
6185 })?
6186 .await;
6187 anyhow::Ok((resolved_tasks, debug_scenarios, task_context))
6188 }
6189 })
6190 }
6191 };
6192
6193 cx.spawn_in(window, async move |editor, cx| {
6194 let (resolved_tasks, debug_scenarios, task_context) = runnable_task.await?;
6195 let code_actions = code_actions_task.await;
6196 let spawn_straight_away = quick_launch
6197 && resolved_tasks
6198 .as_ref()
6199 .is_some_and(|tasks| tasks.templates.len() == 1)
6200 && code_actions
6201 .as_ref()
6202 .is_none_or(|actions| actions.is_empty())
6203 && debug_scenarios.is_empty();
6204
6205 editor.update_in(cx, |editor, window, cx| {
6206 crate::hover_popover::hide_hover(editor, cx);
6207 let actions = CodeActionContents::new(
6208 resolved_tasks,
6209 code_actions,
6210 debug_scenarios,
6211 task_context.unwrap_or_default(),
6212 );
6213
6214 // Don't show the menu if there are no actions available
6215 if actions.is_empty() {
6216 cx.notify();
6217 return Task::ready(Ok(()));
6218 }
6219
6220 *editor.context_menu.borrow_mut() =
6221 Some(CodeContextMenu::CodeActions(CodeActionsMenu {
6222 buffer,
6223 actions,
6224 selected_item: Default::default(),
6225 scroll_handle: UniformListScrollHandle::default(),
6226 deployed_from,
6227 }));
6228 cx.notify();
6229 if spawn_straight_away
6230 && let Some(task) = editor.confirm_code_action(
6231 &ConfirmCodeAction { item_ix: Some(0) },
6232 window,
6233 cx,
6234 )
6235 {
6236 return task;
6237 }
6238
6239 Task::ready(Ok(()))
6240 })
6241 })
6242 .detach_and_log_err(cx);
6243 }
6244
6245 fn debug_scenarios(
6246 &mut self,
6247 resolved_tasks: &Option<ResolvedTasks>,
6248 buffer: &Entity<Buffer>,
6249 cx: &mut App,
6250 ) -> Task<Vec<task::DebugScenario>> {
6251 maybe!({
6252 let project = self.project()?;
6253 let dap_store = project.read(cx).dap_store();
6254 let mut scenarios = vec![];
6255 let resolved_tasks = resolved_tasks.as_ref()?;
6256 let buffer = buffer.read(cx);
6257 let language = buffer.language()?;
6258 let file = buffer.file();
6259 let debug_adapter = language_settings(language.name().into(), file, cx)
6260 .debuggers
6261 .first()
6262 .map(SharedString::from)
6263 .or_else(|| language.config().debuggers.first().map(SharedString::from))?;
6264
6265 dap_store.update(cx, |dap_store, cx| {
6266 for (_, task) in &resolved_tasks.templates {
6267 let maybe_scenario = dap_store.debug_scenario_for_build_task(
6268 task.original_task().clone(),
6269 debug_adapter.clone().into(),
6270 task.display_label().to_owned().into(),
6271 cx,
6272 );
6273 scenarios.push(maybe_scenario);
6274 }
6275 });
6276 Some(cx.background_spawn(async move {
6277 futures::future::join_all(scenarios)
6278 .await
6279 .into_iter()
6280 .flatten()
6281 .collect::<Vec<_>>()
6282 }))
6283 })
6284 .unwrap_or_else(|| Task::ready(vec![]))
6285 }
6286
6287 fn code_actions(
6288 &mut self,
6289 buffer_row: u32,
6290 window: &mut Window,
6291 cx: &mut Context<Self>,
6292 ) -> Task<Option<Rc<[AvailableCodeAction]>>> {
6293 let mut task = self.code_actions_task.take();
6294 cx.spawn_in(window, async move |editor, cx| {
6295 while let Some(prev_task) = task {
6296 prev_task.await.log_err();
6297 task = editor
6298 .update(cx, |this, _| this.code_actions_task.take())
6299 .ok()?;
6300 }
6301
6302 editor
6303 .update(cx, |editor, cx| {
6304 editor
6305 .available_code_actions
6306 .clone()
6307 .and_then(|(location, code_actions)| {
6308 let snapshot = location.buffer.read(cx).snapshot();
6309 let point_range = location.range.to_point(&snapshot);
6310 let point_range = point_range.start.row..=point_range.end.row;
6311 if point_range.contains(&buffer_row) {
6312 Some(code_actions)
6313 } else {
6314 None
6315 }
6316 })
6317 })
6318 .ok()
6319 .flatten()
6320 })
6321 }
6322
6323 pub fn confirm_code_action(
6324 &mut self,
6325 action: &ConfirmCodeAction,
6326 window: &mut Window,
6327 cx: &mut Context<Self>,
6328 ) -> Option<Task<Result<()>>> {
6329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
6330
6331 let actions_menu =
6332 if let CodeContextMenu::CodeActions(menu) = self.hide_context_menu(window, cx)? {
6333 menu
6334 } else {
6335 return None;
6336 };
6337
6338 let action_ix = action.item_ix.unwrap_or(actions_menu.selected_item);
6339 let action = actions_menu.actions.get(action_ix)?;
6340 let title = action.label();
6341 let buffer = actions_menu.buffer;
6342 let workspace = self.workspace()?;
6343
6344 match action {
6345 CodeActionsItem::Task(task_source_kind, resolved_task) => {
6346 workspace.update(cx, |workspace, cx| {
6347 workspace.schedule_resolved_task(
6348 task_source_kind,
6349 resolved_task,
6350 false,
6351 window,
6352 cx,
6353 );
6354
6355 Some(Task::ready(Ok(())))
6356 })
6357 }
6358 CodeActionsItem::CodeAction {
6359 excerpt_id,
6360 action,
6361 provider,
6362 } => {
6363 let apply_code_action =
6364 provider.apply_code_action(buffer, action, excerpt_id, true, window, cx);
6365 let workspace = workspace.downgrade();
6366 Some(cx.spawn_in(window, async move |editor, cx| {
6367 let project_transaction = apply_code_action.await?;
6368 Self::open_project_transaction(
6369 &editor,
6370 workspace,
6371 project_transaction,
6372 title,
6373 cx,
6374 )
6375 .await
6376 }))
6377 }
6378 CodeActionsItem::DebugScenario(scenario) => {
6379 let context = actions_menu.actions.context;
6380
6381 workspace.update(cx, |workspace, cx| {
6382 dap::send_telemetry(&scenario, TelemetrySpawnLocation::Gutter, cx);
6383 workspace.start_debug_session(
6384 scenario,
6385 context,
6386 Some(buffer),
6387 None,
6388 window,
6389 cx,
6390 );
6391 });
6392 Some(Task::ready(Ok(())))
6393 }
6394 }
6395 }
6396
6397 pub async fn open_project_transaction(
6398 editor: &WeakEntity<Editor>,
6399 workspace: WeakEntity<Workspace>,
6400 transaction: ProjectTransaction,
6401 title: String,
6402 cx: &mut AsyncWindowContext,
6403 ) -> Result<()> {
6404 let mut entries = transaction.0.into_iter().collect::<Vec<_>>();
6405 cx.update(|_, cx| {
6406 entries.sort_unstable_by_key(|(buffer, _)| {
6407 buffer.read(cx).file().map(|f| f.path().clone())
6408 });
6409 })?;
6410 if entries.is_empty() {
6411 return Ok(());
6412 }
6413
6414 // If the project transaction's edits are all contained within this editor, then
6415 // avoid opening a new editor to display them.
6416
6417 if let [(buffer, transaction)] = &*entries {
6418 let excerpt = editor.update(cx, |editor, cx| {
6419 editor
6420 .buffer()
6421 .read(cx)
6422 .excerpt_containing(editor.selections.newest_anchor().head(), cx)
6423 })?;
6424 if let Some((_, excerpted_buffer, excerpt_range)) = excerpt
6425 && excerpted_buffer == *buffer
6426 {
6427 let all_edits_within_excerpt = buffer.read_with(cx, |buffer, _| {
6428 let excerpt_range = excerpt_range.to_offset(buffer);
6429 buffer
6430 .edited_ranges_for_transaction::<usize>(transaction)
6431 .all(|range| {
6432 excerpt_range.start <= range.start && excerpt_range.end >= range.end
6433 })
6434 })?;
6435
6436 if all_edits_within_excerpt {
6437 return Ok(());
6438 }
6439 }
6440 }
6441
6442 let mut ranges_to_highlight = Vec::new();
6443 let excerpt_buffer = cx.new(|cx| {
6444 let mut multibuffer = MultiBuffer::new(Capability::ReadWrite).with_title(title);
6445 for (buffer_handle, transaction) in &entries {
6446 let edited_ranges = buffer_handle
6447 .read(cx)
6448 .edited_ranges_for_transaction::<Point>(transaction)
6449 .collect::<Vec<_>>();
6450 let (ranges, _) = multibuffer.set_excerpts_for_path(
6451 PathKey::for_buffer(buffer_handle, cx),
6452 buffer_handle.clone(),
6453 edited_ranges,
6454 multibuffer_context_lines(cx),
6455 cx,
6456 );
6457
6458 ranges_to_highlight.extend(ranges);
6459 }
6460 multibuffer.push_transaction(entries.iter().map(|(b, t)| (b, t)), cx);
6461 multibuffer
6462 })?;
6463
6464 workspace.update_in(cx, |workspace, window, cx| {
6465 let project = workspace.project().clone();
6466 let editor =
6467 cx.new(|cx| Editor::for_multibuffer(excerpt_buffer, Some(project), window, cx));
6468 workspace.add_item_to_active_pane(Box::new(editor.clone()), None, true, window, cx);
6469 editor.update(cx, |editor, cx| {
6470 editor.highlight_background::<Self>(
6471 &ranges_to_highlight,
6472 |theme| theme.colors().editor_highlighted_line_background,
6473 cx,
6474 );
6475 });
6476 })?;
6477
6478 Ok(())
6479 }
6480
6481 pub fn clear_code_action_providers(&mut self) {
6482 self.code_action_providers.clear();
6483 self.available_code_actions.take();
6484 }
6485
6486 pub fn add_code_action_provider(
6487 &mut self,
6488 provider: Rc<dyn CodeActionProvider>,
6489 window: &mut Window,
6490 cx: &mut Context<Self>,
6491 ) {
6492 if self
6493 .code_action_providers
6494 .iter()
6495 .any(|existing_provider| existing_provider.id() == provider.id())
6496 {
6497 return;
6498 }
6499
6500 self.code_action_providers.push(provider);
6501 self.refresh_code_actions(window, cx);
6502 }
6503
6504 pub fn remove_code_action_provider(
6505 &mut self,
6506 id: Arc<str>,
6507 window: &mut Window,
6508 cx: &mut Context<Self>,
6509 ) {
6510 self.code_action_providers
6511 .retain(|provider| provider.id() != id);
6512 self.refresh_code_actions(window, cx);
6513 }
6514
6515 pub fn code_actions_enabled_for_toolbar(&self, cx: &App) -> bool {
6516 !self.code_action_providers.is_empty()
6517 && EditorSettings::get_global(cx).toolbar.code_actions
6518 }
6519
6520 pub fn has_available_code_actions(&self) -> bool {
6521 self.available_code_actions
6522 .as_ref()
6523 .is_some_and(|(_, actions)| !actions.is_empty())
6524 }
6525
6526 fn render_inline_code_actions(
6527 &self,
6528 icon_size: ui::IconSize,
6529 display_row: DisplayRow,
6530 is_active: bool,
6531 cx: &mut Context<Self>,
6532 ) -> AnyElement {
6533 let show_tooltip = !self.context_menu_visible();
6534 IconButton::new("inline_code_actions", ui::IconName::BoltFilled)
6535 .icon_size(icon_size)
6536 .shape(ui::IconButtonShape::Square)
6537 .icon_color(ui::Color::Hidden)
6538 .toggle_state(is_active)
6539 .when(show_tooltip, |this| {
6540 this.tooltip({
6541 let focus_handle = self.focus_handle.clone();
6542 move |_window, cx| {
6543 Tooltip::for_action_in(
6544 "Toggle Code Actions",
6545 &ToggleCodeActions {
6546 deployed_from: None,
6547 quick_launch: false,
6548 },
6549 &focus_handle,
6550 cx,
6551 )
6552 }
6553 })
6554 })
6555 .on_click(cx.listener(move |editor, _: &ClickEvent, window, cx| {
6556 window.focus(&editor.focus_handle(cx));
6557 editor.toggle_code_actions(
6558 &crate::actions::ToggleCodeActions {
6559 deployed_from: Some(crate::actions::CodeActionSource::Indicator(
6560 display_row,
6561 )),
6562 quick_launch: false,
6563 },
6564 window,
6565 cx,
6566 );
6567 }))
6568 .into_any_element()
6569 }
6570
6571 pub fn context_menu(&self) -> &RefCell<Option<CodeContextMenu>> {
6572 &self.context_menu
6573 }
6574
6575 fn refresh_code_actions(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6576 self.code_actions_task = Some(cx.spawn_in(window, async move |this, cx| {
6577 cx.background_executor()
6578 .timer(CODE_ACTIONS_DEBOUNCE_TIMEOUT)
6579 .await;
6580
6581 let (start_buffer, start, _, end, newest_selection) = this
6582 .update(cx, |this, cx| {
6583 let newest_selection = this.selections.newest_anchor().clone();
6584 if newest_selection.head().diff_base_anchor.is_some() {
6585 return None;
6586 }
6587 let display_snapshot = this.display_snapshot(cx);
6588 let newest_selection_adjusted =
6589 this.selections.newest_adjusted(&display_snapshot);
6590 let buffer = this.buffer.read(cx);
6591
6592 let (start_buffer, start) =
6593 buffer.text_anchor_for_position(newest_selection_adjusted.start, cx)?;
6594 let (end_buffer, end) =
6595 buffer.text_anchor_for_position(newest_selection_adjusted.end, cx)?;
6596
6597 Some((start_buffer, start, end_buffer, end, newest_selection))
6598 })?
6599 .filter(|(start_buffer, _, end_buffer, _, _)| start_buffer == end_buffer)
6600 .context(
6601 "Expected selection to lie in a single buffer when refreshing code actions",
6602 )?;
6603 let (providers, tasks) = this.update_in(cx, |this, window, cx| {
6604 let providers = this.code_action_providers.clone();
6605 let tasks = this
6606 .code_action_providers
6607 .iter()
6608 .map(|provider| provider.code_actions(&start_buffer, start..end, window, cx))
6609 .collect::<Vec<_>>();
6610 (providers, tasks)
6611 })?;
6612
6613 let mut actions = Vec::new();
6614 for (provider, provider_actions) in
6615 providers.into_iter().zip(future::join_all(tasks).await)
6616 {
6617 if let Some(provider_actions) = provider_actions.log_err() {
6618 actions.extend(provider_actions.into_iter().map(|action| {
6619 AvailableCodeAction {
6620 excerpt_id: newest_selection.start.excerpt_id,
6621 action,
6622 provider: provider.clone(),
6623 }
6624 }));
6625 }
6626 }
6627
6628 this.update(cx, |this, cx| {
6629 this.available_code_actions = if actions.is_empty() {
6630 None
6631 } else {
6632 Some((
6633 Location {
6634 buffer: start_buffer,
6635 range: start..end,
6636 },
6637 actions.into(),
6638 ))
6639 };
6640 cx.notify();
6641 })
6642 }));
6643 }
6644
6645 fn start_inline_blame_timer(&mut self, window: &mut Window, cx: &mut Context<Self>) {
6646 if let Some(delay) = ProjectSettings::get_global(cx).git.inline_blame_delay() {
6647 self.show_git_blame_inline = false;
6648
6649 self.show_git_blame_inline_delay_task =
6650 Some(cx.spawn_in(window, async move |this, cx| {
6651 cx.background_executor().timer(delay).await;
6652
6653 this.update(cx, |this, cx| {
6654 this.show_git_blame_inline = true;
6655 cx.notify();
6656 })
6657 .log_err();
6658 }));
6659 }
6660 }
6661
6662 pub fn blame_hover(&mut self, _: &BlameHover, window: &mut Window, cx: &mut Context<Self>) {
6663 let snapshot = self.snapshot(window, cx);
6664 let cursor = self
6665 .selections
6666 .newest::<Point>(&snapshot.display_snapshot)
6667 .head();
6668 let Some((buffer, point, _)) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)
6669 else {
6670 return;
6671 };
6672
6673 let Some(blame) = self.blame.as_ref() else {
6674 return;
6675 };
6676
6677 let row_info = RowInfo {
6678 buffer_id: Some(buffer.remote_id()),
6679 buffer_row: Some(point.row),
6680 ..Default::default()
6681 };
6682 let Some((buffer, blame_entry)) = blame
6683 .update(cx, |blame, cx| blame.blame_for_rows(&[row_info], cx).next())
6684 .flatten()
6685 else {
6686 return;
6687 };
6688
6689 let anchor = self.selections.newest_anchor().head();
6690 let position = self.to_pixel_point(anchor, &snapshot, window);
6691 if let (Some(position), Some(last_bounds)) = (position, self.last_bounds) {
6692 self.show_blame_popover(
6693 buffer,
6694 &blame_entry,
6695 position + last_bounds.origin,
6696 true,
6697 cx,
6698 );
6699 };
6700 }
6701
6702 fn show_blame_popover(
6703 &mut self,
6704 buffer: BufferId,
6705 blame_entry: &BlameEntry,
6706 position: gpui::Point<Pixels>,
6707 ignore_timeout: bool,
6708 cx: &mut Context<Self>,
6709 ) {
6710 if let Some(state) = &mut self.inline_blame_popover {
6711 state.hide_task.take();
6712 } else {
6713 let blame_popover_delay = EditorSettings::get_global(cx).hover_popover_delay.0;
6714 let blame_entry = blame_entry.clone();
6715 let show_task = cx.spawn(async move |editor, cx| {
6716 if !ignore_timeout {
6717 cx.background_executor()
6718 .timer(std::time::Duration::from_millis(blame_popover_delay))
6719 .await;
6720 }
6721 editor
6722 .update(cx, |editor, cx| {
6723 editor.inline_blame_popover_show_task.take();
6724 let Some(blame) = editor.blame.as_ref() else {
6725 return;
6726 };
6727 let blame = blame.read(cx);
6728 let details = blame.details_for_entry(buffer, &blame_entry);
6729 let markdown = cx.new(|cx| {
6730 Markdown::new(
6731 details
6732 .as_ref()
6733 .map(|message| message.message.clone())
6734 .unwrap_or_default(),
6735 None,
6736 None,
6737 cx,
6738 )
6739 });
6740 editor.inline_blame_popover = Some(InlineBlamePopover {
6741 position,
6742 hide_task: None,
6743 popover_bounds: None,
6744 popover_state: InlineBlamePopoverState {
6745 scroll_handle: ScrollHandle::new(),
6746 commit_message: details,
6747 markdown,
6748 },
6749 keyboard_grace: ignore_timeout,
6750 });
6751 cx.notify();
6752 })
6753 .ok();
6754 });
6755 self.inline_blame_popover_show_task = Some(show_task);
6756 }
6757 }
6758
6759 fn hide_blame_popover(&mut self, ignore_timeout: bool, cx: &mut Context<Self>) -> bool {
6760 self.inline_blame_popover_show_task.take();
6761 if let Some(state) = &mut self.inline_blame_popover {
6762 let hide_task = cx.spawn(async move |editor, cx| {
6763 if !ignore_timeout {
6764 cx.background_executor()
6765 .timer(std::time::Duration::from_millis(100))
6766 .await;
6767 }
6768 editor
6769 .update(cx, |editor, cx| {
6770 editor.inline_blame_popover.take();
6771 cx.notify();
6772 })
6773 .ok();
6774 });
6775 state.hide_task = Some(hide_task);
6776 true
6777 } else {
6778 false
6779 }
6780 }
6781
6782 fn refresh_document_highlights(&mut self, cx: &mut Context<Self>) -> Option<()> {
6783 if self.pending_rename.is_some() {
6784 return None;
6785 }
6786
6787 let provider = self.semantics_provider.clone()?;
6788 let buffer = self.buffer.read(cx);
6789 let newest_selection = self.selections.newest_anchor().clone();
6790 let cursor_position = newest_selection.head();
6791 let (cursor_buffer, cursor_buffer_position) =
6792 buffer.text_anchor_for_position(cursor_position, cx)?;
6793 let (tail_buffer, tail_buffer_position) =
6794 buffer.text_anchor_for_position(newest_selection.tail(), cx)?;
6795 if cursor_buffer != tail_buffer {
6796 return None;
6797 }
6798
6799 let snapshot = cursor_buffer.read(cx).snapshot();
6800 let (start_word_range, _) = snapshot.surrounding_word(cursor_buffer_position, None);
6801 let (end_word_range, _) = snapshot.surrounding_word(tail_buffer_position, None);
6802 if start_word_range != end_word_range {
6803 self.document_highlights_task.take();
6804 self.clear_background_highlights::<DocumentHighlightRead>(cx);
6805 self.clear_background_highlights::<DocumentHighlightWrite>(cx);
6806 return None;
6807 }
6808
6809 let debounce = EditorSettings::get_global(cx).lsp_highlight_debounce.0;
6810 self.document_highlights_task = Some(cx.spawn(async move |this, cx| {
6811 cx.background_executor()
6812 .timer(Duration::from_millis(debounce))
6813 .await;
6814
6815 let highlights = if let Some(highlights) = cx
6816 .update(|cx| {
6817 provider.document_highlights(&cursor_buffer, cursor_buffer_position, cx)
6818 })
6819 .ok()
6820 .flatten()
6821 {
6822 highlights.await.log_err()
6823 } else {
6824 None
6825 };
6826
6827 if let Some(highlights) = highlights {
6828 this.update(cx, |this, cx| {
6829 if this.pending_rename.is_some() {
6830 return;
6831 }
6832
6833 let buffer = this.buffer.read(cx);
6834 if buffer
6835 .text_anchor_for_position(cursor_position, cx)
6836 .is_none_or(|(buffer, _)| buffer != cursor_buffer)
6837 {
6838 return;
6839 }
6840
6841 let cursor_buffer_snapshot = cursor_buffer.read(cx);
6842 let mut write_ranges = Vec::new();
6843 let mut read_ranges = Vec::new();
6844 for highlight in highlights {
6845 let buffer_id = cursor_buffer.read(cx).remote_id();
6846 for (excerpt_id, excerpt_range) in buffer.excerpts_for_buffer(buffer_id, cx)
6847 {
6848 let start = highlight
6849 .range
6850 .start
6851 .max(&excerpt_range.context.start, cursor_buffer_snapshot);
6852 let end = highlight
6853 .range
6854 .end
6855 .min(&excerpt_range.context.end, cursor_buffer_snapshot);
6856 if start.cmp(&end, cursor_buffer_snapshot).is_ge() {
6857 continue;
6858 }
6859
6860 let range =
6861 Anchor::range_in_buffer(excerpt_id, buffer_id, *start..*end);
6862 if highlight.kind == lsp::DocumentHighlightKind::WRITE {
6863 write_ranges.push(range);
6864 } else {
6865 read_ranges.push(range);
6866 }
6867 }
6868 }
6869
6870 this.highlight_background::<DocumentHighlightRead>(
6871 &read_ranges,
6872 |theme| theme.colors().editor_document_highlight_read_background,
6873 cx,
6874 );
6875 this.highlight_background::<DocumentHighlightWrite>(
6876 &write_ranges,
6877 |theme| theme.colors().editor_document_highlight_write_background,
6878 cx,
6879 );
6880 cx.notify();
6881 })
6882 .log_err();
6883 }
6884 }));
6885 None
6886 }
6887
6888 fn prepare_highlight_query_from_selection(
6889 &mut self,
6890 window: &Window,
6891 cx: &mut Context<Editor>,
6892 ) -> Option<(String, Range<Anchor>)> {
6893 if matches!(self.mode, EditorMode::SingleLine) {
6894 return None;
6895 }
6896 if !EditorSettings::get_global(cx).selection_highlight {
6897 return None;
6898 }
6899 if self.selections.count() != 1 || self.selections.line_mode() {
6900 return None;
6901 }
6902 let snapshot = self.snapshot(window, cx);
6903 let selection = self.selections.newest::<Point>(&snapshot);
6904 // If the selection spans multiple rows OR it is empty
6905 if selection.start.row != selection.end.row
6906 || selection.start.column == selection.end.column
6907 {
6908 return None;
6909 }
6910 let selection_anchor_range = selection.range().to_anchors(snapshot.buffer_snapshot());
6911 let query = snapshot
6912 .buffer_snapshot()
6913 .text_for_range(selection_anchor_range.clone())
6914 .collect::<String>();
6915 if query.trim().is_empty() {
6916 return None;
6917 }
6918 Some((query, selection_anchor_range))
6919 }
6920
6921 fn update_selection_occurrence_highlights(
6922 &mut self,
6923 query_text: String,
6924 query_range: Range<Anchor>,
6925 multi_buffer_range_to_query: Range<Point>,
6926 use_debounce: bool,
6927 window: &mut Window,
6928 cx: &mut Context<Editor>,
6929 ) -> Task<()> {
6930 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
6931 cx.spawn_in(window, async move |editor, cx| {
6932 if use_debounce {
6933 cx.background_executor()
6934 .timer(SELECTION_HIGHLIGHT_DEBOUNCE_TIMEOUT)
6935 .await;
6936 }
6937 let match_task = cx.background_spawn(async move {
6938 let buffer_ranges = multi_buffer_snapshot
6939 .range_to_buffer_ranges(multi_buffer_range_to_query)
6940 .into_iter()
6941 .filter(|(_, excerpt_visible_range, _)| !excerpt_visible_range.is_empty());
6942 let mut match_ranges = Vec::new();
6943 let Ok(regex) = project::search::SearchQuery::text(
6944 query_text.clone(),
6945 false,
6946 false,
6947 false,
6948 Default::default(),
6949 Default::default(),
6950 false,
6951 None,
6952 ) else {
6953 return Vec::default();
6954 };
6955 let query_range = query_range.to_anchors(&multi_buffer_snapshot);
6956 for (buffer_snapshot, search_range, excerpt_id) in buffer_ranges {
6957 match_ranges.extend(
6958 regex
6959 .search(buffer_snapshot, Some(search_range.clone()))
6960 .await
6961 .into_iter()
6962 .filter_map(|match_range| {
6963 let match_start = buffer_snapshot
6964 .anchor_after(search_range.start + match_range.start);
6965 let match_end = buffer_snapshot
6966 .anchor_before(search_range.start + match_range.end);
6967 let match_anchor_range = Anchor::range_in_buffer(
6968 excerpt_id,
6969 buffer_snapshot.remote_id(),
6970 match_start..match_end,
6971 );
6972 (match_anchor_range != query_range).then_some(match_anchor_range)
6973 }),
6974 );
6975 }
6976 match_ranges
6977 });
6978 let match_ranges = match_task.await;
6979 editor
6980 .update_in(cx, |editor, _, cx| {
6981 editor.clear_background_highlights::<SelectedTextHighlight>(cx);
6982 if !match_ranges.is_empty() {
6983 editor.highlight_background::<SelectedTextHighlight>(
6984 &match_ranges,
6985 |theme| theme.colors().editor_document_highlight_bracket_background,
6986 cx,
6987 )
6988 }
6989 })
6990 .log_err();
6991 })
6992 }
6993
6994 fn refresh_single_line_folds(&mut self, window: &mut Window, cx: &mut Context<Editor>) {
6995 struct NewlineFold;
6996 let type_id = std::any::TypeId::of::<NewlineFold>();
6997 if !self.mode.is_single_line() {
6998 return;
6999 }
7000 let snapshot = self.snapshot(window, cx);
7001 if snapshot.buffer_snapshot().max_point().row == 0 {
7002 return;
7003 }
7004 let task = cx.background_spawn(async move {
7005 let new_newlines = snapshot
7006 .buffer_chars_at(0)
7007 .filter_map(|(c, i)| {
7008 if c == '\n' {
7009 Some(
7010 snapshot.buffer_snapshot().anchor_after(i)
7011 ..snapshot.buffer_snapshot().anchor_before(i + 1),
7012 )
7013 } else {
7014 None
7015 }
7016 })
7017 .collect::<Vec<_>>();
7018 let existing_newlines = snapshot
7019 .folds_in_range(0..snapshot.buffer_snapshot().len())
7020 .filter_map(|fold| {
7021 if fold.placeholder.type_tag == Some(type_id) {
7022 Some(fold.range.start..fold.range.end)
7023 } else {
7024 None
7025 }
7026 })
7027 .collect::<Vec<_>>();
7028
7029 (new_newlines, existing_newlines)
7030 });
7031 self.folding_newlines = cx.spawn(async move |this, cx| {
7032 let (new_newlines, existing_newlines) = task.await;
7033 if new_newlines == existing_newlines {
7034 return;
7035 }
7036 let placeholder = FoldPlaceholder {
7037 render: Arc::new(move |_, _, cx| {
7038 div()
7039 .bg(cx.theme().status().hint_background)
7040 .border_b_1()
7041 .size_full()
7042 .font(ThemeSettings::get_global(cx).buffer_font.clone())
7043 .border_color(cx.theme().status().hint)
7044 .child("\\n")
7045 .into_any()
7046 }),
7047 constrain_width: false,
7048 merge_adjacent: false,
7049 type_tag: Some(type_id),
7050 };
7051 let creases = new_newlines
7052 .into_iter()
7053 .map(|range| Crease::simple(range, placeholder.clone()))
7054 .collect();
7055 this.update(cx, |this, cx| {
7056 this.display_map.update(cx, |display_map, cx| {
7057 display_map.remove_folds_with_type(existing_newlines, type_id, cx);
7058 display_map.fold(creases, cx);
7059 });
7060 })
7061 .ok();
7062 });
7063 }
7064
7065 fn refresh_selected_text_highlights(
7066 &mut self,
7067 on_buffer_edit: bool,
7068 window: &mut Window,
7069 cx: &mut Context<Editor>,
7070 ) {
7071 let Some((query_text, query_range)) =
7072 self.prepare_highlight_query_from_selection(window, cx)
7073 else {
7074 self.clear_background_highlights::<SelectedTextHighlight>(cx);
7075 self.quick_selection_highlight_task.take();
7076 self.debounced_selection_highlight_task.take();
7077 return;
7078 };
7079 let multi_buffer_snapshot = self.buffer().read(cx).snapshot(cx);
7080 if on_buffer_edit
7081 || self
7082 .quick_selection_highlight_task
7083 .as_ref()
7084 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7085 {
7086 let multi_buffer_visible_start = self
7087 .scroll_manager
7088 .anchor()
7089 .anchor
7090 .to_point(&multi_buffer_snapshot);
7091 let multi_buffer_visible_end = multi_buffer_snapshot.clip_point(
7092 multi_buffer_visible_start
7093 + Point::new(self.visible_line_count().unwrap_or(0.).ceil() as u32, 0),
7094 Bias::Left,
7095 );
7096 let multi_buffer_visible_range = multi_buffer_visible_start..multi_buffer_visible_end;
7097 self.quick_selection_highlight_task = Some((
7098 query_range.clone(),
7099 self.update_selection_occurrence_highlights(
7100 query_text.clone(),
7101 query_range.clone(),
7102 multi_buffer_visible_range,
7103 false,
7104 window,
7105 cx,
7106 ),
7107 ));
7108 }
7109 if on_buffer_edit
7110 || self
7111 .debounced_selection_highlight_task
7112 .as_ref()
7113 .is_none_or(|(prev_anchor_range, _)| prev_anchor_range != &query_range)
7114 {
7115 let multi_buffer_start = multi_buffer_snapshot
7116 .anchor_before(0)
7117 .to_point(&multi_buffer_snapshot);
7118 let multi_buffer_end = multi_buffer_snapshot
7119 .anchor_after(multi_buffer_snapshot.len())
7120 .to_point(&multi_buffer_snapshot);
7121 let multi_buffer_full_range = multi_buffer_start..multi_buffer_end;
7122 self.debounced_selection_highlight_task = Some((
7123 query_range.clone(),
7124 self.update_selection_occurrence_highlights(
7125 query_text,
7126 query_range,
7127 multi_buffer_full_range,
7128 true,
7129 window,
7130 cx,
7131 ),
7132 ));
7133 }
7134 }
7135
7136 pub fn refresh_edit_prediction(
7137 &mut self,
7138 debounce: bool,
7139 user_requested: bool,
7140 window: &mut Window,
7141 cx: &mut Context<Self>,
7142 ) -> Option<()> {
7143 if DisableAiSettings::get_global(cx).disable_ai {
7144 return None;
7145 }
7146
7147 let provider = self.edit_prediction_provider()?;
7148 let cursor = self.selections.newest_anchor().head();
7149 let (buffer, cursor_buffer_position) =
7150 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7151
7152 if !self.edit_predictions_enabled_in_buffer(&buffer, cursor_buffer_position, cx) {
7153 self.discard_edit_prediction(false, cx);
7154 return None;
7155 }
7156
7157 self.update_visible_edit_prediction(window, cx);
7158
7159 if !user_requested
7160 && (!self.should_show_edit_predictions()
7161 || !self.is_focused(window)
7162 || buffer.read(cx).is_empty())
7163 {
7164 self.discard_edit_prediction(false, cx);
7165 return None;
7166 }
7167
7168 provider.refresh(buffer, cursor_buffer_position, debounce, cx);
7169 Some(())
7170 }
7171
7172 fn show_edit_predictions_in_menu(&self) -> bool {
7173 match self.edit_prediction_settings {
7174 EditPredictionSettings::Disabled => false,
7175 EditPredictionSettings::Enabled { show_in_menu, .. } => show_in_menu,
7176 }
7177 }
7178
7179 pub fn edit_predictions_enabled(&self) -> bool {
7180 match self.edit_prediction_settings {
7181 EditPredictionSettings::Disabled => false,
7182 EditPredictionSettings::Enabled { .. } => true,
7183 }
7184 }
7185
7186 fn edit_prediction_requires_modifier(&self) -> bool {
7187 match self.edit_prediction_settings {
7188 EditPredictionSettings::Disabled => false,
7189 EditPredictionSettings::Enabled {
7190 preview_requires_modifier,
7191 ..
7192 } => preview_requires_modifier,
7193 }
7194 }
7195
7196 pub fn update_edit_prediction_settings(&mut self, cx: &mut Context<Self>) {
7197 if self.edit_prediction_provider.is_none() || DisableAiSettings::get_global(cx).disable_ai {
7198 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7199 self.discard_edit_prediction(false, cx);
7200 } else {
7201 let selection = self.selections.newest_anchor();
7202 let cursor = selection.head();
7203
7204 if let Some((buffer, cursor_buffer_position)) =
7205 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7206 {
7207 self.edit_prediction_settings =
7208 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7209 }
7210 }
7211 }
7212
7213 fn edit_prediction_settings_at_position(
7214 &self,
7215 buffer: &Entity<Buffer>,
7216 buffer_position: language::Anchor,
7217 cx: &App,
7218 ) -> EditPredictionSettings {
7219 if !self.mode.is_full()
7220 || !self.show_edit_predictions_override.unwrap_or(true)
7221 || self.edit_predictions_disabled_in_scope(buffer, buffer_position, cx)
7222 {
7223 return EditPredictionSettings::Disabled;
7224 }
7225
7226 let buffer = buffer.read(cx);
7227
7228 let file = buffer.file();
7229
7230 if !language_settings(buffer.language().map(|l| l.name()), file, cx).show_edit_predictions {
7231 return EditPredictionSettings::Disabled;
7232 };
7233
7234 let by_provider = matches!(
7235 self.menu_edit_predictions_policy,
7236 MenuEditPredictionsPolicy::ByProvider
7237 );
7238
7239 let show_in_menu = by_provider
7240 && self
7241 .edit_prediction_provider
7242 .as_ref()
7243 .is_some_and(|provider| provider.provider.show_completions_in_menu());
7244
7245 let preview_requires_modifier =
7246 all_language_settings(file, cx).edit_predictions_mode() == EditPredictionsMode::Subtle;
7247
7248 EditPredictionSettings::Enabled {
7249 show_in_menu,
7250 preview_requires_modifier,
7251 }
7252 }
7253
7254 fn should_show_edit_predictions(&self) -> bool {
7255 self.snippet_stack.is_empty() && self.edit_predictions_enabled()
7256 }
7257
7258 pub fn edit_prediction_preview_is_active(&self) -> bool {
7259 matches!(
7260 self.edit_prediction_preview,
7261 EditPredictionPreview::Active { .. }
7262 )
7263 }
7264
7265 pub fn edit_predictions_enabled_at_cursor(&self, cx: &App) -> bool {
7266 let cursor = self.selections.newest_anchor().head();
7267 if let Some((buffer, cursor_position)) =
7268 self.buffer.read(cx).text_anchor_for_position(cursor, cx)
7269 {
7270 self.edit_predictions_enabled_in_buffer(&buffer, cursor_position, cx)
7271 } else {
7272 false
7273 }
7274 }
7275
7276 pub fn supports_minimap(&self, cx: &App) -> bool {
7277 !self.minimap_visibility.disabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton
7278 }
7279
7280 fn edit_predictions_enabled_in_buffer(
7281 &self,
7282 buffer: &Entity<Buffer>,
7283 buffer_position: language::Anchor,
7284 cx: &App,
7285 ) -> bool {
7286 maybe!({
7287 if self.read_only(cx) {
7288 return Some(false);
7289 }
7290 let provider = self.edit_prediction_provider()?;
7291 if !provider.is_enabled(buffer, buffer_position, cx) {
7292 return Some(false);
7293 }
7294 let buffer = buffer.read(cx);
7295 let Some(file) = buffer.file() else {
7296 return Some(true);
7297 };
7298 let settings = all_language_settings(Some(file), cx);
7299 Some(settings.edit_predictions_enabled_for_file(file, cx))
7300 })
7301 .unwrap_or(false)
7302 }
7303
7304 fn cycle_edit_prediction(
7305 &mut self,
7306 direction: Direction,
7307 window: &mut Window,
7308 cx: &mut Context<Self>,
7309 ) -> Option<()> {
7310 let provider = self.edit_prediction_provider()?;
7311 let cursor = self.selections.newest_anchor().head();
7312 let (buffer, cursor_buffer_position) =
7313 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7314 if self.edit_predictions_hidden_for_vim_mode || !self.should_show_edit_predictions() {
7315 return None;
7316 }
7317
7318 provider.cycle(buffer, cursor_buffer_position, direction, cx);
7319 self.update_visible_edit_prediction(window, cx);
7320
7321 Some(())
7322 }
7323
7324 pub fn show_edit_prediction(
7325 &mut self,
7326 _: &ShowEditPrediction,
7327 window: &mut Window,
7328 cx: &mut Context<Self>,
7329 ) {
7330 if !self.has_active_edit_prediction() {
7331 self.refresh_edit_prediction(false, true, window, cx);
7332 return;
7333 }
7334
7335 self.update_visible_edit_prediction(window, cx);
7336 }
7337
7338 pub fn display_cursor_names(
7339 &mut self,
7340 _: &DisplayCursorNames,
7341 window: &mut Window,
7342 cx: &mut Context<Self>,
7343 ) {
7344 self.show_cursor_names(window, cx);
7345 }
7346
7347 fn show_cursor_names(&mut self, window: &mut Window, cx: &mut Context<Self>) {
7348 self.show_cursor_names = true;
7349 cx.notify();
7350 cx.spawn_in(window, async move |this, cx| {
7351 cx.background_executor().timer(CURSORS_VISIBLE_FOR).await;
7352 this.update(cx, |this, cx| {
7353 this.show_cursor_names = false;
7354 cx.notify()
7355 })
7356 .ok()
7357 })
7358 .detach();
7359 }
7360
7361 pub fn next_edit_prediction(
7362 &mut self,
7363 _: &NextEditPrediction,
7364 window: &mut Window,
7365 cx: &mut Context<Self>,
7366 ) {
7367 if self.has_active_edit_prediction() {
7368 self.cycle_edit_prediction(Direction::Next, window, cx);
7369 } else {
7370 let is_copilot_disabled = self
7371 .refresh_edit_prediction(false, true, window, cx)
7372 .is_none();
7373 if is_copilot_disabled {
7374 cx.propagate();
7375 }
7376 }
7377 }
7378
7379 pub fn previous_edit_prediction(
7380 &mut self,
7381 _: &PreviousEditPrediction,
7382 window: &mut Window,
7383 cx: &mut Context<Self>,
7384 ) {
7385 if self.has_active_edit_prediction() {
7386 self.cycle_edit_prediction(Direction::Prev, window, cx);
7387 } else {
7388 let is_copilot_disabled = self
7389 .refresh_edit_prediction(false, true, window, cx)
7390 .is_none();
7391 if is_copilot_disabled {
7392 cx.propagate();
7393 }
7394 }
7395 }
7396
7397 pub fn accept_edit_prediction(
7398 &mut self,
7399 _: &AcceptEditPrediction,
7400 window: &mut Window,
7401 cx: &mut Context<Self>,
7402 ) {
7403 if self.show_edit_predictions_in_menu() {
7404 self.hide_context_menu(window, cx);
7405 }
7406
7407 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7408 return;
7409 };
7410
7411 match &active_edit_prediction.completion {
7412 EditPrediction::MoveWithin { target, .. } => {
7413 let target = *target;
7414
7415 if let Some(position_map) = &self.last_position_map {
7416 if position_map
7417 .visible_row_range
7418 .contains(&target.to_display_point(&position_map.snapshot).row())
7419 || !self.edit_prediction_requires_modifier()
7420 {
7421 self.unfold_ranges(&[target..target], true, false, cx);
7422 // Note that this is also done in vim's handler of the Tab action.
7423 self.change_selections(
7424 SelectionEffects::scroll(Autoscroll::newest()),
7425 window,
7426 cx,
7427 |selections| {
7428 selections.select_anchor_ranges([target..target]);
7429 },
7430 );
7431 self.clear_row_highlights::<EditPredictionPreview>();
7432
7433 self.edit_prediction_preview
7434 .set_previous_scroll_position(None);
7435 } else {
7436 self.edit_prediction_preview
7437 .set_previous_scroll_position(Some(
7438 position_map.snapshot.scroll_anchor,
7439 ));
7440
7441 self.highlight_rows::<EditPredictionPreview>(
7442 target..target,
7443 cx.theme().colors().editor_highlighted_line_background,
7444 RowHighlightOptions {
7445 autoscroll: true,
7446 ..Default::default()
7447 },
7448 cx,
7449 );
7450 self.request_autoscroll(Autoscroll::fit(), cx);
7451 }
7452 }
7453 }
7454 EditPrediction::MoveOutside { snapshot, target } => {
7455 if let Some(workspace) = self.workspace() {
7456 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7457 .detach_and_log_err(cx);
7458 }
7459 }
7460 EditPrediction::Edit { edits, .. } => {
7461 self.report_edit_prediction_event(
7462 active_edit_prediction.completion_id.clone(),
7463 true,
7464 cx,
7465 );
7466
7467 if let Some(provider) = self.edit_prediction_provider() {
7468 provider.accept(cx);
7469 }
7470
7471 // Store the transaction ID and selections before applying the edit
7472 let transaction_id_prev = self.buffer.read(cx).last_transaction_id(cx);
7473
7474 let snapshot = self.buffer.read(cx).snapshot(cx);
7475 let last_edit_end = edits.last().unwrap().0.end.bias_right(&snapshot);
7476
7477 self.buffer.update(cx, |buffer, cx| {
7478 buffer.edit(edits.iter().cloned(), None, cx)
7479 });
7480
7481 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
7482 s.select_anchor_ranges([last_edit_end..last_edit_end]);
7483 });
7484
7485 let selections = self.selections.disjoint_anchors_arc();
7486 if let Some(transaction_id_now) = self.buffer.read(cx).last_transaction_id(cx) {
7487 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
7488 if has_new_transaction {
7489 self.selection_history
7490 .insert_transaction(transaction_id_now, selections);
7491 }
7492 }
7493
7494 self.update_visible_edit_prediction(window, cx);
7495 if self.active_edit_prediction.is_none() {
7496 self.refresh_edit_prediction(true, true, window, cx);
7497 }
7498
7499 cx.notify();
7500 }
7501 }
7502
7503 self.edit_prediction_requires_modifier_in_indent_conflict = false;
7504 }
7505
7506 pub fn accept_partial_edit_prediction(
7507 &mut self,
7508 _: &AcceptPartialEditPrediction,
7509 window: &mut Window,
7510 cx: &mut Context<Self>,
7511 ) {
7512 let Some(active_edit_prediction) = self.active_edit_prediction.as_ref() else {
7513 return;
7514 };
7515 if self.selections.count() != 1 {
7516 return;
7517 }
7518
7519 match &active_edit_prediction.completion {
7520 EditPrediction::MoveWithin { target, .. } => {
7521 let target = *target;
7522 self.change_selections(
7523 SelectionEffects::scroll(Autoscroll::newest()),
7524 window,
7525 cx,
7526 |selections| {
7527 selections.select_anchor_ranges([target..target]);
7528 },
7529 );
7530 }
7531 EditPrediction::MoveOutside { snapshot, target } => {
7532 if let Some(workspace) = self.workspace() {
7533 Self::open_editor_at_anchor(snapshot, *target, &workspace, window, cx)
7534 .detach_and_log_err(cx);
7535 }
7536 }
7537 EditPrediction::Edit { edits, .. } => {
7538 self.report_edit_prediction_event(
7539 active_edit_prediction.completion_id.clone(),
7540 true,
7541 cx,
7542 );
7543
7544 // Find an insertion that starts at the cursor position.
7545 let snapshot = self.buffer.read(cx).snapshot(cx);
7546 let cursor_offset = self
7547 .selections
7548 .newest::<usize>(&self.display_snapshot(cx))
7549 .head();
7550 let insertion = edits.iter().find_map(|(range, text)| {
7551 let range = range.to_offset(&snapshot);
7552 if range.is_empty() && range.start == cursor_offset {
7553 Some(text)
7554 } else {
7555 None
7556 }
7557 });
7558
7559 if let Some(text) = insertion {
7560 let mut partial_completion = text
7561 .chars()
7562 .by_ref()
7563 .take_while(|c| c.is_alphabetic())
7564 .collect::<String>();
7565 if partial_completion.is_empty() {
7566 partial_completion = text
7567 .chars()
7568 .by_ref()
7569 .take_while(|c| c.is_whitespace() || !c.is_alphabetic())
7570 .collect::<String>();
7571 }
7572
7573 cx.emit(EditorEvent::InputHandled {
7574 utf16_range_to_replace: None,
7575 text: partial_completion.clone().into(),
7576 });
7577
7578 self.insert_with_autoindent_mode(&partial_completion, None, window, cx);
7579
7580 self.refresh_edit_prediction(true, true, window, cx);
7581 cx.notify();
7582 } else {
7583 self.accept_edit_prediction(&Default::default(), window, cx);
7584 }
7585 }
7586 }
7587 }
7588
7589 fn discard_edit_prediction(
7590 &mut self,
7591 should_report_edit_prediction_event: bool,
7592 cx: &mut Context<Self>,
7593 ) -> bool {
7594 if should_report_edit_prediction_event {
7595 let completion_id = self
7596 .active_edit_prediction
7597 .as_ref()
7598 .and_then(|active_completion| active_completion.completion_id.clone());
7599
7600 self.report_edit_prediction_event(completion_id, false, cx);
7601 }
7602
7603 if let Some(provider) = self.edit_prediction_provider() {
7604 provider.discard(cx);
7605 }
7606
7607 self.take_active_edit_prediction(cx)
7608 }
7609
7610 fn report_edit_prediction_event(&self, id: Option<SharedString>, accepted: bool, cx: &App) {
7611 let Some(provider) = self.edit_prediction_provider() else {
7612 return;
7613 };
7614
7615 let Some((_, buffer, _)) = self
7616 .buffer
7617 .read(cx)
7618 .excerpt_containing(self.selections.newest_anchor().head(), cx)
7619 else {
7620 return;
7621 };
7622
7623 let extension = buffer
7624 .read(cx)
7625 .file()
7626 .and_then(|file| Some(file.path().extension()?.to_string()));
7627
7628 let event_type = match accepted {
7629 true => "Edit Prediction Accepted",
7630 false => "Edit Prediction Discarded",
7631 };
7632 telemetry::event!(
7633 event_type,
7634 provider = provider.name(),
7635 prediction_id = id,
7636 suggestion_accepted = accepted,
7637 file_extension = extension,
7638 );
7639 }
7640
7641 fn open_editor_at_anchor(
7642 snapshot: &language::BufferSnapshot,
7643 target: language::Anchor,
7644 workspace: &Entity<Workspace>,
7645 window: &mut Window,
7646 cx: &mut App,
7647 ) -> Task<Result<()>> {
7648 workspace.update(cx, |workspace, cx| {
7649 let path = snapshot.file().map(|file| file.full_path(cx));
7650 let Some(path) =
7651 path.and_then(|path| workspace.project().read(cx).find_project_path(path, cx))
7652 else {
7653 return Task::ready(Err(anyhow::anyhow!("Project path not found")));
7654 };
7655 let target = text::ToPoint::to_point(&target, snapshot);
7656 let item = workspace.open_path(path, None, true, window, cx);
7657 window.spawn(cx, async move |cx| {
7658 let Some(editor) = item.await?.downcast::<Editor>() else {
7659 return Ok(());
7660 };
7661 editor
7662 .update_in(cx, |editor, window, cx| {
7663 editor.go_to_singleton_buffer_point(target, window, cx);
7664 })
7665 .ok();
7666 anyhow::Ok(())
7667 })
7668 })
7669 }
7670
7671 pub fn has_active_edit_prediction(&self) -> bool {
7672 self.active_edit_prediction.is_some()
7673 }
7674
7675 fn take_active_edit_prediction(&mut self, cx: &mut Context<Self>) -> bool {
7676 let Some(active_edit_prediction) = self.active_edit_prediction.take() else {
7677 return false;
7678 };
7679
7680 self.splice_inlays(&active_edit_prediction.inlay_ids, Default::default(), cx);
7681 self.clear_highlights::<EditPredictionHighlight>(cx);
7682 self.stale_edit_prediction_in_menu = Some(active_edit_prediction);
7683 true
7684 }
7685
7686 /// Returns true when we're displaying the edit prediction popover below the cursor
7687 /// like we are not previewing and the LSP autocomplete menu is visible
7688 /// or we are in `when_holding_modifier` mode.
7689 pub fn edit_prediction_visible_in_cursor_popover(&self, has_completion: bool) -> bool {
7690 if self.edit_prediction_preview_is_active()
7691 || !self.show_edit_predictions_in_menu()
7692 || !self.edit_predictions_enabled()
7693 {
7694 return false;
7695 }
7696
7697 if self.has_visible_completions_menu() {
7698 return true;
7699 }
7700
7701 has_completion && self.edit_prediction_requires_modifier()
7702 }
7703
7704 fn handle_modifiers_changed(
7705 &mut self,
7706 modifiers: Modifiers,
7707 position_map: &PositionMap,
7708 window: &mut Window,
7709 cx: &mut Context<Self>,
7710 ) {
7711 // Ensure that the edit prediction preview is updated, even when not
7712 // enabled, if there's an active edit prediction preview.
7713 if self.show_edit_predictions_in_menu()
7714 || matches!(
7715 self.edit_prediction_preview,
7716 EditPredictionPreview::Active { .. }
7717 )
7718 {
7719 self.update_edit_prediction_preview(&modifiers, window, cx);
7720 }
7721
7722 self.update_selection_mode(&modifiers, position_map, window, cx);
7723
7724 let mouse_position = window.mouse_position();
7725 if !position_map.text_hitbox.is_hovered(window) {
7726 return;
7727 }
7728
7729 self.update_hovered_link(
7730 position_map.point_for_position(mouse_position),
7731 &position_map.snapshot,
7732 modifiers,
7733 window,
7734 cx,
7735 )
7736 }
7737
7738 fn is_cmd_or_ctrl_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7739 match EditorSettings::get_global(cx).multi_cursor_modifier {
7740 MultiCursorModifier::Alt => modifiers.secondary(),
7741 MultiCursorModifier::CmdOrCtrl => modifiers.alt,
7742 }
7743 }
7744
7745 fn is_alt_pressed(modifiers: &Modifiers, cx: &mut Context<Self>) -> bool {
7746 match EditorSettings::get_global(cx).multi_cursor_modifier {
7747 MultiCursorModifier::Alt => modifiers.alt,
7748 MultiCursorModifier::CmdOrCtrl => modifiers.secondary(),
7749 }
7750 }
7751
7752 fn columnar_selection_mode(
7753 modifiers: &Modifiers,
7754 cx: &mut Context<Self>,
7755 ) -> Option<ColumnarMode> {
7756 if modifiers.shift && modifiers.number_of_modifiers() == 2 {
7757 if Self::is_cmd_or_ctrl_pressed(modifiers, cx) {
7758 Some(ColumnarMode::FromMouse)
7759 } else if Self::is_alt_pressed(modifiers, cx) {
7760 Some(ColumnarMode::FromSelection)
7761 } else {
7762 None
7763 }
7764 } else {
7765 None
7766 }
7767 }
7768
7769 fn update_selection_mode(
7770 &mut self,
7771 modifiers: &Modifiers,
7772 position_map: &PositionMap,
7773 window: &mut Window,
7774 cx: &mut Context<Self>,
7775 ) {
7776 let Some(mode) = Self::columnar_selection_mode(modifiers, cx) else {
7777 return;
7778 };
7779 if self.selections.pending_anchor().is_none() {
7780 return;
7781 }
7782
7783 let mouse_position = window.mouse_position();
7784 let point_for_position = position_map.point_for_position(mouse_position);
7785 let position = point_for_position.previous_valid;
7786
7787 self.select(
7788 SelectPhase::BeginColumnar {
7789 position,
7790 reset: false,
7791 mode,
7792 goal_column: point_for_position.exact_unclipped.column(),
7793 },
7794 window,
7795 cx,
7796 );
7797 }
7798
7799 fn update_edit_prediction_preview(
7800 &mut self,
7801 modifiers: &Modifiers,
7802 window: &mut Window,
7803 cx: &mut Context<Self>,
7804 ) {
7805 let mut modifiers_held = false;
7806 if let Some(accept_keystroke) = self
7807 .accept_edit_prediction_keybind(false, window, cx)
7808 .keystroke()
7809 {
7810 modifiers_held = modifiers_held
7811 || (accept_keystroke.modifiers() == modifiers
7812 && accept_keystroke.modifiers().modified());
7813 };
7814 if let Some(accept_partial_keystroke) = self
7815 .accept_edit_prediction_keybind(true, window, cx)
7816 .keystroke()
7817 {
7818 modifiers_held = modifiers_held
7819 || (accept_partial_keystroke.modifiers() == modifiers
7820 && accept_partial_keystroke.modifiers().modified());
7821 }
7822
7823 if modifiers_held {
7824 if matches!(
7825 self.edit_prediction_preview,
7826 EditPredictionPreview::Inactive { .. }
7827 ) {
7828 self.edit_prediction_preview = EditPredictionPreview::Active {
7829 previous_scroll_position: None,
7830 since: Instant::now(),
7831 };
7832
7833 self.update_visible_edit_prediction(window, cx);
7834 cx.notify();
7835 }
7836 } else if let EditPredictionPreview::Active {
7837 previous_scroll_position,
7838 since,
7839 } = self.edit_prediction_preview
7840 {
7841 if let (Some(previous_scroll_position), Some(position_map)) =
7842 (previous_scroll_position, self.last_position_map.as_ref())
7843 {
7844 self.set_scroll_position(
7845 previous_scroll_position
7846 .scroll_position(&position_map.snapshot.display_snapshot),
7847 window,
7848 cx,
7849 );
7850 }
7851
7852 self.edit_prediction_preview = EditPredictionPreview::Inactive {
7853 released_too_fast: since.elapsed() < Duration::from_millis(200),
7854 };
7855 self.clear_row_highlights::<EditPredictionPreview>();
7856 self.update_visible_edit_prediction(window, cx);
7857 cx.notify();
7858 }
7859 }
7860
7861 fn update_visible_edit_prediction(
7862 &mut self,
7863 _window: &mut Window,
7864 cx: &mut Context<Self>,
7865 ) -> Option<()> {
7866 if DisableAiSettings::get_global(cx).disable_ai {
7867 return None;
7868 }
7869
7870 if self.ime_transaction.is_some() {
7871 self.discard_edit_prediction(false, cx);
7872 return None;
7873 }
7874
7875 let selection = self.selections.newest_anchor();
7876 let cursor = selection.head();
7877 let multibuffer = self.buffer.read(cx).snapshot(cx);
7878 let offset_selection = selection.map(|endpoint| endpoint.to_offset(&multibuffer));
7879 let excerpt_id = cursor.excerpt_id;
7880
7881 let show_in_menu = self.show_edit_predictions_in_menu();
7882 let completions_menu_has_precedence = !show_in_menu
7883 && (self.context_menu.borrow().is_some()
7884 || (!self.completion_tasks.is_empty() && !self.has_active_edit_prediction()));
7885
7886 if completions_menu_has_precedence
7887 || !offset_selection.is_empty()
7888 || self
7889 .active_edit_prediction
7890 .as_ref()
7891 .is_some_and(|completion| {
7892 let Some(invalidation_range) = completion.invalidation_range.as_ref() else {
7893 return false;
7894 };
7895 let invalidation_range = invalidation_range.to_offset(&multibuffer);
7896 let invalidation_range = invalidation_range.start..=invalidation_range.end;
7897 !invalidation_range.contains(&offset_selection.head())
7898 })
7899 {
7900 self.discard_edit_prediction(false, cx);
7901 return None;
7902 }
7903
7904 self.take_active_edit_prediction(cx);
7905 let Some(provider) = self.edit_prediction_provider() else {
7906 self.edit_prediction_settings = EditPredictionSettings::Disabled;
7907 return None;
7908 };
7909
7910 let (buffer, cursor_buffer_position) =
7911 self.buffer.read(cx).text_anchor_for_position(cursor, cx)?;
7912
7913 self.edit_prediction_settings =
7914 self.edit_prediction_settings_at_position(&buffer, cursor_buffer_position, cx);
7915
7916 self.edit_prediction_indent_conflict = multibuffer.is_line_whitespace_upto(cursor);
7917
7918 if self.edit_prediction_indent_conflict {
7919 let cursor_point = cursor.to_point(&multibuffer);
7920
7921 let indents = multibuffer.suggested_indents(cursor_point.row..cursor_point.row + 1, cx);
7922
7923 if let Some((_, indent)) = indents.iter().next()
7924 && indent.len == cursor_point.column
7925 {
7926 self.edit_prediction_indent_conflict = false;
7927 }
7928 }
7929
7930 let edit_prediction = provider.suggest(&buffer, cursor_buffer_position, cx)?;
7931
7932 let (completion_id, edits, edit_preview) = match edit_prediction {
7933 edit_prediction::EditPrediction::Local {
7934 id,
7935 edits,
7936 edit_preview,
7937 } => (id, edits, edit_preview),
7938 edit_prediction::EditPrediction::Jump {
7939 id,
7940 snapshot,
7941 target,
7942 } => {
7943 self.stale_edit_prediction_in_menu = None;
7944 self.active_edit_prediction = Some(EditPredictionState {
7945 inlay_ids: vec![],
7946 completion: EditPrediction::MoveOutside { snapshot, target },
7947 completion_id: id,
7948 invalidation_range: None,
7949 });
7950 cx.notify();
7951 return Some(());
7952 }
7953 };
7954
7955 let edits = edits
7956 .into_iter()
7957 .flat_map(|(range, new_text)| {
7958 Some((
7959 multibuffer.anchor_range_in_excerpt(excerpt_id, range)?,
7960 new_text,
7961 ))
7962 })
7963 .collect::<Vec<_>>();
7964 if edits.is_empty() {
7965 return None;
7966 }
7967
7968 let first_edit_start = edits.first().unwrap().0.start;
7969 let first_edit_start_point = first_edit_start.to_point(&multibuffer);
7970 let edit_start_row = first_edit_start_point.row.saturating_sub(2);
7971
7972 let last_edit_end = edits.last().unwrap().0.end;
7973 let last_edit_end_point = last_edit_end.to_point(&multibuffer);
7974 let edit_end_row = cmp::min(multibuffer.max_point().row, last_edit_end_point.row + 2);
7975
7976 let cursor_row = cursor.to_point(&multibuffer).row;
7977
7978 let snapshot = multibuffer.buffer_for_excerpt(excerpt_id).cloned()?;
7979
7980 let mut inlay_ids = Vec::new();
7981 let invalidation_row_range;
7982 let move_invalidation_row_range = if cursor_row < edit_start_row {
7983 Some(cursor_row..edit_end_row)
7984 } else if cursor_row > edit_end_row {
7985 Some(edit_start_row..cursor_row)
7986 } else {
7987 None
7988 };
7989 let supports_jump = self
7990 .edit_prediction_provider
7991 .as_ref()
7992 .map(|provider| provider.provider.supports_jump_to_edit())
7993 .unwrap_or(true);
7994
7995 let is_move = supports_jump
7996 && (move_invalidation_row_range.is_some() || self.edit_predictions_hidden_for_vim_mode);
7997 let completion = if is_move {
7998 invalidation_row_range =
7999 move_invalidation_row_range.unwrap_or(edit_start_row..edit_end_row);
8000 let target = first_edit_start;
8001 EditPrediction::MoveWithin { target, snapshot }
8002 } else {
8003 let show_completions_in_buffer = !self.edit_prediction_visible_in_cursor_popover(true)
8004 && !self.edit_predictions_hidden_for_vim_mode;
8005
8006 if show_completions_in_buffer {
8007 if edits
8008 .iter()
8009 .all(|(range, _)| range.to_offset(&multibuffer).is_empty())
8010 {
8011 let mut inlays = Vec::new();
8012 for (range, new_text) in &edits {
8013 let inlay = Inlay::edit_prediction(
8014 post_inc(&mut self.next_inlay_id),
8015 range.start,
8016 new_text.as_ref(),
8017 );
8018 inlay_ids.push(inlay.id);
8019 inlays.push(inlay);
8020 }
8021
8022 self.splice_inlays(&[], inlays, cx);
8023 } else {
8024 let background_color = cx.theme().status().deleted_background;
8025 self.highlight_text::<EditPredictionHighlight>(
8026 edits.iter().map(|(range, _)| range.clone()).collect(),
8027 HighlightStyle {
8028 background_color: Some(background_color),
8029 ..Default::default()
8030 },
8031 cx,
8032 );
8033 }
8034 }
8035
8036 invalidation_row_range = edit_start_row..edit_end_row;
8037
8038 let display_mode = if all_edits_insertions_or_deletions(&edits, &multibuffer) {
8039 if provider.show_tab_accept_marker() {
8040 EditDisplayMode::TabAccept
8041 } else {
8042 EditDisplayMode::Inline
8043 }
8044 } else {
8045 EditDisplayMode::DiffPopover
8046 };
8047
8048 EditPrediction::Edit {
8049 edits,
8050 edit_preview,
8051 display_mode,
8052 snapshot,
8053 }
8054 };
8055
8056 let invalidation_range = multibuffer
8057 .anchor_before(Point::new(invalidation_row_range.start, 0))
8058 ..multibuffer.anchor_after(Point::new(
8059 invalidation_row_range.end,
8060 multibuffer.line_len(MultiBufferRow(invalidation_row_range.end)),
8061 ));
8062
8063 self.stale_edit_prediction_in_menu = None;
8064 self.active_edit_prediction = Some(EditPredictionState {
8065 inlay_ids,
8066 completion,
8067 completion_id,
8068 invalidation_range: Some(invalidation_range),
8069 });
8070
8071 cx.notify();
8072
8073 Some(())
8074 }
8075
8076 pub fn edit_prediction_provider(&self) -> Option<Arc<dyn EditPredictionProviderHandle>> {
8077 Some(self.edit_prediction_provider.as_ref()?.provider.clone())
8078 }
8079
8080 fn clear_tasks(&mut self) {
8081 self.tasks.clear()
8082 }
8083
8084 fn insert_tasks(&mut self, key: (BufferId, BufferRow), value: RunnableTasks) {
8085 if self.tasks.insert(key, value).is_some() {
8086 // This case should hopefully be rare, but just in case...
8087 log::error!(
8088 "multiple different run targets found on a single line, only the last target will be rendered"
8089 )
8090 }
8091 }
8092
8093 /// Get all display points of breakpoints that will be rendered within editor
8094 ///
8095 /// This function is used to handle overlaps between breakpoints and Code action/runner symbol.
8096 /// It's also used to set the color of line numbers with breakpoints to the breakpoint color.
8097 /// TODO debugger: Use this function to color toggle symbols that house nested breakpoints
8098 fn active_breakpoints(
8099 &self,
8100 range: Range<DisplayRow>,
8101 window: &mut Window,
8102 cx: &mut Context<Self>,
8103 ) -> HashMap<DisplayRow, (Anchor, Breakpoint, Option<BreakpointSessionState>)> {
8104 let mut breakpoint_display_points = HashMap::default();
8105
8106 let Some(breakpoint_store) = self.breakpoint_store.clone() else {
8107 return breakpoint_display_points;
8108 };
8109
8110 let snapshot = self.snapshot(window, cx);
8111
8112 let multi_buffer_snapshot = snapshot.buffer_snapshot();
8113 let Some(project) = self.project() else {
8114 return breakpoint_display_points;
8115 };
8116
8117 let range = snapshot.display_point_to_point(DisplayPoint::new(range.start, 0), Bias::Left)
8118 ..snapshot.display_point_to_point(DisplayPoint::new(range.end, 0), Bias::Right);
8119
8120 for (buffer_snapshot, range, excerpt_id) in
8121 multi_buffer_snapshot.range_to_buffer_ranges(range)
8122 {
8123 let Some(buffer) = project
8124 .read(cx)
8125 .buffer_for_id(buffer_snapshot.remote_id(), cx)
8126 else {
8127 continue;
8128 };
8129 let breakpoints = breakpoint_store.read(cx).breakpoints(
8130 &buffer,
8131 Some(
8132 buffer_snapshot.anchor_before(range.start)
8133 ..buffer_snapshot.anchor_after(range.end),
8134 ),
8135 buffer_snapshot,
8136 cx,
8137 );
8138 for (breakpoint, state) in breakpoints {
8139 let multi_buffer_anchor =
8140 Anchor::in_buffer(excerpt_id, buffer_snapshot.remote_id(), breakpoint.position);
8141 let position = multi_buffer_anchor
8142 .to_point(&multi_buffer_snapshot)
8143 .to_display_point(&snapshot);
8144
8145 breakpoint_display_points.insert(
8146 position.row(),
8147 (multi_buffer_anchor, breakpoint.bp.clone(), state),
8148 );
8149 }
8150 }
8151
8152 breakpoint_display_points
8153 }
8154
8155 fn breakpoint_context_menu(
8156 &self,
8157 anchor: Anchor,
8158 window: &mut Window,
8159 cx: &mut Context<Self>,
8160 ) -> Entity<ui::ContextMenu> {
8161 let weak_editor = cx.weak_entity();
8162 let focus_handle = self.focus_handle(cx);
8163
8164 let row = self
8165 .buffer
8166 .read(cx)
8167 .snapshot(cx)
8168 .summary_for_anchor::<Point>(&anchor)
8169 .row;
8170
8171 let breakpoint = self
8172 .breakpoint_at_row(row, window, cx)
8173 .map(|(anchor, bp)| (anchor, Arc::from(bp)));
8174
8175 let log_breakpoint_msg = if breakpoint.as_ref().is_some_and(|bp| bp.1.message.is_some()) {
8176 "Edit Log Breakpoint"
8177 } else {
8178 "Set Log Breakpoint"
8179 };
8180
8181 let condition_breakpoint_msg = if breakpoint
8182 .as_ref()
8183 .is_some_and(|bp| bp.1.condition.is_some())
8184 {
8185 "Edit Condition Breakpoint"
8186 } else {
8187 "Set Condition Breakpoint"
8188 };
8189
8190 let hit_condition_breakpoint_msg = if breakpoint
8191 .as_ref()
8192 .is_some_and(|bp| bp.1.hit_condition.is_some())
8193 {
8194 "Edit Hit Condition Breakpoint"
8195 } else {
8196 "Set Hit Condition Breakpoint"
8197 };
8198
8199 let set_breakpoint_msg = if breakpoint.as_ref().is_some() {
8200 "Unset Breakpoint"
8201 } else {
8202 "Set Breakpoint"
8203 };
8204
8205 let run_to_cursor = window.is_action_available(&RunToCursor, cx);
8206
8207 let toggle_state_msg = breakpoint.as_ref().map_or(None, |bp| match bp.1.state {
8208 BreakpointState::Enabled => Some("Disable"),
8209 BreakpointState::Disabled => Some("Enable"),
8210 });
8211
8212 let (anchor, breakpoint) =
8213 breakpoint.unwrap_or_else(|| (anchor, Arc::new(Breakpoint::new_standard())));
8214
8215 ui::ContextMenu::build(window, cx, |menu, _, _cx| {
8216 menu.on_blur_subscription(Subscription::new(|| {}))
8217 .context(focus_handle)
8218 .when(run_to_cursor, |this| {
8219 let weak_editor = weak_editor.clone();
8220 this.entry("Run to cursor", None, move |window, cx| {
8221 weak_editor
8222 .update(cx, |editor, cx| {
8223 editor.change_selections(
8224 SelectionEffects::no_scroll(),
8225 window,
8226 cx,
8227 |s| s.select_ranges([Point::new(row, 0)..Point::new(row, 0)]),
8228 );
8229 })
8230 .ok();
8231
8232 window.dispatch_action(Box::new(RunToCursor), cx);
8233 })
8234 .separator()
8235 })
8236 .when_some(toggle_state_msg, |this, msg| {
8237 this.entry(msg, None, {
8238 let weak_editor = weak_editor.clone();
8239 let breakpoint = breakpoint.clone();
8240 move |_window, cx| {
8241 weak_editor
8242 .update(cx, |this, cx| {
8243 this.edit_breakpoint_at_anchor(
8244 anchor,
8245 breakpoint.as_ref().clone(),
8246 BreakpointEditAction::InvertState,
8247 cx,
8248 );
8249 })
8250 .log_err();
8251 }
8252 })
8253 })
8254 .entry(set_breakpoint_msg, None, {
8255 let weak_editor = weak_editor.clone();
8256 let breakpoint = breakpoint.clone();
8257 move |_window, cx| {
8258 weak_editor
8259 .update(cx, |this, cx| {
8260 this.edit_breakpoint_at_anchor(
8261 anchor,
8262 breakpoint.as_ref().clone(),
8263 BreakpointEditAction::Toggle,
8264 cx,
8265 );
8266 })
8267 .log_err();
8268 }
8269 })
8270 .entry(log_breakpoint_msg, None, {
8271 let breakpoint = breakpoint.clone();
8272 let weak_editor = weak_editor.clone();
8273 move |window, cx| {
8274 weak_editor
8275 .update(cx, |this, cx| {
8276 this.add_edit_breakpoint_block(
8277 anchor,
8278 breakpoint.as_ref(),
8279 BreakpointPromptEditAction::Log,
8280 window,
8281 cx,
8282 );
8283 })
8284 .log_err();
8285 }
8286 })
8287 .entry(condition_breakpoint_msg, None, {
8288 let breakpoint = breakpoint.clone();
8289 let weak_editor = weak_editor.clone();
8290 move |window, cx| {
8291 weak_editor
8292 .update(cx, |this, cx| {
8293 this.add_edit_breakpoint_block(
8294 anchor,
8295 breakpoint.as_ref(),
8296 BreakpointPromptEditAction::Condition,
8297 window,
8298 cx,
8299 );
8300 })
8301 .log_err();
8302 }
8303 })
8304 .entry(hit_condition_breakpoint_msg, None, move |window, cx| {
8305 weak_editor
8306 .update(cx, |this, cx| {
8307 this.add_edit_breakpoint_block(
8308 anchor,
8309 breakpoint.as_ref(),
8310 BreakpointPromptEditAction::HitCondition,
8311 window,
8312 cx,
8313 );
8314 })
8315 .log_err();
8316 })
8317 })
8318 }
8319
8320 fn render_breakpoint(
8321 &self,
8322 position: Anchor,
8323 row: DisplayRow,
8324 breakpoint: &Breakpoint,
8325 state: Option<BreakpointSessionState>,
8326 cx: &mut Context<Self>,
8327 ) -> IconButton {
8328 let is_rejected = state.is_some_and(|s| !s.verified);
8329 // Is it a breakpoint that shows up when hovering over gutter?
8330 let (is_phantom, collides_with_existing) = self.gutter_breakpoint_indicator.0.map_or(
8331 (false, false),
8332 |PhantomBreakpointIndicator {
8333 is_active,
8334 display_row,
8335 collides_with_existing_breakpoint,
8336 }| {
8337 (
8338 is_active && display_row == row,
8339 collides_with_existing_breakpoint,
8340 )
8341 },
8342 );
8343
8344 let (color, icon) = {
8345 let icon = match (&breakpoint.message.is_some(), breakpoint.is_disabled()) {
8346 (false, false) => ui::IconName::DebugBreakpoint,
8347 (true, false) => ui::IconName::DebugLogBreakpoint,
8348 (false, true) => ui::IconName::DebugDisabledBreakpoint,
8349 (true, true) => ui::IconName::DebugDisabledLogBreakpoint,
8350 };
8351
8352 let color = if is_phantom {
8353 Color::Hint
8354 } else if is_rejected {
8355 Color::Disabled
8356 } else {
8357 Color::Debugger
8358 };
8359
8360 (color, icon)
8361 };
8362
8363 let breakpoint = Arc::from(breakpoint.clone());
8364
8365 let alt_as_text = gpui::Keystroke {
8366 modifiers: Modifiers::secondary_key(),
8367 ..Default::default()
8368 };
8369 let primary_action_text = if breakpoint.is_disabled() {
8370 "Enable breakpoint"
8371 } else if is_phantom && !collides_with_existing {
8372 "Set breakpoint"
8373 } else {
8374 "Unset breakpoint"
8375 };
8376 let focus_handle = self.focus_handle.clone();
8377
8378 let meta = if is_rejected {
8379 SharedString::from("No executable code is associated with this line.")
8380 } else if collides_with_existing && !breakpoint.is_disabled() {
8381 SharedString::from(format!(
8382 "{alt_as_text}-click to disable,\nright-click for more options."
8383 ))
8384 } else {
8385 SharedString::from("Right-click for more options.")
8386 };
8387 IconButton::new(("breakpoint_indicator", row.0 as usize), icon)
8388 .icon_size(IconSize::XSmall)
8389 .size(ui::ButtonSize::None)
8390 .when(is_rejected, |this| {
8391 this.indicator(Indicator::icon(Icon::new(IconName::Warning)).color(Color::Warning))
8392 })
8393 .icon_color(color)
8394 .style(ButtonStyle::Transparent)
8395 .on_click(cx.listener({
8396 move |editor, event: &ClickEvent, window, cx| {
8397 let edit_action = if event.modifiers().platform || breakpoint.is_disabled() {
8398 BreakpointEditAction::InvertState
8399 } else {
8400 BreakpointEditAction::Toggle
8401 };
8402
8403 window.focus(&editor.focus_handle(cx));
8404 editor.edit_breakpoint_at_anchor(
8405 position,
8406 breakpoint.as_ref().clone(),
8407 edit_action,
8408 cx,
8409 );
8410 }
8411 }))
8412 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8413 editor.set_breakpoint_context_menu(
8414 row,
8415 Some(position),
8416 event.position(),
8417 window,
8418 cx,
8419 );
8420 }))
8421 .tooltip(move |_window, cx| {
8422 Tooltip::with_meta_in(
8423 primary_action_text,
8424 Some(&ToggleBreakpoint),
8425 meta.clone(),
8426 &focus_handle,
8427 cx,
8428 )
8429 })
8430 }
8431
8432 fn build_tasks_context(
8433 project: &Entity<Project>,
8434 buffer: &Entity<Buffer>,
8435 buffer_row: u32,
8436 tasks: &Arc<RunnableTasks>,
8437 cx: &mut Context<Self>,
8438 ) -> Task<Option<task::TaskContext>> {
8439 let position = Point::new(buffer_row, tasks.column);
8440 let range_start = buffer.read(cx).anchor_at(position, Bias::Right);
8441 let location = Location {
8442 buffer: buffer.clone(),
8443 range: range_start..range_start,
8444 };
8445 // Fill in the environmental variables from the tree-sitter captures
8446 let mut captured_task_variables = TaskVariables::default();
8447 for (capture_name, value) in tasks.extra_variables.clone() {
8448 captured_task_variables.insert(
8449 task::VariableName::Custom(capture_name.into()),
8450 value.clone(),
8451 );
8452 }
8453 project.update(cx, |project, cx| {
8454 project.task_store().update(cx, |task_store, cx| {
8455 task_store.task_context_for_location(captured_task_variables, location, cx)
8456 })
8457 })
8458 }
8459
8460 pub fn spawn_nearest_task(
8461 &mut self,
8462 action: &SpawnNearestTask,
8463 window: &mut Window,
8464 cx: &mut Context<Self>,
8465 ) {
8466 let Some((workspace, _)) = self.workspace.clone() else {
8467 return;
8468 };
8469 let Some(project) = self.project.clone() else {
8470 return;
8471 };
8472
8473 // Try to find a closest, enclosing node using tree-sitter that has a task
8474 let Some((buffer, buffer_row, tasks)) = self
8475 .find_enclosing_node_task(cx)
8476 // Or find the task that's closest in row-distance.
8477 .or_else(|| self.find_closest_task(cx))
8478 else {
8479 return;
8480 };
8481
8482 let reveal_strategy = action.reveal;
8483 let task_context = Self::build_tasks_context(&project, &buffer, buffer_row, &tasks, cx);
8484 cx.spawn_in(window, async move |_, cx| {
8485 let context = task_context.await?;
8486 let (task_source_kind, mut resolved_task) = tasks.resolve(&context).next()?;
8487
8488 let resolved = &mut resolved_task.resolved;
8489 resolved.reveal = reveal_strategy;
8490
8491 workspace
8492 .update_in(cx, |workspace, window, cx| {
8493 workspace.schedule_resolved_task(
8494 task_source_kind,
8495 resolved_task,
8496 false,
8497 window,
8498 cx,
8499 );
8500 })
8501 .ok()
8502 })
8503 .detach();
8504 }
8505
8506 fn find_closest_task(
8507 &mut self,
8508 cx: &mut Context<Self>,
8509 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8510 let cursor_row = self
8511 .selections
8512 .newest_adjusted(&self.display_snapshot(cx))
8513 .head()
8514 .row;
8515
8516 let ((buffer_id, row), tasks) = self
8517 .tasks
8518 .iter()
8519 .min_by_key(|((_, row), _)| cursor_row.abs_diff(*row))?;
8520
8521 let buffer = self.buffer.read(cx).buffer(*buffer_id)?;
8522 let tasks = Arc::new(tasks.to_owned());
8523 Some((buffer, *row, tasks))
8524 }
8525
8526 fn find_enclosing_node_task(
8527 &mut self,
8528 cx: &mut Context<Self>,
8529 ) -> Option<(Entity<Buffer>, u32, Arc<RunnableTasks>)> {
8530 let snapshot = self.buffer.read(cx).snapshot(cx);
8531 let offset = self
8532 .selections
8533 .newest::<usize>(&self.display_snapshot(cx))
8534 .head();
8535 let excerpt = snapshot.excerpt_containing(offset..offset)?;
8536 let buffer_id = excerpt.buffer().remote_id();
8537
8538 let layer = excerpt.buffer().syntax_layer_at(offset)?;
8539 let mut cursor = layer.node().walk();
8540
8541 while cursor.goto_first_child_for_byte(offset).is_some() {
8542 if cursor.node().end_byte() == offset {
8543 cursor.goto_next_sibling();
8544 }
8545 }
8546
8547 // Ascend to the smallest ancestor that contains the range and has a task.
8548 loop {
8549 let node = cursor.node();
8550 let node_range = node.byte_range();
8551 let symbol_start_row = excerpt.buffer().offset_to_point(node.start_byte()).row;
8552
8553 // Check if this node contains our offset
8554 if node_range.start <= offset && node_range.end >= offset {
8555 // If it contains offset, check for task
8556 if let Some(tasks) = self.tasks.get(&(buffer_id, symbol_start_row)) {
8557 let buffer = self.buffer.read(cx).buffer(buffer_id)?;
8558 return Some((buffer, symbol_start_row, Arc::new(tasks.to_owned())));
8559 }
8560 }
8561
8562 if !cursor.goto_parent() {
8563 break;
8564 }
8565 }
8566 None
8567 }
8568
8569 fn render_run_indicator(
8570 &self,
8571 _style: &EditorStyle,
8572 is_active: bool,
8573 row: DisplayRow,
8574 breakpoint: Option<(Anchor, Breakpoint, Option<BreakpointSessionState>)>,
8575 cx: &mut Context<Self>,
8576 ) -> IconButton {
8577 let color = Color::Muted;
8578 let position = breakpoint.as_ref().map(|(anchor, _, _)| *anchor);
8579
8580 IconButton::new(
8581 ("run_indicator", row.0 as usize),
8582 ui::IconName::PlayOutlined,
8583 )
8584 .shape(ui::IconButtonShape::Square)
8585 .icon_size(IconSize::XSmall)
8586 .icon_color(color)
8587 .toggle_state(is_active)
8588 .on_click(cx.listener(move |editor, e: &ClickEvent, window, cx| {
8589 let quick_launch = match e {
8590 ClickEvent::Keyboard(_) => true,
8591 ClickEvent::Mouse(e) => e.down.button == MouseButton::Left,
8592 };
8593
8594 window.focus(&editor.focus_handle(cx));
8595 editor.toggle_code_actions(
8596 &ToggleCodeActions {
8597 deployed_from: Some(CodeActionSource::RunMenu(row)),
8598 quick_launch,
8599 },
8600 window,
8601 cx,
8602 );
8603 }))
8604 .on_right_click(cx.listener(move |editor, event: &ClickEvent, window, cx| {
8605 editor.set_breakpoint_context_menu(row, position, event.position(), window, cx);
8606 }))
8607 }
8608
8609 pub fn context_menu_visible(&self) -> bool {
8610 !self.edit_prediction_preview_is_active()
8611 && self
8612 .context_menu
8613 .borrow()
8614 .as_ref()
8615 .is_some_and(|menu| menu.visible())
8616 }
8617
8618 pub fn context_menu_origin(&self) -> Option<ContextMenuOrigin> {
8619 self.context_menu
8620 .borrow()
8621 .as_ref()
8622 .map(|menu| menu.origin())
8623 }
8624
8625 pub fn set_context_menu_options(&mut self, options: ContextMenuOptions) {
8626 self.context_menu_options = Some(options);
8627 }
8628
8629 const EDIT_PREDICTION_POPOVER_PADDING_X: Pixels = px(24.);
8630 const EDIT_PREDICTION_POPOVER_PADDING_Y: Pixels = px(2.);
8631
8632 fn render_edit_prediction_popover(
8633 &mut self,
8634 text_bounds: &Bounds<Pixels>,
8635 content_origin: gpui::Point<Pixels>,
8636 right_margin: Pixels,
8637 editor_snapshot: &EditorSnapshot,
8638 visible_row_range: Range<DisplayRow>,
8639 scroll_top: ScrollOffset,
8640 scroll_bottom: ScrollOffset,
8641 line_layouts: &[LineWithInvisibles],
8642 line_height: Pixels,
8643 scroll_position: gpui::Point<ScrollOffset>,
8644 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8645 newest_selection_head: Option<DisplayPoint>,
8646 editor_width: Pixels,
8647 style: &EditorStyle,
8648 window: &mut Window,
8649 cx: &mut App,
8650 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8651 if self.mode().is_minimap() {
8652 return None;
8653 }
8654 let active_edit_prediction = self.active_edit_prediction.as_ref()?;
8655
8656 if self.edit_prediction_visible_in_cursor_popover(true) {
8657 return None;
8658 }
8659
8660 match &active_edit_prediction.completion {
8661 EditPrediction::MoveWithin { target, .. } => {
8662 let target_display_point = target.to_display_point(editor_snapshot);
8663
8664 if self.edit_prediction_requires_modifier() {
8665 if !self.edit_prediction_preview_is_active() {
8666 return None;
8667 }
8668
8669 self.render_edit_prediction_modifier_jump_popover(
8670 text_bounds,
8671 content_origin,
8672 visible_row_range,
8673 line_layouts,
8674 line_height,
8675 scroll_pixel_position,
8676 newest_selection_head,
8677 target_display_point,
8678 window,
8679 cx,
8680 )
8681 } else {
8682 self.render_edit_prediction_eager_jump_popover(
8683 text_bounds,
8684 content_origin,
8685 editor_snapshot,
8686 visible_row_range,
8687 scroll_top,
8688 scroll_bottom,
8689 line_height,
8690 scroll_pixel_position,
8691 target_display_point,
8692 editor_width,
8693 window,
8694 cx,
8695 )
8696 }
8697 }
8698 EditPrediction::Edit {
8699 display_mode: EditDisplayMode::Inline,
8700 ..
8701 } => None,
8702 EditPrediction::Edit {
8703 display_mode: EditDisplayMode::TabAccept,
8704 edits,
8705 ..
8706 } => {
8707 let range = &edits.first()?.0;
8708 let target_display_point = range.end.to_display_point(editor_snapshot);
8709
8710 self.render_edit_prediction_end_of_line_popover(
8711 "Accept",
8712 editor_snapshot,
8713 visible_row_range,
8714 target_display_point,
8715 line_height,
8716 scroll_pixel_position,
8717 content_origin,
8718 editor_width,
8719 window,
8720 cx,
8721 )
8722 }
8723 EditPrediction::Edit {
8724 edits,
8725 edit_preview,
8726 display_mode: EditDisplayMode::DiffPopover,
8727 snapshot,
8728 } => self.render_edit_prediction_diff_popover(
8729 text_bounds,
8730 content_origin,
8731 right_margin,
8732 editor_snapshot,
8733 visible_row_range,
8734 line_layouts,
8735 line_height,
8736 scroll_position,
8737 scroll_pixel_position,
8738 newest_selection_head,
8739 editor_width,
8740 style,
8741 edits,
8742 edit_preview,
8743 snapshot,
8744 window,
8745 cx,
8746 ),
8747 EditPrediction::MoveOutside { snapshot, .. } => {
8748 let file_name = snapshot
8749 .file()
8750 .map(|file| file.file_name(cx))
8751 .unwrap_or("untitled");
8752 let mut element = self
8753 .render_edit_prediction_line_popover(
8754 format!("Jump to {file_name}"),
8755 Some(IconName::ZedPredict),
8756 window,
8757 cx,
8758 )
8759 .into_any();
8760
8761 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8762 let origin_x = text_bounds.size.width / 2. - size.width / 2.;
8763 let origin_y = text_bounds.size.height - size.height - px(30.);
8764 let origin = text_bounds.origin + gpui::Point::new(origin_x, origin_y);
8765 element.prepaint_at(origin, window, cx);
8766
8767 Some((element, origin))
8768 }
8769 }
8770 }
8771
8772 fn render_edit_prediction_modifier_jump_popover(
8773 &mut self,
8774 text_bounds: &Bounds<Pixels>,
8775 content_origin: gpui::Point<Pixels>,
8776 visible_row_range: Range<DisplayRow>,
8777 line_layouts: &[LineWithInvisibles],
8778 line_height: Pixels,
8779 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8780 newest_selection_head: Option<DisplayPoint>,
8781 target_display_point: DisplayPoint,
8782 window: &mut Window,
8783 cx: &mut App,
8784 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8785 let scrolled_content_origin =
8786 content_origin - gpui::Point::new(scroll_pixel_position.x.into(), Pixels::ZERO);
8787
8788 const SCROLL_PADDING_Y: Pixels = px(12.);
8789
8790 if target_display_point.row() < visible_row_range.start {
8791 return self.render_edit_prediction_scroll_popover(
8792 |_| SCROLL_PADDING_Y,
8793 IconName::ArrowUp,
8794 visible_row_range,
8795 line_layouts,
8796 newest_selection_head,
8797 scrolled_content_origin,
8798 window,
8799 cx,
8800 );
8801 } else if target_display_point.row() >= visible_row_range.end {
8802 return self.render_edit_prediction_scroll_popover(
8803 |size| text_bounds.size.height - size.height - SCROLL_PADDING_Y,
8804 IconName::ArrowDown,
8805 visible_row_range,
8806 line_layouts,
8807 newest_selection_head,
8808 scrolled_content_origin,
8809 window,
8810 cx,
8811 );
8812 }
8813
8814 const POLE_WIDTH: Pixels = px(2.);
8815
8816 let line_layout =
8817 line_layouts.get(target_display_point.row().minus(visible_row_range.start) as usize)?;
8818 let target_column = target_display_point.column() as usize;
8819
8820 let target_x = line_layout.x_for_index(target_column);
8821 let target_y = (target_display_point.row().as_f64() * f64::from(line_height))
8822 - scroll_pixel_position.y;
8823
8824 let flag_on_right = target_x < text_bounds.size.width / 2.;
8825
8826 let mut border_color = Self::edit_prediction_callout_popover_border_color(cx);
8827 border_color.l += 0.001;
8828
8829 let mut element = v_flex()
8830 .items_end()
8831 .when(flag_on_right, |el| el.items_start())
8832 .child(if flag_on_right {
8833 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8834 .rounded_bl(px(0.))
8835 .rounded_tl(px(0.))
8836 .border_l_2()
8837 .border_color(border_color)
8838 } else {
8839 self.render_edit_prediction_line_popover("Jump", None, window, cx)
8840 .rounded_br(px(0.))
8841 .rounded_tr(px(0.))
8842 .border_r_2()
8843 .border_color(border_color)
8844 })
8845 .child(div().w(POLE_WIDTH).bg(border_color).h(line_height))
8846 .into_any();
8847
8848 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8849
8850 let mut origin = scrolled_content_origin + point(target_x, target_y.into())
8851 - point(
8852 if flag_on_right {
8853 POLE_WIDTH
8854 } else {
8855 size.width - POLE_WIDTH
8856 },
8857 size.height - line_height,
8858 );
8859
8860 origin.x = origin.x.max(content_origin.x);
8861
8862 element.prepaint_at(origin, window, cx);
8863
8864 Some((element, origin))
8865 }
8866
8867 fn render_edit_prediction_scroll_popover(
8868 &mut self,
8869 to_y: impl Fn(Size<Pixels>) -> Pixels,
8870 scroll_icon: IconName,
8871 visible_row_range: Range<DisplayRow>,
8872 line_layouts: &[LineWithInvisibles],
8873 newest_selection_head: Option<DisplayPoint>,
8874 scrolled_content_origin: gpui::Point<Pixels>,
8875 window: &mut Window,
8876 cx: &mut App,
8877 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8878 let mut element = self
8879 .render_edit_prediction_line_popover("Scroll", Some(scroll_icon), window, cx)
8880 .into_any();
8881
8882 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8883
8884 let cursor = newest_selection_head?;
8885 let cursor_row_layout =
8886 line_layouts.get(cursor.row().minus(visible_row_range.start) as usize)?;
8887 let cursor_column = cursor.column() as usize;
8888
8889 let cursor_character_x = cursor_row_layout.x_for_index(cursor_column);
8890
8891 let origin = scrolled_content_origin + point(cursor_character_x, to_y(size));
8892
8893 element.prepaint_at(origin, window, cx);
8894 Some((element, origin))
8895 }
8896
8897 fn render_edit_prediction_eager_jump_popover(
8898 &mut self,
8899 text_bounds: &Bounds<Pixels>,
8900 content_origin: gpui::Point<Pixels>,
8901 editor_snapshot: &EditorSnapshot,
8902 visible_row_range: Range<DisplayRow>,
8903 scroll_top: ScrollOffset,
8904 scroll_bottom: ScrollOffset,
8905 line_height: Pixels,
8906 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8907 target_display_point: DisplayPoint,
8908 editor_width: Pixels,
8909 window: &mut Window,
8910 cx: &mut App,
8911 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8912 if target_display_point.row().as_f64() < scroll_top {
8913 let mut element = self
8914 .render_edit_prediction_line_popover(
8915 "Jump to Edit",
8916 Some(IconName::ArrowUp),
8917 window,
8918 cx,
8919 )
8920 .into_any();
8921
8922 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8923 let offset = point(
8924 (text_bounds.size.width - size.width) / 2.,
8925 Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8926 );
8927
8928 let origin = text_bounds.origin + offset;
8929 element.prepaint_at(origin, window, cx);
8930 Some((element, origin))
8931 } else if (target_display_point.row().as_f64() + 1.) > scroll_bottom {
8932 let mut element = self
8933 .render_edit_prediction_line_popover(
8934 "Jump to Edit",
8935 Some(IconName::ArrowDown),
8936 window,
8937 cx,
8938 )
8939 .into_any();
8940
8941 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8942 let offset = point(
8943 (text_bounds.size.width - size.width) / 2.,
8944 text_bounds.size.height - size.height - Self::EDIT_PREDICTION_POPOVER_PADDING_Y,
8945 );
8946
8947 let origin = text_bounds.origin + offset;
8948 element.prepaint_at(origin, window, cx);
8949 Some((element, origin))
8950 } else {
8951 self.render_edit_prediction_end_of_line_popover(
8952 "Jump to Edit",
8953 editor_snapshot,
8954 visible_row_range,
8955 target_display_point,
8956 line_height,
8957 scroll_pixel_position,
8958 content_origin,
8959 editor_width,
8960 window,
8961 cx,
8962 )
8963 }
8964 }
8965
8966 fn render_edit_prediction_end_of_line_popover(
8967 self: &mut Editor,
8968 label: &'static str,
8969 editor_snapshot: &EditorSnapshot,
8970 visible_row_range: Range<DisplayRow>,
8971 target_display_point: DisplayPoint,
8972 line_height: Pixels,
8973 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
8974 content_origin: gpui::Point<Pixels>,
8975 editor_width: Pixels,
8976 window: &mut Window,
8977 cx: &mut App,
8978 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
8979 let target_line_end = DisplayPoint::new(
8980 target_display_point.row(),
8981 editor_snapshot.line_len(target_display_point.row()),
8982 );
8983
8984 let mut element = self
8985 .render_edit_prediction_line_popover(label, None, window, cx)
8986 .into_any();
8987
8988 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
8989
8990 let line_origin = self.display_to_pixel_point(target_line_end, editor_snapshot, window)?;
8991
8992 let start_point = content_origin - point(scroll_pixel_position.x.into(), Pixels::ZERO);
8993 let mut origin = start_point
8994 + line_origin
8995 + point(Self::EDIT_PREDICTION_POPOVER_PADDING_X, Pixels::ZERO);
8996 origin.x = origin.x.max(content_origin.x);
8997
8998 let max_x = content_origin.x + editor_width - size.width;
8999
9000 if origin.x > max_x {
9001 let offset = line_height + Self::EDIT_PREDICTION_POPOVER_PADDING_Y;
9002
9003 let icon = if visible_row_range.contains(&(target_display_point.row() + 2)) {
9004 origin.y += offset;
9005 IconName::ArrowUp
9006 } else {
9007 origin.y -= offset;
9008 IconName::ArrowDown
9009 };
9010
9011 element = self
9012 .render_edit_prediction_line_popover(label, Some(icon), window, cx)
9013 .into_any();
9014
9015 let size = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9016
9017 origin.x = content_origin.x + editor_width - size.width - px(2.);
9018 }
9019
9020 element.prepaint_at(origin, window, cx);
9021 Some((element, origin))
9022 }
9023
9024 fn render_edit_prediction_diff_popover(
9025 self: &Editor,
9026 text_bounds: &Bounds<Pixels>,
9027 content_origin: gpui::Point<Pixels>,
9028 right_margin: Pixels,
9029 editor_snapshot: &EditorSnapshot,
9030 visible_row_range: Range<DisplayRow>,
9031 line_layouts: &[LineWithInvisibles],
9032 line_height: Pixels,
9033 scroll_position: gpui::Point<ScrollOffset>,
9034 scroll_pixel_position: gpui::Point<ScrollPixelOffset>,
9035 newest_selection_head: Option<DisplayPoint>,
9036 editor_width: Pixels,
9037 style: &EditorStyle,
9038 edits: &Vec<(Range<Anchor>, Arc<str>)>,
9039 edit_preview: &Option<language::EditPreview>,
9040 snapshot: &language::BufferSnapshot,
9041 window: &mut Window,
9042 cx: &mut App,
9043 ) -> Option<(AnyElement, gpui::Point<Pixels>)> {
9044 let edit_start = edits
9045 .first()
9046 .unwrap()
9047 .0
9048 .start
9049 .to_display_point(editor_snapshot);
9050 let edit_end = edits
9051 .last()
9052 .unwrap()
9053 .0
9054 .end
9055 .to_display_point(editor_snapshot);
9056
9057 let is_visible = visible_row_range.contains(&edit_start.row())
9058 || visible_row_range.contains(&edit_end.row());
9059 if !is_visible {
9060 return None;
9061 }
9062
9063 let highlighted_edits = if let Some(edit_preview) = edit_preview.as_ref() {
9064 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, false, cx)
9065 } else {
9066 // Fallback for providers without edit_preview
9067 crate::edit_prediction_fallback_text(edits, cx)
9068 };
9069
9070 let styled_text = highlighted_edits.to_styled_text(&style.text);
9071 let line_count = highlighted_edits.text.lines().count();
9072
9073 const BORDER_WIDTH: Pixels = px(1.);
9074
9075 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9076 let has_keybind = keybind.is_some();
9077
9078 let mut element = h_flex()
9079 .items_start()
9080 .child(
9081 h_flex()
9082 .bg(cx.theme().colors().editor_background)
9083 .border(BORDER_WIDTH)
9084 .shadow_xs()
9085 .border_color(cx.theme().colors().border)
9086 .rounded_l_lg()
9087 .when(line_count > 1, |el| el.rounded_br_lg())
9088 .pr_1()
9089 .child(styled_text),
9090 )
9091 .child(
9092 h_flex()
9093 .h(line_height + BORDER_WIDTH * 2.)
9094 .px_1p5()
9095 .gap_1()
9096 // Workaround: For some reason, there's a gap if we don't do this
9097 .ml(-BORDER_WIDTH)
9098 .shadow(vec![gpui::BoxShadow {
9099 color: gpui::black().opacity(0.05),
9100 offset: point(px(1.), px(1.)),
9101 blur_radius: px(2.),
9102 spread_radius: px(0.),
9103 }])
9104 .bg(Editor::edit_prediction_line_popover_bg_color(cx))
9105 .border(BORDER_WIDTH)
9106 .border_color(cx.theme().colors().border)
9107 .rounded_r_lg()
9108 .id("edit_prediction_diff_popover_keybind")
9109 .when(!has_keybind, |el| {
9110 let status_colors = cx.theme().status();
9111
9112 el.bg(status_colors.error_background)
9113 .border_color(status_colors.error.opacity(0.6))
9114 .child(Icon::new(IconName::Info).color(Color::Error))
9115 .cursor_default()
9116 .hoverable_tooltip(move |_window, cx| {
9117 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9118 })
9119 })
9120 .children(keybind),
9121 )
9122 .into_any();
9123
9124 let longest_row =
9125 editor_snapshot.longest_row_in_range(edit_start.row()..edit_end.row() + 1);
9126 let longest_line_width = if visible_row_range.contains(&longest_row) {
9127 line_layouts[(longest_row.0 - visible_row_range.start.0) as usize].width
9128 } else {
9129 layout_line(
9130 longest_row,
9131 editor_snapshot,
9132 style,
9133 editor_width,
9134 |_| false,
9135 window,
9136 cx,
9137 )
9138 .width
9139 };
9140
9141 let viewport_bounds =
9142 Bounds::new(Default::default(), window.viewport_size()).extend(Edges {
9143 right: -right_margin,
9144 ..Default::default()
9145 });
9146
9147 let x_after_longest = Pixels::from(
9148 ScrollPixelOffset::from(
9149 text_bounds.origin.x + longest_line_width + Self::EDIT_PREDICTION_POPOVER_PADDING_X,
9150 ) - scroll_pixel_position.x,
9151 );
9152
9153 let element_bounds = element.layout_as_root(AvailableSpace::min_size(), window, cx);
9154
9155 // Fully visible if it can be displayed within the window (allow overlapping other
9156 // panes). However, this is only allowed if the popover starts within text_bounds.
9157 let can_position_to_the_right = x_after_longest < text_bounds.right()
9158 && x_after_longest + element_bounds.width < viewport_bounds.right();
9159
9160 let mut origin = if can_position_to_the_right {
9161 point(
9162 x_after_longest,
9163 text_bounds.origin.y
9164 + Pixels::from(
9165 edit_start.row().as_f64() * ScrollPixelOffset::from(line_height)
9166 - scroll_pixel_position.y,
9167 ),
9168 )
9169 } else {
9170 let cursor_row = newest_selection_head.map(|head| head.row());
9171 let above_edit = edit_start
9172 .row()
9173 .0
9174 .checked_sub(line_count as u32)
9175 .map(DisplayRow);
9176 let below_edit = Some(edit_end.row() + 1);
9177 let above_cursor =
9178 cursor_row.and_then(|row| row.0.checked_sub(line_count as u32).map(DisplayRow));
9179 let below_cursor = cursor_row.map(|cursor_row| cursor_row + 1);
9180
9181 // Place the edit popover adjacent to the edit if there is a location
9182 // available that is onscreen and does not obscure the cursor. Otherwise,
9183 // place it adjacent to the cursor.
9184 let row_target = [above_edit, below_edit, above_cursor, below_cursor]
9185 .into_iter()
9186 .flatten()
9187 .find(|&start_row| {
9188 let end_row = start_row + line_count as u32;
9189 visible_row_range.contains(&start_row)
9190 && visible_row_range.contains(&end_row)
9191 && cursor_row
9192 .is_none_or(|cursor_row| !((start_row..end_row).contains(&cursor_row)))
9193 })?;
9194
9195 content_origin
9196 + point(
9197 Pixels::from(-scroll_pixel_position.x),
9198 Pixels::from(
9199 (row_target.as_f64() - scroll_position.y) * f64::from(line_height),
9200 ),
9201 )
9202 };
9203
9204 origin.x -= BORDER_WIDTH;
9205
9206 window.defer_draw(element, origin, 1);
9207
9208 // Do not return an element, since it will already be drawn due to defer_draw.
9209 None
9210 }
9211
9212 fn edit_prediction_cursor_popover_height(&self) -> Pixels {
9213 px(30.)
9214 }
9215
9216 fn current_user_player_color(&self, cx: &mut App) -> PlayerColor {
9217 if self.read_only(cx) {
9218 cx.theme().players().read_only()
9219 } else {
9220 self.style.as_ref().unwrap().local_player
9221 }
9222 }
9223
9224 fn render_edit_prediction_accept_keybind(
9225 &self,
9226 window: &mut Window,
9227 cx: &mut App,
9228 ) -> Option<AnyElement> {
9229 let accept_binding = self.accept_edit_prediction_keybind(false, window, cx);
9230 let accept_keystroke = accept_binding.keystroke()?;
9231
9232 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9233
9234 let modifiers_color = if *accept_keystroke.modifiers() == window.modifiers() {
9235 Color::Accent
9236 } else {
9237 Color::Muted
9238 };
9239
9240 h_flex()
9241 .px_0p5()
9242 .when(is_platform_style_mac, |parent| parent.gap_0p5())
9243 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9244 .text_size(TextSize::XSmall.rems(cx))
9245 .child(h_flex().children(ui::render_modifiers(
9246 accept_keystroke.modifiers(),
9247 PlatformStyle::platform(),
9248 Some(modifiers_color),
9249 Some(IconSize::XSmall.rems().into()),
9250 true,
9251 )))
9252 .when(is_platform_style_mac, |parent| {
9253 parent.child(accept_keystroke.key().to_string())
9254 })
9255 .when(!is_platform_style_mac, |parent| {
9256 parent.child(
9257 Key::new(
9258 util::capitalize(accept_keystroke.key()),
9259 Some(Color::Default),
9260 )
9261 .size(Some(IconSize::XSmall.rems().into())),
9262 )
9263 })
9264 .into_any()
9265 .into()
9266 }
9267
9268 fn render_edit_prediction_line_popover(
9269 &self,
9270 label: impl Into<SharedString>,
9271 icon: Option<IconName>,
9272 window: &mut Window,
9273 cx: &mut App,
9274 ) -> Stateful<Div> {
9275 let padding_right = if icon.is_some() { px(4.) } else { px(8.) };
9276
9277 let keybind = self.render_edit_prediction_accept_keybind(window, cx);
9278 let has_keybind = keybind.is_some();
9279
9280 h_flex()
9281 .id("ep-line-popover")
9282 .py_0p5()
9283 .pl_1()
9284 .pr(padding_right)
9285 .gap_1()
9286 .rounded_md()
9287 .border_1()
9288 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9289 .border_color(Self::edit_prediction_callout_popover_border_color(cx))
9290 .shadow_xs()
9291 .when(!has_keybind, |el| {
9292 let status_colors = cx.theme().status();
9293
9294 el.bg(status_colors.error_background)
9295 .border_color(status_colors.error.opacity(0.6))
9296 .pl_2()
9297 .child(Icon::new(IconName::ZedPredictError).color(Color::Error))
9298 .cursor_default()
9299 .hoverable_tooltip(move |_window, cx| {
9300 cx.new(|_| MissingEditPredictionKeybindingTooltip).into()
9301 })
9302 })
9303 .children(keybind)
9304 .child(
9305 Label::new(label)
9306 .size(LabelSize::Small)
9307 .when(!has_keybind, |el| {
9308 el.color(cx.theme().status().error.into()).strikethrough()
9309 }),
9310 )
9311 .when(!has_keybind, |el| {
9312 el.child(
9313 h_flex().ml_1().child(
9314 Icon::new(IconName::Info)
9315 .size(IconSize::Small)
9316 .color(cx.theme().status().error.into()),
9317 ),
9318 )
9319 })
9320 .when_some(icon, |element, icon| {
9321 element.child(
9322 div()
9323 .mt(px(1.5))
9324 .child(Icon::new(icon).size(IconSize::Small)),
9325 )
9326 })
9327 }
9328
9329 fn edit_prediction_line_popover_bg_color(cx: &App) -> Hsla {
9330 let accent_color = cx.theme().colors().text_accent;
9331 let editor_bg_color = cx.theme().colors().editor_background;
9332 editor_bg_color.blend(accent_color.opacity(0.1))
9333 }
9334
9335 fn edit_prediction_callout_popover_border_color(cx: &App) -> Hsla {
9336 let accent_color = cx.theme().colors().text_accent;
9337 let editor_bg_color = cx.theme().colors().editor_background;
9338 editor_bg_color.blend(accent_color.opacity(0.6))
9339 }
9340 fn get_prediction_provider_icon_name(
9341 provider: &Option<RegisteredEditPredictionProvider>,
9342 ) -> IconName {
9343 match provider {
9344 Some(provider) => match provider.provider.name() {
9345 "copilot" => IconName::Copilot,
9346 "supermaven" => IconName::Supermaven,
9347 _ => IconName::ZedPredict,
9348 },
9349 None => IconName::ZedPredict,
9350 }
9351 }
9352
9353 fn render_edit_prediction_cursor_popover(
9354 &self,
9355 min_width: Pixels,
9356 max_width: Pixels,
9357 cursor_point: Point,
9358 style: &EditorStyle,
9359 accept_keystroke: Option<&gpui::KeybindingKeystroke>,
9360 _window: &Window,
9361 cx: &mut Context<Editor>,
9362 ) -> Option<AnyElement> {
9363 let provider = self.edit_prediction_provider.as_ref()?;
9364 let provider_icon = Self::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9365
9366 let is_refreshing = provider.provider.is_refreshing(cx);
9367
9368 fn pending_completion_container(icon: IconName) -> Div {
9369 h_flex().h_full().flex_1().gap_2().child(Icon::new(icon))
9370 }
9371
9372 let completion = match &self.active_edit_prediction {
9373 Some(prediction) => {
9374 if !self.has_visible_completions_menu() {
9375 const RADIUS: Pixels = px(6.);
9376 const BORDER_WIDTH: Pixels = px(1.);
9377
9378 return Some(
9379 h_flex()
9380 .elevation_2(cx)
9381 .border(BORDER_WIDTH)
9382 .border_color(cx.theme().colors().border)
9383 .when(accept_keystroke.is_none(), |el| {
9384 el.border_color(cx.theme().status().error)
9385 })
9386 .rounded(RADIUS)
9387 .rounded_tl(px(0.))
9388 .overflow_hidden()
9389 .child(div().px_1p5().child(match &prediction.completion {
9390 EditPrediction::MoveWithin { target, snapshot } => {
9391 use text::ToPoint as _;
9392 if target.text_anchor.to_point(snapshot).row > cursor_point.row
9393 {
9394 Icon::new(IconName::ZedPredictDown)
9395 } else {
9396 Icon::new(IconName::ZedPredictUp)
9397 }
9398 }
9399 EditPrediction::MoveOutside { .. } => {
9400 // TODO [zeta2] custom icon for external jump?
9401 Icon::new(provider_icon)
9402 }
9403 EditPrediction::Edit { .. } => Icon::new(provider_icon),
9404 }))
9405 .child(
9406 h_flex()
9407 .gap_1()
9408 .py_1()
9409 .px_2()
9410 .rounded_r(RADIUS - BORDER_WIDTH)
9411 .border_l_1()
9412 .border_color(cx.theme().colors().border)
9413 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9414 .when(self.edit_prediction_preview.released_too_fast(), |el| {
9415 el.child(
9416 Label::new("Hold")
9417 .size(LabelSize::Small)
9418 .when(accept_keystroke.is_none(), |el| {
9419 el.strikethrough()
9420 })
9421 .line_height_style(LineHeightStyle::UiLabel),
9422 )
9423 })
9424 .id("edit_prediction_cursor_popover_keybind")
9425 .when(accept_keystroke.is_none(), |el| {
9426 let status_colors = cx.theme().status();
9427
9428 el.bg(status_colors.error_background)
9429 .border_color(status_colors.error.opacity(0.6))
9430 .child(Icon::new(IconName::Info).color(Color::Error))
9431 .cursor_default()
9432 .hoverable_tooltip(move |_window, cx| {
9433 cx.new(|_| MissingEditPredictionKeybindingTooltip)
9434 .into()
9435 })
9436 })
9437 .when_some(
9438 accept_keystroke.as_ref(),
9439 |el, accept_keystroke| {
9440 el.child(h_flex().children(ui::render_modifiers(
9441 accept_keystroke.modifiers(),
9442 PlatformStyle::platform(),
9443 Some(Color::Default),
9444 Some(IconSize::XSmall.rems().into()),
9445 false,
9446 )))
9447 },
9448 ),
9449 )
9450 .into_any(),
9451 );
9452 }
9453
9454 self.render_edit_prediction_cursor_popover_preview(
9455 prediction,
9456 cursor_point,
9457 style,
9458 cx,
9459 )?
9460 }
9461
9462 None if is_refreshing => match &self.stale_edit_prediction_in_menu {
9463 Some(stale_completion) => self.render_edit_prediction_cursor_popover_preview(
9464 stale_completion,
9465 cursor_point,
9466 style,
9467 cx,
9468 )?,
9469
9470 None => pending_completion_container(provider_icon)
9471 .child(Label::new("...").size(LabelSize::Small)),
9472 },
9473
9474 None => pending_completion_container(provider_icon)
9475 .child(Label::new("...").size(LabelSize::Small)),
9476 };
9477
9478 let completion = if is_refreshing || self.active_edit_prediction.is_none() {
9479 completion
9480 .with_animation(
9481 "loading-completion",
9482 Animation::new(Duration::from_secs(2))
9483 .repeat()
9484 .with_easing(pulsating_between(0.4, 0.8)),
9485 |label, delta| label.opacity(delta),
9486 )
9487 .into_any_element()
9488 } else {
9489 completion.into_any_element()
9490 };
9491
9492 let has_completion = self.active_edit_prediction.is_some();
9493
9494 let is_platform_style_mac = PlatformStyle::platform() == PlatformStyle::Mac;
9495 Some(
9496 h_flex()
9497 .min_w(min_width)
9498 .max_w(max_width)
9499 .flex_1()
9500 .elevation_2(cx)
9501 .border_color(cx.theme().colors().border)
9502 .child(
9503 div()
9504 .flex_1()
9505 .py_1()
9506 .px_2()
9507 .overflow_hidden()
9508 .child(completion),
9509 )
9510 .when_some(accept_keystroke, |el, accept_keystroke| {
9511 if !accept_keystroke.modifiers().modified() {
9512 return el;
9513 }
9514
9515 el.child(
9516 h_flex()
9517 .h_full()
9518 .border_l_1()
9519 .rounded_r_lg()
9520 .border_color(cx.theme().colors().border)
9521 .bg(Self::edit_prediction_line_popover_bg_color(cx))
9522 .gap_1()
9523 .py_1()
9524 .px_2()
9525 .child(
9526 h_flex()
9527 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9528 .when(is_platform_style_mac, |parent| parent.gap_1())
9529 .child(h_flex().children(ui::render_modifiers(
9530 accept_keystroke.modifiers(),
9531 PlatformStyle::platform(),
9532 Some(if !has_completion {
9533 Color::Muted
9534 } else {
9535 Color::Default
9536 }),
9537 None,
9538 false,
9539 ))),
9540 )
9541 .child(Label::new("Preview").into_any_element())
9542 .opacity(if has_completion { 1.0 } else { 0.4 }),
9543 )
9544 })
9545 .into_any(),
9546 )
9547 }
9548
9549 fn render_edit_prediction_cursor_popover_preview(
9550 &self,
9551 completion: &EditPredictionState,
9552 cursor_point: Point,
9553 style: &EditorStyle,
9554 cx: &mut Context<Editor>,
9555 ) -> Option<Div> {
9556 use text::ToPoint as _;
9557
9558 fn render_relative_row_jump(
9559 prefix: impl Into<String>,
9560 current_row: u32,
9561 target_row: u32,
9562 ) -> Div {
9563 let (row_diff, arrow) = if target_row < current_row {
9564 (current_row - target_row, IconName::ArrowUp)
9565 } else {
9566 (target_row - current_row, IconName::ArrowDown)
9567 };
9568
9569 h_flex()
9570 .child(
9571 Label::new(format!("{}{}", prefix.into(), row_diff))
9572 .color(Color::Muted)
9573 .size(LabelSize::Small),
9574 )
9575 .child(Icon::new(arrow).color(Color::Muted).size(IconSize::Small))
9576 }
9577
9578 let supports_jump = self
9579 .edit_prediction_provider
9580 .as_ref()
9581 .map(|provider| provider.provider.supports_jump_to_edit())
9582 .unwrap_or(true);
9583
9584 match &completion.completion {
9585 EditPrediction::MoveWithin {
9586 target, snapshot, ..
9587 } => {
9588 if !supports_jump {
9589 return None;
9590 }
9591
9592 Some(
9593 h_flex()
9594 .px_2()
9595 .gap_2()
9596 .flex_1()
9597 .child(
9598 if target.text_anchor.to_point(snapshot).row > cursor_point.row {
9599 Icon::new(IconName::ZedPredictDown)
9600 } else {
9601 Icon::new(IconName::ZedPredictUp)
9602 },
9603 )
9604 .child(Label::new("Jump to Edit")),
9605 )
9606 }
9607 EditPrediction::MoveOutside { snapshot, .. } => {
9608 let file_name = snapshot
9609 .file()
9610 .map(|file| file.file_name(cx))
9611 .unwrap_or("untitled");
9612 Some(
9613 h_flex()
9614 .px_2()
9615 .gap_2()
9616 .flex_1()
9617 .child(Icon::new(IconName::ZedPredict))
9618 .child(Label::new(format!("Jump to {file_name}"))),
9619 )
9620 }
9621 EditPrediction::Edit {
9622 edits,
9623 edit_preview,
9624 snapshot,
9625 display_mode: _,
9626 } => {
9627 let first_edit_row = edits.first()?.0.start.text_anchor.to_point(snapshot).row;
9628
9629 let (highlighted_edits, has_more_lines) =
9630 if let Some(edit_preview) = edit_preview.as_ref() {
9631 crate::edit_prediction_edit_text(snapshot, edits, edit_preview, true, cx)
9632 .first_line_preview()
9633 } else {
9634 crate::edit_prediction_fallback_text(edits, cx).first_line_preview()
9635 };
9636
9637 let styled_text = gpui::StyledText::new(highlighted_edits.text)
9638 .with_default_highlights(&style.text, highlighted_edits.highlights);
9639
9640 let preview = h_flex()
9641 .gap_1()
9642 .min_w_16()
9643 .child(styled_text)
9644 .when(has_more_lines, |parent| parent.child("…"));
9645
9646 let left = if supports_jump && first_edit_row != cursor_point.row {
9647 render_relative_row_jump("", cursor_point.row, first_edit_row)
9648 .into_any_element()
9649 } else {
9650 let icon_name =
9651 Editor::get_prediction_provider_icon_name(&self.edit_prediction_provider);
9652 Icon::new(icon_name).into_any_element()
9653 };
9654
9655 Some(
9656 h_flex()
9657 .h_full()
9658 .flex_1()
9659 .gap_2()
9660 .pr_1()
9661 .overflow_x_hidden()
9662 .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
9663 .child(left)
9664 .child(preview),
9665 )
9666 }
9667 }
9668 }
9669
9670 pub fn render_context_menu(
9671 &self,
9672 style: &EditorStyle,
9673 max_height_in_lines: u32,
9674 window: &mut Window,
9675 cx: &mut Context<Editor>,
9676 ) -> Option<AnyElement> {
9677 let menu = self.context_menu.borrow();
9678 let menu = menu.as_ref()?;
9679 if !menu.visible() {
9680 return None;
9681 };
9682 Some(menu.render(style, max_height_in_lines, window, cx))
9683 }
9684
9685 fn render_context_menu_aside(
9686 &mut self,
9687 max_size: Size<Pixels>,
9688 window: &mut Window,
9689 cx: &mut Context<Editor>,
9690 ) -> Option<AnyElement> {
9691 self.context_menu.borrow_mut().as_mut().and_then(|menu| {
9692 if menu.visible() {
9693 menu.render_aside(max_size, window, cx)
9694 } else {
9695 None
9696 }
9697 })
9698 }
9699
9700 fn hide_context_menu(
9701 &mut self,
9702 window: &mut Window,
9703 cx: &mut Context<Self>,
9704 ) -> Option<CodeContextMenu> {
9705 cx.notify();
9706 self.completion_tasks.clear();
9707 let context_menu = self.context_menu.borrow_mut().take();
9708 self.stale_edit_prediction_in_menu.take();
9709 self.update_visible_edit_prediction(window, cx);
9710 if let Some(CodeContextMenu::Completions(_)) = &context_menu
9711 && let Some(completion_provider) = &self.completion_provider
9712 {
9713 completion_provider.selection_changed(None, window, cx);
9714 }
9715 context_menu
9716 }
9717
9718 fn show_snippet_choices(
9719 &mut self,
9720 choices: &Vec<String>,
9721 selection: Range<Anchor>,
9722 cx: &mut Context<Self>,
9723 ) {
9724 let Some((_, buffer, _)) = self
9725 .buffer()
9726 .read(cx)
9727 .excerpt_containing(selection.start, cx)
9728 else {
9729 return;
9730 };
9731 let Some((_, end_buffer, _)) = self.buffer().read(cx).excerpt_containing(selection.end, cx)
9732 else {
9733 return;
9734 };
9735 if buffer != end_buffer {
9736 log::error!("expected anchor range to have matching buffer IDs");
9737 return;
9738 }
9739
9740 let id = post_inc(&mut self.next_completion_id);
9741 let snippet_sort_order = EditorSettings::get_global(cx).snippet_sort_order;
9742 *self.context_menu.borrow_mut() = Some(CodeContextMenu::Completions(
9743 CompletionsMenu::new_snippet_choices(
9744 id,
9745 true,
9746 choices,
9747 selection,
9748 buffer,
9749 snippet_sort_order,
9750 ),
9751 ));
9752 }
9753
9754 pub fn insert_snippet(
9755 &mut self,
9756 insertion_ranges: &[Range<usize>],
9757 snippet: Snippet,
9758 window: &mut Window,
9759 cx: &mut Context<Self>,
9760 ) -> Result<()> {
9761 struct Tabstop<T> {
9762 is_end_tabstop: bool,
9763 ranges: Vec<Range<T>>,
9764 choices: Option<Vec<String>>,
9765 }
9766
9767 let tabstops = self.buffer.update(cx, |buffer, cx| {
9768 let snippet_text: Arc<str> = snippet.text.clone().into();
9769 let edits = insertion_ranges
9770 .iter()
9771 .cloned()
9772 .map(|range| (range, snippet_text.clone()));
9773 let autoindent_mode = AutoindentMode::Block {
9774 original_indent_columns: Vec::new(),
9775 };
9776 buffer.edit(edits, Some(autoindent_mode), cx);
9777
9778 let snapshot = &*buffer.read(cx);
9779 let snippet = &snippet;
9780 snippet
9781 .tabstops
9782 .iter()
9783 .map(|tabstop| {
9784 let is_end_tabstop = tabstop.ranges.first().is_some_and(|tabstop| {
9785 tabstop.is_empty() && tabstop.start == snippet.text.len() as isize
9786 });
9787 let mut tabstop_ranges = tabstop
9788 .ranges
9789 .iter()
9790 .flat_map(|tabstop_range| {
9791 let mut delta = 0_isize;
9792 insertion_ranges.iter().map(move |insertion_range| {
9793 let insertion_start = insertion_range.start as isize + delta;
9794 delta +=
9795 snippet.text.len() as isize - insertion_range.len() as isize;
9796
9797 let start = ((insertion_start + tabstop_range.start) as usize)
9798 .min(snapshot.len());
9799 let end = ((insertion_start + tabstop_range.end) as usize)
9800 .min(snapshot.len());
9801 snapshot.anchor_before(start)..snapshot.anchor_after(end)
9802 })
9803 })
9804 .collect::<Vec<_>>();
9805 tabstop_ranges.sort_unstable_by(|a, b| a.start.cmp(&b.start, snapshot));
9806
9807 Tabstop {
9808 is_end_tabstop,
9809 ranges: tabstop_ranges,
9810 choices: tabstop.choices.clone(),
9811 }
9812 })
9813 .collect::<Vec<_>>()
9814 });
9815 if let Some(tabstop) = tabstops.first() {
9816 self.change_selections(Default::default(), window, cx, |s| {
9817 // Reverse order so that the first range is the newest created selection.
9818 // Completions will use it and autoscroll will prioritize it.
9819 s.select_ranges(tabstop.ranges.iter().rev().cloned());
9820 });
9821
9822 if let Some(choices) = &tabstop.choices
9823 && let Some(selection) = tabstop.ranges.first()
9824 {
9825 self.show_snippet_choices(choices, selection.clone(), cx)
9826 }
9827
9828 // If we're already at the last tabstop and it's at the end of the snippet,
9829 // we're done, we don't need to keep the state around.
9830 if !tabstop.is_end_tabstop {
9831 let choices = tabstops
9832 .iter()
9833 .map(|tabstop| tabstop.choices.clone())
9834 .collect();
9835
9836 let ranges = tabstops
9837 .into_iter()
9838 .map(|tabstop| tabstop.ranges)
9839 .collect::<Vec<_>>();
9840
9841 self.snippet_stack.push(SnippetState {
9842 active_index: 0,
9843 ranges,
9844 choices,
9845 });
9846 }
9847
9848 // Check whether the just-entered snippet ends with an auto-closable bracket.
9849 if self.autoclose_regions.is_empty() {
9850 let snapshot = self.buffer.read(cx).snapshot(cx);
9851 for selection in &mut self.selections.all::<Point>(&self.display_snapshot(cx)) {
9852 let selection_head = selection.head();
9853 let Some(scope) = snapshot.language_scope_at(selection_head) else {
9854 continue;
9855 };
9856
9857 let mut bracket_pair = None;
9858 let max_lookup_length = scope
9859 .brackets()
9860 .map(|(pair, _)| {
9861 pair.start
9862 .as_str()
9863 .chars()
9864 .count()
9865 .max(pair.end.as_str().chars().count())
9866 })
9867 .max();
9868 if let Some(max_lookup_length) = max_lookup_length {
9869 let next_text = snapshot
9870 .chars_at(selection_head)
9871 .take(max_lookup_length)
9872 .collect::<String>();
9873 let prev_text = snapshot
9874 .reversed_chars_at(selection_head)
9875 .take(max_lookup_length)
9876 .collect::<String>();
9877
9878 for (pair, enabled) in scope.brackets() {
9879 if enabled
9880 && pair.close
9881 && prev_text.starts_with(pair.start.as_str())
9882 && next_text.starts_with(pair.end.as_str())
9883 {
9884 bracket_pair = Some(pair.clone());
9885 break;
9886 }
9887 }
9888 }
9889
9890 if let Some(pair) = bracket_pair {
9891 let snapshot_settings = snapshot.language_settings_at(selection_head, cx);
9892 let autoclose_enabled =
9893 self.use_autoclose && snapshot_settings.use_autoclose;
9894 if autoclose_enabled {
9895 let start = snapshot.anchor_after(selection_head);
9896 let end = snapshot.anchor_after(selection_head);
9897 self.autoclose_regions.push(AutocloseRegion {
9898 selection_id: selection.id,
9899 range: start..end,
9900 pair,
9901 });
9902 }
9903 }
9904 }
9905 }
9906 }
9907 Ok(())
9908 }
9909
9910 pub fn move_to_next_snippet_tabstop(
9911 &mut self,
9912 window: &mut Window,
9913 cx: &mut Context<Self>,
9914 ) -> bool {
9915 self.move_to_snippet_tabstop(Bias::Right, window, cx)
9916 }
9917
9918 pub fn move_to_prev_snippet_tabstop(
9919 &mut self,
9920 window: &mut Window,
9921 cx: &mut Context<Self>,
9922 ) -> bool {
9923 self.move_to_snippet_tabstop(Bias::Left, window, cx)
9924 }
9925
9926 pub fn move_to_snippet_tabstop(
9927 &mut self,
9928 bias: Bias,
9929 window: &mut Window,
9930 cx: &mut Context<Self>,
9931 ) -> bool {
9932 if let Some(mut snippet) = self.snippet_stack.pop() {
9933 match bias {
9934 Bias::Left => {
9935 if snippet.active_index > 0 {
9936 snippet.active_index -= 1;
9937 } else {
9938 self.snippet_stack.push(snippet);
9939 return false;
9940 }
9941 }
9942 Bias::Right => {
9943 if snippet.active_index + 1 < snippet.ranges.len() {
9944 snippet.active_index += 1;
9945 } else {
9946 self.snippet_stack.push(snippet);
9947 return false;
9948 }
9949 }
9950 }
9951 if let Some(current_ranges) = snippet.ranges.get(snippet.active_index) {
9952 self.change_selections(Default::default(), window, cx, |s| {
9953 // Reverse order so that the first range is the newest created selection.
9954 // Completions will use it and autoscroll will prioritize it.
9955 s.select_ranges(current_ranges.iter().rev().cloned())
9956 });
9957
9958 if let Some(choices) = &snippet.choices[snippet.active_index]
9959 && let Some(selection) = current_ranges.first()
9960 {
9961 self.show_snippet_choices(choices, selection.clone(), cx);
9962 }
9963
9964 // If snippet state is not at the last tabstop, push it back on the stack
9965 if snippet.active_index + 1 < snippet.ranges.len() {
9966 self.snippet_stack.push(snippet);
9967 }
9968 return true;
9969 }
9970 }
9971
9972 false
9973 }
9974
9975 pub fn clear(&mut self, window: &mut Window, cx: &mut Context<Self>) {
9976 self.transact(window, cx, |this, window, cx| {
9977 this.select_all(&SelectAll, window, cx);
9978 this.insert("", window, cx);
9979 });
9980 }
9981
9982 pub fn backspace(&mut self, _: &Backspace, window: &mut Window, cx: &mut Context<Self>) {
9983 if self.read_only(cx) {
9984 return;
9985 }
9986 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
9987 self.transact(window, cx, |this, window, cx| {
9988 this.select_autoclose_pair(window, cx);
9989
9990 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
9991
9992 let mut linked_ranges = HashMap::<_, Vec<_>>::default();
9993 if !this.linked_edit_ranges.is_empty() {
9994 let selections = this.selections.all::<MultiBufferPoint>(&display_map);
9995 let snapshot = this.buffer.read(cx).snapshot(cx);
9996
9997 for selection in selections.iter() {
9998 let selection_start = snapshot.anchor_before(selection.start).text_anchor;
9999 let selection_end = snapshot.anchor_after(selection.end).text_anchor;
10000 if selection_start.buffer_id != selection_end.buffer_id {
10001 continue;
10002 }
10003 if let Some(ranges) =
10004 this.linked_editing_ranges_for(selection_start..selection_end, cx)
10005 {
10006 for (buffer, entries) in ranges {
10007 linked_ranges.entry(buffer).or_default().extend(entries);
10008 }
10009 }
10010 }
10011 }
10012
10013 let mut selections = this.selections.all::<MultiBufferPoint>(&display_map);
10014 for selection in &mut selections {
10015 if selection.is_empty() {
10016 let old_head = selection.head();
10017 let mut new_head =
10018 movement::left(&display_map, old_head.to_display_point(&display_map))
10019 .to_point(&display_map);
10020 if let Some((buffer, line_buffer_range)) = display_map
10021 .buffer_snapshot()
10022 .buffer_line_for_row(MultiBufferRow(old_head.row))
10023 {
10024 let indent_size = buffer.indent_size_for_line(line_buffer_range.start.row);
10025 let indent_len = match indent_size.kind {
10026 IndentKind::Space => {
10027 buffer.settings_at(line_buffer_range.start, cx).tab_size
10028 }
10029 IndentKind::Tab => NonZeroU32::new(1).unwrap(),
10030 };
10031 if old_head.column <= indent_size.len && old_head.column > 0 {
10032 let indent_len = indent_len.get();
10033 new_head = cmp::min(
10034 new_head,
10035 MultiBufferPoint::new(
10036 old_head.row,
10037 ((old_head.column - 1) / indent_len) * indent_len,
10038 ),
10039 );
10040 }
10041 }
10042
10043 selection.set_head(new_head, SelectionGoal::None);
10044 }
10045 }
10046
10047 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10048 this.insert("", window, cx);
10049 let empty_str: Arc<str> = Arc::from("");
10050 for (buffer, edits) in linked_ranges {
10051 let snapshot = buffer.read(cx).snapshot();
10052 use text::ToPoint as TP;
10053
10054 let edits = edits
10055 .into_iter()
10056 .map(|range| {
10057 let end_point = TP::to_point(&range.end, &snapshot);
10058 let mut start_point = TP::to_point(&range.start, &snapshot);
10059
10060 if end_point == start_point {
10061 let offset = text::ToOffset::to_offset(&range.start, &snapshot)
10062 .saturating_sub(1);
10063 start_point =
10064 snapshot.clip_point(TP::to_point(&offset, &snapshot), Bias::Left);
10065 };
10066
10067 (start_point..end_point, empty_str.clone())
10068 })
10069 .sorted_by_key(|(range, _)| range.start)
10070 .collect::<Vec<_>>();
10071 buffer.update(cx, |this, cx| {
10072 this.edit(edits, None, cx);
10073 })
10074 }
10075 this.refresh_edit_prediction(true, false, window, cx);
10076 refresh_linked_ranges(this, window, cx);
10077 });
10078 }
10079
10080 pub fn delete(&mut self, _: &Delete, window: &mut Window, cx: &mut Context<Self>) {
10081 if self.read_only(cx) {
10082 return;
10083 }
10084 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10085 self.transact(window, cx, |this, window, cx| {
10086 this.change_selections(Default::default(), window, cx, |s| {
10087 s.move_with(|map, selection| {
10088 if selection.is_empty() {
10089 let cursor = movement::right(map, selection.head());
10090 selection.end = cursor;
10091 selection.reversed = true;
10092 selection.goal = SelectionGoal::None;
10093 }
10094 })
10095 });
10096 this.insert("", window, cx);
10097 this.refresh_edit_prediction(true, false, window, cx);
10098 });
10099 }
10100
10101 pub fn backtab(&mut self, _: &Backtab, window: &mut Window, cx: &mut Context<Self>) {
10102 if self.mode.is_single_line() {
10103 cx.propagate();
10104 return;
10105 }
10106
10107 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10108 if self.move_to_prev_snippet_tabstop(window, cx) {
10109 return;
10110 }
10111 self.outdent(&Outdent, window, cx);
10112 }
10113
10114 pub fn next_snippet_tabstop(
10115 &mut self,
10116 _: &NextSnippetTabstop,
10117 window: &mut Window,
10118 cx: &mut Context<Self>,
10119 ) {
10120 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10121 cx.propagate();
10122 return;
10123 }
10124
10125 if self.move_to_next_snippet_tabstop(window, cx) {
10126 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10127 return;
10128 }
10129 cx.propagate();
10130 }
10131
10132 pub fn previous_snippet_tabstop(
10133 &mut self,
10134 _: &PreviousSnippetTabstop,
10135 window: &mut Window,
10136 cx: &mut Context<Self>,
10137 ) {
10138 if self.mode.is_single_line() || self.snippet_stack.is_empty() {
10139 cx.propagate();
10140 return;
10141 }
10142
10143 if self.move_to_prev_snippet_tabstop(window, cx) {
10144 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10145 return;
10146 }
10147 cx.propagate();
10148 }
10149
10150 pub fn tab(&mut self, _: &Tab, window: &mut Window, cx: &mut Context<Self>) {
10151 if self.mode.is_single_line() {
10152 cx.propagate();
10153 return;
10154 }
10155
10156 if self.move_to_next_snippet_tabstop(window, cx) {
10157 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10158 return;
10159 }
10160 if self.read_only(cx) {
10161 return;
10162 }
10163 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10164 let mut selections = self.selections.all_adjusted(&self.display_snapshot(cx));
10165 let buffer = self.buffer.read(cx);
10166 let snapshot = buffer.snapshot(cx);
10167 let rows_iter = selections.iter().map(|s| s.head().row);
10168 let suggested_indents = snapshot.suggested_indents(rows_iter, cx);
10169
10170 let has_some_cursor_in_whitespace = selections
10171 .iter()
10172 .filter(|selection| selection.is_empty())
10173 .any(|selection| {
10174 let cursor = selection.head();
10175 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10176 cursor.column < current_indent.len
10177 });
10178
10179 let mut edits = Vec::new();
10180 let mut prev_edited_row = 0;
10181 let mut row_delta = 0;
10182 for selection in &mut selections {
10183 if selection.start.row != prev_edited_row {
10184 row_delta = 0;
10185 }
10186 prev_edited_row = selection.end.row;
10187
10188 // If the selection is non-empty, then increase the indentation of the selected lines.
10189 if !selection.is_empty() {
10190 row_delta =
10191 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10192 continue;
10193 }
10194
10195 let cursor = selection.head();
10196 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(cursor.row));
10197 if let Some(suggested_indent) =
10198 suggested_indents.get(&MultiBufferRow(cursor.row)).copied()
10199 {
10200 // Don't do anything if already at suggested indent
10201 // and there is any other cursor which is not
10202 if has_some_cursor_in_whitespace
10203 && cursor.column == current_indent.len
10204 && current_indent.len == suggested_indent.len
10205 {
10206 continue;
10207 }
10208
10209 // Adjust line and move cursor to suggested indent
10210 // if cursor is not at suggested indent
10211 if cursor.column < suggested_indent.len
10212 && cursor.column <= current_indent.len
10213 && current_indent.len <= suggested_indent.len
10214 {
10215 selection.start = Point::new(cursor.row, suggested_indent.len);
10216 selection.end = selection.start;
10217 if row_delta == 0 {
10218 edits.extend(Buffer::edit_for_indent_size_adjustment(
10219 cursor.row,
10220 current_indent,
10221 suggested_indent,
10222 ));
10223 row_delta = suggested_indent.len - current_indent.len;
10224 }
10225 continue;
10226 }
10227
10228 // If current indent is more than suggested indent
10229 // only move cursor to current indent and skip indent
10230 if cursor.column < current_indent.len && current_indent.len > suggested_indent.len {
10231 selection.start = Point::new(cursor.row, current_indent.len);
10232 selection.end = selection.start;
10233 continue;
10234 }
10235 }
10236
10237 // Otherwise, insert a hard or soft tab.
10238 let settings = buffer.language_settings_at(cursor, cx);
10239 let tab_size = if settings.hard_tabs {
10240 IndentSize::tab()
10241 } else {
10242 let tab_size = settings.tab_size.get();
10243 let indent_remainder = snapshot
10244 .text_for_range(Point::new(cursor.row, 0)..cursor)
10245 .flat_map(str::chars)
10246 .fold(row_delta % tab_size, |counter: u32, c| {
10247 if c == '\t' {
10248 0
10249 } else {
10250 (counter + 1) % tab_size
10251 }
10252 });
10253
10254 let chars_to_next_tab_stop = tab_size - indent_remainder;
10255 IndentSize::spaces(chars_to_next_tab_stop)
10256 };
10257 selection.start = Point::new(cursor.row, cursor.column + row_delta + tab_size.len);
10258 selection.end = selection.start;
10259 edits.push((cursor..cursor, tab_size.chars().collect::<String>()));
10260 row_delta += tab_size.len;
10261 }
10262
10263 self.transact(window, cx, |this, window, cx| {
10264 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10265 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10266 this.refresh_edit_prediction(true, false, window, cx);
10267 });
10268 }
10269
10270 pub fn indent(&mut self, _: &Indent, window: &mut Window, cx: &mut Context<Self>) {
10271 if self.read_only(cx) {
10272 return;
10273 }
10274 if self.mode.is_single_line() {
10275 cx.propagate();
10276 return;
10277 }
10278
10279 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10280 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
10281 let mut prev_edited_row = 0;
10282 let mut row_delta = 0;
10283 let mut edits = Vec::new();
10284 let buffer = self.buffer.read(cx);
10285 let snapshot = buffer.snapshot(cx);
10286 for selection in &mut selections {
10287 if selection.start.row != prev_edited_row {
10288 row_delta = 0;
10289 }
10290 prev_edited_row = selection.end.row;
10291
10292 row_delta =
10293 Self::indent_selection(buffer, &snapshot, selection, &mut edits, row_delta, cx);
10294 }
10295
10296 self.transact(window, cx, |this, window, cx| {
10297 this.buffer.update(cx, |b, cx| b.edit(edits, None, cx));
10298 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10299 });
10300 }
10301
10302 fn indent_selection(
10303 buffer: &MultiBuffer,
10304 snapshot: &MultiBufferSnapshot,
10305 selection: &mut Selection<Point>,
10306 edits: &mut Vec<(Range<Point>, String)>,
10307 delta_for_start_row: u32,
10308 cx: &App,
10309 ) -> u32 {
10310 let settings = buffer.language_settings_at(selection.start, cx);
10311 let tab_size = settings.tab_size.get();
10312 let indent_kind = if settings.hard_tabs {
10313 IndentKind::Tab
10314 } else {
10315 IndentKind::Space
10316 };
10317 let mut start_row = selection.start.row;
10318 let mut end_row = selection.end.row + 1;
10319
10320 // If a selection ends at the beginning of a line, don't indent
10321 // that last line.
10322 if selection.end.column == 0 && selection.end.row > selection.start.row {
10323 end_row -= 1;
10324 }
10325
10326 // Avoid re-indenting a row that has already been indented by a
10327 // previous selection, but still update this selection's column
10328 // to reflect that indentation.
10329 if delta_for_start_row > 0 {
10330 start_row += 1;
10331 selection.start.column += delta_for_start_row;
10332 if selection.end.row == selection.start.row {
10333 selection.end.column += delta_for_start_row;
10334 }
10335 }
10336
10337 let mut delta_for_end_row = 0;
10338 let has_multiple_rows = start_row + 1 != end_row;
10339 for row in start_row..end_row {
10340 let current_indent = snapshot.indent_size_for_line(MultiBufferRow(row));
10341 let indent_delta = match (current_indent.kind, indent_kind) {
10342 (IndentKind::Space, IndentKind::Space) => {
10343 let columns_to_next_tab_stop = tab_size - (current_indent.len % tab_size);
10344 IndentSize::spaces(columns_to_next_tab_stop)
10345 }
10346 (IndentKind::Tab, IndentKind::Space) => IndentSize::spaces(tab_size),
10347 (_, IndentKind::Tab) => IndentSize::tab(),
10348 };
10349
10350 let start = if has_multiple_rows || current_indent.len < selection.start.column {
10351 0
10352 } else {
10353 selection.start.column
10354 };
10355 let row_start = Point::new(row, start);
10356 edits.push((
10357 row_start..row_start,
10358 indent_delta.chars().collect::<String>(),
10359 ));
10360
10361 // Update this selection's endpoints to reflect the indentation.
10362 if row == selection.start.row {
10363 selection.start.column += indent_delta.len;
10364 }
10365 if row == selection.end.row {
10366 selection.end.column += indent_delta.len;
10367 delta_for_end_row = indent_delta.len;
10368 }
10369 }
10370
10371 if selection.start.row == selection.end.row {
10372 delta_for_start_row + delta_for_end_row
10373 } else {
10374 delta_for_end_row
10375 }
10376 }
10377
10378 pub fn outdent(&mut self, _: &Outdent, window: &mut Window, cx: &mut Context<Self>) {
10379 if self.read_only(cx) {
10380 return;
10381 }
10382 if self.mode.is_single_line() {
10383 cx.propagate();
10384 return;
10385 }
10386
10387 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10388 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10389 let selections = self.selections.all::<Point>(&display_map);
10390 let mut deletion_ranges = Vec::new();
10391 let mut last_outdent = None;
10392 {
10393 let buffer = self.buffer.read(cx);
10394 let snapshot = buffer.snapshot(cx);
10395 for selection in &selections {
10396 let settings = buffer.language_settings_at(selection.start, cx);
10397 let tab_size = settings.tab_size.get();
10398 let mut rows = selection.spanned_rows(false, &display_map);
10399
10400 // Avoid re-outdenting a row that has already been outdented by a
10401 // previous selection.
10402 if let Some(last_row) = last_outdent
10403 && last_row == rows.start
10404 {
10405 rows.start = rows.start.next_row();
10406 }
10407 let has_multiple_rows = rows.len() > 1;
10408 for row in rows.iter_rows() {
10409 let indent_size = snapshot.indent_size_for_line(row);
10410 if indent_size.len > 0 {
10411 let deletion_len = match indent_size.kind {
10412 IndentKind::Space => {
10413 let columns_to_prev_tab_stop = indent_size.len % tab_size;
10414 if columns_to_prev_tab_stop == 0 {
10415 tab_size
10416 } else {
10417 columns_to_prev_tab_stop
10418 }
10419 }
10420 IndentKind::Tab => 1,
10421 };
10422 let start = if has_multiple_rows
10423 || deletion_len > selection.start.column
10424 || indent_size.len < selection.start.column
10425 {
10426 0
10427 } else {
10428 selection.start.column - deletion_len
10429 };
10430 deletion_ranges.push(
10431 Point::new(row.0, start)..Point::new(row.0, start + deletion_len),
10432 );
10433 last_outdent = Some(row);
10434 }
10435 }
10436 }
10437 }
10438
10439 self.transact(window, cx, |this, window, cx| {
10440 this.buffer.update(cx, |buffer, cx| {
10441 let empty_str: Arc<str> = Arc::default();
10442 buffer.edit(
10443 deletion_ranges
10444 .into_iter()
10445 .map(|range| (range, empty_str.clone())),
10446 None,
10447 cx,
10448 );
10449 });
10450 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10451 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10452 });
10453 }
10454
10455 pub fn autoindent(&mut self, _: &AutoIndent, window: &mut Window, cx: &mut Context<Self>) {
10456 if self.read_only(cx) {
10457 return;
10458 }
10459 if self.mode.is_single_line() {
10460 cx.propagate();
10461 return;
10462 }
10463
10464 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10465 let selections = self
10466 .selections
10467 .all::<usize>(&self.display_snapshot(cx))
10468 .into_iter()
10469 .map(|s| s.range());
10470
10471 self.transact(window, cx, |this, window, cx| {
10472 this.buffer.update(cx, |buffer, cx| {
10473 buffer.autoindent_ranges(selections, cx);
10474 });
10475 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
10476 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
10477 });
10478 }
10479
10480 pub fn delete_line(&mut self, _: &DeleteLine, window: &mut Window, cx: &mut Context<Self>) {
10481 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10482 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
10483 let selections = self.selections.all::<Point>(&display_map);
10484
10485 let mut new_cursors = Vec::new();
10486 let mut edit_ranges = Vec::new();
10487 let mut selections = selections.iter().peekable();
10488 while let Some(selection) = selections.next() {
10489 let mut rows = selection.spanned_rows(false, &display_map);
10490
10491 // Accumulate contiguous regions of rows that we want to delete.
10492 while let Some(next_selection) = selections.peek() {
10493 let next_rows = next_selection.spanned_rows(false, &display_map);
10494 if next_rows.start <= rows.end {
10495 rows.end = next_rows.end;
10496 selections.next().unwrap();
10497 } else {
10498 break;
10499 }
10500 }
10501
10502 let buffer = display_map.buffer_snapshot();
10503 let mut edit_start = ToOffset::to_offset(&Point::new(rows.start.0, 0), buffer);
10504 let (edit_end, target_row) = if buffer.max_point().row >= rows.end.0 {
10505 // If there's a line after the range, delete the \n from the end of the row range
10506 (
10507 ToOffset::to_offset(&Point::new(rows.end.0, 0), buffer),
10508 rows.end,
10509 )
10510 } else {
10511 // If there isn't a line after the range, delete the \n from the line before the
10512 // start of the row range
10513 edit_start = edit_start.saturating_sub(1);
10514 (buffer.len(), rows.start.previous_row())
10515 };
10516
10517 let text_layout_details = self.text_layout_details(window);
10518 let x = display_map.x_for_display_point(
10519 selection.head().to_display_point(&display_map),
10520 &text_layout_details,
10521 );
10522 let row = Point::new(target_row.0, 0)
10523 .to_display_point(&display_map)
10524 .row();
10525 let column = display_map.display_column_for_x(row, x, &text_layout_details);
10526
10527 new_cursors.push((
10528 selection.id,
10529 buffer.anchor_after(DisplayPoint::new(row, column).to_point(&display_map)),
10530 SelectionGoal::None,
10531 ));
10532 edit_ranges.push(edit_start..edit_end);
10533 }
10534
10535 self.transact(window, cx, |this, window, cx| {
10536 let buffer = this.buffer.update(cx, |buffer, cx| {
10537 let empty_str: Arc<str> = Arc::default();
10538 buffer.edit(
10539 edit_ranges
10540 .into_iter()
10541 .map(|range| (range, empty_str.clone())),
10542 None,
10543 cx,
10544 );
10545 buffer.snapshot(cx)
10546 });
10547 let new_selections = new_cursors
10548 .into_iter()
10549 .map(|(id, cursor, goal)| {
10550 let cursor = cursor.to_point(&buffer);
10551 Selection {
10552 id,
10553 start: cursor,
10554 end: cursor,
10555 reversed: false,
10556 goal,
10557 }
10558 })
10559 .collect();
10560
10561 this.change_selections(Default::default(), window, cx, |s| {
10562 s.select(new_selections);
10563 });
10564 });
10565 }
10566
10567 pub fn join_lines_impl(
10568 &mut self,
10569 insert_whitespace: bool,
10570 window: &mut Window,
10571 cx: &mut Context<Self>,
10572 ) {
10573 if self.read_only(cx) {
10574 return;
10575 }
10576 let mut row_ranges = Vec::<Range<MultiBufferRow>>::new();
10577 for selection in self.selections.all::<Point>(&self.display_snapshot(cx)) {
10578 let start = MultiBufferRow(selection.start.row);
10579 // Treat single line selections as if they include the next line. Otherwise this action
10580 // would do nothing for single line selections individual cursors.
10581 let end = if selection.start.row == selection.end.row {
10582 MultiBufferRow(selection.start.row + 1)
10583 } else {
10584 MultiBufferRow(selection.end.row)
10585 };
10586
10587 if let Some(last_row_range) = row_ranges.last_mut()
10588 && start <= last_row_range.end
10589 {
10590 last_row_range.end = end;
10591 continue;
10592 }
10593 row_ranges.push(start..end);
10594 }
10595
10596 let snapshot = self.buffer.read(cx).snapshot(cx);
10597 let mut cursor_positions = Vec::new();
10598 for row_range in &row_ranges {
10599 let anchor = snapshot.anchor_before(Point::new(
10600 row_range.end.previous_row().0,
10601 snapshot.line_len(row_range.end.previous_row()),
10602 ));
10603 cursor_positions.push(anchor..anchor);
10604 }
10605
10606 self.transact(window, cx, |this, window, cx| {
10607 for row_range in row_ranges.into_iter().rev() {
10608 for row in row_range.iter_rows().rev() {
10609 let end_of_line = Point::new(row.0, snapshot.line_len(row));
10610 let next_line_row = row.next_row();
10611 let indent = snapshot.indent_size_for_line(next_line_row);
10612 let start_of_next_line = Point::new(next_line_row.0, indent.len);
10613
10614 let replace =
10615 if snapshot.line_len(next_line_row) > indent.len && insert_whitespace {
10616 " "
10617 } else {
10618 ""
10619 };
10620
10621 this.buffer.update(cx, |buffer, cx| {
10622 buffer.edit([(end_of_line..start_of_next_line, replace)], None, cx)
10623 });
10624 }
10625 }
10626
10627 this.change_selections(Default::default(), window, cx, |s| {
10628 s.select_anchor_ranges(cursor_positions)
10629 });
10630 });
10631 }
10632
10633 pub fn join_lines(&mut self, _: &JoinLines, window: &mut Window, cx: &mut Context<Self>) {
10634 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10635 self.join_lines_impl(true, window, cx);
10636 }
10637
10638 pub fn sort_lines_case_sensitive(
10639 &mut self,
10640 _: &SortLinesCaseSensitive,
10641 window: &mut Window,
10642 cx: &mut Context<Self>,
10643 ) {
10644 self.manipulate_immutable_lines(window, cx, |lines| lines.sort())
10645 }
10646
10647 pub fn sort_lines_by_length(
10648 &mut self,
10649 _: &SortLinesByLength,
10650 window: &mut Window,
10651 cx: &mut Context<Self>,
10652 ) {
10653 self.manipulate_immutable_lines(window, cx, |lines| {
10654 lines.sort_by_key(|&line| line.chars().count())
10655 })
10656 }
10657
10658 pub fn sort_lines_case_insensitive(
10659 &mut self,
10660 _: &SortLinesCaseInsensitive,
10661 window: &mut Window,
10662 cx: &mut Context<Self>,
10663 ) {
10664 self.manipulate_immutable_lines(window, cx, |lines| {
10665 lines.sort_by_key(|line| line.to_lowercase())
10666 })
10667 }
10668
10669 pub fn unique_lines_case_insensitive(
10670 &mut self,
10671 _: &UniqueLinesCaseInsensitive,
10672 window: &mut Window,
10673 cx: &mut Context<Self>,
10674 ) {
10675 self.manipulate_immutable_lines(window, cx, |lines| {
10676 let mut seen = HashSet::default();
10677 lines.retain(|line| seen.insert(line.to_lowercase()));
10678 })
10679 }
10680
10681 pub fn unique_lines_case_sensitive(
10682 &mut self,
10683 _: &UniqueLinesCaseSensitive,
10684 window: &mut Window,
10685 cx: &mut Context<Self>,
10686 ) {
10687 self.manipulate_immutable_lines(window, cx, |lines| {
10688 let mut seen = HashSet::default();
10689 lines.retain(|line| seen.insert(*line));
10690 })
10691 }
10692
10693 fn enable_wrap_selections_in_tag(&self, cx: &App) -> bool {
10694 let snapshot = self.buffer.read(cx).snapshot(cx);
10695 for selection in self.selections.disjoint_anchors_arc().iter() {
10696 if snapshot
10697 .language_at(selection.start)
10698 .and_then(|lang| lang.config().wrap_characters.as_ref())
10699 .is_some()
10700 {
10701 return true;
10702 }
10703 }
10704 false
10705 }
10706
10707 fn wrap_selections_in_tag(
10708 &mut self,
10709 _: &WrapSelectionsInTag,
10710 window: &mut Window,
10711 cx: &mut Context<Self>,
10712 ) {
10713 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10714
10715 let snapshot = self.buffer.read(cx).snapshot(cx);
10716
10717 let mut edits = Vec::new();
10718 let mut boundaries = Vec::new();
10719
10720 for selection in self
10721 .selections
10722 .all_adjusted(&self.display_snapshot(cx))
10723 .iter()
10724 {
10725 let Some(wrap_config) = snapshot
10726 .language_at(selection.start)
10727 .and_then(|lang| lang.config().wrap_characters.clone())
10728 else {
10729 continue;
10730 };
10731
10732 let open_tag = format!("{}{}", wrap_config.start_prefix, wrap_config.start_suffix);
10733 let close_tag = format!("{}{}", wrap_config.end_prefix, wrap_config.end_suffix);
10734
10735 let start_before = snapshot.anchor_before(selection.start);
10736 let end_after = snapshot.anchor_after(selection.end);
10737
10738 edits.push((start_before..start_before, open_tag));
10739 edits.push((end_after..end_after, close_tag));
10740
10741 boundaries.push((
10742 start_before,
10743 end_after,
10744 wrap_config.start_prefix.len(),
10745 wrap_config.end_suffix.len(),
10746 ));
10747 }
10748
10749 if edits.is_empty() {
10750 return;
10751 }
10752
10753 self.transact(window, cx, |this, window, cx| {
10754 let buffer = this.buffer.update(cx, |buffer, cx| {
10755 buffer.edit(edits, None, cx);
10756 buffer.snapshot(cx)
10757 });
10758
10759 let mut new_selections = Vec::with_capacity(boundaries.len() * 2);
10760 for (start_before, end_after, start_prefix_len, end_suffix_len) in
10761 boundaries.into_iter()
10762 {
10763 let open_offset = start_before.to_offset(&buffer) + start_prefix_len;
10764 let close_offset = end_after.to_offset(&buffer).saturating_sub(end_suffix_len);
10765 new_selections.push(open_offset..open_offset);
10766 new_selections.push(close_offset..close_offset);
10767 }
10768
10769 this.change_selections(Default::default(), window, cx, |s| {
10770 s.select_ranges(new_selections);
10771 });
10772
10773 this.request_autoscroll(Autoscroll::fit(), cx);
10774 });
10775 }
10776
10777 pub fn reload_file(&mut self, _: &ReloadFile, window: &mut Window, cx: &mut Context<Self>) {
10778 let Some(project) = self.project.clone() else {
10779 return;
10780 };
10781 self.reload(project, window, cx)
10782 .detach_and_notify_err(window, cx);
10783 }
10784
10785 pub fn restore_file(
10786 &mut self,
10787 _: &::git::RestoreFile,
10788 window: &mut Window,
10789 cx: &mut Context<Self>,
10790 ) {
10791 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10792 let mut buffer_ids = HashSet::default();
10793 let snapshot = self.buffer().read(cx).snapshot(cx);
10794 for selection in self.selections.all::<usize>(&self.display_snapshot(cx)) {
10795 buffer_ids.extend(snapshot.buffer_ids_for_range(selection.range()))
10796 }
10797
10798 let buffer = self.buffer().read(cx);
10799 let ranges = buffer_ids
10800 .into_iter()
10801 .flat_map(|buffer_id| buffer.excerpt_ranges_for_buffer(buffer_id, cx))
10802 .collect::<Vec<_>>();
10803
10804 self.restore_hunks_in_ranges(ranges, window, cx);
10805 }
10806
10807 pub fn git_restore(&mut self, _: &Restore, window: &mut Window, cx: &mut Context<Self>) {
10808 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
10809 let selections = self
10810 .selections
10811 .all(&self.display_snapshot(cx))
10812 .into_iter()
10813 .map(|s| s.range())
10814 .collect();
10815 self.restore_hunks_in_ranges(selections, window, cx);
10816 }
10817
10818 pub fn restore_hunks_in_ranges(
10819 &mut self,
10820 ranges: Vec<Range<Point>>,
10821 window: &mut Window,
10822 cx: &mut Context<Editor>,
10823 ) {
10824 let mut revert_changes = HashMap::default();
10825 let chunk_by = self
10826 .snapshot(window, cx)
10827 .hunks_for_ranges(ranges)
10828 .into_iter()
10829 .chunk_by(|hunk| hunk.buffer_id);
10830 for (buffer_id, hunks) in &chunk_by {
10831 let hunks = hunks.collect::<Vec<_>>();
10832 for hunk in &hunks {
10833 self.prepare_restore_change(&mut revert_changes, hunk, cx);
10834 }
10835 self.do_stage_or_unstage(false, buffer_id, hunks.into_iter(), cx);
10836 }
10837 drop(chunk_by);
10838 if !revert_changes.is_empty() {
10839 self.transact(window, cx, |editor, window, cx| {
10840 editor.restore(revert_changes, window, cx);
10841 });
10842 }
10843 }
10844
10845 pub fn status_for_buffer_id(&self, buffer_id: BufferId, cx: &App) -> Option<FileStatus> {
10846 if let Some(status) = self
10847 .addons
10848 .iter()
10849 .find_map(|(_, addon)| addon.override_status_for_buffer_id(buffer_id, cx))
10850 {
10851 return Some(status);
10852 }
10853 self.project
10854 .as_ref()?
10855 .read(cx)
10856 .status_for_buffer_id(buffer_id, cx)
10857 }
10858
10859 pub fn open_active_item_in_terminal(
10860 &mut self,
10861 _: &OpenInTerminal,
10862 window: &mut Window,
10863 cx: &mut Context<Self>,
10864 ) {
10865 if let Some(working_directory) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
10866 let project_path = buffer.read(cx).project_path(cx)?;
10867 let project = self.project()?.read(cx);
10868 let entry = project.entry_for_path(&project_path, cx)?;
10869 let parent = match &entry.canonical_path {
10870 Some(canonical_path) => canonical_path.to_path_buf(),
10871 None => project.absolute_path(&project_path, cx)?,
10872 }
10873 .parent()?
10874 .to_path_buf();
10875 Some(parent)
10876 }) {
10877 window.dispatch_action(OpenTerminal { working_directory }.boxed_clone(), cx);
10878 }
10879 }
10880
10881 fn set_breakpoint_context_menu(
10882 &mut self,
10883 display_row: DisplayRow,
10884 position: Option<Anchor>,
10885 clicked_point: gpui::Point<Pixels>,
10886 window: &mut Window,
10887 cx: &mut Context<Self>,
10888 ) {
10889 let source = self
10890 .buffer
10891 .read(cx)
10892 .snapshot(cx)
10893 .anchor_before(Point::new(display_row.0, 0u32));
10894
10895 let context_menu = self.breakpoint_context_menu(position.unwrap_or(source), window, cx);
10896
10897 self.mouse_context_menu = MouseContextMenu::pinned_to_editor(
10898 self,
10899 source,
10900 clicked_point,
10901 context_menu,
10902 window,
10903 cx,
10904 );
10905 }
10906
10907 fn add_edit_breakpoint_block(
10908 &mut self,
10909 anchor: Anchor,
10910 breakpoint: &Breakpoint,
10911 edit_action: BreakpointPromptEditAction,
10912 window: &mut Window,
10913 cx: &mut Context<Self>,
10914 ) {
10915 let weak_editor = cx.weak_entity();
10916 let bp_prompt = cx.new(|cx| {
10917 BreakpointPromptEditor::new(
10918 weak_editor,
10919 anchor,
10920 breakpoint.clone(),
10921 edit_action,
10922 window,
10923 cx,
10924 )
10925 });
10926
10927 let height = bp_prompt.update(cx, |this, cx| {
10928 this.prompt
10929 .update(cx, |prompt, cx| prompt.max_point(cx).row().0 + 1 + 2)
10930 });
10931 let cloned_prompt = bp_prompt.clone();
10932 let blocks = vec![BlockProperties {
10933 style: BlockStyle::Sticky,
10934 placement: BlockPlacement::Above(anchor),
10935 height: Some(height),
10936 render: Arc::new(move |cx| {
10937 *cloned_prompt.read(cx).editor_margins.lock() = *cx.margins;
10938 cloned_prompt.clone().into_any_element()
10939 }),
10940 priority: 0,
10941 }];
10942
10943 let focus_handle = bp_prompt.focus_handle(cx);
10944 window.focus(&focus_handle);
10945
10946 let block_ids = self.insert_blocks(blocks, None, cx);
10947 bp_prompt.update(cx, |prompt, _| {
10948 prompt.add_block_ids(block_ids);
10949 });
10950 }
10951
10952 pub(crate) fn breakpoint_at_row(
10953 &self,
10954 row: u32,
10955 window: &mut Window,
10956 cx: &mut Context<Self>,
10957 ) -> Option<(Anchor, Breakpoint)> {
10958 let snapshot = self.snapshot(window, cx);
10959 let breakpoint_position = snapshot.buffer_snapshot().anchor_before(Point::new(row, 0));
10960
10961 self.breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
10962 }
10963
10964 pub(crate) fn breakpoint_at_anchor(
10965 &self,
10966 breakpoint_position: Anchor,
10967 snapshot: &EditorSnapshot,
10968 cx: &mut Context<Self>,
10969 ) -> Option<(Anchor, Breakpoint)> {
10970 let buffer = self
10971 .buffer
10972 .read(cx)
10973 .buffer_for_anchor(breakpoint_position, cx)?;
10974
10975 let enclosing_excerpt = breakpoint_position.excerpt_id;
10976 let buffer_snapshot = buffer.read(cx).snapshot();
10977
10978 let row = buffer_snapshot
10979 .summary_for_anchor::<text::PointUtf16>(&breakpoint_position.text_anchor)
10980 .row;
10981
10982 let line_len = snapshot.buffer_snapshot().line_len(MultiBufferRow(row));
10983 let anchor_end = snapshot
10984 .buffer_snapshot()
10985 .anchor_after(Point::new(row, line_len));
10986
10987 self.breakpoint_store
10988 .as_ref()?
10989 .read_with(cx, |breakpoint_store, cx| {
10990 breakpoint_store
10991 .breakpoints(
10992 &buffer,
10993 Some(breakpoint_position.text_anchor..anchor_end.text_anchor),
10994 &buffer_snapshot,
10995 cx,
10996 )
10997 .next()
10998 .and_then(|(bp, _)| {
10999 let breakpoint_row = buffer_snapshot
11000 .summary_for_anchor::<text::PointUtf16>(&bp.position)
11001 .row;
11002
11003 if breakpoint_row == row {
11004 snapshot
11005 .buffer_snapshot()
11006 .anchor_in_excerpt(enclosing_excerpt, bp.position)
11007 .map(|position| (position, bp.bp.clone()))
11008 } else {
11009 None
11010 }
11011 })
11012 })
11013 }
11014
11015 pub fn edit_log_breakpoint(
11016 &mut self,
11017 _: &EditLogBreakpoint,
11018 window: &mut Window,
11019 cx: &mut Context<Self>,
11020 ) {
11021 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11022 let breakpoint = breakpoint.unwrap_or_else(|| Breakpoint {
11023 message: None,
11024 state: BreakpointState::Enabled,
11025 condition: None,
11026 hit_condition: None,
11027 });
11028
11029 self.add_edit_breakpoint_block(
11030 anchor,
11031 &breakpoint,
11032 BreakpointPromptEditAction::Log,
11033 window,
11034 cx,
11035 );
11036 }
11037 }
11038
11039 fn breakpoints_at_cursors(
11040 &self,
11041 window: &mut Window,
11042 cx: &mut Context<Self>,
11043 ) -> Vec<(Anchor, Option<Breakpoint>)> {
11044 let snapshot = self.snapshot(window, cx);
11045 let cursors = self
11046 .selections
11047 .disjoint_anchors_arc()
11048 .iter()
11049 .map(|selection| {
11050 let cursor_position: Point = selection.head().to_point(&snapshot.buffer_snapshot());
11051
11052 let breakpoint_position = self
11053 .breakpoint_at_row(cursor_position.row, window, cx)
11054 .map(|bp| bp.0)
11055 .unwrap_or_else(|| {
11056 snapshot
11057 .display_snapshot
11058 .buffer_snapshot()
11059 .anchor_after(Point::new(cursor_position.row, 0))
11060 });
11061
11062 let breakpoint = self
11063 .breakpoint_at_anchor(breakpoint_position, &snapshot, cx)
11064 .map(|(anchor, breakpoint)| (anchor, Some(breakpoint)));
11065
11066 breakpoint.unwrap_or_else(|| (breakpoint_position, None))
11067 })
11068 // 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.
11069 .collect::<HashMap<Anchor, _>>();
11070
11071 cursors.into_iter().collect()
11072 }
11073
11074 pub fn enable_breakpoint(
11075 &mut self,
11076 _: &crate::actions::EnableBreakpoint,
11077 window: &mut Window,
11078 cx: &mut Context<Self>,
11079 ) {
11080 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11081 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_disabled()) else {
11082 continue;
11083 };
11084 self.edit_breakpoint_at_anchor(
11085 anchor,
11086 breakpoint,
11087 BreakpointEditAction::InvertState,
11088 cx,
11089 );
11090 }
11091 }
11092
11093 pub fn disable_breakpoint(
11094 &mut self,
11095 _: &crate::actions::DisableBreakpoint,
11096 window: &mut Window,
11097 cx: &mut Context<Self>,
11098 ) {
11099 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11100 let Some(breakpoint) = breakpoint.filter(|breakpoint| breakpoint.is_enabled()) else {
11101 continue;
11102 };
11103 self.edit_breakpoint_at_anchor(
11104 anchor,
11105 breakpoint,
11106 BreakpointEditAction::InvertState,
11107 cx,
11108 );
11109 }
11110 }
11111
11112 pub fn toggle_breakpoint(
11113 &mut self,
11114 _: &crate::actions::ToggleBreakpoint,
11115 window: &mut Window,
11116 cx: &mut Context<Self>,
11117 ) {
11118 for (anchor, breakpoint) in self.breakpoints_at_cursors(window, cx) {
11119 if let Some(breakpoint) = breakpoint {
11120 self.edit_breakpoint_at_anchor(
11121 anchor,
11122 breakpoint,
11123 BreakpointEditAction::Toggle,
11124 cx,
11125 );
11126 } else {
11127 self.edit_breakpoint_at_anchor(
11128 anchor,
11129 Breakpoint::new_standard(),
11130 BreakpointEditAction::Toggle,
11131 cx,
11132 );
11133 }
11134 }
11135 }
11136
11137 pub fn edit_breakpoint_at_anchor(
11138 &mut self,
11139 breakpoint_position: Anchor,
11140 breakpoint: Breakpoint,
11141 edit_action: BreakpointEditAction,
11142 cx: &mut Context<Self>,
11143 ) {
11144 let Some(breakpoint_store) = &self.breakpoint_store else {
11145 return;
11146 };
11147
11148 let Some(buffer) = self
11149 .buffer
11150 .read(cx)
11151 .buffer_for_anchor(breakpoint_position, cx)
11152 else {
11153 return;
11154 };
11155
11156 breakpoint_store.update(cx, |breakpoint_store, cx| {
11157 breakpoint_store.toggle_breakpoint(
11158 buffer,
11159 BreakpointWithPosition {
11160 position: breakpoint_position.text_anchor,
11161 bp: breakpoint,
11162 },
11163 edit_action,
11164 cx,
11165 );
11166 });
11167
11168 cx.notify();
11169 }
11170
11171 #[cfg(any(test, feature = "test-support"))]
11172 pub fn breakpoint_store(&self) -> Option<Entity<BreakpointStore>> {
11173 self.breakpoint_store.clone()
11174 }
11175
11176 pub fn prepare_restore_change(
11177 &self,
11178 revert_changes: &mut HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
11179 hunk: &MultiBufferDiffHunk,
11180 cx: &mut App,
11181 ) -> Option<()> {
11182 if hunk.is_created_file() {
11183 return None;
11184 }
11185 let buffer = self.buffer.read(cx);
11186 let diff = buffer.diff_for(hunk.buffer_id)?;
11187 let buffer = buffer.buffer(hunk.buffer_id)?;
11188 let buffer = buffer.read(cx);
11189 let original_text = diff
11190 .read(cx)
11191 .base_text()
11192 .as_rope()
11193 .slice(hunk.diff_base_byte_range.clone());
11194 let buffer_snapshot = buffer.snapshot();
11195 let buffer_revert_changes = revert_changes.entry(buffer.remote_id()).or_default();
11196 if let Err(i) = buffer_revert_changes.binary_search_by(|probe| {
11197 probe
11198 .0
11199 .start
11200 .cmp(&hunk.buffer_range.start, &buffer_snapshot)
11201 .then(probe.0.end.cmp(&hunk.buffer_range.end, &buffer_snapshot))
11202 }) {
11203 buffer_revert_changes.insert(i, (hunk.buffer_range.clone(), original_text));
11204 Some(())
11205 } else {
11206 None
11207 }
11208 }
11209
11210 pub fn reverse_lines(&mut self, _: &ReverseLines, window: &mut Window, cx: &mut Context<Self>) {
11211 self.manipulate_immutable_lines(window, cx, |lines| lines.reverse())
11212 }
11213
11214 pub fn shuffle_lines(&mut self, _: &ShuffleLines, window: &mut Window, cx: &mut Context<Self>) {
11215 self.manipulate_immutable_lines(window, cx, |lines| lines.shuffle(&mut rand::rng()))
11216 }
11217
11218 fn manipulate_lines<M>(
11219 &mut self,
11220 window: &mut Window,
11221 cx: &mut Context<Self>,
11222 mut manipulate: M,
11223 ) where
11224 M: FnMut(&str) -> LineManipulationResult,
11225 {
11226 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11227
11228 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11229 let buffer = self.buffer.read(cx).snapshot(cx);
11230
11231 let mut edits = Vec::new();
11232
11233 let selections = self.selections.all::<Point>(&display_map);
11234 let mut selections = selections.iter().peekable();
11235 let mut contiguous_row_selections = Vec::new();
11236 let mut new_selections = Vec::new();
11237 let mut added_lines = 0;
11238 let mut removed_lines = 0;
11239
11240 while let Some(selection) = selections.next() {
11241 let (start_row, end_row) = consume_contiguous_rows(
11242 &mut contiguous_row_selections,
11243 selection,
11244 &display_map,
11245 &mut selections,
11246 );
11247
11248 let start_point = Point::new(start_row.0, 0);
11249 let end_point = Point::new(
11250 end_row.previous_row().0,
11251 buffer.line_len(end_row.previous_row()),
11252 );
11253 let text = buffer
11254 .text_for_range(start_point..end_point)
11255 .collect::<String>();
11256
11257 let LineManipulationResult {
11258 new_text,
11259 line_count_before,
11260 line_count_after,
11261 } = manipulate(&text);
11262
11263 edits.push((start_point..end_point, new_text));
11264
11265 // Selections must change based on added and removed line count
11266 let start_row =
11267 MultiBufferRow(start_point.row + added_lines as u32 - removed_lines as u32);
11268 let end_row = MultiBufferRow(start_row.0 + line_count_after.saturating_sub(1) as u32);
11269 new_selections.push(Selection {
11270 id: selection.id,
11271 start: start_row,
11272 end: end_row,
11273 goal: SelectionGoal::None,
11274 reversed: selection.reversed,
11275 });
11276
11277 if line_count_after > line_count_before {
11278 added_lines += line_count_after - line_count_before;
11279 } else if line_count_before > line_count_after {
11280 removed_lines += line_count_before - line_count_after;
11281 }
11282 }
11283
11284 self.transact(window, cx, |this, window, cx| {
11285 let buffer = this.buffer.update(cx, |buffer, cx| {
11286 buffer.edit(edits, None, cx);
11287 buffer.snapshot(cx)
11288 });
11289
11290 // Recalculate offsets on newly edited buffer
11291 let new_selections = new_selections
11292 .iter()
11293 .map(|s| {
11294 let start_point = Point::new(s.start.0, 0);
11295 let end_point = Point::new(s.end.0, buffer.line_len(s.end));
11296 Selection {
11297 id: s.id,
11298 start: buffer.point_to_offset(start_point),
11299 end: buffer.point_to_offset(end_point),
11300 goal: s.goal,
11301 reversed: s.reversed,
11302 }
11303 })
11304 .collect();
11305
11306 this.change_selections(Default::default(), window, cx, |s| {
11307 s.select(new_selections);
11308 });
11309
11310 this.request_autoscroll(Autoscroll::fit(), cx);
11311 });
11312 }
11313
11314 fn manipulate_immutable_lines<Fn>(
11315 &mut self,
11316 window: &mut Window,
11317 cx: &mut Context<Self>,
11318 mut callback: Fn,
11319 ) where
11320 Fn: FnMut(&mut Vec<&str>),
11321 {
11322 self.manipulate_lines(window, cx, |text| {
11323 let mut lines: Vec<&str> = text.split('\n').collect();
11324 let line_count_before = lines.len();
11325
11326 callback(&mut lines);
11327
11328 LineManipulationResult {
11329 new_text: lines.join("\n"),
11330 line_count_before,
11331 line_count_after: lines.len(),
11332 }
11333 });
11334 }
11335
11336 fn manipulate_mutable_lines<Fn>(
11337 &mut self,
11338 window: &mut Window,
11339 cx: &mut Context<Self>,
11340 mut callback: Fn,
11341 ) where
11342 Fn: FnMut(&mut Vec<Cow<'_, str>>),
11343 {
11344 self.manipulate_lines(window, cx, |text| {
11345 let mut lines: Vec<Cow<str>> = text.split('\n').map(Cow::from).collect();
11346 let line_count_before = lines.len();
11347
11348 callback(&mut lines);
11349
11350 LineManipulationResult {
11351 new_text: lines.join("\n"),
11352 line_count_before,
11353 line_count_after: lines.len(),
11354 }
11355 });
11356 }
11357
11358 pub fn convert_indentation_to_spaces(
11359 &mut self,
11360 _: &ConvertIndentationToSpaces,
11361 window: &mut Window,
11362 cx: &mut Context<Self>,
11363 ) {
11364 let settings = self.buffer.read(cx).language_settings(cx);
11365 let tab_size = settings.tab_size.get() as usize;
11366
11367 self.manipulate_mutable_lines(window, cx, |lines| {
11368 // Allocates a reasonably sized scratch buffer once for the whole loop
11369 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11370 // Avoids recomputing spaces that could be inserted many times
11371 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11372 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11373 .collect();
11374
11375 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11376 let mut chars = line.as_ref().chars();
11377 let mut col = 0;
11378 let mut changed = false;
11379
11380 for ch in chars.by_ref() {
11381 match ch {
11382 ' ' => {
11383 reindented_line.push(' ');
11384 col += 1;
11385 }
11386 '\t' => {
11387 // \t are converted to spaces depending on the current column
11388 let spaces_len = tab_size - (col % tab_size);
11389 reindented_line.extend(&space_cache[spaces_len - 1]);
11390 col += spaces_len;
11391 changed = true;
11392 }
11393 _ => {
11394 // If we dont append before break, the character is consumed
11395 reindented_line.push(ch);
11396 break;
11397 }
11398 }
11399 }
11400
11401 if !changed {
11402 reindented_line.clear();
11403 continue;
11404 }
11405 // Append the rest of the line and replace old reference with new one
11406 reindented_line.extend(chars);
11407 *line = Cow::Owned(reindented_line.clone());
11408 reindented_line.clear();
11409 }
11410 });
11411 }
11412
11413 pub fn convert_indentation_to_tabs(
11414 &mut self,
11415 _: &ConvertIndentationToTabs,
11416 window: &mut Window,
11417 cx: &mut Context<Self>,
11418 ) {
11419 let settings = self.buffer.read(cx).language_settings(cx);
11420 let tab_size = settings.tab_size.get() as usize;
11421
11422 self.manipulate_mutable_lines(window, cx, |lines| {
11423 // Allocates a reasonably sized buffer once for the whole loop
11424 let mut reindented_line = String::with_capacity(MAX_LINE_LEN);
11425 // Avoids recomputing spaces that could be inserted many times
11426 let space_cache: Vec<Vec<char>> = (1..=tab_size)
11427 .map(|n| IndentSize::spaces(n as u32).chars().collect())
11428 .collect();
11429
11430 for line in lines.iter_mut().filter(|line| !line.is_empty()) {
11431 let mut chars = line.chars();
11432 let mut spaces_count = 0;
11433 let mut first_non_indent_char = None;
11434 let mut changed = false;
11435
11436 for ch in chars.by_ref() {
11437 match ch {
11438 ' ' => {
11439 // Keep track of spaces. Append \t when we reach tab_size
11440 spaces_count += 1;
11441 changed = true;
11442 if spaces_count == tab_size {
11443 reindented_line.push('\t');
11444 spaces_count = 0;
11445 }
11446 }
11447 '\t' => {
11448 reindented_line.push('\t');
11449 spaces_count = 0;
11450 }
11451 _ => {
11452 // Dont append it yet, we might have remaining spaces
11453 first_non_indent_char = Some(ch);
11454 break;
11455 }
11456 }
11457 }
11458
11459 if !changed {
11460 reindented_line.clear();
11461 continue;
11462 }
11463 // Remaining spaces that didn't make a full tab stop
11464 if spaces_count > 0 {
11465 reindented_line.extend(&space_cache[spaces_count - 1]);
11466 }
11467 // If we consume an extra character that was not indentation, add it back
11468 if let Some(extra_char) = first_non_indent_char {
11469 reindented_line.push(extra_char);
11470 }
11471 // Append the rest of the line and replace old reference with new one
11472 reindented_line.extend(chars);
11473 *line = Cow::Owned(reindented_line.clone());
11474 reindented_line.clear();
11475 }
11476 });
11477 }
11478
11479 pub fn convert_to_upper_case(
11480 &mut self,
11481 _: &ConvertToUpperCase,
11482 window: &mut Window,
11483 cx: &mut Context<Self>,
11484 ) {
11485 self.manipulate_text(window, cx, |text| text.to_uppercase())
11486 }
11487
11488 pub fn convert_to_lower_case(
11489 &mut self,
11490 _: &ConvertToLowerCase,
11491 window: &mut Window,
11492 cx: &mut Context<Self>,
11493 ) {
11494 self.manipulate_text(window, cx, |text| text.to_lowercase())
11495 }
11496
11497 pub fn convert_to_title_case(
11498 &mut self,
11499 _: &ConvertToTitleCase,
11500 window: &mut Window,
11501 cx: &mut Context<Self>,
11502 ) {
11503 self.manipulate_text(window, cx, |text| {
11504 text.split('\n')
11505 .map(|line| line.to_case(Case::Title))
11506 .join("\n")
11507 })
11508 }
11509
11510 pub fn convert_to_snake_case(
11511 &mut self,
11512 _: &ConvertToSnakeCase,
11513 window: &mut Window,
11514 cx: &mut Context<Self>,
11515 ) {
11516 self.manipulate_text(window, cx, |text| text.to_case(Case::Snake))
11517 }
11518
11519 pub fn convert_to_kebab_case(
11520 &mut self,
11521 _: &ConvertToKebabCase,
11522 window: &mut Window,
11523 cx: &mut Context<Self>,
11524 ) {
11525 self.manipulate_text(window, cx, |text| text.to_case(Case::Kebab))
11526 }
11527
11528 pub fn convert_to_upper_camel_case(
11529 &mut self,
11530 _: &ConvertToUpperCamelCase,
11531 window: &mut Window,
11532 cx: &mut Context<Self>,
11533 ) {
11534 self.manipulate_text(window, cx, |text| {
11535 text.split('\n')
11536 .map(|line| line.to_case(Case::UpperCamel))
11537 .join("\n")
11538 })
11539 }
11540
11541 pub fn convert_to_lower_camel_case(
11542 &mut self,
11543 _: &ConvertToLowerCamelCase,
11544 window: &mut Window,
11545 cx: &mut Context<Self>,
11546 ) {
11547 self.manipulate_text(window, cx, |text| text.to_case(Case::Camel))
11548 }
11549
11550 pub fn convert_to_opposite_case(
11551 &mut self,
11552 _: &ConvertToOppositeCase,
11553 window: &mut Window,
11554 cx: &mut Context<Self>,
11555 ) {
11556 self.manipulate_text(window, cx, |text| {
11557 text.chars()
11558 .fold(String::with_capacity(text.len()), |mut t, c| {
11559 if c.is_uppercase() {
11560 t.extend(c.to_lowercase());
11561 } else {
11562 t.extend(c.to_uppercase());
11563 }
11564 t
11565 })
11566 })
11567 }
11568
11569 pub fn convert_to_sentence_case(
11570 &mut self,
11571 _: &ConvertToSentenceCase,
11572 window: &mut Window,
11573 cx: &mut Context<Self>,
11574 ) {
11575 self.manipulate_text(window, cx, |text| text.to_case(Case::Sentence))
11576 }
11577
11578 pub fn toggle_case(&mut self, _: &ToggleCase, window: &mut Window, cx: &mut Context<Self>) {
11579 self.manipulate_text(window, cx, |text| {
11580 let has_upper_case_characters = text.chars().any(|c| c.is_uppercase());
11581 if has_upper_case_characters {
11582 text.to_lowercase()
11583 } else {
11584 text.to_uppercase()
11585 }
11586 })
11587 }
11588
11589 pub fn convert_to_rot13(
11590 &mut self,
11591 _: &ConvertToRot13,
11592 window: &mut Window,
11593 cx: &mut Context<Self>,
11594 ) {
11595 self.manipulate_text(window, cx, |text| {
11596 text.chars()
11597 .map(|c| match c {
11598 'A'..='M' | 'a'..='m' => ((c as u8) + 13) as char,
11599 'N'..='Z' | 'n'..='z' => ((c as u8) - 13) as char,
11600 _ => c,
11601 })
11602 .collect()
11603 })
11604 }
11605
11606 pub fn convert_to_rot47(
11607 &mut self,
11608 _: &ConvertToRot47,
11609 window: &mut Window,
11610 cx: &mut Context<Self>,
11611 ) {
11612 self.manipulate_text(window, cx, |text| {
11613 text.chars()
11614 .map(|c| {
11615 let code_point = c as u32;
11616 if code_point >= 33 && code_point <= 126 {
11617 return char::from_u32(33 + ((code_point + 14) % 94)).unwrap();
11618 }
11619 c
11620 })
11621 .collect()
11622 })
11623 }
11624
11625 fn manipulate_text<Fn>(&mut self, window: &mut Window, cx: &mut Context<Self>, mut callback: Fn)
11626 where
11627 Fn: FnMut(&str) -> String,
11628 {
11629 let buffer = self.buffer.read(cx).snapshot(cx);
11630
11631 let mut new_selections = Vec::new();
11632 let mut edits = Vec::new();
11633 let mut selection_adjustment = 0i32;
11634
11635 for selection in self.selections.all_adjusted(&self.display_snapshot(cx)) {
11636 let selection_is_empty = selection.is_empty();
11637
11638 let (start, end) = if selection_is_empty {
11639 let (word_range, _) = buffer.surrounding_word(selection.start, None);
11640 (word_range.start, word_range.end)
11641 } else {
11642 (
11643 buffer.point_to_offset(selection.start),
11644 buffer.point_to_offset(selection.end),
11645 )
11646 };
11647
11648 let text = buffer.text_for_range(start..end).collect::<String>();
11649 let old_length = text.len() as i32;
11650 let text = callback(&text);
11651
11652 new_selections.push(Selection {
11653 start: (start as i32 - selection_adjustment) as usize,
11654 end: ((start + text.len()) as i32 - selection_adjustment) as usize,
11655 goal: SelectionGoal::None,
11656 id: selection.id,
11657 reversed: selection.reversed,
11658 });
11659
11660 selection_adjustment += old_length - text.len() as i32;
11661
11662 edits.push((start..end, text));
11663 }
11664
11665 self.transact(window, cx, |this, window, cx| {
11666 this.buffer.update(cx, |buffer, cx| {
11667 buffer.edit(edits, None, cx);
11668 });
11669
11670 this.change_selections(Default::default(), window, cx, |s| {
11671 s.select(new_selections);
11672 });
11673
11674 this.request_autoscroll(Autoscroll::fit(), cx);
11675 });
11676 }
11677
11678 pub fn move_selection_on_drop(
11679 &mut self,
11680 selection: &Selection<Anchor>,
11681 target: DisplayPoint,
11682 is_cut: bool,
11683 window: &mut Window,
11684 cx: &mut Context<Self>,
11685 ) {
11686 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11687 let buffer = display_map.buffer_snapshot();
11688 let mut edits = Vec::new();
11689 let insert_point = display_map
11690 .clip_point(target, Bias::Left)
11691 .to_point(&display_map);
11692 let text = buffer
11693 .text_for_range(selection.start..selection.end)
11694 .collect::<String>();
11695 if is_cut {
11696 edits.push(((selection.start..selection.end), String::new()));
11697 }
11698 let insert_anchor = buffer.anchor_before(insert_point);
11699 edits.push(((insert_anchor..insert_anchor), text));
11700 let last_edit_start = insert_anchor.bias_left(buffer);
11701 let last_edit_end = insert_anchor.bias_right(buffer);
11702 self.transact(window, cx, |this, window, cx| {
11703 this.buffer.update(cx, |buffer, cx| {
11704 buffer.edit(edits, None, cx);
11705 });
11706 this.change_selections(Default::default(), window, cx, |s| {
11707 s.select_anchor_ranges([last_edit_start..last_edit_end]);
11708 });
11709 });
11710 }
11711
11712 pub fn clear_selection_drag_state(&mut self) {
11713 self.selection_drag_state = SelectionDragState::None;
11714 }
11715
11716 pub fn duplicate(
11717 &mut self,
11718 upwards: bool,
11719 whole_lines: bool,
11720 window: &mut Window,
11721 cx: &mut Context<Self>,
11722 ) {
11723 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11724
11725 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11726 let buffer = display_map.buffer_snapshot();
11727 let selections = self.selections.all::<Point>(&display_map);
11728
11729 let mut edits = Vec::new();
11730 let mut selections_iter = selections.iter().peekable();
11731 while let Some(selection) = selections_iter.next() {
11732 let mut rows = selection.spanned_rows(false, &display_map);
11733 // duplicate line-wise
11734 if whole_lines || selection.start == selection.end {
11735 // Avoid duplicating the same lines twice.
11736 while let Some(next_selection) = selections_iter.peek() {
11737 let next_rows = next_selection.spanned_rows(false, &display_map);
11738 if next_rows.start < rows.end {
11739 rows.end = next_rows.end;
11740 selections_iter.next().unwrap();
11741 } else {
11742 break;
11743 }
11744 }
11745
11746 // Copy the text from the selected row region and splice it either at the start
11747 // or end of the region.
11748 let start = Point::new(rows.start.0, 0);
11749 let end = Point::new(
11750 rows.end.previous_row().0,
11751 buffer.line_len(rows.end.previous_row()),
11752 );
11753
11754 let mut text = buffer.text_for_range(start..end).collect::<String>();
11755
11756 let insert_location = if upwards {
11757 // When duplicating upward, we need to insert before the current line.
11758 // If we're on the last line and it doesn't end with a newline,
11759 // we need to add a newline before the duplicated content.
11760 let needs_leading_newline = rows.end.0 >= buffer.max_point().row
11761 && buffer.max_point().column > 0
11762 && !text.ends_with('\n');
11763
11764 if needs_leading_newline {
11765 text.insert(0, '\n');
11766 end
11767 } else {
11768 text.push('\n');
11769 Point::new(rows.start.0, 0)
11770 }
11771 } else {
11772 text.push('\n');
11773 start
11774 };
11775 edits.push((insert_location..insert_location, text));
11776 } else {
11777 // duplicate character-wise
11778 let start = selection.start;
11779 let end = selection.end;
11780 let text = buffer.text_for_range(start..end).collect::<String>();
11781 edits.push((selection.end..selection.end, text));
11782 }
11783 }
11784
11785 self.transact(window, cx, |this, window, cx| {
11786 this.buffer.update(cx, |buffer, cx| {
11787 buffer.edit(edits, None, cx);
11788 });
11789
11790 // When duplicating upward with whole lines, move the cursor to the duplicated line
11791 if upwards && whole_lines {
11792 let display_map = this.display_map.update(cx, |map, cx| map.snapshot(cx));
11793
11794 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
11795 let mut new_ranges = Vec::new();
11796 let selections = s.all::<Point>(&display_map);
11797 let mut selections_iter = selections.iter().peekable();
11798
11799 while let Some(first_selection) = selections_iter.next() {
11800 // Group contiguous selections together to find the total row span
11801 let mut group_selections = vec![first_selection];
11802 let mut rows = first_selection.spanned_rows(false, &display_map);
11803
11804 while let Some(next_selection) = selections_iter.peek() {
11805 let next_rows = next_selection.spanned_rows(false, &display_map);
11806 if next_rows.start < rows.end {
11807 rows.end = next_rows.end;
11808 group_selections.push(selections_iter.next().unwrap());
11809 } else {
11810 break;
11811 }
11812 }
11813
11814 let row_count = rows.end.0 - rows.start.0;
11815
11816 // Move all selections in this group up by the total number of duplicated rows
11817 for selection in group_selections {
11818 let new_start = Point::new(
11819 selection.start.row.saturating_sub(row_count),
11820 selection.start.column,
11821 );
11822
11823 let new_end = Point::new(
11824 selection.end.row.saturating_sub(row_count),
11825 selection.end.column,
11826 );
11827
11828 new_ranges.push(new_start..new_end);
11829 }
11830 }
11831
11832 s.select_ranges(new_ranges);
11833 });
11834 }
11835
11836 this.request_autoscroll(Autoscroll::fit(), cx);
11837 });
11838 }
11839
11840 pub fn duplicate_line_up(
11841 &mut self,
11842 _: &DuplicateLineUp,
11843 window: &mut Window,
11844 cx: &mut Context<Self>,
11845 ) {
11846 self.duplicate(true, true, window, cx);
11847 }
11848
11849 pub fn duplicate_line_down(
11850 &mut self,
11851 _: &DuplicateLineDown,
11852 window: &mut Window,
11853 cx: &mut Context<Self>,
11854 ) {
11855 self.duplicate(false, true, window, cx);
11856 }
11857
11858 pub fn duplicate_selection(
11859 &mut self,
11860 _: &DuplicateSelection,
11861 window: &mut Window,
11862 cx: &mut Context<Self>,
11863 ) {
11864 self.duplicate(false, false, window, cx);
11865 }
11866
11867 pub fn move_line_up(&mut self, _: &MoveLineUp, window: &mut Window, cx: &mut Context<Self>) {
11868 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11869 if self.mode.is_single_line() {
11870 cx.propagate();
11871 return;
11872 }
11873
11874 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11875 let buffer = self.buffer.read(cx).snapshot(cx);
11876
11877 let mut edits = Vec::new();
11878 let mut unfold_ranges = Vec::new();
11879 let mut refold_creases = Vec::new();
11880
11881 let selections = self.selections.all::<Point>(&display_map);
11882 let mut selections = selections.iter().peekable();
11883 let mut contiguous_row_selections = Vec::new();
11884 let mut new_selections = Vec::new();
11885
11886 while let Some(selection) = selections.next() {
11887 // Find all the selections that span a contiguous row range
11888 let (start_row, end_row) = consume_contiguous_rows(
11889 &mut contiguous_row_selections,
11890 selection,
11891 &display_map,
11892 &mut selections,
11893 );
11894
11895 // Move the text spanned by the row range to be before the line preceding the row range
11896 if start_row.0 > 0 {
11897 let range_to_move = Point::new(
11898 start_row.previous_row().0,
11899 buffer.line_len(start_row.previous_row()),
11900 )
11901 ..Point::new(
11902 end_row.previous_row().0,
11903 buffer.line_len(end_row.previous_row()),
11904 );
11905 let insertion_point = display_map
11906 .prev_line_boundary(Point::new(start_row.previous_row().0, 0))
11907 .0;
11908
11909 // Don't move lines across excerpts
11910 if buffer
11911 .excerpt_containing(insertion_point..range_to_move.end)
11912 .is_some()
11913 {
11914 let text = buffer
11915 .text_for_range(range_to_move.clone())
11916 .flat_map(|s| s.chars())
11917 .skip(1)
11918 .chain(['\n'])
11919 .collect::<String>();
11920
11921 edits.push((
11922 buffer.anchor_after(range_to_move.start)
11923 ..buffer.anchor_before(range_to_move.end),
11924 String::new(),
11925 ));
11926 let insertion_anchor = buffer.anchor_after(insertion_point);
11927 edits.push((insertion_anchor..insertion_anchor, text));
11928
11929 let row_delta = range_to_move.start.row - insertion_point.row + 1;
11930
11931 // Move selections up
11932 new_selections.extend(contiguous_row_selections.drain(..).map(
11933 |mut selection| {
11934 selection.start.row -= row_delta;
11935 selection.end.row -= row_delta;
11936 selection
11937 },
11938 ));
11939
11940 // Move folds up
11941 unfold_ranges.push(range_to_move.clone());
11942 for fold in display_map.folds_in_range(
11943 buffer.anchor_before(range_to_move.start)
11944 ..buffer.anchor_after(range_to_move.end),
11945 ) {
11946 let mut start = fold.range.start.to_point(&buffer);
11947 let mut end = fold.range.end.to_point(&buffer);
11948 start.row -= row_delta;
11949 end.row -= row_delta;
11950 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
11951 }
11952 }
11953 }
11954
11955 // If we didn't move line(s), preserve the existing selections
11956 new_selections.append(&mut contiguous_row_selections);
11957 }
11958
11959 self.transact(window, cx, |this, window, cx| {
11960 this.unfold_ranges(&unfold_ranges, true, true, cx);
11961 this.buffer.update(cx, |buffer, cx| {
11962 for (range, text) in edits {
11963 buffer.edit([(range, text)], None, cx);
11964 }
11965 });
11966 this.fold_creases(refold_creases, true, window, cx);
11967 this.change_selections(Default::default(), window, cx, |s| {
11968 s.select(new_selections);
11969 })
11970 });
11971 }
11972
11973 pub fn move_line_down(
11974 &mut self,
11975 _: &MoveLineDown,
11976 window: &mut Window,
11977 cx: &mut Context<Self>,
11978 ) {
11979 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
11980 if self.mode.is_single_line() {
11981 cx.propagate();
11982 return;
11983 }
11984
11985 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
11986 let buffer = self.buffer.read(cx).snapshot(cx);
11987
11988 let mut edits = Vec::new();
11989 let mut unfold_ranges = Vec::new();
11990 let mut refold_creases = Vec::new();
11991
11992 let selections = self.selections.all::<Point>(&display_map);
11993 let mut selections = selections.iter().peekable();
11994 let mut contiguous_row_selections = Vec::new();
11995 let mut new_selections = Vec::new();
11996
11997 while let Some(selection) = selections.next() {
11998 // Find all the selections that span a contiguous row range
11999 let (start_row, end_row) = consume_contiguous_rows(
12000 &mut contiguous_row_selections,
12001 selection,
12002 &display_map,
12003 &mut selections,
12004 );
12005
12006 // Move the text spanned by the row range to be after the last line of the row range
12007 if end_row.0 <= buffer.max_point().row {
12008 let range_to_move =
12009 MultiBufferPoint::new(start_row.0, 0)..MultiBufferPoint::new(end_row.0, 0);
12010 let insertion_point = display_map
12011 .next_line_boundary(MultiBufferPoint::new(end_row.0, 0))
12012 .0;
12013
12014 // Don't move lines across excerpt boundaries
12015 if buffer
12016 .excerpt_containing(range_to_move.start..insertion_point)
12017 .is_some()
12018 {
12019 let mut text = String::from("\n");
12020 text.extend(buffer.text_for_range(range_to_move.clone()));
12021 text.pop(); // Drop trailing newline
12022 edits.push((
12023 buffer.anchor_after(range_to_move.start)
12024 ..buffer.anchor_before(range_to_move.end),
12025 String::new(),
12026 ));
12027 let insertion_anchor = buffer.anchor_after(insertion_point);
12028 edits.push((insertion_anchor..insertion_anchor, text));
12029
12030 let row_delta = insertion_point.row - range_to_move.end.row + 1;
12031
12032 // Move selections down
12033 new_selections.extend(contiguous_row_selections.drain(..).map(
12034 |mut selection| {
12035 selection.start.row += row_delta;
12036 selection.end.row += row_delta;
12037 selection
12038 },
12039 ));
12040
12041 // Move folds down
12042 unfold_ranges.push(range_to_move.clone());
12043 for fold in display_map.folds_in_range(
12044 buffer.anchor_before(range_to_move.start)
12045 ..buffer.anchor_after(range_to_move.end),
12046 ) {
12047 let mut start = fold.range.start.to_point(&buffer);
12048 let mut end = fold.range.end.to_point(&buffer);
12049 start.row += row_delta;
12050 end.row += row_delta;
12051 refold_creases.push(Crease::simple(start..end, fold.placeholder.clone()));
12052 }
12053 }
12054 }
12055
12056 // If we didn't move line(s), preserve the existing selections
12057 new_selections.append(&mut contiguous_row_selections);
12058 }
12059
12060 self.transact(window, cx, |this, window, cx| {
12061 this.unfold_ranges(&unfold_ranges, true, true, cx);
12062 this.buffer.update(cx, |buffer, cx| {
12063 for (range, text) in edits {
12064 buffer.edit([(range, text)], None, cx);
12065 }
12066 });
12067 this.fold_creases(refold_creases, true, window, cx);
12068 this.change_selections(Default::default(), window, cx, |s| s.select(new_selections));
12069 });
12070 }
12071
12072 pub fn transpose(&mut self, _: &Transpose, window: &mut Window, cx: &mut Context<Self>) {
12073 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12074 let text_layout_details = &self.text_layout_details(window);
12075 self.transact(window, cx, |this, window, cx| {
12076 let edits = this.change_selections(Default::default(), window, cx, |s| {
12077 let mut edits: Vec<(Range<usize>, String)> = Default::default();
12078 s.move_with(|display_map, selection| {
12079 if !selection.is_empty() {
12080 return;
12081 }
12082
12083 let mut head = selection.head();
12084 let mut transpose_offset = head.to_offset(display_map, Bias::Right);
12085 if head.column() == display_map.line_len(head.row()) {
12086 transpose_offset = display_map
12087 .buffer_snapshot()
12088 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12089 }
12090
12091 if transpose_offset == 0 {
12092 return;
12093 }
12094
12095 *head.column_mut() += 1;
12096 head = display_map.clip_point(head, Bias::Right);
12097 let goal = SelectionGoal::HorizontalPosition(
12098 display_map
12099 .x_for_display_point(head, text_layout_details)
12100 .into(),
12101 );
12102 selection.collapse_to(head, goal);
12103
12104 let transpose_start = display_map
12105 .buffer_snapshot()
12106 .clip_offset(transpose_offset.saturating_sub(1), Bias::Left);
12107 if edits.last().is_none_or(|e| e.0.end <= transpose_start) {
12108 let transpose_end = display_map
12109 .buffer_snapshot()
12110 .clip_offset(transpose_offset + 1, Bias::Right);
12111 if let Some(ch) = display_map
12112 .buffer_snapshot()
12113 .chars_at(transpose_start)
12114 .next()
12115 {
12116 edits.push((transpose_start..transpose_offset, String::new()));
12117 edits.push((transpose_end..transpose_end, ch.to_string()));
12118 }
12119 }
12120 });
12121 edits
12122 });
12123 this.buffer
12124 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12125 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12126 this.change_selections(Default::default(), window, cx, |s| {
12127 s.select(selections);
12128 });
12129 });
12130 }
12131
12132 pub fn rewrap(&mut self, _: &Rewrap, _: &mut Window, cx: &mut Context<Self>) {
12133 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12134 if self.mode.is_single_line() {
12135 cx.propagate();
12136 return;
12137 }
12138
12139 self.rewrap_impl(RewrapOptions::default(), cx)
12140 }
12141
12142 pub fn rewrap_impl(&mut self, options: RewrapOptions, cx: &mut Context<Self>) {
12143 let buffer = self.buffer.read(cx).snapshot(cx);
12144 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12145
12146 #[derive(Clone, Debug, PartialEq)]
12147 enum CommentFormat {
12148 /// single line comment, with prefix for line
12149 Line(String),
12150 /// single line within a block comment, with prefix for line
12151 BlockLine(String),
12152 /// a single line of a block comment that includes the initial delimiter
12153 BlockCommentWithStart(BlockCommentConfig),
12154 /// a single line of a block comment that includes the ending delimiter
12155 BlockCommentWithEnd(BlockCommentConfig),
12156 }
12157
12158 // Split selections to respect paragraph, indent, and comment prefix boundaries.
12159 let wrap_ranges = selections.into_iter().flat_map(|selection| {
12160 let mut non_blank_rows_iter = (selection.start.row..=selection.end.row)
12161 .filter(|row| !buffer.is_line_blank(MultiBufferRow(*row)))
12162 .peekable();
12163
12164 let first_row = if let Some(&row) = non_blank_rows_iter.peek() {
12165 row
12166 } else {
12167 return Vec::new();
12168 };
12169
12170 let language_settings = buffer.language_settings_at(selection.head(), cx);
12171 let language_scope = buffer.language_scope_at(selection.head());
12172
12173 let indent_and_prefix_for_row =
12174 |row: u32| -> (IndentSize, Option<CommentFormat>, Option<String>) {
12175 let indent = buffer.indent_size_for_line(MultiBufferRow(row));
12176 let (comment_prefix, rewrap_prefix) = if let Some(language_scope) =
12177 &language_scope
12178 {
12179 let indent_end = Point::new(row, indent.len);
12180 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
12181 let line_text_after_indent = buffer
12182 .text_for_range(indent_end..line_end)
12183 .collect::<String>();
12184
12185 let is_within_comment_override = buffer
12186 .language_scope_at(indent_end)
12187 .is_some_and(|scope| scope.override_name() == Some("comment"));
12188 let comment_delimiters = if is_within_comment_override {
12189 // we are within a comment syntax node, but we don't
12190 // yet know what kind of comment: block, doc or line
12191 match (
12192 language_scope.documentation_comment(),
12193 language_scope.block_comment(),
12194 ) {
12195 (Some(config), _) | (_, Some(config))
12196 if buffer.contains_str_at(indent_end, &config.start) =>
12197 {
12198 Some(CommentFormat::BlockCommentWithStart(config.clone()))
12199 }
12200 (Some(config), _) | (_, Some(config))
12201 if line_text_after_indent.ends_with(config.end.as_ref()) =>
12202 {
12203 Some(CommentFormat::BlockCommentWithEnd(config.clone()))
12204 }
12205 (Some(config), _) | (_, Some(config))
12206 if buffer.contains_str_at(indent_end, &config.prefix) =>
12207 {
12208 Some(CommentFormat::BlockLine(config.prefix.to_string()))
12209 }
12210 (_, _) => language_scope
12211 .line_comment_prefixes()
12212 .iter()
12213 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12214 .map(|prefix| CommentFormat::Line(prefix.to_string())),
12215 }
12216 } else {
12217 // we not in an overridden comment node, but we may
12218 // be within a non-overridden line comment node
12219 language_scope
12220 .line_comment_prefixes()
12221 .iter()
12222 .find(|prefix| buffer.contains_str_at(indent_end, prefix))
12223 .map(|prefix| CommentFormat::Line(prefix.to_string()))
12224 };
12225
12226 let rewrap_prefix = language_scope
12227 .rewrap_prefixes()
12228 .iter()
12229 .find_map(|prefix_regex| {
12230 prefix_regex.find(&line_text_after_indent).map(|mat| {
12231 if mat.start() == 0 {
12232 Some(mat.as_str().to_string())
12233 } else {
12234 None
12235 }
12236 })
12237 })
12238 .flatten();
12239 (comment_delimiters, rewrap_prefix)
12240 } else {
12241 (None, None)
12242 };
12243 (indent, comment_prefix, rewrap_prefix)
12244 };
12245
12246 let mut ranges = Vec::new();
12247 let from_empty_selection = selection.is_empty();
12248
12249 let mut current_range_start = first_row;
12250 let mut prev_row = first_row;
12251 let (
12252 mut current_range_indent,
12253 mut current_range_comment_delimiters,
12254 mut current_range_rewrap_prefix,
12255 ) = indent_and_prefix_for_row(first_row);
12256
12257 for row in non_blank_rows_iter.skip(1) {
12258 let has_paragraph_break = row > prev_row + 1;
12259
12260 let (row_indent, row_comment_delimiters, row_rewrap_prefix) =
12261 indent_and_prefix_for_row(row);
12262
12263 let has_indent_change = row_indent != current_range_indent;
12264 let has_comment_change = row_comment_delimiters != current_range_comment_delimiters;
12265
12266 let has_boundary_change = has_comment_change
12267 || row_rewrap_prefix.is_some()
12268 || (has_indent_change && current_range_comment_delimiters.is_some());
12269
12270 if has_paragraph_break || has_boundary_change {
12271 ranges.push((
12272 language_settings.clone(),
12273 Point::new(current_range_start, 0)
12274 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12275 current_range_indent,
12276 current_range_comment_delimiters.clone(),
12277 current_range_rewrap_prefix.clone(),
12278 from_empty_selection,
12279 ));
12280 current_range_start = row;
12281 current_range_indent = row_indent;
12282 current_range_comment_delimiters = row_comment_delimiters;
12283 current_range_rewrap_prefix = row_rewrap_prefix;
12284 }
12285 prev_row = row;
12286 }
12287
12288 ranges.push((
12289 language_settings.clone(),
12290 Point::new(current_range_start, 0)
12291 ..Point::new(prev_row, buffer.line_len(MultiBufferRow(prev_row))),
12292 current_range_indent,
12293 current_range_comment_delimiters,
12294 current_range_rewrap_prefix,
12295 from_empty_selection,
12296 ));
12297
12298 ranges
12299 });
12300
12301 let mut edits = Vec::new();
12302 let mut rewrapped_row_ranges = Vec::<RangeInclusive<u32>>::new();
12303
12304 for (
12305 language_settings,
12306 wrap_range,
12307 mut indent_size,
12308 comment_prefix,
12309 rewrap_prefix,
12310 from_empty_selection,
12311 ) in wrap_ranges
12312 {
12313 let mut start_row = wrap_range.start.row;
12314 let mut end_row = wrap_range.end.row;
12315
12316 // Skip selections that overlap with a range that has already been rewrapped.
12317 let selection_range = start_row..end_row;
12318 if rewrapped_row_ranges
12319 .iter()
12320 .any(|range| range.overlaps(&selection_range))
12321 {
12322 continue;
12323 }
12324
12325 let tab_size = language_settings.tab_size;
12326
12327 let (line_prefix, inside_comment) = match &comment_prefix {
12328 Some(CommentFormat::Line(prefix) | CommentFormat::BlockLine(prefix)) => {
12329 (Some(prefix.as_str()), true)
12330 }
12331 Some(CommentFormat::BlockCommentWithEnd(BlockCommentConfig { prefix, .. })) => {
12332 (Some(prefix.as_ref()), true)
12333 }
12334 Some(CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12335 start: _,
12336 end: _,
12337 prefix,
12338 tab_size,
12339 })) => {
12340 indent_size.len += tab_size;
12341 (Some(prefix.as_ref()), true)
12342 }
12343 None => (None, false),
12344 };
12345 let indent_prefix = indent_size.chars().collect::<String>();
12346 let line_prefix = format!("{indent_prefix}{}", line_prefix.unwrap_or(""));
12347
12348 let allow_rewrap_based_on_language = match language_settings.allow_rewrap {
12349 RewrapBehavior::InComments => inside_comment,
12350 RewrapBehavior::InSelections => !wrap_range.is_empty(),
12351 RewrapBehavior::Anywhere => true,
12352 };
12353
12354 let should_rewrap = options.override_language_settings
12355 || allow_rewrap_based_on_language
12356 || self.hard_wrap.is_some();
12357 if !should_rewrap {
12358 continue;
12359 }
12360
12361 if from_empty_selection {
12362 'expand_upwards: while start_row > 0 {
12363 let prev_row = start_row - 1;
12364 if buffer.contains_str_at(Point::new(prev_row, 0), &line_prefix)
12365 && buffer.line_len(MultiBufferRow(prev_row)) as usize > line_prefix.len()
12366 && !buffer.is_line_blank(MultiBufferRow(prev_row))
12367 {
12368 start_row = prev_row;
12369 } else {
12370 break 'expand_upwards;
12371 }
12372 }
12373
12374 'expand_downwards: while end_row < buffer.max_point().row {
12375 let next_row = end_row + 1;
12376 if buffer.contains_str_at(Point::new(next_row, 0), &line_prefix)
12377 && buffer.line_len(MultiBufferRow(next_row)) as usize > line_prefix.len()
12378 && !buffer.is_line_blank(MultiBufferRow(next_row))
12379 {
12380 end_row = next_row;
12381 } else {
12382 break 'expand_downwards;
12383 }
12384 }
12385 }
12386
12387 let start = Point::new(start_row, 0);
12388 let start_offset = ToOffset::to_offset(&start, &buffer);
12389 let end = Point::new(end_row, buffer.line_len(MultiBufferRow(end_row)));
12390 let selection_text = buffer.text_for_range(start..end).collect::<String>();
12391 let mut first_line_delimiter = None;
12392 let mut last_line_delimiter = None;
12393 let Some(lines_without_prefixes) = selection_text
12394 .lines()
12395 .enumerate()
12396 .map(|(ix, line)| {
12397 let line_trimmed = line.trim_start();
12398 if rewrap_prefix.is_some() && ix > 0 {
12399 Ok(line_trimmed)
12400 } else if let Some(
12401 CommentFormat::BlockCommentWithStart(BlockCommentConfig {
12402 start,
12403 prefix,
12404 end,
12405 tab_size,
12406 })
12407 | CommentFormat::BlockCommentWithEnd(BlockCommentConfig {
12408 start,
12409 prefix,
12410 end,
12411 tab_size,
12412 }),
12413 ) = &comment_prefix
12414 {
12415 let line_trimmed = line_trimmed
12416 .strip_prefix(start.as_ref())
12417 .map(|s| {
12418 let mut indent_size = indent_size;
12419 indent_size.len -= tab_size;
12420 let indent_prefix: String = indent_size.chars().collect();
12421 first_line_delimiter = Some((indent_prefix, start));
12422 s.trim_start()
12423 })
12424 .unwrap_or(line_trimmed);
12425 let line_trimmed = line_trimmed
12426 .strip_suffix(end.as_ref())
12427 .map(|s| {
12428 last_line_delimiter = Some(end);
12429 s.trim_end()
12430 })
12431 .unwrap_or(line_trimmed);
12432 let line_trimmed = line_trimmed
12433 .strip_prefix(prefix.as_ref())
12434 .unwrap_or(line_trimmed);
12435 Ok(line_trimmed)
12436 } else if let Some(CommentFormat::BlockLine(prefix)) = &comment_prefix {
12437 line_trimmed.strip_prefix(prefix).with_context(|| {
12438 format!("line did not start with prefix {prefix:?}: {line:?}")
12439 })
12440 } else {
12441 line_trimmed
12442 .strip_prefix(&line_prefix.trim_start())
12443 .with_context(|| {
12444 format!("line did not start with prefix {line_prefix:?}: {line:?}")
12445 })
12446 }
12447 })
12448 .collect::<Result<Vec<_>, _>>()
12449 .log_err()
12450 else {
12451 continue;
12452 };
12453
12454 let wrap_column = self.hard_wrap.unwrap_or_else(|| {
12455 buffer
12456 .language_settings_at(Point::new(start_row, 0), cx)
12457 .preferred_line_length as usize
12458 });
12459
12460 let subsequent_lines_prefix = if let Some(rewrap_prefix_str) = &rewrap_prefix {
12461 format!("{}{}", indent_prefix, " ".repeat(rewrap_prefix_str.len()))
12462 } else {
12463 line_prefix.clone()
12464 };
12465
12466 let wrapped_text = {
12467 let mut wrapped_text = wrap_with_prefix(
12468 line_prefix,
12469 subsequent_lines_prefix,
12470 lines_without_prefixes.join("\n"),
12471 wrap_column,
12472 tab_size,
12473 options.preserve_existing_whitespace,
12474 );
12475
12476 if let Some((indent, delimiter)) = first_line_delimiter {
12477 wrapped_text = format!("{indent}{delimiter}\n{wrapped_text}");
12478 }
12479 if let Some(last_line) = last_line_delimiter {
12480 wrapped_text = format!("{wrapped_text}\n{indent_prefix}{last_line}");
12481 }
12482
12483 wrapped_text
12484 };
12485
12486 // TODO: should always use char-based diff while still supporting cursor behavior that
12487 // matches vim.
12488 let mut diff_options = DiffOptions::default();
12489 if options.override_language_settings {
12490 diff_options.max_word_diff_len = 0;
12491 diff_options.max_word_diff_line_count = 0;
12492 } else {
12493 diff_options.max_word_diff_len = usize::MAX;
12494 diff_options.max_word_diff_line_count = usize::MAX;
12495 }
12496
12497 for (old_range, new_text) in
12498 text_diff_with_options(&selection_text, &wrapped_text, diff_options)
12499 {
12500 let edit_start = buffer.anchor_after(start_offset + old_range.start);
12501 let edit_end = buffer.anchor_after(start_offset + old_range.end);
12502 edits.push((edit_start..edit_end, new_text));
12503 }
12504
12505 rewrapped_row_ranges.push(start_row..=end_row);
12506 }
12507
12508 self.buffer
12509 .update(cx, |buffer, cx| buffer.edit(edits, None, cx));
12510 }
12511
12512 pub fn cut_common(
12513 &mut self,
12514 cut_no_selection_line: bool,
12515 window: &mut Window,
12516 cx: &mut Context<Self>,
12517 ) -> ClipboardItem {
12518 let mut text = String::new();
12519 let buffer = self.buffer.read(cx).snapshot(cx);
12520 let mut selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12521 let mut clipboard_selections = Vec::with_capacity(selections.len());
12522 {
12523 let max_point = buffer.max_point();
12524 let mut is_first = true;
12525 for selection in &mut selections {
12526 let is_entire_line =
12527 (selection.is_empty() && cut_no_selection_line) || self.selections.line_mode();
12528 if is_entire_line {
12529 selection.start = Point::new(selection.start.row, 0);
12530 if !selection.is_empty() && selection.end.column == 0 {
12531 selection.end = cmp::min(max_point, selection.end);
12532 } else {
12533 selection.end = cmp::min(max_point, Point::new(selection.end.row + 1, 0));
12534 }
12535 selection.goal = SelectionGoal::None;
12536 }
12537 if is_first {
12538 is_first = false;
12539 } else {
12540 text += "\n";
12541 }
12542 let mut len = 0;
12543 for chunk in buffer.text_for_range(selection.start..selection.end) {
12544 text.push_str(chunk);
12545 len += chunk.len();
12546 }
12547 clipboard_selections.push(ClipboardSelection {
12548 len,
12549 is_entire_line,
12550 first_line_indent: buffer
12551 .indent_size_for_line(MultiBufferRow(selection.start.row))
12552 .len,
12553 });
12554 }
12555 }
12556
12557 self.transact(window, cx, |this, window, cx| {
12558 this.change_selections(Default::default(), window, cx, |s| {
12559 s.select(selections);
12560 });
12561 this.insert("", window, cx);
12562 });
12563 ClipboardItem::new_string_with_json_metadata(text, clipboard_selections)
12564 }
12565
12566 pub fn cut(&mut self, _: &Cut, window: &mut Window, cx: &mut Context<Self>) {
12567 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12568 let item = self.cut_common(true, window, cx);
12569 cx.write_to_clipboard(item);
12570 }
12571
12572 pub fn kill_ring_cut(&mut self, _: &KillRingCut, window: &mut Window, cx: &mut Context<Self>) {
12573 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12574 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12575 s.move_with(|snapshot, sel| {
12576 if sel.is_empty() {
12577 sel.end = DisplayPoint::new(sel.end.row(), snapshot.line_len(sel.end.row()));
12578 }
12579 if sel.is_empty() {
12580 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
12581 }
12582 });
12583 });
12584 let item = self.cut_common(false, window, cx);
12585 cx.set_global(KillRing(item))
12586 }
12587
12588 pub fn kill_ring_yank(
12589 &mut self,
12590 _: &KillRingYank,
12591 window: &mut Window,
12592 cx: &mut Context<Self>,
12593 ) {
12594 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12595 let (text, metadata) = if let Some(KillRing(item)) = cx.try_global() {
12596 if let Some(ClipboardEntry::String(kill_ring)) = item.entries().first() {
12597 (kill_ring.text().to_string(), kill_ring.metadata_json())
12598 } else {
12599 return;
12600 }
12601 } else {
12602 return;
12603 };
12604 self.do_paste(&text, metadata, false, window, cx);
12605 }
12606
12607 pub fn copy_and_trim(&mut self, _: &CopyAndTrim, _: &mut Window, cx: &mut Context<Self>) {
12608 self.do_copy(true, cx);
12609 }
12610
12611 pub fn copy(&mut self, _: &Copy, _: &mut Window, cx: &mut Context<Self>) {
12612 self.do_copy(false, cx);
12613 }
12614
12615 fn do_copy(&self, strip_leading_indents: bool, cx: &mut Context<Self>) {
12616 let selections = self.selections.all::<Point>(&self.display_snapshot(cx));
12617 let buffer = self.buffer.read(cx).read(cx);
12618 let mut text = String::new();
12619
12620 let mut clipboard_selections = Vec::with_capacity(selections.len());
12621 {
12622 let max_point = buffer.max_point();
12623 let mut is_first = true;
12624 for selection in &selections {
12625 let mut start = selection.start;
12626 let mut end = selection.end;
12627 let is_entire_line = selection.is_empty() || self.selections.line_mode();
12628 let mut add_trailing_newline = false;
12629 if is_entire_line {
12630 start = Point::new(start.row, 0);
12631 let next_line_start = Point::new(end.row + 1, 0);
12632 if next_line_start <= max_point {
12633 end = next_line_start;
12634 } else {
12635 // We're on the last line without a trailing newline.
12636 // Copy to the end of the line and add a newline afterwards.
12637 end = Point::new(end.row, buffer.line_len(MultiBufferRow(end.row)));
12638 add_trailing_newline = true;
12639 }
12640 }
12641
12642 let mut trimmed_selections = Vec::new();
12643 if strip_leading_indents && end.row.saturating_sub(start.row) > 0 {
12644 let row = MultiBufferRow(start.row);
12645 let first_indent = buffer.indent_size_for_line(row);
12646 if first_indent.len == 0 || start.column > first_indent.len {
12647 trimmed_selections.push(start..end);
12648 } else {
12649 trimmed_selections.push(
12650 Point::new(row.0, first_indent.len)
12651 ..Point::new(row.0, buffer.line_len(row)),
12652 );
12653 for row in start.row + 1..=end.row {
12654 let mut line_len = buffer.line_len(MultiBufferRow(row));
12655 if row == end.row {
12656 line_len = end.column;
12657 }
12658 if line_len == 0 {
12659 trimmed_selections
12660 .push(Point::new(row, 0)..Point::new(row, line_len));
12661 continue;
12662 }
12663 let row_indent_size = buffer.indent_size_for_line(MultiBufferRow(row));
12664 if row_indent_size.len >= first_indent.len {
12665 trimmed_selections.push(
12666 Point::new(row, first_indent.len)..Point::new(row, line_len),
12667 );
12668 } else {
12669 trimmed_selections.clear();
12670 trimmed_selections.push(start..end);
12671 break;
12672 }
12673 }
12674 }
12675 } else {
12676 trimmed_selections.push(start..end);
12677 }
12678
12679 for trimmed_range in trimmed_selections {
12680 if is_first {
12681 is_first = false;
12682 } else {
12683 text += "\n";
12684 }
12685 let mut len = 0;
12686 for chunk in buffer.text_for_range(trimmed_range.start..trimmed_range.end) {
12687 text.push_str(chunk);
12688 len += chunk.len();
12689 }
12690 if add_trailing_newline {
12691 text.push('\n');
12692 len += 1;
12693 }
12694 clipboard_selections.push(ClipboardSelection {
12695 len,
12696 is_entire_line,
12697 first_line_indent: buffer
12698 .indent_size_for_line(MultiBufferRow(trimmed_range.start.row))
12699 .len,
12700 });
12701 }
12702 }
12703 }
12704
12705 cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
12706 text,
12707 clipboard_selections,
12708 ));
12709 }
12710
12711 pub fn do_paste(
12712 &mut self,
12713 text: &String,
12714 clipboard_selections: Option<Vec<ClipboardSelection>>,
12715 handle_entire_lines: bool,
12716 window: &mut Window,
12717 cx: &mut Context<Self>,
12718 ) {
12719 if self.read_only(cx) {
12720 return;
12721 }
12722
12723 let clipboard_text = Cow::Borrowed(text.as_str());
12724
12725 self.transact(window, cx, |this, window, cx| {
12726 let had_active_edit_prediction = this.has_active_edit_prediction();
12727 let display_map = this.display_snapshot(cx);
12728 let old_selections = this.selections.all::<usize>(&display_map);
12729 let cursor_offset = this.selections.last::<usize>(&display_map).head();
12730
12731 if let Some(mut clipboard_selections) = clipboard_selections {
12732 let all_selections_were_entire_line =
12733 clipboard_selections.iter().all(|s| s.is_entire_line);
12734 let first_selection_indent_column =
12735 clipboard_selections.first().map(|s| s.first_line_indent);
12736 if clipboard_selections.len() != old_selections.len() {
12737 clipboard_selections.drain(..);
12738 }
12739 let mut auto_indent_on_paste = true;
12740
12741 this.buffer.update(cx, |buffer, cx| {
12742 let snapshot = buffer.read(cx);
12743 auto_indent_on_paste = snapshot
12744 .language_settings_at(cursor_offset, cx)
12745 .auto_indent_on_paste;
12746
12747 let mut start_offset = 0;
12748 let mut edits = Vec::new();
12749 let mut original_indent_columns = Vec::new();
12750 for (ix, selection) in old_selections.iter().enumerate() {
12751 let to_insert;
12752 let entire_line;
12753 let original_indent_column;
12754 if let Some(clipboard_selection) = clipboard_selections.get(ix) {
12755 let end_offset = start_offset + clipboard_selection.len;
12756 to_insert = &clipboard_text[start_offset..end_offset];
12757 entire_line = clipboard_selection.is_entire_line;
12758 start_offset = end_offset + 1;
12759 original_indent_column = Some(clipboard_selection.first_line_indent);
12760 } else {
12761 to_insert = &*clipboard_text;
12762 entire_line = all_selections_were_entire_line;
12763 original_indent_column = first_selection_indent_column
12764 }
12765
12766 let (range, to_insert) =
12767 if selection.is_empty() && handle_entire_lines && entire_line {
12768 // If the corresponding selection was empty when this slice of the
12769 // clipboard text was written, then the entire line containing the
12770 // selection was copied. If this selection is also currently empty,
12771 // then paste the line before the current line of the buffer.
12772 let column = selection.start.to_point(&snapshot).column as usize;
12773 let line_start = selection.start - column;
12774 (line_start..line_start, Cow::Borrowed(to_insert))
12775 } else {
12776 let language = snapshot.language_at(selection.head());
12777 let range = selection.range();
12778 if let Some(language) = language
12779 && language.name() == "Markdown".into()
12780 {
12781 edit_for_markdown_paste(
12782 &snapshot,
12783 range,
12784 to_insert,
12785 url::Url::parse(to_insert).ok(),
12786 )
12787 } else {
12788 (range, Cow::Borrowed(to_insert))
12789 }
12790 };
12791
12792 edits.push((range, to_insert));
12793 original_indent_columns.push(original_indent_column);
12794 }
12795 drop(snapshot);
12796
12797 buffer.edit(
12798 edits,
12799 if auto_indent_on_paste {
12800 Some(AutoindentMode::Block {
12801 original_indent_columns,
12802 })
12803 } else {
12804 None
12805 },
12806 cx,
12807 );
12808 });
12809
12810 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
12811 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
12812 } else {
12813 let url = url::Url::parse(&clipboard_text).ok();
12814
12815 let auto_indent_mode = if !clipboard_text.is_empty() {
12816 Some(AutoindentMode::Block {
12817 original_indent_columns: Vec::new(),
12818 })
12819 } else {
12820 None
12821 };
12822
12823 let selection_anchors = this.buffer.update(cx, |buffer, cx| {
12824 let snapshot = buffer.snapshot(cx);
12825
12826 let anchors = old_selections
12827 .iter()
12828 .map(|s| {
12829 let anchor = snapshot.anchor_after(s.head());
12830 s.map(|_| anchor)
12831 })
12832 .collect::<Vec<_>>();
12833
12834 let mut edits = Vec::new();
12835
12836 for selection in old_selections.iter() {
12837 let language = snapshot.language_at(selection.head());
12838 let range = selection.range();
12839
12840 let (edit_range, edit_text) = if let Some(language) = language
12841 && language.name() == "Markdown".into()
12842 {
12843 edit_for_markdown_paste(&snapshot, range, &clipboard_text, url.clone())
12844 } else {
12845 (range, clipboard_text.clone())
12846 };
12847
12848 edits.push((edit_range, edit_text));
12849 }
12850
12851 drop(snapshot);
12852 buffer.edit(edits, auto_indent_mode, cx);
12853
12854 anchors
12855 });
12856
12857 this.change_selections(Default::default(), window, cx, |s| {
12858 s.select_anchors(selection_anchors);
12859 });
12860 }
12861
12862 // 🤔 | .. | show_in_menu |
12863 // | .. | true true
12864 // | had_edit_prediction | false true
12865
12866 let trigger_in_words =
12867 this.show_edit_predictions_in_menu() || !had_active_edit_prediction;
12868
12869 this.trigger_completion_on_input(text, trigger_in_words, window, cx);
12870 });
12871 }
12872
12873 pub fn diff_clipboard_with_selection(
12874 &mut self,
12875 _: &DiffClipboardWithSelection,
12876 window: &mut Window,
12877 cx: &mut Context<Self>,
12878 ) {
12879 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
12880
12881 if selections.is_empty() {
12882 log::warn!("There should always be at least one selection in Zed. This is a bug.");
12883 return;
12884 };
12885
12886 let clipboard_text = match cx.read_from_clipboard() {
12887 Some(item) => match item.entries().first() {
12888 Some(ClipboardEntry::String(text)) => Some(text.text().to_string()),
12889 _ => None,
12890 },
12891 None => None,
12892 };
12893
12894 let Some(clipboard_text) = clipboard_text else {
12895 log::warn!("Clipboard doesn't contain text.");
12896 return;
12897 };
12898
12899 window.dispatch_action(
12900 Box::new(DiffClipboardWithSelectionData {
12901 clipboard_text,
12902 editor: cx.entity(),
12903 }),
12904 cx,
12905 );
12906 }
12907
12908 pub fn paste(&mut self, _: &Paste, window: &mut Window, cx: &mut Context<Self>) {
12909 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12910 if let Some(item) = cx.read_from_clipboard() {
12911 let entries = item.entries();
12912
12913 match entries.first() {
12914 // For now, we only support applying metadata if there's one string. In the future, we can incorporate all the selections
12915 // of all the pasted entries.
12916 Some(ClipboardEntry::String(clipboard_string)) if entries.len() == 1 => self
12917 .do_paste(
12918 clipboard_string.text(),
12919 clipboard_string.metadata_json::<Vec<ClipboardSelection>>(),
12920 true,
12921 window,
12922 cx,
12923 ),
12924 _ => self.do_paste(&item.text().unwrap_or_default(), None, true, window, cx),
12925 }
12926 }
12927 }
12928
12929 pub fn undo(&mut self, _: &Undo, window: &mut Window, cx: &mut Context<Self>) {
12930 if self.read_only(cx) {
12931 return;
12932 }
12933
12934 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12935
12936 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.undo(cx)) {
12937 if let Some((selections, _)) =
12938 self.selection_history.transaction(transaction_id).cloned()
12939 {
12940 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12941 s.select_anchors(selections.to_vec());
12942 });
12943 } else {
12944 log::error!(
12945 "No entry in selection_history found for undo. \
12946 This may correspond to a bug where undo does not update the selection. \
12947 If this is occurring, please add details to \
12948 https://github.com/zed-industries/zed/issues/22692"
12949 );
12950 }
12951 self.request_autoscroll(Autoscroll::fit(), cx);
12952 self.unmark_text(window, cx);
12953 self.refresh_edit_prediction(true, false, window, cx);
12954 cx.emit(EditorEvent::Edited { transaction_id });
12955 cx.emit(EditorEvent::TransactionUndone { transaction_id });
12956 }
12957 }
12958
12959 pub fn redo(&mut self, _: &Redo, window: &mut Window, cx: &mut Context<Self>) {
12960 if self.read_only(cx) {
12961 return;
12962 }
12963
12964 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
12965
12966 if let Some(transaction_id) = self.buffer.update(cx, |buffer, cx| buffer.redo(cx)) {
12967 if let Some((_, Some(selections))) =
12968 self.selection_history.transaction(transaction_id).cloned()
12969 {
12970 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
12971 s.select_anchors(selections.to_vec());
12972 });
12973 } else {
12974 log::error!(
12975 "No entry in selection_history found for redo. \
12976 This may correspond to a bug where undo does not update the selection. \
12977 If this is occurring, please add details to \
12978 https://github.com/zed-industries/zed/issues/22692"
12979 );
12980 }
12981 self.request_autoscroll(Autoscroll::fit(), cx);
12982 self.unmark_text(window, cx);
12983 self.refresh_edit_prediction(true, false, window, cx);
12984 cx.emit(EditorEvent::Edited { transaction_id });
12985 }
12986 }
12987
12988 pub fn finalize_last_transaction(&mut self, cx: &mut Context<Self>) {
12989 self.buffer
12990 .update(cx, |buffer, cx| buffer.finalize_last_transaction(cx));
12991 }
12992
12993 pub fn group_until_transaction(&mut self, tx_id: TransactionId, cx: &mut Context<Self>) {
12994 self.buffer
12995 .update(cx, |buffer, cx| buffer.group_until_transaction(tx_id, cx));
12996 }
12997
12998 pub fn move_left(&mut self, _: &MoveLeft, window: &mut Window, cx: &mut Context<Self>) {
12999 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13000 self.change_selections(Default::default(), window, cx, |s| {
13001 s.move_with(|map, selection| {
13002 let cursor = if selection.is_empty() {
13003 movement::left(map, selection.start)
13004 } else {
13005 selection.start
13006 };
13007 selection.collapse_to(cursor, SelectionGoal::None);
13008 });
13009 })
13010 }
13011
13012 pub fn select_left(&mut self, _: &SelectLeft, window: &mut Window, cx: &mut Context<Self>) {
13013 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13014 self.change_selections(Default::default(), window, cx, |s| {
13015 s.move_heads_with(|map, head, _| (movement::left(map, head), SelectionGoal::None));
13016 })
13017 }
13018
13019 pub fn move_right(&mut self, _: &MoveRight, window: &mut Window, cx: &mut Context<Self>) {
13020 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13021 self.change_selections(Default::default(), window, cx, |s| {
13022 s.move_with(|map, selection| {
13023 let cursor = if selection.is_empty() {
13024 movement::right(map, selection.end)
13025 } else {
13026 selection.end
13027 };
13028 selection.collapse_to(cursor, SelectionGoal::None)
13029 });
13030 })
13031 }
13032
13033 pub fn select_right(&mut self, _: &SelectRight, window: &mut Window, cx: &mut Context<Self>) {
13034 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13035 self.change_selections(Default::default(), window, cx, |s| {
13036 s.move_heads_with(|map, head, _| (movement::right(map, head), SelectionGoal::None));
13037 });
13038 }
13039
13040 pub fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
13041 if self.take_rename(true, window, cx).is_some() {
13042 return;
13043 }
13044
13045 if self.mode.is_single_line() {
13046 cx.propagate();
13047 return;
13048 }
13049
13050 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13051
13052 let text_layout_details = &self.text_layout_details(window);
13053 let selection_count = self.selections.count();
13054 let first_selection = self.selections.first_anchor();
13055
13056 self.change_selections(Default::default(), window, cx, |s| {
13057 s.move_with(|map, selection| {
13058 if !selection.is_empty() {
13059 selection.goal = SelectionGoal::None;
13060 }
13061 let (cursor, goal) = movement::up(
13062 map,
13063 selection.start,
13064 selection.goal,
13065 false,
13066 text_layout_details,
13067 );
13068 selection.collapse_to(cursor, goal);
13069 });
13070 });
13071
13072 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13073 {
13074 cx.propagate();
13075 }
13076 }
13077
13078 pub fn move_up_by_lines(
13079 &mut self,
13080 action: &MoveUpByLines,
13081 window: &mut Window,
13082 cx: &mut Context<Self>,
13083 ) {
13084 if self.take_rename(true, window, cx).is_some() {
13085 return;
13086 }
13087
13088 if self.mode.is_single_line() {
13089 cx.propagate();
13090 return;
13091 }
13092
13093 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13094
13095 let text_layout_details = &self.text_layout_details(window);
13096
13097 self.change_selections(Default::default(), window, cx, |s| {
13098 s.move_with(|map, selection| {
13099 if !selection.is_empty() {
13100 selection.goal = SelectionGoal::None;
13101 }
13102 let (cursor, goal) = movement::up_by_rows(
13103 map,
13104 selection.start,
13105 action.lines,
13106 selection.goal,
13107 false,
13108 text_layout_details,
13109 );
13110 selection.collapse_to(cursor, goal);
13111 });
13112 })
13113 }
13114
13115 pub fn move_down_by_lines(
13116 &mut self,
13117 action: &MoveDownByLines,
13118 window: &mut Window,
13119 cx: &mut Context<Self>,
13120 ) {
13121 if self.take_rename(true, window, cx).is_some() {
13122 return;
13123 }
13124
13125 if self.mode.is_single_line() {
13126 cx.propagate();
13127 return;
13128 }
13129
13130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13131
13132 let text_layout_details = &self.text_layout_details(window);
13133
13134 self.change_selections(Default::default(), window, cx, |s| {
13135 s.move_with(|map, selection| {
13136 if !selection.is_empty() {
13137 selection.goal = SelectionGoal::None;
13138 }
13139 let (cursor, goal) = movement::down_by_rows(
13140 map,
13141 selection.start,
13142 action.lines,
13143 selection.goal,
13144 false,
13145 text_layout_details,
13146 );
13147 selection.collapse_to(cursor, goal);
13148 });
13149 })
13150 }
13151
13152 pub fn select_down_by_lines(
13153 &mut self,
13154 action: &SelectDownByLines,
13155 window: &mut Window,
13156 cx: &mut Context<Self>,
13157 ) {
13158 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13159 let text_layout_details = &self.text_layout_details(window);
13160 self.change_selections(Default::default(), window, cx, |s| {
13161 s.move_heads_with(|map, head, goal| {
13162 movement::down_by_rows(map, head, action.lines, goal, false, text_layout_details)
13163 })
13164 })
13165 }
13166
13167 pub fn select_up_by_lines(
13168 &mut self,
13169 action: &SelectUpByLines,
13170 window: &mut Window,
13171 cx: &mut Context<Self>,
13172 ) {
13173 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13174 let text_layout_details = &self.text_layout_details(window);
13175 self.change_selections(Default::default(), window, cx, |s| {
13176 s.move_heads_with(|map, head, goal| {
13177 movement::up_by_rows(map, head, action.lines, goal, false, text_layout_details)
13178 })
13179 })
13180 }
13181
13182 pub fn select_page_up(
13183 &mut self,
13184 _: &SelectPageUp,
13185 window: &mut Window,
13186 cx: &mut Context<Self>,
13187 ) {
13188 let Some(row_count) = self.visible_row_count() else {
13189 return;
13190 };
13191
13192 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13193
13194 let text_layout_details = &self.text_layout_details(window);
13195
13196 self.change_selections(Default::default(), window, cx, |s| {
13197 s.move_heads_with(|map, head, goal| {
13198 movement::up_by_rows(map, head, row_count, goal, false, text_layout_details)
13199 })
13200 })
13201 }
13202
13203 pub fn move_page_up(
13204 &mut self,
13205 action: &MovePageUp,
13206 window: &mut Window,
13207 cx: &mut Context<Self>,
13208 ) {
13209 if self.take_rename(true, window, cx).is_some() {
13210 return;
13211 }
13212
13213 if self
13214 .context_menu
13215 .borrow_mut()
13216 .as_mut()
13217 .map(|menu| menu.select_first(self.completion_provider.as_deref(), window, cx))
13218 .unwrap_or(false)
13219 {
13220 return;
13221 }
13222
13223 if matches!(self.mode, EditorMode::SingleLine) {
13224 cx.propagate();
13225 return;
13226 }
13227
13228 let Some(row_count) = self.visible_row_count() else {
13229 return;
13230 };
13231
13232 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13233
13234 let effects = if action.center_cursor {
13235 SelectionEffects::scroll(Autoscroll::center())
13236 } else {
13237 SelectionEffects::default()
13238 };
13239
13240 let text_layout_details = &self.text_layout_details(window);
13241
13242 self.change_selections(effects, window, cx, |s| {
13243 s.move_with(|map, selection| {
13244 if !selection.is_empty() {
13245 selection.goal = SelectionGoal::None;
13246 }
13247 let (cursor, goal) = movement::up_by_rows(
13248 map,
13249 selection.end,
13250 row_count,
13251 selection.goal,
13252 false,
13253 text_layout_details,
13254 );
13255 selection.collapse_to(cursor, goal);
13256 });
13257 });
13258 }
13259
13260 pub fn select_up(&mut self, _: &SelectUp, window: &mut Window, cx: &mut Context<Self>) {
13261 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13262 let text_layout_details = &self.text_layout_details(window);
13263 self.change_selections(Default::default(), window, cx, |s| {
13264 s.move_heads_with(|map, head, goal| {
13265 movement::up(map, head, goal, false, text_layout_details)
13266 })
13267 })
13268 }
13269
13270 pub fn move_down(&mut self, _: &MoveDown, window: &mut Window, cx: &mut Context<Self>) {
13271 self.take_rename(true, window, cx);
13272
13273 if self.mode.is_single_line() {
13274 cx.propagate();
13275 return;
13276 }
13277
13278 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13279
13280 let text_layout_details = &self.text_layout_details(window);
13281 let selection_count = self.selections.count();
13282 let first_selection = self.selections.first_anchor();
13283
13284 self.change_selections(Default::default(), window, cx, |s| {
13285 s.move_with(|map, selection| {
13286 if !selection.is_empty() {
13287 selection.goal = SelectionGoal::None;
13288 }
13289 let (cursor, goal) = movement::down(
13290 map,
13291 selection.end,
13292 selection.goal,
13293 false,
13294 text_layout_details,
13295 );
13296 selection.collapse_to(cursor, goal);
13297 });
13298 });
13299
13300 if selection_count == 1 && first_selection.range() == self.selections.first_anchor().range()
13301 {
13302 cx.propagate();
13303 }
13304 }
13305
13306 pub fn select_page_down(
13307 &mut self,
13308 _: &SelectPageDown,
13309 window: &mut Window,
13310 cx: &mut Context<Self>,
13311 ) {
13312 let Some(row_count) = self.visible_row_count() else {
13313 return;
13314 };
13315
13316 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13317
13318 let text_layout_details = &self.text_layout_details(window);
13319
13320 self.change_selections(Default::default(), window, cx, |s| {
13321 s.move_heads_with(|map, head, goal| {
13322 movement::down_by_rows(map, head, row_count, goal, false, text_layout_details)
13323 })
13324 })
13325 }
13326
13327 pub fn move_page_down(
13328 &mut self,
13329 action: &MovePageDown,
13330 window: &mut Window,
13331 cx: &mut Context<Self>,
13332 ) {
13333 if self.take_rename(true, window, cx).is_some() {
13334 return;
13335 }
13336
13337 if self
13338 .context_menu
13339 .borrow_mut()
13340 .as_mut()
13341 .map(|menu| menu.select_last(self.completion_provider.as_deref(), window, cx))
13342 .unwrap_or(false)
13343 {
13344 return;
13345 }
13346
13347 if matches!(self.mode, EditorMode::SingleLine) {
13348 cx.propagate();
13349 return;
13350 }
13351
13352 let Some(row_count) = self.visible_row_count() else {
13353 return;
13354 };
13355
13356 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13357
13358 let effects = if action.center_cursor {
13359 SelectionEffects::scroll(Autoscroll::center())
13360 } else {
13361 SelectionEffects::default()
13362 };
13363
13364 let text_layout_details = &self.text_layout_details(window);
13365 self.change_selections(effects, window, cx, |s| {
13366 s.move_with(|map, selection| {
13367 if !selection.is_empty() {
13368 selection.goal = SelectionGoal::None;
13369 }
13370 let (cursor, goal) = movement::down_by_rows(
13371 map,
13372 selection.end,
13373 row_count,
13374 selection.goal,
13375 false,
13376 text_layout_details,
13377 );
13378 selection.collapse_to(cursor, goal);
13379 });
13380 });
13381 }
13382
13383 pub fn select_down(&mut self, _: &SelectDown, window: &mut Window, cx: &mut Context<Self>) {
13384 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13385 let text_layout_details = &self.text_layout_details(window);
13386 self.change_selections(Default::default(), window, cx, |s| {
13387 s.move_heads_with(|map, head, goal| {
13388 movement::down(map, head, goal, false, text_layout_details)
13389 })
13390 });
13391 }
13392
13393 pub fn context_menu_first(
13394 &mut self,
13395 _: &ContextMenuFirst,
13396 window: &mut Window,
13397 cx: &mut Context<Self>,
13398 ) {
13399 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13400 context_menu.select_first(self.completion_provider.as_deref(), window, cx);
13401 }
13402 }
13403
13404 pub fn context_menu_prev(
13405 &mut self,
13406 _: &ContextMenuPrevious,
13407 window: &mut Window,
13408 cx: &mut Context<Self>,
13409 ) {
13410 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13411 context_menu.select_prev(self.completion_provider.as_deref(), window, cx);
13412 }
13413 }
13414
13415 pub fn context_menu_next(
13416 &mut self,
13417 _: &ContextMenuNext,
13418 window: &mut Window,
13419 cx: &mut Context<Self>,
13420 ) {
13421 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13422 context_menu.select_next(self.completion_provider.as_deref(), window, cx);
13423 }
13424 }
13425
13426 pub fn context_menu_last(
13427 &mut self,
13428 _: &ContextMenuLast,
13429 window: &mut Window,
13430 cx: &mut Context<Self>,
13431 ) {
13432 if let Some(context_menu) = self.context_menu.borrow_mut().as_mut() {
13433 context_menu.select_last(self.completion_provider.as_deref(), window, cx);
13434 }
13435 }
13436
13437 pub fn signature_help_prev(
13438 &mut self,
13439 _: &SignatureHelpPrevious,
13440 _: &mut Window,
13441 cx: &mut Context<Self>,
13442 ) {
13443 if let Some(popover) = self.signature_help_state.popover_mut() {
13444 if popover.current_signature == 0 {
13445 popover.current_signature = popover.signatures.len() - 1;
13446 } else {
13447 popover.current_signature -= 1;
13448 }
13449 cx.notify();
13450 }
13451 }
13452
13453 pub fn signature_help_next(
13454 &mut self,
13455 _: &SignatureHelpNext,
13456 _: &mut Window,
13457 cx: &mut Context<Self>,
13458 ) {
13459 if let Some(popover) = self.signature_help_state.popover_mut() {
13460 if popover.current_signature + 1 == popover.signatures.len() {
13461 popover.current_signature = 0;
13462 } else {
13463 popover.current_signature += 1;
13464 }
13465 cx.notify();
13466 }
13467 }
13468
13469 pub fn move_to_previous_word_start(
13470 &mut self,
13471 _: &MoveToPreviousWordStart,
13472 window: &mut Window,
13473 cx: &mut Context<Self>,
13474 ) {
13475 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13476 self.change_selections(Default::default(), window, cx, |s| {
13477 s.move_cursors_with(|map, head, _| {
13478 (
13479 movement::previous_word_start(map, head),
13480 SelectionGoal::None,
13481 )
13482 });
13483 })
13484 }
13485
13486 pub fn move_to_previous_subword_start(
13487 &mut self,
13488 _: &MoveToPreviousSubwordStart,
13489 window: &mut Window,
13490 cx: &mut Context<Self>,
13491 ) {
13492 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13493 self.change_selections(Default::default(), window, cx, |s| {
13494 s.move_cursors_with(|map, head, _| {
13495 (
13496 movement::previous_subword_start(map, head),
13497 SelectionGoal::None,
13498 )
13499 });
13500 })
13501 }
13502
13503 pub fn select_to_previous_word_start(
13504 &mut self,
13505 _: &SelectToPreviousWordStart,
13506 window: &mut Window,
13507 cx: &mut Context<Self>,
13508 ) {
13509 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13510 self.change_selections(Default::default(), window, cx, |s| {
13511 s.move_heads_with(|map, head, _| {
13512 (
13513 movement::previous_word_start(map, head),
13514 SelectionGoal::None,
13515 )
13516 });
13517 })
13518 }
13519
13520 pub fn select_to_previous_subword_start(
13521 &mut self,
13522 _: &SelectToPreviousSubwordStart,
13523 window: &mut Window,
13524 cx: &mut Context<Self>,
13525 ) {
13526 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13527 self.change_selections(Default::default(), window, cx, |s| {
13528 s.move_heads_with(|map, head, _| {
13529 (
13530 movement::previous_subword_start(map, head),
13531 SelectionGoal::None,
13532 )
13533 });
13534 })
13535 }
13536
13537 pub fn delete_to_previous_word_start(
13538 &mut self,
13539 action: &DeleteToPreviousWordStart,
13540 window: &mut Window,
13541 cx: &mut Context<Self>,
13542 ) {
13543 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13544 self.transact(window, cx, |this, window, cx| {
13545 this.select_autoclose_pair(window, cx);
13546 this.change_selections(Default::default(), window, cx, |s| {
13547 s.move_with(|map, selection| {
13548 if selection.is_empty() {
13549 let mut cursor = if action.ignore_newlines {
13550 movement::previous_word_start(map, selection.head())
13551 } else {
13552 movement::previous_word_start_or_newline(map, selection.head())
13553 };
13554 cursor = movement::adjust_greedy_deletion(
13555 map,
13556 selection.head(),
13557 cursor,
13558 action.ignore_brackets,
13559 );
13560 selection.set_head(cursor, SelectionGoal::None);
13561 }
13562 });
13563 });
13564 this.insert("", window, cx);
13565 });
13566 }
13567
13568 pub fn delete_to_previous_subword_start(
13569 &mut self,
13570 _: &DeleteToPreviousSubwordStart,
13571 window: &mut Window,
13572 cx: &mut Context<Self>,
13573 ) {
13574 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13575 self.transact(window, cx, |this, window, cx| {
13576 this.select_autoclose_pair(window, cx);
13577 this.change_selections(Default::default(), window, cx, |s| {
13578 s.move_with(|map, selection| {
13579 if selection.is_empty() {
13580 let mut cursor = movement::previous_subword_start(map, selection.head());
13581 cursor =
13582 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13583 selection.set_head(cursor, SelectionGoal::None);
13584 }
13585 });
13586 });
13587 this.insert("", window, cx);
13588 });
13589 }
13590
13591 pub fn move_to_next_word_end(
13592 &mut self,
13593 _: &MoveToNextWordEnd,
13594 window: &mut Window,
13595 cx: &mut Context<Self>,
13596 ) {
13597 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13598 self.change_selections(Default::default(), window, cx, |s| {
13599 s.move_cursors_with(|map, head, _| {
13600 (movement::next_word_end(map, head), SelectionGoal::None)
13601 });
13602 })
13603 }
13604
13605 pub fn move_to_next_subword_end(
13606 &mut self,
13607 _: &MoveToNextSubwordEnd,
13608 window: &mut Window,
13609 cx: &mut Context<Self>,
13610 ) {
13611 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13612 self.change_selections(Default::default(), window, cx, |s| {
13613 s.move_cursors_with(|map, head, _| {
13614 (movement::next_subword_end(map, head), SelectionGoal::None)
13615 });
13616 })
13617 }
13618
13619 pub fn select_to_next_word_end(
13620 &mut self,
13621 _: &SelectToNextWordEnd,
13622 window: &mut Window,
13623 cx: &mut Context<Self>,
13624 ) {
13625 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13626 self.change_selections(Default::default(), window, cx, |s| {
13627 s.move_heads_with(|map, head, _| {
13628 (movement::next_word_end(map, head), SelectionGoal::None)
13629 });
13630 })
13631 }
13632
13633 pub fn select_to_next_subword_end(
13634 &mut self,
13635 _: &SelectToNextSubwordEnd,
13636 window: &mut Window,
13637 cx: &mut Context<Self>,
13638 ) {
13639 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13640 self.change_selections(Default::default(), window, cx, |s| {
13641 s.move_heads_with(|map, head, _| {
13642 (movement::next_subword_end(map, head), SelectionGoal::None)
13643 });
13644 })
13645 }
13646
13647 pub fn delete_to_next_word_end(
13648 &mut self,
13649 action: &DeleteToNextWordEnd,
13650 window: &mut Window,
13651 cx: &mut Context<Self>,
13652 ) {
13653 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13654 self.transact(window, cx, |this, window, cx| {
13655 this.change_selections(Default::default(), window, cx, |s| {
13656 s.move_with(|map, selection| {
13657 if selection.is_empty() {
13658 let mut cursor = if action.ignore_newlines {
13659 movement::next_word_end(map, selection.head())
13660 } else {
13661 movement::next_word_end_or_newline(map, selection.head())
13662 };
13663 cursor = movement::adjust_greedy_deletion(
13664 map,
13665 selection.head(),
13666 cursor,
13667 action.ignore_brackets,
13668 );
13669 selection.set_head(cursor, SelectionGoal::None);
13670 }
13671 });
13672 });
13673 this.insert("", window, cx);
13674 });
13675 }
13676
13677 pub fn delete_to_next_subword_end(
13678 &mut self,
13679 _: &DeleteToNextSubwordEnd,
13680 window: &mut Window,
13681 cx: &mut Context<Self>,
13682 ) {
13683 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13684 self.transact(window, cx, |this, window, cx| {
13685 this.change_selections(Default::default(), window, cx, |s| {
13686 s.move_with(|map, selection| {
13687 if selection.is_empty() {
13688 let mut cursor = movement::next_subword_end(map, selection.head());
13689 cursor =
13690 movement::adjust_greedy_deletion(map, selection.head(), cursor, false);
13691 selection.set_head(cursor, SelectionGoal::None);
13692 }
13693 });
13694 });
13695 this.insert("", window, cx);
13696 });
13697 }
13698
13699 pub fn move_to_beginning_of_line(
13700 &mut self,
13701 action: &MoveToBeginningOfLine,
13702 window: &mut Window,
13703 cx: &mut Context<Self>,
13704 ) {
13705 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13706 self.change_selections(Default::default(), window, cx, |s| {
13707 s.move_cursors_with(|map, head, _| {
13708 (
13709 movement::indented_line_beginning(
13710 map,
13711 head,
13712 action.stop_at_soft_wraps,
13713 action.stop_at_indent,
13714 ),
13715 SelectionGoal::None,
13716 )
13717 });
13718 })
13719 }
13720
13721 pub fn select_to_beginning_of_line(
13722 &mut self,
13723 action: &SelectToBeginningOfLine,
13724 window: &mut Window,
13725 cx: &mut Context<Self>,
13726 ) {
13727 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13728 self.change_selections(Default::default(), window, cx, |s| {
13729 s.move_heads_with(|map, head, _| {
13730 (
13731 movement::indented_line_beginning(
13732 map,
13733 head,
13734 action.stop_at_soft_wraps,
13735 action.stop_at_indent,
13736 ),
13737 SelectionGoal::None,
13738 )
13739 });
13740 });
13741 }
13742
13743 pub fn delete_to_beginning_of_line(
13744 &mut self,
13745 action: &DeleteToBeginningOfLine,
13746 window: &mut Window,
13747 cx: &mut Context<Self>,
13748 ) {
13749 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13750 self.transact(window, cx, |this, window, cx| {
13751 this.change_selections(Default::default(), window, cx, |s| {
13752 s.move_with(|_, selection| {
13753 selection.reversed = true;
13754 });
13755 });
13756
13757 this.select_to_beginning_of_line(
13758 &SelectToBeginningOfLine {
13759 stop_at_soft_wraps: false,
13760 stop_at_indent: action.stop_at_indent,
13761 },
13762 window,
13763 cx,
13764 );
13765 this.backspace(&Backspace, window, cx);
13766 });
13767 }
13768
13769 pub fn move_to_end_of_line(
13770 &mut self,
13771 action: &MoveToEndOfLine,
13772 window: &mut Window,
13773 cx: &mut Context<Self>,
13774 ) {
13775 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13776 self.change_selections(Default::default(), window, cx, |s| {
13777 s.move_cursors_with(|map, head, _| {
13778 (
13779 movement::line_end(map, head, action.stop_at_soft_wraps),
13780 SelectionGoal::None,
13781 )
13782 });
13783 })
13784 }
13785
13786 pub fn select_to_end_of_line(
13787 &mut self,
13788 action: &SelectToEndOfLine,
13789 window: &mut Window,
13790 cx: &mut Context<Self>,
13791 ) {
13792 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13793 self.change_selections(Default::default(), window, cx, |s| {
13794 s.move_heads_with(|map, head, _| {
13795 (
13796 movement::line_end(map, head, action.stop_at_soft_wraps),
13797 SelectionGoal::None,
13798 )
13799 });
13800 })
13801 }
13802
13803 pub fn delete_to_end_of_line(
13804 &mut self,
13805 _: &DeleteToEndOfLine,
13806 window: &mut Window,
13807 cx: &mut Context<Self>,
13808 ) {
13809 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13810 self.transact(window, cx, |this, window, cx| {
13811 this.select_to_end_of_line(
13812 &SelectToEndOfLine {
13813 stop_at_soft_wraps: false,
13814 },
13815 window,
13816 cx,
13817 );
13818 this.delete(&Delete, window, cx);
13819 });
13820 }
13821
13822 pub fn cut_to_end_of_line(
13823 &mut self,
13824 action: &CutToEndOfLine,
13825 window: &mut Window,
13826 cx: &mut Context<Self>,
13827 ) {
13828 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13829 self.transact(window, cx, |this, window, cx| {
13830 this.select_to_end_of_line(
13831 &SelectToEndOfLine {
13832 stop_at_soft_wraps: false,
13833 },
13834 window,
13835 cx,
13836 );
13837 if !action.stop_at_newlines {
13838 this.change_selections(Default::default(), window, cx, |s| {
13839 s.move_with(|_, sel| {
13840 if sel.is_empty() {
13841 sel.end = DisplayPoint::new(sel.end.row() + 1_u32, 0);
13842 }
13843 });
13844 });
13845 }
13846 this.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
13847 let item = this.cut_common(false, window, cx);
13848 cx.write_to_clipboard(item);
13849 });
13850 }
13851
13852 pub fn move_to_start_of_paragraph(
13853 &mut self,
13854 _: &MoveToStartOfParagraph,
13855 window: &mut Window,
13856 cx: &mut Context<Self>,
13857 ) {
13858 if matches!(self.mode, EditorMode::SingleLine) {
13859 cx.propagate();
13860 return;
13861 }
13862 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13863 self.change_selections(Default::default(), window, cx, |s| {
13864 s.move_with(|map, selection| {
13865 selection.collapse_to(
13866 movement::start_of_paragraph(map, selection.head(), 1),
13867 SelectionGoal::None,
13868 )
13869 });
13870 })
13871 }
13872
13873 pub fn move_to_end_of_paragraph(
13874 &mut self,
13875 _: &MoveToEndOfParagraph,
13876 window: &mut Window,
13877 cx: &mut Context<Self>,
13878 ) {
13879 if matches!(self.mode, EditorMode::SingleLine) {
13880 cx.propagate();
13881 return;
13882 }
13883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13884 self.change_selections(Default::default(), window, cx, |s| {
13885 s.move_with(|map, selection| {
13886 selection.collapse_to(
13887 movement::end_of_paragraph(map, selection.head(), 1),
13888 SelectionGoal::None,
13889 )
13890 });
13891 })
13892 }
13893
13894 pub fn select_to_start_of_paragraph(
13895 &mut self,
13896 _: &SelectToStartOfParagraph,
13897 window: &mut Window,
13898 cx: &mut Context<Self>,
13899 ) {
13900 if matches!(self.mode, EditorMode::SingleLine) {
13901 cx.propagate();
13902 return;
13903 }
13904 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13905 self.change_selections(Default::default(), window, cx, |s| {
13906 s.move_heads_with(|map, head, _| {
13907 (
13908 movement::start_of_paragraph(map, head, 1),
13909 SelectionGoal::None,
13910 )
13911 });
13912 })
13913 }
13914
13915 pub fn select_to_end_of_paragraph(
13916 &mut self,
13917 _: &SelectToEndOfParagraph,
13918 window: &mut Window,
13919 cx: &mut Context<Self>,
13920 ) {
13921 if matches!(self.mode, EditorMode::SingleLine) {
13922 cx.propagate();
13923 return;
13924 }
13925 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13926 self.change_selections(Default::default(), window, cx, |s| {
13927 s.move_heads_with(|map, head, _| {
13928 (
13929 movement::end_of_paragraph(map, head, 1),
13930 SelectionGoal::None,
13931 )
13932 });
13933 })
13934 }
13935
13936 pub fn move_to_start_of_excerpt(
13937 &mut self,
13938 _: &MoveToStartOfExcerpt,
13939 window: &mut Window,
13940 cx: &mut Context<Self>,
13941 ) {
13942 if matches!(self.mode, EditorMode::SingleLine) {
13943 cx.propagate();
13944 return;
13945 }
13946 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13947 self.change_selections(Default::default(), window, cx, |s| {
13948 s.move_with(|map, selection| {
13949 selection.collapse_to(
13950 movement::start_of_excerpt(
13951 map,
13952 selection.head(),
13953 workspace::searchable::Direction::Prev,
13954 ),
13955 SelectionGoal::None,
13956 )
13957 });
13958 })
13959 }
13960
13961 pub fn move_to_start_of_next_excerpt(
13962 &mut self,
13963 _: &MoveToStartOfNextExcerpt,
13964 window: &mut Window,
13965 cx: &mut Context<Self>,
13966 ) {
13967 if matches!(self.mode, EditorMode::SingleLine) {
13968 cx.propagate();
13969 return;
13970 }
13971
13972 self.change_selections(Default::default(), window, cx, |s| {
13973 s.move_with(|map, selection| {
13974 selection.collapse_to(
13975 movement::start_of_excerpt(
13976 map,
13977 selection.head(),
13978 workspace::searchable::Direction::Next,
13979 ),
13980 SelectionGoal::None,
13981 )
13982 });
13983 })
13984 }
13985
13986 pub fn move_to_end_of_excerpt(
13987 &mut self,
13988 _: &MoveToEndOfExcerpt,
13989 window: &mut Window,
13990 cx: &mut Context<Self>,
13991 ) {
13992 if matches!(self.mode, EditorMode::SingleLine) {
13993 cx.propagate();
13994 return;
13995 }
13996 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
13997 self.change_selections(Default::default(), window, cx, |s| {
13998 s.move_with(|map, selection| {
13999 selection.collapse_to(
14000 movement::end_of_excerpt(
14001 map,
14002 selection.head(),
14003 workspace::searchable::Direction::Next,
14004 ),
14005 SelectionGoal::None,
14006 )
14007 });
14008 })
14009 }
14010
14011 pub fn move_to_end_of_previous_excerpt(
14012 &mut self,
14013 _: &MoveToEndOfPreviousExcerpt,
14014 window: &mut Window,
14015 cx: &mut Context<Self>,
14016 ) {
14017 if matches!(self.mode, EditorMode::SingleLine) {
14018 cx.propagate();
14019 return;
14020 }
14021 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14022 self.change_selections(Default::default(), window, cx, |s| {
14023 s.move_with(|map, selection| {
14024 selection.collapse_to(
14025 movement::end_of_excerpt(
14026 map,
14027 selection.head(),
14028 workspace::searchable::Direction::Prev,
14029 ),
14030 SelectionGoal::None,
14031 )
14032 });
14033 })
14034 }
14035
14036 pub fn select_to_start_of_excerpt(
14037 &mut self,
14038 _: &SelectToStartOfExcerpt,
14039 window: &mut Window,
14040 cx: &mut Context<Self>,
14041 ) {
14042 if matches!(self.mode, EditorMode::SingleLine) {
14043 cx.propagate();
14044 return;
14045 }
14046 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14047 self.change_selections(Default::default(), window, cx, |s| {
14048 s.move_heads_with(|map, head, _| {
14049 (
14050 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14051 SelectionGoal::None,
14052 )
14053 });
14054 })
14055 }
14056
14057 pub fn select_to_start_of_next_excerpt(
14058 &mut self,
14059 _: &SelectToStartOfNextExcerpt,
14060 window: &mut Window,
14061 cx: &mut Context<Self>,
14062 ) {
14063 if matches!(self.mode, EditorMode::SingleLine) {
14064 cx.propagate();
14065 return;
14066 }
14067 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14068 self.change_selections(Default::default(), window, cx, |s| {
14069 s.move_heads_with(|map, head, _| {
14070 (
14071 movement::start_of_excerpt(map, head, workspace::searchable::Direction::Next),
14072 SelectionGoal::None,
14073 )
14074 });
14075 })
14076 }
14077
14078 pub fn select_to_end_of_excerpt(
14079 &mut self,
14080 _: &SelectToEndOfExcerpt,
14081 window: &mut Window,
14082 cx: &mut Context<Self>,
14083 ) {
14084 if matches!(self.mode, EditorMode::SingleLine) {
14085 cx.propagate();
14086 return;
14087 }
14088 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14089 self.change_selections(Default::default(), window, cx, |s| {
14090 s.move_heads_with(|map, head, _| {
14091 (
14092 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Next),
14093 SelectionGoal::None,
14094 )
14095 });
14096 })
14097 }
14098
14099 pub fn select_to_end_of_previous_excerpt(
14100 &mut self,
14101 _: &SelectToEndOfPreviousExcerpt,
14102 window: &mut Window,
14103 cx: &mut Context<Self>,
14104 ) {
14105 if matches!(self.mode, EditorMode::SingleLine) {
14106 cx.propagate();
14107 return;
14108 }
14109 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14110 self.change_selections(Default::default(), window, cx, |s| {
14111 s.move_heads_with(|map, head, _| {
14112 (
14113 movement::end_of_excerpt(map, head, workspace::searchable::Direction::Prev),
14114 SelectionGoal::None,
14115 )
14116 });
14117 })
14118 }
14119
14120 pub fn move_to_beginning(
14121 &mut self,
14122 _: &MoveToBeginning,
14123 window: &mut Window,
14124 cx: &mut Context<Self>,
14125 ) {
14126 if matches!(self.mode, EditorMode::SingleLine) {
14127 cx.propagate();
14128 return;
14129 }
14130 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14131 self.change_selections(Default::default(), window, cx, |s| {
14132 s.select_ranges(vec![0..0]);
14133 });
14134 }
14135
14136 pub fn select_to_beginning(
14137 &mut self,
14138 _: &SelectToBeginning,
14139 window: &mut Window,
14140 cx: &mut Context<Self>,
14141 ) {
14142 let mut selection = self.selections.last::<Point>(&self.display_snapshot(cx));
14143 selection.set_head(Point::zero(), SelectionGoal::None);
14144 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14145 self.change_selections(Default::default(), window, cx, |s| {
14146 s.select(vec![selection]);
14147 });
14148 }
14149
14150 pub fn move_to_end(&mut self, _: &MoveToEnd, window: &mut Window, cx: &mut Context<Self>) {
14151 if matches!(self.mode, EditorMode::SingleLine) {
14152 cx.propagate();
14153 return;
14154 }
14155 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14156 let cursor = self.buffer.read(cx).read(cx).len();
14157 self.change_selections(Default::default(), window, cx, |s| {
14158 s.select_ranges(vec![cursor..cursor])
14159 });
14160 }
14161
14162 pub fn set_nav_history(&mut self, nav_history: Option<ItemNavHistory>) {
14163 self.nav_history = nav_history;
14164 }
14165
14166 pub fn nav_history(&self) -> Option<&ItemNavHistory> {
14167 self.nav_history.as_ref()
14168 }
14169
14170 pub fn create_nav_history_entry(&mut self, cx: &mut Context<Self>) {
14171 self.push_to_nav_history(
14172 self.selections.newest_anchor().head(),
14173 None,
14174 false,
14175 true,
14176 cx,
14177 );
14178 }
14179
14180 fn push_to_nav_history(
14181 &mut self,
14182 cursor_anchor: Anchor,
14183 new_position: Option<Point>,
14184 is_deactivate: bool,
14185 always: bool,
14186 cx: &mut Context<Self>,
14187 ) {
14188 if let Some(nav_history) = self.nav_history.as_mut() {
14189 let buffer = self.buffer.read(cx).read(cx);
14190 let cursor_position = cursor_anchor.to_point(&buffer);
14191 let scroll_state = self.scroll_manager.anchor();
14192 let scroll_top_row = scroll_state.top_row(&buffer);
14193 drop(buffer);
14194
14195 if let Some(new_position) = new_position {
14196 let row_delta = (new_position.row as i64 - cursor_position.row as i64).abs();
14197 if row_delta == 0 || (row_delta < MIN_NAVIGATION_HISTORY_ROW_DELTA && !always) {
14198 return;
14199 }
14200 }
14201
14202 nav_history.push(
14203 Some(NavigationData {
14204 cursor_anchor,
14205 cursor_position,
14206 scroll_anchor: scroll_state,
14207 scroll_top_row,
14208 }),
14209 cx,
14210 );
14211 cx.emit(EditorEvent::PushedToNavHistory {
14212 anchor: cursor_anchor,
14213 is_deactivate,
14214 })
14215 }
14216 }
14217
14218 pub fn select_to_end(&mut self, _: &SelectToEnd, window: &mut Window, cx: &mut Context<Self>) {
14219 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14220 let buffer = self.buffer.read(cx).snapshot(cx);
14221 let mut selection = self.selections.first::<usize>(&self.display_snapshot(cx));
14222 selection.set_head(buffer.len(), SelectionGoal::None);
14223 self.change_selections(Default::default(), window, cx, |s| {
14224 s.select(vec![selection]);
14225 });
14226 }
14227
14228 pub fn select_all(&mut self, _: &SelectAll, window: &mut Window, cx: &mut Context<Self>) {
14229 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14230 let end = self.buffer.read(cx).read(cx).len();
14231 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
14232 s.select_ranges(vec![0..end]);
14233 });
14234 }
14235
14236 pub fn select_line(&mut self, _: &SelectLine, window: &mut Window, cx: &mut Context<Self>) {
14237 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14238 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14239 let mut selections = self.selections.all::<Point>(&display_map);
14240 let max_point = display_map.buffer_snapshot().max_point();
14241 for selection in &mut selections {
14242 let rows = selection.spanned_rows(true, &display_map);
14243 selection.start = Point::new(rows.start.0, 0);
14244 selection.end = cmp::min(max_point, Point::new(rows.end.0, 0));
14245 selection.reversed = false;
14246 }
14247 self.change_selections(Default::default(), window, cx, |s| {
14248 s.select(selections);
14249 });
14250 }
14251
14252 pub fn split_selection_into_lines(
14253 &mut self,
14254 action: &SplitSelectionIntoLines,
14255 window: &mut Window,
14256 cx: &mut Context<Self>,
14257 ) {
14258 let selections = self
14259 .selections
14260 .all::<Point>(&self.display_snapshot(cx))
14261 .into_iter()
14262 .map(|selection| selection.start..selection.end)
14263 .collect::<Vec<_>>();
14264 self.unfold_ranges(&selections, true, true, cx);
14265
14266 let mut new_selection_ranges = Vec::new();
14267 {
14268 let buffer = self.buffer.read(cx).read(cx);
14269 for selection in selections {
14270 for row in selection.start.row..selection.end.row {
14271 let line_start = Point::new(row, 0);
14272 let line_end = Point::new(row, buffer.line_len(MultiBufferRow(row)));
14273
14274 if action.keep_selections {
14275 // Keep the selection range for each line
14276 let selection_start = if row == selection.start.row {
14277 selection.start
14278 } else {
14279 line_start
14280 };
14281 new_selection_ranges.push(selection_start..line_end);
14282 } else {
14283 // Collapse to cursor at end of line
14284 new_selection_ranges.push(line_end..line_end);
14285 }
14286 }
14287
14288 let is_multiline_selection = selection.start.row != selection.end.row;
14289 // Don't insert last one if it's a multi-line selection ending at the start of a line,
14290 // so this action feels more ergonomic when paired with other selection operations
14291 let should_skip_last = is_multiline_selection && selection.end.column == 0;
14292 if !should_skip_last {
14293 if action.keep_selections {
14294 if is_multiline_selection {
14295 let line_start = Point::new(selection.end.row, 0);
14296 new_selection_ranges.push(line_start..selection.end);
14297 } else {
14298 new_selection_ranges.push(selection.start..selection.end);
14299 }
14300 } else {
14301 new_selection_ranges.push(selection.end..selection.end);
14302 }
14303 }
14304 }
14305 }
14306 self.change_selections(Default::default(), window, cx, |s| {
14307 s.select_ranges(new_selection_ranges);
14308 });
14309 }
14310
14311 pub fn add_selection_above(
14312 &mut self,
14313 action: &AddSelectionAbove,
14314 window: &mut Window,
14315 cx: &mut Context<Self>,
14316 ) {
14317 self.add_selection(true, action.skip_soft_wrap, window, cx);
14318 }
14319
14320 pub fn add_selection_below(
14321 &mut self,
14322 action: &AddSelectionBelow,
14323 window: &mut Window,
14324 cx: &mut Context<Self>,
14325 ) {
14326 self.add_selection(false, action.skip_soft_wrap, window, cx);
14327 }
14328
14329 fn add_selection(
14330 &mut self,
14331 above: bool,
14332 skip_soft_wrap: bool,
14333 window: &mut Window,
14334 cx: &mut Context<Self>,
14335 ) {
14336 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14337
14338 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14339 let all_selections = self.selections.all::<Point>(&display_map);
14340 let text_layout_details = self.text_layout_details(window);
14341
14342 let (mut columnar_selections, new_selections_to_columnarize) = {
14343 if let Some(state) = self.add_selections_state.as_ref() {
14344 let columnar_selection_ids: HashSet<_> = state
14345 .groups
14346 .iter()
14347 .flat_map(|group| group.stack.iter())
14348 .copied()
14349 .collect();
14350
14351 all_selections
14352 .into_iter()
14353 .partition(|s| columnar_selection_ids.contains(&s.id))
14354 } else {
14355 (Vec::new(), all_selections)
14356 }
14357 };
14358
14359 let mut state = self
14360 .add_selections_state
14361 .take()
14362 .unwrap_or_else(|| AddSelectionsState { groups: Vec::new() });
14363
14364 for selection in new_selections_to_columnarize {
14365 let range = selection.display_range(&display_map).sorted();
14366 let start_x = display_map.x_for_display_point(range.start, &text_layout_details);
14367 let end_x = display_map.x_for_display_point(range.end, &text_layout_details);
14368 let positions = start_x.min(end_x)..start_x.max(end_x);
14369 let mut stack = Vec::new();
14370 for row in range.start.row().0..=range.end.row().0 {
14371 if let Some(selection) = self.selections.build_columnar_selection(
14372 &display_map,
14373 DisplayRow(row),
14374 &positions,
14375 selection.reversed,
14376 &text_layout_details,
14377 ) {
14378 stack.push(selection.id);
14379 columnar_selections.push(selection);
14380 }
14381 }
14382 if !stack.is_empty() {
14383 if above {
14384 stack.reverse();
14385 }
14386 state.groups.push(AddSelectionsGroup { above, stack });
14387 }
14388 }
14389
14390 let mut final_selections = Vec::new();
14391 let end_row = if above {
14392 DisplayRow(0)
14393 } else {
14394 display_map.max_point().row()
14395 };
14396
14397 let mut last_added_item_per_group = HashMap::default();
14398 for group in state.groups.iter_mut() {
14399 if let Some(last_id) = group.stack.last() {
14400 last_added_item_per_group.insert(*last_id, group);
14401 }
14402 }
14403
14404 for selection in columnar_selections {
14405 if let Some(group) = last_added_item_per_group.get_mut(&selection.id) {
14406 if above == group.above {
14407 let range = selection.display_range(&display_map).sorted();
14408 debug_assert_eq!(range.start.row(), range.end.row());
14409 let mut row = range.start.row();
14410 let positions =
14411 if let SelectionGoal::HorizontalRange { start, end } = selection.goal {
14412 Pixels::from(start)..Pixels::from(end)
14413 } else {
14414 let start_x =
14415 display_map.x_for_display_point(range.start, &text_layout_details);
14416 let end_x =
14417 display_map.x_for_display_point(range.end, &text_layout_details);
14418 start_x.min(end_x)..start_x.max(end_x)
14419 };
14420
14421 let mut maybe_new_selection = None;
14422 let direction = if above { -1 } else { 1 };
14423
14424 while row != end_row {
14425 if skip_soft_wrap {
14426 row = display_map
14427 .start_of_relative_buffer_row(DisplayPoint::new(row, 0), direction)
14428 .row();
14429 } else if above {
14430 row.0 -= 1;
14431 } else {
14432 row.0 += 1;
14433 }
14434
14435 if let Some(new_selection) = self.selections.build_columnar_selection(
14436 &display_map,
14437 row,
14438 &positions,
14439 selection.reversed,
14440 &text_layout_details,
14441 ) {
14442 maybe_new_selection = Some(new_selection);
14443 break;
14444 }
14445 }
14446
14447 if let Some(new_selection) = maybe_new_selection {
14448 group.stack.push(new_selection.id);
14449 if above {
14450 final_selections.push(new_selection);
14451 final_selections.push(selection);
14452 } else {
14453 final_selections.push(selection);
14454 final_selections.push(new_selection);
14455 }
14456 } else {
14457 final_selections.push(selection);
14458 }
14459 } else {
14460 group.stack.pop();
14461 }
14462 } else {
14463 final_selections.push(selection);
14464 }
14465 }
14466
14467 self.change_selections(Default::default(), window, cx, |s| {
14468 s.select(final_selections);
14469 });
14470
14471 let final_selection_ids: HashSet<_> = self
14472 .selections
14473 .all::<Point>(&display_map)
14474 .iter()
14475 .map(|s| s.id)
14476 .collect();
14477 state.groups.retain_mut(|group| {
14478 // selections might get merged above so we remove invalid items from stacks
14479 group.stack.retain(|id| final_selection_ids.contains(id));
14480
14481 // single selection in stack can be treated as initial state
14482 group.stack.len() > 1
14483 });
14484
14485 if !state.groups.is_empty() {
14486 self.add_selections_state = Some(state);
14487 }
14488 }
14489
14490 fn select_match_ranges(
14491 &mut self,
14492 range: Range<usize>,
14493 reversed: bool,
14494 replace_newest: bool,
14495 auto_scroll: Option<Autoscroll>,
14496 window: &mut Window,
14497 cx: &mut Context<Editor>,
14498 ) {
14499 self.unfold_ranges(
14500 std::slice::from_ref(&range),
14501 false,
14502 auto_scroll.is_some(),
14503 cx,
14504 );
14505 let effects = if let Some(scroll) = auto_scroll {
14506 SelectionEffects::scroll(scroll)
14507 } else {
14508 SelectionEffects::no_scroll()
14509 };
14510 self.change_selections(effects, window, cx, |s| {
14511 if replace_newest {
14512 s.delete(s.newest_anchor().id);
14513 }
14514 if reversed {
14515 s.insert_range(range.end..range.start);
14516 } else {
14517 s.insert_range(range);
14518 }
14519 });
14520 }
14521
14522 pub fn select_next_match_internal(
14523 &mut self,
14524 display_map: &DisplaySnapshot,
14525 replace_newest: bool,
14526 autoscroll: Option<Autoscroll>,
14527 window: &mut Window,
14528 cx: &mut Context<Self>,
14529 ) -> Result<()> {
14530 let buffer = display_map.buffer_snapshot();
14531 let mut selections = self.selections.all::<usize>(&display_map);
14532 if let Some(mut select_next_state) = self.select_next_state.take() {
14533 let query = &select_next_state.query;
14534 if !select_next_state.done {
14535 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14536 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14537 let mut next_selected_range = None;
14538
14539 let bytes_after_last_selection =
14540 buffer.bytes_in_range(last_selection.end..buffer.len());
14541 let bytes_before_first_selection = buffer.bytes_in_range(0..first_selection.start);
14542 let query_matches = query
14543 .stream_find_iter(bytes_after_last_selection)
14544 .map(|result| (last_selection.end, result))
14545 .chain(
14546 query
14547 .stream_find_iter(bytes_before_first_selection)
14548 .map(|result| (0, result)),
14549 );
14550
14551 for (start_offset, query_match) in query_matches {
14552 let query_match = query_match.unwrap(); // can only fail due to I/O
14553 let offset_range =
14554 start_offset + query_match.start()..start_offset + query_match.end();
14555
14556 if !select_next_state.wordwise
14557 || (!buffer.is_inside_word(offset_range.start, None)
14558 && !buffer.is_inside_word(offset_range.end, None))
14559 {
14560 let idx = selections
14561 .partition_point(|selection| selection.end <= offset_range.start);
14562 let overlaps = selections
14563 .get(idx)
14564 .map_or(false, |selection| selection.start < offset_range.end);
14565
14566 if !overlaps {
14567 next_selected_range = Some(offset_range);
14568 break;
14569 }
14570 }
14571 }
14572
14573 if let Some(next_selected_range) = next_selected_range {
14574 self.select_match_ranges(
14575 next_selected_range,
14576 last_selection.reversed,
14577 replace_newest,
14578 autoscroll,
14579 window,
14580 cx,
14581 );
14582 } else {
14583 select_next_state.done = true;
14584 }
14585 }
14586
14587 self.select_next_state = Some(select_next_state);
14588 } else {
14589 let mut only_carets = true;
14590 let mut same_text_selected = true;
14591 let mut selected_text = None;
14592
14593 let mut selections_iter = selections.iter().peekable();
14594 while let Some(selection) = selections_iter.next() {
14595 if selection.start != selection.end {
14596 only_carets = false;
14597 }
14598
14599 if same_text_selected {
14600 if selected_text.is_none() {
14601 selected_text =
14602 Some(buffer.text_for_range(selection.range()).collect::<String>());
14603 }
14604
14605 if let Some(next_selection) = selections_iter.peek() {
14606 if next_selection.range().len() == selection.range().len() {
14607 let next_selected_text = buffer
14608 .text_for_range(next_selection.range())
14609 .collect::<String>();
14610 if Some(next_selected_text) != selected_text {
14611 same_text_selected = false;
14612 selected_text = None;
14613 }
14614 } else {
14615 same_text_selected = false;
14616 selected_text = None;
14617 }
14618 }
14619 }
14620 }
14621
14622 if only_carets {
14623 for selection in &mut selections {
14624 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14625 selection.start = word_range.start;
14626 selection.end = word_range.end;
14627 selection.goal = SelectionGoal::None;
14628 selection.reversed = false;
14629 self.select_match_ranges(
14630 selection.start..selection.end,
14631 selection.reversed,
14632 replace_newest,
14633 autoscroll,
14634 window,
14635 cx,
14636 );
14637 }
14638
14639 if selections.len() == 1 {
14640 let selection = selections
14641 .last()
14642 .expect("ensured that there's only one selection");
14643 let query = buffer
14644 .text_for_range(selection.start..selection.end)
14645 .collect::<String>();
14646 let is_empty = query.is_empty();
14647 let select_state = SelectNextState {
14648 query: AhoCorasick::new(&[query])?,
14649 wordwise: true,
14650 done: is_empty,
14651 };
14652 self.select_next_state = Some(select_state);
14653 } else {
14654 self.select_next_state = None;
14655 }
14656 } else if let Some(selected_text) = selected_text {
14657 self.select_next_state = Some(SelectNextState {
14658 query: AhoCorasick::new(&[selected_text])?,
14659 wordwise: false,
14660 done: false,
14661 });
14662 self.select_next_match_internal(
14663 display_map,
14664 replace_newest,
14665 autoscroll,
14666 window,
14667 cx,
14668 )?;
14669 }
14670 }
14671 Ok(())
14672 }
14673
14674 pub fn select_all_matches(
14675 &mut self,
14676 _action: &SelectAllMatches,
14677 window: &mut Window,
14678 cx: &mut Context<Self>,
14679 ) -> Result<()> {
14680 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14681
14682 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14683
14684 self.select_next_match_internal(&display_map, false, None, window, cx)?;
14685 let Some(select_next_state) = self.select_next_state.as_mut() else {
14686 return Ok(());
14687 };
14688 if select_next_state.done {
14689 return Ok(());
14690 }
14691
14692 let mut new_selections = Vec::new();
14693
14694 let reversed = self.selections.oldest::<usize>(&display_map).reversed;
14695 let buffer = display_map.buffer_snapshot();
14696 let query_matches = select_next_state
14697 .query
14698 .stream_find_iter(buffer.bytes_in_range(0..buffer.len()));
14699
14700 for query_match in query_matches.into_iter() {
14701 let query_match = query_match.context("query match for select all action")?; // can only fail due to I/O
14702 let offset_range = if reversed {
14703 query_match.end()..query_match.start()
14704 } else {
14705 query_match.start()..query_match.end()
14706 };
14707
14708 if !select_next_state.wordwise
14709 || (!buffer.is_inside_word(offset_range.start, None)
14710 && !buffer.is_inside_word(offset_range.end, None))
14711 {
14712 new_selections.push(offset_range.start..offset_range.end);
14713 }
14714 }
14715
14716 select_next_state.done = true;
14717
14718 if new_selections.is_empty() {
14719 log::error!("bug: new_selections is empty in select_all_matches");
14720 return Ok(());
14721 }
14722
14723 self.unfold_ranges(&new_selections.clone(), false, false, cx);
14724 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
14725 selections.select_ranges(new_selections)
14726 });
14727
14728 Ok(())
14729 }
14730
14731 pub fn select_next(
14732 &mut self,
14733 action: &SelectNext,
14734 window: &mut Window,
14735 cx: &mut Context<Self>,
14736 ) -> Result<()> {
14737 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14738 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14739 self.select_next_match_internal(
14740 &display_map,
14741 action.replace_newest,
14742 Some(Autoscroll::newest()),
14743 window,
14744 cx,
14745 )?;
14746 Ok(())
14747 }
14748
14749 pub fn select_previous(
14750 &mut self,
14751 action: &SelectPrevious,
14752 window: &mut Window,
14753 cx: &mut Context<Self>,
14754 ) -> Result<()> {
14755 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
14756 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
14757 let buffer = display_map.buffer_snapshot();
14758 let mut selections = self.selections.all::<usize>(&display_map);
14759 if let Some(mut select_prev_state) = self.select_prev_state.take() {
14760 let query = &select_prev_state.query;
14761 if !select_prev_state.done {
14762 let first_selection = selections.iter().min_by_key(|s| s.id).unwrap();
14763 let last_selection = selections.iter().max_by_key(|s| s.id).unwrap();
14764 let mut next_selected_range = None;
14765 // When we're iterating matches backwards, the oldest match will actually be the furthest one in the buffer.
14766 let bytes_before_last_selection =
14767 buffer.reversed_bytes_in_range(0..last_selection.start);
14768 let bytes_after_first_selection =
14769 buffer.reversed_bytes_in_range(first_selection.end..buffer.len());
14770 let query_matches = query
14771 .stream_find_iter(bytes_before_last_selection)
14772 .map(|result| (last_selection.start, result))
14773 .chain(
14774 query
14775 .stream_find_iter(bytes_after_first_selection)
14776 .map(|result| (buffer.len(), result)),
14777 );
14778 for (end_offset, query_match) in query_matches {
14779 let query_match = query_match.unwrap(); // can only fail due to I/O
14780 let offset_range =
14781 end_offset - query_match.end()..end_offset - query_match.start();
14782
14783 if !select_prev_state.wordwise
14784 || (!buffer.is_inside_word(offset_range.start, None)
14785 && !buffer.is_inside_word(offset_range.end, None))
14786 {
14787 next_selected_range = Some(offset_range);
14788 break;
14789 }
14790 }
14791
14792 if let Some(next_selected_range) = next_selected_range {
14793 self.select_match_ranges(
14794 next_selected_range,
14795 last_selection.reversed,
14796 action.replace_newest,
14797 Some(Autoscroll::newest()),
14798 window,
14799 cx,
14800 );
14801 } else {
14802 select_prev_state.done = true;
14803 }
14804 }
14805
14806 self.select_prev_state = Some(select_prev_state);
14807 } else {
14808 let mut only_carets = true;
14809 let mut same_text_selected = true;
14810 let mut selected_text = None;
14811
14812 let mut selections_iter = selections.iter().peekable();
14813 while let Some(selection) = selections_iter.next() {
14814 if selection.start != selection.end {
14815 only_carets = false;
14816 }
14817
14818 if same_text_selected {
14819 if selected_text.is_none() {
14820 selected_text =
14821 Some(buffer.text_for_range(selection.range()).collect::<String>());
14822 }
14823
14824 if let Some(next_selection) = selections_iter.peek() {
14825 if next_selection.range().len() == selection.range().len() {
14826 let next_selected_text = buffer
14827 .text_for_range(next_selection.range())
14828 .collect::<String>();
14829 if Some(next_selected_text) != selected_text {
14830 same_text_selected = false;
14831 selected_text = None;
14832 }
14833 } else {
14834 same_text_selected = false;
14835 selected_text = None;
14836 }
14837 }
14838 }
14839 }
14840
14841 if only_carets {
14842 for selection in &mut selections {
14843 let (word_range, _) = buffer.surrounding_word(selection.start, None);
14844 selection.start = word_range.start;
14845 selection.end = word_range.end;
14846 selection.goal = SelectionGoal::None;
14847 selection.reversed = false;
14848 self.select_match_ranges(
14849 selection.start..selection.end,
14850 selection.reversed,
14851 action.replace_newest,
14852 Some(Autoscroll::newest()),
14853 window,
14854 cx,
14855 );
14856 }
14857 if selections.len() == 1 {
14858 let selection = selections
14859 .last()
14860 .expect("ensured that there's only one selection");
14861 let query = buffer
14862 .text_for_range(selection.start..selection.end)
14863 .collect::<String>();
14864 let is_empty = query.is_empty();
14865 let select_state = SelectNextState {
14866 query: AhoCorasick::new(&[query.chars().rev().collect::<String>()])?,
14867 wordwise: true,
14868 done: is_empty,
14869 };
14870 self.select_prev_state = Some(select_state);
14871 } else {
14872 self.select_prev_state = None;
14873 }
14874 } else if let Some(selected_text) = selected_text {
14875 self.select_prev_state = Some(SelectNextState {
14876 query: AhoCorasick::new(&[selected_text.chars().rev().collect::<String>()])?,
14877 wordwise: false,
14878 done: false,
14879 });
14880 self.select_previous(action, window, cx)?;
14881 }
14882 }
14883 Ok(())
14884 }
14885
14886 pub fn find_next_match(
14887 &mut self,
14888 _: &FindNextMatch,
14889 window: &mut Window,
14890 cx: &mut Context<Self>,
14891 ) -> Result<()> {
14892 let selections = self.selections.disjoint_anchors_arc();
14893 match selections.first() {
14894 Some(first) if selections.len() >= 2 => {
14895 self.change_selections(Default::default(), window, cx, |s| {
14896 s.select_ranges([first.range()]);
14897 });
14898 }
14899 _ => self.select_next(
14900 &SelectNext {
14901 replace_newest: true,
14902 },
14903 window,
14904 cx,
14905 )?,
14906 }
14907 Ok(())
14908 }
14909
14910 pub fn find_previous_match(
14911 &mut self,
14912 _: &FindPreviousMatch,
14913 window: &mut Window,
14914 cx: &mut Context<Self>,
14915 ) -> Result<()> {
14916 let selections = self.selections.disjoint_anchors_arc();
14917 match selections.last() {
14918 Some(last) if selections.len() >= 2 => {
14919 self.change_selections(Default::default(), window, cx, |s| {
14920 s.select_ranges([last.range()]);
14921 });
14922 }
14923 _ => self.select_previous(
14924 &SelectPrevious {
14925 replace_newest: true,
14926 },
14927 window,
14928 cx,
14929 )?,
14930 }
14931 Ok(())
14932 }
14933
14934 pub fn toggle_comments(
14935 &mut self,
14936 action: &ToggleComments,
14937 window: &mut Window,
14938 cx: &mut Context<Self>,
14939 ) {
14940 if self.read_only(cx) {
14941 return;
14942 }
14943 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
14944 let text_layout_details = &self.text_layout_details(window);
14945 self.transact(window, cx, |this, window, cx| {
14946 let mut selections = this
14947 .selections
14948 .all::<MultiBufferPoint>(&this.display_snapshot(cx));
14949 let mut edits = Vec::new();
14950 let mut selection_edit_ranges = Vec::new();
14951 let mut last_toggled_row = None;
14952 let snapshot = this.buffer.read(cx).read(cx);
14953 let empty_str: Arc<str> = Arc::default();
14954 let mut suffixes_inserted = Vec::new();
14955 let ignore_indent = action.ignore_indent;
14956
14957 fn comment_prefix_range(
14958 snapshot: &MultiBufferSnapshot,
14959 row: MultiBufferRow,
14960 comment_prefix: &str,
14961 comment_prefix_whitespace: &str,
14962 ignore_indent: bool,
14963 ) -> Range<Point> {
14964 let indent_size = if ignore_indent {
14965 0
14966 } else {
14967 snapshot.indent_size_for_line(row).len
14968 };
14969
14970 let start = Point::new(row.0, indent_size);
14971
14972 let mut line_bytes = snapshot
14973 .bytes_in_range(start..snapshot.max_point())
14974 .flatten()
14975 .copied();
14976
14977 // If this line currently begins with the line comment prefix, then record
14978 // the range containing the prefix.
14979 if line_bytes
14980 .by_ref()
14981 .take(comment_prefix.len())
14982 .eq(comment_prefix.bytes())
14983 {
14984 // Include any whitespace that matches the comment prefix.
14985 let matching_whitespace_len = line_bytes
14986 .zip(comment_prefix_whitespace.bytes())
14987 .take_while(|(a, b)| a == b)
14988 .count() as u32;
14989 let end = Point::new(
14990 start.row,
14991 start.column + comment_prefix.len() as u32 + matching_whitespace_len,
14992 );
14993 start..end
14994 } else {
14995 start..start
14996 }
14997 }
14998
14999 fn comment_suffix_range(
15000 snapshot: &MultiBufferSnapshot,
15001 row: MultiBufferRow,
15002 comment_suffix: &str,
15003 comment_suffix_has_leading_space: bool,
15004 ) -> Range<Point> {
15005 let end = Point::new(row.0, snapshot.line_len(row));
15006 let suffix_start_column = end.column.saturating_sub(comment_suffix.len() as u32);
15007
15008 let mut line_end_bytes = snapshot
15009 .bytes_in_range(Point::new(end.row, suffix_start_column.saturating_sub(1))..end)
15010 .flatten()
15011 .copied();
15012
15013 let leading_space_len = if suffix_start_column > 0
15014 && line_end_bytes.next() == Some(b' ')
15015 && comment_suffix_has_leading_space
15016 {
15017 1
15018 } else {
15019 0
15020 };
15021
15022 // If this line currently begins with the line comment prefix, then record
15023 // the range containing the prefix.
15024 if line_end_bytes.by_ref().eq(comment_suffix.bytes()) {
15025 let start = Point::new(end.row, suffix_start_column - leading_space_len);
15026 start..end
15027 } else {
15028 end..end
15029 }
15030 }
15031
15032 // TODO: Handle selections that cross excerpts
15033 for selection in &mut selections {
15034 let start_column = snapshot
15035 .indent_size_for_line(MultiBufferRow(selection.start.row))
15036 .len;
15037 let language = if let Some(language) =
15038 snapshot.language_scope_at(Point::new(selection.start.row, start_column))
15039 {
15040 language
15041 } else {
15042 continue;
15043 };
15044
15045 selection_edit_ranges.clear();
15046
15047 // If multiple selections contain a given row, avoid processing that
15048 // row more than once.
15049 let mut start_row = MultiBufferRow(selection.start.row);
15050 if last_toggled_row == Some(start_row) {
15051 start_row = start_row.next_row();
15052 }
15053 let end_row =
15054 if selection.end.row > selection.start.row && selection.end.column == 0 {
15055 MultiBufferRow(selection.end.row - 1)
15056 } else {
15057 MultiBufferRow(selection.end.row)
15058 };
15059 last_toggled_row = Some(end_row);
15060
15061 if start_row > end_row {
15062 continue;
15063 }
15064
15065 // If the language has line comments, toggle those.
15066 let mut full_comment_prefixes = language.line_comment_prefixes().to_vec();
15067
15068 // If ignore_indent is set, trim spaces from the right side of all full_comment_prefixes
15069 if ignore_indent {
15070 full_comment_prefixes = full_comment_prefixes
15071 .into_iter()
15072 .map(|s| Arc::from(s.trim_end()))
15073 .collect();
15074 }
15075
15076 if !full_comment_prefixes.is_empty() {
15077 let first_prefix = full_comment_prefixes
15078 .first()
15079 .expect("prefixes is non-empty");
15080 let prefix_trimmed_lengths = full_comment_prefixes
15081 .iter()
15082 .map(|p| p.trim_end_matches(' ').len())
15083 .collect::<SmallVec<[usize; 4]>>();
15084
15085 let mut all_selection_lines_are_comments = true;
15086
15087 for row in start_row.0..=end_row.0 {
15088 let row = MultiBufferRow(row);
15089 if start_row < end_row && snapshot.is_line_blank(row) {
15090 continue;
15091 }
15092
15093 let prefix_range = full_comment_prefixes
15094 .iter()
15095 .zip(prefix_trimmed_lengths.iter().copied())
15096 .map(|(prefix, trimmed_prefix_len)| {
15097 comment_prefix_range(
15098 snapshot.deref(),
15099 row,
15100 &prefix[..trimmed_prefix_len],
15101 &prefix[trimmed_prefix_len..],
15102 ignore_indent,
15103 )
15104 })
15105 .max_by_key(|range| range.end.column - range.start.column)
15106 .expect("prefixes is non-empty");
15107
15108 if prefix_range.is_empty() {
15109 all_selection_lines_are_comments = false;
15110 }
15111
15112 selection_edit_ranges.push(prefix_range);
15113 }
15114
15115 if all_selection_lines_are_comments {
15116 edits.extend(
15117 selection_edit_ranges
15118 .iter()
15119 .cloned()
15120 .map(|range| (range, empty_str.clone())),
15121 );
15122 } else {
15123 let min_column = selection_edit_ranges
15124 .iter()
15125 .map(|range| range.start.column)
15126 .min()
15127 .unwrap_or(0);
15128 edits.extend(selection_edit_ranges.iter().map(|range| {
15129 let position = Point::new(range.start.row, min_column);
15130 (position..position, first_prefix.clone())
15131 }));
15132 }
15133 } else if let Some(BlockCommentConfig {
15134 start: full_comment_prefix,
15135 end: comment_suffix,
15136 ..
15137 }) = language.block_comment()
15138 {
15139 let comment_prefix = full_comment_prefix.trim_end_matches(' ');
15140 let comment_prefix_whitespace = &full_comment_prefix[comment_prefix.len()..];
15141 let prefix_range = comment_prefix_range(
15142 snapshot.deref(),
15143 start_row,
15144 comment_prefix,
15145 comment_prefix_whitespace,
15146 ignore_indent,
15147 );
15148 let suffix_range = comment_suffix_range(
15149 snapshot.deref(),
15150 end_row,
15151 comment_suffix.trim_start_matches(' '),
15152 comment_suffix.starts_with(' '),
15153 );
15154
15155 if prefix_range.is_empty() || suffix_range.is_empty() {
15156 edits.push((
15157 prefix_range.start..prefix_range.start,
15158 full_comment_prefix.clone(),
15159 ));
15160 edits.push((suffix_range.end..suffix_range.end, comment_suffix.clone()));
15161 suffixes_inserted.push((end_row, comment_suffix.len()));
15162 } else {
15163 edits.push((prefix_range, empty_str.clone()));
15164 edits.push((suffix_range, empty_str.clone()));
15165 }
15166 } else {
15167 continue;
15168 }
15169 }
15170
15171 drop(snapshot);
15172 this.buffer.update(cx, |buffer, cx| {
15173 buffer.edit(edits, None, cx);
15174 });
15175
15176 // Adjust selections so that they end before any comment suffixes that
15177 // were inserted.
15178 let mut suffixes_inserted = suffixes_inserted.into_iter().peekable();
15179 let mut selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15180 let snapshot = this.buffer.read(cx).read(cx);
15181 for selection in &mut selections {
15182 while let Some((row, suffix_len)) = suffixes_inserted.peek().copied() {
15183 match row.cmp(&MultiBufferRow(selection.end.row)) {
15184 Ordering::Less => {
15185 suffixes_inserted.next();
15186 continue;
15187 }
15188 Ordering::Greater => break,
15189 Ordering::Equal => {
15190 if selection.end.column == snapshot.line_len(row) {
15191 if selection.is_empty() {
15192 selection.start.column -= suffix_len as u32;
15193 }
15194 selection.end.column -= suffix_len as u32;
15195 }
15196 break;
15197 }
15198 }
15199 }
15200 }
15201
15202 drop(snapshot);
15203 this.change_selections(Default::default(), window, cx, |s| s.select(selections));
15204
15205 let selections = this.selections.all::<Point>(&this.display_snapshot(cx));
15206 let selections_on_single_row = selections.windows(2).all(|selections| {
15207 selections[0].start.row == selections[1].start.row
15208 && selections[0].end.row == selections[1].end.row
15209 && selections[0].start.row == selections[0].end.row
15210 });
15211 let selections_selecting = selections
15212 .iter()
15213 .any(|selection| selection.start != selection.end);
15214 let advance_downwards = action.advance_downwards
15215 && selections_on_single_row
15216 && !selections_selecting
15217 && !matches!(this.mode, EditorMode::SingleLine);
15218
15219 if advance_downwards {
15220 let snapshot = this.buffer.read(cx).snapshot(cx);
15221
15222 this.change_selections(Default::default(), window, cx, |s| {
15223 s.move_cursors_with(|display_snapshot, display_point, _| {
15224 let mut point = display_point.to_point(display_snapshot);
15225 point.row += 1;
15226 point = snapshot.clip_point(point, Bias::Left);
15227 let display_point = point.to_display_point(display_snapshot);
15228 let goal = SelectionGoal::HorizontalPosition(
15229 display_snapshot
15230 .x_for_display_point(display_point, text_layout_details)
15231 .into(),
15232 );
15233 (display_point, goal)
15234 })
15235 });
15236 }
15237 });
15238 }
15239
15240 pub fn select_enclosing_symbol(
15241 &mut self,
15242 _: &SelectEnclosingSymbol,
15243 window: &mut Window,
15244 cx: &mut Context<Self>,
15245 ) {
15246 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15247
15248 let buffer = self.buffer.read(cx).snapshot(cx);
15249 let old_selections = self
15250 .selections
15251 .all::<usize>(&self.display_snapshot(cx))
15252 .into_boxed_slice();
15253
15254 fn update_selection(
15255 selection: &Selection<usize>,
15256 buffer_snap: &MultiBufferSnapshot,
15257 ) -> Option<Selection<usize>> {
15258 let cursor = selection.head();
15259 let (_buffer_id, symbols) = buffer_snap.symbols_containing(cursor, None)?;
15260 for symbol in symbols.iter().rev() {
15261 let start = symbol.range.start.to_offset(buffer_snap);
15262 let end = symbol.range.end.to_offset(buffer_snap);
15263 let new_range = start..end;
15264 if start < selection.start || end > selection.end {
15265 return Some(Selection {
15266 id: selection.id,
15267 start: new_range.start,
15268 end: new_range.end,
15269 goal: SelectionGoal::None,
15270 reversed: selection.reversed,
15271 });
15272 }
15273 }
15274 None
15275 }
15276
15277 let mut selected_larger_symbol = false;
15278 let new_selections = old_selections
15279 .iter()
15280 .map(|selection| match update_selection(selection, &buffer) {
15281 Some(new_selection) => {
15282 if new_selection.range() != selection.range() {
15283 selected_larger_symbol = true;
15284 }
15285 new_selection
15286 }
15287 None => selection.clone(),
15288 })
15289 .collect::<Vec<_>>();
15290
15291 if selected_larger_symbol {
15292 self.change_selections(Default::default(), window, cx, |s| {
15293 s.select(new_selections);
15294 });
15295 }
15296 }
15297
15298 pub fn select_larger_syntax_node(
15299 &mut self,
15300 _: &SelectLargerSyntaxNode,
15301 window: &mut Window,
15302 cx: &mut Context<Self>,
15303 ) {
15304 let Some(visible_row_count) = self.visible_row_count() else {
15305 return;
15306 };
15307 let old_selections: Box<[_]> = self
15308 .selections
15309 .all::<usize>(&self.display_snapshot(cx))
15310 .into();
15311 if old_selections.is_empty() {
15312 return;
15313 }
15314
15315 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15316
15317 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
15318 let buffer = self.buffer.read(cx).snapshot(cx);
15319
15320 let mut selected_larger_node = false;
15321 let mut new_selections = old_selections
15322 .iter()
15323 .map(|selection| {
15324 let old_range = selection.start..selection.end;
15325
15326 if let Some((node, _)) = buffer.syntax_ancestor(old_range.clone()) {
15327 // manually select word at selection
15328 if ["string_content", "inline"].contains(&node.kind()) {
15329 let (word_range, _) = buffer.surrounding_word(old_range.start, None);
15330 // ignore if word is already selected
15331 if !word_range.is_empty() && old_range != word_range {
15332 let (last_word_range, _) = buffer.surrounding_word(old_range.end, None);
15333 // only select word if start and end point belongs to same word
15334 if word_range == last_word_range {
15335 selected_larger_node = true;
15336 return Selection {
15337 id: selection.id,
15338 start: word_range.start,
15339 end: word_range.end,
15340 goal: SelectionGoal::None,
15341 reversed: selection.reversed,
15342 };
15343 }
15344 }
15345 }
15346 }
15347
15348 let mut new_range = old_range.clone();
15349 while let Some((node, range)) = buffer.syntax_ancestor(new_range.clone()) {
15350 new_range = range;
15351 if !node.is_named() {
15352 continue;
15353 }
15354 if !display_map.intersects_fold(new_range.start)
15355 && !display_map.intersects_fold(new_range.end)
15356 {
15357 break;
15358 }
15359 }
15360
15361 selected_larger_node |= new_range != old_range;
15362 Selection {
15363 id: selection.id,
15364 start: new_range.start,
15365 end: new_range.end,
15366 goal: SelectionGoal::None,
15367 reversed: selection.reversed,
15368 }
15369 })
15370 .collect::<Vec<_>>();
15371
15372 if !selected_larger_node {
15373 return; // don't put this call in the history
15374 }
15375
15376 // scroll based on transformation done to the last selection created by the user
15377 let (last_old, last_new) = old_selections
15378 .last()
15379 .zip(new_selections.last().cloned())
15380 .expect("old_selections isn't empty");
15381
15382 // revert selection
15383 let is_selection_reversed = {
15384 let should_newest_selection_be_reversed = last_old.start != last_new.start;
15385 new_selections.last_mut().expect("checked above").reversed =
15386 should_newest_selection_be_reversed;
15387 should_newest_selection_be_reversed
15388 };
15389
15390 if selected_larger_node {
15391 self.select_syntax_node_history.disable_clearing = true;
15392 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15393 s.select(new_selections.clone());
15394 });
15395 self.select_syntax_node_history.disable_clearing = false;
15396 }
15397
15398 let start_row = last_new.start.to_display_point(&display_map).row().0;
15399 let end_row = last_new.end.to_display_point(&display_map).row().0;
15400 let selection_height = end_row - start_row + 1;
15401 let scroll_margin_rows = self.vertical_scroll_margin() as u32;
15402
15403 let fits_on_the_screen = visible_row_count >= selection_height + scroll_margin_rows * 2;
15404 let scroll_behavior = if fits_on_the_screen {
15405 self.request_autoscroll(Autoscroll::fit(), cx);
15406 SelectSyntaxNodeScrollBehavior::FitSelection
15407 } else if is_selection_reversed {
15408 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15409 SelectSyntaxNodeScrollBehavior::CursorTop
15410 } else {
15411 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15412 SelectSyntaxNodeScrollBehavior::CursorBottom
15413 };
15414
15415 self.select_syntax_node_history.push((
15416 old_selections,
15417 scroll_behavior,
15418 is_selection_reversed,
15419 ));
15420 }
15421
15422 pub fn select_smaller_syntax_node(
15423 &mut self,
15424 _: &SelectSmallerSyntaxNode,
15425 window: &mut Window,
15426 cx: &mut Context<Self>,
15427 ) {
15428 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15429
15430 if let Some((mut selections, scroll_behavior, is_selection_reversed)) =
15431 self.select_syntax_node_history.pop()
15432 {
15433 if let Some(selection) = selections.last_mut() {
15434 selection.reversed = is_selection_reversed;
15435 }
15436
15437 self.select_syntax_node_history.disable_clearing = true;
15438 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
15439 s.select(selections.to_vec());
15440 });
15441 self.select_syntax_node_history.disable_clearing = false;
15442
15443 match scroll_behavior {
15444 SelectSyntaxNodeScrollBehavior::CursorTop => {
15445 self.scroll_cursor_top(&ScrollCursorTop, window, cx);
15446 }
15447 SelectSyntaxNodeScrollBehavior::FitSelection => {
15448 self.request_autoscroll(Autoscroll::fit(), cx);
15449 }
15450 SelectSyntaxNodeScrollBehavior::CursorBottom => {
15451 self.scroll_cursor_bottom(&ScrollCursorBottom, window, cx);
15452 }
15453 }
15454 }
15455 }
15456
15457 pub fn unwrap_syntax_node(
15458 &mut self,
15459 _: &UnwrapSyntaxNode,
15460 window: &mut Window,
15461 cx: &mut Context<Self>,
15462 ) {
15463 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15464
15465 let buffer = self.buffer.read(cx).snapshot(cx);
15466 let selections = self
15467 .selections
15468 .all::<usize>(&self.display_snapshot(cx))
15469 .into_iter()
15470 // subtracting the offset requires sorting
15471 .sorted_by_key(|i| i.start);
15472
15473 let full_edits = selections
15474 .into_iter()
15475 .filter_map(|selection| {
15476 let child = if selection.is_empty()
15477 && let Some((_, ancestor_range)) =
15478 buffer.syntax_ancestor(selection.start..selection.end)
15479 {
15480 ancestor_range
15481 } else {
15482 selection.range()
15483 };
15484
15485 let mut parent = child.clone();
15486 while let Some((_, ancestor_range)) = buffer.syntax_ancestor(parent.clone()) {
15487 parent = ancestor_range;
15488 if parent.start < child.start || parent.end > child.end {
15489 break;
15490 }
15491 }
15492
15493 if parent == child {
15494 return None;
15495 }
15496 let text = buffer.text_for_range(child).collect::<String>();
15497 Some((selection.id, parent, text))
15498 })
15499 .collect::<Vec<_>>();
15500 if full_edits.is_empty() {
15501 return;
15502 }
15503
15504 self.transact(window, cx, |this, window, cx| {
15505 this.buffer.update(cx, |buffer, cx| {
15506 buffer.edit(
15507 full_edits
15508 .iter()
15509 .map(|(_, p, t)| (p.clone(), t.clone()))
15510 .collect::<Vec<_>>(),
15511 None,
15512 cx,
15513 );
15514 });
15515 this.change_selections(Default::default(), window, cx, |s| {
15516 let mut offset = 0;
15517 let mut selections = vec![];
15518 for (id, parent, text) in full_edits {
15519 let start = parent.start - offset;
15520 offset += parent.len() - text.len();
15521 selections.push(Selection {
15522 id,
15523 start,
15524 end: start + text.len(),
15525 reversed: false,
15526 goal: Default::default(),
15527 });
15528 }
15529 s.select(selections);
15530 });
15531 });
15532 }
15533
15534 pub fn select_next_syntax_node(
15535 &mut self,
15536 _: &SelectNextSyntaxNode,
15537 window: &mut Window,
15538 cx: &mut Context<Self>,
15539 ) {
15540 let old_selections: Box<[_]> = self
15541 .selections
15542 .all::<usize>(&self.display_snapshot(cx))
15543 .into();
15544 if old_selections.is_empty() {
15545 return;
15546 }
15547
15548 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15549
15550 let buffer = self.buffer.read(cx).snapshot(cx);
15551 let mut selected_sibling = false;
15552
15553 let new_selections = old_selections
15554 .iter()
15555 .map(|selection| {
15556 let old_range = selection.start..selection.end;
15557
15558 if let Some(node) = buffer.syntax_next_sibling(old_range) {
15559 let new_range = node.byte_range();
15560 selected_sibling = true;
15561 Selection {
15562 id: selection.id,
15563 start: new_range.start,
15564 end: new_range.end,
15565 goal: SelectionGoal::None,
15566 reversed: selection.reversed,
15567 }
15568 } else {
15569 selection.clone()
15570 }
15571 })
15572 .collect::<Vec<_>>();
15573
15574 if selected_sibling {
15575 self.change_selections(
15576 SelectionEffects::scroll(Autoscroll::fit()),
15577 window,
15578 cx,
15579 |s| {
15580 s.select(new_selections);
15581 },
15582 );
15583 }
15584 }
15585
15586 pub fn select_prev_syntax_node(
15587 &mut self,
15588 _: &SelectPreviousSyntaxNode,
15589 window: &mut Window,
15590 cx: &mut Context<Self>,
15591 ) {
15592 let old_selections: Box<[_]> = self
15593 .selections
15594 .all::<usize>(&self.display_snapshot(cx))
15595 .into();
15596 if old_selections.is_empty() {
15597 return;
15598 }
15599
15600 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15601
15602 let buffer = self.buffer.read(cx).snapshot(cx);
15603 let mut selected_sibling = false;
15604
15605 let new_selections = old_selections
15606 .iter()
15607 .map(|selection| {
15608 let old_range = selection.start..selection.end;
15609
15610 if let Some(node) = buffer.syntax_prev_sibling(old_range) {
15611 let new_range = node.byte_range();
15612 selected_sibling = true;
15613 Selection {
15614 id: selection.id,
15615 start: new_range.start,
15616 end: new_range.end,
15617 goal: SelectionGoal::None,
15618 reversed: selection.reversed,
15619 }
15620 } else {
15621 selection.clone()
15622 }
15623 })
15624 .collect::<Vec<_>>();
15625
15626 if selected_sibling {
15627 self.change_selections(
15628 SelectionEffects::scroll(Autoscroll::fit()),
15629 window,
15630 cx,
15631 |s| {
15632 s.select(new_selections);
15633 },
15634 );
15635 }
15636 }
15637
15638 fn refresh_runnables(&mut self, window: &mut Window, cx: &mut Context<Self>) -> Task<()> {
15639 if !EditorSettings::get_global(cx).gutter.runnables {
15640 self.clear_tasks();
15641 return Task::ready(());
15642 }
15643 let project = self.project().map(Entity::downgrade);
15644 let task_sources = self.lsp_task_sources(cx);
15645 let multi_buffer = self.buffer.downgrade();
15646 cx.spawn_in(window, async move |editor, cx| {
15647 cx.background_executor().timer(UPDATE_DEBOUNCE).await;
15648 let Some(project) = project.and_then(|p| p.upgrade()) else {
15649 return;
15650 };
15651 let Ok(display_snapshot) = editor.update(cx, |this, cx| {
15652 this.display_map.update(cx, |map, cx| map.snapshot(cx))
15653 }) else {
15654 return;
15655 };
15656
15657 let hide_runnables = project
15658 .update(cx, |project, _| project.is_via_collab())
15659 .unwrap_or(true);
15660 if hide_runnables {
15661 return;
15662 }
15663 let new_rows =
15664 cx.background_spawn({
15665 let snapshot = display_snapshot.clone();
15666 async move {
15667 Self::fetch_runnable_ranges(&snapshot, Anchor::min()..Anchor::max())
15668 }
15669 })
15670 .await;
15671 let Ok(lsp_tasks) =
15672 cx.update(|_, cx| crate::lsp_tasks(project.clone(), &task_sources, None, cx))
15673 else {
15674 return;
15675 };
15676 let lsp_tasks = lsp_tasks.await;
15677
15678 let Ok(mut lsp_tasks_by_rows) = cx.update(|_, cx| {
15679 lsp_tasks
15680 .into_iter()
15681 .flat_map(|(kind, tasks)| {
15682 tasks.into_iter().filter_map(move |(location, task)| {
15683 Some((kind.clone(), location?, task))
15684 })
15685 })
15686 .fold(HashMap::default(), |mut acc, (kind, location, task)| {
15687 let buffer = location.target.buffer;
15688 let buffer_snapshot = buffer.read(cx).snapshot();
15689 let offset = display_snapshot.buffer_snapshot().excerpts().find_map(
15690 |(excerpt_id, snapshot, _)| {
15691 if snapshot.remote_id() == buffer_snapshot.remote_id() {
15692 display_snapshot
15693 .buffer_snapshot()
15694 .anchor_in_excerpt(excerpt_id, location.target.range.start)
15695 } else {
15696 None
15697 }
15698 },
15699 );
15700 if let Some(offset) = offset {
15701 let task_buffer_range =
15702 location.target.range.to_point(&buffer_snapshot);
15703 let context_buffer_range =
15704 task_buffer_range.to_offset(&buffer_snapshot);
15705 let context_range = BufferOffset(context_buffer_range.start)
15706 ..BufferOffset(context_buffer_range.end);
15707
15708 acc.entry((buffer_snapshot.remote_id(), task_buffer_range.start.row))
15709 .or_insert_with(|| RunnableTasks {
15710 templates: Vec::new(),
15711 offset,
15712 column: task_buffer_range.start.column,
15713 extra_variables: HashMap::default(),
15714 context_range,
15715 })
15716 .templates
15717 .push((kind, task.original_task().clone()));
15718 }
15719
15720 acc
15721 })
15722 }) else {
15723 return;
15724 };
15725
15726 let Ok(prefer_lsp) = multi_buffer.update(cx, |buffer, cx| {
15727 buffer.language_settings(cx).tasks.prefer_lsp
15728 }) else {
15729 return;
15730 };
15731
15732 let rows = Self::runnable_rows(
15733 project,
15734 display_snapshot,
15735 prefer_lsp && !lsp_tasks_by_rows.is_empty(),
15736 new_rows,
15737 cx.clone(),
15738 )
15739 .await;
15740 editor
15741 .update(cx, |editor, _| {
15742 editor.clear_tasks();
15743 for (key, mut value) in rows {
15744 if let Some(lsp_tasks) = lsp_tasks_by_rows.remove(&key) {
15745 value.templates.extend(lsp_tasks.templates);
15746 }
15747
15748 editor.insert_tasks(key, value);
15749 }
15750 for (key, value) in lsp_tasks_by_rows {
15751 editor.insert_tasks(key, value);
15752 }
15753 })
15754 .ok();
15755 })
15756 }
15757 fn fetch_runnable_ranges(
15758 snapshot: &DisplaySnapshot,
15759 range: Range<Anchor>,
15760 ) -> Vec<language::RunnableRange> {
15761 snapshot.buffer_snapshot().runnable_ranges(range).collect()
15762 }
15763
15764 fn runnable_rows(
15765 project: Entity<Project>,
15766 snapshot: DisplaySnapshot,
15767 prefer_lsp: bool,
15768 runnable_ranges: Vec<RunnableRange>,
15769 cx: AsyncWindowContext,
15770 ) -> Task<Vec<((BufferId, BufferRow), RunnableTasks)>> {
15771 cx.spawn(async move |cx| {
15772 let mut runnable_rows = Vec::with_capacity(runnable_ranges.len());
15773 for mut runnable in runnable_ranges {
15774 let Some(tasks) = cx
15775 .update(|_, cx| Self::templates_with_tags(&project, &mut runnable.runnable, cx))
15776 .ok()
15777 else {
15778 continue;
15779 };
15780 let mut tasks = tasks.await;
15781
15782 if prefer_lsp {
15783 tasks.retain(|(task_kind, _)| {
15784 !matches!(task_kind, TaskSourceKind::Language { .. })
15785 });
15786 }
15787 if tasks.is_empty() {
15788 continue;
15789 }
15790
15791 let point = runnable
15792 .run_range
15793 .start
15794 .to_point(&snapshot.buffer_snapshot());
15795 let Some(row) = snapshot
15796 .buffer_snapshot()
15797 .buffer_line_for_row(MultiBufferRow(point.row))
15798 .map(|(_, range)| range.start.row)
15799 else {
15800 continue;
15801 };
15802
15803 let context_range =
15804 BufferOffset(runnable.full_range.start)..BufferOffset(runnable.full_range.end);
15805 runnable_rows.push((
15806 (runnable.buffer_id, row),
15807 RunnableTasks {
15808 templates: tasks,
15809 offset: snapshot
15810 .buffer_snapshot()
15811 .anchor_before(runnable.run_range.start),
15812 context_range,
15813 column: point.column,
15814 extra_variables: runnable.extra_captures,
15815 },
15816 ));
15817 }
15818 runnable_rows
15819 })
15820 }
15821
15822 fn templates_with_tags(
15823 project: &Entity<Project>,
15824 runnable: &mut Runnable,
15825 cx: &mut App,
15826 ) -> Task<Vec<(TaskSourceKind, TaskTemplate)>> {
15827 let (inventory, worktree_id, file) = project.read_with(cx, |project, cx| {
15828 let (worktree_id, file) = project
15829 .buffer_for_id(runnable.buffer, cx)
15830 .and_then(|buffer| buffer.read(cx).file())
15831 .map(|file| (file.worktree_id(cx), file.clone()))
15832 .unzip();
15833
15834 (
15835 project.task_store().read(cx).task_inventory().cloned(),
15836 worktree_id,
15837 file,
15838 )
15839 });
15840
15841 let tags = mem::take(&mut runnable.tags);
15842 let language = runnable.language.clone();
15843 cx.spawn(async move |cx| {
15844 let mut templates_with_tags = Vec::new();
15845 if let Some(inventory) = inventory {
15846 for RunnableTag(tag) in tags {
15847 let Ok(new_tasks) = inventory.update(cx, |inventory, cx| {
15848 inventory.list_tasks(file.clone(), Some(language.clone()), worktree_id, cx)
15849 }) else {
15850 return templates_with_tags;
15851 };
15852 templates_with_tags.extend(new_tasks.await.into_iter().filter(
15853 move |(_, template)| {
15854 template.tags.iter().any(|source_tag| source_tag == &tag)
15855 },
15856 ));
15857 }
15858 }
15859 templates_with_tags.sort_by_key(|(kind, _)| kind.to_owned());
15860
15861 if let Some((leading_tag_source, _)) = templates_with_tags.first() {
15862 // Strongest source wins; if we have worktree tag binding, prefer that to
15863 // global and language bindings;
15864 // if we have a global binding, prefer that to language binding.
15865 let first_mismatch = templates_with_tags
15866 .iter()
15867 .position(|(tag_source, _)| tag_source != leading_tag_source);
15868 if let Some(index) = first_mismatch {
15869 templates_with_tags.truncate(index);
15870 }
15871 }
15872
15873 templates_with_tags
15874 })
15875 }
15876
15877 pub fn move_to_enclosing_bracket(
15878 &mut self,
15879 _: &MoveToEnclosingBracket,
15880 window: &mut Window,
15881 cx: &mut Context<Self>,
15882 ) {
15883 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15884 self.change_selections(Default::default(), window, cx, |s| {
15885 s.move_offsets_with(|snapshot, selection| {
15886 let Some(enclosing_bracket_ranges) =
15887 snapshot.enclosing_bracket_ranges(selection.start..selection.end)
15888 else {
15889 return;
15890 };
15891
15892 let mut best_length = usize::MAX;
15893 let mut best_inside = false;
15894 let mut best_in_bracket_range = false;
15895 let mut best_destination = None;
15896 for (open, close) in enclosing_bracket_ranges {
15897 let close = close.to_inclusive();
15898 let length = close.end() - open.start;
15899 let inside = selection.start >= open.end && selection.end <= *close.start();
15900 let in_bracket_range = open.to_inclusive().contains(&selection.head())
15901 || close.contains(&selection.head());
15902
15903 // If best is next to a bracket and current isn't, skip
15904 if !in_bracket_range && best_in_bracket_range {
15905 continue;
15906 }
15907
15908 // Prefer smaller lengths unless best is inside and current isn't
15909 if length > best_length && (best_inside || !inside) {
15910 continue;
15911 }
15912
15913 best_length = length;
15914 best_inside = inside;
15915 best_in_bracket_range = in_bracket_range;
15916 best_destination = Some(
15917 if close.contains(&selection.start) && close.contains(&selection.end) {
15918 if inside { open.end } else { open.start }
15919 } else if inside {
15920 *close.start()
15921 } else {
15922 *close.end()
15923 },
15924 );
15925 }
15926
15927 if let Some(destination) = best_destination {
15928 selection.collapse_to(destination, SelectionGoal::None);
15929 }
15930 })
15931 });
15932 }
15933
15934 pub fn undo_selection(
15935 &mut self,
15936 _: &UndoSelection,
15937 window: &mut Window,
15938 cx: &mut Context<Self>,
15939 ) {
15940 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15941 if let Some(entry) = self.selection_history.undo_stack.pop_back() {
15942 self.selection_history.mode = SelectionHistoryMode::Undoing;
15943 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15944 this.end_selection(window, cx);
15945 this.change_selections(
15946 SelectionEffects::scroll(Autoscroll::newest()),
15947 window,
15948 cx,
15949 |s| s.select_anchors(entry.selections.to_vec()),
15950 );
15951 });
15952 self.selection_history.mode = SelectionHistoryMode::Normal;
15953
15954 self.select_next_state = entry.select_next_state;
15955 self.select_prev_state = entry.select_prev_state;
15956 self.add_selections_state = entry.add_selections_state;
15957 }
15958 }
15959
15960 pub fn redo_selection(
15961 &mut self,
15962 _: &RedoSelection,
15963 window: &mut Window,
15964 cx: &mut Context<Self>,
15965 ) {
15966 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
15967 if let Some(entry) = self.selection_history.redo_stack.pop_back() {
15968 self.selection_history.mode = SelectionHistoryMode::Redoing;
15969 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
15970 this.end_selection(window, cx);
15971 this.change_selections(
15972 SelectionEffects::scroll(Autoscroll::newest()),
15973 window,
15974 cx,
15975 |s| s.select_anchors(entry.selections.to_vec()),
15976 );
15977 });
15978 self.selection_history.mode = SelectionHistoryMode::Normal;
15979
15980 self.select_next_state = entry.select_next_state;
15981 self.select_prev_state = entry.select_prev_state;
15982 self.add_selections_state = entry.add_selections_state;
15983 }
15984 }
15985
15986 pub fn expand_excerpts(
15987 &mut self,
15988 action: &ExpandExcerpts,
15989 _: &mut Window,
15990 cx: &mut Context<Self>,
15991 ) {
15992 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::UpAndDown, cx)
15993 }
15994
15995 pub fn expand_excerpts_down(
15996 &mut self,
15997 action: &ExpandExcerptsDown,
15998 _: &mut Window,
15999 cx: &mut Context<Self>,
16000 ) {
16001 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Down, cx)
16002 }
16003
16004 pub fn expand_excerpts_up(
16005 &mut self,
16006 action: &ExpandExcerptsUp,
16007 _: &mut Window,
16008 cx: &mut Context<Self>,
16009 ) {
16010 self.expand_excerpts_for_direction(action.lines, ExpandExcerptDirection::Up, cx)
16011 }
16012
16013 pub fn expand_excerpts_for_direction(
16014 &mut self,
16015 lines: u32,
16016 direction: ExpandExcerptDirection,
16017
16018 cx: &mut Context<Self>,
16019 ) {
16020 let selections = self.selections.disjoint_anchors_arc();
16021
16022 let lines = if lines == 0 {
16023 EditorSettings::get_global(cx).expand_excerpt_lines
16024 } else {
16025 lines
16026 };
16027
16028 self.buffer.update(cx, |buffer, cx| {
16029 let snapshot = buffer.snapshot(cx);
16030 let mut excerpt_ids = selections
16031 .iter()
16032 .flat_map(|selection| snapshot.excerpt_ids_for_range(selection.range()))
16033 .collect::<Vec<_>>();
16034 excerpt_ids.sort();
16035 excerpt_ids.dedup();
16036 buffer.expand_excerpts(excerpt_ids, lines, direction, cx)
16037 })
16038 }
16039
16040 pub fn expand_excerpt(
16041 &mut self,
16042 excerpt: ExcerptId,
16043 direction: ExpandExcerptDirection,
16044 window: &mut Window,
16045 cx: &mut Context<Self>,
16046 ) {
16047 let current_scroll_position = self.scroll_position(cx);
16048 let lines_to_expand = EditorSettings::get_global(cx).expand_excerpt_lines;
16049 let mut scroll = None;
16050
16051 if direction == ExpandExcerptDirection::Down {
16052 let multi_buffer = self.buffer.read(cx);
16053 let snapshot = multi_buffer.snapshot(cx);
16054 if let Some(buffer_id) = snapshot.buffer_id_for_excerpt(excerpt)
16055 && let Some(buffer) = multi_buffer.buffer(buffer_id)
16056 && let Some(excerpt_range) = snapshot.context_range_for_excerpt(excerpt)
16057 {
16058 let buffer_snapshot = buffer.read(cx).snapshot();
16059 let excerpt_end_row = Point::from_anchor(&excerpt_range.end, &buffer_snapshot).row;
16060 let last_row = buffer_snapshot.max_point().row;
16061 let lines_below = last_row.saturating_sub(excerpt_end_row);
16062 if lines_below >= lines_to_expand {
16063 scroll = Some(
16064 current_scroll_position
16065 + gpui::Point::new(0.0, lines_to_expand as ScrollOffset),
16066 );
16067 }
16068 }
16069 }
16070 if direction == ExpandExcerptDirection::Up
16071 && self
16072 .buffer
16073 .read(cx)
16074 .snapshot(cx)
16075 .excerpt_before(excerpt)
16076 .is_none()
16077 {
16078 scroll = Some(current_scroll_position);
16079 }
16080
16081 self.buffer.update(cx, |buffer, cx| {
16082 buffer.expand_excerpts([excerpt], lines_to_expand, direction, cx)
16083 });
16084
16085 if let Some(new_scroll_position) = scroll {
16086 self.set_scroll_position(new_scroll_position, window, cx);
16087 }
16088 }
16089
16090 pub fn go_to_singleton_buffer_point(
16091 &mut self,
16092 point: Point,
16093 window: &mut Window,
16094 cx: &mut Context<Self>,
16095 ) {
16096 self.go_to_singleton_buffer_range(point..point, window, cx);
16097 }
16098
16099 pub fn go_to_singleton_buffer_range(
16100 &mut self,
16101 range: Range<Point>,
16102 window: &mut Window,
16103 cx: &mut Context<Self>,
16104 ) {
16105 let multibuffer = self.buffer().read(cx);
16106 let Some(buffer) = multibuffer.as_singleton() else {
16107 return;
16108 };
16109 let Some(start) = multibuffer.buffer_point_to_anchor(&buffer, range.start, cx) else {
16110 return;
16111 };
16112 let Some(end) = multibuffer.buffer_point_to_anchor(&buffer, range.end, cx) else {
16113 return;
16114 };
16115 self.change_selections(
16116 SelectionEffects::default().nav_history(true),
16117 window,
16118 cx,
16119 |s| s.select_anchor_ranges([start..end]),
16120 );
16121 }
16122
16123 pub fn go_to_diagnostic(
16124 &mut self,
16125 action: &GoToDiagnostic,
16126 window: &mut Window,
16127 cx: &mut Context<Self>,
16128 ) {
16129 if !self.diagnostics_enabled() {
16130 return;
16131 }
16132 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16133 self.go_to_diagnostic_impl(Direction::Next, action.severity, window, cx)
16134 }
16135
16136 pub fn go_to_prev_diagnostic(
16137 &mut self,
16138 action: &GoToPreviousDiagnostic,
16139 window: &mut Window,
16140 cx: &mut Context<Self>,
16141 ) {
16142 if !self.diagnostics_enabled() {
16143 return;
16144 }
16145 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16146 self.go_to_diagnostic_impl(Direction::Prev, action.severity, window, cx)
16147 }
16148
16149 pub fn go_to_diagnostic_impl(
16150 &mut self,
16151 direction: Direction,
16152 severity: GoToDiagnosticSeverityFilter,
16153 window: &mut Window,
16154 cx: &mut Context<Self>,
16155 ) {
16156 let buffer = self.buffer.read(cx).snapshot(cx);
16157 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
16158
16159 let mut active_group_id = None;
16160 if let ActiveDiagnostic::Group(active_group) = &self.active_diagnostics
16161 && active_group.active_range.start.to_offset(&buffer) == selection.start
16162 {
16163 active_group_id = Some(active_group.group_id);
16164 }
16165
16166 fn filtered<'a>(
16167 severity: GoToDiagnosticSeverityFilter,
16168 diagnostics: impl Iterator<Item = DiagnosticEntryRef<'a, usize>>,
16169 ) -> impl Iterator<Item = DiagnosticEntryRef<'a, usize>> {
16170 diagnostics
16171 .filter(move |entry| severity.matches(entry.diagnostic.severity))
16172 .filter(|entry| entry.range.start != entry.range.end)
16173 .filter(|entry| !entry.diagnostic.is_unnecessary)
16174 }
16175
16176 let before = filtered(
16177 severity,
16178 buffer
16179 .diagnostics_in_range(0..selection.start)
16180 .filter(|entry| entry.range.start <= selection.start),
16181 );
16182 let after = filtered(
16183 severity,
16184 buffer
16185 .diagnostics_in_range(selection.start..buffer.len())
16186 .filter(|entry| entry.range.start >= selection.start),
16187 );
16188
16189 let mut found: Option<DiagnosticEntryRef<usize>> = None;
16190 if direction == Direction::Prev {
16191 'outer: for prev_diagnostics in [before.collect::<Vec<_>>(), after.collect::<Vec<_>>()]
16192 {
16193 for diagnostic in prev_diagnostics.into_iter().rev() {
16194 if diagnostic.range.start != selection.start
16195 || active_group_id
16196 .is_some_and(|active| diagnostic.diagnostic.group_id < active)
16197 {
16198 found = Some(diagnostic);
16199 break 'outer;
16200 }
16201 }
16202 }
16203 } else {
16204 for diagnostic in after.chain(before) {
16205 if diagnostic.range.start != selection.start
16206 || active_group_id.is_some_and(|active| diagnostic.diagnostic.group_id > active)
16207 {
16208 found = Some(diagnostic);
16209 break;
16210 }
16211 }
16212 }
16213 let Some(next_diagnostic) = found else {
16214 return;
16215 };
16216
16217 let next_diagnostic_start = buffer.anchor_after(next_diagnostic.range.start);
16218 let Some(buffer_id) = buffer.buffer_id_for_anchor(next_diagnostic_start) else {
16219 return;
16220 };
16221 let snapshot = self.snapshot(window, cx);
16222 if snapshot.intersects_fold(next_diagnostic.range.start) {
16223 self.unfold_ranges(
16224 std::slice::from_ref(&next_diagnostic.range),
16225 true,
16226 false,
16227 cx,
16228 );
16229 }
16230 self.change_selections(Default::default(), window, cx, |s| {
16231 s.select_ranges(vec![
16232 next_diagnostic.range.start..next_diagnostic.range.start,
16233 ])
16234 });
16235 self.activate_diagnostics(buffer_id, next_diagnostic, window, cx);
16236 self.refresh_edit_prediction(false, true, window, cx);
16237 }
16238
16239 pub fn go_to_next_hunk(&mut self, _: &GoToHunk, window: &mut Window, cx: &mut Context<Self>) {
16240 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16241 let snapshot = self.snapshot(window, cx);
16242 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
16243 self.go_to_hunk_before_or_after_position(
16244 &snapshot,
16245 selection.head(),
16246 Direction::Next,
16247 window,
16248 cx,
16249 );
16250 }
16251
16252 pub fn go_to_hunk_before_or_after_position(
16253 &mut self,
16254 snapshot: &EditorSnapshot,
16255 position: Point,
16256 direction: Direction,
16257 window: &mut Window,
16258 cx: &mut Context<Editor>,
16259 ) {
16260 let row = if direction == Direction::Next {
16261 self.hunk_after_position(snapshot, position)
16262 .map(|hunk| hunk.row_range.start)
16263 } else {
16264 self.hunk_before_position(snapshot, position)
16265 };
16266
16267 if let Some(row) = row {
16268 let destination = Point::new(row.0, 0);
16269 let autoscroll = Autoscroll::center();
16270
16271 self.unfold_ranges(&[destination..destination], false, false, cx);
16272 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16273 s.select_ranges([destination..destination]);
16274 });
16275 }
16276 }
16277
16278 fn hunk_after_position(
16279 &mut self,
16280 snapshot: &EditorSnapshot,
16281 position: Point,
16282 ) -> Option<MultiBufferDiffHunk> {
16283 snapshot
16284 .buffer_snapshot()
16285 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
16286 .find(|hunk| hunk.row_range.start.0 > position.row)
16287 .or_else(|| {
16288 snapshot
16289 .buffer_snapshot()
16290 .diff_hunks_in_range(Point::zero()..position)
16291 .find(|hunk| hunk.row_range.end.0 < position.row)
16292 })
16293 }
16294
16295 fn go_to_prev_hunk(
16296 &mut self,
16297 _: &GoToPreviousHunk,
16298 window: &mut Window,
16299 cx: &mut Context<Self>,
16300 ) {
16301 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16302 let snapshot = self.snapshot(window, cx);
16303 let selection = self.selections.newest::<Point>(&snapshot.display_snapshot);
16304 self.go_to_hunk_before_or_after_position(
16305 &snapshot,
16306 selection.head(),
16307 Direction::Prev,
16308 window,
16309 cx,
16310 );
16311 }
16312
16313 fn hunk_before_position(
16314 &mut self,
16315 snapshot: &EditorSnapshot,
16316 position: Point,
16317 ) -> Option<MultiBufferRow> {
16318 snapshot
16319 .buffer_snapshot()
16320 .diff_hunk_before(position)
16321 .or_else(|| snapshot.buffer_snapshot().diff_hunk_before(Point::MAX))
16322 }
16323
16324 fn go_to_next_change(
16325 &mut self,
16326 _: &GoToNextChange,
16327 window: &mut Window,
16328 cx: &mut Context<Self>,
16329 ) {
16330 if let Some(selections) = self
16331 .change_list
16332 .next_change(1, Direction::Next)
16333 .map(|s| s.to_vec())
16334 {
16335 self.change_selections(Default::default(), window, cx, |s| {
16336 let map = s.display_snapshot();
16337 s.select_display_ranges(selections.iter().map(|a| {
16338 let point = a.to_display_point(&map);
16339 point..point
16340 }))
16341 })
16342 }
16343 }
16344
16345 fn go_to_previous_change(
16346 &mut self,
16347 _: &GoToPreviousChange,
16348 window: &mut Window,
16349 cx: &mut Context<Self>,
16350 ) {
16351 if let Some(selections) = self
16352 .change_list
16353 .next_change(1, Direction::Prev)
16354 .map(|s| s.to_vec())
16355 {
16356 self.change_selections(Default::default(), window, cx, |s| {
16357 let map = s.display_snapshot();
16358 s.select_display_ranges(selections.iter().map(|a| {
16359 let point = a.to_display_point(&map);
16360 point..point
16361 }))
16362 })
16363 }
16364 }
16365
16366 pub fn go_to_next_document_highlight(
16367 &mut self,
16368 _: &GoToNextDocumentHighlight,
16369 window: &mut Window,
16370 cx: &mut Context<Self>,
16371 ) {
16372 self.go_to_document_highlight_before_or_after_position(Direction::Next, window, cx);
16373 }
16374
16375 pub fn go_to_prev_document_highlight(
16376 &mut self,
16377 _: &GoToPreviousDocumentHighlight,
16378 window: &mut Window,
16379 cx: &mut Context<Self>,
16380 ) {
16381 self.go_to_document_highlight_before_or_after_position(Direction::Prev, window, cx);
16382 }
16383
16384 pub fn go_to_document_highlight_before_or_after_position(
16385 &mut self,
16386 direction: Direction,
16387 window: &mut Window,
16388 cx: &mut Context<Editor>,
16389 ) {
16390 self.hide_mouse_cursor(HideMouseCursorOrigin::MovementAction, cx);
16391 let snapshot = self.snapshot(window, cx);
16392 let buffer = &snapshot.buffer_snapshot();
16393 let position = self
16394 .selections
16395 .newest::<Point>(&snapshot.display_snapshot)
16396 .head();
16397 let anchor_position = buffer.anchor_after(position);
16398
16399 // Get all document highlights (both read and write)
16400 let mut all_highlights = Vec::new();
16401
16402 if let Some((_, read_highlights)) = self
16403 .background_highlights
16404 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
16405 {
16406 all_highlights.extend(read_highlights.iter());
16407 }
16408
16409 if let Some((_, write_highlights)) = self
16410 .background_highlights
16411 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
16412 {
16413 all_highlights.extend(write_highlights.iter());
16414 }
16415
16416 if all_highlights.is_empty() {
16417 return;
16418 }
16419
16420 // Sort highlights by position
16421 all_highlights.sort_by(|a, b| a.start.cmp(&b.start, buffer));
16422
16423 let target_highlight = match direction {
16424 Direction::Next => {
16425 // Find the first highlight after the current position
16426 all_highlights
16427 .iter()
16428 .find(|highlight| highlight.start.cmp(&anchor_position, buffer).is_gt())
16429 }
16430 Direction::Prev => {
16431 // Find the last highlight before the current position
16432 all_highlights
16433 .iter()
16434 .rev()
16435 .find(|highlight| highlight.end.cmp(&anchor_position, buffer).is_lt())
16436 }
16437 };
16438
16439 if let Some(highlight) = target_highlight {
16440 let destination = highlight.start.to_point(buffer);
16441 let autoscroll = Autoscroll::center();
16442
16443 self.unfold_ranges(&[destination..destination], false, false, cx);
16444 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
16445 s.select_ranges([destination..destination]);
16446 });
16447 }
16448 }
16449
16450 fn go_to_line<T: 'static>(
16451 &mut self,
16452 position: Anchor,
16453 highlight_color: Option<Hsla>,
16454 window: &mut Window,
16455 cx: &mut Context<Self>,
16456 ) {
16457 let snapshot = self.snapshot(window, cx).display_snapshot;
16458 let position = position.to_point(&snapshot.buffer_snapshot());
16459 let start = snapshot
16460 .buffer_snapshot()
16461 .clip_point(Point::new(position.row, 0), Bias::Left);
16462 let end = start + Point::new(1, 0);
16463 let start = snapshot.buffer_snapshot().anchor_before(start);
16464 let end = snapshot.buffer_snapshot().anchor_before(end);
16465
16466 self.highlight_rows::<T>(
16467 start..end,
16468 highlight_color
16469 .unwrap_or_else(|| cx.theme().colors().editor_highlighted_line_background),
16470 Default::default(),
16471 cx,
16472 );
16473
16474 if self.buffer.read(cx).is_singleton() {
16475 self.request_autoscroll(Autoscroll::center().for_anchor(start), cx);
16476 }
16477 }
16478
16479 pub fn go_to_definition(
16480 &mut self,
16481 _: &GoToDefinition,
16482 window: &mut Window,
16483 cx: &mut Context<Self>,
16484 ) -> Task<Result<Navigated>> {
16485 let definition =
16486 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, false, window, cx);
16487 let fallback_strategy = EditorSettings::get_global(cx).go_to_definition_fallback;
16488 cx.spawn_in(window, async move |editor, cx| {
16489 if definition.await? == Navigated::Yes {
16490 return Ok(Navigated::Yes);
16491 }
16492 match fallback_strategy {
16493 GoToDefinitionFallback::None => Ok(Navigated::No),
16494 GoToDefinitionFallback::FindAllReferences => {
16495 match editor.update_in(cx, |editor, window, cx| {
16496 editor.find_all_references(&FindAllReferences, window, cx)
16497 })? {
16498 Some(references) => references.await,
16499 None => Ok(Navigated::No),
16500 }
16501 }
16502 }
16503 })
16504 }
16505
16506 pub fn go_to_declaration(
16507 &mut self,
16508 _: &GoToDeclaration,
16509 window: &mut Window,
16510 cx: &mut Context<Self>,
16511 ) -> Task<Result<Navigated>> {
16512 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, false, window, cx)
16513 }
16514
16515 pub fn go_to_declaration_split(
16516 &mut self,
16517 _: &GoToDeclaration,
16518 window: &mut Window,
16519 cx: &mut Context<Self>,
16520 ) -> Task<Result<Navigated>> {
16521 self.go_to_definition_of_kind(GotoDefinitionKind::Declaration, true, window, cx)
16522 }
16523
16524 pub fn go_to_implementation(
16525 &mut self,
16526 _: &GoToImplementation,
16527 window: &mut Window,
16528 cx: &mut Context<Self>,
16529 ) -> Task<Result<Navigated>> {
16530 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, false, window, cx)
16531 }
16532
16533 pub fn go_to_implementation_split(
16534 &mut self,
16535 _: &GoToImplementationSplit,
16536 window: &mut Window,
16537 cx: &mut Context<Self>,
16538 ) -> Task<Result<Navigated>> {
16539 self.go_to_definition_of_kind(GotoDefinitionKind::Implementation, true, window, cx)
16540 }
16541
16542 pub fn go_to_type_definition(
16543 &mut self,
16544 _: &GoToTypeDefinition,
16545 window: &mut Window,
16546 cx: &mut Context<Self>,
16547 ) -> Task<Result<Navigated>> {
16548 self.go_to_definition_of_kind(GotoDefinitionKind::Type, false, window, cx)
16549 }
16550
16551 pub fn go_to_definition_split(
16552 &mut self,
16553 _: &GoToDefinitionSplit,
16554 window: &mut Window,
16555 cx: &mut Context<Self>,
16556 ) -> Task<Result<Navigated>> {
16557 self.go_to_definition_of_kind(GotoDefinitionKind::Symbol, true, window, cx)
16558 }
16559
16560 pub fn go_to_type_definition_split(
16561 &mut self,
16562 _: &GoToTypeDefinitionSplit,
16563 window: &mut Window,
16564 cx: &mut Context<Self>,
16565 ) -> Task<Result<Navigated>> {
16566 self.go_to_definition_of_kind(GotoDefinitionKind::Type, true, window, cx)
16567 }
16568
16569 fn go_to_definition_of_kind(
16570 &mut self,
16571 kind: GotoDefinitionKind,
16572 split: bool,
16573 window: &mut Window,
16574 cx: &mut Context<Self>,
16575 ) -> Task<Result<Navigated>> {
16576 let Some(provider) = self.semantics_provider.clone() else {
16577 return Task::ready(Ok(Navigated::No));
16578 };
16579 let head = self
16580 .selections
16581 .newest::<usize>(&self.display_snapshot(cx))
16582 .head();
16583 let buffer = self.buffer.read(cx);
16584 let Some((buffer, head)) = buffer.text_anchor_for_position(head, cx) else {
16585 return Task::ready(Ok(Navigated::No));
16586 };
16587 let Some(definitions) = provider.definitions(&buffer, head, kind, cx) else {
16588 return Task::ready(Ok(Navigated::No));
16589 };
16590
16591 cx.spawn_in(window, async move |editor, cx| {
16592 let Some(definitions) = definitions.await? else {
16593 return Ok(Navigated::No);
16594 };
16595 let navigated = editor
16596 .update_in(cx, |editor, window, cx| {
16597 editor.navigate_to_hover_links(
16598 Some(kind),
16599 definitions
16600 .into_iter()
16601 .filter(|location| {
16602 hover_links::exclude_link_to_position(&buffer, &head, location, cx)
16603 })
16604 .map(HoverLink::Text)
16605 .collect::<Vec<_>>(),
16606 split,
16607 window,
16608 cx,
16609 )
16610 })?
16611 .await?;
16612 anyhow::Ok(navigated)
16613 })
16614 }
16615
16616 pub fn open_url(&mut self, _: &OpenUrl, window: &mut Window, cx: &mut Context<Self>) {
16617 let selection = self.selections.newest_anchor();
16618 let head = selection.head();
16619 let tail = selection.tail();
16620
16621 let Some((buffer, start_position)) =
16622 self.buffer.read(cx).text_anchor_for_position(head, cx)
16623 else {
16624 return;
16625 };
16626
16627 let end_position = if head != tail {
16628 let Some((_, pos)) = self.buffer.read(cx).text_anchor_for_position(tail, cx) else {
16629 return;
16630 };
16631 Some(pos)
16632 } else {
16633 None
16634 };
16635
16636 let url_finder = cx.spawn_in(window, async move |_editor, cx| {
16637 let url = if let Some(end_pos) = end_position {
16638 find_url_from_range(&buffer, start_position..end_pos, cx.clone())
16639 } else {
16640 find_url(&buffer, start_position, cx.clone()).map(|(_, url)| url)
16641 };
16642
16643 if let Some(url) = url {
16644 cx.update(|window, cx| {
16645 if parse_zed_link(&url, cx).is_some() {
16646 window.dispatch_action(Box::new(zed_actions::OpenZedUrl { url }), cx);
16647 } else {
16648 cx.open_url(&url);
16649 }
16650 })?;
16651 }
16652
16653 anyhow::Ok(())
16654 });
16655
16656 url_finder.detach();
16657 }
16658
16659 pub fn open_selected_filename(
16660 &mut self,
16661 _: &OpenSelectedFilename,
16662 window: &mut Window,
16663 cx: &mut Context<Self>,
16664 ) {
16665 let Some(workspace) = self.workspace() else {
16666 return;
16667 };
16668
16669 let position = self.selections.newest_anchor().head();
16670
16671 let Some((buffer, buffer_position)) =
16672 self.buffer.read(cx).text_anchor_for_position(position, cx)
16673 else {
16674 return;
16675 };
16676
16677 let project = self.project.clone();
16678
16679 cx.spawn_in(window, async move |_, cx| {
16680 let result = find_file(&buffer, project, buffer_position, cx).await;
16681
16682 if let Some((_, path)) = result {
16683 workspace
16684 .update_in(cx, |workspace, window, cx| {
16685 workspace.open_resolved_path(path, window, cx)
16686 })?
16687 .await?;
16688 }
16689 anyhow::Ok(())
16690 })
16691 .detach();
16692 }
16693
16694 pub(crate) fn navigate_to_hover_links(
16695 &mut self,
16696 kind: Option<GotoDefinitionKind>,
16697 definitions: Vec<HoverLink>,
16698 split: bool,
16699 window: &mut Window,
16700 cx: &mut Context<Editor>,
16701 ) -> Task<Result<Navigated>> {
16702 // Separate out url and file links, we can only handle one of them at most or an arbitrary number of locations
16703 let mut first_url_or_file = None;
16704 let definitions: Vec<_> = definitions
16705 .into_iter()
16706 .filter_map(|def| match def {
16707 HoverLink::Text(link) => Some(Task::ready(anyhow::Ok(Some(link.target)))),
16708 HoverLink::InlayHint(lsp_location, server_id) => {
16709 let computation =
16710 self.compute_target_location(lsp_location, server_id, window, cx);
16711 Some(cx.background_spawn(computation))
16712 }
16713 HoverLink::Url(url) => {
16714 first_url_or_file = Some(Either::Left(url));
16715 None
16716 }
16717 HoverLink::File(path) => {
16718 first_url_or_file = Some(Either::Right(path));
16719 None
16720 }
16721 })
16722 .collect();
16723
16724 let workspace = self.workspace();
16725
16726 cx.spawn_in(window, async move |editor, cx| {
16727 let locations: Vec<Location> = future::join_all(definitions)
16728 .await
16729 .into_iter()
16730 .filter_map(|location| location.transpose())
16731 .collect::<Result<_>>()
16732 .context("location tasks")?;
16733 let mut locations = cx.update(|_, cx| {
16734 locations
16735 .into_iter()
16736 .map(|location| {
16737 let buffer = location.buffer.read(cx);
16738 (location.buffer, location.range.to_point(buffer))
16739 })
16740 .into_group_map()
16741 })?;
16742 let mut num_locations = 0;
16743 for ranges in locations.values_mut() {
16744 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
16745 ranges.dedup();
16746 num_locations += ranges.len();
16747 }
16748
16749 if num_locations > 1 {
16750 let Some(workspace) = workspace else {
16751 return Ok(Navigated::No);
16752 };
16753
16754 let tab_kind = match kind {
16755 Some(GotoDefinitionKind::Implementation) => "Implementations",
16756 Some(GotoDefinitionKind::Symbol) | None => "Definitions",
16757 Some(GotoDefinitionKind::Declaration) => "Declarations",
16758 Some(GotoDefinitionKind::Type) => "Types",
16759 };
16760 let title = editor
16761 .update_in(cx, |_, _, cx| {
16762 let target = locations
16763 .iter()
16764 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
16765 .map(|(buffer, location)| {
16766 buffer
16767 .read(cx)
16768 .text_for_range(location.clone())
16769 .collect::<String>()
16770 })
16771 .filter(|text| !text.contains('\n'))
16772 .unique()
16773 .take(3)
16774 .join(", ");
16775 if target.is_empty() {
16776 tab_kind.to_owned()
16777 } else {
16778 format!("{tab_kind} for {target}")
16779 }
16780 })
16781 .context("buffer title")?;
16782
16783 let opened = workspace
16784 .update_in(cx, |workspace, window, cx| {
16785 Self::open_locations_in_multibuffer(
16786 workspace,
16787 locations,
16788 title,
16789 split,
16790 MultibufferSelectionMode::First,
16791 window,
16792 cx,
16793 )
16794 })
16795 .is_ok();
16796
16797 anyhow::Ok(Navigated::from_bool(opened))
16798 } else if num_locations == 0 {
16799 // If there is one url or file, open it directly
16800 match first_url_or_file {
16801 Some(Either::Left(url)) => {
16802 cx.update(|_, cx| cx.open_url(&url))?;
16803 Ok(Navigated::Yes)
16804 }
16805 Some(Either::Right(path)) => {
16806 let Some(workspace) = workspace else {
16807 return Ok(Navigated::No);
16808 };
16809
16810 workspace
16811 .update_in(cx, |workspace, window, cx| {
16812 workspace.open_resolved_path(path, window, cx)
16813 })?
16814 .await?;
16815 Ok(Navigated::Yes)
16816 }
16817 None => Ok(Navigated::No),
16818 }
16819 } else {
16820 let Some(workspace) = workspace else {
16821 return Ok(Navigated::No);
16822 };
16823
16824 let (target_buffer, target_ranges) = locations.into_iter().next().unwrap();
16825 let target_range = target_ranges.first().unwrap().clone();
16826
16827 editor.update_in(cx, |editor, window, cx| {
16828 let range = target_range.to_point(target_buffer.read(cx));
16829 let range = editor.range_for_match(&range, false);
16830 let range = collapse_multiline_range(range);
16831
16832 if !split
16833 && Some(&target_buffer) == editor.buffer.read(cx).as_singleton().as_ref()
16834 {
16835 editor.go_to_singleton_buffer_range(range, window, cx);
16836 } else {
16837 let pane = workspace.read(cx).active_pane().clone();
16838 window.defer(cx, move |window, cx| {
16839 let target_editor: Entity<Self> =
16840 workspace.update(cx, |workspace, cx| {
16841 let pane = if split {
16842 workspace.adjacent_pane(window, cx)
16843 } else {
16844 workspace.active_pane().clone()
16845 };
16846
16847 workspace.open_project_item(
16848 pane,
16849 target_buffer.clone(),
16850 true,
16851 true,
16852 window,
16853 cx,
16854 )
16855 });
16856 target_editor.update(cx, |target_editor, cx| {
16857 // When selecting a definition in a different buffer, disable the nav history
16858 // to avoid creating a history entry at the previous cursor location.
16859 pane.update(cx, |pane, _| pane.disable_history());
16860 target_editor.go_to_singleton_buffer_range(range, window, cx);
16861 pane.update(cx, |pane, _| pane.enable_history());
16862 });
16863 });
16864 }
16865 Navigated::Yes
16866 })
16867 }
16868 })
16869 }
16870
16871 fn compute_target_location(
16872 &self,
16873 lsp_location: lsp::Location,
16874 server_id: LanguageServerId,
16875 window: &mut Window,
16876 cx: &mut Context<Self>,
16877 ) -> Task<anyhow::Result<Option<Location>>> {
16878 let Some(project) = self.project.clone() else {
16879 return Task::ready(Ok(None));
16880 };
16881
16882 cx.spawn_in(window, async move |editor, cx| {
16883 let location_task = editor.update(cx, |_, cx| {
16884 project.update(cx, |project, cx| {
16885 project.open_local_buffer_via_lsp(lsp_location.uri.clone(), server_id, cx)
16886 })
16887 })?;
16888 let location = Some({
16889 let target_buffer_handle = location_task.await.context("open local buffer")?;
16890 let range = target_buffer_handle.read_with(cx, |target_buffer, _| {
16891 let target_start = target_buffer
16892 .clip_point_utf16(point_from_lsp(lsp_location.range.start), Bias::Left);
16893 let target_end = target_buffer
16894 .clip_point_utf16(point_from_lsp(lsp_location.range.end), Bias::Left);
16895 target_buffer.anchor_after(target_start)
16896 ..target_buffer.anchor_before(target_end)
16897 })?;
16898 Location {
16899 buffer: target_buffer_handle,
16900 range,
16901 }
16902 });
16903 Ok(location)
16904 })
16905 }
16906
16907 fn go_to_next_reference(
16908 &mut self,
16909 _: &GoToNextReference,
16910 window: &mut Window,
16911 cx: &mut Context<Self>,
16912 ) {
16913 let task = self.go_to_reference_before_or_after_position(Direction::Next, 1, window, cx);
16914 if let Some(task) = task {
16915 task.detach();
16916 };
16917 }
16918
16919 fn go_to_prev_reference(
16920 &mut self,
16921 _: &GoToPreviousReference,
16922 window: &mut Window,
16923 cx: &mut Context<Self>,
16924 ) {
16925 let task = self.go_to_reference_before_or_after_position(Direction::Prev, 1, window, cx);
16926 if let Some(task) = task {
16927 task.detach();
16928 };
16929 }
16930
16931 pub fn go_to_reference_before_or_after_position(
16932 &mut self,
16933 direction: Direction,
16934 count: usize,
16935 window: &mut Window,
16936 cx: &mut Context<Self>,
16937 ) -> Option<Task<Result<()>>> {
16938 let selection = self.selections.newest_anchor();
16939 let head = selection.head();
16940
16941 let multi_buffer = self.buffer.read(cx);
16942
16943 let (buffer, text_head) = multi_buffer.text_anchor_for_position(head, cx)?;
16944 let workspace = self.workspace()?;
16945 let project = workspace.read(cx).project().clone();
16946 let references =
16947 project.update(cx, |project, cx| project.references(&buffer, text_head, cx));
16948 Some(cx.spawn_in(window, async move |editor, cx| -> Result<()> {
16949 let Some(locations) = references.await? else {
16950 return Ok(());
16951 };
16952
16953 if locations.is_empty() {
16954 // totally normal - the cursor may be on something which is not
16955 // a symbol (e.g. a keyword)
16956 log::info!("no references found under cursor");
16957 return Ok(());
16958 }
16959
16960 let multi_buffer = editor.read_with(cx, |editor, _| editor.buffer().clone())?;
16961
16962 let multi_buffer_snapshot =
16963 multi_buffer.read_with(cx, |multi_buffer, cx| multi_buffer.snapshot(cx))?;
16964
16965 let (locations, current_location_index) =
16966 multi_buffer.update(cx, |multi_buffer, cx| {
16967 let mut locations = locations
16968 .into_iter()
16969 .filter_map(|loc| {
16970 let start = multi_buffer.buffer_anchor_to_anchor(
16971 &loc.buffer,
16972 loc.range.start,
16973 cx,
16974 )?;
16975 let end = multi_buffer.buffer_anchor_to_anchor(
16976 &loc.buffer,
16977 loc.range.end,
16978 cx,
16979 )?;
16980 Some(start..end)
16981 })
16982 .collect::<Vec<_>>();
16983
16984 // There is an O(n) implementation, but given this list will be
16985 // small (usually <100 items), the extra O(log(n)) factor isn't
16986 // worth the (surprisingly large amount of) extra complexity.
16987 locations
16988 .sort_unstable_by(|l, r| l.start.cmp(&r.start, &multi_buffer_snapshot));
16989
16990 let head_offset = head.to_offset(&multi_buffer_snapshot);
16991
16992 let current_location_index = locations.iter().position(|loc| {
16993 loc.start.to_offset(&multi_buffer_snapshot) <= head_offset
16994 && loc.end.to_offset(&multi_buffer_snapshot) >= head_offset
16995 });
16996
16997 (locations, current_location_index)
16998 })?;
16999
17000 let Some(current_location_index) = current_location_index else {
17001 // This indicates something has gone wrong, because we already
17002 // handle the "no references" case above
17003 log::error!(
17004 "failed to find current reference under cursor. Total references: {}",
17005 locations.len()
17006 );
17007 return Ok(());
17008 };
17009
17010 let destination_location_index = match direction {
17011 Direction::Next => (current_location_index + count) % locations.len(),
17012 Direction::Prev => {
17013 (current_location_index + locations.len() - count % locations.len())
17014 % locations.len()
17015 }
17016 };
17017
17018 // TODO(cameron): is this needed?
17019 // the thinking is to avoid "jumping to the current location" (avoid
17020 // polluting "jumplist" in vim terms)
17021 if current_location_index == destination_location_index {
17022 return Ok(());
17023 }
17024
17025 let Range { start, end } = locations[destination_location_index];
17026
17027 editor.update_in(cx, |editor, window, cx| {
17028 let effects = SelectionEffects::default();
17029
17030 editor.unfold_ranges(&[start..end], false, false, cx);
17031 editor.change_selections(effects, window, cx, |s| {
17032 s.select_ranges([start..start]);
17033 });
17034 })?;
17035
17036 Ok(())
17037 }))
17038 }
17039
17040 pub fn find_all_references(
17041 &mut self,
17042 _: &FindAllReferences,
17043 window: &mut Window,
17044 cx: &mut Context<Self>,
17045 ) -> Option<Task<Result<Navigated>>> {
17046 let selection = self.selections.newest::<usize>(&self.display_snapshot(cx));
17047 let multi_buffer = self.buffer.read(cx);
17048 let head = selection.head();
17049
17050 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
17051 let head_anchor = multi_buffer_snapshot.anchor_at(
17052 head,
17053 if head < selection.tail() {
17054 Bias::Right
17055 } else {
17056 Bias::Left
17057 },
17058 );
17059
17060 match self
17061 .find_all_references_task_sources
17062 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17063 {
17064 Ok(_) => {
17065 log::info!(
17066 "Ignoring repeated FindAllReferences invocation with the position of already running task"
17067 );
17068 return None;
17069 }
17070 Err(i) => {
17071 self.find_all_references_task_sources.insert(i, head_anchor);
17072 }
17073 }
17074
17075 let (buffer, head) = multi_buffer.text_anchor_for_position(head, cx)?;
17076 let workspace = self.workspace()?;
17077 let project = workspace.read(cx).project().clone();
17078 let references = project.update(cx, |project, cx| project.references(&buffer, head, cx));
17079 Some(cx.spawn_in(window, async move |editor, cx| {
17080 let _cleanup = cx.on_drop(&editor, move |editor, _| {
17081 if let Ok(i) = editor
17082 .find_all_references_task_sources
17083 .binary_search_by(|anchor| anchor.cmp(&head_anchor, &multi_buffer_snapshot))
17084 {
17085 editor.find_all_references_task_sources.remove(i);
17086 }
17087 });
17088
17089 let Some(locations) = references.await? else {
17090 return anyhow::Ok(Navigated::No);
17091 };
17092 let mut locations = cx.update(|_, cx| {
17093 locations
17094 .into_iter()
17095 .map(|location| {
17096 let buffer = location.buffer.read(cx);
17097 (location.buffer, location.range.to_point(buffer))
17098 })
17099 .into_group_map()
17100 })?;
17101 if locations.is_empty() {
17102 return anyhow::Ok(Navigated::No);
17103 }
17104 for ranges in locations.values_mut() {
17105 ranges.sort_by_key(|range| (range.start, Reverse(range.end)));
17106 ranges.dedup();
17107 }
17108
17109 workspace.update_in(cx, |workspace, window, cx| {
17110 let target = locations
17111 .iter()
17112 .flat_map(|(k, v)| iter::repeat(k.clone()).zip(v))
17113 .map(|(buffer, location)| {
17114 buffer
17115 .read(cx)
17116 .text_for_range(location.clone())
17117 .collect::<String>()
17118 })
17119 .filter(|text| !text.contains('\n'))
17120 .unique()
17121 .take(3)
17122 .join(", ");
17123 let title = if target.is_empty() {
17124 "References".to_owned()
17125 } else {
17126 format!("References to {target}")
17127 };
17128 Self::open_locations_in_multibuffer(
17129 workspace,
17130 locations,
17131 title,
17132 false,
17133 MultibufferSelectionMode::First,
17134 window,
17135 cx,
17136 );
17137 Navigated::Yes
17138 })
17139 }))
17140 }
17141
17142 /// Opens a multibuffer with the given project locations in it
17143 pub fn open_locations_in_multibuffer(
17144 workspace: &mut Workspace,
17145 locations: std::collections::HashMap<Entity<Buffer>, Vec<Range<Point>>>,
17146 title: String,
17147 split: bool,
17148 multibuffer_selection_mode: MultibufferSelectionMode,
17149 window: &mut Window,
17150 cx: &mut Context<Workspace>,
17151 ) {
17152 if locations.is_empty() {
17153 log::error!("bug: open_locations_in_multibuffer called with empty list of locations");
17154 return;
17155 }
17156
17157 let capability = workspace.project().read(cx).capability();
17158 let mut ranges = <Vec<Range<Anchor>>>::new();
17159
17160 // a key to find existing multibuffer editors with the same set of locations
17161 // to prevent us from opening more and more multibuffer tabs for searches and the like
17162 let mut key = (title.clone(), vec![]);
17163 let excerpt_buffer = cx.new(|cx| {
17164 let key = &mut key.1;
17165 let mut multibuffer = MultiBuffer::new(capability);
17166 for (buffer, mut ranges_for_buffer) in locations {
17167 ranges_for_buffer.sort_by_key(|range| (range.start, Reverse(range.end)));
17168 key.push((buffer.read(cx).remote_id(), ranges_for_buffer.clone()));
17169 let (new_ranges, _) = multibuffer.set_excerpts_for_path(
17170 PathKey::for_buffer(&buffer, cx),
17171 buffer.clone(),
17172 ranges_for_buffer,
17173 multibuffer_context_lines(cx),
17174 cx,
17175 );
17176 ranges.extend(new_ranges)
17177 }
17178
17179 multibuffer.with_title(title)
17180 });
17181 let existing = workspace.active_pane().update(cx, |pane, cx| {
17182 pane.items()
17183 .filter_map(|item| item.downcast::<Editor>())
17184 .find(|editor| {
17185 editor
17186 .read(cx)
17187 .lookup_key
17188 .as_ref()
17189 .and_then(|it| {
17190 it.downcast_ref::<(String, Vec<(BufferId, Vec<Range<Point>>)>)>()
17191 })
17192 .is_some_and(|it| *it == key)
17193 })
17194 });
17195 let editor = existing.unwrap_or_else(|| {
17196 cx.new(|cx| {
17197 let mut editor = Editor::for_multibuffer(
17198 excerpt_buffer,
17199 Some(workspace.project().clone()),
17200 window,
17201 cx,
17202 );
17203 editor.lookup_key = Some(Box::new(key));
17204 editor
17205 })
17206 });
17207 editor.update(cx, |editor, cx| match multibuffer_selection_mode {
17208 MultibufferSelectionMode::First => {
17209 if let Some(first_range) = ranges.first() {
17210 editor.change_selections(
17211 SelectionEffects::no_scroll(),
17212 window,
17213 cx,
17214 |selections| {
17215 selections.clear_disjoint();
17216 selections.select_anchor_ranges(std::iter::once(first_range.clone()));
17217 },
17218 );
17219 }
17220 editor.highlight_background::<Self>(
17221 &ranges,
17222 |theme| theme.colors().editor_highlighted_line_background,
17223 cx,
17224 );
17225 }
17226 MultibufferSelectionMode::All => {
17227 editor.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
17228 selections.clear_disjoint();
17229 selections.select_anchor_ranges(ranges);
17230 });
17231 }
17232 });
17233
17234 let item = Box::new(editor);
17235 let item_id = item.item_id();
17236
17237 if split {
17238 let pane = workspace.adjacent_pane(window, cx);
17239 workspace.add_item(pane, item, None, true, true, window, cx);
17240 } else if PreviewTabsSettings::get_global(cx).enable_preview_from_code_navigation {
17241 let (preview_item_id, preview_item_idx) =
17242 workspace.active_pane().read_with(cx, |pane, _| {
17243 (pane.preview_item_id(), pane.preview_item_idx())
17244 });
17245
17246 workspace.add_item_to_active_pane(item, preview_item_idx, true, window, cx);
17247
17248 if let Some(preview_item_id) = preview_item_id {
17249 workspace.active_pane().update(cx, |pane, cx| {
17250 pane.remove_item(preview_item_id, false, false, window, cx);
17251 });
17252 }
17253 } else {
17254 workspace.add_item_to_active_pane(item, None, true, window, cx);
17255 }
17256 workspace.active_pane().update(cx, |pane, cx| {
17257 pane.set_preview_item_id(Some(item_id), cx);
17258 });
17259 }
17260
17261 pub fn rename(
17262 &mut self,
17263 _: &Rename,
17264 window: &mut Window,
17265 cx: &mut Context<Self>,
17266 ) -> Option<Task<Result<()>>> {
17267 use language::ToOffset as _;
17268
17269 let provider = self.semantics_provider.clone()?;
17270 let selection = self.selections.newest_anchor().clone();
17271 let (cursor_buffer, cursor_buffer_position) = self
17272 .buffer
17273 .read(cx)
17274 .text_anchor_for_position(selection.head(), cx)?;
17275 let (tail_buffer, cursor_buffer_position_end) = self
17276 .buffer
17277 .read(cx)
17278 .text_anchor_for_position(selection.tail(), cx)?;
17279 if tail_buffer != cursor_buffer {
17280 return None;
17281 }
17282
17283 let snapshot = cursor_buffer.read(cx).snapshot();
17284 let cursor_buffer_offset = cursor_buffer_position.to_offset(&snapshot);
17285 let cursor_buffer_offset_end = cursor_buffer_position_end.to_offset(&snapshot);
17286 let prepare_rename = provider
17287 .range_for_rename(&cursor_buffer, cursor_buffer_position, cx)
17288 .unwrap_or_else(|| Task::ready(Ok(None)));
17289 drop(snapshot);
17290
17291 Some(cx.spawn_in(window, async move |this, cx| {
17292 let rename_range = if let Some(range) = prepare_rename.await? {
17293 Some(range)
17294 } else {
17295 this.update(cx, |this, cx| {
17296 let buffer = this.buffer.read(cx).snapshot(cx);
17297 let mut buffer_highlights = this
17298 .document_highlights_for_position(selection.head(), &buffer)
17299 .filter(|highlight| {
17300 highlight.start.excerpt_id == selection.head().excerpt_id
17301 && highlight.end.excerpt_id == selection.head().excerpt_id
17302 });
17303 buffer_highlights
17304 .next()
17305 .map(|highlight| highlight.start.text_anchor..highlight.end.text_anchor)
17306 })?
17307 };
17308 if let Some(rename_range) = rename_range {
17309 this.update_in(cx, |this, window, cx| {
17310 let snapshot = cursor_buffer.read(cx).snapshot();
17311 let rename_buffer_range = rename_range.to_offset(&snapshot);
17312 let cursor_offset_in_rename_range =
17313 cursor_buffer_offset.saturating_sub(rename_buffer_range.start);
17314 let cursor_offset_in_rename_range_end =
17315 cursor_buffer_offset_end.saturating_sub(rename_buffer_range.start);
17316
17317 this.take_rename(false, window, cx);
17318 let buffer = this.buffer.read(cx).read(cx);
17319 let cursor_offset = selection.head().to_offset(&buffer);
17320 let rename_start = cursor_offset.saturating_sub(cursor_offset_in_rename_range);
17321 let rename_end = rename_start + rename_buffer_range.len();
17322 let range = buffer.anchor_before(rename_start)..buffer.anchor_after(rename_end);
17323 let mut old_highlight_id = None;
17324 let old_name: Arc<str> = buffer
17325 .chunks(rename_start..rename_end, true)
17326 .map(|chunk| {
17327 if old_highlight_id.is_none() {
17328 old_highlight_id = chunk.syntax_highlight_id;
17329 }
17330 chunk.text
17331 })
17332 .collect::<String>()
17333 .into();
17334
17335 drop(buffer);
17336
17337 // Position the selection in the rename editor so that it matches the current selection.
17338 this.show_local_selections = false;
17339 let rename_editor = cx.new(|cx| {
17340 let mut editor = Editor::single_line(window, cx);
17341 editor.buffer.update(cx, |buffer, cx| {
17342 buffer.edit([(0..0, old_name.clone())], None, cx)
17343 });
17344 let rename_selection_range = match cursor_offset_in_rename_range
17345 .cmp(&cursor_offset_in_rename_range_end)
17346 {
17347 Ordering::Equal => {
17348 editor.select_all(&SelectAll, window, cx);
17349 return editor;
17350 }
17351 Ordering::Less => {
17352 cursor_offset_in_rename_range..cursor_offset_in_rename_range_end
17353 }
17354 Ordering::Greater => {
17355 cursor_offset_in_rename_range_end..cursor_offset_in_rename_range
17356 }
17357 };
17358 if rename_selection_range.end > old_name.len() {
17359 editor.select_all(&SelectAll, window, cx);
17360 } else {
17361 editor.change_selections(Default::default(), window, cx, |s| {
17362 s.select_ranges([rename_selection_range]);
17363 });
17364 }
17365 editor
17366 });
17367 cx.subscribe(&rename_editor, |_, _, e: &EditorEvent, cx| {
17368 if e == &EditorEvent::Focused {
17369 cx.emit(EditorEvent::FocusedIn)
17370 }
17371 })
17372 .detach();
17373
17374 let write_highlights =
17375 this.clear_background_highlights::<DocumentHighlightWrite>(cx);
17376 let read_highlights =
17377 this.clear_background_highlights::<DocumentHighlightRead>(cx);
17378 let ranges = write_highlights
17379 .iter()
17380 .flat_map(|(_, ranges)| ranges.iter())
17381 .chain(read_highlights.iter().flat_map(|(_, ranges)| ranges.iter()))
17382 .cloned()
17383 .collect();
17384
17385 this.highlight_text::<Rename>(
17386 ranges,
17387 HighlightStyle {
17388 fade_out: Some(0.6),
17389 ..Default::default()
17390 },
17391 cx,
17392 );
17393 let rename_focus_handle = rename_editor.focus_handle(cx);
17394 window.focus(&rename_focus_handle);
17395 let block_id = this.insert_blocks(
17396 [BlockProperties {
17397 style: BlockStyle::Flex,
17398 placement: BlockPlacement::Below(range.start),
17399 height: Some(1),
17400 render: Arc::new({
17401 let rename_editor = rename_editor.clone();
17402 move |cx: &mut BlockContext| {
17403 let mut text_style = cx.editor_style.text.clone();
17404 if let Some(highlight_style) = old_highlight_id
17405 .and_then(|h| h.style(&cx.editor_style.syntax))
17406 {
17407 text_style = text_style.highlight(highlight_style);
17408 }
17409 div()
17410 .block_mouse_except_scroll()
17411 .pl(cx.anchor_x)
17412 .child(EditorElement::new(
17413 &rename_editor,
17414 EditorStyle {
17415 background: cx.theme().system().transparent,
17416 local_player: cx.editor_style.local_player,
17417 text: text_style,
17418 scrollbar_width: cx.editor_style.scrollbar_width,
17419 syntax: cx.editor_style.syntax.clone(),
17420 status: cx.editor_style.status.clone(),
17421 inlay_hints_style: HighlightStyle {
17422 font_weight: Some(FontWeight::BOLD),
17423 ..make_inlay_hints_style(cx.app)
17424 },
17425 edit_prediction_styles: make_suggestion_styles(
17426 cx.app,
17427 ),
17428 ..EditorStyle::default()
17429 },
17430 ))
17431 .into_any_element()
17432 }
17433 }),
17434 priority: 0,
17435 }],
17436 Some(Autoscroll::fit()),
17437 cx,
17438 )[0];
17439 this.pending_rename = Some(RenameState {
17440 range,
17441 old_name,
17442 editor: rename_editor,
17443 block_id,
17444 });
17445 })?;
17446 }
17447
17448 Ok(())
17449 }))
17450 }
17451
17452 pub fn confirm_rename(
17453 &mut self,
17454 _: &ConfirmRename,
17455 window: &mut Window,
17456 cx: &mut Context<Self>,
17457 ) -> Option<Task<Result<()>>> {
17458 let rename = self.take_rename(false, window, cx)?;
17459 let workspace = self.workspace()?.downgrade();
17460 let (buffer, start) = self
17461 .buffer
17462 .read(cx)
17463 .text_anchor_for_position(rename.range.start, cx)?;
17464 let (end_buffer, _) = self
17465 .buffer
17466 .read(cx)
17467 .text_anchor_for_position(rename.range.end, cx)?;
17468 if buffer != end_buffer {
17469 return None;
17470 }
17471
17472 let old_name = rename.old_name;
17473 let new_name = rename.editor.read(cx).text(cx);
17474
17475 let rename = self.semantics_provider.as_ref()?.perform_rename(
17476 &buffer,
17477 start,
17478 new_name.clone(),
17479 cx,
17480 )?;
17481
17482 Some(cx.spawn_in(window, async move |editor, cx| {
17483 let project_transaction = rename.await?;
17484 Self::open_project_transaction(
17485 &editor,
17486 workspace,
17487 project_transaction,
17488 format!("Rename: {} → {}", old_name, new_name),
17489 cx,
17490 )
17491 .await?;
17492
17493 editor.update(cx, |editor, cx| {
17494 editor.refresh_document_highlights(cx);
17495 })?;
17496 Ok(())
17497 }))
17498 }
17499
17500 fn take_rename(
17501 &mut self,
17502 moving_cursor: bool,
17503 window: &mut Window,
17504 cx: &mut Context<Self>,
17505 ) -> Option<RenameState> {
17506 let rename = self.pending_rename.take()?;
17507 if rename.editor.focus_handle(cx).is_focused(window) {
17508 window.focus(&self.focus_handle);
17509 }
17510
17511 self.remove_blocks(
17512 [rename.block_id].into_iter().collect(),
17513 Some(Autoscroll::fit()),
17514 cx,
17515 );
17516 self.clear_highlights::<Rename>(cx);
17517 self.show_local_selections = true;
17518
17519 if moving_cursor {
17520 let cursor_in_rename_editor = rename.editor.update(cx, |editor, cx| {
17521 editor
17522 .selections
17523 .newest::<usize>(&editor.display_snapshot(cx))
17524 .head()
17525 });
17526
17527 // Update the selection to match the position of the selection inside
17528 // the rename editor.
17529 let snapshot = self.buffer.read(cx).read(cx);
17530 let rename_range = rename.range.to_offset(&snapshot);
17531 let cursor_in_editor = snapshot
17532 .clip_offset(rename_range.start + cursor_in_rename_editor, Bias::Left)
17533 .min(rename_range.end);
17534 drop(snapshot);
17535
17536 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
17537 s.select_ranges(vec![cursor_in_editor..cursor_in_editor])
17538 });
17539 } else {
17540 self.refresh_document_highlights(cx);
17541 }
17542
17543 Some(rename)
17544 }
17545
17546 pub fn pending_rename(&self) -> Option<&RenameState> {
17547 self.pending_rename.as_ref()
17548 }
17549
17550 fn format(
17551 &mut self,
17552 _: &Format,
17553 window: &mut Window,
17554 cx: &mut Context<Self>,
17555 ) -> Option<Task<Result<()>>> {
17556 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17557
17558 let project = match &self.project {
17559 Some(project) => project.clone(),
17560 None => return None,
17561 };
17562
17563 Some(self.perform_format(
17564 project,
17565 FormatTrigger::Manual,
17566 FormatTarget::Buffers(self.buffer.read(cx).all_buffers()),
17567 window,
17568 cx,
17569 ))
17570 }
17571
17572 fn format_selections(
17573 &mut self,
17574 _: &FormatSelections,
17575 window: &mut Window,
17576 cx: &mut Context<Self>,
17577 ) -> Option<Task<Result<()>>> {
17578 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17579
17580 let project = match &self.project {
17581 Some(project) => project.clone(),
17582 None => return None,
17583 };
17584
17585 let ranges = self
17586 .selections
17587 .all_adjusted(&self.display_snapshot(cx))
17588 .into_iter()
17589 .map(|selection| selection.range())
17590 .collect_vec();
17591
17592 Some(self.perform_format(
17593 project,
17594 FormatTrigger::Manual,
17595 FormatTarget::Ranges(ranges),
17596 window,
17597 cx,
17598 ))
17599 }
17600
17601 fn perform_format(
17602 &mut self,
17603 project: Entity<Project>,
17604 trigger: FormatTrigger,
17605 target: FormatTarget,
17606 window: &mut Window,
17607 cx: &mut Context<Self>,
17608 ) -> Task<Result<()>> {
17609 let buffer = self.buffer.clone();
17610 let (buffers, target) = match target {
17611 FormatTarget::Buffers(buffers) => (buffers, LspFormatTarget::Buffers),
17612 FormatTarget::Ranges(selection_ranges) => {
17613 let multi_buffer = buffer.read(cx);
17614 let snapshot = multi_buffer.read(cx);
17615 let mut buffers = HashSet::default();
17616 let mut buffer_id_to_ranges: BTreeMap<BufferId, Vec<Range<text::Anchor>>> =
17617 BTreeMap::new();
17618 for selection_range in selection_ranges {
17619 for (buffer, buffer_range, _) in
17620 snapshot.range_to_buffer_ranges(selection_range)
17621 {
17622 let buffer_id = buffer.remote_id();
17623 let start = buffer.anchor_before(buffer_range.start);
17624 let end = buffer.anchor_after(buffer_range.end);
17625 buffers.insert(multi_buffer.buffer(buffer_id).unwrap());
17626 buffer_id_to_ranges
17627 .entry(buffer_id)
17628 .and_modify(|buffer_ranges| buffer_ranges.push(start..end))
17629 .or_insert_with(|| vec![start..end]);
17630 }
17631 }
17632 (buffers, LspFormatTarget::Ranges(buffer_id_to_ranges))
17633 }
17634 };
17635
17636 let transaction_id_prev = buffer.read(cx).last_transaction_id(cx);
17637 let selections_prev = transaction_id_prev
17638 .and_then(|transaction_id_prev| {
17639 // default to selections as they were after the last edit, if we have them,
17640 // instead of how they are now.
17641 // This will make it so that editing, moving somewhere else, formatting, then undoing the format
17642 // will take you back to where you made the last edit, instead of staying where you scrolled
17643 self.selection_history
17644 .transaction(transaction_id_prev)
17645 .map(|t| t.0.clone())
17646 })
17647 .unwrap_or_else(|| self.selections.disjoint_anchors_arc());
17648
17649 let mut timeout = cx.background_executor().timer(FORMAT_TIMEOUT).fuse();
17650 let format = project.update(cx, |project, cx| {
17651 project.format(buffers, target, true, trigger, cx)
17652 });
17653
17654 cx.spawn_in(window, async move |editor, cx| {
17655 let transaction = futures::select_biased! {
17656 transaction = format.log_err().fuse() => transaction,
17657 () = timeout => {
17658 log::warn!("timed out waiting for formatting");
17659 None
17660 }
17661 };
17662
17663 buffer
17664 .update(cx, |buffer, cx| {
17665 if let Some(transaction) = transaction
17666 && !buffer.is_singleton()
17667 {
17668 buffer.push_transaction(&transaction.0, cx);
17669 }
17670 cx.notify();
17671 })
17672 .ok();
17673
17674 if let Some(transaction_id_now) =
17675 buffer.read_with(cx, |b, cx| b.last_transaction_id(cx))?
17676 {
17677 let has_new_transaction = transaction_id_prev != Some(transaction_id_now);
17678 if has_new_transaction {
17679 _ = editor.update(cx, |editor, _| {
17680 editor
17681 .selection_history
17682 .insert_transaction(transaction_id_now, selections_prev);
17683 });
17684 }
17685 }
17686
17687 Ok(())
17688 })
17689 }
17690
17691 fn organize_imports(
17692 &mut self,
17693 _: &OrganizeImports,
17694 window: &mut Window,
17695 cx: &mut Context<Self>,
17696 ) -> Option<Task<Result<()>>> {
17697 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
17698 let project = match &self.project {
17699 Some(project) => project.clone(),
17700 None => return None,
17701 };
17702 Some(self.perform_code_action_kind(
17703 project,
17704 CodeActionKind::SOURCE_ORGANIZE_IMPORTS,
17705 window,
17706 cx,
17707 ))
17708 }
17709
17710 fn perform_code_action_kind(
17711 &mut self,
17712 project: Entity<Project>,
17713 kind: CodeActionKind,
17714 window: &mut Window,
17715 cx: &mut Context<Self>,
17716 ) -> Task<Result<()>> {
17717 let buffer = self.buffer.clone();
17718 let buffers = buffer.read(cx).all_buffers();
17719 let mut timeout = cx.background_executor().timer(CODE_ACTION_TIMEOUT).fuse();
17720 let apply_action = project.update(cx, |project, cx| {
17721 project.apply_code_action_kind(buffers, kind, true, cx)
17722 });
17723 cx.spawn_in(window, async move |_, cx| {
17724 let transaction = futures::select_biased! {
17725 () = timeout => {
17726 log::warn!("timed out waiting for executing code action");
17727 None
17728 }
17729 transaction = apply_action.log_err().fuse() => transaction,
17730 };
17731 buffer
17732 .update(cx, |buffer, cx| {
17733 // check if we need this
17734 if let Some(transaction) = transaction
17735 && !buffer.is_singleton()
17736 {
17737 buffer.push_transaction(&transaction.0, cx);
17738 }
17739 cx.notify();
17740 })
17741 .ok();
17742 Ok(())
17743 })
17744 }
17745
17746 pub fn restart_language_server(
17747 &mut self,
17748 _: &RestartLanguageServer,
17749 _: &mut Window,
17750 cx: &mut Context<Self>,
17751 ) {
17752 if let Some(project) = self.project.clone() {
17753 self.buffer.update(cx, |multi_buffer, cx| {
17754 project.update(cx, |project, cx| {
17755 project.restart_language_servers_for_buffers(
17756 multi_buffer.all_buffers().into_iter().collect(),
17757 HashSet::default(),
17758 cx,
17759 );
17760 });
17761 })
17762 }
17763 }
17764
17765 pub fn stop_language_server(
17766 &mut self,
17767 _: &StopLanguageServer,
17768 _: &mut Window,
17769 cx: &mut Context<Self>,
17770 ) {
17771 if let Some(project) = self.project.clone() {
17772 self.buffer.update(cx, |multi_buffer, cx| {
17773 project.update(cx, |project, cx| {
17774 project.stop_language_servers_for_buffers(
17775 multi_buffer.all_buffers().into_iter().collect(),
17776 HashSet::default(),
17777 cx,
17778 );
17779 });
17780 });
17781 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
17782 }
17783 }
17784
17785 fn cancel_language_server_work(
17786 workspace: &mut Workspace,
17787 _: &actions::CancelLanguageServerWork,
17788 _: &mut Window,
17789 cx: &mut Context<Workspace>,
17790 ) {
17791 let project = workspace.project();
17792 let buffers = workspace
17793 .active_item(cx)
17794 .and_then(|item| item.act_as::<Editor>(cx))
17795 .map_or(HashSet::default(), |editor| {
17796 editor.read(cx).buffer.read(cx).all_buffers()
17797 });
17798 project.update(cx, |project, cx| {
17799 project.cancel_language_server_work_for_buffers(buffers, cx);
17800 });
17801 }
17802
17803 fn show_character_palette(
17804 &mut self,
17805 _: &ShowCharacterPalette,
17806 window: &mut Window,
17807 _: &mut Context<Self>,
17808 ) {
17809 window.show_character_palette();
17810 }
17811
17812 fn refresh_active_diagnostics(&mut self, cx: &mut Context<Editor>) {
17813 if !self.diagnostics_enabled() {
17814 return;
17815 }
17816
17817 if let ActiveDiagnostic::Group(active_diagnostics) = &mut self.active_diagnostics {
17818 let buffer = self.buffer.read(cx).snapshot(cx);
17819 let primary_range_start = active_diagnostics.active_range.start.to_offset(&buffer);
17820 let primary_range_end = active_diagnostics.active_range.end.to_offset(&buffer);
17821 let is_valid = buffer
17822 .diagnostics_in_range::<usize>(primary_range_start..primary_range_end)
17823 .any(|entry| {
17824 entry.diagnostic.is_primary
17825 && !entry.range.is_empty()
17826 && entry.range.start == primary_range_start
17827 && entry.diagnostic.message == active_diagnostics.active_message
17828 });
17829
17830 if !is_valid {
17831 self.dismiss_diagnostics(cx);
17832 }
17833 }
17834 }
17835
17836 pub fn active_diagnostic_group(&self) -> Option<&ActiveDiagnosticGroup> {
17837 match &self.active_diagnostics {
17838 ActiveDiagnostic::Group(group) => Some(group),
17839 _ => None,
17840 }
17841 }
17842
17843 pub fn set_all_diagnostics_active(&mut self, cx: &mut Context<Self>) {
17844 if !self.diagnostics_enabled() {
17845 return;
17846 }
17847 self.dismiss_diagnostics(cx);
17848 self.active_diagnostics = ActiveDiagnostic::All;
17849 }
17850
17851 fn activate_diagnostics(
17852 &mut self,
17853 buffer_id: BufferId,
17854 diagnostic: DiagnosticEntryRef<'_, usize>,
17855 window: &mut Window,
17856 cx: &mut Context<Self>,
17857 ) {
17858 if !self.diagnostics_enabled() || matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17859 return;
17860 }
17861 self.dismiss_diagnostics(cx);
17862 let snapshot = self.snapshot(window, cx);
17863 let buffer = self.buffer.read(cx).snapshot(cx);
17864 let Some(renderer) = GlobalDiagnosticRenderer::global(cx) else {
17865 return;
17866 };
17867
17868 let diagnostic_group = buffer
17869 .diagnostic_group(buffer_id, diagnostic.diagnostic.group_id)
17870 .collect::<Vec<_>>();
17871
17872 let blocks =
17873 renderer.render_group(diagnostic_group, buffer_id, snapshot, cx.weak_entity(), cx);
17874
17875 let blocks = self.display_map.update(cx, |display_map, cx| {
17876 display_map.insert_blocks(blocks, cx).into_iter().collect()
17877 });
17878 self.active_diagnostics = ActiveDiagnostic::Group(ActiveDiagnosticGroup {
17879 active_range: buffer.anchor_before(diagnostic.range.start)
17880 ..buffer.anchor_after(diagnostic.range.end),
17881 active_message: diagnostic.diagnostic.message.clone(),
17882 group_id: diagnostic.diagnostic.group_id,
17883 blocks,
17884 });
17885 cx.notify();
17886 }
17887
17888 fn dismiss_diagnostics(&mut self, cx: &mut Context<Self>) {
17889 if matches!(self.active_diagnostics, ActiveDiagnostic::All) {
17890 return;
17891 };
17892
17893 let prev = mem::replace(&mut self.active_diagnostics, ActiveDiagnostic::None);
17894 if let ActiveDiagnostic::Group(group) = prev {
17895 self.display_map.update(cx, |display_map, cx| {
17896 display_map.remove_blocks(group.blocks, cx);
17897 });
17898 cx.notify();
17899 }
17900 }
17901
17902 /// Disable inline diagnostics rendering for this editor.
17903 pub fn disable_inline_diagnostics(&mut self) {
17904 self.inline_diagnostics_enabled = false;
17905 self.inline_diagnostics_update = Task::ready(());
17906 self.inline_diagnostics.clear();
17907 }
17908
17909 pub fn disable_diagnostics(&mut self, cx: &mut Context<Self>) {
17910 self.diagnostics_enabled = false;
17911 self.dismiss_diagnostics(cx);
17912 self.inline_diagnostics_update = Task::ready(());
17913 self.inline_diagnostics.clear();
17914 }
17915
17916 pub fn disable_word_completions(&mut self) {
17917 self.word_completions_enabled = false;
17918 }
17919
17920 pub fn diagnostics_enabled(&self) -> bool {
17921 self.diagnostics_enabled && self.mode.is_full()
17922 }
17923
17924 pub fn inline_diagnostics_enabled(&self) -> bool {
17925 self.inline_diagnostics_enabled && self.diagnostics_enabled()
17926 }
17927
17928 pub fn show_inline_diagnostics(&self) -> bool {
17929 self.show_inline_diagnostics
17930 }
17931
17932 pub fn toggle_inline_diagnostics(
17933 &mut self,
17934 _: &ToggleInlineDiagnostics,
17935 window: &mut Window,
17936 cx: &mut Context<Editor>,
17937 ) {
17938 self.show_inline_diagnostics = !self.show_inline_diagnostics;
17939 self.refresh_inline_diagnostics(false, window, cx);
17940 }
17941
17942 pub fn set_max_diagnostics_severity(&mut self, severity: DiagnosticSeverity, cx: &mut App) {
17943 self.diagnostics_max_severity = severity;
17944 self.display_map.update(cx, |display_map, _| {
17945 display_map.diagnostics_max_severity = self.diagnostics_max_severity;
17946 });
17947 }
17948
17949 pub fn toggle_diagnostics(
17950 &mut self,
17951 _: &ToggleDiagnostics,
17952 window: &mut Window,
17953 cx: &mut Context<Editor>,
17954 ) {
17955 if !self.diagnostics_enabled() {
17956 return;
17957 }
17958
17959 let new_severity = if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17960 EditorSettings::get_global(cx)
17961 .diagnostics_max_severity
17962 .filter(|severity| severity != &DiagnosticSeverity::Off)
17963 .unwrap_or(DiagnosticSeverity::Hint)
17964 } else {
17965 DiagnosticSeverity::Off
17966 };
17967 self.set_max_diagnostics_severity(new_severity, cx);
17968 if self.diagnostics_max_severity == DiagnosticSeverity::Off {
17969 self.active_diagnostics = ActiveDiagnostic::None;
17970 self.inline_diagnostics_update = Task::ready(());
17971 self.inline_diagnostics.clear();
17972 } else {
17973 self.refresh_inline_diagnostics(false, window, cx);
17974 }
17975
17976 cx.notify();
17977 }
17978
17979 pub fn toggle_minimap(
17980 &mut self,
17981 _: &ToggleMinimap,
17982 window: &mut Window,
17983 cx: &mut Context<Editor>,
17984 ) {
17985 if self.supports_minimap(cx) {
17986 self.set_minimap_visibility(self.minimap_visibility.toggle_visibility(), window, cx);
17987 }
17988 }
17989
17990 fn refresh_inline_diagnostics(
17991 &mut self,
17992 debounce: bool,
17993 window: &mut Window,
17994 cx: &mut Context<Self>,
17995 ) {
17996 let max_severity = ProjectSettings::get_global(cx)
17997 .diagnostics
17998 .inline
17999 .max_severity
18000 .unwrap_or(self.diagnostics_max_severity);
18001
18002 if !self.inline_diagnostics_enabled()
18003 || !self.diagnostics_enabled()
18004 || !self.show_inline_diagnostics
18005 || max_severity == DiagnosticSeverity::Off
18006 {
18007 self.inline_diagnostics_update = Task::ready(());
18008 self.inline_diagnostics.clear();
18009 return;
18010 }
18011
18012 let debounce_ms = ProjectSettings::get_global(cx)
18013 .diagnostics
18014 .inline
18015 .update_debounce_ms;
18016 let debounce = if debounce && debounce_ms > 0 {
18017 Some(Duration::from_millis(debounce_ms))
18018 } else {
18019 None
18020 };
18021 self.inline_diagnostics_update = cx.spawn_in(window, async move |editor, cx| {
18022 if let Some(debounce) = debounce {
18023 cx.background_executor().timer(debounce).await;
18024 }
18025 let Some(snapshot) = editor.upgrade().and_then(|editor| {
18026 editor
18027 .update(cx, |editor, cx| editor.buffer().read(cx).snapshot(cx))
18028 .ok()
18029 }) else {
18030 return;
18031 };
18032
18033 let new_inline_diagnostics = cx
18034 .background_spawn(async move {
18035 let mut inline_diagnostics = Vec::<(Anchor, InlineDiagnostic)>::new();
18036 for diagnostic_entry in snapshot.diagnostics_in_range(0..snapshot.len()) {
18037 let message = diagnostic_entry
18038 .diagnostic
18039 .message
18040 .split_once('\n')
18041 .map(|(line, _)| line)
18042 .map(SharedString::new)
18043 .unwrap_or_else(|| {
18044 SharedString::new(&*diagnostic_entry.diagnostic.message)
18045 });
18046 let start_anchor = snapshot.anchor_before(diagnostic_entry.range.start);
18047 let (Ok(i) | Err(i)) = inline_diagnostics
18048 .binary_search_by(|(probe, _)| probe.cmp(&start_anchor, &snapshot));
18049 inline_diagnostics.insert(
18050 i,
18051 (
18052 start_anchor,
18053 InlineDiagnostic {
18054 message,
18055 group_id: diagnostic_entry.diagnostic.group_id,
18056 start: diagnostic_entry.range.start.to_point(&snapshot),
18057 is_primary: diagnostic_entry.diagnostic.is_primary,
18058 severity: diagnostic_entry.diagnostic.severity,
18059 },
18060 ),
18061 );
18062 }
18063 inline_diagnostics
18064 })
18065 .await;
18066
18067 editor
18068 .update(cx, |editor, cx| {
18069 editor.inline_diagnostics = new_inline_diagnostics;
18070 cx.notify();
18071 })
18072 .ok();
18073 });
18074 }
18075
18076 fn pull_diagnostics(
18077 &mut self,
18078 buffer_id: Option<BufferId>,
18079 window: &Window,
18080 cx: &mut Context<Self>,
18081 ) -> Option<()> {
18082 if self.ignore_lsp_data() || !self.diagnostics_enabled() {
18083 return None;
18084 }
18085 let pull_diagnostics_settings = ProjectSettings::get_global(cx)
18086 .diagnostics
18087 .lsp_pull_diagnostics;
18088 if !pull_diagnostics_settings.enabled {
18089 return None;
18090 }
18091 let project = self.project()?.downgrade();
18092 let debounce = Duration::from_millis(pull_diagnostics_settings.debounce_ms);
18093 let mut buffers = self.buffer.read(cx).all_buffers();
18094 buffers.retain(|buffer| {
18095 let buffer_id_to_retain = buffer.read(cx).remote_id();
18096 buffer_id.is_none_or(|buffer_id| buffer_id == buffer_id_to_retain)
18097 && self.registered_buffers.contains_key(&buffer_id_to_retain)
18098 });
18099 if buffers.is_empty() {
18100 self.pull_diagnostics_task = Task::ready(());
18101 return None;
18102 }
18103
18104 self.pull_diagnostics_task = cx.spawn_in(window, async move |editor, cx| {
18105 cx.background_executor().timer(debounce).await;
18106
18107 let Ok(mut pull_diagnostics_tasks) = cx.update(|_, cx| {
18108 buffers
18109 .into_iter()
18110 .filter_map(|buffer| {
18111 project
18112 .update(cx, |project, cx| {
18113 project.lsp_store().update(cx, |lsp_store, cx| {
18114 lsp_store.pull_diagnostics_for_buffer(buffer, cx)
18115 })
18116 })
18117 .ok()
18118 })
18119 .collect::<FuturesUnordered<_>>()
18120 }) else {
18121 return;
18122 };
18123
18124 while let Some(pull_task) = pull_diagnostics_tasks.next().await {
18125 match pull_task {
18126 Ok(()) => {
18127 if editor
18128 .update_in(cx, |editor, window, cx| {
18129 editor.update_diagnostics_state(window, cx);
18130 })
18131 .is_err()
18132 {
18133 return;
18134 }
18135 }
18136 Err(e) => log::error!("Failed to update project diagnostics: {e:#}"),
18137 }
18138 }
18139 });
18140
18141 Some(())
18142 }
18143
18144 pub fn set_selections_from_remote(
18145 &mut self,
18146 selections: Vec<Selection<Anchor>>,
18147 pending_selection: Option<Selection<Anchor>>,
18148 window: &mut Window,
18149 cx: &mut Context<Self>,
18150 ) {
18151 let old_cursor_position = self.selections.newest_anchor().head();
18152 self.selections
18153 .change_with(&self.display_snapshot(cx), |s| {
18154 s.select_anchors(selections);
18155 if let Some(pending_selection) = pending_selection {
18156 s.set_pending(pending_selection, SelectMode::Character);
18157 } else {
18158 s.clear_pending();
18159 }
18160 });
18161 self.selections_did_change(
18162 false,
18163 &old_cursor_position,
18164 SelectionEffects::default(),
18165 window,
18166 cx,
18167 );
18168 }
18169
18170 pub fn transact(
18171 &mut self,
18172 window: &mut Window,
18173 cx: &mut Context<Self>,
18174 update: impl FnOnce(&mut Self, &mut Window, &mut Context<Self>),
18175 ) -> Option<TransactionId> {
18176 self.with_selection_effects_deferred(window, cx, |this, window, cx| {
18177 this.start_transaction_at(Instant::now(), window, cx);
18178 update(this, window, cx);
18179 this.end_transaction_at(Instant::now(), cx)
18180 })
18181 }
18182
18183 pub fn start_transaction_at(
18184 &mut self,
18185 now: Instant,
18186 window: &mut Window,
18187 cx: &mut Context<Self>,
18188 ) -> Option<TransactionId> {
18189 self.end_selection(window, cx);
18190 if let Some(tx_id) = self
18191 .buffer
18192 .update(cx, |buffer, cx| buffer.start_transaction_at(now, cx))
18193 {
18194 self.selection_history
18195 .insert_transaction(tx_id, self.selections.disjoint_anchors_arc());
18196 cx.emit(EditorEvent::TransactionBegun {
18197 transaction_id: tx_id,
18198 });
18199 Some(tx_id)
18200 } else {
18201 None
18202 }
18203 }
18204
18205 pub fn end_transaction_at(
18206 &mut self,
18207 now: Instant,
18208 cx: &mut Context<Self>,
18209 ) -> Option<TransactionId> {
18210 if let Some(transaction_id) = self
18211 .buffer
18212 .update(cx, |buffer, cx| buffer.end_transaction_at(now, cx))
18213 {
18214 if let Some((_, end_selections)) =
18215 self.selection_history.transaction_mut(transaction_id)
18216 {
18217 *end_selections = Some(self.selections.disjoint_anchors_arc());
18218 } else {
18219 log::error!("unexpectedly ended a transaction that wasn't started by this editor");
18220 }
18221
18222 cx.emit(EditorEvent::Edited { transaction_id });
18223 Some(transaction_id)
18224 } else {
18225 None
18226 }
18227 }
18228
18229 pub fn modify_transaction_selection_history(
18230 &mut self,
18231 transaction_id: TransactionId,
18232 modify: impl FnOnce(&mut (Arc<[Selection<Anchor>]>, Option<Arc<[Selection<Anchor>]>>)),
18233 ) -> bool {
18234 self.selection_history
18235 .transaction_mut(transaction_id)
18236 .map(modify)
18237 .is_some()
18238 }
18239
18240 pub fn set_mark(&mut self, _: &actions::SetMark, window: &mut Window, cx: &mut Context<Self>) {
18241 if self.selection_mark_mode {
18242 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18243 s.move_with(|_, sel| {
18244 sel.collapse_to(sel.head(), SelectionGoal::None);
18245 });
18246 })
18247 }
18248 self.selection_mark_mode = true;
18249 cx.notify();
18250 }
18251
18252 pub fn swap_selection_ends(
18253 &mut self,
18254 _: &actions::SwapSelectionEnds,
18255 window: &mut Window,
18256 cx: &mut Context<Self>,
18257 ) {
18258 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
18259 s.move_with(|_, sel| {
18260 if sel.start != sel.end {
18261 sel.reversed = !sel.reversed
18262 }
18263 });
18264 });
18265 self.request_autoscroll(Autoscroll::newest(), cx);
18266 cx.notify();
18267 }
18268
18269 pub fn toggle_focus(
18270 workspace: &mut Workspace,
18271 _: &actions::ToggleFocus,
18272 window: &mut Window,
18273 cx: &mut Context<Workspace>,
18274 ) {
18275 let Some(item) = workspace.recent_active_item_by_type::<Self>(cx) else {
18276 return;
18277 };
18278 workspace.activate_item(&item, true, true, window, cx);
18279 }
18280
18281 pub fn toggle_fold(
18282 &mut self,
18283 _: &actions::ToggleFold,
18284 window: &mut Window,
18285 cx: &mut Context<Self>,
18286 ) {
18287 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18288 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18289 let selection = self.selections.newest::<Point>(&display_map);
18290
18291 let range = if selection.is_empty() {
18292 let point = selection.head().to_display_point(&display_map);
18293 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18294 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18295 .to_point(&display_map);
18296 start..end
18297 } else {
18298 selection.range()
18299 };
18300 if display_map.folds_in_range(range).next().is_some() {
18301 self.unfold_lines(&Default::default(), window, cx)
18302 } else {
18303 self.fold(&Default::default(), window, cx)
18304 }
18305 } else {
18306 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18307 let buffer_ids: HashSet<_> = self
18308 .selections
18309 .disjoint_anchor_ranges()
18310 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18311 .collect();
18312
18313 let should_unfold = buffer_ids
18314 .iter()
18315 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18316
18317 for buffer_id in buffer_ids {
18318 if should_unfold {
18319 self.unfold_buffer(buffer_id, cx);
18320 } else {
18321 self.fold_buffer(buffer_id, cx);
18322 }
18323 }
18324 }
18325 }
18326
18327 pub fn toggle_fold_recursive(
18328 &mut self,
18329 _: &actions::ToggleFoldRecursive,
18330 window: &mut Window,
18331 cx: &mut Context<Self>,
18332 ) {
18333 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
18334
18335 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18336 let range = if selection.is_empty() {
18337 let point = selection.head().to_display_point(&display_map);
18338 let start = DisplayPoint::new(point.row(), 0).to_point(&display_map);
18339 let end = DisplayPoint::new(point.row(), display_map.line_len(point.row()))
18340 .to_point(&display_map);
18341 start..end
18342 } else {
18343 selection.range()
18344 };
18345 if display_map.folds_in_range(range).next().is_some() {
18346 self.unfold_recursive(&Default::default(), window, cx)
18347 } else {
18348 self.fold_recursive(&Default::default(), window, cx)
18349 }
18350 }
18351
18352 pub fn fold(&mut self, _: &actions::Fold, window: &mut Window, cx: &mut Context<Self>) {
18353 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18354 let mut to_fold = Vec::new();
18355 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18356 let selections = self.selections.all_adjusted(&display_map);
18357
18358 for selection in selections {
18359 let range = selection.range().sorted();
18360 let buffer_start_row = range.start.row;
18361
18362 if range.start.row != range.end.row {
18363 let mut found = false;
18364 let mut row = range.start.row;
18365 while row <= range.end.row {
18366 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18367 {
18368 found = true;
18369 row = crease.range().end.row + 1;
18370 to_fold.push(crease);
18371 } else {
18372 row += 1
18373 }
18374 }
18375 if found {
18376 continue;
18377 }
18378 }
18379
18380 for row in (0..=range.start.row).rev() {
18381 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row))
18382 && crease.range().end.row >= buffer_start_row
18383 {
18384 to_fold.push(crease);
18385 if row <= range.start.row {
18386 break;
18387 }
18388 }
18389 }
18390 }
18391
18392 self.fold_creases(to_fold, true, window, cx);
18393 } else {
18394 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18395 let buffer_ids = self
18396 .selections
18397 .disjoint_anchor_ranges()
18398 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18399 .collect::<HashSet<_>>();
18400 for buffer_id in buffer_ids {
18401 self.fold_buffer(buffer_id, cx);
18402 }
18403 }
18404 }
18405
18406 pub fn toggle_fold_all(
18407 &mut self,
18408 _: &actions::ToggleFoldAll,
18409 window: &mut Window,
18410 cx: &mut Context<Self>,
18411 ) {
18412 if self.buffer.read(cx).is_singleton() {
18413 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18414 let has_folds = display_map
18415 .folds_in_range(0..display_map.buffer_snapshot().len())
18416 .next()
18417 .is_some();
18418
18419 if has_folds {
18420 self.unfold_all(&actions::UnfoldAll, window, cx);
18421 } else {
18422 self.fold_all(&actions::FoldAll, window, cx);
18423 }
18424 } else {
18425 let buffer_ids = self.buffer.read(cx).excerpt_buffer_ids();
18426 let should_unfold = buffer_ids
18427 .iter()
18428 .any(|buffer_id| self.is_buffer_folded(*buffer_id, cx));
18429
18430 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18431 editor
18432 .update_in(cx, |editor, _, cx| {
18433 for buffer_id in buffer_ids {
18434 if should_unfold {
18435 editor.unfold_buffer(buffer_id, cx);
18436 } else {
18437 editor.fold_buffer(buffer_id, cx);
18438 }
18439 }
18440 })
18441 .ok();
18442 });
18443 }
18444 }
18445
18446 fn fold_at_level(
18447 &mut self,
18448 fold_at: &FoldAtLevel,
18449 window: &mut Window,
18450 cx: &mut Context<Self>,
18451 ) {
18452 if !self.buffer.read(cx).is_singleton() {
18453 return;
18454 }
18455
18456 let fold_at_level = fold_at.0;
18457 let snapshot = self.buffer.read(cx).snapshot(cx);
18458 let mut to_fold = Vec::new();
18459 let mut stack = vec![(0, snapshot.max_row().0, 1)];
18460
18461 let row_ranges_to_keep: Vec<Range<u32>> = self
18462 .selections
18463 .all::<Point>(&self.display_snapshot(cx))
18464 .into_iter()
18465 .map(|sel| sel.start.row..sel.end.row)
18466 .collect();
18467
18468 while let Some((mut start_row, end_row, current_level)) = stack.pop() {
18469 while start_row < end_row {
18470 match self
18471 .snapshot(window, cx)
18472 .crease_for_buffer_row(MultiBufferRow(start_row))
18473 {
18474 Some(crease) => {
18475 let nested_start_row = crease.range().start.row + 1;
18476 let nested_end_row = crease.range().end.row;
18477
18478 if current_level < fold_at_level {
18479 stack.push((nested_start_row, nested_end_row, current_level + 1));
18480 } else if current_level == fold_at_level {
18481 // Fold iff there is no selection completely contained within the fold region
18482 if !row_ranges_to_keep.iter().any(|selection| {
18483 selection.end >= nested_start_row
18484 && selection.start <= nested_end_row
18485 }) {
18486 to_fold.push(crease);
18487 }
18488 }
18489
18490 start_row = nested_end_row + 1;
18491 }
18492 None => start_row += 1,
18493 }
18494 }
18495 }
18496
18497 self.fold_creases(to_fold, true, window, cx);
18498 }
18499
18500 pub fn fold_at_level_1(
18501 &mut self,
18502 _: &actions::FoldAtLevel1,
18503 window: &mut Window,
18504 cx: &mut Context<Self>,
18505 ) {
18506 self.fold_at_level(&actions::FoldAtLevel(1), window, cx);
18507 }
18508
18509 pub fn fold_at_level_2(
18510 &mut self,
18511 _: &actions::FoldAtLevel2,
18512 window: &mut Window,
18513 cx: &mut Context<Self>,
18514 ) {
18515 self.fold_at_level(&actions::FoldAtLevel(2), window, cx);
18516 }
18517
18518 pub fn fold_at_level_3(
18519 &mut self,
18520 _: &actions::FoldAtLevel3,
18521 window: &mut Window,
18522 cx: &mut Context<Self>,
18523 ) {
18524 self.fold_at_level(&actions::FoldAtLevel(3), window, cx);
18525 }
18526
18527 pub fn fold_at_level_4(
18528 &mut self,
18529 _: &actions::FoldAtLevel4,
18530 window: &mut Window,
18531 cx: &mut Context<Self>,
18532 ) {
18533 self.fold_at_level(&actions::FoldAtLevel(4), window, cx);
18534 }
18535
18536 pub fn fold_at_level_5(
18537 &mut self,
18538 _: &actions::FoldAtLevel5,
18539 window: &mut Window,
18540 cx: &mut Context<Self>,
18541 ) {
18542 self.fold_at_level(&actions::FoldAtLevel(5), window, cx);
18543 }
18544
18545 pub fn fold_at_level_6(
18546 &mut self,
18547 _: &actions::FoldAtLevel6,
18548 window: &mut Window,
18549 cx: &mut Context<Self>,
18550 ) {
18551 self.fold_at_level(&actions::FoldAtLevel(6), window, cx);
18552 }
18553
18554 pub fn fold_at_level_7(
18555 &mut self,
18556 _: &actions::FoldAtLevel7,
18557 window: &mut Window,
18558 cx: &mut Context<Self>,
18559 ) {
18560 self.fold_at_level(&actions::FoldAtLevel(7), window, cx);
18561 }
18562
18563 pub fn fold_at_level_8(
18564 &mut self,
18565 _: &actions::FoldAtLevel8,
18566 window: &mut Window,
18567 cx: &mut Context<Self>,
18568 ) {
18569 self.fold_at_level(&actions::FoldAtLevel(8), window, cx);
18570 }
18571
18572 pub fn fold_at_level_9(
18573 &mut self,
18574 _: &actions::FoldAtLevel9,
18575 window: &mut Window,
18576 cx: &mut Context<Self>,
18577 ) {
18578 self.fold_at_level(&actions::FoldAtLevel(9), window, cx);
18579 }
18580
18581 pub fn fold_all(&mut self, _: &actions::FoldAll, window: &mut Window, cx: &mut Context<Self>) {
18582 if self.buffer.read(cx).is_singleton() {
18583 let mut fold_ranges = Vec::new();
18584 let snapshot = self.buffer.read(cx).snapshot(cx);
18585
18586 for row in 0..snapshot.max_row().0 {
18587 if let Some(foldable_range) = self
18588 .snapshot(window, cx)
18589 .crease_for_buffer_row(MultiBufferRow(row))
18590 {
18591 fold_ranges.push(foldable_range);
18592 }
18593 }
18594
18595 self.fold_creases(fold_ranges, true, window, cx);
18596 } else {
18597 self.toggle_fold_multiple_buffers = cx.spawn_in(window, async move |editor, cx| {
18598 editor
18599 .update_in(cx, |editor, _, cx| {
18600 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18601 editor.fold_buffer(buffer_id, cx);
18602 }
18603 })
18604 .ok();
18605 });
18606 }
18607 }
18608
18609 pub fn fold_function_bodies(
18610 &mut self,
18611 _: &actions::FoldFunctionBodies,
18612 window: &mut Window,
18613 cx: &mut Context<Self>,
18614 ) {
18615 let snapshot = self.buffer.read(cx).snapshot(cx);
18616
18617 let ranges = snapshot
18618 .text_object_ranges(0..snapshot.len(), TreeSitterOptions::default())
18619 .filter_map(|(range, obj)| (obj == TextObject::InsideFunction).then_some(range))
18620 .collect::<Vec<_>>();
18621
18622 let creases = ranges
18623 .into_iter()
18624 .map(|range| Crease::simple(range, self.display_map.read(cx).fold_placeholder.clone()))
18625 .collect();
18626
18627 self.fold_creases(creases, true, window, cx);
18628 }
18629
18630 pub fn fold_recursive(
18631 &mut self,
18632 _: &actions::FoldRecursive,
18633 window: &mut Window,
18634 cx: &mut Context<Self>,
18635 ) {
18636 let mut to_fold = Vec::new();
18637 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18638 let selections = self.selections.all_adjusted(&display_map);
18639
18640 for selection in selections {
18641 let range = selection.range().sorted();
18642 let buffer_start_row = range.start.row;
18643
18644 if range.start.row != range.end.row {
18645 let mut found = false;
18646 for row in range.start.row..=range.end.row {
18647 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18648 found = true;
18649 to_fold.push(crease);
18650 }
18651 }
18652 if found {
18653 continue;
18654 }
18655 }
18656
18657 for row in (0..=range.start.row).rev() {
18658 if let Some(crease) = display_map.crease_for_buffer_row(MultiBufferRow(row)) {
18659 if crease.range().end.row >= buffer_start_row {
18660 to_fold.push(crease);
18661 } else {
18662 break;
18663 }
18664 }
18665 }
18666 }
18667
18668 self.fold_creases(to_fold, true, window, cx);
18669 }
18670
18671 pub fn fold_at(
18672 &mut self,
18673 buffer_row: MultiBufferRow,
18674 window: &mut Window,
18675 cx: &mut Context<Self>,
18676 ) {
18677 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18678
18679 if let Some(crease) = display_map.crease_for_buffer_row(buffer_row) {
18680 let autoscroll = self
18681 .selections
18682 .all::<Point>(&display_map)
18683 .iter()
18684 .any(|selection| crease.range().overlaps(&selection.range()));
18685
18686 self.fold_creases(vec![crease], autoscroll, window, cx);
18687 }
18688 }
18689
18690 pub fn unfold_lines(&mut self, _: &UnfoldLines, _window: &mut Window, cx: &mut Context<Self>) {
18691 if self.buffer_kind(cx) == ItemBufferKind::Singleton {
18692 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18693 let buffer = display_map.buffer_snapshot();
18694 let selections = self.selections.all::<Point>(&display_map);
18695 let ranges = selections
18696 .iter()
18697 .map(|s| {
18698 let range = s.display_range(&display_map).sorted();
18699 let mut start = range.start.to_point(&display_map);
18700 let mut end = range.end.to_point(&display_map);
18701 start.column = 0;
18702 end.column = buffer.line_len(MultiBufferRow(end.row));
18703 start..end
18704 })
18705 .collect::<Vec<_>>();
18706
18707 self.unfold_ranges(&ranges, true, true, cx);
18708 } else {
18709 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
18710 let buffer_ids = self
18711 .selections
18712 .disjoint_anchor_ranges()
18713 .flat_map(|range| multi_buffer_snapshot.buffer_ids_for_range(range))
18714 .collect::<HashSet<_>>();
18715 for buffer_id in buffer_ids {
18716 self.unfold_buffer(buffer_id, cx);
18717 }
18718 }
18719 }
18720
18721 pub fn unfold_recursive(
18722 &mut self,
18723 _: &UnfoldRecursive,
18724 _window: &mut Window,
18725 cx: &mut Context<Self>,
18726 ) {
18727 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18728 let selections = self.selections.all::<Point>(&display_map);
18729 let ranges = selections
18730 .iter()
18731 .map(|s| {
18732 let mut range = s.display_range(&display_map).sorted();
18733 *range.start.column_mut() = 0;
18734 *range.end.column_mut() = display_map.line_len(range.end.row());
18735 let start = range.start.to_point(&display_map);
18736 let end = range.end.to_point(&display_map);
18737 start..end
18738 })
18739 .collect::<Vec<_>>();
18740
18741 self.unfold_ranges(&ranges, true, true, cx);
18742 }
18743
18744 pub fn unfold_at(
18745 &mut self,
18746 buffer_row: MultiBufferRow,
18747 _window: &mut Window,
18748 cx: &mut Context<Self>,
18749 ) {
18750 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18751
18752 let intersection_range = Point::new(buffer_row.0, 0)
18753 ..Point::new(
18754 buffer_row.0,
18755 display_map.buffer_snapshot().line_len(buffer_row),
18756 );
18757
18758 let autoscroll = self
18759 .selections
18760 .all::<Point>(&display_map)
18761 .iter()
18762 .any(|selection| RangeExt::overlaps(&selection.range(), &intersection_range));
18763
18764 self.unfold_ranges(&[intersection_range], true, autoscroll, cx);
18765 }
18766
18767 pub fn unfold_all(
18768 &mut self,
18769 _: &actions::UnfoldAll,
18770 _window: &mut Window,
18771 cx: &mut Context<Self>,
18772 ) {
18773 if self.buffer.read(cx).is_singleton() {
18774 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18775 self.unfold_ranges(&[0..display_map.buffer_snapshot().len()], true, true, cx);
18776 } else {
18777 self.toggle_fold_multiple_buffers = cx.spawn(async move |editor, cx| {
18778 editor
18779 .update(cx, |editor, cx| {
18780 for buffer_id in editor.buffer.read(cx).excerpt_buffer_ids() {
18781 editor.unfold_buffer(buffer_id, cx);
18782 }
18783 })
18784 .ok();
18785 });
18786 }
18787 }
18788
18789 pub fn fold_selected_ranges(
18790 &mut self,
18791 _: &FoldSelectedRanges,
18792 window: &mut Window,
18793 cx: &mut Context<Self>,
18794 ) {
18795 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18796 let selections = self.selections.all_adjusted(&display_map);
18797 let ranges = selections
18798 .into_iter()
18799 .map(|s| Crease::simple(s.range(), display_map.fold_placeholder.clone()))
18800 .collect::<Vec<_>>();
18801 self.fold_creases(ranges, true, window, cx);
18802 }
18803
18804 pub fn fold_ranges<T: ToOffset + Clone>(
18805 &mut self,
18806 ranges: Vec<Range<T>>,
18807 auto_scroll: bool,
18808 window: &mut Window,
18809 cx: &mut Context<Self>,
18810 ) {
18811 let display_map = self.display_map.update(cx, |map, cx| map.snapshot(cx));
18812 let ranges = ranges
18813 .into_iter()
18814 .map(|r| Crease::simple(r, display_map.fold_placeholder.clone()))
18815 .collect::<Vec<_>>();
18816 self.fold_creases(ranges, auto_scroll, window, cx);
18817 }
18818
18819 pub fn fold_creases<T: ToOffset + Clone>(
18820 &mut self,
18821 creases: Vec<Crease<T>>,
18822 auto_scroll: bool,
18823 _window: &mut Window,
18824 cx: &mut Context<Self>,
18825 ) {
18826 if creases.is_empty() {
18827 return;
18828 }
18829
18830 self.display_map.update(cx, |map, cx| map.fold(creases, cx));
18831
18832 if auto_scroll {
18833 self.request_autoscroll(Autoscroll::fit(), cx);
18834 }
18835
18836 cx.notify();
18837
18838 self.scrollbar_marker_state.dirty = true;
18839 self.folds_did_change(cx);
18840 }
18841
18842 /// Removes any folds whose ranges intersect any of the given ranges.
18843 pub fn unfold_ranges<T: ToOffset + Clone>(
18844 &mut self,
18845 ranges: &[Range<T>],
18846 inclusive: bool,
18847 auto_scroll: bool,
18848 cx: &mut Context<Self>,
18849 ) {
18850 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18851 map.unfold_intersecting(ranges.iter().cloned(), inclusive, cx)
18852 });
18853 self.folds_did_change(cx);
18854 }
18855
18856 pub fn fold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18857 if self.buffer().read(cx).is_singleton() || self.is_buffer_folded(buffer_id, cx) {
18858 return;
18859 }
18860 let folded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18861 self.display_map.update(cx, |display_map, cx| {
18862 display_map.fold_buffers([buffer_id], cx)
18863 });
18864 cx.emit(EditorEvent::BufferFoldToggled {
18865 ids: folded_excerpts.iter().map(|&(id, _)| id).collect(),
18866 folded: true,
18867 });
18868 cx.notify();
18869 }
18870
18871 pub fn unfold_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18872 if self.buffer().read(cx).is_singleton() || !self.is_buffer_folded(buffer_id, cx) {
18873 return;
18874 }
18875 let unfolded_excerpts = self.buffer().read(cx).excerpts_for_buffer(buffer_id, cx);
18876 self.display_map.update(cx, |display_map, cx| {
18877 display_map.unfold_buffers([buffer_id], cx);
18878 });
18879 cx.emit(EditorEvent::BufferFoldToggled {
18880 ids: unfolded_excerpts.iter().map(|&(id, _)| id).collect(),
18881 folded: false,
18882 });
18883 cx.notify();
18884 }
18885
18886 pub fn is_buffer_folded(&self, buffer: BufferId, cx: &App) -> bool {
18887 self.display_map.read(cx).is_buffer_folded(buffer)
18888 }
18889
18890 pub fn folded_buffers<'a>(&self, cx: &'a App) -> &'a HashSet<BufferId> {
18891 self.display_map.read(cx).folded_buffers()
18892 }
18893
18894 pub fn disable_header_for_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
18895 self.display_map.update(cx, |display_map, cx| {
18896 display_map.disable_header_for_buffer(buffer_id, cx);
18897 });
18898 cx.notify();
18899 }
18900
18901 /// Removes any folds with the given ranges.
18902 pub fn remove_folds_with_type<T: ToOffset + Clone>(
18903 &mut self,
18904 ranges: &[Range<T>],
18905 type_id: TypeId,
18906 auto_scroll: bool,
18907 cx: &mut Context<Self>,
18908 ) {
18909 self.remove_folds_with(ranges, auto_scroll, cx, |map, cx| {
18910 map.remove_folds_with_type(ranges.iter().cloned(), type_id, cx)
18911 });
18912 self.folds_did_change(cx);
18913 }
18914
18915 fn remove_folds_with<T: ToOffset + Clone>(
18916 &mut self,
18917 ranges: &[Range<T>],
18918 auto_scroll: bool,
18919 cx: &mut Context<Self>,
18920 update: impl FnOnce(&mut DisplayMap, &mut Context<DisplayMap>),
18921 ) {
18922 if ranges.is_empty() {
18923 return;
18924 }
18925
18926 let mut buffers_affected = HashSet::default();
18927 let multi_buffer = self.buffer().read(cx);
18928 for range in ranges {
18929 if let Some((_, buffer, _)) = multi_buffer.excerpt_containing(range.start.clone(), cx) {
18930 buffers_affected.insert(buffer.read(cx).remote_id());
18931 };
18932 }
18933
18934 self.display_map.update(cx, update);
18935
18936 if auto_scroll {
18937 self.request_autoscroll(Autoscroll::fit(), cx);
18938 }
18939
18940 cx.notify();
18941 self.scrollbar_marker_state.dirty = true;
18942 self.active_indent_guides_state.dirty = true;
18943 }
18944
18945 pub fn update_renderer_widths(
18946 &mut self,
18947 widths: impl IntoIterator<Item = (ChunkRendererId, Pixels)>,
18948 cx: &mut Context<Self>,
18949 ) -> bool {
18950 self.display_map
18951 .update(cx, |map, cx| map.update_fold_widths(widths, cx))
18952 }
18953
18954 pub fn default_fold_placeholder(&self, cx: &App) -> FoldPlaceholder {
18955 self.display_map.read(cx).fold_placeholder.clone()
18956 }
18957
18958 pub fn set_expand_all_diff_hunks(&mut self, cx: &mut App) {
18959 self.buffer.update(cx, |buffer, cx| {
18960 buffer.set_all_diff_hunks_expanded(cx);
18961 });
18962 }
18963
18964 pub fn expand_all_diff_hunks(
18965 &mut self,
18966 _: &ExpandAllDiffHunks,
18967 _window: &mut Window,
18968 cx: &mut Context<Self>,
18969 ) {
18970 self.buffer.update(cx, |buffer, cx| {
18971 buffer.expand_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18972 });
18973 }
18974
18975 pub fn collapse_all_diff_hunks(
18976 &mut self,
18977 _: &CollapseAllDiffHunks,
18978 _window: &mut Window,
18979 cx: &mut Context<Self>,
18980 ) {
18981 self.buffer.update(cx, |buffer, cx| {
18982 buffer.collapse_diff_hunks(vec![Anchor::min()..Anchor::max()], cx)
18983 });
18984 }
18985
18986 pub fn toggle_selected_diff_hunks(
18987 &mut self,
18988 _: &ToggleSelectedDiffHunks,
18989 _window: &mut Window,
18990 cx: &mut Context<Self>,
18991 ) {
18992 let ranges: Vec<_> = self
18993 .selections
18994 .disjoint_anchors()
18995 .iter()
18996 .map(|s| s.range())
18997 .collect();
18998 self.toggle_diff_hunks_in_ranges(ranges, cx);
18999 }
19000
19001 pub fn diff_hunks_in_ranges<'a>(
19002 &'a self,
19003 ranges: &'a [Range<Anchor>],
19004 buffer: &'a MultiBufferSnapshot,
19005 ) -> impl 'a + Iterator<Item = MultiBufferDiffHunk> {
19006 ranges.iter().flat_map(move |range| {
19007 let end_excerpt_id = range.end.excerpt_id;
19008 let range = range.to_point(buffer);
19009 let mut peek_end = range.end;
19010 if range.end.row < buffer.max_row().0 {
19011 peek_end = Point::new(range.end.row + 1, 0);
19012 }
19013 buffer
19014 .diff_hunks_in_range(range.start..peek_end)
19015 .filter(move |hunk| hunk.excerpt_id.cmp(&end_excerpt_id, buffer).is_le())
19016 })
19017 }
19018
19019 pub fn has_stageable_diff_hunks_in_ranges(
19020 &self,
19021 ranges: &[Range<Anchor>],
19022 snapshot: &MultiBufferSnapshot,
19023 ) -> bool {
19024 let mut hunks = self.diff_hunks_in_ranges(ranges, snapshot);
19025 hunks.any(|hunk| hunk.status().has_secondary_hunk())
19026 }
19027
19028 pub fn toggle_staged_selected_diff_hunks(
19029 &mut self,
19030 _: &::git::ToggleStaged,
19031 _: &mut Window,
19032 cx: &mut Context<Self>,
19033 ) {
19034 let snapshot = self.buffer.read(cx).snapshot(cx);
19035 let ranges: Vec<_> = self
19036 .selections
19037 .disjoint_anchors()
19038 .iter()
19039 .map(|s| s.range())
19040 .collect();
19041 let stage = self.has_stageable_diff_hunks_in_ranges(&ranges, &snapshot);
19042 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19043 }
19044
19045 pub fn set_render_diff_hunk_controls(
19046 &mut self,
19047 render_diff_hunk_controls: RenderDiffHunkControlsFn,
19048 cx: &mut Context<Self>,
19049 ) {
19050 self.render_diff_hunk_controls = render_diff_hunk_controls;
19051 cx.notify();
19052 }
19053
19054 pub fn stage_and_next(
19055 &mut self,
19056 _: &::git::StageAndNext,
19057 window: &mut Window,
19058 cx: &mut Context<Self>,
19059 ) {
19060 self.do_stage_or_unstage_and_next(true, window, cx);
19061 }
19062
19063 pub fn unstage_and_next(
19064 &mut self,
19065 _: &::git::UnstageAndNext,
19066 window: &mut Window,
19067 cx: &mut Context<Self>,
19068 ) {
19069 self.do_stage_or_unstage_and_next(false, window, cx);
19070 }
19071
19072 pub fn stage_or_unstage_diff_hunks(
19073 &mut self,
19074 stage: bool,
19075 ranges: Vec<Range<Anchor>>,
19076 cx: &mut Context<Self>,
19077 ) {
19078 let task = self.save_buffers_for_ranges_if_needed(&ranges, cx);
19079 cx.spawn(async move |this, cx| {
19080 task.await?;
19081 this.update(cx, |this, cx| {
19082 let snapshot = this.buffer.read(cx).snapshot(cx);
19083 let chunk_by = this
19084 .diff_hunks_in_ranges(&ranges, &snapshot)
19085 .chunk_by(|hunk| hunk.buffer_id);
19086 for (buffer_id, hunks) in &chunk_by {
19087 this.do_stage_or_unstage(stage, buffer_id, hunks, cx);
19088 }
19089 })
19090 })
19091 .detach_and_log_err(cx);
19092 }
19093
19094 fn save_buffers_for_ranges_if_needed(
19095 &mut self,
19096 ranges: &[Range<Anchor>],
19097 cx: &mut Context<Editor>,
19098 ) -> Task<Result<()>> {
19099 let multibuffer = self.buffer.read(cx);
19100 let snapshot = multibuffer.read(cx);
19101 let buffer_ids: HashSet<_> = ranges
19102 .iter()
19103 .flat_map(|range| snapshot.buffer_ids_for_range(range.clone()))
19104 .collect();
19105 drop(snapshot);
19106
19107 let mut buffers = HashSet::default();
19108 for buffer_id in buffer_ids {
19109 if let Some(buffer_entity) = multibuffer.buffer(buffer_id) {
19110 let buffer = buffer_entity.read(cx);
19111 if buffer.file().is_some_and(|file| file.disk_state().exists()) && buffer.is_dirty()
19112 {
19113 buffers.insert(buffer_entity);
19114 }
19115 }
19116 }
19117
19118 if let Some(project) = &self.project {
19119 project.update(cx, |project, cx| project.save_buffers(buffers, cx))
19120 } else {
19121 Task::ready(Ok(()))
19122 }
19123 }
19124
19125 fn do_stage_or_unstage_and_next(
19126 &mut self,
19127 stage: bool,
19128 window: &mut Window,
19129 cx: &mut Context<Self>,
19130 ) {
19131 let ranges = self.selections.disjoint_anchor_ranges().collect::<Vec<_>>();
19132
19133 if ranges.iter().any(|range| range.start != range.end) {
19134 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19135 return;
19136 }
19137
19138 self.stage_or_unstage_diff_hunks(stage, ranges, cx);
19139 let snapshot = self.snapshot(window, cx);
19140 let position = self
19141 .selections
19142 .newest::<Point>(&snapshot.display_snapshot)
19143 .head();
19144 let mut row = snapshot
19145 .buffer_snapshot()
19146 .diff_hunks_in_range(position..snapshot.buffer_snapshot().max_point())
19147 .find(|hunk| hunk.row_range.start.0 > position.row)
19148 .map(|hunk| hunk.row_range.start);
19149
19150 let all_diff_hunks_expanded = self.buffer().read(cx).all_diff_hunks_expanded();
19151 // Outside of the project diff editor, wrap around to the beginning.
19152 if !all_diff_hunks_expanded {
19153 row = row.or_else(|| {
19154 snapshot
19155 .buffer_snapshot()
19156 .diff_hunks_in_range(Point::zero()..position)
19157 .find(|hunk| hunk.row_range.end.0 < position.row)
19158 .map(|hunk| hunk.row_range.start)
19159 });
19160 }
19161
19162 if let Some(row) = row {
19163 let destination = Point::new(row.0, 0);
19164 let autoscroll = Autoscroll::center();
19165
19166 self.unfold_ranges(&[destination..destination], false, false, cx);
19167 self.change_selections(SelectionEffects::scroll(autoscroll), window, cx, |s| {
19168 s.select_ranges([destination..destination]);
19169 });
19170 }
19171 }
19172
19173 fn do_stage_or_unstage(
19174 &self,
19175 stage: bool,
19176 buffer_id: BufferId,
19177 hunks: impl Iterator<Item = MultiBufferDiffHunk>,
19178 cx: &mut App,
19179 ) -> Option<()> {
19180 let project = self.project()?;
19181 let buffer = project.read(cx).buffer_for_id(buffer_id, cx)?;
19182 let diff = self.buffer.read(cx).diff_for(buffer_id)?;
19183 let buffer_snapshot = buffer.read(cx).snapshot();
19184 let file_exists = buffer_snapshot
19185 .file()
19186 .is_some_and(|file| file.disk_state().exists());
19187 diff.update(cx, |diff, cx| {
19188 diff.stage_or_unstage_hunks(
19189 stage,
19190 &hunks
19191 .map(|hunk| buffer_diff::DiffHunk {
19192 buffer_range: hunk.buffer_range,
19193 diff_base_byte_range: hunk.diff_base_byte_range,
19194 secondary_status: hunk.secondary_status,
19195 range: Point::zero()..Point::zero(), // unused
19196 })
19197 .collect::<Vec<_>>(),
19198 &buffer_snapshot,
19199 file_exists,
19200 cx,
19201 )
19202 });
19203 None
19204 }
19205
19206 pub fn expand_selected_diff_hunks(&mut self, cx: &mut Context<Self>) {
19207 let ranges: Vec<_> = self
19208 .selections
19209 .disjoint_anchors()
19210 .iter()
19211 .map(|s| s.range())
19212 .collect();
19213 self.buffer
19214 .update(cx, |buffer, cx| buffer.expand_diff_hunks(ranges, cx))
19215 }
19216
19217 pub fn clear_expanded_diff_hunks(&mut self, cx: &mut Context<Self>) -> bool {
19218 self.buffer.update(cx, |buffer, cx| {
19219 let ranges = vec![Anchor::min()..Anchor::max()];
19220 if !buffer.all_diff_hunks_expanded()
19221 && buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx)
19222 {
19223 buffer.collapse_diff_hunks(ranges, cx);
19224 true
19225 } else {
19226 false
19227 }
19228 })
19229 }
19230
19231 fn toggle_diff_hunks_in_ranges(
19232 &mut self,
19233 ranges: Vec<Range<Anchor>>,
19234 cx: &mut Context<Editor>,
19235 ) {
19236 self.buffer.update(cx, |buffer, cx| {
19237 let expand = !buffer.has_expanded_diff_hunks_in_ranges(&ranges, cx);
19238 buffer.expand_or_collapse_diff_hunks(ranges, expand, cx);
19239 })
19240 }
19241
19242 fn toggle_single_diff_hunk(&mut self, range: Range<Anchor>, cx: &mut Context<Self>) {
19243 self.buffer.update(cx, |buffer, cx| {
19244 let snapshot = buffer.snapshot(cx);
19245 let excerpt_id = range.end.excerpt_id;
19246 let point_range = range.to_point(&snapshot);
19247 let expand = !buffer.single_hunk_is_expanded(range, cx);
19248 buffer.expand_or_collapse_diff_hunks_inner([(point_range, excerpt_id)], expand, cx);
19249 })
19250 }
19251
19252 pub(crate) fn apply_all_diff_hunks(
19253 &mut self,
19254 _: &ApplyAllDiffHunks,
19255 window: &mut Window,
19256 cx: &mut Context<Self>,
19257 ) {
19258 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19259
19260 let buffers = self.buffer.read(cx).all_buffers();
19261 for branch_buffer in buffers {
19262 branch_buffer.update(cx, |branch_buffer, cx| {
19263 branch_buffer.merge_into_base(Vec::new(), cx);
19264 });
19265 }
19266
19267 if let Some(project) = self.project.clone() {
19268 self.save(
19269 SaveOptions {
19270 format: true,
19271 autosave: false,
19272 },
19273 project,
19274 window,
19275 cx,
19276 )
19277 .detach_and_log_err(cx);
19278 }
19279 }
19280
19281 pub(crate) fn apply_selected_diff_hunks(
19282 &mut self,
19283 _: &ApplyDiffHunk,
19284 window: &mut Window,
19285 cx: &mut Context<Self>,
19286 ) {
19287 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
19288 let snapshot = self.snapshot(window, cx);
19289 let hunks = snapshot.hunks_for_ranges(
19290 self.selections
19291 .all(&snapshot.display_snapshot)
19292 .into_iter()
19293 .map(|selection| selection.range()),
19294 );
19295 let mut ranges_by_buffer = HashMap::default();
19296 self.transact(window, cx, |editor, _window, cx| {
19297 for hunk in hunks {
19298 if let Some(buffer) = editor.buffer.read(cx).buffer(hunk.buffer_id) {
19299 ranges_by_buffer
19300 .entry(buffer.clone())
19301 .or_insert_with(Vec::new)
19302 .push(hunk.buffer_range.to_offset(buffer.read(cx)));
19303 }
19304 }
19305
19306 for (buffer, ranges) in ranges_by_buffer {
19307 buffer.update(cx, |buffer, cx| {
19308 buffer.merge_into_base(ranges, cx);
19309 });
19310 }
19311 });
19312
19313 if let Some(project) = self.project.clone() {
19314 self.save(
19315 SaveOptions {
19316 format: true,
19317 autosave: false,
19318 },
19319 project,
19320 window,
19321 cx,
19322 )
19323 .detach_and_log_err(cx);
19324 }
19325 }
19326
19327 pub fn set_gutter_hovered(&mut self, hovered: bool, cx: &mut Context<Self>) {
19328 if hovered != self.gutter_hovered {
19329 self.gutter_hovered = hovered;
19330 cx.notify();
19331 }
19332 }
19333
19334 pub fn insert_blocks(
19335 &mut self,
19336 blocks: impl IntoIterator<Item = BlockProperties<Anchor>>,
19337 autoscroll: Option<Autoscroll>,
19338 cx: &mut Context<Self>,
19339 ) -> Vec<CustomBlockId> {
19340 let blocks = self
19341 .display_map
19342 .update(cx, |display_map, cx| display_map.insert_blocks(blocks, cx));
19343 if let Some(autoscroll) = autoscroll {
19344 self.request_autoscroll(autoscroll, cx);
19345 }
19346 cx.notify();
19347 blocks
19348 }
19349
19350 pub fn resize_blocks(
19351 &mut self,
19352 heights: HashMap<CustomBlockId, u32>,
19353 autoscroll: Option<Autoscroll>,
19354 cx: &mut Context<Self>,
19355 ) {
19356 self.display_map
19357 .update(cx, |display_map, cx| display_map.resize_blocks(heights, cx));
19358 if let Some(autoscroll) = autoscroll {
19359 self.request_autoscroll(autoscroll, cx);
19360 }
19361 cx.notify();
19362 }
19363
19364 pub fn replace_blocks(
19365 &mut self,
19366 renderers: HashMap<CustomBlockId, RenderBlock>,
19367 autoscroll: Option<Autoscroll>,
19368 cx: &mut Context<Self>,
19369 ) {
19370 self.display_map
19371 .update(cx, |display_map, _cx| display_map.replace_blocks(renderers));
19372 if let Some(autoscroll) = autoscroll {
19373 self.request_autoscroll(autoscroll, cx);
19374 }
19375 cx.notify();
19376 }
19377
19378 pub fn remove_blocks(
19379 &mut self,
19380 block_ids: HashSet<CustomBlockId>,
19381 autoscroll: Option<Autoscroll>,
19382 cx: &mut Context<Self>,
19383 ) {
19384 self.display_map.update(cx, |display_map, cx| {
19385 display_map.remove_blocks(block_ids, cx)
19386 });
19387 if let Some(autoscroll) = autoscroll {
19388 self.request_autoscroll(autoscroll, cx);
19389 }
19390 cx.notify();
19391 }
19392
19393 pub fn row_for_block(
19394 &self,
19395 block_id: CustomBlockId,
19396 cx: &mut Context<Self>,
19397 ) -> Option<DisplayRow> {
19398 self.display_map
19399 .update(cx, |map, cx| map.row_for_block(block_id, cx))
19400 }
19401
19402 pub(crate) fn set_focused_block(&mut self, focused_block: FocusedBlock) {
19403 self.focused_block = Some(focused_block);
19404 }
19405
19406 pub(crate) fn take_focused_block(&mut self) -> Option<FocusedBlock> {
19407 self.focused_block.take()
19408 }
19409
19410 pub fn insert_creases(
19411 &mut self,
19412 creases: impl IntoIterator<Item = Crease<Anchor>>,
19413 cx: &mut Context<Self>,
19414 ) -> Vec<CreaseId> {
19415 self.display_map
19416 .update(cx, |map, cx| map.insert_creases(creases, cx))
19417 }
19418
19419 pub fn remove_creases(
19420 &mut self,
19421 ids: impl IntoIterator<Item = CreaseId>,
19422 cx: &mut Context<Self>,
19423 ) -> Vec<(CreaseId, Range<Anchor>)> {
19424 self.display_map
19425 .update(cx, |map, cx| map.remove_creases(ids, cx))
19426 }
19427
19428 pub fn longest_row(&self, cx: &mut App) -> DisplayRow {
19429 self.display_map
19430 .update(cx, |map, cx| map.snapshot(cx))
19431 .longest_row()
19432 }
19433
19434 pub fn max_point(&self, cx: &mut App) -> DisplayPoint {
19435 self.display_map
19436 .update(cx, |map, cx| map.snapshot(cx))
19437 .max_point()
19438 }
19439
19440 pub fn text(&self, cx: &App) -> String {
19441 self.buffer.read(cx).read(cx).text()
19442 }
19443
19444 pub fn is_empty(&self, cx: &App) -> bool {
19445 self.buffer.read(cx).read(cx).is_empty()
19446 }
19447
19448 pub fn text_option(&self, cx: &App) -> Option<String> {
19449 let text = self.text(cx);
19450 let text = text.trim();
19451
19452 if text.is_empty() {
19453 return None;
19454 }
19455
19456 Some(text.to_string())
19457 }
19458
19459 pub fn set_text(
19460 &mut self,
19461 text: impl Into<Arc<str>>,
19462 window: &mut Window,
19463 cx: &mut Context<Self>,
19464 ) {
19465 self.transact(window, cx, |this, _, cx| {
19466 this.buffer
19467 .read(cx)
19468 .as_singleton()
19469 .expect("you can only call set_text on editors for singleton buffers")
19470 .update(cx, |buffer, cx| buffer.set_text(text, cx));
19471 });
19472 }
19473
19474 pub fn display_text(&self, cx: &mut App) -> String {
19475 self.display_map
19476 .update(cx, |map, cx| map.snapshot(cx))
19477 .text()
19478 }
19479
19480 fn create_minimap(
19481 &self,
19482 minimap_settings: MinimapSettings,
19483 window: &mut Window,
19484 cx: &mut Context<Self>,
19485 ) -> Option<Entity<Self>> {
19486 (minimap_settings.minimap_enabled() && self.buffer_kind(cx) == ItemBufferKind::Singleton)
19487 .then(|| self.initialize_new_minimap(minimap_settings, window, cx))
19488 }
19489
19490 fn initialize_new_minimap(
19491 &self,
19492 minimap_settings: MinimapSettings,
19493 window: &mut Window,
19494 cx: &mut Context<Self>,
19495 ) -> Entity<Self> {
19496 const MINIMAP_FONT_WEIGHT: gpui::FontWeight = gpui::FontWeight::BLACK;
19497
19498 let mut minimap = Editor::new_internal(
19499 EditorMode::Minimap {
19500 parent: cx.weak_entity(),
19501 },
19502 self.buffer.clone(),
19503 None,
19504 Some(self.display_map.clone()),
19505 window,
19506 cx,
19507 );
19508 minimap.scroll_manager.clone_state(&self.scroll_manager);
19509 minimap.set_text_style_refinement(TextStyleRefinement {
19510 font_size: Some(MINIMAP_FONT_SIZE),
19511 font_weight: Some(MINIMAP_FONT_WEIGHT),
19512 ..Default::default()
19513 });
19514 minimap.update_minimap_configuration(minimap_settings, cx);
19515 cx.new(|_| minimap)
19516 }
19517
19518 fn update_minimap_configuration(&mut self, minimap_settings: MinimapSettings, cx: &App) {
19519 let current_line_highlight = minimap_settings
19520 .current_line_highlight
19521 .unwrap_or_else(|| EditorSettings::get_global(cx).current_line_highlight);
19522 self.set_current_line_highlight(Some(current_line_highlight));
19523 }
19524
19525 pub fn minimap(&self) -> Option<&Entity<Self>> {
19526 self.minimap
19527 .as_ref()
19528 .filter(|_| self.minimap_visibility.visible())
19529 }
19530
19531 pub fn wrap_guides(&self, cx: &App) -> SmallVec<[(usize, bool); 2]> {
19532 let mut wrap_guides = smallvec![];
19533
19534 if self.show_wrap_guides == Some(false) {
19535 return wrap_guides;
19536 }
19537
19538 let settings = self.buffer.read(cx).language_settings(cx);
19539 if settings.show_wrap_guides {
19540 match self.soft_wrap_mode(cx) {
19541 SoftWrap::Column(soft_wrap) => {
19542 wrap_guides.push((soft_wrap as usize, true));
19543 }
19544 SoftWrap::Bounded(soft_wrap) => {
19545 wrap_guides.push((soft_wrap as usize, true));
19546 }
19547 SoftWrap::GitDiff | SoftWrap::None | SoftWrap::EditorWidth => {}
19548 }
19549 wrap_guides.extend(settings.wrap_guides.iter().map(|guide| (*guide, false)))
19550 }
19551
19552 wrap_guides
19553 }
19554
19555 pub fn soft_wrap_mode(&self, cx: &App) -> SoftWrap {
19556 let settings = self.buffer.read(cx).language_settings(cx);
19557 let mode = self.soft_wrap_mode_override.unwrap_or(settings.soft_wrap);
19558 match mode {
19559 language_settings::SoftWrap::PreferLine | language_settings::SoftWrap::None => {
19560 SoftWrap::None
19561 }
19562 language_settings::SoftWrap::EditorWidth => SoftWrap::EditorWidth,
19563 language_settings::SoftWrap::PreferredLineLength => {
19564 SoftWrap::Column(settings.preferred_line_length)
19565 }
19566 language_settings::SoftWrap::Bounded => {
19567 SoftWrap::Bounded(settings.preferred_line_length)
19568 }
19569 }
19570 }
19571
19572 pub fn set_soft_wrap_mode(
19573 &mut self,
19574 mode: language_settings::SoftWrap,
19575
19576 cx: &mut Context<Self>,
19577 ) {
19578 self.soft_wrap_mode_override = Some(mode);
19579 cx.notify();
19580 }
19581
19582 pub fn set_hard_wrap(&mut self, hard_wrap: Option<usize>, cx: &mut Context<Self>) {
19583 self.hard_wrap = hard_wrap;
19584 cx.notify();
19585 }
19586
19587 pub fn set_text_style_refinement(&mut self, style: TextStyleRefinement) {
19588 self.text_style_refinement = Some(style);
19589 }
19590
19591 /// called by the Element so we know what style we were most recently rendered with.
19592 pub fn set_style(&mut self, style: EditorStyle, window: &mut Window, cx: &mut Context<Self>) {
19593 // We intentionally do not inform the display map about the minimap style
19594 // so that wrapping is not recalculated and stays consistent for the editor
19595 // and its linked minimap.
19596 if !self.mode.is_minimap() {
19597 let font = style.text.font();
19598 let font_size = style.text.font_size.to_pixels(window.rem_size());
19599 let display_map = self
19600 .placeholder_display_map
19601 .as_ref()
19602 .filter(|_| self.is_empty(cx))
19603 .unwrap_or(&self.display_map);
19604
19605 display_map.update(cx, |map, cx| map.set_font(font, font_size, cx));
19606 }
19607 self.style = Some(style);
19608 }
19609
19610 pub fn style(&self) -> Option<&EditorStyle> {
19611 self.style.as_ref()
19612 }
19613
19614 // Called by the element. This method is not designed to be called outside of the editor
19615 // element's layout code because it does not notify when rewrapping is computed synchronously.
19616 pub(crate) fn set_wrap_width(&self, width: Option<Pixels>, cx: &mut App) -> bool {
19617 if self.is_empty(cx) {
19618 self.placeholder_display_map
19619 .as_ref()
19620 .map_or(false, |display_map| {
19621 display_map.update(cx, |map, cx| map.set_wrap_width(width, cx))
19622 })
19623 } else {
19624 self.display_map
19625 .update(cx, |map, cx| map.set_wrap_width(width, cx))
19626 }
19627 }
19628
19629 pub fn set_soft_wrap(&mut self) {
19630 self.soft_wrap_mode_override = Some(language_settings::SoftWrap::EditorWidth)
19631 }
19632
19633 pub fn toggle_soft_wrap(&mut self, _: &ToggleSoftWrap, _: &mut Window, cx: &mut Context<Self>) {
19634 if self.soft_wrap_mode_override.is_some() {
19635 self.soft_wrap_mode_override.take();
19636 } else {
19637 let soft_wrap = match self.soft_wrap_mode(cx) {
19638 SoftWrap::GitDiff => return,
19639 SoftWrap::None => language_settings::SoftWrap::EditorWidth,
19640 SoftWrap::EditorWidth | SoftWrap::Column(_) | SoftWrap::Bounded(_) => {
19641 language_settings::SoftWrap::None
19642 }
19643 };
19644 self.soft_wrap_mode_override = Some(soft_wrap);
19645 }
19646 cx.notify();
19647 }
19648
19649 pub fn toggle_tab_bar(&mut self, _: &ToggleTabBar, _: &mut Window, cx: &mut Context<Self>) {
19650 let Some(workspace) = self.workspace() else {
19651 return;
19652 };
19653 let fs = workspace.read(cx).app_state().fs.clone();
19654 let current_show = TabBarSettings::get_global(cx).show;
19655 update_settings_file(fs, cx, move |setting, _| {
19656 setting.tab_bar.get_or_insert_default().show = Some(!current_show);
19657 });
19658 }
19659
19660 pub fn toggle_indent_guides(
19661 &mut self,
19662 _: &ToggleIndentGuides,
19663 _: &mut Window,
19664 cx: &mut Context<Self>,
19665 ) {
19666 let currently_enabled = self.should_show_indent_guides().unwrap_or_else(|| {
19667 self.buffer
19668 .read(cx)
19669 .language_settings(cx)
19670 .indent_guides
19671 .enabled
19672 });
19673 self.show_indent_guides = Some(!currently_enabled);
19674 cx.notify();
19675 }
19676
19677 fn should_show_indent_guides(&self) -> Option<bool> {
19678 self.show_indent_guides
19679 }
19680
19681 pub fn toggle_line_numbers(
19682 &mut self,
19683 _: &ToggleLineNumbers,
19684 _: &mut Window,
19685 cx: &mut Context<Self>,
19686 ) {
19687 let mut editor_settings = EditorSettings::get_global(cx).clone();
19688 editor_settings.gutter.line_numbers = !editor_settings.gutter.line_numbers;
19689 EditorSettings::override_global(editor_settings, cx);
19690 }
19691
19692 pub fn line_numbers_enabled(&self, cx: &App) -> bool {
19693 if let Some(show_line_numbers) = self.show_line_numbers {
19694 return show_line_numbers;
19695 }
19696 EditorSettings::get_global(cx).gutter.line_numbers
19697 }
19698
19699 pub fn relative_line_numbers(&self, cx: &mut App) -> RelativeLineNumbers {
19700 match (
19701 self.use_relative_line_numbers,
19702 EditorSettings::get_global(cx).relative_line_numbers,
19703 ) {
19704 (None, setting) => setting,
19705 (Some(false), _) => RelativeLineNumbers::Disabled,
19706 (Some(true), RelativeLineNumbers::Wrapped) => RelativeLineNumbers::Wrapped,
19707 (Some(true), _) => RelativeLineNumbers::Enabled,
19708 }
19709 }
19710
19711 pub fn toggle_relative_line_numbers(
19712 &mut self,
19713 _: &ToggleRelativeLineNumbers,
19714 _: &mut Window,
19715 cx: &mut Context<Self>,
19716 ) {
19717 let is_relative = self.relative_line_numbers(cx);
19718 self.set_relative_line_number(Some(!is_relative.enabled()), cx)
19719 }
19720
19721 pub fn set_relative_line_number(&mut self, is_relative: Option<bool>, cx: &mut Context<Self>) {
19722 self.use_relative_line_numbers = is_relative;
19723 cx.notify();
19724 }
19725
19726 pub fn set_show_gutter(&mut self, show_gutter: bool, cx: &mut Context<Self>) {
19727 self.show_gutter = show_gutter;
19728 cx.notify();
19729 }
19730
19731 pub fn set_show_scrollbars(&mut self, show: bool, cx: &mut Context<Self>) {
19732 self.show_scrollbars = ScrollbarAxes {
19733 horizontal: show,
19734 vertical: show,
19735 };
19736 cx.notify();
19737 }
19738
19739 pub fn set_show_vertical_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19740 self.show_scrollbars.vertical = show;
19741 cx.notify();
19742 }
19743
19744 pub fn set_show_horizontal_scrollbar(&mut self, show: bool, cx: &mut Context<Self>) {
19745 self.show_scrollbars.horizontal = show;
19746 cx.notify();
19747 }
19748
19749 pub fn set_minimap_visibility(
19750 &mut self,
19751 minimap_visibility: MinimapVisibility,
19752 window: &mut Window,
19753 cx: &mut Context<Self>,
19754 ) {
19755 if self.minimap_visibility != minimap_visibility {
19756 if minimap_visibility.visible() && self.minimap.is_none() {
19757 let minimap_settings = EditorSettings::get_global(cx).minimap;
19758 self.minimap =
19759 self.create_minimap(minimap_settings.with_show_override(), window, cx);
19760 }
19761 self.minimap_visibility = minimap_visibility;
19762 cx.notify();
19763 }
19764 }
19765
19766 pub fn disable_scrollbars_and_minimap(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19767 self.set_show_scrollbars(false, cx);
19768 self.set_minimap_visibility(MinimapVisibility::Disabled, window, cx);
19769 }
19770
19771 pub fn hide_minimap_by_default(&mut self, window: &mut Window, cx: &mut Context<Self>) {
19772 self.set_minimap_visibility(self.minimap_visibility.hidden(), window, cx);
19773 }
19774
19775 /// Normally the text in full mode and auto height editors is padded on the
19776 /// left side by roughly half a character width for improved hit testing.
19777 ///
19778 /// Use this method to disable this for cases where this is not wanted (e.g.
19779 /// if you want to align the editor text with some other text above or below)
19780 /// or if you want to add this padding to single-line editors.
19781 pub fn set_offset_content(&mut self, offset_content: bool, cx: &mut Context<Self>) {
19782 self.offset_content = offset_content;
19783 cx.notify();
19784 }
19785
19786 pub fn set_show_line_numbers(&mut self, show_line_numbers: bool, cx: &mut Context<Self>) {
19787 self.show_line_numbers = Some(show_line_numbers);
19788 cx.notify();
19789 }
19790
19791 pub fn disable_expand_excerpt_buttons(&mut self, cx: &mut Context<Self>) {
19792 self.disable_expand_excerpt_buttons = true;
19793 cx.notify();
19794 }
19795
19796 pub fn set_show_git_diff_gutter(&mut self, show_git_diff_gutter: bool, cx: &mut Context<Self>) {
19797 self.show_git_diff_gutter = Some(show_git_diff_gutter);
19798 cx.notify();
19799 }
19800
19801 pub fn set_show_code_actions(&mut self, show_code_actions: bool, cx: &mut Context<Self>) {
19802 self.show_code_actions = Some(show_code_actions);
19803 cx.notify();
19804 }
19805
19806 pub fn set_show_runnables(&mut self, show_runnables: bool, cx: &mut Context<Self>) {
19807 self.show_runnables = Some(show_runnables);
19808 cx.notify();
19809 }
19810
19811 pub fn set_show_breakpoints(&mut self, show_breakpoints: bool, cx: &mut Context<Self>) {
19812 self.show_breakpoints = Some(show_breakpoints);
19813 cx.notify();
19814 }
19815
19816 pub fn set_masked(&mut self, masked: bool, cx: &mut Context<Self>) {
19817 if self.display_map.read(cx).masked != masked {
19818 self.display_map.update(cx, |map, _| map.masked = masked);
19819 }
19820 cx.notify()
19821 }
19822
19823 pub fn set_show_wrap_guides(&mut self, show_wrap_guides: bool, cx: &mut Context<Self>) {
19824 self.show_wrap_guides = Some(show_wrap_guides);
19825 cx.notify();
19826 }
19827
19828 pub fn set_show_indent_guides(&mut self, show_indent_guides: bool, cx: &mut Context<Self>) {
19829 self.show_indent_guides = Some(show_indent_guides);
19830 cx.notify();
19831 }
19832
19833 pub fn working_directory(&self, cx: &App) -> Option<PathBuf> {
19834 if let Some(buffer) = self.buffer().read(cx).as_singleton() {
19835 if let Some(file) = buffer.read(cx).file().and_then(|f| f.as_local())
19836 && let Some(dir) = file.abs_path(cx).parent()
19837 {
19838 return Some(dir.to_owned());
19839 }
19840 }
19841
19842 None
19843 }
19844
19845 fn target_file<'a>(&self, cx: &'a App) -> Option<&'a dyn language::LocalFile> {
19846 self.active_excerpt(cx)?
19847 .1
19848 .read(cx)
19849 .file()
19850 .and_then(|f| f.as_local())
19851 }
19852
19853 pub fn target_file_abs_path(&self, cx: &mut Context<Self>) -> Option<PathBuf> {
19854 self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19855 let buffer = buffer.read(cx);
19856 if let Some(project_path) = buffer.project_path(cx) {
19857 let project = self.project()?.read(cx);
19858 project.absolute_path(&project_path, cx)
19859 } else {
19860 buffer
19861 .file()
19862 .and_then(|file| file.as_local().map(|file| file.abs_path(cx)))
19863 }
19864 })
19865 }
19866
19867 pub fn reveal_in_finder(
19868 &mut self,
19869 _: &RevealInFileManager,
19870 _window: &mut Window,
19871 cx: &mut Context<Self>,
19872 ) {
19873 if let Some(target) = self.target_file(cx) {
19874 cx.reveal_path(&target.abs_path(cx));
19875 }
19876 }
19877
19878 pub fn copy_path(
19879 &mut self,
19880 _: &zed_actions::workspace::CopyPath,
19881 _window: &mut Window,
19882 cx: &mut Context<Self>,
19883 ) {
19884 if let Some(path) = self.target_file_abs_path(cx)
19885 && let Some(path) = path.to_str()
19886 {
19887 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19888 } else {
19889 cx.propagate();
19890 }
19891 }
19892
19893 pub fn copy_relative_path(
19894 &mut self,
19895 _: &zed_actions::workspace::CopyRelativePath,
19896 _window: &mut Window,
19897 cx: &mut Context<Self>,
19898 ) {
19899 if let Some(path) = self.active_excerpt(cx).and_then(|(_, buffer, _)| {
19900 let project = self.project()?.read(cx);
19901 let path = buffer.read(cx).file()?.path();
19902 let path = path.display(project.path_style(cx));
19903 Some(path)
19904 }) {
19905 cx.write_to_clipboard(ClipboardItem::new_string(path.to_string()));
19906 } else {
19907 cx.propagate();
19908 }
19909 }
19910
19911 /// Returns the project path for the editor's buffer, if any buffer is
19912 /// opened in the editor.
19913 pub fn project_path(&self, cx: &App) -> Option<ProjectPath> {
19914 if let Some(buffer) = self.buffer.read(cx).as_singleton() {
19915 buffer.read(cx).project_path(cx)
19916 } else {
19917 None
19918 }
19919 }
19920
19921 // Returns true if the editor handled a go-to-line request
19922 pub fn go_to_active_debug_line(&mut self, window: &mut Window, cx: &mut Context<Self>) -> bool {
19923 maybe!({
19924 let breakpoint_store = self.breakpoint_store.as_ref()?;
19925
19926 let Some(active_stack_frame) = breakpoint_store.read(cx).active_position().cloned()
19927 else {
19928 self.clear_row_highlights::<ActiveDebugLine>();
19929 return None;
19930 };
19931
19932 let position = active_stack_frame.position;
19933 let buffer_id = position.buffer_id?;
19934 let snapshot = self
19935 .project
19936 .as_ref()?
19937 .read(cx)
19938 .buffer_for_id(buffer_id, cx)?
19939 .read(cx)
19940 .snapshot();
19941
19942 let mut handled = false;
19943 for (id, ExcerptRange { context, .. }) in
19944 self.buffer.read(cx).excerpts_for_buffer(buffer_id, cx)
19945 {
19946 if context.start.cmp(&position, &snapshot).is_ge()
19947 || context.end.cmp(&position, &snapshot).is_lt()
19948 {
19949 continue;
19950 }
19951 let snapshot = self.buffer.read(cx).snapshot(cx);
19952 let multibuffer_anchor = snapshot.anchor_in_excerpt(id, position)?;
19953
19954 handled = true;
19955 self.clear_row_highlights::<ActiveDebugLine>();
19956
19957 self.go_to_line::<ActiveDebugLine>(
19958 multibuffer_anchor,
19959 Some(cx.theme().colors().editor_debugger_active_line_background),
19960 window,
19961 cx,
19962 );
19963
19964 cx.notify();
19965 }
19966
19967 handled.then_some(())
19968 })
19969 .is_some()
19970 }
19971
19972 pub fn copy_file_name_without_extension(
19973 &mut self,
19974 _: &CopyFileNameWithoutExtension,
19975 _: &mut Window,
19976 cx: &mut Context<Self>,
19977 ) {
19978 if let Some(file) = self.target_file(cx)
19979 && let Some(file_stem) = file.path().file_stem()
19980 {
19981 cx.write_to_clipboard(ClipboardItem::new_string(file_stem.to_string()));
19982 }
19983 }
19984
19985 pub fn copy_file_name(&mut self, _: &CopyFileName, _: &mut Window, cx: &mut Context<Self>) {
19986 if let Some(file) = self.target_file(cx)
19987 && let Some(name) = file.path().file_name()
19988 {
19989 cx.write_to_clipboard(ClipboardItem::new_string(name.to_string()));
19990 }
19991 }
19992
19993 pub fn toggle_git_blame(
19994 &mut self,
19995 _: &::git::Blame,
19996 window: &mut Window,
19997 cx: &mut Context<Self>,
19998 ) {
19999 self.show_git_blame_gutter = !self.show_git_blame_gutter;
20000
20001 if self.show_git_blame_gutter && !self.has_blame_entries(cx) {
20002 self.start_git_blame(true, window, cx);
20003 }
20004
20005 cx.notify();
20006 }
20007
20008 pub fn toggle_git_blame_inline(
20009 &mut self,
20010 _: &ToggleGitBlameInline,
20011 window: &mut Window,
20012 cx: &mut Context<Self>,
20013 ) {
20014 self.toggle_git_blame_inline_internal(true, window, cx);
20015 cx.notify();
20016 }
20017
20018 pub fn open_git_blame_commit(
20019 &mut self,
20020 _: &OpenGitBlameCommit,
20021 window: &mut Window,
20022 cx: &mut Context<Self>,
20023 ) {
20024 self.open_git_blame_commit_internal(window, cx);
20025 }
20026
20027 fn open_git_blame_commit_internal(
20028 &mut self,
20029 window: &mut Window,
20030 cx: &mut Context<Self>,
20031 ) -> Option<()> {
20032 let blame = self.blame.as_ref()?;
20033 let snapshot = self.snapshot(window, cx);
20034 let cursor = self
20035 .selections
20036 .newest::<Point>(&snapshot.display_snapshot)
20037 .head();
20038 let (buffer, point, _) = snapshot.buffer_snapshot().point_to_buffer_point(cursor)?;
20039 let (_, blame_entry) = blame
20040 .update(cx, |blame, cx| {
20041 blame
20042 .blame_for_rows(
20043 &[RowInfo {
20044 buffer_id: Some(buffer.remote_id()),
20045 buffer_row: Some(point.row),
20046 ..Default::default()
20047 }],
20048 cx,
20049 )
20050 .next()
20051 })
20052 .flatten()?;
20053 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
20054 let repo = blame.read(cx).repository(cx, buffer.remote_id())?;
20055 let workspace = self.workspace()?.downgrade();
20056 renderer.open_blame_commit(blame_entry, repo, workspace, window, cx);
20057 None
20058 }
20059
20060 pub fn git_blame_inline_enabled(&self) -> bool {
20061 self.git_blame_inline_enabled
20062 }
20063
20064 pub fn toggle_selection_menu(
20065 &mut self,
20066 _: &ToggleSelectionMenu,
20067 _: &mut Window,
20068 cx: &mut Context<Self>,
20069 ) {
20070 self.show_selection_menu = self
20071 .show_selection_menu
20072 .map(|show_selections_menu| !show_selections_menu)
20073 .or_else(|| Some(!EditorSettings::get_global(cx).toolbar.selections_menu));
20074
20075 cx.notify();
20076 }
20077
20078 pub fn selection_menu_enabled(&self, cx: &App) -> bool {
20079 self.show_selection_menu
20080 .unwrap_or_else(|| EditorSettings::get_global(cx).toolbar.selections_menu)
20081 }
20082
20083 fn start_git_blame(
20084 &mut self,
20085 user_triggered: bool,
20086 window: &mut Window,
20087 cx: &mut Context<Self>,
20088 ) {
20089 if let Some(project) = self.project() {
20090 if let Some(buffer) = self.buffer().read(cx).as_singleton()
20091 && buffer.read(cx).file().is_none()
20092 {
20093 return;
20094 }
20095
20096 let focused = self.focus_handle(cx).contains_focused(window, cx);
20097
20098 let project = project.clone();
20099 let blame = cx
20100 .new(|cx| GitBlame::new(self.buffer.clone(), project, user_triggered, focused, cx));
20101 self.blame_subscription =
20102 Some(cx.observe_in(&blame, window, |_, _, _, cx| cx.notify()));
20103 self.blame = Some(blame);
20104 }
20105 }
20106
20107 fn toggle_git_blame_inline_internal(
20108 &mut self,
20109 user_triggered: bool,
20110 window: &mut Window,
20111 cx: &mut Context<Self>,
20112 ) {
20113 if self.git_blame_inline_enabled {
20114 self.git_blame_inline_enabled = false;
20115 self.show_git_blame_inline = false;
20116 self.show_git_blame_inline_delay_task.take();
20117 } else {
20118 self.git_blame_inline_enabled = true;
20119 self.start_git_blame_inline(user_triggered, window, cx);
20120 }
20121
20122 cx.notify();
20123 }
20124
20125 fn start_git_blame_inline(
20126 &mut self,
20127 user_triggered: bool,
20128 window: &mut Window,
20129 cx: &mut Context<Self>,
20130 ) {
20131 self.start_git_blame(user_triggered, window, cx);
20132
20133 if ProjectSettings::get_global(cx)
20134 .git
20135 .inline_blame_delay()
20136 .is_some()
20137 {
20138 self.start_inline_blame_timer(window, cx);
20139 } else {
20140 self.show_git_blame_inline = true
20141 }
20142 }
20143
20144 pub fn blame(&self) -> Option<&Entity<GitBlame>> {
20145 self.blame.as_ref()
20146 }
20147
20148 pub fn show_git_blame_gutter(&self) -> bool {
20149 self.show_git_blame_gutter
20150 }
20151
20152 pub fn render_git_blame_gutter(&self, cx: &App) -> bool {
20153 !self.mode().is_minimap() && self.show_git_blame_gutter && self.has_blame_entries(cx)
20154 }
20155
20156 pub fn render_git_blame_inline(&self, window: &Window, cx: &App) -> bool {
20157 self.show_git_blame_inline
20158 && (self.focus_handle.is_focused(window) || self.inline_blame_popover.is_some())
20159 && !self.newest_selection_head_on_empty_line(cx)
20160 && self.has_blame_entries(cx)
20161 }
20162
20163 fn has_blame_entries(&self, cx: &App) -> bool {
20164 self.blame()
20165 .is_some_and(|blame| blame.read(cx).has_generated_entries())
20166 }
20167
20168 fn newest_selection_head_on_empty_line(&self, cx: &App) -> bool {
20169 let cursor_anchor = self.selections.newest_anchor().head();
20170
20171 let snapshot = self.buffer.read(cx).snapshot(cx);
20172 let buffer_row = MultiBufferRow(cursor_anchor.to_point(&snapshot).row);
20173
20174 snapshot.line_len(buffer_row) == 0
20175 }
20176
20177 fn get_permalink_to_line(&self, cx: &mut Context<Self>) -> Task<Result<url::Url>> {
20178 let buffer_and_selection = maybe!({
20179 let selection = self.selections.newest::<Point>(&self.display_snapshot(cx));
20180 let selection_range = selection.range();
20181
20182 let multi_buffer = self.buffer().read(cx);
20183 let multi_buffer_snapshot = multi_buffer.snapshot(cx);
20184 let buffer_ranges = multi_buffer_snapshot.range_to_buffer_ranges(selection_range);
20185
20186 let (buffer, range, _) = if selection.reversed {
20187 buffer_ranges.first()
20188 } else {
20189 buffer_ranges.last()
20190 }?;
20191
20192 let selection = text::ToPoint::to_point(&range.start, buffer).row
20193 ..text::ToPoint::to_point(&range.end, buffer).row;
20194 Some((multi_buffer.buffer(buffer.remote_id()).unwrap(), selection))
20195 });
20196
20197 let Some((buffer, selection)) = buffer_and_selection else {
20198 return Task::ready(Err(anyhow!("failed to determine buffer and selection")));
20199 };
20200
20201 let Some(project) = self.project() else {
20202 return Task::ready(Err(anyhow!("editor does not have project")));
20203 };
20204
20205 project.update(cx, |project, cx| {
20206 project.get_permalink_to_line(&buffer, selection, cx)
20207 })
20208 }
20209
20210 pub fn copy_permalink_to_line(
20211 &mut self,
20212 _: &CopyPermalinkToLine,
20213 window: &mut Window,
20214 cx: &mut Context<Self>,
20215 ) {
20216 let permalink_task = self.get_permalink_to_line(cx);
20217 let workspace = self.workspace();
20218
20219 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20220 Ok(permalink) => {
20221 cx.update(|_, cx| {
20222 cx.write_to_clipboard(ClipboardItem::new_string(permalink.to_string()));
20223 })
20224 .ok();
20225 }
20226 Err(err) => {
20227 let message = format!("Failed to copy permalink: {err}");
20228
20229 anyhow::Result::<()>::Err(err).log_err();
20230
20231 if let Some(workspace) = workspace {
20232 workspace
20233 .update_in(cx, |workspace, _, cx| {
20234 struct CopyPermalinkToLine;
20235
20236 workspace.show_toast(
20237 Toast::new(
20238 NotificationId::unique::<CopyPermalinkToLine>(),
20239 message,
20240 ),
20241 cx,
20242 )
20243 })
20244 .ok();
20245 }
20246 }
20247 })
20248 .detach();
20249 }
20250
20251 pub fn copy_file_location(
20252 &mut self,
20253 _: &CopyFileLocation,
20254 _: &mut Window,
20255 cx: &mut Context<Self>,
20256 ) {
20257 let selection = self
20258 .selections
20259 .newest::<Point>(&self.display_snapshot(cx))
20260 .start
20261 .row
20262 + 1;
20263 if let Some(file) = self.target_file(cx) {
20264 let path = file.path().display(file.path_style(cx));
20265 cx.write_to_clipboard(ClipboardItem::new_string(format!("{path}:{selection}")));
20266 }
20267 }
20268
20269 pub fn open_permalink_to_line(
20270 &mut self,
20271 _: &OpenPermalinkToLine,
20272 window: &mut Window,
20273 cx: &mut Context<Self>,
20274 ) {
20275 let permalink_task = self.get_permalink_to_line(cx);
20276 let workspace = self.workspace();
20277
20278 cx.spawn_in(window, async move |_, cx| match permalink_task.await {
20279 Ok(permalink) => {
20280 cx.update(|_, cx| {
20281 cx.open_url(permalink.as_ref());
20282 })
20283 .ok();
20284 }
20285 Err(err) => {
20286 let message = format!("Failed to open permalink: {err}");
20287
20288 anyhow::Result::<()>::Err(err).log_err();
20289
20290 if let Some(workspace) = workspace {
20291 workspace
20292 .update(cx, |workspace, cx| {
20293 struct OpenPermalinkToLine;
20294
20295 workspace.show_toast(
20296 Toast::new(
20297 NotificationId::unique::<OpenPermalinkToLine>(),
20298 message,
20299 ),
20300 cx,
20301 )
20302 })
20303 .ok();
20304 }
20305 }
20306 })
20307 .detach();
20308 }
20309
20310 pub fn insert_uuid_v4(
20311 &mut self,
20312 _: &InsertUuidV4,
20313 window: &mut Window,
20314 cx: &mut Context<Self>,
20315 ) {
20316 self.insert_uuid(UuidVersion::V4, window, cx);
20317 }
20318
20319 pub fn insert_uuid_v7(
20320 &mut self,
20321 _: &InsertUuidV7,
20322 window: &mut Window,
20323 cx: &mut Context<Self>,
20324 ) {
20325 self.insert_uuid(UuidVersion::V7, window, cx);
20326 }
20327
20328 fn insert_uuid(&mut self, version: UuidVersion, window: &mut Window, cx: &mut Context<Self>) {
20329 self.hide_mouse_cursor(HideMouseCursorOrigin::TypingAction, cx);
20330 self.transact(window, cx, |this, window, cx| {
20331 let edits = this
20332 .selections
20333 .all::<Point>(&this.display_snapshot(cx))
20334 .into_iter()
20335 .map(|selection| {
20336 let uuid = match version {
20337 UuidVersion::V4 => uuid::Uuid::new_v4(),
20338 UuidVersion::V7 => uuid::Uuid::now_v7(),
20339 };
20340
20341 (selection.range(), uuid.to_string())
20342 });
20343 this.edit(edits, cx);
20344 this.refresh_edit_prediction(true, false, window, cx);
20345 });
20346 }
20347
20348 pub fn open_selections_in_multibuffer(
20349 &mut self,
20350 _: &OpenSelectionsInMultibuffer,
20351 window: &mut Window,
20352 cx: &mut Context<Self>,
20353 ) {
20354 let multibuffer = self.buffer.read(cx);
20355
20356 let Some(buffer) = multibuffer.as_singleton() else {
20357 return;
20358 };
20359
20360 let Some(workspace) = self.workspace() else {
20361 return;
20362 };
20363
20364 let title = multibuffer.title(cx).to_string();
20365
20366 let locations = self
20367 .selections
20368 .all_anchors(&self.display_snapshot(cx))
20369 .iter()
20370 .map(|selection| {
20371 (
20372 buffer.clone(),
20373 (selection.start.text_anchor..selection.end.text_anchor)
20374 .to_point(buffer.read(cx)),
20375 )
20376 })
20377 .into_group_map();
20378
20379 cx.spawn_in(window, async move |_, cx| {
20380 workspace.update_in(cx, |workspace, window, cx| {
20381 Self::open_locations_in_multibuffer(
20382 workspace,
20383 locations,
20384 format!("Selections for '{title}'"),
20385 false,
20386 MultibufferSelectionMode::All,
20387 window,
20388 cx,
20389 );
20390 })
20391 })
20392 .detach();
20393 }
20394
20395 /// Adds a row highlight for the given range. If a row has multiple highlights, the
20396 /// last highlight added will be used.
20397 ///
20398 /// If the range ends at the beginning of a line, then that line will not be highlighted.
20399 pub fn highlight_rows<T: 'static>(
20400 &mut self,
20401 range: Range<Anchor>,
20402 color: Hsla,
20403 options: RowHighlightOptions,
20404 cx: &mut Context<Self>,
20405 ) {
20406 let snapshot = self.buffer().read(cx).snapshot(cx);
20407 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20408 let ix = row_highlights.binary_search_by(|highlight| {
20409 Ordering::Equal
20410 .then_with(|| highlight.range.start.cmp(&range.start, &snapshot))
20411 .then_with(|| highlight.range.end.cmp(&range.end, &snapshot))
20412 });
20413
20414 if let Err(mut ix) = ix {
20415 let index = post_inc(&mut self.highlight_order);
20416
20417 // If this range intersects with the preceding highlight, then merge it with
20418 // the preceding highlight. Otherwise insert a new highlight.
20419 let mut merged = false;
20420 if ix > 0 {
20421 let prev_highlight = &mut row_highlights[ix - 1];
20422 if prev_highlight
20423 .range
20424 .end
20425 .cmp(&range.start, &snapshot)
20426 .is_ge()
20427 {
20428 ix -= 1;
20429 if prev_highlight.range.end.cmp(&range.end, &snapshot).is_lt() {
20430 prev_highlight.range.end = range.end;
20431 }
20432 merged = true;
20433 prev_highlight.index = index;
20434 prev_highlight.color = color;
20435 prev_highlight.options = options;
20436 }
20437 }
20438
20439 if !merged {
20440 row_highlights.insert(
20441 ix,
20442 RowHighlight {
20443 range,
20444 index,
20445 color,
20446 options,
20447 type_id: TypeId::of::<T>(),
20448 },
20449 );
20450 }
20451
20452 // If any of the following highlights intersect with this one, merge them.
20453 while let Some(next_highlight) = row_highlights.get(ix + 1) {
20454 let highlight = &row_highlights[ix];
20455 if next_highlight
20456 .range
20457 .start
20458 .cmp(&highlight.range.end, &snapshot)
20459 .is_le()
20460 {
20461 if next_highlight
20462 .range
20463 .end
20464 .cmp(&highlight.range.end, &snapshot)
20465 .is_gt()
20466 {
20467 row_highlights[ix].range.end = next_highlight.range.end;
20468 }
20469 row_highlights.remove(ix + 1);
20470 } else {
20471 break;
20472 }
20473 }
20474 }
20475 }
20476
20477 /// Remove any highlighted row ranges of the given type that intersect the
20478 /// given ranges.
20479 pub fn remove_highlighted_rows<T: 'static>(
20480 &mut self,
20481 ranges_to_remove: Vec<Range<Anchor>>,
20482 cx: &mut Context<Self>,
20483 ) {
20484 let snapshot = self.buffer().read(cx).snapshot(cx);
20485 let row_highlights = self.highlighted_rows.entry(TypeId::of::<T>()).or_default();
20486 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20487 row_highlights.retain(|highlight| {
20488 while let Some(range_to_remove) = ranges_to_remove.peek() {
20489 match range_to_remove.end.cmp(&highlight.range.start, &snapshot) {
20490 Ordering::Less | Ordering::Equal => {
20491 ranges_to_remove.next();
20492 }
20493 Ordering::Greater => {
20494 match range_to_remove.start.cmp(&highlight.range.end, &snapshot) {
20495 Ordering::Less | Ordering::Equal => {
20496 return false;
20497 }
20498 Ordering::Greater => break,
20499 }
20500 }
20501 }
20502 }
20503
20504 true
20505 })
20506 }
20507
20508 /// Clear all anchor ranges for a certain highlight context type, so no corresponding rows will be highlighted.
20509 pub fn clear_row_highlights<T: 'static>(&mut self) {
20510 self.highlighted_rows.remove(&TypeId::of::<T>());
20511 }
20512
20513 /// For a highlight given context type, gets all anchor ranges that will be used for row highlighting.
20514 pub fn highlighted_rows<T: 'static>(&self) -> impl '_ + Iterator<Item = (Range<Anchor>, Hsla)> {
20515 self.highlighted_rows
20516 .get(&TypeId::of::<T>())
20517 .map_or(&[] as &[_], |vec| vec.as_slice())
20518 .iter()
20519 .map(|highlight| (highlight.range.clone(), highlight.color))
20520 }
20521
20522 /// Merges all anchor ranges for all context types ever set, picking the last highlight added in case of a row conflict.
20523 /// Returns a map of display rows that are highlighted and their corresponding highlight color.
20524 /// Allows to ignore certain kinds of highlights.
20525 pub fn highlighted_display_rows(
20526 &self,
20527 window: &mut Window,
20528 cx: &mut App,
20529 ) -> BTreeMap<DisplayRow, LineHighlight> {
20530 let snapshot = self.snapshot(window, cx);
20531 let mut used_highlight_orders = HashMap::default();
20532 self.highlighted_rows
20533 .iter()
20534 .flat_map(|(_, highlighted_rows)| highlighted_rows.iter())
20535 .fold(
20536 BTreeMap::<DisplayRow, LineHighlight>::new(),
20537 |mut unique_rows, highlight| {
20538 let start = highlight.range.start.to_display_point(&snapshot);
20539 let end = highlight.range.end.to_display_point(&snapshot);
20540 let start_row = start.row().0;
20541 let end_row = if highlight.range.end.text_anchor != text::Anchor::MAX
20542 && end.column() == 0
20543 {
20544 end.row().0.saturating_sub(1)
20545 } else {
20546 end.row().0
20547 };
20548 for row in start_row..=end_row {
20549 let used_index =
20550 used_highlight_orders.entry(row).or_insert(highlight.index);
20551 if highlight.index >= *used_index {
20552 *used_index = highlight.index;
20553 unique_rows.insert(
20554 DisplayRow(row),
20555 LineHighlight {
20556 include_gutter: highlight.options.include_gutter,
20557 border: None,
20558 background: highlight.color.into(),
20559 type_id: Some(highlight.type_id),
20560 },
20561 );
20562 }
20563 }
20564 unique_rows
20565 },
20566 )
20567 }
20568
20569 pub fn highlighted_display_row_for_autoscroll(
20570 &self,
20571 snapshot: &DisplaySnapshot,
20572 ) -> Option<DisplayRow> {
20573 self.highlighted_rows
20574 .values()
20575 .flat_map(|highlighted_rows| highlighted_rows.iter())
20576 .filter_map(|highlight| {
20577 if highlight.options.autoscroll {
20578 Some(highlight.range.start.to_display_point(snapshot).row())
20579 } else {
20580 None
20581 }
20582 })
20583 .min()
20584 }
20585
20586 pub fn set_search_within_ranges(&mut self, ranges: &[Range<Anchor>], cx: &mut Context<Self>) {
20587 self.highlight_background::<SearchWithinRange>(
20588 ranges,
20589 |colors| colors.colors().editor_document_highlight_read_background,
20590 cx,
20591 )
20592 }
20593
20594 pub fn set_breadcrumb_header(&mut self, new_header: String) {
20595 self.breadcrumb_header = Some(new_header);
20596 }
20597
20598 pub fn clear_search_within_ranges(&mut self, cx: &mut Context<Self>) {
20599 self.clear_background_highlights::<SearchWithinRange>(cx);
20600 }
20601
20602 pub fn highlight_background<T: 'static>(
20603 &mut self,
20604 ranges: &[Range<Anchor>],
20605 color_fetcher: fn(&Theme) -> Hsla,
20606 cx: &mut Context<Self>,
20607 ) {
20608 self.background_highlights.insert(
20609 HighlightKey::Type(TypeId::of::<T>()),
20610 (color_fetcher, Arc::from(ranges)),
20611 );
20612 self.scrollbar_marker_state.dirty = true;
20613 cx.notify();
20614 }
20615
20616 pub fn highlight_background_key<T: 'static>(
20617 &mut self,
20618 key: usize,
20619 ranges: &[Range<Anchor>],
20620 color_fetcher: fn(&Theme) -> Hsla,
20621 cx: &mut Context<Self>,
20622 ) {
20623 self.background_highlights.insert(
20624 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20625 (color_fetcher, Arc::from(ranges)),
20626 );
20627 self.scrollbar_marker_state.dirty = true;
20628 cx.notify();
20629 }
20630
20631 pub fn clear_background_highlights<T: 'static>(
20632 &mut self,
20633 cx: &mut Context<Self>,
20634 ) -> Option<BackgroundHighlight> {
20635 let text_highlights = self
20636 .background_highlights
20637 .remove(&HighlightKey::Type(TypeId::of::<T>()))?;
20638 if !text_highlights.1.is_empty() {
20639 self.scrollbar_marker_state.dirty = true;
20640 cx.notify();
20641 }
20642 Some(text_highlights)
20643 }
20644
20645 pub fn highlight_gutter<T: 'static>(
20646 &mut self,
20647 ranges: impl Into<Vec<Range<Anchor>>>,
20648 color_fetcher: fn(&App) -> Hsla,
20649 cx: &mut Context<Self>,
20650 ) {
20651 self.gutter_highlights
20652 .insert(TypeId::of::<T>(), (color_fetcher, ranges.into()));
20653 cx.notify();
20654 }
20655
20656 pub fn clear_gutter_highlights<T: 'static>(
20657 &mut self,
20658 cx: &mut Context<Self>,
20659 ) -> Option<GutterHighlight> {
20660 cx.notify();
20661 self.gutter_highlights.remove(&TypeId::of::<T>())
20662 }
20663
20664 pub fn insert_gutter_highlight<T: 'static>(
20665 &mut self,
20666 range: Range<Anchor>,
20667 color_fetcher: fn(&App) -> Hsla,
20668 cx: &mut Context<Self>,
20669 ) {
20670 let snapshot = self.buffer().read(cx).snapshot(cx);
20671 let mut highlights = self
20672 .gutter_highlights
20673 .remove(&TypeId::of::<T>())
20674 .map(|(_, highlights)| highlights)
20675 .unwrap_or_default();
20676 let ix = highlights.binary_search_by(|highlight| {
20677 Ordering::Equal
20678 .then_with(|| highlight.start.cmp(&range.start, &snapshot))
20679 .then_with(|| highlight.end.cmp(&range.end, &snapshot))
20680 });
20681 if let Err(ix) = ix {
20682 highlights.insert(ix, range);
20683 }
20684 self.gutter_highlights
20685 .insert(TypeId::of::<T>(), (color_fetcher, highlights));
20686 }
20687
20688 pub fn remove_gutter_highlights<T: 'static>(
20689 &mut self,
20690 ranges_to_remove: Vec<Range<Anchor>>,
20691 cx: &mut Context<Self>,
20692 ) {
20693 let snapshot = self.buffer().read(cx).snapshot(cx);
20694 let Some((color_fetcher, mut gutter_highlights)) =
20695 self.gutter_highlights.remove(&TypeId::of::<T>())
20696 else {
20697 return;
20698 };
20699 let mut ranges_to_remove = ranges_to_remove.iter().peekable();
20700 gutter_highlights.retain(|highlight| {
20701 while let Some(range_to_remove) = ranges_to_remove.peek() {
20702 match range_to_remove.end.cmp(&highlight.start, &snapshot) {
20703 Ordering::Less | Ordering::Equal => {
20704 ranges_to_remove.next();
20705 }
20706 Ordering::Greater => {
20707 match range_to_remove.start.cmp(&highlight.end, &snapshot) {
20708 Ordering::Less | Ordering::Equal => {
20709 return false;
20710 }
20711 Ordering::Greater => break,
20712 }
20713 }
20714 }
20715 }
20716
20717 true
20718 });
20719 self.gutter_highlights
20720 .insert(TypeId::of::<T>(), (color_fetcher, gutter_highlights));
20721 }
20722
20723 #[cfg(feature = "test-support")]
20724 pub fn all_text_highlights(
20725 &self,
20726 window: &mut Window,
20727 cx: &mut Context<Self>,
20728 ) -> Vec<(HighlightStyle, Vec<Range<DisplayPoint>>)> {
20729 let snapshot = self.snapshot(window, cx);
20730 self.display_map.update(cx, |display_map, _| {
20731 display_map
20732 .all_text_highlights()
20733 .map(|highlight| {
20734 let (style, ranges) = highlight.as_ref();
20735 (
20736 *style,
20737 ranges
20738 .iter()
20739 .map(|range| range.clone().to_display_points(&snapshot))
20740 .collect(),
20741 )
20742 })
20743 .collect()
20744 })
20745 }
20746
20747 #[cfg(feature = "test-support")]
20748 pub fn all_text_background_highlights(
20749 &self,
20750 window: &mut Window,
20751 cx: &mut Context<Self>,
20752 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20753 let snapshot = self.snapshot(window, cx);
20754 let buffer = &snapshot.buffer_snapshot();
20755 let start = buffer.anchor_before(0);
20756 let end = buffer.anchor_after(buffer.len());
20757 self.sorted_background_highlights_in_range(start..end, &snapshot, cx.theme())
20758 }
20759
20760 #[cfg(any(test, feature = "test-support"))]
20761 pub fn sorted_background_highlights_in_range(
20762 &self,
20763 search_range: Range<Anchor>,
20764 display_snapshot: &DisplaySnapshot,
20765 theme: &Theme,
20766 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20767 let mut res = self.background_highlights_in_range(search_range, display_snapshot, theme);
20768 res.sort_by(|a, b| {
20769 a.0.start
20770 .cmp(&b.0.start)
20771 .then_with(|| a.0.end.cmp(&b.0.end))
20772 .then_with(|| a.1.cmp(&b.1))
20773 });
20774 res
20775 }
20776
20777 #[cfg(feature = "test-support")]
20778 pub fn search_background_highlights(&mut self, cx: &mut Context<Self>) -> Vec<Range<Point>> {
20779 let snapshot = self.buffer().read(cx).snapshot(cx);
20780
20781 let highlights = self
20782 .background_highlights
20783 .get(&HighlightKey::Type(TypeId::of::<
20784 items::BufferSearchHighlights,
20785 >()));
20786
20787 if let Some((_color, ranges)) = highlights {
20788 ranges
20789 .iter()
20790 .map(|range| range.start.to_point(&snapshot)..range.end.to_point(&snapshot))
20791 .collect_vec()
20792 } else {
20793 vec![]
20794 }
20795 }
20796
20797 fn document_highlights_for_position<'a>(
20798 &'a self,
20799 position: Anchor,
20800 buffer: &'a MultiBufferSnapshot,
20801 ) -> impl 'a + Iterator<Item = &'a Range<Anchor>> {
20802 let read_highlights = self
20803 .background_highlights
20804 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightRead>()))
20805 .map(|h| &h.1);
20806 let write_highlights = self
20807 .background_highlights
20808 .get(&HighlightKey::Type(TypeId::of::<DocumentHighlightWrite>()))
20809 .map(|h| &h.1);
20810 let left_position = position.bias_left(buffer);
20811 let right_position = position.bias_right(buffer);
20812 read_highlights
20813 .into_iter()
20814 .chain(write_highlights)
20815 .flat_map(move |ranges| {
20816 let start_ix = match ranges.binary_search_by(|probe| {
20817 let cmp = probe.end.cmp(&left_position, buffer);
20818 if cmp.is_ge() {
20819 Ordering::Greater
20820 } else {
20821 Ordering::Less
20822 }
20823 }) {
20824 Ok(i) | Err(i) => i,
20825 };
20826
20827 ranges[start_ix..]
20828 .iter()
20829 .take_while(move |range| range.start.cmp(&right_position, buffer).is_le())
20830 })
20831 }
20832
20833 pub fn has_background_highlights<T: 'static>(&self) -> bool {
20834 self.background_highlights
20835 .get(&HighlightKey::Type(TypeId::of::<T>()))
20836 .is_some_and(|(_, highlights)| !highlights.is_empty())
20837 }
20838
20839 /// Returns all background highlights for a given range.
20840 ///
20841 /// The order of highlights is not deterministic, do sort the ranges if needed for the logic.
20842 pub fn background_highlights_in_range(
20843 &self,
20844 search_range: Range<Anchor>,
20845 display_snapshot: &DisplaySnapshot,
20846 theme: &Theme,
20847 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20848 let mut results = Vec::new();
20849 for (color_fetcher, ranges) in self.background_highlights.values() {
20850 let color = color_fetcher(theme);
20851 let start_ix = match ranges.binary_search_by(|probe| {
20852 let cmp = probe
20853 .end
20854 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20855 if cmp.is_gt() {
20856 Ordering::Greater
20857 } else {
20858 Ordering::Less
20859 }
20860 }) {
20861 Ok(i) | Err(i) => i,
20862 };
20863 for range in &ranges[start_ix..] {
20864 if range
20865 .start
20866 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20867 .is_ge()
20868 {
20869 break;
20870 }
20871
20872 let start = range.start.to_display_point(display_snapshot);
20873 let end = range.end.to_display_point(display_snapshot);
20874 results.push((start..end, color))
20875 }
20876 }
20877 results
20878 }
20879
20880 pub fn gutter_highlights_in_range(
20881 &self,
20882 search_range: Range<Anchor>,
20883 display_snapshot: &DisplaySnapshot,
20884 cx: &App,
20885 ) -> Vec<(Range<DisplayPoint>, Hsla)> {
20886 let mut results = Vec::new();
20887 for (color_fetcher, ranges) in self.gutter_highlights.values() {
20888 let color = color_fetcher(cx);
20889 let start_ix = match ranges.binary_search_by(|probe| {
20890 let cmp = probe
20891 .end
20892 .cmp(&search_range.start, &display_snapshot.buffer_snapshot());
20893 if cmp.is_gt() {
20894 Ordering::Greater
20895 } else {
20896 Ordering::Less
20897 }
20898 }) {
20899 Ok(i) | Err(i) => i,
20900 };
20901 for range in &ranges[start_ix..] {
20902 if range
20903 .start
20904 .cmp(&search_range.end, &display_snapshot.buffer_snapshot())
20905 .is_ge()
20906 {
20907 break;
20908 }
20909
20910 let start = range.start.to_display_point(display_snapshot);
20911 let end = range.end.to_display_point(display_snapshot);
20912 results.push((start..end, color))
20913 }
20914 }
20915 results
20916 }
20917
20918 /// Get the text ranges corresponding to the redaction query
20919 pub fn redacted_ranges(
20920 &self,
20921 search_range: Range<Anchor>,
20922 display_snapshot: &DisplaySnapshot,
20923 cx: &App,
20924 ) -> Vec<Range<DisplayPoint>> {
20925 display_snapshot
20926 .buffer_snapshot()
20927 .redacted_ranges(search_range, |file| {
20928 if let Some(file) = file {
20929 file.is_private()
20930 && EditorSettings::get(
20931 Some(SettingsLocation {
20932 worktree_id: file.worktree_id(cx),
20933 path: file.path().as_ref(),
20934 }),
20935 cx,
20936 )
20937 .redact_private_values
20938 } else {
20939 false
20940 }
20941 })
20942 .map(|range| {
20943 range.start.to_display_point(display_snapshot)
20944 ..range.end.to_display_point(display_snapshot)
20945 })
20946 .collect()
20947 }
20948
20949 pub fn highlight_text_key<T: 'static>(
20950 &mut self,
20951 key: usize,
20952 ranges: Vec<Range<Anchor>>,
20953 style: HighlightStyle,
20954 cx: &mut Context<Self>,
20955 ) {
20956 self.display_map.update(cx, |map, _| {
20957 map.highlight_text(
20958 HighlightKey::TypePlus(TypeId::of::<T>(), key),
20959 ranges,
20960 style,
20961 );
20962 });
20963 cx.notify();
20964 }
20965
20966 pub fn highlight_text<T: 'static>(
20967 &mut self,
20968 ranges: Vec<Range<Anchor>>,
20969 style: HighlightStyle,
20970 cx: &mut Context<Self>,
20971 ) {
20972 self.display_map.update(cx, |map, _| {
20973 map.highlight_text(HighlightKey::Type(TypeId::of::<T>()), ranges, style)
20974 });
20975 cx.notify();
20976 }
20977
20978 pub fn text_highlights<'a, T: 'static>(
20979 &'a self,
20980 cx: &'a App,
20981 ) -> Option<(HighlightStyle, &'a [Range<Anchor>])> {
20982 self.display_map.read(cx).text_highlights(TypeId::of::<T>())
20983 }
20984
20985 pub fn clear_highlights<T: 'static>(&mut self, cx: &mut Context<Self>) {
20986 let cleared = self
20987 .display_map
20988 .update(cx, |map, _| map.clear_highlights(TypeId::of::<T>()));
20989 if cleared {
20990 cx.notify();
20991 }
20992 }
20993
20994 pub fn show_local_cursors(&self, window: &mut Window, cx: &mut App) -> bool {
20995 (self.read_only(cx) || self.blink_manager.read(cx).visible())
20996 && self.focus_handle.is_focused(window)
20997 }
20998
20999 pub fn set_show_cursor_when_unfocused(&mut self, is_enabled: bool, cx: &mut Context<Self>) {
21000 self.show_cursor_when_unfocused = is_enabled;
21001 cx.notify();
21002 }
21003
21004 fn on_buffer_changed(&mut self, _: Entity<MultiBuffer>, cx: &mut Context<Self>) {
21005 cx.notify();
21006 }
21007
21008 fn on_debug_session_event(
21009 &mut self,
21010 _session: Entity<Session>,
21011 event: &SessionEvent,
21012 cx: &mut Context<Self>,
21013 ) {
21014 if let SessionEvent::InvalidateInlineValue = event {
21015 self.refresh_inline_values(cx);
21016 }
21017 }
21018
21019 pub fn refresh_inline_values(&mut self, cx: &mut Context<Self>) {
21020 let Some(project) = self.project.clone() else {
21021 return;
21022 };
21023
21024 if !self.inline_value_cache.enabled {
21025 let inlays = std::mem::take(&mut self.inline_value_cache.inlays);
21026 self.splice_inlays(&inlays, Vec::new(), cx);
21027 return;
21028 }
21029
21030 let current_execution_position = self
21031 .highlighted_rows
21032 .get(&TypeId::of::<ActiveDebugLine>())
21033 .and_then(|lines| lines.last().map(|line| line.range.end));
21034
21035 self.inline_value_cache.refresh_task = cx.spawn(async move |editor, cx| {
21036 let inline_values = editor
21037 .update(cx, |editor, cx| {
21038 let Some(current_execution_position) = current_execution_position else {
21039 return Some(Task::ready(Ok(Vec::new())));
21040 };
21041
21042 let buffer = editor.buffer.read_with(cx, |buffer, cx| {
21043 let snapshot = buffer.snapshot(cx);
21044
21045 let excerpt = snapshot.excerpt_containing(
21046 current_execution_position..current_execution_position,
21047 )?;
21048
21049 editor.buffer.read(cx).buffer(excerpt.buffer_id())
21050 })?;
21051
21052 let range =
21053 buffer.read(cx).anchor_before(0)..current_execution_position.text_anchor;
21054
21055 project.inline_values(buffer, range, cx)
21056 })
21057 .ok()
21058 .flatten()?
21059 .await
21060 .context("refreshing debugger inlays")
21061 .log_err()?;
21062
21063 let mut buffer_inline_values: HashMap<BufferId, Vec<InlayHint>> = HashMap::default();
21064
21065 for (buffer_id, inline_value) in inline_values
21066 .into_iter()
21067 .filter_map(|hint| Some((hint.position.buffer_id?, hint)))
21068 {
21069 buffer_inline_values
21070 .entry(buffer_id)
21071 .or_default()
21072 .push(inline_value);
21073 }
21074
21075 editor
21076 .update(cx, |editor, cx| {
21077 let snapshot = editor.buffer.read(cx).snapshot(cx);
21078 let mut new_inlays = Vec::default();
21079
21080 for (excerpt_id, buffer_snapshot, _) in snapshot.excerpts() {
21081 let buffer_id = buffer_snapshot.remote_id();
21082 buffer_inline_values
21083 .get(&buffer_id)
21084 .into_iter()
21085 .flatten()
21086 .for_each(|hint| {
21087 let inlay = Inlay::debugger(
21088 post_inc(&mut editor.next_inlay_id),
21089 Anchor::in_buffer(excerpt_id, buffer_id, hint.position),
21090 hint.text(),
21091 );
21092 if !inlay.text().chars().contains(&'\n') {
21093 new_inlays.push(inlay);
21094 }
21095 });
21096 }
21097
21098 let mut inlay_ids = new_inlays.iter().map(|inlay| inlay.id).collect();
21099 std::mem::swap(&mut editor.inline_value_cache.inlays, &mut inlay_ids);
21100
21101 editor.splice_inlays(&inlay_ids, new_inlays, cx);
21102 })
21103 .ok()?;
21104 Some(())
21105 });
21106 }
21107
21108 fn on_buffer_event(
21109 &mut self,
21110 multibuffer: &Entity<MultiBuffer>,
21111 event: &multi_buffer::Event,
21112 window: &mut Window,
21113 cx: &mut Context<Self>,
21114 ) {
21115 match event {
21116 multi_buffer::Event::Edited { edited_buffer } => {
21117 self.scrollbar_marker_state.dirty = true;
21118 self.active_indent_guides_state.dirty = true;
21119 self.refresh_active_diagnostics(cx);
21120 self.refresh_code_actions(window, cx);
21121 self.refresh_selected_text_highlights(true, window, cx);
21122 self.refresh_single_line_folds(window, cx);
21123 self.refresh_matching_bracket_highlights(window, cx);
21124 if self.has_active_edit_prediction() {
21125 self.update_visible_edit_prediction(window, cx);
21126 }
21127
21128 if let Some(buffer) = edited_buffer {
21129 if buffer.read(cx).file().is_none() {
21130 cx.emit(EditorEvent::TitleChanged);
21131 }
21132
21133 if self.project.is_some() {
21134 let buffer_id = buffer.read(cx).remote_id();
21135 self.register_buffer(buffer_id, cx);
21136 self.update_lsp_data(Some(buffer_id), window, cx);
21137 self.refresh_inlay_hints(
21138 InlayHintRefreshReason::BufferEdited(buffer_id),
21139 cx,
21140 );
21141 }
21142 }
21143
21144 cx.emit(EditorEvent::BufferEdited);
21145 cx.emit(SearchEvent::MatchesInvalidated);
21146
21147 let Some(project) = &self.project else { return };
21148 let (telemetry, is_via_ssh) = {
21149 let project = project.read(cx);
21150 let telemetry = project.client().telemetry().clone();
21151 let is_via_ssh = project.is_via_remote_server();
21152 (telemetry, is_via_ssh)
21153 };
21154 telemetry.log_edit_event("editor", is_via_ssh);
21155 }
21156 multi_buffer::Event::ExcerptsAdded {
21157 buffer,
21158 predecessor,
21159 excerpts,
21160 } => {
21161 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21162 let buffer_id = buffer.read(cx).remote_id();
21163 if self.buffer.read(cx).diff_for(buffer_id).is_none()
21164 && let Some(project) = &self.project
21165 {
21166 update_uncommitted_diff_for_buffer(
21167 cx.entity(),
21168 project,
21169 [buffer.clone()],
21170 self.buffer.clone(),
21171 cx,
21172 )
21173 .detach();
21174 }
21175 self.update_lsp_data(Some(buffer_id), window, cx);
21176 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21177 cx.emit(EditorEvent::ExcerptsAdded {
21178 buffer: buffer.clone(),
21179 predecessor: *predecessor,
21180 excerpts: excerpts.clone(),
21181 });
21182 }
21183 multi_buffer::Event::ExcerptsRemoved {
21184 ids,
21185 removed_buffer_ids,
21186 } => {
21187 if let Some(inlay_hints) = &mut self.inlay_hints {
21188 inlay_hints.remove_inlay_chunk_data(removed_buffer_ids);
21189 }
21190 self.refresh_inlay_hints(InlayHintRefreshReason::ExcerptsRemoved(ids.clone()), cx);
21191 for buffer_id in removed_buffer_ids {
21192 self.registered_buffers.remove(buffer_id);
21193 }
21194 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21195 cx.emit(EditorEvent::ExcerptsRemoved {
21196 ids: ids.clone(),
21197 removed_buffer_ids: removed_buffer_ids.clone(),
21198 });
21199 }
21200 multi_buffer::Event::ExcerptsEdited {
21201 excerpt_ids,
21202 buffer_ids,
21203 } => {
21204 self.display_map.update(cx, |map, cx| {
21205 map.unfold_buffers(buffer_ids.iter().copied(), cx)
21206 });
21207 cx.emit(EditorEvent::ExcerptsEdited {
21208 ids: excerpt_ids.clone(),
21209 });
21210 }
21211 multi_buffer::Event::ExcerptsExpanded { ids } => {
21212 self.refresh_inlay_hints(InlayHintRefreshReason::NewLinesShown, cx);
21213 self.refresh_document_highlights(cx);
21214 cx.emit(EditorEvent::ExcerptsExpanded { ids: ids.clone() })
21215 }
21216 multi_buffer::Event::Reparsed(buffer_id) => {
21217 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21218 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21219
21220 cx.emit(EditorEvent::Reparsed(*buffer_id));
21221 }
21222 multi_buffer::Event::DiffHunksToggled => {
21223 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21224 }
21225 multi_buffer::Event::LanguageChanged(buffer_id) => {
21226 self.registered_buffers.remove(&buffer_id);
21227 jsx_tag_auto_close::refresh_enabled_in_any_buffer(self, multibuffer, cx);
21228 cx.emit(EditorEvent::Reparsed(*buffer_id));
21229 cx.notify();
21230 }
21231 multi_buffer::Event::DirtyChanged => cx.emit(EditorEvent::DirtyChanged),
21232 multi_buffer::Event::Saved => cx.emit(EditorEvent::Saved),
21233 multi_buffer::Event::FileHandleChanged
21234 | multi_buffer::Event::Reloaded
21235 | multi_buffer::Event::BufferDiffChanged => cx.emit(EditorEvent::TitleChanged),
21236 multi_buffer::Event::DiagnosticsUpdated => {
21237 self.update_diagnostics_state(window, cx);
21238 }
21239 _ => {}
21240 };
21241 }
21242
21243 fn update_diagnostics_state(&mut self, window: &mut Window, cx: &mut Context<'_, Editor>) {
21244 if !self.diagnostics_enabled() {
21245 return;
21246 }
21247 self.refresh_active_diagnostics(cx);
21248 self.refresh_inline_diagnostics(true, window, cx);
21249 self.scrollbar_marker_state.dirty = true;
21250 cx.notify();
21251 }
21252
21253 pub fn start_temporary_diff_override(&mut self) {
21254 self.load_diff_task.take();
21255 self.temporary_diff_override = true;
21256 }
21257
21258 pub fn end_temporary_diff_override(&mut self, cx: &mut Context<Self>) {
21259 self.temporary_diff_override = false;
21260 self.set_render_diff_hunk_controls(Arc::new(render_diff_hunk_controls), cx);
21261 self.buffer.update(cx, |buffer, cx| {
21262 buffer.set_all_diff_hunks_collapsed(cx);
21263 });
21264
21265 if let Some(project) = self.project.clone() {
21266 self.load_diff_task = Some(
21267 update_uncommitted_diff_for_buffer(
21268 cx.entity(),
21269 &project,
21270 self.buffer.read(cx).all_buffers(),
21271 self.buffer.clone(),
21272 cx,
21273 )
21274 .shared(),
21275 );
21276 }
21277 }
21278
21279 fn on_display_map_changed(
21280 &mut self,
21281 _: Entity<DisplayMap>,
21282 _: &mut Window,
21283 cx: &mut Context<Self>,
21284 ) {
21285 cx.notify();
21286 }
21287
21288 fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21289 if self.diagnostics_enabled() {
21290 let new_severity = EditorSettings::get_global(cx)
21291 .diagnostics_max_severity
21292 .unwrap_or(DiagnosticSeverity::Hint);
21293 self.set_max_diagnostics_severity(new_severity, cx);
21294 }
21295 self.tasks_update_task = Some(self.refresh_runnables(window, cx));
21296 self.update_edit_prediction_settings(cx);
21297 self.refresh_edit_prediction(true, false, window, cx);
21298 self.refresh_inline_values(cx);
21299 self.refresh_inlay_hints(
21300 InlayHintRefreshReason::SettingsChange(inlay_hint_settings(
21301 self.selections.newest_anchor().head(),
21302 &self.buffer.read(cx).snapshot(cx),
21303 cx,
21304 )),
21305 cx,
21306 );
21307
21308 let old_cursor_shape = self.cursor_shape;
21309 let old_show_breadcrumbs = self.show_breadcrumbs;
21310
21311 {
21312 let editor_settings = EditorSettings::get_global(cx);
21313 self.scroll_manager.vertical_scroll_margin = editor_settings.vertical_scroll_margin;
21314 self.show_breadcrumbs = editor_settings.toolbar.breadcrumbs;
21315 self.cursor_shape = editor_settings.cursor_shape.unwrap_or_default();
21316 self.hide_mouse_mode = editor_settings.hide_mouse.unwrap_or_default();
21317 }
21318
21319 if old_cursor_shape != self.cursor_shape {
21320 cx.emit(EditorEvent::CursorShapeChanged);
21321 }
21322
21323 if old_show_breadcrumbs != self.show_breadcrumbs {
21324 cx.emit(EditorEvent::BreadcrumbsChanged);
21325 }
21326
21327 let project_settings = ProjectSettings::get_global(cx);
21328 self.buffer_serialization = self
21329 .should_serialize_buffer()
21330 .then(|| BufferSerialization::new(project_settings.session.restore_unsaved_buffers));
21331
21332 if self.mode.is_full() {
21333 let show_inline_diagnostics = project_settings.diagnostics.inline.enabled;
21334 let inline_blame_enabled = project_settings.git.inline_blame.enabled;
21335 if self.show_inline_diagnostics != show_inline_diagnostics {
21336 self.show_inline_diagnostics = show_inline_diagnostics;
21337 self.refresh_inline_diagnostics(false, window, cx);
21338 }
21339
21340 if self.git_blame_inline_enabled != inline_blame_enabled {
21341 self.toggle_git_blame_inline_internal(false, window, cx);
21342 }
21343
21344 let minimap_settings = EditorSettings::get_global(cx).minimap;
21345 if self.minimap_visibility != MinimapVisibility::Disabled {
21346 if self.minimap_visibility.settings_visibility()
21347 != minimap_settings.minimap_enabled()
21348 {
21349 self.set_minimap_visibility(
21350 MinimapVisibility::for_mode(self.mode(), cx),
21351 window,
21352 cx,
21353 );
21354 } else if let Some(minimap_entity) = self.minimap.as_ref() {
21355 minimap_entity.update(cx, |minimap_editor, cx| {
21356 minimap_editor.update_minimap_configuration(minimap_settings, cx)
21357 })
21358 }
21359 }
21360 }
21361
21362 if let Some(inlay_splice) = self.colors.as_mut().and_then(|colors| {
21363 colors.render_mode_updated(EditorSettings::get_global(cx).lsp_document_colors)
21364 }) {
21365 if !inlay_splice.is_empty() {
21366 self.splice_inlays(&inlay_splice.to_remove, inlay_splice.to_insert, cx);
21367 }
21368 self.refresh_colors_for_visible_range(None, window, cx);
21369 }
21370
21371 cx.notify();
21372 }
21373
21374 pub fn set_searchable(&mut self, searchable: bool) {
21375 self.searchable = searchable;
21376 }
21377
21378 pub fn searchable(&self) -> bool {
21379 self.searchable
21380 }
21381
21382 pub fn open_excerpts_in_split(
21383 &mut self,
21384 _: &OpenExcerptsSplit,
21385 window: &mut Window,
21386 cx: &mut Context<Self>,
21387 ) {
21388 self.open_excerpts_common(None, true, window, cx)
21389 }
21390
21391 pub fn open_excerpts(&mut self, _: &OpenExcerpts, window: &mut Window, cx: &mut Context<Self>) {
21392 self.open_excerpts_common(None, false, window, cx)
21393 }
21394
21395 fn open_excerpts_common(
21396 &mut self,
21397 jump_data: Option<JumpData>,
21398 split: bool,
21399 window: &mut Window,
21400 cx: &mut Context<Self>,
21401 ) {
21402 let Some(workspace) = self.workspace() else {
21403 cx.propagate();
21404 return;
21405 };
21406
21407 if self.buffer.read(cx).is_singleton() {
21408 cx.propagate();
21409 return;
21410 }
21411
21412 let mut new_selections_by_buffer = HashMap::default();
21413 match &jump_data {
21414 Some(JumpData::MultiBufferPoint {
21415 excerpt_id,
21416 position,
21417 anchor,
21418 line_offset_from_top,
21419 }) => {
21420 let multi_buffer_snapshot = self.buffer.read(cx).snapshot(cx);
21421 if let Some(buffer) = multi_buffer_snapshot
21422 .buffer_id_for_excerpt(*excerpt_id)
21423 .and_then(|buffer_id| self.buffer.read(cx).buffer(buffer_id))
21424 {
21425 let buffer_snapshot = buffer.read(cx).snapshot();
21426 let jump_to_point = if buffer_snapshot.can_resolve(anchor) {
21427 language::ToPoint::to_point(anchor, &buffer_snapshot)
21428 } else {
21429 buffer_snapshot.clip_point(*position, Bias::Left)
21430 };
21431 let jump_to_offset = buffer_snapshot.point_to_offset(jump_to_point);
21432 new_selections_by_buffer.insert(
21433 buffer,
21434 (
21435 vec![jump_to_offset..jump_to_offset],
21436 Some(*line_offset_from_top),
21437 ),
21438 );
21439 }
21440 }
21441 Some(JumpData::MultiBufferRow {
21442 row,
21443 line_offset_from_top,
21444 }) => {
21445 let point = MultiBufferPoint::new(row.0, 0);
21446 if let Some((buffer, buffer_point, _)) =
21447 self.buffer.read(cx).point_to_buffer_point(point, cx)
21448 {
21449 let buffer_offset = buffer.read(cx).point_to_offset(buffer_point);
21450 new_selections_by_buffer
21451 .entry(buffer)
21452 .or_insert((Vec::new(), Some(*line_offset_from_top)))
21453 .0
21454 .push(buffer_offset..buffer_offset)
21455 }
21456 }
21457 None => {
21458 let selections = self.selections.all::<usize>(&self.display_snapshot(cx));
21459 let multi_buffer = self.buffer.read(cx);
21460 for selection in selections {
21461 for (snapshot, range, _, anchor) in multi_buffer
21462 .snapshot(cx)
21463 .range_to_buffer_ranges_with_deleted_hunks(selection.range())
21464 {
21465 if let Some(anchor) = anchor {
21466 let Some(buffer_handle) = multi_buffer.buffer_for_anchor(anchor, cx)
21467 else {
21468 continue;
21469 };
21470 let offset = text::ToOffset::to_offset(
21471 &anchor.text_anchor,
21472 &buffer_handle.read(cx).snapshot(),
21473 );
21474 let range = offset..offset;
21475 new_selections_by_buffer
21476 .entry(buffer_handle)
21477 .or_insert((Vec::new(), None))
21478 .0
21479 .push(range)
21480 } else {
21481 let Some(buffer_handle) = multi_buffer.buffer(snapshot.remote_id())
21482 else {
21483 continue;
21484 };
21485 new_selections_by_buffer
21486 .entry(buffer_handle)
21487 .or_insert((Vec::new(), None))
21488 .0
21489 .push(range)
21490 }
21491 }
21492 }
21493 }
21494 }
21495
21496 new_selections_by_buffer
21497 .retain(|buffer, _| Self::can_open_excerpts_in_file(buffer.read(cx).file()));
21498
21499 if new_selections_by_buffer.is_empty() {
21500 return;
21501 }
21502
21503 // We defer the pane interaction because we ourselves are a workspace item
21504 // and activating a new item causes the pane to call a method on us reentrantly,
21505 // which panics if we're on the stack.
21506 window.defer(cx, move |window, cx| {
21507 workspace.update(cx, |workspace, cx| {
21508 let pane = if split {
21509 workspace.adjacent_pane(window, cx)
21510 } else {
21511 workspace.active_pane().clone()
21512 };
21513
21514 for (buffer, (ranges, scroll_offset)) in new_selections_by_buffer {
21515 let editor = buffer
21516 .read(cx)
21517 .file()
21518 .is_none()
21519 .then(|| {
21520 // Handle file-less buffers separately: those are not really the project items, so won't have a project path or entity id,
21521 // so `workspace.open_project_item` will never find them, always opening a new editor.
21522 // Instead, we try to activate the existing editor in the pane first.
21523 let (editor, pane_item_index) =
21524 pane.read(cx).items().enumerate().find_map(|(i, item)| {
21525 let editor = item.downcast::<Editor>()?;
21526 let singleton_buffer =
21527 editor.read(cx).buffer().read(cx).as_singleton()?;
21528 if singleton_buffer == buffer {
21529 Some((editor, i))
21530 } else {
21531 None
21532 }
21533 })?;
21534 pane.update(cx, |pane, cx| {
21535 pane.activate_item(pane_item_index, true, true, window, cx)
21536 });
21537 Some(editor)
21538 })
21539 .flatten()
21540 .unwrap_or_else(|| {
21541 workspace.open_project_item::<Self>(
21542 pane.clone(),
21543 buffer,
21544 true,
21545 true,
21546 window,
21547 cx,
21548 )
21549 });
21550
21551 editor.update(cx, |editor, cx| {
21552 let autoscroll = match scroll_offset {
21553 Some(scroll_offset) => Autoscroll::top_relative(scroll_offset as usize),
21554 None => Autoscroll::newest(),
21555 };
21556 let nav_history = editor.nav_history.take();
21557 editor.change_selections(
21558 SelectionEffects::scroll(autoscroll),
21559 window,
21560 cx,
21561 |s| {
21562 s.select_ranges(ranges);
21563 },
21564 );
21565 editor.nav_history = nav_history;
21566 });
21567 }
21568 })
21569 });
21570 }
21571
21572 // For now, don't allow opening excerpts in buffers that aren't backed by
21573 // regular project files.
21574 fn can_open_excerpts_in_file(file: Option<&Arc<dyn language::File>>) -> bool {
21575 file.is_none_or(|file| project::File::from_dyn(Some(file)).is_some())
21576 }
21577
21578 fn marked_text_ranges(&self, cx: &App) -> Option<Vec<Range<OffsetUtf16>>> {
21579 let snapshot = self.buffer.read(cx).read(cx);
21580 let (_, ranges) = self.text_highlights::<InputComposition>(cx)?;
21581 Some(
21582 ranges
21583 .iter()
21584 .map(move |range| {
21585 range.start.to_offset_utf16(&snapshot)..range.end.to_offset_utf16(&snapshot)
21586 })
21587 .collect(),
21588 )
21589 }
21590
21591 fn selection_replacement_ranges(
21592 &self,
21593 range: Range<OffsetUtf16>,
21594 cx: &mut App,
21595 ) -> Vec<Range<OffsetUtf16>> {
21596 let selections = self
21597 .selections
21598 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21599 let newest_selection = selections
21600 .iter()
21601 .max_by_key(|selection| selection.id)
21602 .unwrap();
21603 let start_delta = range.start.0 as isize - newest_selection.start.0 as isize;
21604 let end_delta = range.end.0 as isize - newest_selection.end.0 as isize;
21605 let snapshot = self.buffer.read(cx).read(cx);
21606 selections
21607 .into_iter()
21608 .map(|mut selection| {
21609 selection.start.0 =
21610 (selection.start.0 as isize).saturating_add(start_delta) as usize;
21611 selection.end.0 = (selection.end.0 as isize).saturating_add(end_delta) as usize;
21612 snapshot.clip_offset_utf16(selection.start, Bias::Left)
21613 ..snapshot.clip_offset_utf16(selection.end, Bias::Right)
21614 })
21615 .collect()
21616 }
21617
21618 fn report_editor_event(
21619 &self,
21620 reported_event: ReportEditorEvent,
21621 file_extension: Option<String>,
21622 cx: &App,
21623 ) {
21624 if cfg!(any(test, feature = "test-support")) {
21625 return;
21626 }
21627
21628 let Some(project) = &self.project else { return };
21629
21630 // If None, we are in a file without an extension
21631 let file = self
21632 .buffer
21633 .read(cx)
21634 .as_singleton()
21635 .and_then(|b| b.read(cx).file());
21636 let file_extension = file_extension.or(file
21637 .as_ref()
21638 .and_then(|file| Path::new(file.file_name(cx)).extension())
21639 .and_then(|e| e.to_str())
21640 .map(|a| a.to_string()));
21641
21642 let vim_mode = vim_flavor(cx).is_some();
21643
21644 let edit_predictions_provider = all_language_settings(file, cx).edit_predictions.provider;
21645 let copilot_enabled = edit_predictions_provider
21646 == language::language_settings::EditPredictionProvider::Copilot;
21647 let copilot_enabled_for_language = self
21648 .buffer
21649 .read(cx)
21650 .language_settings(cx)
21651 .show_edit_predictions;
21652
21653 let project = project.read(cx);
21654 let event_type = reported_event.event_type();
21655
21656 if let ReportEditorEvent::Saved { auto_saved } = reported_event {
21657 telemetry::event!(
21658 event_type,
21659 type = if auto_saved {"autosave"} else {"manual"},
21660 file_extension,
21661 vim_mode,
21662 copilot_enabled,
21663 copilot_enabled_for_language,
21664 edit_predictions_provider,
21665 is_via_ssh = project.is_via_remote_server(),
21666 );
21667 } else {
21668 telemetry::event!(
21669 event_type,
21670 file_extension,
21671 vim_mode,
21672 copilot_enabled,
21673 copilot_enabled_for_language,
21674 edit_predictions_provider,
21675 is_via_ssh = project.is_via_remote_server(),
21676 );
21677 };
21678 }
21679
21680 /// Copy the highlighted chunks to the clipboard as JSON. The format is an array of lines,
21681 /// with each line being an array of {text, highlight} objects.
21682 fn copy_highlight_json(
21683 &mut self,
21684 _: &CopyHighlightJson,
21685 window: &mut Window,
21686 cx: &mut Context<Self>,
21687 ) {
21688 #[derive(Serialize)]
21689 struct Chunk<'a> {
21690 text: String,
21691 highlight: Option<&'a str>,
21692 }
21693
21694 let snapshot = self.buffer.read(cx).snapshot(cx);
21695 let range = self
21696 .selected_text_range(false, window, cx)
21697 .and_then(|selection| {
21698 if selection.range.is_empty() {
21699 None
21700 } else {
21701 Some(
21702 snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.start))
21703 ..snapshot.offset_utf16_to_offset(OffsetUtf16(selection.range.end)),
21704 )
21705 }
21706 })
21707 .unwrap_or_else(|| 0..snapshot.len());
21708
21709 let chunks = snapshot.chunks(range, true);
21710 let mut lines = Vec::new();
21711 let mut line: VecDeque<Chunk> = VecDeque::new();
21712
21713 let Some(style) = self.style.as_ref() else {
21714 return;
21715 };
21716
21717 for chunk in chunks {
21718 let highlight = chunk
21719 .syntax_highlight_id
21720 .and_then(|id| id.name(&style.syntax));
21721 let mut chunk_lines = chunk.text.split('\n').peekable();
21722 while let Some(text) = chunk_lines.next() {
21723 let mut merged_with_last_token = false;
21724 if let Some(last_token) = line.back_mut()
21725 && last_token.highlight == highlight
21726 {
21727 last_token.text.push_str(text);
21728 merged_with_last_token = true;
21729 }
21730
21731 if !merged_with_last_token {
21732 line.push_back(Chunk {
21733 text: text.into(),
21734 highlight,
21735 });
21736 }
21737
21738 if chunk_lines.peek().is_some() {
21739 if line.len() > 1 && line.front().unwrap().text.is_empty() {
21740 line.pop_front();
21741 }
21742 if line.len() > 1 && line.back().unwrap().text.is_empty() {
21743 line.pop_back();
21744 }
21745
21746 lines.push(mem::take(&mut line));
21747 }
21748 }
21749 }
21750
21751 let Some(lines) = serde_json::to_string_pretty(&lines).log_err() else {
21752 return;
21753 };
21754 cx.write_to_clipboard(ClipboardItem::new_string(lines));
21755 }
21756
21757 pub fn open_context_menu(
21758 &mut self,
21759 _: &OpenContextMenu,
21760 window: &mut Window,
21761 cx: &mut Context<Self>,
21762 ) {
21763 self.request_autoscroll(Autoscroll::newest(), cx);
21764 let position = self
21765 .selections
21766 .newest_display(&self.display_snapshot(cx))
21767 .start;
21768 mouse_context_menu::deploy_context_menu(self, None, position, window, cx);
21769 }
21770
21771 pub fn replay_insert_event(
21772 &mut self,
21773 text: &str,
21774 relative_utf16_range: Option<Range<isize>>,
21775 window: &mut Window,
21776 cx: &mut Context<Self>,
21777 ) {
21778 if !self.input_enabled {
21779 cx.emit(EditorEvent::InputIgnored { text: text.into() });
21780 return;
21781 }
21782 if let Some(relative_utf16_range) = relative_utf16_range {
21783 let selections = self
21784 .selections
21785 .all::<OffsetUtf16>(&self.display_snapshot(cx));
21786 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21787 let new_ranges = selections.into_iter().map(|range| {
21788 let start = OffsetUtf16(
21789 range
21790 .head()
21791 .0
21792 .saturating_add_signed(relative_utf16_range.start),
21793 );
21794 let end = OffsetUtf16(
21795 range
21796 .head()
21797 .0
21798 .saturating_add_signed(relative_utf16_range.end),
21799 );
21800 start..end
21801 });
21802 s.select_ranges(new_ranges);
21803 });
21804 }
21805
21806 self.handle_input(text, window, cx);
21807 }
21808
21809 pub fn is_focused(&self, window: &Window) -> bool {
21810 self.focus_handle.is_focused(window)
21811 }
21812
21813 fn handle_focus(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21814 cx.emit(EditorEvent::Focused);
21815
21816 if let Some(descendant) = self
21817 .last_focused_descendant
21818 .take()
21819 .and_then(|descendant| descendant.upgrade())
21820 {
21821 window.focus(&descendant);
21822 } else {
21823 if let Some(blame) = self.blame.as_ref() {
21824 blame.update(cx, GitBlame::focus)
21825 }
21826
21827 self.blink_manager.update(cx, BlinkManager::enable);
21828 self.show_cursor_names(window, cx);
21829 self.buffer.update(cx, |buffer, cx| {
21830 buffer.finalize_last_transaction(cx);
21831 if self.leader_id.is_none() {
21832 buffer.set_active_selections(
21833 &self.selections.disjoint_anchors_arc(),
21834 self.selections.line_mode(),
21835 self.cursor_shape,
21836 cx,
21837 );
21838 }
21839 });
21840 }
21841 }
21842
21843 fn handle_focus_in(&mut self, _: &mut Window, cx: &mut Context<Self>) {
21844 cx.emit(EditorEvent::FocusedIn)
21845 }
21846
21847 fn handle_focus_out(
21848 &mut self,
21849 event: FocusOutEvent,
21850 _window: &mut Window,
21851 cx: &mut Context<Self>,
21852 ) {
21853 if event.blurred != self.focus_handle {
21854 self.last_focused_descendant = Some(event.blurred);
21855 }
21856 self.selection_drag_state = SelectionDragState::None;
21857 self.refresh_inlay_hints(InlayHintRefreshReason::ModifiersChanged(false), cx);
21858 }
21859
21860 pub fn handle_blur(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21861 self.blink_manager.update(cx, BlinkManager::disable);
21862 self.buffer
21863 .update(cx, |buffer, cx| buffer.remove_active_selections(cx));
21864
21865 if let Some(blame) = self.blame.as_ref() {
21866 blame.update(cx, GitBlame::blur)
21867 }
21868 if !self.hover_state.focused(window, cx) {
21869 hide_hover(self, cx);
21870 }
21871 if !self
21872 .context_menu
21873 .borrow()
21874 .as_ref()
21875 .is_some_and(|context_menu| context_menu.focused(window, cx))
21876 {
21877 self.hide_context_menu(window, cx);
21878 }
21879 self.take_active_edit_prediction(cx);
21880 cx.emit(EditorEvent::Blurred);
21881 cx.notify();
21882 }
21883
21884 pub fn observe_pending_input(&mut self, window: &mut Window, cx: &mut Context<Self>) {
21885 let mut pending: String = window
21886 .pending_input_keystrokes()
21887 .into_iter()
21888 .flatten()
21889 .filter_map(|keystroke| {
21890 if keystroke.modifiers.is_subset_of(&Modifiers::shift()) {
21891 keystroke.key_char.clone()
21892 } else {
21893 None
21894 }
21895 })
21896 .collect();
21897
21898 if !self.input_enabled || self.read_only || !self.focus_handle.is_focused(window) {
21899 pending = "".to_string();
21900 }
21901
21902 let existing_pending = self
21903 .text_highlights::<PendingInput>(cx)
21904 .map(|(_, ranges)| ranges.to_vec());
21905 if existing_pending.is_none() && pending.is_empty() {
21906 return;
21907 }
21908 let transaction =
21909 self.transact(window, cx, |this, window, cx| {
21910 let selections = this.selections.all::<usize>(&this.display_snapshot(cx));
21911 let edits = selections
21912 .iter()
21913 .map(|selection| (selection.end..selection.end, pending.clone()));
21914 this.edit(edits, cx);
21915 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
21916 s.select_ranges(selections.into_iter().enumerate().map(|(ix, sel)| {
21917 sel.start + ix * pending.len()..sel.end + ix * pending.len()
21918 }));
21919 });
21920 if let Some(existing_ranges) = existing_pending {
21921 let edits = existing_ranges.iter().map(|range| (range.clone(), ""));
21922 this.edit(edits, cx);
21923 }
21924 });
21925
21926 let snapshot = self.snapshot(window, cx);
21927 let ranges = self
21928 .selections
21929 .all::<usize>(&snapshot.display_snapshot)
21930 .into_iter()
21931 .map(|selection| {
21932 snapshot.buffer_snapshot().anchor_after(selection.end)
21933 ..snapshot
21934 .buffer_snapshot()
21935 .anchor_before(selection.end + pending.len())
21936 })
21937 .collect();
21938
21939 if pending.is_empty() {
21940 self.clear_highlights::<PendingInput>(cx);
21941 } else {
21942 self.highlight_text::<PendingInput>(
21943 ranges,
21944 HighlightStyle {
21945 underline: Some(UnderlineStyle {
21946 thickness: px(1.),
21947 color: None,
21948 wavy: false,
21949 }),
21950 ..Default::default()
21951 },
21952 cx,
21953 );
21954 }
21955
21956 self.ime_transaction = self.ime_transaction.or(transaction);
21957 if let Some(transaction) = self.ime_transaction {
21958 self.buffer.update(cx, |buffer, cx| {
21959 buffer.group_until_transaction(transaction, cx);
21960 });
21961 }
21962
21963 if self.text_highlights::<PendingInput>(cx).is_none() {
21964 self.ime_transaction.take();
21965 }
21966 }
21967
21968 pub fn register_action_renderer(
21969 &mut self,
21970 listener: impl Fn(&Editor, &mut Window, &mut Context<Editor>) + 'static,
21971 ) -> Subscription {
21972 let id = self.next_editor_action_id.post_inc();
21973 self.editor_actions
21974 .borrow_mut()
21975 .insert(id, Box::new(listener));
21976
21977 let editor_actions = self.editor_actions.clone();
21978 Subscription::new(move || {
21979 editor_actions.borrow_mut().remove(&id);
21980 })
21981 }
21982
21983 pub fn register_action<A: Action>(
21984 &mut self,
21985 listener: impl Fn(&A, &mut Window, &mut App) + 'static,
21986 ) -> Subscription {
21987 let id = self.next_editor_action_id.post_inc();
21988 let listener = Arc::new(listener);
21989 self.editor_actions.borrow_mut().insert(
21990 id,
21991 Box::new(move |_, window, _| {
21992 let listener = listener.clone();
21993 window.on_action(TypeId::of::<A>(), move |action, phase, window, cx| {
21994 let action = action.downcast_ref().unwrap();
21995 if phase == DispatchPhase::Bubble {
21996 listener(action, window, cx)
21997 }
21998 })
21999 }),
22000 );
22001
22002 let editor_actions = self.editor_actions.clone();
22003 Subscription::new(move || {
22004 editor_actions.borrow_mut().remove(&id);
22005 })
22006 }
22007
22008 pub fn file_header_size(&self) -> u32 {
22009 FILE_HEADER_HEIGHT
22010 }
22011
22012 pub fn restore(
22013 &mut self,
22014 revert_changes: HashMap<BufferId, Vec<(Range<text::Anchor>, Rope)>>,
22015 window: &mut Window,
22016 cx: &mut Context<Self>,
22017 ) {
22018 let workspace = self.workspace();
22019 let project = self.project();
22020 let save_tasks = self.buffer().update(cx, |multi_buffer, cx| {
22021 let mut tasks = Vec::new();
22022 for (buffer_id, changes) in revert_changes {
22023 if let Some(buffer) = multi_buffer.buffer(buffer_id) {
22024 buffer.update(cx, |buffer, cx| {
22025 buffer.edit(
22026 changes
22027 .into_iter()
22028 .map(|(range, text)| (range, text.to_string())),
22029 None,
22030 cx,
22031 );
22032 });
22033
22034 if let Some(project) =
22035 project.filter(|_| multi_buffer.all_diff_hunks_expanded())
22036 {
22037 project.update(cx, |project, cx| {
22038 tasks.push((buffer.clone(), project.save_buffer(buffer, cx)));
22039 })
22040 }
22041 }
22042 }
22043 tasks
22044 });
22045 cx.spawn_in(window, async move |_, cx| {
22046 for (buffer, task) in save_tasks {
22047 let result = task.await;
22048 if result.is_err() {
22049 let Some(path) = buffer
22050 .read_with(cx, |buffer, cx| buffer.project_path(cx))
22051 .ok()
22052 else {
22053 continue;
22054 };
22055 if let Some((workspace, path)) = workspace.as_ref().zip(path) {
22056 let Some(task) = cx
22057 .update_window_entity(workspace, |workspace, window, cx| {
22058 workspace
22059 .open_path_preview(path, None, false, false, false, window, cx)
22060 })
22061 .ok()
22062 else {
22063 continue;
22064 };
22065 task.await.log_err();
22066 }
22067 }
22068 }
22069 })
22070 .detach();
22071 self.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
22072 selections.refresh()
22073 });
22074 }
22075
22076 pub fn to_pixel_point(
22077 &self,
22078 source: multi_buffer::Anchor,
22079 editor_snapshot: &EditorSnapshot,
22080 window: &mut Window,
22081 ) -> Option<gpui::Point<Pixels>> {
22082 let source_point = source.to_display_point(editor_snapshot);
22083 self.display_to_pixel_point(source_point, editor_snapshot, window)
22084 }
22085
22086 pub fn display_to_pixel_point(
22087 &self,
22088 source: DisplayPoint,
22089 editor_snapshot: &EditorSnapshot,
22090 window: &mut Window,
22091 ) -> Option<gpui::Point<Pixels>> {
22092 let line_height = self.style()?.text.line_height_in_pixels(window.rem_size());
22093 let text_layout_details = self.text_layout_details(window);
22094 let scroll_top = text_layout_details
22095 .scroll_anchor
22096 .scroll_position(editor_snapshot)
22097 .y;
22098
22099 if source.row().as_f64() < scroll_top.floor() {
22100 return None;
22101 }
22102 let source_x = editor_snapshot.x_for_display_point(source, &text_layout_details);
22103 let source_y = line_height * (source.row().as_f64() - scroll_top) as f32;
22104 Some(gpui::Point::new(source_x, source_y))
22105 }
22106
22107 pub fn has_visible_completions_menu(&self) -> bool {
22108 !self.edit_prediction_preview_is_active()
22109 && self.context_menu.borrow().as_ref().is_some_and(|menu| {
22110 menu.visible() && matches!(menu, CodeContextMenu::Completions(_))
22111 })
22112 }
22113
22114 pub fn register_addon<T: Addon>(&mut self, instance: T) {
22115 if self.mode.is_minimap() {
22116 return;
22117 }
22118 self.addons
22119 .insert(std::any::TypeId::of::<T>(), Box::new(instance));
22120 }
22121
22122 pub fn unregister_addon<T: Addon>(&mut self) {
22123 self.addons.remove(&std::any::TypeId::of::<T>());
22124 }
22125
22126 pub fn addon<T: Addon>(&self) -> Option<&T> {
22127 let type_id = std::any::TypeId::of::<T>();
22128 self.addons
22129 .get(&type_id)
22130 .and_then(|item| item.to_any().downcast_ref::<T>())
22131 }
22132
22133 pub fn addon_mut<T: Addon>(&mut self) -> Option<&mut T> {
22134 let type_id = std::any::TypeId::of::<T>();
22135 self.addons
22136 .get_mut(&type_id)
22137 .and_then(|item| item.to_any_mut()?.downcast_mut::<T>())
22138 }
22139
22140 fn character_dimensions(&self, window: &mut Window) -> CharacterDimensions {
22141 let text_layout_details = self.text_layout_details(window);
22142 let style = &text_layout_details.editor_style;
22143 let font_id = window.text_system().resolve_font(&style.text.font());
22144 let font_size = style.text.font_size.to_pixels(window.rem_size());
22145 let line_height = style.text.line_height_in_pixels(window.rem_size());
22146 let em_width = window.text_system().em_width(font_id, font_size).unwrap();
22147 let em_advance = window.text_system().em_advance(font_id, font_size).unwrap();
22148
22149 CharacterDimensions {
22150 em_width,
22151 em_advance,
22152 line_height,
22153 }
22154 }
22155
22156 pub fn wait_for_diff_to_load(&self) -> Option<Shared<Task<()>>> {
22157 self.load_diff_task.clone()
22158 }
22159
22160 fn read_metadata_from_db(
22161 &mut self,
22162 item_id: u64,
22163 workspace_id: WorkspaceId,
22164 window: &mut Window,
22165 cx: &mut Context<Editor>,
22166 ) {
22167 if self.buffer_kind(cx) == ItemBufferKind::Singleton
22168 && !self.mode.is_minimap()
22169 && WorkspaceSettings::get(None, cx).restore_on_startup != RestoreOnStartupBehavior::None
22170 {
22171 let buffer_snapshot = OnceCell::new();
22172
22173 if let Some(folds) = DB.get_editor_folds(item_id, workspace_id).log_err()
22174 && !folds.is_empty()
22175 {
22176 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22177 self.fold_ranges(
22178 folds
22179 .into_iter()
22180 .map(|(start, end)| {
22181 snapshot.clip_offset(start, Bias::Left)
22182 ..snapshot.clip_offset(end, Bias::Right)
22183 })
22184 .collect(),
22185 false,
22186 window,
22187 cx,
22188 );
22189 }
22190
22191 if let Some(selections) = DB.get_editor_selections(item_id, workspace_id).log_err()
22192 && !selections.is_empty()
22193 {
22194 let snapshot = buffer_snapshot.get_or_init(|| self.buffer.read(cx).snapshot(cx));
22195 // skip adding the initial selection to selection history
22196 self.selection_history.mode = SelectionHistoryMode::Skipping;
22197 self.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
22198 s.select_ranges(selections.into_iter().map(|(start, end)| {
22199 snapshot.clip_offset(start, Bias::Left)
22200 ..snapshot.clip_offset(end, Bias::Right)
22201 }));
22202 });
22203 self.selection_history.mode = SelectionHistoryMode::Normal;
22204 };
22205 }
22206
22207 self.read_scroll_position_from_db(item_id, workspace_id, window, cx);
22208 }
22209
22210 fn update_lsp_data(
22211 &mut self,
22212 for_buffer: Option<BufferId>,
22213 window: &mut Window,
22214 cx: &mut Context<'_, Self>,
22215 ) {
22216 self.pull_diagnostics(for_buffer, window, cx);
22217 self.refresh_colors_for_visible_range(for_buffer, window, cx);
22218 }
22219
22220 fn register_visible_buffers(&mut self, cx: &mut Context<Self>) {
22221 if self.ignore_lsp_data() {
22222 return;
22223 }
22224 for (_, (visible_buffer, _, _)) in self.visible_excerpts(cx) {
22225 self.register_buffer(visible_buffer.read(cx).remote_id(), cx);
22226 }
22227 }
22228
22229 fn register_buffer(&mut self, buffer_id: BufferId, cx: &mut Context<Self>) {
22230 if self.ignore_lsp_data() {
22231 return;
22232 }
22233
22234 if !self.registered_buffers.contains_key(&buffer_id)
22235 && let Some(project) = self.project.as_ref()
22236 {
22237 if let Some(buffer) = self.buffer.read(cx).buffer(buffer_id) {
22238 project.update(cx, |project, cx| {
22239 self.registered_buffers.insert(
22240 buffer_id,
22241 project.register_buffer_with_language_servers(&buffer, cx),
22242 );
22243 });
22244 } else {
22245 self.registered_buffers.remove(&buffer_id);
22246 }
22247 }
22248 }
22249
22250 fn ignore_lsp_data(&self) -> bool {
22251 // `ActiveDiagnostic::All` is a special mode where editor's diagnostics are managed by the external view,
22252 // skip any LSP updates for it.
22253 self.active_diagnostics == ActiveDiagnostic::All || !self.mode().is_full()
22254 }
22255}
22256
22257fn edit_for_markdown_paste<'a>(
22258 buffer: &MultiBufferSnapshot,
22259 range: Range<usize>,
22260 to_insert: &'a str,
22261 url: Option<url::Url>,
22262) -> (Range<usize>, Cow<'a, str>) {
22263 if url.is_none() {
22264 return (range, Cow::Borrowed(to_insert));
22265 };
22266
22267 let old_text = buffer.text_for_range(range.clone()).collect::<String>();
22268
22269 let new_text = if range.is_empty() || url::Url::parse(&old_text).is_ok() {
22270 Cow::Borrowed(to_insert)
22271 } else {
22272 Cow::Owned(format!("[{old_text}]({to_insert})"))
22273 };
22274 (range, new_text)
22275}
22276
22277#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
22278pub enum VimFlavor {
22279 Vim,
22280 Helix,
22281}
22282
22283pub fn vim_flavor(cx: &App) -> Option<VimFlavor> {
22284 if vim_mode_setting::HelixModeSetting::try_get(cx)
22285 .map(|helix_mode| helix_mode.0)
22286 .unwrap_or(false)
22287 {
22288 Some(VimFlavor::Helix)
22289 } else if vim_mode_setting::VimModeSetting::try_get(cx)
22290 .map(|vim_mode| vim_mode.0)
22291 .unwrap_or(false)
22292 {
22293 Some(VimFlavor::Vim)
22294 } else {
22295 None // neither vim nor helix mode
22296 }
22297}
22298
22299fn process_completion_for_edit(
22300 completion: &Completion,
22301 intent: CompletionIntent,
22302 buffer: &Entity<Buffer>,
22303 cursor_position: &text::Anchor,
22304 cx: &mut Context<Editor>,
22305) -> CompletionEdit {
22306 let buffer = buffer.read(cx);
22307 let buffer_snapshot = buffer.snapshot();
22308 let (snippet, new_text) = if completion.is_snippet() {
22309 let mut snippet_source = completion.new_text.clone();
22310 // Workaround for typescript language server issues so that methods don't expand within
22311 // strings and functions with type expressions. The previous point is used because the query
22312 // for function identifier doesn't match when the cursor is immediately after. See PR #30312
22313 let previous_point = text::ToPoint::to_point(cursor_position, &buffer_snapshot);
22314 let previous_point = if previous_point.column > 0 {
22315 cursor_position.to_previous_offset(&buffer_snapshot)
22316 } else {
22317 cursor_position.to_offset(&buffer_snapshot)
22318 };
22319 if let Some(scope) = buffer_snapshot.language_scope_at(previous_point)
22320 && scope.prefers_label_for_snippet_in_completion()
22321 && let Some(label) = completion.label()
22322 && matches!(
22323 completion.kind(),
22324 Some(CompletionItemKind::FUNCTION) | Some(CompletionItemKind::METHOD)
22325 )
22326 {
22327 snippet_source = label;
22328 }
22329 match Snippet::parse(&snippet_source).log_err() {
22330 Some(parsed_snippet) => (Some(parsed_snippet.clone()), parsed_snippet.text),
22331 None => (None, completion.new_text.clone()),
22332 }
22333 } else {
22334 (None, completion.new_text.clone())
22335 };
22336
22337 let mut range_to_replace = {
22338 let replace_range = &completion.replace_range;
22339 if let CompletionSource::Lsp {
22340 insert_range: Some(insert_range),
22341 ..
22342 } = &completion.source
22343 {
22344 debug_assert_eq!(
22345 insert_range.start, replace_range.start,
22346 "insert_range and replace_range should start at the same position"
22347 );
22348 debug_assert!(
22349 insert_range
22350 .start
22351 .cmp(cursor_position, &buffer_snapshot)
22352 .is_le(),
22353 "insert_range should start before or at cursor position"
22354 );
22355 debug_assert!(
22356 replace_range
22357 .start
22358 .cmp(cursor_position, &buffer_snapshot)
22359 .is_le(),
22360 "replace_range should start before or at cursor position"
22361 );
22362
22363 let should_replace = match intent {
22364 CompletionIntent::CompleteWithInsert => false,
22365 CompletionIntent::CompleteWithReplace => true,
22366 CompletionIntent::Complete | CompletionIntent::Compose => {
22367 let insert_mode =
22368 language_settings(buffer.language().map(|l| l.name()), buffer.file(), cx)
22369 .completions
22370 .lsp_insert_mode;
22371 match insert_mode {
22372 LspInsertMode::Insert => false,
22373 LspInsertMode::Replace => true,
22374 LspInsertMode::ReplaceSubsequence => {
22375 let mut text_to_replace = buffer.chars_for_range(
22376 buffer.anchor_before(replace_range.start)
22377 ..buffer.anchor_after(replace_range.end),
22378 );
22379 let mut current_needle = text_to_replace.next();
22380 for haystack_ch in completion.label.text.chars() {
22381 if let Some(needle_ch) = current_needle
22382 && haystack_ch.eq_ignore_ascii_case(&needle_ch)
22383 {
22384 current_needle = text_to_replace.next();
22385 }
22386 }
22387 current_needle.is_none()
22388 }
22389 LspInsertMode::ReplaceSuffix => {
22390 if replace_range
22391 .end
22392 .cmp(cursor_position, &buffer_snapshot)
22393 .is_gt()
22394 {
22395 let range_after_cursor = *cursor_position..replace_range.end;
22396 let text_after_cursor = buffer
22397 .text_for_range(
22398 buffer.anchor_before(range_after_cursor.start)
22399 ..buffer.anchor_after(range_after_cursor.end),
22400 )
22401 .collect::<String>()
22402 .to_ascii_lowercase();
22403 completion
22404 .label
22405 .text
22406 .to_ascii_lowercase()
22407 .ends_with(&text_after_cursor)
22408 } else {
22409 true
22410 }
22411 }
22412 }
22413 }
22414 };
22415
22416 if should_replace {
22417 replace_range.clone()
22418 } else {
22419 insert_range.clone()
22420 }
22421 } else {
22422 replace_range.clone()
22423 }
22424 };
22425
22426 if range_to_replace
22427 .end
22428 .cmp(cursor_position, &buffer_snapshot)
22429 .is_lt()
22430 {
22431 range_to_replace.end = *cursor_position;
22432 }
22433
22434 CompletionEdit {
22435 new_text,
22436 replace_range: range_to_replace.to_offset(buffer),
22437 snippet,
22438 }
22439}
22440
22441struct CompletionEdit {
22442 new_text: String,
22443 replace_range: Range<usize>,
22444 snippet: Option<Snippet>,
22445}
22446
22447fn insert_extra_newline_brackets(
22448 buffer: &MultiBufferSnapshot,
22449 range: Range<usize>,
22450 language: &language::LanguageScope,
22451) -> bool {
22452 let leading_whitespace_len = buffer
22453 .reversed_chars_at(range.start)
22454 .take_while(|c| c.is_whitespace() && *c != '\n')
22455 .map(|c| c.len_utf8())
22456 .sum::<usize>();
22457 let trailing_whitespace_len = buffer
22458 .chars_at(range.end)
22459 .take_while(|c| c.is_whitespace() && *c != '\n')
22460 .map(|c| c.len_utf8())
22461 .sum::<usize>();
22462 let range = range.start - leading_whitespace_len..range.end + trailing_whitespace_len;
22463
22464 language.brackets().any(|(pair, enabled)| {
22465 let pair_start = pair.start.trim_end();
22466 let pair_end = pair.end.trim_start();
22467
22468 enabled
22469 && pair.newline
22470 && buffer.contains_str_at(range.end, pair_end)
22471 && buffer.contains_str_at(range.start.saturating_sub(pair_start.len()), pair_start)
22472 })
22473}
22474
22475fn insert_extra_newline_tree_sitter(buffer: &MultiBufferSnapshot, range: Range<usize>) -> bool {
22476 let (buffer, range) = match buffer.range_to_buffer_ranges(range).as_slice() {
22477 [(buffer, range, _)] => (*buffer, range.clone()),
22478 _ => return false,
22479 };
22480 let pair = {
22481 let mut result: Option<BracketMatch> = None;
22482
22483 for pair in buffer
22484 .all_bracket_ranges(range.clone())
22485 .filter(move |pair| {
22486 pair.open_range.start <= range.start && pair.close_range.end >= range.end
22487 })
22488 {
22489 let len = pair.close_range.end - pair.open_range.start;
22490
22491 if let Some(existing) = &result {
22492 let existing_len = existing.close_range.end - existing.open_range.start;
22493 if len > existing_len {
22494 continue;
22495 }
22496 }
22497
22498 result = Some(pair);
22499 }
22500
22501 result
22502 };
22503 let Some(pair) = pair else {
22504 return false;
22505 };
22506 pair.newline_only
22507 && buffer
22508 .chars_for_range(pair.open_range.end..range.start)
22509 .chain(buffer.chars_for_range(range.end..pair.close_range.start))
22510 .all(|c| c.is_whitespace() && c != '\n')
22511}
22512
22513fn update_uncommitted_diff_for_buffer(
22514 editor: Entity<Editor>,
22515 project: &Entity<Project>,
22516 buffers: impl IntoIterator<Item = Entity<Buffer>>,
22517 buffer: Entity<MultiBuffer>,
22518 cx: &mut App,
22519) -> Task<()> {
22520 let mut tasks = Vec::new();
22521 project.update(cx, |project, cx| {
22522 for buffer in buffers {
22523 if project::File::from_dyn(buffer.read(cx).file()).is_some() {
22524 tasks.push(project.open_uncommitted_diff(buffer.clone(), cx))
22525 }
22526 }
22527 });
22528 cx.spawn(async move |cx| {
22529 let diffs = future::join_all(tasks).await;
22530 if editor
22531 .read_with(cx, |editor, _cx| editor.temporary_diff_override)
22532 .unwrap_or(false)
22533 {
22534 return;
22535 }
22536
22537 buffer
22538 .update(cx, |buffer, cx| {
22539 for diff in diffs.into_iter().flatten() {
22540 buffer.add_diff(diff, cx);
22541 }
22542 })
22543 .ok();
22544 })
22545}
22546
22547fn char_len_with_expanded_tabs(offset: usize, text: &str, tab_size: NonZeroU32) -> usize {
22548 let tab_size = tab_size.get() as usize;
22549 let mut width = offset;
22550
22551 for ch in text.chars() {
22552 width += if ch == '\t' {
22553 tab_size - (width % tab_size)
22554 } else {
22555 1
22556 };
22557 }
22558
22559 width - offset
22560}
22561
22562#[cfg(test)]
22563mod tests {
22564 use super::*;
22565
22566 #[test]
22567 fn test_string_size_with_expanded_tabs() {
22568 let nz = |val| NonZeroU32::new(val).unwrap();
22569 assert_eq!(char_len_with_expanded_tabs(0, "", nz(4)), 0);
22570 assert_eq!(char_len_with_expanded_tabs(0, "hello", nz(4)), 5);
22571 assert_eq!(char_len_with_expanded_tabs(0, "\thello", nz(4)), 9);
22572 assert_eq!(char_len_with_expanded_tabs(0, "abc\tab", nz(4)), 6);
22573 assert_eq!(char_len_with_expanded_tabs(0, "hello\t", nz(4)), 8);
22574 assert_eq!(char_len_with_expanded_tabs(0, "\t\t", nz(8)), 16);
22575 assert_eq!(char_len_with_expanded_tabs(0, "x\t", nz(8)), 8);
22576 assert_eq!(char_len_with_expanded_tabs(7, "x\t", nz(8)), 9);
22577 }
22578}
22579
22580/// Tokenizes a string into runs of text that should stick together, or that is whitespace.
22581struct WordBreakingTokenizer<'a> {
22582 input: &'a str,
22583}
22584
22585impl<'a> WordBreakingTokenizer<'a> {
22586 fn new(input: &'a str) -> Self {
22587 Self { input }
22588 }
22589}
22590
22591fn is_char_ideographic(ch: char) -> bool {
22592 use unicode_script::Script::*;
22593 use unicode_script::UnicodeScript;
22594 matches!(ch.script(), Han | Tangut | Yi)
22595}
22596
22597fn is_grapheme_ideographic(text: &str) -> bool {
22598 text.chars().any(is_char_ideographic)
22599}
22600
22601fn is_grapheme_whitespace(text: &str) -> bool {
22602 text.chars().any(|x| x.is_whitespace())
22603}
22604
22605fn should_stay_with_preceding_ideograph(text: &str) -> bool {
22606 text.chars()
22607 .next()
22608 .is_some_and(|ch| matches!(ch, '。' | '、' | ',' | '?' | '!' | ':' | ';' | '…'))
22609}
22610
22611#[derive(PartialEq, Eq, Debug, Clone, Copy)]
22612enum WordBreakToken<'a> {
22613 Word { token: &'a str, grapheme_len: usize },
22614 InlineWhitespace { token: &'a str, grapheme_len: usize },
22615 Newline,
22616}
22617
22618impl<'a> Iterator for WordBreakingTokenizer<'a> {
22619 /// Yields a span, the count of graphemes in the token, and whether it was
22620 /// whitespace. Note that it also breaks at word boundaries.
22621 type Item = WordBreakToken<'a>;
22622
22623 fn next(&mut self) -> Option<Self::Item> {
22624 use unicode_segmentation::UnicodeSegmentation;
22625 if self.input.is_empty() {
22626 return None;
22627 }
22628
22629 let mut iter = self.input.graphemes(true).peekable();
22630 let mut offset = 0;
22631 let mut grapheme_len = 0;
22632 if let Some(first_grapheme) = iter.next() {
22633 let is_newline = first_grapheme == "\n";
22634 let is_whitespace = is_grapheme_whitespace(first_grapheme);
22635 offset += first_grapheme.len();
22636 grapheme_len += 1;
22637 if is_grapheme_ideographic(first_grapheme) && !is_whitespace {
22638 if let Some(grapheme) = iter.peek().copied()
22639 && should_stay_with_preceding_ideograph(grapheme)
22640 {
22641 offset += grapheme.len();
22642 grapheme_len += 1;
22643 }
22644 } else {
22645 let mut words = self.input[offset..].split_word_bound_indices().peekable();
22646 let mut next_word_bound = words.peek().copied();
22647 if next_word_bound.is_some_and(|(i, _)| i == 0) {
22648 next_word_bound = words.next();
22649 }
22650 while let Some(grapheme) = iter.peek().copied() {
22651 if next_word_bound.is_some_and(|(i, _)| i == offset) {
22652 break;
22653 };
22654 if is_grapheme_whitespace(grapheme) != is_whitespace
22655 || (grapheme == "\n") != is_newline
22656 {
22657 break;
22658 };
22659 offset += grapheme.len();
22660 grapheme_len += 1;
22661 iter.next();
22662 }
22663 }
22664 let token = &self.input[..offset];
22665 self.input = &self.input[offset..];
22666 if token == "\n" {
22667 Some(WordBreakToken::Newline)
22668 } else if is_whitespace {
22669 Some(WordBreakToken::InlineWhitespace {
22670 token,
22671 grapheme_len,
22672 })
22673 } else {
22674 Some(WordBreakToken::Word {
22675 token,
22676 grapheme_len,
22677 })
22678 }
22679 } else {
22680 None
22681 }
22682 }
22683}
22684
22685#[test]
22686fn test_word_breaking_tokenizer() {
22687 let tests: &[(&str, &[WordBreakToken<'static>])] = &[
22688 ("", &[]),
22689 (" ", &[whitespace(" ", 2)]),
22690 ("Ʒ", &[word("Ʒ", 1)]),
22691 ("Ǽ", &[word("Ǽ", 1)]),
22692 ("⋑", &[word("⋑", 1)]),
22693 ("⋑⋑", &[word("⋑⋑", 2)]),
22694 (
22695 "原理,进而",
22696 &[word("原", 1), word("理,", 2), word("进", 1), word("而", 1)],
22697 ),
22698 (
22699 "hello world",
22700 &[word("hello", 5), whitespace(" ", 1), word("world", 5)],
22701 ),
22702 (
22703 "hello, world",
22704 &[word("hello,", 6), whitespace(" ", 1), word("world", 5)],
22705 ),
22706 (
22707 " hello world",
22708 &[
22709 whitespace(" ", 2),
22710 word("hello", 5),
22711 whitespace(" ", 1),
22712 word("world", 5),
22713 ],
22714 ),
22715 (
22716 "这是什么 \n 钢笔",
22717 &[
22718 word("这", 1),
22719 word("是", 1),
22720 word("什", 1),
22721 word("么", 1),
22722 whitespace(" ", 1),
22723 newline(),
22724 whitespace(" ", 1),
22725 word("钢", 1),
22726 word("笔", 1),
22727 ],
22728 ),
22729 (" mutton", &[whitespace(" ", 1), word("mutton", 6)]),
22730 ];
22731
22732 fn word(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22733 WordBreakToken::Word {
22734 token,
22735 grapheme_len,
22736 }
22737 }
22738
22739 fn whitespace(token: &'static str, grapheme_len: usize) -> WordBreakToken<'static> {
22740 WordBreakToken::InlineWhitespace {
22741 token,
22742 grapheme_len,
22743 }
22744 }
22745
22746 fn newline() -> WordBreakToken<'static> {
22747 WordBreakToken::Newline
22748 }
22749
22750 for (input, result) in tests {
22751 assert_eq!(
22752 WordBreakingTokenizer::new(input)
22753 .collect::<Vec<_>>()
22754 .as_slice(),
22755 *result,
22756 );
22757 }
22758}
22759
22760fn wrap_with_prefix(
22761 first_line_prefix: String,
22762 subsequent_lines_prefix: String,
22763 unwrapped_text: String,
22764 wrap_column: usize,
22765 tab_size: NonZeroU32,
22766 preserve_existing_whitespace: bool,
22767) -> String {
22768 let first_line_prefix_len = char_len_with_expanded_tabs(0, &first_line_prefix, tab_size);
22769 let subsequent_lines_prefix_len =
22770 char_len_with_expanded_tabs(0, &subsequent_lines_prefix, tab_size);
22771 let mut wrapped_text = String::new();
22772 let mut current_line = first_line_prefix;
22773 let mut is_first_line = true;
22774
22775 let tokenizer = WordBreakingTokenizer::new(&unwrapped_text);
22776 let mut current_line_len = first_line_prefix_len;
22777 let mut in_whitespace = false;
22778 for token in tokenizer {
22779 let have_preceding_whitespace = in_whitespace;
22780 match token {
22781 WordBreakToken::Word {
22782 token,
22783 grapheme_len,
22784 } => {
22785 in_whitespace = false;
22786 let current_prefix_len = if is_first_line {
22787 first_line_prefix_len
22788 } else {
22789 subsequent_lines_prefix_len
22790 };
22791 if current_line_len + grapheme_len > wrap_column
22792 && current_line_len != current_prefix_len
22793 {
22794 wrapped_text.push_str(current_line.trim_end());
22795 wrapped_text.push('\n');
22796 is_first_line = false;
22797 current_line = subsequent_lines_prefix.clone();
22798 current_line_len = subsequent_lines_prefix_len;
22799 }
22800 current_line.push_str(token);
22801 current_line_len += grapheme_len;
22802 }
22803 WordBreakToken::InlineWhitespace {
22804 mut token,
22805 mut grapheme_len,
22806 } => {
22807 in_whitespace = true;
22808 if have_preceding_whitespace && !preserve_existing_whitespace {
22809 continue;
22810 }
22811 if !preserve_existing_whitespace {
22812 // Keep a single whitespace grapheme as-is
22813 if let Some(first) =
22814 unicode_segmentation::UnicodeSegmentation::graphemes(token, true).next()
22815 {
22816 token = first;
22817 } else {
22818 token = " ";
22819 }
22820 grapheme_len = 1;
22821 }
22822 let current_prefix_len = if is_first_line {
22823 first_line_prefix_len
22824 } else {
22825 subsequent_lines_prefix_len
22826 };
22827 if current_line_len + grapheme_len > wrap_column {
22828 wrapped_text.push_str(current_line.trim_end());
22829 wrapped_text.push('\n');
22830 is_first_line = false;
22831 current_line = subsequent_lines_prefix.clone();
22832 current_line_len = subsequent_lines_prefix_len;
22833 } else if current_line_len != current_prefix_len || preserve_existing_whitespace {
22834 current_line.push_str(token);
22835 current_line_len += grapheme_len;
22836 }
22837 }
22838 WordBreakToken::Newline => {
22839 in_whitespace = true;
22840 let current_prefix_len = if is_first_line {
22841 first_line_prefix_len
22842 } else {
22843 subsequent_lines_prefix_len
22844 };
22845 if preserve_existing_whitespace {
22846 wrapped_text.push_str(current_line.trim_end());
22847 wrapped_text.push('\n');
22848 is_first_line = false;
22849 current_line = subsequent_lines_prefix.clone();
22850 current_line_len = subsequent_lines_prefix_len;
22851 } else if have_preceding_whitespace {
22852 continue;
22853 } else if current_line_len + 1 > wrap_column
22854 && current_line_len != current_prefix_len
22855 {
22856 wrapped_text.push_str(current_line.trim_end());
22857 wrapped_text.push('\n');
22858 is_first_line = false;
22859 current_line = subsequent_lines_prefix.clone();
22860 current_line_len = subsequent_lines_prefix_len;
22861 } else if current_line_len != current_prefix_len {
22862 current_line.push(' ');
22863 current_line_len += 1;
22864 }
22865 }
22866 }
22867 }
22868
22869 if !current_line.is_empty() {
22870 wrapped_text.push_str(¤t_line);
22871 }
22872 wrapped_text
22873}
22874
22875#[test]
22876fn test_wrap_with_prefix() {
22877 assert_eq!(
22878 wrap_with_prefix(
22879 "# ".to_string(),
22880 "# ".to_string(),
22881 "abcdefg".to_string(),
22882 4,
22883 NonZeroU32::new(4).unwrap(),
22884 false,
22885 ),
22886 "# abcdefg"
22887 );
22888 assert_eq!(
22889 wrap_with_prefix(
22890 "".to_string(),
22891 "".to_string(),
22892 "\thello world".to_string(),
22893 8,
22894 NonZeroU32::new(4).unwrap(),
22895 false,
22896 ),
22897 "hello\nworld"
22898 );
22899 assert_eq!(
22900 wrap_with_prefix(
22901 "// ".to_string(),
22902 "// ".to_string(),
22903 "xx \nyy zz aa bb cc".to_string(),
22904 12,
22905 NonZeroU32::new(4).unwrap(),
22906 false,
22907 ),
22908 "// xx yy zz\n// aa bb cc"
22909 );
22910 assert_eq!(
22911 wrap_with_prefix(
22912 String::new(),
22913 String::new(),
22914 "这是什么 \n 钢笔".to_string(),
22915 3,
22916 NonZeroU32::new(4).unwrap(),
22917 false,
22918 ),
22919 "这是什\n么 钢\n笔"
22920 );
22921 assert_eq!(
22922 wrap_with_prefix(
22923 String::new(),
22924 String::new(),
22925 format!("foo{}bar", '\u{2009}'), // thin space
22926 80,
22927 NonZeroU32::new(4).unwrap(),
22928 false,
22929 ),
22930 format!("foo{}bar", '\u{2009}')
22931 );
22932}
22933
22934pub trait CollaborationHub {
22935 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator>;
22936 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex>;
22937 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString>;
22938}
22939
22940impl CollaborationHub for Entity<Project> {
22941 fn collaborators<'a>(&self, cx: &'a App) -> &'a HashMap<PeerId, Collaborator> {
22942 self.read(cx).collaborators()
22943 }
22944
22945 fn user_participant_indices<'a>(&self, cx: &'a App) -> &'a HashMap<u64, ParticipantIndex> {
22946 self.read(cx).user_store().read(cx).participant_indices()
22947 }
22948
22949 fn user_names(&self, cx: &App) -> HashMap<u64, SharedString> {
22950 let this = self.read(cx);
22951 let user_ids = this.collaborators().values().map(|c| c.user_id);
22952 this.user_store().read(cx).participant_names(user_ids, cx)
22953 }
22954}
22955
22956pub trait SemanticsProvider {
22957 fn hover(
22958 &self,
22959 buffer: &Entity<Buffer>,
22960 position: text::Anchor,
22961 cx: &mut App,
22962 ) -> Option<Task<Option<Vec<project::Hover>>>>;
22963
22964 fn inline_values(
22965 &self,
22966 buffer_handle: Entity<Buffer>,
22967 range: Range<text::Anchor>,
22968 cx: &mut App,
22969 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>>;
22970
22971 fn applicable_inlay_chunks(
22972 &self,
22973 buffer: &Entity<Buffer>,
22974 ranges: &[Range<text::Anchor>],
22975 cx: &mut App,
22976 ) -> Vec<Range<BufferRow>>;
22977
22978 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App);
22979
22980 fn inlay_hints(
22981 &self,
22982 invalidate: InvalidationStrategy,
22983 buffer: Entity<Buffer>,
22984 ranges: Vec<Range<text::Anchor>>,
22985 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
22986 cx: &mut App,
22987 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>>;
22988
22989 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool;
22990
22991 fn document_highlights(
22992 &self,
22993 buffer: &Entity<Buffer>,
22994 position: text::Anchor,
22995 cx: &mut App,
22996 ) -> Option<Task<Result<Vec<DocumentHighlight>>>>;
22997
22998 fn definitions(
22999 &self,
23000 buffer: &Entity<Buffer>,
23001 position: text::Anchor,
23002 kind: GotoDefinitionKind,
23003 cx: &mut App,
23004 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>>;
23005
23006 fn range_for_rename(
23007 &self,
23008 buffer: &Entity<Buffer>,
23009 position: text::Anchor,
23010 cx: &mut App,
23011 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>>;
23012
23013 fn perform_rename(
23014 &self,
23015 buffer: &Entity<Buffer>,
23016 position: text::Anchor,
23017 new_name: String,
23018 cx: &mut App,
23019 ) -> Option<Task<Result<ProjectTransaction>>>;
23020}
23021
23022pub trait CompletionProvider {
23023 fn completions(
23024 &self,
23025 excerpt_id: ExcerptId,
23026 buffer: &Entity<Buffer>,
23027 buffer_position: text::Anchor,
23028 trigger: CompletionContext,
23029 window: &mut Window,
23030 cx: &mut Context<Editor>,
23031 ) -> Task<Result<Vec<CompletionResponse>>>;
23032
23033 fn resolve_completions(
23034 &self,
23035 _buffer: Entity<Buffer>,
23036 _completion_indices: Vec<usize>,
23037 _completions: Rc<RefCell<Box<[Completion]>>>,
23038 _cx: &mut Context<Editor>,
23039 ) -> Task<Result<bool>> {
23040 Task::ready(Ok(false))
23041 }
23042
23043 fn apply_additional_edits_for_completion(
23044 &self,
23045 _buffer: Entity<Buffer>,
23046 _completions: Rc<RefCell<Box<[Completion]>>>,
23047 _completion_index: usize,
23048 _push_to_history: bool,
23049 _cx: &mut Context<Editor>,
23050 ) -> Task<Result<Option<language::Transaction>>> {
23051 Task::ready(Ok(None))
23052 }
23053
23054 fn is_completion_trigger(
23055 &self,
23056 buffer: &Entity<Buffer>,
23057 position: language::Anchor,
23058 text: &str,
23059 trigger_in_words: bool,
23060 menu_is_open: bool,
23061 cx: &mut Context<Editor>,
23062 ) -> bool;
23063
23064 fn selection_changed(&self, _mat: Option<&StringMatch>, _window: &mut Window, _cx: &mut App) {}
23065
23066 fn sort_completions(&self) -> bool {
23067 true
23068 }
23069
23070 fn filter_completions(&self) -> bool {
23071 true
23072 }
23073
23074 fn show_snippets(&self) -> bool {
23075 false
23076 }
23077}
23078
23079pub trait CodeActionProvider {
23080 fn id(&self) -> Arc<str>;
23081
23082 fn code_actions(
23083 &self,
23084 buffer: &Entity<Buffer>,
23085 range: Range<text::Anchor>,
23086 window: &mut Window,
23087 cx: &mut App,
23088 ) -> Task<Result<Vec<CodeAction>>>;
23089
23090 fn apply_code_action(
23091 &self,
23092 buffer_handle: Entity<Buffer>,
23093 action: CodeAction,
23094 excerpt_id: ExcerptId,
23095 push_to_history: bool,
23096 window: &mut Window,
23097 cx: &mut App,
23098 ) -> Task<Result<ProjectTransaction>>;
23099}
23100
23101impl CodeActionProvider for Entity<Project> {
23102 fn id(&self) -> Arc<str> {
23103 "project".into()
23104 }
23105
23106 fn code_actions(
23107 &self,
23108 buffer: &Entity<Buffer>,
23109 range: Range<text::Anchor>,
23110 _window: &mut Window,
23111 cx: &mut App,
23112 ) -> Task<Result<Vec<CodeAction>>> {
23113 self.update(cx, |project, cx| {
23114 let code_lens_actions = project.code_lens_actions(buffer, range.clone(), cx);
23115 let code_actions = project.code_actions(buffer, range, None, cx);
23116 cx.background_spawn(async move {
23117 let (code_lens_actions, code_actions) = join(code_lens_actions, code_actions).await;
23118 Ok(code_lens_actions
23119 .context("code lens fetch")?
23120 .into_iter()
23121 .flatten()
23122 .chain(
23123 code_actions
23124 .context("code action fetch")?
23125 .into_iter()
23126 .flatten(),
23127 )
23128 .collect())
23129 })
23130 })
23131 }
23132
23133 fn apply_code_action(
23134 &self,
23135 buffer_handle: Entity<Buffer>,
23136 action: CodeAction,
23137 _excerpt_id: ExcerptId,
23138 push_to_history: bool,
23139 _window: &mut Window,
23140 cx: &mut App,
23141 ) -> Task<Result<ProjectTransaction>> {
23142 self.update(cx, |project, cx| {
23143 project.apply_code_action(buffer_handle, action, push_to_history, cx)
23144 })
23145 }
23146}
23147
23148fn snippet_completions(
23149 project: &Project,
23150 buffer: &Entity<Buffer>,
23151 buffer_position: text::Anchor,
23152 cx: &mut App,
23153) -> Task<Result<CompletionResponse>> {
23154 let languages = buffer.read(cx).languages_at(buffer_position);
23155 let snippet_store = project.snippets().read(cx);
23156
23157 let scopes: Vec<_> = languages
23158 .iter()
23159 .filter_map(|language| {
23160 let language_name = language.lsp_id();
23161 let snippets = snippet_store.snippets_for(Some(language_name), cx);
23162
23163 if snippets.is_empty() {
23164 None
23165 } else {
23166 Some((language.default_scope(), snippets))
23167 }
23168 })
23169 .collect();
23170
23171 if scopes.is_empty() {
23172 return Task::ready(Ok(CompletionResponse {
23173 completions: vec![],
23174 display_options: CompletionDisplayOptions::default(),
23175 is_incomplete: false,
23176 }));
23177 }
23178
23179 let snapshot = buffer.read(cx).text_snapshot();
23180 let executor = cx.background_executor().clone();
23181
23182 cx.background_spawn(async move {
23183 let mut is_incomplete = false;
23184 let mut completions: Vec<Completion> = Vec::new();
23185 for (scope, snippets) in scopes.into_iter() {
23186 let classifier =
23187 CharClassifier::new(Some(scope)).scope_context(Some(CharScopeContext::Completion));
23188
23189 const MAX_WORD_PREFIX_LEN: usize = 128;
23190 let last_word: String = snapshot
23191 .reversed_chars_for_range(text::Anchor::MIN..buffer_position)
23192 .take(MAX_WORD_PREFIX_LEN)
23193 .take_while(|c| classifier.is_word(*c))
23194 .collect::<String>()
23195 .chars()
23196 .rev()
23197 .collect();
23198
23199 if last_word.is_empty() {
23200 return Ok(CompletionResponse {
23201 completions: vec![],
23202 display_options: CompletionDisplayOptions::default(),
23203 is_incomplete: true,
23204 });
23205 }
23206
23207 let as_offset = text::ToOffset::to_offset(&buffer_position, &snapshot);
23208 let to_lsp = |point: &text::Anchor| {
23209 let end = text::ToPointUtf16::to_point_utf16(point, &snapshot);
23210 point_to_lsp(end)
23211 };
23212 let lsp_end = to_lsp(&buffer_position);
23213
23214 let candidates = snippets
23215 .iter()
23216 .enumerate()
23217 .flat_map(|(ix, snippet)| {
23218 snippet
23219 .prefix
23220 .iter()
23221 .map(move |prefix| StringMatchCandidate::new(ix, prefix))
23222 })
23223 .collect::<Vec<StringMatchCandidate>>();
23224
23225 const MAX_RESULTS: usize = 100;
23226 let mut matches = fuzzy::match_strings(
23227 &candidates,
23228 &last_word,
23229 last_word.chars().any(|c| c.is_uppercase()),
23230 true,
23231 MAX_RESULTS,
23232 &Default::default(),
23233 executor.clone(),
23234 )
23235 .await;
23236
23237 if matches.len() >= MAX_RESULTS {
23238 is_incomplete = true;
23239 }
23240
23241 // Remove all candidates where the query's start does not match the start of any word in the candidate
23242 if let Some(query_start) = last_word.chars().next() {
23243 matches.retain(|string_match| {
23244 split_words(&string_match.string).any(|word| {
23245 // Check that the first codepoint of the word as lowercase matches the first
23246 // codepoint of the query as lowercase
23247 word.chars()
23248 .flat_map(|codepoint| codepoint.to_lowercase())
23249 .zip(query_start.to_lowercase())
23250 .all(|(word_cp, query_cp)| word_cp == query_cp)
23251 })
23252 });
23253 }
23254
23255 let matched_strings = matches
23256 .into_iter()
23257 .map(|m| m.string)
23258 .collect::<HashSet<_>>();
23259
23260 completions.extend(snippets.iter().filter_map(|snippet| {
23261 let matching_prefix = snippet
23262 .prefix
23263 .iter()
23264 .find(|prefix| matched_strings.contains(*prefix))?;
23265 let start = as_offset - last_word.len();
23266 let start = snapshot.anchor_before(start);
23267 let range = start..buffer_position;
23268 let lsp_start = to_lsp(&start);
23269 let lsp_range = lsp::Range {
23270 start: lsp_start,
23271 end: lsp_end,
23272 };
23273 Some(Completion {
23274 replace_range: range,
23275 new_text: snippet.body.clone(),
23276 source: CompletionSource::Lsp {
23277 insert_range: None,
23278 server_id: LanguageServerId(usize::MAX),
23279 resolved: true,
23280 lsp_completion: Box::new(lsp::CompletionItem {
23281 label: snippet.prefix.first().unwrap().clone(),
23282 kind: Some(CompletionItemKind::SNIPPET),
23283 label_details: snippet.description.as_ref().map(|description| {
23284 lsp::CompletionItemLabelDetails {
23285 detail: Some(description.clone()),
23286 description: None,
23287 }
23288 }),
23289 insert_text_format: Some(InsertTextFormat::SNIPPET),
23290 text_edit: Some(lsp::CompletionTextEdit::InsertAndReplace(
23291 lsp::InsertReplaceEdit {
23292 new_text: snippet.body.clone(),
23293 insert: lsp_range,
23294 replace: lsp_range,
23295 },
23296 )),
23297 filter_text: Some(snippet.body.clone()),
23298 sort_text: Some(char::MAX.to_string()),
23299 ..lsp::CompletionItem::default()
23300 }),
23301 lsp_defaults: None,
23302 },
23303 label: CodeLabel::plain(matching_prefix.clone(), None),
23304 icon_path: None,
23305 documentation: Some(CompletionDocumentation::SingleLineAndMultiLinePlainText {
23306 single_line: snippet.name.clone().into(),
23307 plain_text: snippet
23308 .description
23309 .clone()
23310 .map(|description| description.into()),
23311 }),
23312 insert_text_mode: None,
23313 confirm: None,
23314 })
23315 }))
23316 }
23317
23318 Ok(CompletionResponse {
23319 completions,
23320 display_options: CompletionDisplayOptions::default(),
23321 is_incomplete,
23322 })
23323 })
23324}
23325
23326impl CompletionProvider for Entity<Project> {
23327 fn completions(
23328 &self,
23329 _excerpt_id: ExcerptId,
23330 buffer: &Entity<Buffer>,
23331 buffer_position: text::Anchor,
23332 options: CompletionContext,
23333 _window: &mut Window,
23334 cx: &mut Context<Editor>,
23335 ) -> Task<Result<Vec<CompletionResponse>>> {
23336 self.update(cx, |project, cx| {
23337 let task = project.completions(buffer, buffer_position, options, cx);
23338 cx.background_spawn(task)
23339 })
23340 }
23341
23342 fn resolve_completions(
23343 &self,
23344 buffer: Entity<Buffer>,
23345 completion_indices: Vec<usize>,
23346 completions: Rc<RefCell<Box<[Completion]>>>,
23347 cx: &mut Context<Editor>,
23348 ) -> Task<Result<bool>> {
23349 self.update(cx, |project, cx| {
23350 project.lsp_store().update(cx, |lsp_store, cx| {
23351 lsp_store.resolve_completions(buffer, completion_indices, completions, cx)
23352 })
23353 })
23354 }
23355
23356 fn apply_additional_edits_for_completion(
23357 &self,
23358 buffer: Entity<Buffer>,
23359 completions: Rc<RefCell<Box<[Completion]>>>,
23360 completion_index: usize,
23361 push_to_history: bool,
23362 cx: &mut Context<Editor>,
23363 ) -> Task<Result<Option<language::Transaction>>> {
23364 self.update(cx, |project, cx| {
23365 project.lsp_store().update(cx, |lsp_store, cx| {
23366 lsp_store.apply_additional_edits_for_completion(
23367 buffer,
23368 completions,
23369 completion_index,
23370 push_to_history,
23371 cx,
23372 )
23373 })
23374 })
23375 }
23376
23377 fn is_completion_trigger(
23378 &self,
23379 buffer: &Entity<Buffer>,
23380 position: language::Anchor,
23381 text: &str,
23382 trigger_in_words: bool,
23383 menu_is_open: bool,
23384 cx: &mut Context<Editor>,
23385 ) -> bool {
23386 let mut chars = text.chars();
23387 let char = if let Some(char) = chars.next() {
23388 char
23389 } else {
23390 return false;
23391 };
23392 if chars.next().is_some() {
23393 return false;
23394 }
23395
23396 let buffer = buffer.read(cx);
23397 let snapshot = buffer.snapshot();
23398 if !menu_is_open && !snapshot.settings_at(position, cx).show_completions_on_input {
23399 return false;
23400 }
23401 let classifier = snapshot
23402 .char_classifier_at(position)
23403 .scope_context(Some(CharScopeContext::Completion));
23404 if trigger_in_words && classifier.is_word(char) {
23405 return true;
23406 }
23407
23408 buffer.completion_triggers().contains(text)
23409 }
23410
23411 fn show_snippets(&self) -> bool {
23412 true
23413 }
23414}
23415
23416impl SemanticsProvider for Entity<Project> {
23417 fn hover(
23418 &self,
23419 buffer: &Entity<Buffer>,
23420 position: text::Anchor,
23421 cx: &mut App,
23422 ) -> Option<Task<Option<Vec<project::Hover>>>> {
23423 Some(self.update(cx, |project, cx| project.hover(buffer, position, cx)))
23424 }
23425
23426 fn document_highlights(
23427 &self,
23428 buffer: &Entity<Buffer>,
23429 position: text::Anchor,
23430 cx: &mut App,
23431 ) -> Option<Task<Result<Vec<DocumentHighlight>>>> {
23432 Some(self.update(cx, |project, cx| {
23433 project.document_highlights(buffer, position, cx)
23434 }))
23435 }
23436
23437 fn definitions(
23438 &self,
23439 buffer: &Entity<Buffer>,
23440 position: text::Anchor,
23441 kind: GotoDefinitionKind,
23442 cx: &mut App,
23443 ) -> Option<Task<Result<Option<Vec<LocationLink>>>>> {
23444 Some(self.update(cx, |project, cx| match kind {
23445 GotoDefinitionKind::Symbol => project.definitions(buffer, position, cx),
23446 GotoDefinitionKind::Declaration => project.declarations(buffer, position, cx),
23447 GotoDefinitionKind::Type => project.type_definitions(buffer, position, cx),
23448 GotoDefinitionKind::Implementation => project.implementations(buffer, position, cx),
23449 }))
23450 }
23451
23452 fn supports_inlay_hints(&self, buffer: &Entity<Buffer>, cx: &mut App) -> bool {
23453 self.update(cx, |project, cx| {
23454 if project
23455 .active_debug_session(cx)
23456 .is_some_and(|(session, _)| session.read(cx).any_stopped_thread())
23457 {
23458 return true;
23459 }
23460
23461 buffer.update(cx, |buffer, cx| {
23462 project.any_language_server_supports_inlay_hints(buffer, cx)
23463 })
23464 })
23465 }
23466
23467 fn inline_values(
23468 &self,
23469 buffer_handle: Entity<Buffer>,
23470 range: Range<text::Anchor>,
23471 cx: &mut App,
23472 ) -> Option<Task<anyhow::Result<Vec<InlayHint>>>> {
23473 self.update(cx, |project, cx| {
23474 let (session, active_stack_frame) = project.active_debug_session(cx)?;
23475
23476 Some(project.inline_values(session, active_stack_frame, buffer_handle, range, cx))
23477 })
23478 }
23479
23480 fn applicable_inlay_chunks(
23481 &self,
23482 buffer: &Entity<Buffer>,
23483 ranges: &[Range<text::Anchor>],
23484 cx: &mut App,
23485 ) -> Vec<Range<BufferRow>> {
23486 self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23487 lsp_store.applicable_inlay_chunks(buffer, ranges, cx)
23488 })
23489 }
23490
23491 fn invalidate_inlay_hints(&self, for_buffers: &HashSet<BufferId>, cx: &mut App) {
23492 self.read(cx).lsp_store().update(cx, |lsp_store, _| {
23493 lsp_store.invalidate_inlay_hints(for_buffers)
23494 });
23495 }
23496
23497 fn inlay_hints(
23498 &self,
23499 invalidate: InvalidationStrategy,
23500 buffer: Entity<Buffer>,
23501 ranges: Vec<Range<text::Anchor>>,
23502 known_chunks: Option<(clock::Global, HashSet<Range<BufferRow>>)>,
23503 cx: &mut App,
23504 ) -> Option<HashMap<Range<BufferRow>, Task<Result<CacheInlayHints>>>> {
23505 Some(self.read(cx).lsp_store().update(cx, |lsp_store, cx| {
23506 lsp_store.inlay_hints(invalidate, buffer, ranges, known_chunks, cx)
23507 }))
23508 }
23509
23510 fn range_for_rename(
23511 &self,
23512 buffer: &Entity<Buffer>,
23513 position: text::Anchor,
23514 cx: &mut App,
23515 ) -> Option<Task<Result<Option<Range<text::Anchor>>>>> {
23516 Some(self.update(cx, |project, cx| {
23517 let buffer = buffer.clone();
23518 let task = project.prepare_rename(buffer.clone(), position, cx);
23519 cx.spawn(async move |_, cx| {
23520 Ok(match task.await? {
23521 PrepareRenameResponse::Success(range) => Some(range),
23522 PrepareRenameResponse::InvalidPosition => None,
23523 PrepareRenameResponse::OnlyUnpreparedRenameSupported => {
23524 // Fallback on using TreeSitter info to determine identifier range
23525 buffer.read_with(cx, |buffer, _| {
23526 let snapshot = buffer.snapshot();
23527 let (range, kind) = snapshot.surrounding_word(position, None);
23528 if kind != Some(CharKind::Word) {
23529 return None;
23530 }
23531 Some(
23532 snapshot.anchor_before(range.start)
23533 ..snapshot.anchor_after(range.end),
23534 )
23535 })?
23536 }
23537 })
23538 })
23539 }))
23540 }
23541
23542 fn perform_rename(
23543 &self,
23544 buffer: &Entity<Buffer>,
23545 position: text::Anchor,
23546 new_name: String,
23547 cx: &mut App,
23548 ) -> Option<Task<Result<ProjectTransaction>>> {
23549 Some(self.update(cx, |project, cx| {
23550 project.perform_rename(buffer.clone(), position, new_name, cx)
23551 }))
23552 }
23553}
23554
23555fn consume_contiguous_rows(
23556 contiguous_row_selections: &mut Vec<Selection<Point>>,
23557 selection: &Selection<Point>,
23558 display_map: &DisplaySnapshot,
23559 selections: &mut Peekable<std::slice::Iter<Selection<Point>>>,
23560) -> (MultiBufferRow, MultiBufferRow) {
23561 contiguous_row_selections.push(selection.clone());
23562 let start_row = starting_row(selection, display_map);
23563 let mut end_row = ending_row(selection, display_map);
23564
23565 while let Some(next_selection) = selections.peek() {
23566 if next_selection.start.row <= end_row.0 {
23567 end_row = ending_row(next_selection, display_map);
23568 contiguous_row_selections.push(selections.next().unwrap().clone());
23569 } else {
23570 break;
23571 }
23572 }
23573 (start_row, end_row)
23574}
23575
23576fn starting_row(selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23577 if selection.start.column > 0 {
23578 MultiBufferRow(display_map.prev_line_boundary(selection.start).0.row)
23579 } else {
23580 MultiBufferRow(selection.start.row)
23581 }
23582}
23583
23584fn ending_row(next_selection: &Selection<Point>, display_map: &DisplaySnapshot) -> MultiBufferRow {
23585 if next_selection.end.column > 0 || next_selection.is_empty() {
23586 MultiBufferRow(display_map.next_line_boundary(next_selection.end).0.row + 1)
23587 } else {
23588 MultiBufferRow(next_selection.end.row)
23589 }
23590}
23591
23592impl EditorSnapshot {
23593 pub fn remote_selections_in_range<'a>(
23594 &'a self,
23595 range: &'a Range<Anchor>,
23596 collaboration_hub: &dyn CollaborationHub,
23597 cx: &'a App,
23598 ) -> impl 'a + Iterator<Item = RemoteSelection> {
23599 let participant_names = collaboration_hub.user_names(cx);
23600 let participant_indices = collaboration_hub.user_participant_indices(cx);
23601 let collaborators_by_peer_id = collaboration_hub.collaborators(cx);
23602 let collaborators_by_replica_id = collaborators_by_peer_id
23603 .values()
23604 .map(|collaborator| (collaborator.replica_id, collaborator))
23605 .collect::<HashMap<_, _>>();
23606 self.buffer_snapshot()
23607 .selections_in_range(range, false)
23608 .filter_map(move |(replica_id, line_mode, cursor_shape, selection)| {
23609 if replica_id == ReplicaId::AGENT {
23610 Some(RemoteSelection {
23611 replica_id,
23612 selection,
23613 cursor_shape,
23614 line_mode,
23615 collaborator_id: CollaboratorId::Agent,
23616 user_name: Some("Agent".into()),
23617 color: cx.theme().players().agent(),
23618 })
23619 } else {
23620 let collaborator = collaborators_by_replica_id.get(&replica_id)?;
23621 let participant_index = participant_indices.get(&collaborator.user_id).copied();
23622 let user_name = participant_names.get(&collaborator.user_id).cloned();
23623 Some(RemoteSelection {
23624 replica_id,
23625 selection,
23626 cursor_shape,
23627 line_mode,
23628 collaborator_id: CollaboratorId::PeerId(collaborator.peer_id),
23629 user_name,
23630 color: if let Some(index) = participant_index {
23631 cx.theme().players().color_for_participant(index.0)
23632 } else {
23633 cx.theme().players().absent()
23634 },
23635 })
23636 }
23637 })
23638 }
23639
23640 pub fn hunks_for_ranges(
23641 &self,
23642 ranges: impl IntoIterator<Item = Range<Point>>,
23643 ) -> Vec<MultiBufferDiffHunk> {
23644 let mut hunks = Vec::new();
23645 let mut processed_buffer_rows: HashMap<BufferId, HashSet<Range<text::Anchor>>> =
23646 HashMap::default();
23647 for query_range in ranges {
23648 let query_rows =
23649 MultiBufferRow(query_range.start.row)..MultiBufferRow(query_range.end.row + 1);
23650 for hunk in self.buffer_snapshot().diff_hunks_in_range(
23651 Point::new(query_rows.start.0, 0)..Point::new(query_rows.end.0, 0),
23652 ) {
23653 // Include deleted hunks that are adjacent to the query range, because
23654 // otherwise they would be missed.
23655 let mut intersects_range = hunk.row_range.overlaps(&query_rows);
23656 if hunk.status().is_deleted() {
23657 intersects_range |= hunk.row_range.start == query_rows.end;
23658 intersects_range |= hunk.row_range.end == query_rows.start;
23659 }
23660 if intersects_range {
23661 if !processed_buffer_rows
23662 .entry(hunk.buffer_id)
23663 .or_default()
23664 .insert(hunk.buffer_range.start..hunk.buffer_range.end)
23665 {
23666 continue;
23667 }
23668 hunks.push(hunk);
23669 }
23670 }
23671 }
23672
23673 hunks
23674 }
23675
23676 fn display_diff_hunks_for_rows<'a>(
23677 &'a self,
23678 display_rows: Range<DisplayRow>,
23679 folded_buffers: &'a HashSet<BufferId>,
23680 ) -> impl 'a + Iterator<Item = DisplayDiffHunk> {
23681 let buffer_start = DisplayPoint::new(display_rows.start, 0).to_point(self);
23682 let buffer_end = DisplayPoint::new(display_rows.end, 0).to_point(self);
23683
23684 self.buffer_snapshot()
23685 .diff_hunks_in_range(buffer_start..buffer_end)
23686 .filter_map(|hunk| {
23687 if folded_buffers.contains(&hunk.buffer_id) {
23688 return None;
23689 }
23690
23691 let hunk_start_point = Point::new(hunk.row_range.start.0, 0);
23692 let hunk_end_point = Point::new(hunk.row_range.end.0, 0);
23693
23694 let hunk_display_start = self.point_to_display_point(hunk_start_point, Bias::Left);
23695 let hunk_display_end = self.point_to_display_point(hunk_end_point, Bias::Right);
23696
23697 let display_hunk = if hunk_display_start.column() != 0 {
23698 DisplayDiffHunk::Folded {
23699 display_row: hunk_display_start.row(),
23700 }
23701 } else {
23702 let mut end_row = hunk_display_end.row();
23703 if hunk_display_end.column() > 0 {
23704 end_row.0 += 1;
23705 }
23706 let is_created_file = hunk.is_created_file();
23707 DisplayDiffHunk::Unfolded {
23708 status: hunk.status(),
23709 diff_base_byte_range: hunk.diff_base_byte_range,
23710 display_row_range: hunk_display_start.row()..end_row,
23711 multi_buffer_range: Anchor::range_in_buffer(
23712 hunk.excerpt_id,
23713 hunk.buffer_id,
23714 hunk.buffer_range,
23715 ),
23716 is_created_file,
23717 }
23718 };
23719
23720 Some(display_hunk)
23721 })
23722 }
23723
23724 pub fn language_at<T: ToOffset>(&self, position: T) -> Option<&Arc<Language>> {
23725 self.display_snapshot
23726 .buffer_snapshot()
23727 .language_at(position)
23728 }
23729
23730 pub fn is_focused(&self) -> bool {
23731 self.is_focused
23732 }
23733
23734 pub fn placeholder_text(&self) -> Option<String> {
23735 self.placeholder_display_snapshot
23736 .as_ref()
23737 .map(|display_map| display_map.text())
23738 }
23739
23740 pub fn scroll_position(&self) -> gpui::Point<ScrollOffset> {
23741 self.scroll_anchor.scroll_position(&self.display_snapshot)
23742 }
23743
23744 fn gutter_dimensions(
23745 &self,
23746 font_id: FontId,
23747 font_size: Pixels,
23748 max_line_number_width: Pixels,
23749 cx: &App,
23750 ) -> Option<GutterDimensions> {
23751 if !self.show_gutter {
23752 return None;
23753 }
23754
23755 let ch_width = cx.text_system().ch_width(font_id, font_size).log_err()?;
23756 let ch_advance = cx.text_system().ch_advance(font_id, font_size).log_err()?;
23757
23758 let show_git_gutter = self.show_git_diff_gutter.unwrap_or_else(|| {
23759 matches!(
23760 ProjectSettings::get_global(cx).git.git_gutter,
23761 GitGutterSetting::TrackedFiles
23762 )
23763 });
23764 let gutter_settings = EditorSettings::get_global(cx).gutter;
23765 let show_line_numbers = self
23766 .show_line_numbers
23767 .unwrap_or(gutter_settings.line_numbers);
23768 let line_gutter_width = if show_line_numbers {
23769 // Avoid flicker-like gutter resizes when the line number gains another digit by
23770 // only resizing the gutter on files with > 10**min_line_number_digits lines.
23771 let min_width_for_number_on_gutter =
23772 ch_advance * gutter_settings.min_line_number_digits as f32;
23773 max_line_number_width.max(min_width_for_number_on_gutter)
23774 } else {
23775 0.0.into()
23776 };
23777
23778 let show_runnables = self.show_runnables.unwrap_or(gutter_settings.runnables);
23779 let show_breakpoints = self.show_breakpoints.unwrap_or(gutter_settings.breakpoints);
23780
23781 let git_blame_entries_width =
23782 self.git_blame_gutter_max_author_length
23783 .map(|max_author_length| {
23784 let renderer = cx.global::<GlobalBlameRenderer>().0.clone();
23785 const MAX_RELATIVE_TIMESTAMP: &str = "60 minutes ago";
23786
23787 /// The number of characters to dedicate to gaps and margins.
23788 const SPACING_WIDTH: usize = 4;
23789
23790 let max_char_count = max_author_length.min(renderer.max_author_length())
23791 + ::git::SHORT_SHA_LENGTH
23792 + MAX_RELATIVE_TIMESTAMP.len()
23793 + SPACING_WIDTH;
23794
23795 ch_advance * max_char_count
23796 });
23797
23798 let is_singleton = self.buffer_snapshot().is_singleton();
23799
23800 let mut left_padding = git_blame_entries_width.unwrap_or(Pixels::ZERO);
23801 left_padding += if !is_singleton {
23802 ch_width * 4.0
23803 } else if show_runnables || show_breakpoints {
23804 ch_width * 3.0
23805 } else if show_git_gutter && show_line_numbers {
23806 ch_width * 2.0
23807 } else if show_git_gutter || show_line_numbers {
23808 ch_width
23809 } else {
23810 px(0.)
23811 };
23812
23813 let shows_folds = is_singleton && gutter_settings.folds;
23814
23815 let right_padding = if shows_folds && show_line_numbers {
23816 ch_width * 4.0
23817 } else if shows_folds || (!is_singleton && show_line_numbers) {
23818 ch_width * 3.0
23819 } else if show_line_numbers {
23820 ch_width
23821 } else {
23822 px(0.)
23823 };
23824
23825 Some(GutterDimensions {
23826 left_padding,
23827 right_padding,
23828 width: line_gutter_width + left_padding + right_padding,
23829 margin: GutterDimensions::default_gutter_margin(font_id, font_size, cx),
23830 git_blame_entries_width,
23831 })
23832 }
23833
23834 pub fn render_crease_toggle(
23835 &self,
23836 buffer_row: MultiBufferRow,
23837 row_contains_cursor: bool,
23838 editor: Entity<Editor>,
23839 window: &mut Window,
23840 cx: &mut App,
23841 ) -> Option<AnyElement> {
23842 let folded = self.is_line_folded(buffer_row);
23843 let mut is_foldable = false;
23844
23845 if let Some(crease) = self
23846 .crease_snapshot
23847 .query_row(buffer_row, self.buffer_snapshot())
23848 {
23849 is_foldable = true;
23850 match crease {
23851 Crease::Inline { render_toggle, .. } | Crease::Block { render_toggle, .. } => {
23852 if let Some(render_toggle) = render_toggle {
23853 let toggle_callback =
23854 Arc::new(move |folded, window: &mut Window, cx: &mut App| {
23855 if folded {
23856 editor.update(cx, |editor, cx| {
23857 editor.fold_at(buffer_row, window, cx)
23858 });
23859 } else {
23860 editor.update(cx, |editor, cx| {
23861 editor.unfold_at(buffer_row, window, cx)
23862 });
23863 }
23864 });
23865 return Some((render_toggle)(
23866 buffer_row,
23867 folded,
23868 toggle_callback,
23869 window,
23870 cx,
23871 ));
23872 }
23873 }
23874 }
23875 }
23876
23877 is_foldable |= self.starts_indent(buffer_row);
23878
23879 if folded || (is_foldable && (row_contains_cursor || self.gutter_hovered)) {
23880 Some(
23881 Disclosure::new(("gutter_crease", buffer_row.0), !folded)
23882 .toggle_state(folded)
23883 .on_click(window.listener_for(&editor, move |this, _e, window, cx| {
23884 if folded {
23885 this.unfold_at(buffer_row, window, cx);
23886 } else {
23887 this.fold_at(buffer_row, window, cx);
23888 }
23889 }))
23890 .into_any_element(),
23891 )
23892 } else {
23893 None
23894 }
23895 }
23896
23897 pub fn render_crease_trailer(
23898 &self,
23899 buffer_row: MultiBufferRow,
23900 window: &mut Window,
23901 cx: &mut App,
23902 ) -> Option<AnyElement> {
23903 let folded = self.is_line_folded(buffer_row);
23904 if let Crease::Inline { render_trailer, .. } = self
23905 .crease_snapshot
23906 .query_row(buffer_row, self.buffer_snapshot())?
23907 {
23908 let render_trailer = render_trailer.as_ref()?;
23909 Some(render_trailer(buffer_row, folded, window, cx))
23910 } else {
23911 None
23912 }
23913 }
23914}
23915
23916impl Deref for EditorSnapshot {
23917 type Target = DisplaySnapshot;
23918
23919 fn deref(&self) -> &Self::Target {
23920 &self.display_snapshot
23921 }
23922}
23923
23924#[derive(Clone, Debug, PartialEq, Eq)]
23925pub enum EditorEvent {
23926 InputIgnored {
23927 text: Arc<str>,
23928 },
23929 InputHandled {
23930 utf16_range_to_replace: Option<Range<isize>>,
23931 text: Arc<str>,
23932 },
23933 ExcerptsAdded {
23934 buffer: Entity<Buffer>,
23935 predecessor: ExcerptId,
23936 excerpts: Vec<(ExcerptId, ExcerptRange<language::Anchor>)>,
23937 },
23938 ExcerptsRemoved {
23939 ids: Vec<ExcerptId>,
23940 removed_buffer_ids: Vec<BufferId>,
23941 },
23942 BufferFoldToggled {
23943 ids: Vec<ExcerptId>,
23944 folded: bool,
23945 },
23946 ExcerptsEdited {
23947 ids: Vec<ExcerptId>,
23948 },
23949 ExcerptsExpanded {
23950 ids: Vec<ExcerptId>,
23951 },
23952 BufferEdited,
23953 Edited {
23954 transaction_id: clock::Lamport,
23955 },
23956 Reparsed(BufferId),
23957 Focused,
23958 FocusedIn,
23959 Blurred,
23960 DirtyChanged,
23961 Saved,
23962 TitleChanged,
23963 SelectionsChanged {
23964 local: bool,
23965 },
23966 ScrollPositionChanged {
23967 local: bool,
23968 autoscroll: bool,
23969 },
23970 TransactionUndone {
23971 transaction_id: clock::Lamport,
23972 },
23973 TransactionBegun {
23974 transaction_id: clock::Lamport,
23975 },
23976 CursorShapeChanged,
23977 BreadcrumbsChanged,
23978 PushedToNavHistory {
23979 anchor: Anchor,
23980 is_deactivate: bool,
23981 },
23982}
23983
23984impl EventEmitter<EditorEvent> for Editor {}
23985
23986impl Focusable for Editor {
23987 fn focus_handle(&self, _cx: &App) -> FocusHandle {
23988 self.focus_handle.clone()
23989 }
23990}
23991
23992impl Render for Editor {
23993 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
23994 let settings = ThemeSettings::get_global(cx);
23995
23996 let mut text_style = match self.mode {
23997 EditorMode::SingleLine | EditorMode::AutoHeight { .. } => TextStyle {
23998 color: cx.theme().colors().editor_foreground,
23999 font_family: settings.ui_font.family.clone(),
24000 font_features: settings.ui_font.features.clone(),
24001 font_fallbacks: settings.ui_font.fallbacks.clone(),
24002 font_size: rems(0.875).into(),
24003 font_weight: settings.ui_font.weight,
24004 line_height: relative(settings.buffer_line_height.value()),
24005 ..Default::default()
24006 },
24007 EditorMode::Full { .. } | EditorMode::Minimap { .. } => TextStyle {
24008 color: cx.theme().colors().editor_foreground,
24009 font_family: settings.buffer_font.family.clone(),
24010 font_features: settings.buffer_font.features.clone(),
24011 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24012 font_size: settings.buffer_font_size(cx).into(),
24013 font_weight: settings.buffer_font.weight,
24014 line_height: relative(settings.buffer_line_height.value()),
24015 ..Default::default()
24016 },
24017 };
24018 if let Some(text_style_refinement) = &self.text_style_refinement {
24019 text_style.refine(text_style_refinement)
24020 }
24021
24022 let background = match self.mode {
24023 EditorMode::SingleLine => cx.theme().system().transparent,
24024 EditorMode::AutoHeight { .. } => cx.theme().system().transparent,
24025 EditorMode::Full { .. } => cx.theme().colors().editor_background,
24026 EditorMode::Minimap { .. } => cx.theme().colors().editor_background.opacity(0.7),
24027 };
24028
24029 EditorElement::new(
24030 &cx.entity(),
24031 EditorStyle {
24032 background,
24033 border: cx.theme().colors().border,
24034 local_player: cx.theme().players().local(),
24035 text: text_style,
24036 scrollbar_width: EditorElement::SCROLLBAR_WIDTH,
24037 syntax: cx.theme().syntax().clone(),
24038 status: cx.theme().status().clone(),
24039 inlay_hints_style: make_inlay_hints_style(cx),
24040 edit_prediction_styles: make_suggestion_styles(cx),
24041 unnecessary_code_fade: ThemeSettings::get_global(cx).unnecessary_code_fade,
24042 show_underlines: self.diagnostics_enabled(),
24043 },
24044 )
24045 }
24046}
24047
24048impl EntityInputHandler for Editor {
24049 fn text_for_range(
24050 &mut self,
24051 range_utf16: Range<usize>,
24052 adjusted_range: &mut Option<Range<usize>>,
24053 _: &mut Window,
24054 cx: &mut Context<Self>,
24055 ) -> Option<String> {
24056 let snapshot = self.buffer.read(cx).read(cx);
24057 let start = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.start), Bias::Left);
24058 let end = snapshot.clip_offset_utf16(OffsetUtf16(range_utf16.end), Bias::Right);
24059 if (start.0..end.0) != range_utf16 {
24060 adjusted_range.replace(start.0..end.0);
24061 }
24062 Some(snapshot.text_for_range(start..end).collect())
24063 }
24064
24065 fn selected_text_range(
24066 &mut self,
24067 ignore_disabled_input: bool,
24068 _: &mut Window,
24069 cx: &mut Context<Self>,
24070 ) -> Option<UTF16Selection> {
24071 // Prevent the IME menu from appearing when holding down an alphabetic key
24072 // while input is disabled.
24073 if !ignore_disabled_input && !self.input_enabled {
24074 return None;
24075 }
24076
24077 let selection = self
24078 .selections
24079 .newest::<OffsetUtf16>(&self.display_snapshot(cx));
24080 let range = selection.range();
24081
24082 Some(UTF16Selection {
24083 range: range.start.0..range.end.0,
24084 reversed: selection.reversed,
24085 })
24086 }
24087
24088 fn marked_text_range(&self, _: &mut Window, cx: &mut Context<Self>) -> Option<Range<usize>> {
24089 let snapshot = self.buffer.read(cx).read(cx);
24090 let range = self.text_highlights::<InputComposition>(cx)?.1.first()?;
24091 Some(range.start.to_offset_utf16(&snapshot).0..range.end.to_offset_utf16(&snapshot).0)
24092 }
24093
24094 fn unmark_text(&mut self, _: &mut Window, cx: &mut Context<Self>) {
24095 self.clear_highlights::<InputComposition>(cx);
24096 self.ime_transaction.take();
24097 }
24098
24099 fn replace_text_in_range(
24100 &mut self,
24101 range_utf16: Option<Range<usize>>,
24102 text: &str,
24103 window: &mut Window,
24104 cx: &mut Context<Self>,
24105 ) {
24106 if !self.input_enabled {
24107 cx.emit(EditorEvent::InputIgnored { text: text.into() });
24108 return;
24109 }
24110
24111 self.transact(window, cx, |this, window, cx| {
24112 let new_selected_ranges = if let Some(range_utf16) = range_utf16 {
24113 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24114 Some(this.selection_replacement_ranges(range_utf16, cx))
24115 } else {
24116 this.marked_text_ranges(cx)
24117 };
24118
24119 let range_to_replace = new_selected_ranges.as_ref().and_then(|ranges_to_replace| {
24120 let newest_selection_id = this.selections.newest_anchor().id;
24121 this.selections
24122 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24123 .iter()
24124 .zip(ranges_to_replace.iter())
24125 .find_map(|(selection, range)| {
24126 if selection.id == newest_selection_id {
24127 Some(
24128 (range.start.0 as isize - selection.head().0 as isize)
24129 ..(range.end.0 as isize - selection.head().0 as isize),
24130 )
24131 } else {
24132 None
24133 }
24134 })
24135 });
24136
24137 cx.emit(EditorEvent::InputHandled {
24138 utf16_range_to_replace: range_to_replace,
24139 text: text.into(),
24140 });
24141
24142 if let Some(new_selected_ranges) = new_selected_ranges {
24143 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24144 selections.select_ranges(new_selected_ranges)
24145 });
24146 this.backspace(&Default::default(), window, cx);
24147 }
24148
24149 this.handle_input(text, window, cx);
24150 });
24151
24152 if let Some(transaction) = self.ime_transaction {
24153 self.buffer.update(cx, |buffer, cx| {
24154 buffer.group_until_transaction(transaction, cx);
24155 });
24156 }
24157
24158 self.unmark_text(window, cx);
24159 }
24160
24161 fn replace_and_mark_text_in_range(
24162 &mut self,
24163 range_utf16: Option<Range<usize>>,
24164 text: &str,
24165 new_selected_range_utf16: Option<Range<usize>>,
24166 window: &mut Window,
24167 cx: &mut Context<Self>,
24168 ) {
24169 if !self.input_enabled {
24170 return;
24171 }
24172
24173 let transaction = self.transact(window, cx, |this, window, cx| {
24174 let ranges_to_replace = if let Some(mut marked_ranges) = this.marked_text_ranges(cx) {
24175 let snapshot = this.buffer.read(cx).read(cx);
24176 if let Some(relative_range_utf16) = range_utf16.as_ref() {
24177 for marked_range in &mut marked_ranges {
24178 marked_range.end.0 = marked_range.start.0 + relative_range_utf16.end;
24179 marked_range.start.0 += relative_range_utf16.start;
24180 marked_range.start =
24181 snapshot.clip_offset_utf16(marked_range.start, Bias::Left);
24182 marked_range.end =
24183 snapshot.clip_offset_utf16(marked_range.end, Bias::Right);
24184 }
24185 }
24186 Some(marked_ranges)
24187 } else if let Some(range_utf16) = range_utf16 {
24188 let range_utf16 = OffsetUtf16(range_utf16.start)..OffsetUtf16(range_utf16.end);
24189 Some(this.selection_replacement_ranges(range_utf16, cx))
24190 } else {
24191 None
24192 };
24193
24194 let range_to_replace = ranges_to_replace.as_ref().and_then(|ranges_to_replace| {
24195 let newest_selection_id = this.selections.newest_anchor().id;
24196 this.selections
24197 .all::<OffsetUtf16>(&this.display_snapshot(cx))
24198 .iter()
24199 .zip(ranges_to_replace.iter())
24200 .find_map(|(selection, range)| {
24201 if selection.id == newest_selection_id {
24202 Some(
24203 (range.start.0 as isize - selection.head().0 as isize)
24204 ..(range.end.0 as isize - selection.head().0 as isize),
24205 )
24206 } else {
24207 None
24208 }
24209 })
24210 });
24211
24212 cx.emit(EditorEvent::InputHandled {
24213 utf16_range_to_replace: range_to_replace,
24214 text: text.into(),
24215 });
24216
24217 if let Some(ranges) = ranges_to_replace {
24218 this.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
24219 s.select_ranges(ranges)
24220 });
24221 }
24222
24223 let marked_ranges = {
24224 let snapshot = this.buffer.read(cx).read(cx);
24225 this.selections
24226 .disjoint_anchors_arc()
24227 .iter()
24228 .map(|selection| {
24229 selection.start.bias_left(&snapshot)..selection.end.bias_right(&snapshot)
24230 })
24231 .collect::<Vec<_>>()
24232 };
24233
24234 if text.is_empty() {
24235 this.unmark_text(window, cx);
24236 } else {
24237 this.highlight_text::<InputComposition>(
24238 marked_ranges.clone(),
24239 HighlightStyle {
24240 underline: Some(UnderlineStyle {
24241 thickness: px(1.),
24242 color: None,
24243 wavy: false,
24244 }),
24245 ..Default::default()
24246 },
24247 cx,
24248 );
24249 }
24250
24251 // Disable auto-closing when composing text (i.e. typing a `"` on a Brazilian keyboard)
24252 let use_autoclose = this.use_autoclose;
24253 let use_auto_surround = this.use_auto_surround;
24254 this.set_use_autoclose(false);
24255 this.set_use_auto_surround(false);
24256 this.handle_input(text, window, cx);
24257 this.set_use_autoclose(use_autoclose);
24258 this.set_use_auto_surround(use_auto_surround);
24259
24260 if let Some(new_selected_range) = new_selected_range_utf16 {
24261 let snapshot = this.buffer.read(cx).read(cx);
24262 let new_selected_ranges = marked_ranges
24263 .into_iter()
24264 .map(|marked_range| {
24265 let insertion_start = marked_range.start.to_offset_utf16(&snapshot).0;
24266 let new_start = OffsetUtf16(new_selected_range.start + insertion_start);
24267 let new_end = OffsetUtf16(new_selected_range.end + insertion_start);
24268 snapshot.clip_offset_utf16(new_start, Bias::Left)
24269 ..snapshot.clip_offset_utf16(new_end, Bias::Right)
24270 })
24271 .collect::<Vec<_>>();
24272
24273 drop(snapshot);
24274 this.change_selections(SelectionEffects::no_scroll(), window, cx, |selections| {
24275 selections.select_ranges(new_selected_ranges)
24276 });
24277 }
24278 });
24279
24280 self.ime_transaction = self.ime_transaction.or(transaction);
24281 if let Some(transaction) = self.ime_transaction {
24282 self.buffer.update(cx, |buffer, cx| {
24283 buffer.group_until_transaction(transaction, cx);
24284 });
24285 }
24286
24287 if self.text_highlights::<InputComposition>(cx).is_none() {
24288 self.ime_transaction.take();
24289 }
24290 }
24291
24292 fn bounds_for_range(
24293 &mut self,
24294 range_utf16: Range<usize>,
24295 element_bounds: gpui::Bounds<Pixels>,
24296 window: &mut Window,
24297 cx: &mut Context<Self>,
24298 ) -> Option<gpui::Bounds<Pixels>> {
24299 let text_layout_details = self.text_layout_details(window);
24300 let CharacterDimensions {
24301 em_width,
24302 em_advance,
24303 line_height,
24304 } = self.character_dimensions(window);
24305
24306 let snapshot = self.snapshot(window, cx);
24307 let scroll_position = snapshot.scroll_position();
24308 let scroll_left = scroll_position.x * ScrollOffset::from(em_advance);
24309
24310 let start = OffsetUtf16(range_utf16.start).to_display_point(&snapshot);
24311 let x = Pixels::from(
24312 ScrollOffset::from(
24313 snapshot.x_for_display_point(start, &text_layout_details)
24314 + self.gutter_dimensions.full_width(),
24315 ) - scroll_left,
24316 );
24317 let y = line_height * (start.row().as_f64() - scroll_position.y) as f32;
24318
24319 Some(Bounds {
24320 origin: element_bounds.origin + point(x, y),
24321 size: size(em_width, line_height),
24322 })
24323 }
24324
24325 fn character_index_for_point(
24326 &mut self,
24327 point: gpui::Point<Pixels>,
24328 _window: &mut Window,
24329 _cx: &mut Context<Self>,
24330 ) -> Option<usize> {
24331 let position_map = self.last_position_map.as_ref()?;
24332 if !position_map.text_hitbox.contains(&point) {
24333 return None;
24334 }
24335 let display_point = position_map.point_for_position(point).previous_valid;
24336 let anchor = position_map
24337 .snapshot
24338 .display_point_to_anchor(display_point, Bias::Left);
24339 let utf16_offset = anchor.to_offset_utf16(&position_map.snapshot.buffer_snapshot());
24340 Some(utf16_offset.0)
24341 }
24342
24343 fn accepts_text_input(&self, _window: &mut Window, _cx: &mut Context<Self>) -> bool {
24344 self.input_enabled
24345 }
24346}
24347
24348trait SelectionExt {
24349 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint>;
24350 fn spanned_rows(
24351 &self,
24352 include_end_if_at_line_start: bool,
24353 map: &DisplaySnapshot,
24354 ) -> Range<MultiBufferRow>;
24355}
24356
24357impl<T: ToPoint + ToOffset> SelectionExt for Selection<T> {
24358 fn display_range(&self, map: &DisplaySnapshot) -> Range<DisplayPoint> {
24359 let start = self
24360 .start
24361 .to_point(map.buffer_snapshot())
24362 .to_display_point(map);
24363 let end = self
24364 .end
24365 .to_point(map.buffer_snapshot())
24366 .to_display_point(map);
24367 if self.reversed {
24368 end..start
24369 } else {
24370 start..end
24371 }
24372 }
24373
24374 fn spanned_rows(
24375 &self,
24376 include_end_if_at_line_start: bool,
24377 map: &DisplaySnapshot,
24378 ) -> Range<MultiBufferRow> {
24379 let start = self.start.to_point(map.buffer_snapshot());
24380 let mut end = self.end.to_point(map.buffer_snapshot());
24381 if !include_end_if_at_line_start && start.row != end.row && end.column == 0 {
24382 end.row -= 1;
24383 }
24384
24385 let buffer_start = map.prev_line_boundary(start).0;
24386 let buffer_end = map.next_line_boundary(end).0;
24387 MultiBufferRow(buffer_start.row)..MultiBufferRow(buffer_end.row + 1)
24388 }
24389}
24390
24391impl<T: InvalidationRegion> InvalidationStack<T> {
24392 fn invalidate<S>(&mut self, selections: &[Selection<S>], buffer: &MultiBufferSnapshot)
24393 where
24394 S: Clone + ToOffset,
24395 {
24396 while let Some(region) = self.last() {
24397 let all_selections_inside_invalidation_ranges =
24398 if selections.len() == region.ranges().len() {
24399 selections
24400 .iter()
24401 .zip(region.ranges().iter().map(|r| r.to_offset(buffer)))
24402 .all(|(selection, invalidation_range)| {
24403 let head = selection.head().to_offset(buffer);
24404 invalidation_range.start <= head && invalidation_range.end >= head
24405 })
24406 } else {
24407 false
24408 };
24409
24410 if all_selections_inside_invalidation_ranges {
24411 break;
24412 } else {
24413 self.pop();
24414 }
24415 }
24416 }
24417}
24418
24419impl<T> Default for InvalidationStack<T> {
24420 fn default() -> Self {
24421 Self(Default::default())
24422 }
24423}
24424
24425impl<T> Deref for InvalidationStack<T> {
24426 type Target = Vec<T>;
24427
24428 fn deref(&self) -> &Self::Target {
24429 &self.0
24430 }
24431}
24432
24433impl<T> DerefMut for InvalidationStack<T> {
24434 fn deref_mut(&mut self) -> &mut Self::Target {
24435 &mut self.0
24436 }
24437}
24438
24439impl InvalidationRegion for SnippetState {
24440 fn ranges(&self) -> &[Range<Anchor>] {
24441 &self.ranges[self.active_index]
24442 }
24443}
24444
24445fn edit_prediction_edit_text(
24446 current_snapshot: &BufferSnapshot,
24447 edits: &[(Range<Anchor>, impl AsRef<str>)],
24448 edit_preview: &EditPreview,
24449 include_deletions: bool,
24450 cx: &App,
24451) -> HighlightedText {
24452 let edits = edits
24453 .iter()
24454 .map(|(anchor, text)| (anchor.start.text_anchor..anchor.end.text_anchor, text))
24455 .collect::<Vec<_>>();
24456
24457 edit_preview.highlight_edits(current_snapshot, &edits, include_deletions, cx)
24458}
24459
24460fn edit_prediction_fallback_text(edits: &[(Range<Anchor>, Arc<str>)], cx: &App) -> HighlightedText {
24461 // Fallback for providers that don't provide edit_preview (like Copilot/Supermaven)
24462 // Just show the raw edit text with basic styling
24463 let mut text = String::new();
24464 let mut highlights = Vec::new();
24465
24466 let insertion_highlight_style = HighlightStyle {
24467 color: Some(cx.theme().colors().text),
24468 ..Default::default()
24469 };
24470
24471 for (_, edit_text) in edits {
24472 let start_offset = text.len();
24473 text.push_str(edit_text);
24474 let end_offset = text.len();
24475
24476 if start_offset < end_offset {
24477 highlights.push((start_offset..end_offset, insertion_highlight_style));
24478 }
24479 }
24480
24481 HighlightedText {
24482 text: text.into(),
24483 highlights,
24484 }
24485}
24486
24487pub fn diagnostic_style(severity: lsp::DiagnosticSeverity, colors: &StatusColors) -> Hsla {
24488 match severity {
24489 lsp::DiagnosticSeverity::ERROR => colors.error,
24490 lsp::DiagnosticSeverity::WARNING => colors.warning,
24491 lsp::DiagnosticSeverity::INFORMATION => colors.info,
24492 lsp::DiagnosticSeverity::HINT => colors.info,
24493 _ => colors.ignored,
24494 }
24495}
24496
24497pub fn styled_runs_for_code_label<'a>(
24498 label: &'a CodeLabel,
24499 syntax_theme: &'a theme::SyntaxTheme,
24500) -> impl 'a + Iterator<Item = (Range<usize>, HighlightStyle)> {
24501 let fade_out = HighlightStyle {
24502 fade_out: Some(0.35),
24503 ..Default::default()
24504 };
24505
24506 let mut prev_end = label.filter_range.end;
24507 label
24508 .runs
24509 .iter()
24510 .enumerate()
24511 .flat_map(move |(ix, (range, highlight_id))| {
24512 let style = if let Some(style) = highlight_id.style(syntax_theme) {
24513 style
24514 } else {
24515 return Default::default();
24516 };
24517 let muted_style = style.highlight(fade_out);
24518
24519 let mut runs = SmallVec::<[(Range<usize>, HighlightStyle); 3]>::new();
24520 if range.start >= label.filter_range.end {
24521 if range.start > prev_end {
24522 runs.push((prev_end..range.start, fade_out));
24523 }
24524 runs.push((range.clone(), muted_style));
24525 } else if range.end <= label.filter_range.end {
24526 runs.push((range.clone(), style));
24527 } else {
24528 runs.push((range.start..label.filter_range.end, style));
24529 runs.push((label.filter_range.end..range.end, muted_style));
24530 }
24531 prev_end = cmp::max(prev_end, range.end);
24532
24533 if ix + 1 == label.runs.len() && label.text.len() > prev_end {
24534 runs.push((prev_end..label.text.len(), fade_out));
24535 }
24536
24537 runs
24538 })
24539}
24540
24541pub(crate) fn split_words(text: &str) -> impl std::iter::Iterator<Item = &str> + '_ {
24542 let mut prev_index = 0;
24543 let mut prev_codepoint: Option<char> = None;
24544 text.char_indices()
24545 .chain([(text.len(), '\0')])
24546 .filter_map(move |(index, codepoint)| {
24547 let prev_codepoint = prev_codepoint.replace(codepoint)?;
24548 let is_boundary = index == text.len()
24549 || !prev_codepoint.is_uppercase() && codepoint.is_uppercase()
24550 || !prev_codepoint.is_alphanumeric() && codepoint.is_alphanumeric();
24551 if is_boundary {
24552 let chunk = &text[prev_index..index];
24553 prev_index = index;
24554 Some(chunk)
24555 } else {
24556 None
24557 }
24558 })
24559}
24560
24561pub trait RangeToAnchorExt: Sized {
24562 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor>;
24563
24564 fn to_display_points(self, snapshot: &EditorSnapshot) -> Range<DisplayPoint> {
24565 let anchor_range = self.to_anchors(&snapshot.buffer_snapshot());
24566 anchor_range.start.to_display_point(snapshot)..anchor_range.end.to_display_point(snapshot)
24567 }
24568}
24569
24570impl<T: ToOffset> RangeToAnchorExt for Range<T> {
24571 fn to_anchors(self, snapshot: &MultiBufferSnapshot) -> Range<Anchor> {
24572 let start_offset = self.start.to_offset(snapshot);
24573 let end_offset = self.end.to_offset(snapshot);
24574 if start_offset == end_offset {
24575 snapshot.anchor_before(start_offset)..snapshot.anchor_before(end_offset)
24576 } else {
24577 snapshot.anchor_after(self.start)..snapshot.anchor_before(self.end)
24578 }
24579 }
24580}
24581
24582pub trait RowExt {
24583 fn as_f64(&self) -> f64;
24584
24585 fn next_row(&self) -> Self;
24586
24587 fn previous_row(&self) -> Self;
24588
24589 fn minus(&self, other: Self) -> u32;
24590}
24591
24592impl RowExt for DisplayRow {
24593 fn as_f64(&self) -> f64 {
24594 self.0 as _
24595 }
24596
24597 fn next_row(&self) -> Self {
24598 Self(self.0 + 1)
24599 }
24600
24601 fn previous_row(&self) -> Self {
24602 Self(self.0.saturating_sub(1))
24603 }
24604
24605 fn minus(&self, other: Self) -> u32 {
24606 self.0 - other.0
24607 }
24608}
24609
24610impl RowExt for MultiBufferRow {
24611 fn as_f64(&self) -> f64 {
24612 self.0 as _
24613 }
24614
24615 fn next_row(&self) -> Self {
24616 Self(self.0 + 1)
24617 }
24618
24619 fn previous_row(&self) -> Self {
24620 Self(self.0.saturating_sub(1))
24621 }
24622
24623 fn minus(&self, other: Self) -> u32 {
24624 self.0 - other.0
24625 }
24626}
24627
24628trait RowRangeExt {
24629 type Row;
24630
24631 fn len(&self) -> usize;
24632
24633 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = Self::Row>;
24634}
24635
24636impl RowRangeExt for Range<MultiBufferRow> {
24637 type Row = MultiBufferRow;
24638
24639 fn len(&self) -> usize {
24640 (self.end.0 - self.start.0) as usize
24641 }
24642
24643 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = MultiBufferRow> {
24644 (self.start.0..self.end.0).map(MultiBufferRow)
24645 }
24646}
24647
24648impl RowRangeExt for Range<DisplayRow> {
24649 type Row = DisplayRow;
24650
24651 fn len(&self) -> usize {
24652 (self.end.0 - self.start.0) as usize
24653 }
24654
24655 fn iter_rows(&self) -> impl DoubleEndedIterator<Item = DisplayRow> {
24656 (self.start.0..self.end.0).map(DisplayRow)
24657 }
24658}
24659
24660/// If select range has more than one line, we
24661/// just point the cursor to range.start.
24662fn collapse_multiline_range(range: Range<Point>) -> Range<Point> {
24663 if range.start.row == range.end.row {
24664 range
24665 } else {
24666 range.start..range.start
24667 }
24668}
24669pub struct KillRing(ClipboardItem);
24670impl Global for KillRing {}
24671
24672const UPDATE_DEBOUNCE: Duration = Duration::from_millis(50);
24673
24674enum BreakpointPromptEditAction {
24675 Log,
24676 Condition,
24677 HitCondition,
24678}
24679
24680struct BreakpointPromptEditor {
24681 pub(crate) prompt: Entity<Editor>,
24682 editor: WeakEntity<Editor>,
24683 breakpoint_anchor: Anchor,
24684 breakpoint: Breakpoint,
24685 edit_action: BreakpointPromptEditAction,
24686 block_ids: HashSet<CustomBlockId>,
24687 editor_margins: Arc<Mutex<EditorMargins>>,
24688 _subscriptions: Vec<Subscription>,
24689}
24690
24691impl BreakpointPromptEditor {
24692 const MAX_LINES: u8 = 4;
24693
24694 fn new(
24695 editor: WeakEntity<Editor>,
24696 breakpoint_anchor: Anchor,
24697 breakpoint: Breakpoint,
24698 edit_action: BreakpointPromptEditAction,
24699 window: &mut Window,
24700 cx: &mut Context<Self>,
24701 ) -> Self {
24702 let base_text = match edit_action {
24703 BreakpointPromptEditAction::Log => breakpoint.message.as_ref(),
24704 BreakpointPromptEditAction::Condition => breakpoint.condition.as_ref(),
24705 BreakpointPromptEditAction::HitCondition => breakpoint.hit_condition.as_ref(),
24706 }
24707 .map(|msg| msg.to_string())
24708 .unwrap_or_default();
24709
24710 let buffer = cx.new(|cx| Buffer::local(base_text, cx));
24711 let buffer = cx.new(|cx| MultiBuffer::singleton(buffer, cx));
24712
24713 let prompt = cx.new(|cx| {
24714 let mut prompt = Editor::new(
24715 EditorMode::AutoHeight {
24716 min_lines: 1,
24717 max_lines: Some(Self::MAX_LINES as usize),
24718 },
24719 buffer,
24720 None,
24721 window,
24722 cx,
24723 );
24724 prompt.set_soft_wrap_mode(language::language_settings::SoftWrap::EditorWidth, cx);
24725 prompt.set_show_cursor_when_unfocused(false, cx);
24726 prompt.set_placeholder_text(
24727 match edit_action {
24728 BreakpointPromptEditAction::Log => "Message to log when a breakpoint is hit. Expressions within {} are interpolated.",
24729 BreakpointPromptEditAction::Condition => "Condition when a breakpoint is hit. Expressions within {} are interpolated.",
24730 BreakpointPromptEditAction::HitCondition => "How many breakpoint hits to ignore",
24731 },
24732 window,
24733 cx,
24734 );
24735
24736 prompt
24737 });
24738
24739 Self {
24740 prompt,
24741 editor,
24742 breakpoint_anchor,
24743 breakpoint,
24744 edit_action,
24745 editor_margins: Arc::new(Mutex::new(EditorMargins::default())),
24746 block_ids: Default::default(),
24747 _subscriptions: vec![],
24748 }
24749 }
24750
24751 pub(crate) fn add_block_ids(&mut self, block_ids: Vec<CustomBlockId>) {
24752 self.block_ids.extend(block_ids)
24753 }
24754
24755 fn confirm(&mut self, _: &menu::Confirm, window: &mut Window, cx: &mut Context<Self>) {
24756 if let Some(editor) = self.editor.upgrade() {
24757 let message = self
24758 .prompt
24759 .read(cx)
24760 .buffer
24761 .read(cx)
24762 .as_singleton()
24763 .expect("A multi buffer in breakpoint prompt isn't possible")
24764 .read(cx)
24765 .as_rope()
24766 .to_string();
24767
24768 editor.update(cx, |editor, cx| {
24769 editor.edit_breakpoint_at_anchor(
24770 self.breakpoint_anchor,
24771 self.breakpoint.clone(),
24772 match self.edit_action {
24773 BreakpointPromptEditAction::Log => {
24774 BreakpointEditAction::EditLogMessage(message.into())
24775 }
24776 BreakpointPromptEditAction::Condition => {
24777 BreakpointEditAction::EditCondition(message.into())
24778 }
24779 BreakpointPromptEditAction::HitCondition => {
24780 BreakpointEditAction::EditHitCondition(message.into())
24781 }
24782 },
24783 cx,
24784 );
24785
24786 editor.remove_blocks(self.block_ids.clone(), None, cx);
24787 cx.focus_self(window);
24788 });
24789 }
24790 }
24791
24792 fn cancel(&mut self, _: &menu::Cancel, window: &mut Window, cx: &mut Context<Self>) {
24793 self.editor
24794 .update(cx, |editor, cx| {
24795 editor.remove_blocks(self.block_ids.clone(), None, cx);
24796 window.focus(&editor.focus_handle);
24797 })
24798 .log_err();
24799 }
24800
24801 fn render_prompt_editor(&self, cx: &mut Context<Self>) -> impl IntoElement {
24802 let settings = ThemeSettings::get_global(cx);
24803 let text_style = TextStyle {
24804 color: if self.prompt.read(cx).read_only(cx) {
24805 cx.theme().colors().text_disabled
24806 } else {
24807 cx.theme().colors().text
24808 },
24809 font_family: settings.buffer_font.family.clone(),
24810 font_fallbacks: settings.buffer_font.fallbacks.clone(),
24811 font_size: settings.buffer_font_size(cx).into(),
24812 font_weight: settings.buffer_font.weight,
24813 line_height: relative(settings.buffer_line_height.value()),
24814 ..Default::default()
24815 };
24816 EditorElement::new(
24817 &self.prompt,
24818 EditorStyle {
24819 background: cx.theme().colors().editor_background,
24820 local_player: cx.theme().players().local(),
24821 text: text_style,
24822 ..Default::default()
24823 },
24824 )
24825 }
24826}
24827
24828impl Render for BreakpointPromptEditor {
24829 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24830 let editor_margins = *self.editor_margins.lock();
24831 let gutter_dimensions = editor_margins.gutter;
24832 h_flex()
24833 .key_context("Editor")
24834 .bg(cx.theme().colors().editor_background)
24835 .border_y_1()
24836 .border_color(cx.theme().status().info_border)
24837 .size_full()
24838 .py(window.line_height() / 2.5)
24839 .on_action(cx.listener(Self::confirm))
24840 .on_action(cx.listener(Self::cancel))
24841 .child(h_flex().w(gutter_dimensions.full_width() + (gutter_dimensions.margin / 2.0)))
24842 .child(div().flex_1().child(self.render_prompt_editor(cx)))
24843 }
24844}
24845
24846impl Focusable for BreakpointPromptEditor {
24847 fn focus_handle(&self, cx: &App) -> FocusHandle {
24848 self.prompt.focus_handle(cx)
24849 }
24850}
24851
24852fn all_edits_insertions_or_deletions(
24853 edits: &Vec<(Range<Anchor>, Arc<str>)>,
24854 snapshot: &MultiBufferSnapshot,
24855) -> bool {
24856 let mut all_insertions = true;
24857 let mut all_deletions = true;
24858
24859 for (range, new_text) in edits.iter() {
24860 let range_is_empty = range.to_offset(snapshot).is_empty();
24861 let text_is_empty = new_text.is_empty();
24862
24863 if range_is_empty != text_is_empty {
24864 if range_is_empty {
24865 all_deletions = false;
24866 } else {
24867 all_insertions = false;
24868 }
24869 } else {
24870 return false;
24871 }
24872
24873 if !all_insertions && !all_deletions {
24874 return false;
24875 }
24876 }
24877 all_insertions || all_deletions
24878}
24879
24880struct MissingEditPredictionKeybindingTooltip;
24881
24882impl Render for MissingEditPredictionKeybindingTooltip {
24883 fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
24884 ui::tooltip_container(cx, |container, cx| {
24885 container
24886 .flex_shrink_0()
24887 .max_w_80()
24888 .min_h(rems_from_px(124.))
24889 .justify_between()
24890 .child(
24891 v_flex()
24892 .flex_1()
24893 .text_ui_sm(cx)
24894 .child(Label::new("Conflict with Accept Keybinding"))
24895 .child("Your keymap currently overrides the default accept keybinding. To continue, assign one keybinding for the `editor::AcceptEditPrediction` action.")
24896 )
24897 .child(
24898 h_flex()
24899 .pb_1()
24900 .gap_1()
24901 .items_end()
24902 .w_full()
24903 .child(Button::new("open-keymap", "Assign Keybinding").size(ButtonSize::Compact).on_click(|_ev, window, cx| {
24904 window.dispatch_action(zed_actions::OpenKeymapFile.boxed_clone(), cx)
24905 }))
24906 .child(Button::new("see-docs", "See Docs").size(ButtonSize::Compact).on_click(|_ev, _window, cx| {
24907 cx.open_url("https://zed.dev/docs/completions#edit-predictions-missing-keybinding");
24908 })),
24909 )
24910 })
24911 }
24912}
24913
24914#[derive(Debug, Clone, Copy, PartialEq)]
24915pub struct LineHighlight {
24916 pub background: Background,
24917 pub border: Option<gpui::Hsla>,
24918 pub include_gutter: bool,
24919 pub type_id: Option<TypeId>,
24920}
24921
24922struct LineManipulationResult {
24923 pub new_text: String,
24924 pub line_count_before: usize,
24925 pub line_count_after: usize,
24926}
24927
24928fn render_diff_hunk_controls(
24929 row: u32,
24930 status: &DiffHunkStatus,
24931 hunk_range: Range<Anchor>,
24932 is_created_file: bool,
24933 line_height: Pixels,
24934 editor: &Entity<Editor>,
24935 _window: &mut Window,
24936 cx: &mut App,
24937) -> AnyElement {
24938 h_flex()
24939 .h(line_height)
24940 .mr_1()
24941 .gap_1()
24942 .px_0p5()
24943 .pb_1()
24944 .border_x_1()
24945 .border_b_1()
24946 .border_color(cx.theme().colors().border_variant)
24947 .rounded_b_lg()
24948 .bg(cx.theme().colors().editor_background)
24949 .gap_1()
24950 .block_mouse_except_scroll()
24951 .shadow_md()
24952 .child(if status.has_secondary_hunk() {
24953 Button::new(("stage", row as u64), "Stage")
24954 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24955 .tooltip({
24956 let focus_handle = editor.focus_handle(cx);
24957 move |_window, cx| {
24958 Tooltip::for_action_in(
24959 "Stage Hunk",
24960 &::git::ToggleStaged,
24961 &focus_handle,
24962 cx,
24963 )
24964 }
24965 })
24966 .on_click({
24967 let editor = editor.clone();
24968 move |_event, _window, cx| {
24969 editor.update(cx, |editor, cx| {
24970 editor.stage_or_unstage_diff_hunks(
24971 true,
24972 vec![hunk_range.start..hunk_range.start],
24973 cx,
24974 );
24975 });
24976 }
24977 })
24978 } else {
24979 Button::new(("unstage", row as u64), "Unstage")
24980 .alpha(if status.is_pending() { 0.66 } else { 1.0 })
24981 .tooltip({
24982 let focus_handle = editor.focus_handle(cx);
24983 move |_window, cx| {
24984 Tooltip::for_action_in(
24985 "Unstage Hunk",
24986 &::git::ToggleStaged,
24987 &focus_handle,
24988 cx,
24989 )
24990 }
24991 })
24992 .on_click({
24993 let editor = editor.clone();
24994 move |_event, _window, cx| {
24995 editor.update(cx, |editor, cx| {
24996 editor.stage_or_unstage_diff_hunks(
24997 false,
24998 vec![hunk_range.start..hunk_range.start],
24999 cx,
25000 );
25001 });
25002 }
25003 })
25004 })
25005 .child(
25006 Button::new(("restore", row as u64), "Restore")
25007 .tooltip({
25008 let focus_handle = editor.focus_handle(cx);
25009 move |_window, cx| {
25010 Tooltip::for_action_in("Restore Hunk", &::git::Restore, &focus_handle, cx)
25011 }
25012 })
25013 .on_click({
25014 let editor = editor.clone();
25015 move |_event, window, cx| {
25016 editor.update(cx, |editor, cx| {
25017 let snapshot = editor.snapshot(window, cx);
25018 let point = hunk_range.start.to_point(&snapshot.buffer_snapshot());
25019 editor.restore_hunks_in_ranges(vec![point..point], window, cx);
25020 });
25021 }
25022 })
25023 .disabled(is_created_file),
25024 )
25025 .when(
25026 !editor.read(cx).buffer().read(cx).all_diff_hunks_expanded(),
25027 |el| {
25028 el.child(
25029 IconButton::new(("next-hunk", row as u64), IconName::ArrowDown)
25030 .shape(IconButtonShape::Square)
25031 .icon_size(IconSize::Small)
25032 // .disabled(!has_multiple_hunks)
25033 .tooltip({
25034 let focus_handle = editor.focus_handle(cx);
25035 move |_window, cx| {
25036 Tooltip::for_action_in("Next Hunk", &GoToHunk, &focus_handle, cx)
25037 }
25038 })
25039 .on_click({
25040 let editor = editor.clone();
25041 move |_event, window, cx| {
25042 editor.update(cx, |editor, cx| {
25043 let snapshot = editor.snapshot(window, cx);
25044 let position =
25045 hunk_range.end.to_point(&snapshot.buffer_snapshot());
25046 editor.go_to_hunk_before_or_after_position(
25047 &snapshot,
25048 position,
25049 Direction::Next,
25050 window,
25051 cx,
25052 );
25053 editor.expand_selected_diff_hunks(cx);
25054 });
25055 }
25056 }),
25057 )
25058 .child(
25059 IconButton::new(("prev-hunk", row as u64), IconName::ArrowUp)
25060 .shape(IconButtonShape::Square)
25061 .icon_size(IconSize::Small)
25062 // .disabled(!has_multiple_hunks)
25063 .tooltip({
25064 let focus_handle = editor.focus_handle(cx);
25065 move |_window, cx| {
25066 Tooltip::for_action_in(
25067 "Previous Hunk",
25068 &GoToPreviousHunk,
25069 &focus_handle,
25070 cx,
25071 )
25072 }
25073 })
25074 .on_click({
25075 let editor = editor.clone();
25076 move |_event, window, cx| {
25077 editor.update(cx, |editor, cx| {
25078 let snapshot = editor.snapshot(window, cx);
25079 let point =
25080 hunk_range.start.to_point(&snapshot.buffer_snapshot());
25081 editor.go_to_hunk_before_or_after_position(
25082 &snapshot,
25083 point,
25084 Direction::Prev,
25085 window,
25086 cx,
25087 );
25088 editor.expand_selected_diff_hunks(cx);
25089 });
25090 }
25091 }),
25092 )
25093 },
25094 )
25095 .into_any_element()
25096}
25097
25098pub fn multibuffer_context_lines(cx: &App) -> u32 {
25099 EditorSettings::try_get(cx)
25100 .map(|settings| settings.excerpt_context_lines)
25101 .unwrap_or(2)
25102 .min(32)
25103}